From 44bafa989af0cc380696bb6381048fc1ee55dd5b Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Fri, 3 May 2024 11:08:50 -0400 Subject: [PATCH 1/2] [db] Fix duplicate calls to recreate tables on startup (#10919) * fix: move recreateTables() to integration hooks * feat: recreate and seed at load, not in virtual runtime * feat: eager build db on startup and seed file change * fix: respect database_file in dbUrl * chore: remove duplicate recreateTables call * chore: remove now self-explanatory comments * fix: remove invalidateModule call for eager loading * feat: respect seed package paths * fix: remove duplicate recreateTables() call * refactor: move recreateTables() to vite-plugin-db * refactor: move queries.ts from runtime/ to core/ * fix: update test import to core/queries * refactor: move executeSeedFile to vite-plugin-db * refactor: extract seeding and recreating to helper fns * chore: changeset * chore: revert connectToStudio refactor * wip: log db url * fix(test): normalize astro_database_file flag for windows * Revert "wip: log db url" This reverts commit 558e2de67a09a611377929b625127c649b8504d6. * Revert "Revert "wip: log db url"" This reverts commit ffd004e00dff485b7bc5ddde0278dde6ff058b9e. * fix: correctly resolve relative paths with unit test * chore: remove unused dbDirPath Co-authored-by: Chris Swithinbank * chore: remove unused import Co-authored-by: Chris Swithinbank * chore: remove unused type Co-authored-by: Chris Swithinbank * fix: remove bad import * [db] Load seed files with vite dev server (#10941) * feat: load seed files with full vite dev server * chore: remove unused export --------- Co-authored-by: Chris Swithinbank --- .changeset/young-pots-brake.md | 6 + .../db/src/core/cli/commands/execute/index.ts | 4 +- .../db/src/core/cli/commands/shell/index.ts | 10 +- packages/db/src/core/cli/migration-queries.ts | 2 +- packages/db/src/core/integration/index.ts | 131 ++++++++++------ .../db/src/core/integration/vite-plugin-db.ts | 140 ++++++++++-------- packages/db/src/core/load-file.ts | 16 +- packages/db/src/{runtime => core}/queries.ts | 8 +- packages/db/src/core/utils.ts | 5 + packages/db/src/runtime/index.ts | 1 - packages/db/src/runtime/seed-local.ts | 59 -------- packages/db/src/runtime/utils.ts | 2 +- packages/db/test/local-prod.test.js | 7 +- packages/db/test/test-utils.js | 2 +- 14 files changed, 211 insertions(+), 182 deletions(-) create mode 100644 .changeset/young-pots-brake.md rename packages/db/src/{runtime => core}/queries.ts (97%) delete mode 100644 packages/db/src/runtime/seed-local.ts diff --git a/.changeset/young-pots-brake.md b/.changeset/young-pots-brake.md new file mode 100644 index 000000000000..33b5e7fd68be --- /dev/null +++ b/.changeset/young-pots-brake.md @@ -0,0 +1,6 @@ +--- +"@astrojs/db": minor +--- + +- Fix duplicate table recreations when you start your dev server. +- Remove eager re-seeding when updating your seed file in development. Seeding still runs on dev server startup for SQLite inspector tools. diff --git a/packages/db/src/core/cli/commands/execute/index.ts b/packages/db/src/core/cli/commands/execute/index.ts index 05a04ad61e85..b596d4c0d3b6 100644 --- a/packages/db/src/core/cli/commands/execute/index.ts +++ b/packages/db/src/core/cli/commands/execute/index.ts @@ -15,7 +15,7 @@ import { } from '../../../integration/vite-plugin-db.js'; import { bundleFile, importBundledFile } from '../../../load-file.js'; import { getManagedAppTokenOrExit } from '../../../tokens.js'; -import { type DBConfig } from '../../../types.js'; +import type { DBConfig } from '../../../types.js'; export async function cmd({ astroConfig, @@ -51,8 +51,6 @@ export async function cmd({ virtualModContents = getLocalVirtualModContents({ tables: dbConfig.tables ?? {}, root: astroConfig.root, - shouldSeed: false, - seedFiles: [], }); } const { code } = await bundleFile({ virtualModContents, root: astroConfig.root, fileUrl }); diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts index 57fb6e959e40..0c1883cd20a6 100644 --- a/packages/db/src/core/cli/commands/shell/index.ts +++ b/packages/db/src/core/cli/commands/shell/index.ts @@ -9,7 +9,8 @@ import { DB_PATH } from '../../../consts.js'; import { SHELL_QUERY_MISSING_ERROR } from '../../../errors.js'; import { getManagedAppTokenOrExit } from '../../../tokens.js'; import type { DBConfigInput } from '../../../types.js'; -import { getRemoteDatabaseUrl } from '../../../utils.js'; +import { getAstroEnv, getRemoteDatabaseUrl } from '../../../utils.js'; +import { normalizeDatabaseUrl } from '../../../../runtime/index.js'; export async function cmd({ flags, @@ -31,7 +32,12 @@ export async function cmd({ await appToken.destroy(); console.log(result); } else { - const db = createLocalDatabaseClient({ dbUrl: new URL(DB_PATH, astroConfig.root).href }); + const { ASTRO_DATABASE_FILE } = getAstroEnv(); + const dbUrl = normalizeDatabaseUrl( + ASTRO_DATABASE_FILE, + new URL(DB_PATH, astroConfig.root).href + ); + const db = createLocalDatabaseClient({ dbUrl }); const result = await db.run(sql.raw(query)); console.log(result); } diff --git a/packages/db/src/core/cli/migration-queries.ts b/packages/db/src/core/cli/migration-queries.ts index d5fe959cb116..94674f9d197b 100644 --- a/packages/db/src/core/cli/migration-queries.ts +++ b/packages/db/src/core/cli/migration-queries.ts @@ -12,7 +12,7 @@ import { getReferencesConfig, hasDefault, schemaTypeToSqlType, -} from '../../runtime/queries.js'; +} from '../queries.js'; import { isSerializedSQL } from '../../runtime/types.js'; import { safeFetch } from '../../runtime/utils.js'; import { MIGRATION_VERSION } from '../consts.js'; diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 2e3c1f2ea174..403d1901b3f7 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -4,9 +4,16 @@ import { fileURLToPath } from 'url'; import type { AstroConfig, AstroIntegration } from 'astro'; import { mkdir, writeFile } from 'fs/promises'; import { blue, yellow } from 'kleur/colors'; -import { loadEnv } from 'vite'; +import { + createServer, + loadEnv, + mergeConfig, + type HMRPayload, + type UserConfig, + type ViteDevServer, +} from 'vite'; import parseArgs from 'yargs-parser'; -import { SEED_DEV_FILE_NAME } from '../../runtime/queries.js'; +import { SEED_DEV_FILE_NAME } from '../queries.js'; import { AstroDbError } from '../../runtime/utils.js'; import { CONFIG_FILE_NAMES, DB_PATH } from '../consts.js'; import { resolveDbConfig } from '../load-file.js'; @@ -14,14 +21,24 @@ import { type ManagedAppToken, getManagedAppTokenOrExit } from '../tokens.js'; import { type VitePlugin, getDbDirectoryUrl } from '../utils.js'; import { fileURLIntegration } from './file-url.js'; import { typegenInternal } from './typegen.js'; -import { type LateSeedFiles, type LateTables, resolved, vitePluginDb } from './vite-plugin-db.js'; +import { + type LateSeedFiles, + type LateTables, + vitePluginDb, + type SeedHandler, + resolved, +} from './vite-plugin-db.js'; import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js'; +import { LibsqlError } from '@libsql/client'; +import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR } from '../errors.js'; function astroDBIntegration(): AstroIntegration { let connectToStudio = false; let configFileDependencies: string[] = []; let root: URL; let appToken: ManagedAppToken | undefined; + // Used during production builds to load seed files. + let tempViteServer: ViteDevServer | undefined; // Make table loading "late" to pass to plugins from `config:setup`, // but load during `config:done` to wait for integrations to settle. @@ -35,6 +52,13 @@ function astroDBIntegration(): AstroIntegration { throw new Error('[astro:db] INTERNAL Seed files not loaded yet'); }, }; + let seedHandler: SeedHandler = { + execute: () => { + throw new Error('[astro:db] INTERNAL Seed handler not loaded yet'); + }, + inProgress: false, + }; + let command: 'dev' | 'build' | 'preview'; let output: AstroConfig['output'] = 'server'; return { @@ -60,6 +84,7 @@ function astroDBIntegration(): AstroIntegration { root: config.root, srcDir: config.srcDir, output: config.output, + seedHandler, }); } else { dbPlugin = vitePluginDb({ @@ -69,6 +94,8 @@ function astroDBIntegration(): AstroIntegration { root: config.root, srcDir: config.srcDir, output: config.output, + logger, + seedHandler, }); } @@ -98,6 +125,9 @@ function astroDBIntegration(): AstroIntegration { await typegenInternal({ tables: tables.get() ?? {}, root: config.root }); }, 'astro:server:setup': async ({ server, logger }) => { + seedHandler.execute = async (fileUrl) => { + await executeSeedFile({ fileUrl, viteServer: server }); + }; const filesToWatch = [ ...CONFIG_FILE_NAMES.map((c) => new URL(c, getDbDirectoryUrl(root))), ...configFileDependencies.map((c) => new URL(c, root)), @@ -118,46 +148,11 @@ function astroDBIntegration(): AstroIntegration { const localSeedPaths = SEED_DEV_FILE_NAME.map( (name) => new URL(name, getDbDirectoryUrl(root)) ); - let seedInFlight = false; - // Load seed file on dev server startup. + // Eager load astro:db module on startup if (seedFiles.get().length || localSeedPaths.find((path) => existsSync(path))) { - loadSeedModule(); - } - const eagerReloadIntegrationSeedPaths = seedFiles - .get() - // Map integration seed paths to URLs, if possible. - // Module paths like `@example/seed` will be ignored - // from eager reloading. - .map((s) => (typeof s === 'string' && s.startsWith('.') ? new URL(s, root) : s)) - .filter((s): s is URL => s instanceof URL); - const eagerReloadSeedPaths = [...eagerReloadIntegrationSeedPaths, ...localSeedPaths]; - server.watcher.on('all', (event, relativeEntry) => { - if (event === 'unlink' || event === 'unlinkDir') return; - // When a seed file changes, load manually - // to track when seeding finishes and log a message. - const entry = new URL(relativeEntry, root); - if (eagerReloadSeedPaths.find((path) => entry.href === path.href)) { - loadSeedModule(); - } - }); - - function loadSeedModule() { - if (seedInFlight) return; - - seedInFlight = true; - const mod = server.moduleGraph.getModuleById(resolved.seedVirtual); - if (mod) server.moduleGraph.invalidateModule(mod); - server - .ssrLoadModule(resolved.seedVirtual) - .then(() => { - logger.info('Seeded database.'); - }) - .catch((e) => { - logger.error(e instanceof Error ? e.message : String(e)); - }) - .finally(() => { - seedInFlight = false; - }); + server.ssrLoadModule(resolved.module).catch((e) => { + logger.error(e instanceof Error ? e.message : String(e)); + }); } }, 100); }, @@ -175,8 +170,15 @@ function astroDBIntegration(): AstroIntegration { logger.info('database: ' + (connectToStudio ? yellow('remote') : blue('local database.'))); }, + 'astro:build:setup': async ({ vite }) => { + tempViteServer = await getTempViteServer({ viteConfig: vite }); + seedHandler.execute = async (fileUrl) => { + await executeSeedFile({ fileUrl, viteServer: tempViteServer! }); + }; + }, 'astro:build:done': async ({}) => { await appToken?.destroy(); + await tempViteServer?.close(); }, }, }; @@ -190,3 +192,48 @@ function databaseFileEnvDefined() { export function integration(): AstroIntegration[] { return [astroDBIntegration(), fileURLIntegration()]; } + +async function executeSeedFile({ + fileUrl, + viteServer, +}: { + fileUrl: URL; + viteServer: ViteDevServer; +}) { + const mod = await viteServer.ssrLoadModule(fileUrl.pathname); + if (typeof mod.default !== 'function') { + throw new AstroDbError(EXEC_DEFAULT_EXPORT_ERROR(fileURLToPath(fileUrl))); + } + try { + await mod.default(); + } catch (e) { + if (e instanceof LibsqlError) { + throw new AstroDbError(EXEC_ERROR(e.message)); + } + throw e; + } +} + +/** + * Inspired by Astro content collection config loader. + */ +async function getTempViteServer({ viteConfig }: { viteConfig: UserConfig }) { + const tempViteServer = await createServer( + mergeConfig(viteConfig, { + server: { middlewareMode: true, hmr: false, watch: null }, + optimizeDeps: { noDiscovery: true }, + ssr: { external: [] }, + logLevel: 'silent', + }) + ); + + const hotSend = tempViteServer.hot.send; + tempViteServer.hot.send = (payload: HMRPayload) => { + if (payload.type === 'error') { + throw payload.err; + } + return hotSend(payload); + }; + + return tempViteServer; +} diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts index df31d527673b..865fcd168086 100644 --- a/packages/db/src/core/integration/vite-plugin-db.ts +++ b/packages/db/src/core/integration/vite-plugin-db.ts @@ -1,16 +1,19 @@ import { fileURLToPath } from 'node:url'; -import type { AstroConfig } from 'astro'; -import { normalizePath } from 'vite'; -import { SEED_DEV_FILE_NAME } from '../../runtime/queries.js'; +import type { AstroConfig, AstroIntegrationLogger } from 'astro'; +import { SEED_DEV_FILE_NAME, getCreateIndexQueries, getCreateTableQuery } from '../queries.js'; import { DB_PATH, RUNTIME_IMPORT, RUNTIME_VIRTUAL_IMPORT, VIRTUAL_MODULE_ID } from '../consts.js'; import type { DBTables } from '../types.js'; -import { type VitePlugin, getDbDirectoryUrl, getRemoteDatabaseUrl } from '../utils.js'; - -const WITH_SEED_VIRTUAL_MODULE_ID = 'astro:db:seed'; +import { type VitePlugin, getDbDirectoryUrl, getRemoteDatabaseUrl, getAstroEnv } from '../utils.js'; +import { createLocalDatabaseClient } from '../../runtime/db-client.js'; +import { type SQL, sql } from 'drizzle-orm'; +import { existsSync } from 'node:fs'; +import { normalizeDatabaseUrl } from '../../runtime/index.js'; +import { getResolvedFileUrl } from '../load-file.js'; +import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; export const resolved = { - virtual: '\0' + VIRTUAL_MODULE_ID, - seedVirtual: '\0' + WITH_SEED_VIRTUAL_MODULE_ID, + module: '\0' + VIRTUAL_MODULE_ID, + importedFromSeedFile: '\0' + VIRTUAL_MODULE_ID + ':seed', }; export type LateTables = { @@ -19,6 +22,10 @@ export type LateTables = { export type LateSeedFiles = { get: () => Array; }; +export type SeedHandler = { + inProgress: boolean; + execute: (fileUrl: URL) => Promise; +}; type VitePluginDBParams = | { @@ -27,7 +34,9 @@ type VitePluginDBParams = seedFiles: LateSeedFiles; srcDir: URL; root: URL; + logger?: AstroIntegrationLogger; output: AstroConfig['output']; + seedHandler: SeedHandler; } | { connectToStudio: true; @@ -36,11 +45,10 @@ type VitePluginDBParams = srcDir: URL; root: URL; output: AstroConfig['output']; + seedHandler: SeedHandler; }; export function vitePluginDb(params: VitePluginDBParams): VitePlugin { - const srcDirPath = normalizePath(fileURLToPath(params.srcDir)); - const dbDirPath = normalizePath(fileURLToPath(getDbDirectoryUrl(params.root))); let command: 'build' | 'serve' = 'build'; return { name: 'astro:db', @@ -48,22 +56,15 @@ export function vitePluginDb(params: VitePluginDBParams): VitePlugin { configResolved(resolvedConfig) { command = resolvedConfig.command; }, - async resolveId(id, rawImporter) { + async resolveId(id) { if (id !== VIRTUAL_MODULE_ID) return; - if (params.connectToStudio) return resolved.virtual; - - const importer = rawImporter ? await this.resolve(rawImporter) : null; - if (!importer) return resolved.virtual; - - if (importer.id.startsWith(srcDirPath) && !importer.id.startsWith(dbDirPath)) { - // Seed only if the importer is in the src directory. - // Otherwise, we may get recursive seed calls (ex. import from db/seed.ts). - return resolved.seedVirtual; + if (params.seedHandler.inProgress) { + return resolved.importedFromSeedFile; } - return resolved.virtual; + return resolved.module; }, async load(id) { - if (id !== resolved.virtual && id !== resolved.seedVirtual) return; + if (id !== resolved.module && id !== resolved.importedFromSeedFile) return; if (params.connectToStudio) { return getStudioVirtualModContents({ @@ -73,11 +74,35 @@ export function vitePluginDb(params: VitePluginDBParams): VitePlugin { output: params.output, }); } + + // When seeding, we resolved to a different virtual module. + // this prevents an infinite loop attempting to rerun seed files. + // Short circuit with the module contents in this case. + if (id === resolved.importedFromSeedFile) { + return getLocalVirtualModContents({ + root: params.root, + tables: params.tables.get(), + }); + } + + await recreateTables(params); + const seedFiles = getResolvedSeedFiles(params); + for await (const seedFile of seedFiles) { + // Use `addWatchFile()` to invalidate the `astro:db` module + // when a seed file changes. + this.addWatchFile(fileURLToPath(seedFile)); + if (existsSync(seedFile)) { + params.seedHandler.inProgress = true; + await params.seedHandler.execute(seedFile); + } + } + if (params.seedHandler.inProgress) { + (params.logger ?? console).info('Seeded database.'); + params.seedHandler.inProgress = false; + } return getLocalVirtualModContents({ root: params.root, tables: params.tables.get(), - seedFiles: params.seedFiles.get(), - shouldSeed: id === resolved.seedVirtual, }); }, }; @@ -90,53 +115,17 @@ export function getConfigVirtualModContents() { export function getLocalVirtualModContents({ tables, root, - seedFiles, - shouldSeed, }: { tables: DBTables; - seedFiles: Array; root: URL; - shouldSeed: boolean; }) { - const userSeedFilePaths = SEED_DEV_FILE_NAME.map( - // Format as /db/[name].ts - // for Vite import.meta.glob - (name) => new URL(name, getDbDirectoryUrl('file:///')).pathname - ); - const resolveId = (id: string) => - id.startsWith('.') ? normalizePath(fileURLToPath(new URL(id, root))) : id; - // Use top-level imports to correctly resolve `astro:db` within seed files. - // Dynamic imports cause a silent build failure, - // potentially because of circular module references. - const integrationSeedImportStatements: string[] = []; - const integrationSeedImportNames: string[] = []; - seedFiles.forEach((pathOrUrl, index) => { - const path = typeof pathOrUrl === 'string' ? resolveId(pathOrUrl) : pathOrUrl.pathname; - const importName = 'integration_seed_' + index; - integrationSeedImportStatements.push(`import ${importName} from ${JSON.stringify(path)};`); - integrationSeedImportNames.push(importName); - }); - const dbUrl = new URL(DB_PATH, root); return ` import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl } from ${RUNTIME_IMPORT}; -${shouldSeed ? `import { seedLocal } from ${RUNTIME_IMPORT};` : ''} -${shouldSeed ? integrationSeedImportStatements.join('\n') : ''} const dbUrl = normalizeDatabaseUrl(import.meta.env.ASTRO_DATABASE_FILE, ${JSON.stringify(dbUrl)}); export const db = createLocalDatabaseClient({ dbUrl }); -${ - shouldSeed - ? `await seedLocal({ - db, - tables: ${JSON.stringify(tables)}, - userSeedGlob: import.meta.glob(${JSON.stringify(userSeedFilePaths)}, { eager: true }), - integrationSeedFunctions: [${integrationSeedImportNames.join(',')}], -});` - : '' -} - export * from ${RUNTIME_VIRTUAL_IMPORT}; ${getStringifiedTableExports(tables)}`; @@ -194,3 +183,34 @@ function getStringifiedTableExports(tables: DBTables) { ) .join('\n'); } + +const sqlite = new SQLiteAsyncDialect(); + +async function recreateTables({ tables, root }: { tables: LateTables; root: URL }) { + const { ASTRO_DATABASE_FILE } = getAstroEnv(); + const dbUrl = normalizeDatabaseUrl(ASTRO_DATABASE_FILE, new URL(DB_PATH, root).href); + const db = createLocalDatabaseClient({ dbUrl }); + const setupQueries: SQL[] = []; + for (const [name, table] of Object.entries(tables.get() ?? {})) { + const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`); + const createQuery = sql.raw(getCreateTableQuery(name, table)); + const indexQueries = getCreateIndexQueries(name, table); + setupQueries.push(dropQuery, createQuery, ...indexQueries.map((s) => sql.raw(s))); + } + await db.batch([ + db.run(sql`pragma defer_foreign_keys=true;`), + ...setupQueries.map((q) => db.run(q)), + ]); +} + +function getResolvedSeedFiles({ + root, + seedFiles, +}: { + root: URL; + seedFiles: LateSeedFiles; +}) { + const localSeedFiles = SEED_DEV_FILE_NAME.map((name) => new URL(name, getDbDirectoryUrl(root))); + const integrationSeedFiles = seedFiles.get().map((s) => getResolvedFileUrl(root, s)); + return [...integrationSeedFiles, ...localSeedFiles]; +} diff --git a/packages/db/src/core/load-file.ts b/packages/db/src/core/load-file.ts index 7bc7387c824c..dd48df928cbd 100644 --- a/packages/db/src/core/load-file.ts +++ b/packages/db/src/core/load-file.ts @@ -10,7 +10,7 @@ import { errorMap } from './integration/error-map.js'; import { getConfigVirtualModContents } from './integration/vite-plugin-db.js'; import { dbConfigSchema } from './schemas.js'; import { type AstroDbIntegration } from './types.js'; -import { getDbDirectoryUrl } from './utils.js'; +import { getAstroEnv, getDbDirectoryUrl } from './utils.js'; const isDbIntegration = (integration: AstroIntegration): integration is AstroDbIntegration => 'astro:db:setup' in integration.hooks; @@ -85,15 +85,17 @@ async function loadUserConfigFile( return await loadAndBundleDbConfigFile({ root, fileUrl: configFileUrl }); } -async function loadIntegrationConfigFile(root: URL, filePathOrUrl: string | URL) { - let fileUrl: URL; +export function getResolvedFileUrl(root: URL, filePathOrUrl: string | URL): URL { if (typeof filePathOrUrl === 'string') { const { resolve } = createRequire(root); const resolvedFilePath = resolve(filePathOrUrl); - fileUrl = pathToFileURL(resolvedFilePath); - } else { - fileUrl = filePathOrUrl; + return pathToFileURL(resolvedFilePath); } + return filePathOrUrl; +} + +async function loadIntegrationConfigFile(root: URL, filePathOrUrl: string | URL) { + const fileUrl = getResolvedFileUrl(root, filePathOrUrl); return await loadAndBundleDbConfigFile({ root, fileUrl }); } @@ -133,6 +135,7 @@ export async function bundleFile({ root: URL; virtualModContents: string; }) { + const { ASTRO_DATABASE_FILE } = getAstroEnv(); const result = await esbuild({ absWorkingDir: process.cwd(), entryPoints: [fileURLToPath(fileUrl)], @@ -147,6 +150,7 @@ export async function bundleFile({ metafile: true, define: { 'import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL': 'undefined', + 'import.meta.env.ASTRO_DATABASE_FILE': JSON.stringify(ASTRO_DATABASE_FILE ?? ''), }, plugins: [ { diff --git a/packages/db/src/runtime/queries.ts b/packages/db/src/core/queries.ts similarity index 97% rename from packages/db/src/runtime/queries.ts rename to packages/db/src/core/queries.ts index f58cb1ce233f..ff4d56a5d124 100644 --- a/packages/db/src/runtime/queries.ts +++ b/packages/db/src/core/queries.ts @@ -10,15 +10,15 @@ import type { JsonColumn, NumberColumn, TextColumn, -} from '../core/types.js'; +} from './types.js'; import { FOREIGN_KEY_DNE_ERROR, FOREIGN_KEY_REFERENCES_EMPTY_ERROR, FOREIGN_KEY_REFERENCES_LENGTH_ERROR, REFERENCE_DNE_ERROR, -} from './errors.js'; -import { hasPrimaryKey } from './index.js'; -import { isSerializedSQL } from './types.js'; +} from '../runtime/errors.js'; +import { hasPrimaryKey } from '../runtime/index.js'; +import { isSerializedSQL } from '../runtime/types.js'; const sqlite = new SQLiteAsyncDialect(); diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index ebc2547b3e2e..d9f1e032386e 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -9,6 +9,11 @@ export function getAstroStudioEnv(envMode = ''): Record<`ASTRO_STUDIO_${string}` return env; } +export function getAstroEnv(envMode = ''): Record<`ASTRO_${string}`, string> { + const env = loadEnv(envMode, process.cwd(), 'ASTRO_'); + return env; +} + export function getRemoteDatabaseUrl(): string { const env = getAstroStudioEnv(); return env.ASTRO_STUDIO_REMOTE_DB_URL || 'https://db.services.astro.build'; diff --git a/packages/db/src/runtime/index.ts b/packages/db/src/runtime/index.ts index 544df372a503..ba157f01da40 100644 --- a/packages/db/src/runtime/index.ts +++ b/packages/db/src/runtime/index.ts @@ -14,7 +14,6 @@ import { pathToFileURL } from './utils.js'; export type { Table } from './types.js'; export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js'; -export { seedLocal } from './seed-local.js'; export function hasPrimaryKey(column: DBColumn) { return 'primaryKey' in column.schema && !!column.schema.primaryKey; diff --git a/packages/db/src/runtime/seed-local.ts b/packages/db/src/runtime/seed-local.ts deleted file mode 100644 index 3fb6c61d0c6b..000000000000 --- a/packages/db/src/runtime/seed-local.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { LibsqlError } from '@libsql/client'; -import { type SQL, sql } from 'drizzle-orm'; -import type { LibSQLDatabase } from 'drizzle-orm/libsql'; -import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; -import { type DBTables } from '../core/types.js'; -import { SEED_DEFAULT_EXPORT_ERROR } from './errors.js'; -import { getCreateIndexQueries, getCreateTableQuery } from './queries.js'; -import { AstroDbError } from './utils.js'; - -const sqlite = new SQLiteAsyncDialect(); - -export async function seedLocal({ - db, - tables, - // Glob all potential seed files to catch renames and deletions. - userSeedGlob, - integrationSeedFunctions, -}: { - db: LibSQLDatabase; - tables: DBTables; - userSeedGlob: Record Promise }>; - integrationSeedFunctions: Array<() => Promise>; -}) { - await recreateTables({ db, tables }); - const seedFunctions: Array<() => Promise> = []; - const seedFilePath = Object.keys(userSeedGlob)[0]; - if (seedFilePath) { - const mod = userSeedGlob[seedFilePath]; - if (!mod.default) throw new AstroDbError(SEED_DEFAULT_EXPORT_ERROR(seedFilePath)); - seedFunctions.push(mod.default); - } - for (const seedFn of integrationSeedFunctions) { - seedFunctions.push(seedFn); - } - for (const seed of seedFunctions) { - try { - await seed(); - } catch (e) { - if (e instanceof LibsqlError) { - throw new AstroDbError(`Failed to seed database:\n${e.message}`); - } - throw e; - } - } -} - -async function recreateTables({ db, tables }: { db: LibSQLDatabase; tables: DBTables }) { - const setupQueries: SQL[] = []; - for (const [name, table] of Object.entries(tables)) { - const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`); - const createQuery = sql.raw(getCreateTableQuery(name, table)); - const indexQueries = getCreateIndexQueries(name, table); - setupQueries.push(dropQuery, createQuery, ...indexQueries.map((s) => sql.raw(s))); - } - await db.batch([ - db.run(sql`pragma defer_foreign_keys=true;`), - ...setupQueries.map((q) => db.run(q)), - ]); -} diff --git a/packages/db/src/runtime/utils.ts b/packages/db/src/runtime/utils.ts index 97ad21ccdfdd..2fe837d8fad6 100644 --- a/packages/db/src/runtime/utils.ts +++ b/packages/db/src/runtime/utils.ts @@ -25,7 +25,7 @@ export class AstroDbError extends AstroError { name = 'Astro DB Error'; } -export default function slash(path: string) { +function slash(path: string) { const isExtendedLengthPath = path.startsWith('\\\\?\\'); if (isExtendedLengthPath) { diff --git a/packages/db/test/local-prod.test.js b/packages/db/test/local-prod.test.js index 3a2793392e8d..ff3b2c633ca5 100644 --- a/packages/db/test/local-prod.test.js +++ b/packages/db/test/local-prod.test.js @@ -2,6 +2,7 @@ import { fileURLToPath } from 'url'; import { expect } from 'chai'; import testAdapter from '../../astro/test/test-adapter.js'; import { loadFixture } from '../../astro/test/test-utils.js'; +import { relative } from 'path'; describe('astro:db local database', () => { let fixture; @@ -32,8 +33,10 @@ describe('astro:db local database', () => { }); }); - describe('build (not remote) with DATABASE_FILE env (file path)', () => { - const prodDbPath = fileURLToPath(new URL('./fixtures/basics/dist/astro.db', import.meta.url)); + describe('build (not remote) with DATABASE_FILE env (relative file path)', () => { + const absoluteFileUrl = new URL('./fixtures/basics/dist/astro.db', import.meta.url); + const prodDbPath = relative(process.cwd(), fileURLToPath(absoluteFileUrl)); + before(async () => { process.env.ASTRO_DATABASE_FILE = prodDbPath; await fixture.build(); diff --git a/packages/db/test/test-utils.js b/packages/db/test/test-utils.js index 2ff59e552e44..8be80a879a7e 100644 --- a/packages/db/test/test-utils.js +++ b/packages/db/test/test-utils.js @@ -3,7 +3,7 @@ import { createClient } from '@libsql/client'; import { z } from 'zod'; import { cli } from '../dist/core/cli/index.js'; import { resolveDbConfig } from '../dist/core/load-file.js'; -import { getCreateIndexQueries, getCreateTableQuery } from '../dist/runtime/queries.js'; +import { getCreateIndexQueries, getCreateTableQuery } from '../dist/core/queries.js'; const singleQuerySchema = z.object({ sql: z.string(), From befbda7fa3d712388789a5a9be1e0597834f86db Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Fri, 3 May 2024 15:09:39 +0000 Subject: [PATCH 2/2] [ci] format --- packages/db/src/core/cli/commands/shell/index.ts | 2 +- packages/db/src/core/cli/migration-queries.ts | 8 ++++---- packages/db/src/core/integration/index.ts | 14 +++++++------- .../db/src/core/integration/vite-plugin-db.ts | 14 +++++++------- packages/db/src/core/queries.ts | 16 ++++++++-------- packages/db/test/local-prod.test.js | 2 +- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts index 0c1883cd20a6..c93a127fa99c 100644 --- a/packages/db/src/core/cli/commands/shell/index.ts +++ b/packages/db/src/core/cli/commands/shell/index.ts @@ -5,12 +5,12 @@ import { createLocalDatabaseClient, createRemoteDatabaseClient, } from '../../../../runtime/db-client.js'; +import { normalizeDatabaseUrl } from '../../../../runtime/index.js'; import { DB_PATH } from '../../../consts.js'; import { SHELL_QUERY_MISSING_ERROR } from '../../../errors.js'; import { getManagedAppTokenOrExit } from '../../../tokens.js'; import type { DBConfigInput } from '../../../types.js'; import { getAstroEnv, getRemoteDatabaseUrl } from '../../../utils.js'; -import { normalizeDatabaseUrl } from '../../../../runtime/index.js'; export async function cmd({ flags, diff --git a/packages/db/src/core/cli/migration-queries.ts b/packages/db/src/core/cli/migration-queries.ts index 94674f9d197b..f00d194bf71f 100644 --- a/packages/db/src/core/cli/migration-queries.ts +++ b/packages/db/src/core/cli/migration-queries.ts @@ -4,6 +4,10 @@ import * as color from 'kleur/colors'; import { customAlphabet } from 'nanoid'; import stripAnsi from 'strip-ansi'; import { hasPrimaryKey } from '../../runtime/index.js'; +import { isSerializedSQL } from '../../runtime/types.js'; +import { safeFetch } from '../../runtime/utils.js'; +import { MIGRATION_VERSION } from '../consts.js'; +import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from '../errors.js'; import { getCreateIndexQueries, getCreateTableQuery, @@ -13,10 +17,6 @@ import { hasDefault, schemaTypeToSqlType, } from '../queries.js'; -import { isSerializedSQL } from '../../runtime/types.js'; -import { safeFetch } from '../../runtime/utils.js'; -import { MIGRATION_VERSION } from '../consts.js'; -import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from '../errors.js'; import { columnSchema } from '../schemas.js'; import { type BooleanColumn, diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 403d1901b3f7..99b69bed6551 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -1,22 +1,24 @@ import { existsSync } from 'fs'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; +import { LibsqlError } from '@libsql/client'; import type { AstroConfig, AstroIntegration } from 'astro'; import { mkdir, writeFile } from 'fs/promises'; import { blue, yellow } from 'kleur/colors'; import { - createServer, - loadEnv, - mergeConfig, type HMRPayload, type UserConfig, type ViteDevServer, + createServer, + loadEnv, + mergeConfig, } from 'vite'; import parseArgs from 'yargs-parser'; -import { SEED_DEV_FILE_NAME } from '../queries.js'; import { AstroDbError } from '../../runtime/utils.js'; import { CONFIG_FILE_NAMES, DB_PATH } from '../consts.js'; +import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR } from '../errors.js'; import { resolveDbConfig } from '../load-file.js'; +import { SEED_DEV_FILE_NAME } from '../queries.js'; import { type ManagedAppToken, getManagedAppTokenOrExit } from '../tokens.js'; import { type VitePlugin, getDbDirectoryUrl } from '../utils.js'; import { fileURLIntegration } from './file-url.js'; @@ -24,13 +26,11 @@ import { typegenInternal } from './typegen.js'; import { type LateSeedFiles, type LateTables, - vitePluginDb, type SeedHandler, resolved, + vitePluginDb, } from './vite-plugin-db.js'; import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js'; -import { LibsqlError } from '@libsql/client'; -import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR } from '../errors.js'; function astroDBIntegration(): AstroIntegration { let connectToStudio = false; diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts index 865fcd168086..cd334b4deb7c 100644 --- a/packages/db/src/core/integration/vite-plugin-db.ts +++ b/packages/db/src/core/integration/vite-plugin-db.ts @@ -1,15 +1,15 @@ +import { existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import type { AstroConfig, AstroIntegrationLogger } from 'astro'; -import { SEED_DEV_FILE_NAME, getCreateIndexQueries, getCreateTableQuery } from '../queries.js'; -import { DB_PATH, RUNTIME_IMPORT, RUNTIME_VIRTUAL_IMPORT, VIRTUAL_MODULE_ID } from '../consts.js'; -import type { DBTables } from '../types.js'; -import { type VitePlugin, getDbDirectoryUrl, getRemoteDatabaseUrl, getAstroEnv } from '../utils.js'; -import { createLocalDatabaseClient } from '../../runtime/db-client.js'; import { type SQL, sql } from 'drizzle-orm'; -import { existsSync } from 'node:fs'; +import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; +import { createLocalDatabaseClient } from '../../runtime/db-client.js'; import { normalizeDatabaseUrl } from '../../runtime/index.js'; +import { DB_PATH, RUNTIME_IMPORT, RUNTIME_VIRTUAL_IMPORT, VIRTUAL_MODULE_ID } from '../consts.js'; import { getResolvedFileUrl } from '../load-file.js'; -import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; +import { SEED_DEV_FILE_NAME, getCreateIndexQueries, getCreateTableQuery } from '../queries.js'; +import type { DBTables } from '../types.js'; +import { type VitePlugin, getAstroEnv, getDbDirectoryUrl, getRemoteDatabaseUrl } from '../utils.js'; export const resolved = { module: '\0' + VIRTUAL_MODULE_ID, diff --git a/packages/db/src/core/queries.ts b/packages/db/src/core/queries.ts index ff4d56a5d124..200476c14843 100644 --- a/packages/db/src/core/queries.ts +++ b/packages/db/src/core/queries.ts @@ -1,6 +1,14 @@ import { type SQL } from 'drizzle-orm'; import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'; import { bold } from 'kleur/colors'; +import { + FOREIGN_KEY_DNE_ERROR, + FOREIGN_KEY_REFERENCES_EMPTY_ERROR, + FOREIGN_KEY_REFERENCES_LENGTH_ERROR, + REFERENCE_DNE_ERROR, +} from '../runtime/errors.js'; +import { hasPrimaryKey } from '../runtime/index.js'; +import { isSerializedSQL } from '../runtime/types.js'; import type { BooleanColumn, ColumnType, @@ -11,14 +19,6 @@ import type { NumberColumn, TextColumn, } from './types.js'; -import { - FOREIGN_KEY_DNE_ERROR, - FOREIGN_KEY_REFERENCES_EMPTY_ERROR, - FOREIGN_KEY_REFERENCES_LENGTH_ERROR, - REFERENCE_DNE_ERROR, -} from '../runtime/errors.js'; -import { hasPrimaryKey } from '../runtime/index.js'; -import { isSerializedSQL } from '../runtime/types.js'; const sqlite = new SQLiteAsyncDialect(); diff --git a/packages/db/test/local-prod.test.js b/packages/db/test/local-prod.test.js index ff3b2c633ca5..4796bfc9beb4 100644 --- a/packages/db/test/local-prod.test.js +++ b/packages/db/test/local-prod.test.js @@ -1,8 +1,8 @@ +import { relative } from 'path'; import { fileURLToPath } from 'url'; import { expect } from 'chai'; import testAdapter from '../../astro/test/test-adapter.js'; import { loadFixture } from '../../astro/test/test-utils.js'; -import { relative } from 'path'; describe('astro:db local database', () => { let fixture;