From 1c250817677100a3f47f1a8ac4ea30736b4f2000 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 25 Jan 2024 18:44:55 -0500 Subject: [PATCH] feat: glob support with { db, table } signature --- packages/db/src/config.ts | 35 ++++++++---- packages/db/src/index.ts | 2 +- packages/db/src/internal.ts | 3 + .../db/test/fixtures/glob/astro.config.ts | 19 ++----- packages/db/test/fixtures/glob/utils.ts | 56 +++++++++++++++++++ 5 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 packages/db/test/fixtures/glob/utils.ts diff --git a/packages/db/src/config.ts b/packages/db/src/config.ts index 717d214553..b4c9e163af 100644 --- a/packages/db/src/config.ts +++ b/packages/db/src/config.ts @@ -14,6 +14,7 @@ import { } from './types.js'; import { z } from 'zod'; import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core'; +import type { SqliteDB } from './internal.js'; export const dbConfigSchema = z.object({ studio: z.boolean().optional(), @@ -26,18 +27,33 @@ export const dbConfigSchema = z.object({ .optional(), }); +export type SetDataFn< + TFields extends z.input['fields'] = z.input< + typeof collectionSchema + >['fields'], +> = (params: { + db: SqliteDB; + table: Table< + string, + /** TODO: true type inference */ Record, DBField> + >; + mode: 'dev' | 'build'; +}) => MaybePromise; + export type DBUserConfig = Omit, 'data'> & { data(params: { set['fields']>( collection: ResolvedCollectionConfig, - data: MaybeArray< - SQLiteInsertValue< - Table< - string, - /** TODO: true type inference */ Record, DBField> - > - > - > + data: + | MaybeArray< + SQLiteInsertValue< + Table< + string, + /** TODO: true type inference */ Record, DBField> + > + > + > + | SetDataFn ): Promise /** TODO: type output */; }): MaybePromise; }; @@ -63,7 +79,6 @@ type CollectionConfig< seed?: Writable extends false ? never : () => MaybePromise & { id?: string }>>; - _: CollectionMeta; } : { fields: TFields; @@ -71,7 +86,6 @@ type CollectionConfig< data?: Writable extends true ? never : () => MaybePromise & { id?: string }>>; - _: CollectionMeta; }; type ResolvedCollectionConfig< @@ -79,6 +93,7 @@ type ResolvedCollectionConfig< Writable extends boolean, > = CollectionConfig & { writable: Writable; + _: CollectionMeta; }; export function defineCollection['fields']>( diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 96c54fc6e9..71c6ef50f3 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,4 +1,4 @@ -export { defineCollection, defineWritableCollection, field } from './config.js'; +export { defineCollection, defineWritableCollection, field, type SetDataFn } from './config.js'; export { cli } from './cli/index.js'; export { integration as default } from './integration.js'; diff --git a/packages/db/src/internal.ts b/packages/db/src/internal.ts index 7d1ed420da..3035fea08d 100644 --- a/packages/db/src/internal.ts +++ b/packages/db/src/internal.ts @@ -120,6 +120,9 @@ export async function setupDbTables({ ); } const table = collectionToTable(collectionName, collectionSchema.parse(collection)); + if (typeof values === 'function') { + return await values({ db, table: table as any, mode }); + } const result = Array.isArray(values) ? await db.insert(table).values(values).returning() : await db.insert(table).values(values).returning().get(); diff --git a/packages/db/test/fixtures/glob/astro.config.ts b/packages/db/test/fixtures/glob/astro.config.ts index af9545d625..72ee1c3d98 100644 --- a/packages/db/test/fixtures/glob/astro.config.ts +++ b/packages/db/test/fixtures/glob/astro.config.ts @@ -1,30 +1,21 @@ import { defineConfig } from 'astro/config'; import db, { defineCollection, field } from '@astrojs/db'; -import glob from 'fast-glob'; -import { readFile } from 'fs/promises'; +import { asJson, glob } from './utils'; const Quote = defineCollection({ fields: { author: field.text(), body: field.text(), - }, - async data() { - const quotes = await glob('quotes/*.json'); - return Promise.all( - quotes.map(async (quote) => { - const data = JSON.parse(await readFile(quote, 'utf-8')); - return { - author: data.author, - body: data.body, - }; - }) - ); + file: field.text({ unique: true }), }, }); export default defineConfig({ db: { collections: { Quote }, + data({ set }) { + set(Quote, glob('quotes/*.json', asJson)); + }, }, integrations: [db()], }); diff --git a/packages/db/test/fixtures/glob/utils.ts b/packages/db/test/fixtures/glob/utils.ts new file mode 100644 index 0000000000..d170e36d60 --- /dev/null +++ b/packages/db/test/fixtures/glob/utils.ts @@ -0,0 +1,56 @@ +import fastGlob from 'fast-glob'; +import { readFile } from 'fs/promises'; +import chokidar from 'chokidar'; +import { eq } from 'drizzle-orm'; +import { type SetDataFn } from '@astrojs/db'; + +export function glob( + pattern: string, + parser: (params: { file: string; content: string }) => Record +) { + const setDataFn: SetDataFn = async (ctx) => { + const fileField = ctx.table.file; + if (!fileField) { + throw new Error('`file` field is required for glob collections.'); + } + if (ctx.mode === 'dev') { + chokidar + .watch(pattern) + .on('add', async (file) => { + const content = await readFile(file, 'utf-8'); + const parsed = parser({ file, content }); + await ctx.db.insert(ctx.table).values({ ...parsed, file }); + }) + .on('change', async (file) => { + const content = await readFile(file, 'utf-8'); + const parsed = parser({ file, content }); + await ctx.db + .insert(ctx.table) + .values({ ...parsed, file }) + .onConflictDoUpdate({ + target: fileField, + set: parsed, + }); + }) + .on('unlink', async (file) => { + await ctx.db.delete(ctx.table).where(eq(fileField, file)); + }); + } else { + const files = await fastGlob(pattern); + for (const file of files) { + const content = await readFile(file, 'utf-8'); + const parsed = parser({ file, content }); + await ctx.db.insert(ctx.table).values({ ...parsed, file }); + } + } + }; + return setDataFn; +} + +export function asJson(params: { file: string; content: string }) { + try { + return JSON.parse(params.content); + } catch (e) { + throw new Error(`Error parsing ${params.file}: ${e.message}`); + } +}