From d35e47bb10ff63311025a0de479801323fc755cd Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 10 May 2024 18:45:05 -0400 Subject: [PATCH 1/7] fix: use LibsqlError for remote db errors --- packages/db/src/runtime/db-client.ts | 25 ++++++++++++++++--------- packages/db/src/runtime/utils.ts | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index d52af72fce95..cc1d93f5f750 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -4,7 +4,7 @@ import type { LibSQLDatabase } from 'drizzle-orm/libsql'; import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql'; import { type SqliteRemoteDatabase, drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy'; import { z } from 'zod'; -import { AstroDbError, safeFetch } from './utils.js'; +import { DetailedLibsqlError, safeFetch } from './utils.js'; const isWebContainer = !!process.versions?.webcontainer; @@ -65,7 +65,10 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResult = remoteResultSchema.parse(json); } catch (e) { - throw new AstroDbError(await getUnexpectedResponseMessage(res)); + throw new DetailedLibsqlError({ + message: await getUnexpectedResponseMessage(res), + code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, + }); } if (method === 'run') return remoteResult; @@ -107,7 +110,10 @@ export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string const json = await res.json(); remoteResults = z.array(remoteResultSchema).parse(json); } catch (e) { - throw new AstroDbError(await getUnexpectedResponseMessage(res)); + throw new DetailedLibsqlError({ + message: await getUnexpectedResponseMessage(res), + code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, + }); } let results: any[] = []; for (const [idx, rawResult] of remoteResults.entries()) { @@ -153,20 +159,21 @@ const KNOWN_ERROR_CODES = { const getUnexpectedResponseMessage = async (response: Response) => `Unexpected response from remote database:\n(Status ${response.status}) ${await response.text()}`; -async function parseRemoteError(response: Response): Promise { +async function parseRemoteError(response: Response): Promise { let error; try { error = errorSchema.parse(await response.json()).error; } catch (e) { - return new AstroDbError(await getUnexpectedResponseMessage(response)); + return new DetailedLibsqlError({ + message: await getUnexpectedResponseMessage(response), + code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, + }); } // Strip LibSQL error prefixes - let details = - error.details?.replace(/.*SQLite error: /, '') ?? - `(Code ${error.code}) \nError querying remote database.`; + let details = error.details?.replace(/.*SQLite error: /, '') ?? 'Error 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); + return new DetailedLibsqlError({ message: details, code: error.code, hint }); } diff --git a/packages/db/src/runtime/utils.ts b/packages/db/src/runtime/utils.ts index 2fe837d8fad6..0c2e820e0b98 100644 --- a/packages/db/src/runtime/utils.ts +++ b/packages/db/src/runtime/utils.ts @@ -1,3 +1,4 @@ +import { LibsqlError } from '@libsql/client'; import { AstroError } from 'astro/errors'; const isWindows = process?.platform === 'win32'; @@ -25,6 +26,22 @@ export class AstroDbError extends AstroError { name = 'Astro DB Error'; } +export class DetailedLibsqlError extends LibsqlError { + name = 'Astro DB Error'; + hint?: string; + + constructor({ + message, + code, + hint, + rawCode, + cause, + }: { message: string; code: string; hint?: string; rawCode?: number; cause?: Error }) { + super(message, code, rawCode, cause); + this.hint = hint; + } +} + function slash(path: string) { const isExtendedLengthPath = path.startsWith('\\\\?\\'); From 1ddecdb3027f1f39ae4e57a5969e4175e722d6a9 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 13 May 2024 11:18:38 -0400 Subject: [PATCH 2/7] chore: remove unused drizzle.ts --- packages/db/src/runtime/drizzle.ts | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 packages/db/src/runtime/drizzle.ts diff --git a/packages/db/src/runtime/drizzle.ts b/packages/db/src/runtime/drizzle.ts deleted file mode 100644 index d2dfa9e3257b..000000000000 --- a/packages/db/src/runtime/drizzle.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Drizzle utilities we expose directly from `astro:db` -export { - sql, - eq, - gt, - gte, - lt, - lte, - ne, - isNull, - isNotNull, - inArray, - notInArray, - exists, - notExists, - between, - notBetween, - like, - notIlike, - not, - asc, - desc, - and, - or, -} from 'drizzle-orm'; From 7b56cfc247784c391bee5836adb1ea7cc5c7eff7 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 13 May 2024 11:52:49 -0400 Subject: [PATCH 3/7] fix(test): return expected `error` object --- packages/db/test/test-utils.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/db/test/test-utils.js b/packages/db/test/test-utils.js index 8be80a879a7e..9f5ba6d9d85b 100644 --- a/packages/db/test/test-utils.js +++ b/packages/db/test/test-utils.js @@ -1,5 +1,5 @@ import { createServer } from 'node:http'; -import { createClient } from '@libsql/client'; +import { LibsqlError, createClient } from '@libsql/client'; import { z } from 'zod'; import { cli } from '../dist/core/cli/index.js'; import { resolveDbConfig } from '../dist/core/load-file.js'; @@ -112,7 +112,10 @@ function createRemoteDbServer() { res.end( JSON.stringify({ success: false, - message: e.message, + error: { + code: e instanceof LibsqlError ? e.code : 'SQLITE_QUERY_FAILED', + details: e.message, + } }) ); } From 57fd6921b4c63ba9ca3b41e1f80cca6a2ff2bbae Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 13 May 2024 11:58:37 -0400 Subject: [PATCH 4/7] fix: error detail formatting --- packages/db/src/runtime/db-client.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index cc1d93f5f750..3bea14b0a88c 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -157,12 +157,12 @@ const KNOWN_ERROR_CODES = { }; const getUnexpectedResponseMessage = async (response: Response) => - `Unexpected response from remote database:\n(Status ${response.status}) ${await response.text()}`; + `Unexpected response from remote database:\n(Status ${response.status}) ${await response.clone().text()}`; async function parseRemoteError(response: Response): Promise { let error; try { - error = errorSchema.parse(await response.json()).error; + error = errorSchema.parse(await response.clone().json()).error; } catch (e) { return new DetailedLibsqlError({ message: await getUnexpectedResponseMessage(response), @@ -170,7 +170,9 @@ async function parseRemoteError(response: Response): Promise Date: Mon, 13 May 2024 11:58:56 -0400 Subject: [PATCH 5/7] feat(test): error messages with remote adapter --- packages/db/test/error-handling.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/db/test/error-handling.test.js b/packages/db/test/error-handling.test.js index d67bd161b8a4..44aa97fe8151 100644 --- a/packages/db/test/error-handling.test.js +++ b/packages/db/test/error-handling.test.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { loadFixture } from '../../astro/test/test-utils.js'; +import { setupRemoteDbServer } from './test-utils.js'; const foreignKeyConstraintError = 'LibsqlError: SQLITE_CONSTRAINT_FOREIGNKEY: FOREIGN KEY constraint failed'; @@ -29,11 +30,18 @@ describe('astro:db - error handling', () => { }); }); - describe('build', () => { + describe('build --remote', () => { + let remoteDbServer; + before(async () => { + remoteDbServer = await setupRemoteDbServer(fixture.config); await fixture.build(); }); + after(async () => { + await remoteDbServer?.stop(); + }); + it('Raises foreign key constraint LibsqlError', async () => { const json = await fixture.readFile('/foreign-key-constraint.json'); expect(JSON.parse(json).error).to.equal(foreignKeyConstraintError); From 0bb4978ab543d675d919cbb029af77706ad85048 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 13 May 2024 12:09:06 -0400 Subject: [PATCH 6/7] feat(test): add code to test body --- packages/db/test/error-handling.test.js | 10 ++++++++-- .../src/pages/foreign-key-constraint.json.ts | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/db/test/error-handling.test.js b/packages/db/test/error-handling.test.js index 44aa97fe8151..9d40507b2d9e 100644 --- a/packages/db/test/error-handling.test.js +++ b/packages/db/test/error-handling.test.js @@ -26,7 +26,10 @@ describe('astro:db - error handling', () => { it('Raises foreign key constraint LibsqlError', async () => { const json = await fixture.fetch('/foreign-key-constraint.json').then((res) => res.json()); - expect(json.error).to.equal(foreignKeyConstraintError); + expect(json).to.deep.equal({ + message: foreignKeyConstraintError, + code: 'SQLITE_CONSTRAINT_FOREIGNKEY', + }); }); }); @@ -44,7 +47,10 @@ describe('astro:db - error handling', () => { it('Raises foreign key constraint LibsqlError', async () => { const json = await fixture.readFile('/foreign-key-constraint.json'); - expect(JSON.parse(json).error).to.equal(foreignKeyConstraintError); + expect(JSON.parse(json)).to.deep.equal({ + message: foreignKeyConstraintError, + code: 'SQLITE_CONSTRAINT_FOREIGNKEY', + }); }); }); }); diff --git a/packages/db/test/fixtures/error-handling/src/pages/foreign-key-constraint.json.ts b/packages/db/test/fixtures/error-handling/src/pages/foreign-key-constraint.json.ts index 8e6bad8c36fc..358a9a95c65f 100644 --- a/packages/db/test/fixtures/error-handling/src/pages/foreign-key-constraint.json.ts +++ b/packages/db/test/fixtures/error-handling/src/pages/foreign-key-constraint.json.ts @@ -11,8 +11,8 @@ export const GET: APIRoute = async () => { }); } catch (e) { if (isDbError(e)) { - return new Response(JSON.stringify({ error: `LibsqlError: ${e.message}` })); + return new Response(JSON.stringify({ message: `LibsqlError: ${e.message}`, code: e.code })); } } - return new Response(JSON.stringify({ error: 'Did not raise expected exception' })); + return new Response(JSON.stringify({ message: 'Did not raise expected exception' })); }; From 40d7f3a8f56557cace680cd3b0b269e9f63ca96a Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 13 May 2024 12:12:27 -0400 Subject: [PATCH 7/7] chore: changeset --- .changeset/tidy-cows-change.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tidy-cows-change.md diff --git a/.changeset/tidy-cows-change.md b/.changeset/tidy-cows-change.md new file mode 100644 index 000000000000..21438fcbd556 --- /dev/null +++ b/.changeset/tidy-cows-change.md @@ -0,0 +1,5 @@ +--- +"@astrojs/db": patch +--- + +Fix `isDbError()` returning `false` for remote database errors. Astro will now return a `LibsqlError` in development and production.