0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-03 22:57:08 -05:00

cleanup primary key support, db push

This commit is contained in:
Fred K. Schott 2024-01-25 03:00:56 -08:00
parent f249be392f
commit 2c897aea2a
3 changed files with 23 additions and 31 deletions

View file

@ -7,13 +7,14 @@ import type { Arguments } from 'yargs-parser';
import { appTokenError } from '../../../errors.js'; import { appTokenError } from '../../../errors.js';
import { collectionToTable, createLocalDatabaseClient } from '../../../internal.js'; import { collectionToTable, createLocalDatabaseClient } from '../../../internal.js';
import { import {
createCurrentSnapshot,
createEmptySnapshot, createEmptySnapshot,
getMigrations, getMigrations,
initializeFromMigrations, initializeFromMigrations,
loadInitialSnapshot, loadInitialSnapshot,
loadMigration, loadMigration,
} from '../../../migrations.js'; } from '../../../migrations.js';
import type { DBCollections } from '../../../types.js'; import type { DBCollections, DBSnapshot } from '../../../types.js';
import { import {
STUDIO_ADMIN_TABLE_ROW_ID, STUDIO_ADMIN_TABLE_ROW_ID,
adminTable, adminTable,
@ -30,9 +31,7 @@ export async function cmd({ config, flags }: { config: AstroConfig; flags: Argum
const isSeedData = flags.seed; const isSeedData = flags.seed;
const isDryRun = flags.dryRun; const isDryRun = flags.dryRun;
const appToken = flags.token ?? getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN; const appToken = flags.token ?? getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
const currentSnapshot = createCurrentSnapshot(config);
const currentDb: DBCollections = (config.db?.collections ?? {}) as DBCollections;
const currentSnapshot = JSON.parse(JSON.stringify(currentDb));
const allMigrationFiles = await getMigrations(); const allMigrationFiles = await getMigrations();
if (allMigrationFiles.length === 0) { if (allMigrationFiles.length === 0) {
console.log('Project not yet initialized!'); console.log('Project not yet initialized!');
@ -43,6 +42,7 @@ export async function cmd({ config, flags }: { config: AstroConfig; flags: Argum
const calculatedDiff = diff(prevSnapshot, currentSnapshot); const calculatedDiff = diff(prevSnapshot, currentSnapshot);
if (calculatedDiff) { if (calculatedDiff) {
console.log('Changes detected!'); console.log('Changes detected!');
console.log(calculatedDiff);
process.exit(1); process.exit(1);
} }
@ -74,7 +74,7 @@ export async function cmd({ config, flags }: { config: AstroConfig; flags: Argum
} }
if (isSeedData) { if (isSeedData) {
console.info('Pushing data...'); console.info('Pushing data...');
await tempDataPush({ currentDb, appToken, isDryRun }); await pushData({ config, appToken, isDryRun });
} }
console.info('Push complete!'); console.info('Push complete!');
} }
@ -90,7 +90,7 @@ async function pushSchema({
appToken: string; appToken: string;
isDryRun: boolean; isDryRun: boolean;
db: ReturnType<typeof createRemoteDatabaseClient>; db: ReturnType<typeof createRemoteDatabaseClient>;
currentSnapshot: DBCollections; currentSnapshot: DBSnapshot;
}) { }) {
// load all missing migrations // load all missing migrations
const initialSnapshot = migrations.find((m) => m === '0000_snapshot.json'); const initialSnapshot = migrations.find((m) => m === '0000_snapshot.json');
@ -120,28 +120,26 @@ async function pushSchema({
} }
/** TODO: refine with migration changes */ /** TODO: refine with migration changes */
async function tempDataPush({ async function pushData({
currentDb, config,
appToken, appToken,
isDryRun, isDryRun,
}: { }: {
currentDb: DBCollections; config: AstroConfig;
appToken: string; appToken: string;
isDryRun?: boolean; isDryRun?: boolean;
}) { }) {
const db = await createLocalDatabaseClient({ const db = await createLocalDatabaseClient({
collections: JSON.parse(JSON.stringify(currentDb)), collections: config.db!.collections! as DBCollections,
dbUrl: ':memory:', dbUrl: ':memory:',
seeding: true, seeding: true,
}); });
const queries: Query[] = []; const queries: Query[] = [];
for (const [name, collection] of Object.entries(currentDb)) { for (const [name, collection] of Object.entries(config.db!.collections! as DBCollections)) {
console.log(name, collection);
if (collection.writable || !collection.data) continue; if (collection.writable || !collection.data) continue;
const table = collectionToTable(name, collection); const table = collectionToTable(name, collection);
const insert = db.insert(table).values(await collection.data()); const insert = db.insert(table).values(await collection.data());
queries.push(insert.toSQL()); queries.push(insert.toSQL());
} }
console.log(queries); console.log(queries);

View file

@ -15,6 +15,7 @@ import type {
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import prompts from 'prompts'; import prompts from 'prompts';
import { hasPrimaryKey } from '../internal.js';
const sqlite = new SQLiteAsyncDialect(); const sqlite = new SQLiteAsyncDialect();
const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10); const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
@ -408,7 +409,7 @@ export function getCreateTableQuery(collectionName: string, collection: DBCollec
([, field]) => hasPrimaryKey(field) ([, field]) => hasPrimaryKey(field)
); );
if (!colHasPrimaryKey) { if (!colHasPrimaryKey) {
colQueries.push('_id INTEGER PRIMARY KEY AUTOINCREMENT'); colQueries.push('_id INTEGER PRIMARY KEY');
} }
for (const [columnName, column] of Object.entries(collection.fields)) { for (const [columnName, column] of Object.entries(collection.fields)) {
const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType( const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType(
@ -570,10 +571,6 @@ type DBFieldWithDefault =
| WithDefaultDefined<BooleanField> | WithDefaultDefined<BooleanField>
| WithDefaultDefined<JsonField>; | WithDefaultDefined<JsonField>;
function hasPrimaryKey(field: DBField) {
return 'primaryKey' in field && !!field.primaryKey;
}
// Type narrowing the default fails on union types, so use a type guard // Type narrowing the default fails on union types, so use a type guard
function hasDefault(field: DBField): field is DBFieldWithDefault { function hasDefault(field: DBField): field is DBFieldWithDefault {
if (field.default !== undefined) { if (field.default !== undefined) {

View file

@ -39,6 +39,10 @@ export type { Table } from './types.js';
const sqlite = new SQLiteAsyncDialect(); const sqlite = new SQLiteAsyncDialect();
export function hasPrimaryKey(field: DBField) {
return 'primaryKey' in field && !!field.primaryKey;
}
function isReadableCollection(collection: DBCollection): collection is ReadableDBCollection { function isReadableCollection(collection: DBCollection): collection is ReadableDBCollection {
return !collection.writable; return !collection.writable;
} }
@ -232,12 +236,6 @@ const jsonType = customType<{ data: unknown; driverData: string }>({
}, },
}); });
const initialColumns = {
id: text('id')
.primaryKey()
.$default(() => generateId()),
};
type D1ColumnBuilder = SQLiteColumnBuilderBase< type D1ColumnBuilder = SQLiteColumnBuilderBase<
ColumnBuilderBaseConfig<ColumnDataType, string> & { data: unknown } ColumnBuilderBaseConfig<ColumnDataType, string> & { data: unknown }
>; >;
@ -247,17 +245,14 @@ export function collectionToTable(
collection: DBCollection, collection: DBCollection,
isJsonSerializable = true isJsonSerializable = true
) { ) {
const columns: Record<string, D1ColumnBuilder> & typeof initialColumns = { const columns: Record<string, D1ColumnBuilder> = {};
// Spread to avoid mutating `initialColumns` if (!Object.entries(collection.fields).some(([, field]) => hasPrimaryKey(field))) {
...initialColumns, columns['_id'] = integer('_id').primaryKey();
}; }
for (const [fieldName, field] of Object.entries(collection.fields)) { for (const [fieldName, field] of Object.entries(collection.fields)) {
columns[fieldName] = columnMapper(fieldName, field, isJsonSerializable); columns[fieldName] = columnMapper(fieldName, field, isJsonSerializable);
} }
const table = sqliteTable(name, columns); const table = sqliteTable(name, columns);
return table; return table;
} }
@ -276,11 +271,13 @@ function columnMapper(fieldName: string, field: DBField, isJsonSerializable: boo
// Duplicate default logic across cases to preserve type inference. // Duplicate default logic across cases to preserve type inference.
// No clean generic for every column builder. // No clean generic for every column builder.
if (field.default !== undefined) c = c.default(field.default); if (field.default !== undefined) c = c.default(field.default);
if (field.primaryKey === true) c = c.primaryKey();
break; break;
} }
case 'number': { case 'number': {
c = integer(fieldName); c = integer(fieldName);
if (field.default !== undefined) c = c.default(field.default); if (field.default !== undefined) c = c.default(field.default);
if (field.primaryKey === true) c = c.primaryKey({autoIncrement: true});
break; break;
} }
case 'boolean': { case 'boolean': {