mirror of
https://github.com/withastro/astro.git
synced 2025-01-27 22:19:04 -05:00
fix: avoid dropping tables on production seed
This commit is contained in:
parent
03277b81e1
commit
bab9871dad
3 changed files with 73 additions and 49 deletions
|
@ -1,10 +1,12 @@
|
||||||
import { createClient, type InStatement } from '@libsql/client';
|
import { createClient, type InStatement } from '@libsql/client';
|
||||||
import type { AstroConfig } from 'astro';
|
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 { red } from 'kleur/colors';
|
||||||
import prompts from 'prompts';
|
import prompts from 'prompts';
|
||||||
import type { Arguments } from 'yargs-parser';
|
import type { Arguments } from 'yargs-parser';
|
||||||
import { setupDbTables } from '../../../queries.js';
|
import { recreateTables, seedData } from '../../../queries.js';
|
||||||
import { getManagedAppTokenOrExit } from '../../../tokens.js';
|
import { getManagedAppTokenOrExit } from '../../../tokens.js';
|
||||||
import { collectionsSchema, type AstroConfigWithDB, type DBSnapshot } from '../../../types.js';
|
import { collectionsSchema, type AstroConfigWithDB, type DBSnapshot } from '../../../types.js';
|
||||||
import { getRemoteDatabaseUrl } from '../../../utils.js';
|
import { getRemoteDatabaseUrl } from '../../../utils.js';
|
||||||
|
@ -128,6 +130,8 @@ async function pushSchema({
|
||||||
await runMigrateQuery({ queries, migrations, snapshot: currentSnapshot, appToken, isDryRun });
|
await runMigrateQuery({ queries, migrations, snapshot: currentSnapshot, appToken, isDryRun });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sqlite = new SQLiteAsyncDialect();
|
||||||
|
|
||||||
async function pushData({
|
async function pushData({
|
||||||
config,
|
config,
|
||||||
appToken,
|
appToken,
|
||||||
|
@ -140,13 +144,28 @@ async function pushData({
|
||||||
const queries: InStatement[] = [];
|
const queries: InStatement[] = [];
|
||||||
if (config.db?.data) {
|
if (config.db?.data) {
|
||||||
const libsqlClient = createClient({ url: ':memory:' });
|
const libsqlClient = createClient({ url: ':memory:' });
|
||||||
// Use proxy to trace all queries to queue up in a batch
|
// Stand up tables locally to mirror inserts.
|
||||||
const db = await drizzle(async (sqlQuery, params, method) => {
|
// 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 };
|
const stmt: InStatement = { sql: sqlQuery, args: params };
|
||||||
queries.push(stmt);
|
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);
|
const { rows } = await libsqlClient.execute(stmt);
|
||||||
// Drizzle expects each row as an array of its values
|
|
||||||
const rowValues: unknown[][] = [];
|
const rowValues: unknown[][] = [];
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (row != null && typeof row === 'object') {
|
if (row != null && typeof row === 'object') {
|
||||||
|
@ -158,10 +177,9 @@ async function pushData({
|
||||||
}
|
}
|
||||||
return { rows: rowValues };
|
return { rows: rowValues };
|
||||||
});
|
});
|
||||||
await setupDbTables({
|
await seedData({
|
||||||
db,
|
db,
|
||||||
mode: 'build',
|
mode: 'build',
|
||||||
collections: collectionsSchema.parse(config.db.collections ?? {}),
|
|
||||||
data: config.db.data,
|
data: config.db.data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { dirname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { blue, yellow } from 'kleur/colors';
|
import { blue, yellow } from 'kleur/colors';
|
||||||
import { fileURLIntegration } from './file-url.js';
|
import { fileURLIntegration } from './file-url.js';
|
||||||
import { setupDbTables } from '../queries.js';
|
import { recreateTables, seedData } from '../queries.js';
|
||||||
import { getManagedAppTokenOrExit, type ManagedAppToken } from '../tokens.js';
|
import { getManagedAppTokenOrExit, type ManagedAppToken } from '../tokens.js';
|
||||||
|
|
||||||
function astroDBIntegration(): AstroIntegration {
|
function astroDBIntegration(): AstroIntegration {
|
||||||
|
@ -63,13 +63,15 @@ function astroDBIntegration(): AstroIntegration {
|
||||||
dbUrl: dbUrl.toString(),
|
dbUrl: dbUrl.toString(),
|
||||||
seeding: true,
|
seeding: true,
|
||||||
});
|
});
|
||||||
await setupDbTables({
|
await recreateTables({ db, collections });
|
||||||
db,
|
if (configWithDb.db?.data) {
|
||||||
collections,
|
await seedData({
|
||||||
data: configWithDb.db?.data,
|
db,
|
||||||
logger,
|
data: configWithDb.db.data,
|
||||||
mode: command === 'dev' ? 'dev' : 'build',
|
logger,
|
||||||
});
|
mode: command === 'dev' ? 'dev' : 'build',
|
||||||
|
});
|
||||||
|
}
|
||||||
logger.debug('Database setup complete.');
|
logger.debug('Database setup complete.');
|
||||||
|
|
||||||
dbPlugin = vitePluginDb({
|
dbPlugin = vitePluginDb({
|
||||||
|
|
|
@ -20,19 +20,12 @@ import { isSerializedSQL } from '../runtime/types.js';
|
||||||
|
|
||||||
const sqlite = new SQLiteAsyncDialect();
|
const sqlite = new SQLiteAsyncDialect();
|
||||||
|
|
||||||
export async function setupDbTables({
|
export async function recreateTables({
|
||||||
db,
|
db,
|
||||||
data,
|
|
||||||
collections,
|
collections,
|
||||||
logger,
|
|
||||||
mode,
|
|
||||||
// TODO: Remove once Turso has foreign key PRAGMA support
|
|
||||||
}: {
|
}: {
|
||||||
db: SqliteRemoteDatabase;
|
db: SqliteRemoteDatabase;
|
||||||
data?: DBUserConfig['data'];
|
|
||||||
collections: DBCollections;
|
collections: DBCollections;
|
||||||
logger?: AstroIntegrationLogger;
|
|
||||||
mode: 'dev' | 'build';
|
|
||||||
}) {
|
}) {
|
||||||
const setupQueries: SQL[] = [];
|
const setupQueries: SQL[] = [];
|
||||||
for (const [name, collection] of Object.entries(collections)) {
|
for (const [name, collection] of Object.entries(collections)) {
|
||||||
|
@ -44,30 +37,41 @@ export async function setupDbTables({
|
||||||
for (const q of setupQueries) {
|
for (const q of setupQueries) {
|
||||||
await db.run(q);
|
await db.run(q);
|
||||||
}
|
}
|
||||||
if (data) {
|
}
|
||||||
try {
|
|
||||||
await data({
|
export async function seedData({
|
||||||
seed: async ({ table }, values) => {
|
db,
|
||||||
const result = Array.isArray(values)
|
data,
|
||||||
? db.insert(table).values(values).returning()
|
logger,
|
||||||
: db
|
mode,
|
||||||
.insert(table)
|
}: {
|
||||||
.values(values as any)
|
db: SqliteRemoteDatabase;
|
||||||
.returning()
|
data: DBUserConfig['data'];
|
||||||
.get();
|
logger?: AstroIntegrationLogger;
|
||||||
// Drizzle types don't *quite* line up, and it's tough to debug why.
|
mode: 'dev' | 'build';
|
||||||
// we're casting and calling this close enough :)
|
}) {
|
||||||
return result as any;
|
try {
|
||||||
},
|
await data({
|
||||||
db,
|
seed: async ({ table }, values) => {
|
||||||
mode,
|
const result = Array.isArray(values)
|
||||||
});
|
? db.insert(table).values(values).returning()
|
||||||
} catch (error) {
|
: db
|
||||||
(logger ?? console).error(
|
.insert(table)
|
||||||
`Failed to seed data. Did you update to match recent schema changes?`
|
.values(values as any)
|
||||||
);
|
.returning()
|
||||||
(logger ?? console).error(error as string);
|
.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`
|
// Using `DBField` will not narrow `default` based on the column `type`
|
||||||
// Handle each field separately
|
// Handle each field separately
|
||||||
type WithDefaultDefined<T extends DBField> = T & {
|
type WithDefaultDefined<T extends DBField> = T & {
|
||||||
schema: Required<Pick<T['schema'], 'default'>>
|
schema: Required<Pick<T['schema'], 'default'>>;
|
||||||
};
|
};
|
||||||
type DBFieldWithDefault =
|
type DBFieldWithDefault =
|
||||||
| WithDefaultDefined<TextField>
|
| WithDefaultDefined<TextField>
|
||||||
|
|
Loading…
Add table
Reference in a new issue