0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

db: expose isDbError() utility (#10498)

* feat: expose isDbError

* test: foreign key constraint error detection

* fix(test); use isDbError

* chore: changeset
This commit is contained in:
Ben Holmes 2024-03-20 07:27:48 -04:00 committed by GitHub
parent 19e42c3681
commit f0fc78c873
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 192 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
"@astrojs/db": patch
---
Expose `isDbError()` utility to handle database exceptions when querying.

View file

@ -9,6 +9,7 @@ import type {
TableConfig,
TextColumnOpts,
} from '../core/types.js';
import { LibsqlError } from '@libsql/client';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
@ -24,6 +25,10 @@ function createColumn<S extends string, T extends Record<string, unknown>>(type:
};
}
export function isDbError(err: unknown): err is LibsqlError {
return err instanceof LibsqlError;
}
export const column = {
number: <T extends NumberColumnOpts>(opts: T = {} as T) => {
return createColumn('number', opts) satisfies { type: 'number' };

View file

@ -0,0 +1,42 @@
import { expect } from 'chai';
import { loadFixture } from '../../astro/test/test-utils.js';
const foreignKeyConstraintError =
'LibsqlError: SQLITE_CONSTRAINT_FOREIGNKEY: FOREIGN KEY constraint failed';
describe('astro:db - error handling', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: new URL('./fixtures/error-handling/', import.meta.url),
});
});
describe('development', () => {
let devServer;
before(async () => {
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
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);
});
});
describe('build', () => {
before(async () => {
await fixture.build();
});
it('Raises foreign key constraint LibsqlError', async () => {
const json = await fixture.readFile('/foreign-key-constraint.json');
expect(JSON.parse(json).error).to.equal(foreignKeyConstraintError);
});
});
});

View file

@ -0,0 +1,10 @@
import db from '@astrojs/db';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
integrations: [db()],
devToolbar: {
enabled: false,
},
});

View file

@ -0,0 +1,26 @@
import { column, defineDb, defineTable } from 'astro:db';
const Recipe = defineTable({
columns: {
id: column.number({ primaryKey: true }),
title: column.text(),
description: column.text(),
},
});
const Ingredient = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
quantity: column.number(),
recipeId: column.number(),
},
indexes: {
recipeIdx: { on: 'recipeId' },
},
foreignKeys: [{ columns: 'recipeId', references: () => [Recipe.columns.id] }],
});
export default defineDb({
tables: { Recipe, Ingredient },
});

View file

@ -0,0 +1,62 @@
import { Ingredient, Recipe, db } from 'astro:db';
export default async function () {
const pancakes = await db
.insert(Recipe)
.values({
title: 'Pancakes',
description: 'A delicious breakfast',
})
.returning()
.get();
await db.insert(Ingredient).values([
{
name: 'Flour',
quantity: 1,
recipeId: pancakes.id,
},
{
name: 'Eggs',
quantity: 2,
recipeId: pancakes.id,
},
{
name: 'Milk',
quantity: 1,
recipeId: pancakes.id,
},
]);
const pizza = await db
.insert(Recipe)
.values({
title: 'Pizza',
description: 'A delicious dinner',
})
.returning()
.get();
await db.insert(Ingredient).values([
{
name: 'Flour',
quantity: 1,
recipeId: pizza.id,
},
{
name: 'Eggs',
quantity: 2,
recipeId: pizza.id,
},
{
name: 'Milk',
quantity: 1,
recipeId: pizza.id,
},
{
name: 'Tomato Sauce',
quantity: 1,
recipeId: pizza.id,
},
]);
}

View file

@ -0,0 +1,14 @@
{
"name": "@test/error-handling",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/db": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,18 @@
import type { APIRoute } from 'astro';
import { db, Ingredient, isDbError } from 'astro:db';
export const GET: APIRoute = async () => {
try {
await db.insert(Ingredient).values({
name: 'Flour',
quantity: 1,
// Trigger foreign key constraint error
recipeId: 42,
});
} catch (e) {
if (isDbError(e)) {
return new Response(JSON.stringify({ error: `LibsqlError: ${e.message}` }));
}
}
return new Response(JSON.stringify({ error: 'Did not raise expected exception' }));
};

View file

@ -11,6 +11,7 @@ declare module 'astro:db' {
export const column: RuntimeConfig['column'];
export const defineDb: RuntimeConfig['defineDb'];
export const defineTable: RuntimeConfig['defineTable'];
export const isDbError: RuntimeConfig['isDbError'];
export const eq: RuntimeConfig['eq'];
export const gt: RuntimeConfig['gt'];

9
pnpm-lock.yaml generated
View file

@ -3921,6 +3921,15 @@ importers:
specifier: workspace:*
version: link:../../../../astro
packages/db/test/fixtures/error-handling:
dependencies:
'@astrojs/db':
specifier: workspace:*
version: link:../../..
astro:
specifier: workspace:*
version: link:../../../../astro
packages/db/test/fixtures/integration-only:
dependencies:
'@astrojs/db':