0
Fork 0
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:
Matthew Phillips 2024-01-12 16:39:30 -05:00 committed by Nate Moore
parent 2efa6b1db9
commit bb46addb29
7 changed files with 123 additions and 21 deletions

View file

@ -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 🚀');

View file

@ -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;
}

View file

@ -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};

View file

@ -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('');
});
});
});

View file

@ -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 },
}
});

View 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>

View 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>