mirror of
https://github.com/withastro/astro.git
synced 2025-02-03 22:29:08 -05:00
Runtime checking of writable
This commit is contained in:
parent
2efa6b1db9
commit
bb46addb29
7 changed files with 123 additions and 21 deletions
|
@ -22,7 +22,7 @@ export function integration(): AstroIntegration {
|
|||
if (existsSync(dbUrl)) {
|
||||
await rm(dbUrl);
|
||||
}
|
||||
const db = await createDb({ dbUrl: dbUrl.href });
|
||||
const db = await createDb({ collections, dbUrl: dbUrl.href, seeding: true });
|
||||
await setupDbTables({ db, collections, logger });
|
||||
logger.info('Collections set up 🚀');
|
||||
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
|
||||
import { createClient } from '@libsql/client';
|
||||
import type {
|
||||
BooleanField,
|
||||
DBCollection,
|
||||
DBCollections,
|
||||
DBField,
|
||||
DateField,
|
||||
FieldType,
|
||||
JsonField,
|
||||
NumberField,
|
||||
TextField,
|
||||
import {
|
||||
collectionSchema,
|
||||
type BooleanField,
|
||||
type DBCollection,
|
||||
type DBCollections,
|
||||
type DBField,
|
||||
type DateField,
|
||||
type FieldType,
|
||||
type JsonField,
|
||||
type NumberField,
|
||||
type TextField,
|
||||
} from './types.js';
|
||||
import { type LibSQLDatabase, drizzle } from 'drizzle-orm/libsql';
|
||||
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
|
||||
import { SQLiteAsyncDialect, SQLiteTable } from 'drizzle-orm/sqlite-core';
|
||||
import { bold } from 'kleur/colors';
|
||||
import { type SQL, type ColumnBuilderBaseConfig, type ColumnDataType, sql } from 'drizzle-orm';
|
||||
import {
|
||||
|
@ -39,10 +40,45 @@ export type {
|
|||
|
||||
const sqlite = new SQLiteAsyncDialect();
|
||||
|
||||
export async function createDb({ dbUrl }: { dbUrl: string }) {
|
||||
function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteTable) {
|
||||
// This totally works, don't worry about it
|
||||
const tableName = (Table as any)[(SQLiteTable as any).Symbol.Name];
|
||||
const collection = collections[tableName];
|
||||
if(collection.source === 'readable') {
|
||||
throw new Error(`The [${tableName}] collection is read-only.`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createDb({ collections, dbUrl, seeding }: {
|
||||
dbUrl: string;
|
||||
collections: DBCollections;
|
||||
seeding: boolean;
|
||||
}) {
|
||||
const client = createClient({ url: dbUrl });
|
||||
const db = drizzle(client);
|
||||
return db;
|
||||
|
||||
if(seeding) return db;
|
||||
|
||||
const {
|
||||
insert: drizzleInsert,
|
||||
update: drizzleUpdate,
|
||||
delete: drizzleDelete
|
||||
} = db;
|
||||
return Object.assign(db, {
|
||||
insert(Table: SQLiteTable) {
|
||||
//console.log('Table info...', Table._);
|
||||
checkIfModificationIsAllowed(collections, Table);
|
||||
return drizzleInsert.call(this, Table);
|
||||
},
|
||||
update(Table: SQLiteTable) {
|
||||
checkIfModificationIsAllowed(collections, Table);
|
||||
return drizzleUpdate.call(this, Table);
|
||||
},
|
||||
delete(Table: SQLiteTable) {
|
||||
checkIfModificationIsAllowed(collections, Table);
|
||||
return drizzleDelete.call(this, Table);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupDbTables({
|
||||
|
@ -217,6 +253,7 @@ export function collectionToTable(
|
|||
}
|
||||
|
||||
const table = sqliteTable(name, columns);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,14 +35,13 @@ export function getVirtualModContents({
|
|||
root: URL;
|
||||
}) {
|
||||
const dbUrl = getDbUrl(root).href;
|
||||
const shouldSetUpDb = !existsSync(getDbUrl(root));
|
||||
return `
|
||||
import { collectionToTable, createDb } from ${INTERNAL_MOD_IMPORT};
|
||||
|
||||
export const db = await createDb(${JSON.stringify({
|
||||
collections,
|
||||
dbUrl,
|
||||
createTables: shouldSetUpDb,
|
||||
seeding: false,
|
||||
})});
|
||||
export * from ${DRIZZLE_MOD_IMPORT};
|
||||
|
||||
|
|
|
@ -1,27 +1,53 @@
|
|||
import { expect } from 'chai';
|
||||
import { load as cheerioLoad } from 'cheerio';
|
||||
import { loadFixture } from '../../astro/test/test-utils.js';
|
||||
import testAdapter from '../../astro/test/test-adapter.js';
|
||||
|
||||
describe('astro:db', () => {
|
||||
let fixture;
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: new URL('./fixtures/basics/', import.meta.url),
|
||||
output: 'server',
|
||||
adapter: testAdapter()
|
||||
});
|
||||
});
|
||||
|
||||
describe('build', () => {
|
||||
describe('production', () => {
|
||||
before(async () => {
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Prints the list of authors', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/');
|
||||
const res = await app.render(request);
|
||||
const html = await res.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const ul = $('ul');
|
||||
expect(ul.children()).to.have.a.lengthOf(5);
|
||||
expect(ul.children().eq(0).text()).to.equal('Ben');
|
||||
});
|
||||
|
||||
it('Errors when inserting to a readonly collection', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/insert-into-readonly');
|
||||
const res = await app.render(request);
|
||||
const html = await res.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
expect($('#error').text()).to.equal('The [Author] collection is read-only.');
|
||||
});
|
||||
|
||||
it('Does not error when inserting into writable collection', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/insert-into-writable');
|
||||
const res = await app.render(request);
|
||||
const html = await res.text();
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
expect($('#error').text()).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
20
packages/db/test/fixtures/basics/astro.config.ts
vendored
20
packages/db/test/fixtures/basics/astro.config.ts
vendored
|
@ -5,7 +5,7 @@ const Author = defineCollection({
|
|||
fields: {
|
||||
name: field.text(),
|
||||
},
|
||||
source: 'local',
|
||||
source: 'readable',
|
||||
data() {
|
||||
return [
|
||||
{
|
||||
|
@ -25,13 +25,27 @@ const Author = defineCollection({
|
|||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const Themes = defineCollection({
|
||||
fields: {
|
||||
name: field.text(),
|
||||
},
|
||||
source: 'writable',
|
||||
data() {
|
||||
return [
|
||||
{
|
||||
name: 'One',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [db()],
|
||||
db: {
|
||||
collections: { Author },
|
||||
collections: { Author, Themes },
|
||||
}
|
||||
});
|
||||
|
||||
|
|
14
packages/db/test/fixtures/basics/src/pages/insert-into-readonly.astro
vendored
Normal file
14
packages/db/test/fixtures/basics/src/pages/insert-into-readonly.astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import { Author, db } from 'astro:db';
|
||||
|
||||
const authors = await db.select().from(Author);
|
||||
|
||||
let error: any = {};
|
||||
try {
|
||||
db.insert(Author).values({ name: 'Person A' })
|
||||
} catch(err) {
|
||||
error = err;
|
||||
}
|
||||
---
|
||||
|
||||
<div id="error">{error.message}</div>
|
12
packages/db/test/fixtures/basics/src/pages/insert-into-writable.astro
vendored
Normal file
12
packages/db/test/fixtures/basics/src/pages/insert-into-writable.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
import { Themes, db } from 'astro:db';
|
||||
|
||||
let error: any = {};
|
||||
try {
|
||||
db.insert(Themes).values({ name: 'Person A' });
|
||||
} catch(err) {
|
||||
error = err;
|
||||
}
|
||||
---
|
||||
|
||||
<div id="error">{error.message}</div>
|
Loading…
Add table
Reference in a new issue