From aab8d764dffae8218e4c4786e32fb16df2905024 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 14:03:53 -0400 Subject: [PATCH 1/7] feat: clear error messages on remote db error --- packages/db/src/runtime/db-client.ts | 58 +++++++++++++++++++--------- packages/db/src/utils.ts | 6 +++ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index 50e1861f403e..a3948f7191ba 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -5,6 +5,7 @@ import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql'; import { type SqliteRemoteDatabase, drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy'; import { z } from 'zod'; import { safeFetch } from './utils.js'; +import { AstroDbError } from '../utils.js'; const isWebContainer = !!process.versions?.webcontainer; @@ -55,10 +56,8 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string }, body: JSON.stringify(requestBody), }, - (response) => { - throw new Error( - `Failed to execute query.\nQuery: ${sql}\nFull error: ${response.status} ${response.statusText}` - ); + async (response) => { + throw await parseRemoteError(response); } ); @@ -67,11 +66,7 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResult = remoteResultSchema.parse(json); } catch (e) { - throw new Error( - `Failed to execute query.\nQuery: ${sql}\nFull error: Unexpected JSON response. ${ - e instanceof Error ? e.message : String(e) - }` - ); + throw new AstroDbError(await getUnexpectedErrorMessage(res)); } if (method === 'run') return remoteResult; @@ -103,10 +98,8 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string }, body: JSON.stringify(stmts), }, - (response) => { - throw new Error( - `Failed to execute batch queries.\nFull error: ${response.status} ${response.statusText}}` - ); + async (response) => { + throw await parseRemoteError(response); } ); @@ -115,11 +108,7 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResults = z.array(remoteResultSchema).parse(json); } catch (e) { - throw new Error( - `Failed to execute batch queries.\nFull error: Unexpected JSON response. ${ - e instanceof Error ? e.message : String(e) - }` - ); + throw new AstroDbError(await getUnexpectedErrorMessage(res)); } let results: any[] = []; for (const [idx, rawResult] of remoteResults.entries()) { @@ -149,3 +138,36 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string applyTransactionNotSupported(db); return db; } + +const errorSchema = z.object({ + success: z.boolean(), + error: z.object({ + code: z.string(), + details: z.string().optional(), + }), +}); + +const KNOWN_ERROR_CODES = { + SQL_QUERY_FAILED: 'SQL_QUERY_FAILED', +}; + +const getUnexpectedErrorMessage = async (response: Response) => + `Unexpected response from remote database:\n(Status ${response.status}) ${await response.text()}`; + +async function parseRemoteError(response: Response): Promise { + let error; + try { + error = errorSchema.parse(await response.json()).error; + } catch (e) { + return new AstroDbError(await getUnexpectedErrorMessage(response)); + } + // Strip LibSQL error prefixes + let details = + error.details?.replace(/.*SQLite error: /, '') ?? + `(Code ${error.code}) \nError querying remote database.`; + let hint = `See the Astro DB guide for query and push instructions: https://docs.astro.build/en/guides/astro-db/#query-your-database`; + if (error.code === KNOWN_ERROR_CODES.SQL_QUERY_FAILED && details.includes('no such table')) { + hint = `Did you run \`astro db push\` to push your latest table schemas?`; + } + return new AstroDbError(details, hint); +} diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index 4e1a18685ebf..1e7b00b12698 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -1,2 +1,8 @@ +import { AstroError } from 'astro/errors'; + export { defineDbIntegration } from './core/utils.js'; export { asDrizzleTable } from './runtime/index.js'; + +export class AstroDbError extends AstroError { + name = 'Astro DB Error'; +} From 49b9c45254714dd27e11a01704791d17ea557597 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 14:04:24 -0400 Subject: [PATCH 2/7] refactor: use AstroDbError for correct error name --- packages/db/src/core/integration/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 21da2f761658..5c001bd46702 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -2,7 +2,6 @@ import { existsSync } from 'fs'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import type { AstroConfig, AstroIntegration } from 'astro'; -import { AstroError } from 'astro/errors'; import { mkdir, writeFile } from 'fs/promises'; import { blue, yellow } from 'kleur/colors'; import { loadEnv } from 'vite'; @@ -16,6 +15,7 @@ import { fileURLIntegration } from './file-url.js'; import { typegenInternal } from './typegen.js'; import { type LateSeedFiles, type LateTables, resolved, vitePluginDb } from './vite-plugin-db.js'; import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js'; +import { AstroDbError } from '../../utils.js'; function astroDBIntegration(): AstroIntegration { let connectToStudio = false; @@ -161,7 +161,7 @@ function astroDBIntegration(): AstroIntegration { const message = `Attempting to build without the --remote flag or the ASTRO_DATABASE_FILE environment variable defined. You probably want to pass --remote to astro build.`; const hint = 'Learn more connecting to Studio: https://docs.astro.build/en/guides/astro-db/#connect-to-astro-studio'; - throw new AstroError(message, hint); + throw new AstroDbError(message, hint); } logger.info('database: ' + (connectToStudio ? yellow('remote') : blue('local database.'))); From 98c2f3f814918f8729efa2f5f0996a1cf9ad22a3 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 14:10:42 -0400 Subject: [PATCH 3/7] refactor: errorMessage -> responseMessage --- packages/db/src/runtime/db-client.ts | 8 ++++---- packages/db/test/fixtures/basics/db/seed.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index a3948f7191ba..4518af1744ca 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -66,7 +66,7 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResult = remoteResultSchema.parse(json); } catch (e) { - throw new AstroDbError(await getUnexpectedErrorMessage(res)); + throw new AstroDbError(await getUnexpectedResponseMessage(res)); } if (method === 'run') return remoteResult; @@ -108,7 +108,7 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResults = z.array(remoteResultSchema).parse(json); } catch (e) { - throw new AstroDbError(await getUnexpectedErrorMessage(res)); + throw new AstroDbError(await getUnexpectedResponseMessage(res)); } let results: any[] = []; for (const [idx, rawResult] of remoteResults.entries()) { @@ -151,7 +151,7 @@ const KNOWN_ERROR_CODES = { SQL_QUERY_FAILED: 'SQL_QUERY_FAILED', }; -const getUnexpectedErrorMessage = async (response: Response) => +const getUnexpectedResponseMessage = async (response: Response) => `Unexpected response from remote database:\n(Status ${response.status}) ${await response.text()}`; async function parseRemoteError(response: Response): Promise { @@ -159,7 +159,7 @@ async function parseRemoteError(response: Response): Promise { try { error = errorSchema.parse(await response.json()).error; } catch (e) { - return new AstroDbError(await getUnexpectedErrorMessage(response)); + return new AstroDbError(await getUnexpectedResponseMessage(response)); } // Strip LibSQL error prefixes let details = diff --git a/packages/db/test/fixtures/basics/db/seed.ts b/packages/db/test/fixtures/basics/db/seed.ts index ec5ab2e0c274..46bcc1696d6b 100644 --- a/packages/db/test/fixtures/basics/db/seed.ts +++ b/packages/db/test/fixtures/basics/db/seed.ts @@ -19,6 +19,6 @@ export default async function () { { name: 'Sarah' }, ]), db.insert(User).values([{ id: 'mario', username: 'Mario', password: 'itsame' }]), - db.insert(Session).values([{ id: '12345', expiresAt: new Date().valueOf(), userId: 'mario' }]), + db.insert(Session).values([{ id: '12345', expiresAt: new Date().valueOf(), userId: 'fake' }]), ]); } From 65d4d0b5bdf6aee68a438ef06ee92eaf63cd345c Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 14:14:04 -0400 Subject: [PATCH 4/7] chore: changeset --- .changeset/plenty-lobsters-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/plenty-lobsters-design.md diff --git a/.changeset/plenty-lobsters-design.md b/.changeset/plenty-lobsters-design.md new file mode 100644 index 000000000000..5f2a6bb39bd9 --- /dev/null +++ b/.changeset/plenty-lobsters-design.md @@ -0,0 +1,5 @@ +--- +"@astrojs/db": patch +--- + +Detailed error messages for remote database exceptions. From 20b513938bb69beb5bf17ffad8aad707e026297f Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 16:26:31 -0400 Subject: [PATCH 5/7] fix: revert seed file change --- packages/db/test/fixtures/basics/db/seed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/test/fixtures/basics/db/seed.ts b/packages/db/test/fixtures/basics/db/seed.ts index 46bcc1696d6b..ec5ab2e0c274 100644 --- a/packages/db/test/fixtures/basics/db/seed.ts +++ b/packages/db/test/fixtures/basics/db/seed.ts @@ -19,6 +19,6 @@ export default async function () { { name: 'Sarah' }, ]), db.insert(User).values([{ id: 'mario', username: 'Mario', password: 'itsame' }]), - db.insert(Session).values([{ id: '12345', expiresAt: new Date().valueOf(), userId: 'fake' }]), + db.insert(Session).values([{ id: '12345', expiresAt: new Date().valueOf(), userId: 'mario' }]), ]); } From 009c10a6a781f16be2897997247de7660c481f87 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 16:39:28 -0400 Subject: [PATCH 6/7] fix: format seed errors as AstroDbError --- packages/db/src/runtime/errors.ts | 6 +----- packages/db/src/runtime/seed-local.ts | 7 ++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/db/src/runtime/errors.ts b/packages/db/src/runtime/errors.ts index 2026e57e975d..51febbff47b4 100644 --- a/packages/db/src/runtime/errors.ts +++ b/packages/db/src/runtime/errors.ts @@ -24,10 +24,6 @@ export const REFERENCE_DNE_ERROR = (columnName: string) => { )} references a table that does not exist. Did you apply the referenced table to the \`tables\` object in your db config?`; }; -export const SEED_ERROR = (error: string) => { - return `${red(`Error while seeding database:`)}\n\n${error}`; -}; - export const SEED_DEFAULT_EXPORT_ERROR = (fileName: string) => { - return SEED_ERROR(`Missing default function export in ${bold(fileName)}`); + return `Missing default function export in ${bold(fileName)}`; }; diff --git a/packages/db/src/runtime/seed-local.ts b/packages/db/src/runtime/seed-local.ts index e4d8064ed156..ee6492380e8b 100644 --- a/packages/db/src/runtime/seed-local.ts +++ b/packages/db/src/runtime/seed-local.ts @@ -3,8 +3,9 @@ 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, SEED_ERROR } from './errors.js'; +import { SEED_DEFAULT_EXPORT_ERROR } from './errors.js'; import { getCreateIndexQueries, getCreateTableQuery } from './queries.js'; +import { AstroDbError } from '../utils.js'; const sqlite = new SQLiteAsyncDialect(); @@ -25,7 +26,7 @@ export async function seedLocal({ const seedFilePath = Object.keys(userSeedGlob)[0]; if (seedFilePath) { const mod = userSeedGlob[seedFilePath]; - if (!mod.default) throw new Error(SEED_DEFAULT_EXPORT_ERROR(seedFilePath)); + if (!mod.default) throw new AstroDbError(SEED_DEFAULT_EXPORT_ERROR(seedFilePath)); seedFunctions.push(mod.default); } for (const seedFn of integrationSeedFunctions) { @@ -36,7 +37,7 @@ export async function seedLocal({ await seed(); } catch (e) { if (e instanceof LibsqlError) { - throw new Error(SEED_ERROR(e.message)); + throw new AstroDbError(`Failed to seed database:\n${e.message}`); } throw e; } From 561290285d950c34081e2ee2af59e9a77f72202c Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 1 Apr 2024 16:39:39 -0400 Subject: [PATCH 7/7] fix: correctly log eager seed errors --- packages/db/src/core/integration/index.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 5c001bd46702..986b2f84c6ed 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -145,10 +145,17 @@ function astroDBIntegration(): AstroIntegration { seedInFlight = true; const mod = server.moduleGraph.getModuleById(resolved.seedVirtual); if (mod) server.moduleGraph.invalidateModule(mod); - server.ssrLoadModule(resolved.seedVirtual).then(() => { - seedInFlight = false; - logger.info('Seeded database.'); - }); + server + .ssrLoadModule(resolved.seedVirtual) + .then(() => { + logger.info('Seeded database.'); + }) + .catch((e) => { + logger.error(e instanceof Error ? e.message : String(e)); + }) + .finally(() => { + seedInFlight = false; + }); } }, 100); },