0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-20 22:12:38 -05:00

fix: avoid dropping tables on production seed

This commit is contained in:
bholmesdev 2024-02-13 14:09:57 -05:00
parent 03277b81e1
commit bab9871dad
3 changed files with 73 additions and 49 deletions

View file

@ -1,10 +1,12 @@
import { createClient, type InStatement } from '@libsql/client';
import type { AstroConfig } from 'astro';
import { drizzle } from 'drizzle-orm/sqlite-proxy';
import { drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy';
import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql';
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
import { red } from 'kleur/colors';
import prompts from 'prompts';
import type { Arguments } from 'yargs-parser';
import { setupDbTables } from '../../../queries.js';
import { recreateTables, seedData } from '../../../queries.js';
import { getManagedAppTokenOrExit } from '../../../tokens.js';
import { collectionsSchema, type AstroConfigWithDB, type DBSnapshot } from '../../../types.js';
import { getRemoteDatabaseUrl } from '../../../utils.js';
@ -128,6 +130,8 @@ async function pushSchema({
await runMigrateQuery({ queries, migrations, snapshot: currentSnapshot, appToken, isDryRun });
}
const sqlite = new SQLiteAsyncDialect();
async function pushData({
config,
appToken,
@ -140,13 +144,28 @@ async function pushData({
const queries: InStatement[] = [];
if (config.db?.data) {
const libsqlClient = createClient({ url: ':memory:' });
// Use proxy to trace all queries to queue up in a batch
const db = await drizzle(async (sqlQuery, params, method) => {
// Stand up tables locally to mirror inserts.
// Needed to generate return values.
await recreateTables({
db: drizzleLibsql(libsqlClient),
collections: collectionsSchema.parse(config.db.collections ?? {}),
});
for (const [collectionName, { writable }] of Object.entries(config.db.collections ?? {})) {
if (!writable) {
queries.push({
sql: `DELETE FROM ${sqlite.escapeName(collectionName)}`,
args: [],
});
}
}
// Use proxy to trace all queries to queue up in a batch.
const db = await drizzleProxy(async (sqlQuery, params, method) => {
const stmt: InStatement = { sql: sqlQuery, args: params };
queries.push(stmt);
// Use in-memory database to generate results for `returning()`
// Use in-memory database to generate results for `returning()`.
const { rows } = await libsqlClient.execute(stmt);
// Drizzle expects each row as an array of its values
const rowValues: unknown[][] = [];
for (const row of rows) {
if (row != null && typeof row === 'object') {
@ -158,10 +177,9 @@ async function pushData({
}
return { rows: rowValues };
});
await setupDbTables({
await seedData({
db,
mode: 'build',
collections: collectionsSchema.parse(config.db.collections ?? {}),
data: config.db.data,
});
}

View file

@ -14,7 +14,7 @@ import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { blue, yellow } from 'kleur/colors';
import { fileURLIntegration } from './file-url.js';
import { setupDbTables } from '../queries.js';
import { recreateTables, seedData } from '../queries.js';
import { getManagedAppTokenOrExit, type ManagedAppToken } from '../tokens.js';
function astroDBIntegration(): AstroIntegration {
@ -63,13 +63,15 @@ function astroDBIntegration(): AstroIntegration {
dbUrl: dbUrl.toString(),
seeding: true,
});
await setupDbTables({
db,
collections,
data: configWithDb.db?.data,
logger,
mode: command === 'dev' ? 'dev' : 'build',
});
await recreateTables({ db, collections });
if (configWithDb.db?.data) {
await seedData({
db,
data: configWithDb.db.data,
logger,
mode: command === 'dev' ? 'dev' : 'build',
});
}
logger.debug('Database setup complete.');
dbPlugin = vitePluginDb({

View file

@ -20,19 +20,12 @@ import { isSerializedSQL } from '../runtime/types.js';
const sqlite = new SQLiteAsyncDialect();
export async function setupDbTables({
export async function recreateTables({
db,
data,
collections,
logger,
mode,
// TODO: Remove once Turso has foreign key PRAGMA support
}: {
db: SqliteRemoteDatabase;
data?: DBUserConfig['data'];
collections: DBCollections;
logger?: AstroIntegrationLogger;
mode: 'dev' | 'build';
}) {
const setupQueries: SQL[] = [];
for (const [name, collection] of Object.entries(collections)) {
@ -44,30 +37,41 @@ export async function setupDbTables({
for (const q of setupQueries) {
await db.run(q);
}
if (data) {
try {
await data({
seed: async ({ table }, values) => {
const result = Array.isArray(values)
? db.insert(table).values(values).returning()
: db
.insert(table)
.values(values as any)
.returning()
.get();
// Drizzle types don't *quite* line up, and it's tough to debug why.
// we're casting and calling this close enough :)
return result as any;
},
db,
mode,
});
} catch (error) {
(logger ?? console).error(
`Failed to seed data. Did you update to match recent schema changes?`
);
(logger ?? console).error(error as string);
}
}
export async function seedData({
db,
data,
logger,
mode,
}: {
db: SqliteRemoteDatabase;
data: DBUserConfig['data'];
logger?: AstroIntegrationLogger;
mode: 'dev' | 'build';
}) {
try {
await data({
seed: async ({ table }, values) => {
const result = Array.isArray(values)
? db.insert(table).values(values).returning()
: db
.insert(table)
.values(values as any)
.returning()
.get();
// Drizzle types don't *quite* line up, and it's tough to debug why.
// we're casting and calling this close enough :)
return result as any;
},
db,
mode,
});
} catch (error) {
(logger ?? console).error(
`Failed to seed data. Did you update to match recent schema changes?`
);
(logger ?? console).error(error as string);
}
}
@ -192,7 +196,7 @@ export function getReferencesConfig(field: DBField) {
// Using `DBField` will not narrow `default` based on the column `type`
// Handle each field separately
type WithDefaultDefined<T extends DBField> = T & {
schema: Required<Pick<T['schema'], 'default'>>
schema: Required<Pick<T['schema'], 'default'>>;
};
type DBFieldWithDefault =
| WithDefaultDefined<TextField>