mirror of
https://github.com/withastro/astro.git
synced 2025-02-10 22:38:53 -05:00
wip: data() -> set() concept
This commit is contained in:
parent
f66b9df1c2
commit
efafea2a96
5 changed files with 111 additions and 61 deletions
|
@ -136,12 +136,13 @@ async function pushData({
|
||||||
});
|
});
|
||||||
const queries: Query[] = [];
|
const queries: Query[] = [];
|
||||||
|
|
||||||
for (const [name, collection] of Object.entries(config.db!.collections! as DBCollections)) {
|
// TODO: update migration seeding
|
||||||
if (collection.writable || !collection.data) continue;
|
// for (const [name, collection] of Object.entries(config.db!.collections! as DBCollections)) {
|
||||||
const table = collectionToTable(name, collection);
|
// if (collection.writable || !collection.data) continue;
|
||||||
const insert = db.insert(table).values(await collection.data());
|
// const table = collectionToTable(name, collection);
|
||||||
queries.push(insert.toSQL());
|
// const insert = db.insert(table).values(await collection.data());
|
||||||
}
|
// queries.push(insert.toSQL());
|
||||||
|
// }
|
||||||
const url = new URL('/db/query', getRemoteDatabaseUrl());
|
const url = new URL('/db/query', getRemoteDatabaseUrl());
|
||||||
const requestBody: InStatement[] = queries.map((q) => ({
|
const requestBody: InStatement[] = queries.map((q) => ({
|
||||||
sql: q.sql,
|
sql: q.sql,
|
||||||
|
|
|
@ -8,17 +8,30 @@ import {
|
||||||
type collectionSchema,
|
type collectionSchema,
|
||||||
collectionsSchema,
|
collectionsSchema,
|
||||||
type MaybePromise,
|
type MaybePromise,
|
||||||
|
type Table,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { type SqliteDB } from './internal.js';
|
||||||
|
import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
export const dbConfigSchema = z.object({
|
export const dbConfigSchema = z.object({
|
||||||
studio: z.boolean().optional(),
|
studio: z.boolean().optional(),
|
||||||
collections: collectionsSchema.optional(),
|
collections: collectionsSchema.optional(),
|
||||||
// TODO: strict types
|
// TODO: strict types
|
||||||
data: z.function().args(z.any()).optional(),
|
data: z
|
||||||
|
.function()
|
||||||
|
.args()
|
||||||
|
.returns(z.union([z.void(), z.promise(z.void())]))
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DBUserConfig = z.input<typeof dbConfigSchema>;
|
export type DBUserConfig = Omit<z.input<typeof dbConfigSchema>, 'data' | 'collections'> & {
|
||||||
|
collections: Record<
|
||||||
|
string,
|
||||||
|
ResolvedCollectionConfig<z.input<typeof collectionSchema>['fields'], boolean>
|
||||||
|
>;
|
||||||
|
data(): MaybePromise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
export const astroConfigWithDbSchema = z.object({
|
export const astroConfigWithDbSchema = z.object({
|
||||||
db: dbConfigSchema.optional(),
|
db: dbConfigSchema.optional(),
|
||||||
|
@ -48,14 +61,36 @@ type ResolvedCollectionConfig<
|
||||||
Writable extends boolean,
|
Writable extends boolean,
|
||||||
> = CollectionConfig<TFields, Writable> & {
|
> = CollectionConfig<TFields, Writable> & {
|
||||||
writable: Writable;
|
writable: Writable;
|
||||||
|
set(data: SQLiteInsertValue<Table<string, TFields>>): Promise<any> /** TODO: type output */;
|
||||||
};
|
};
|
||||||
|
type SetData<TFields extends z.input<typeof collectionSchema>['fields']> = SQLiteInsertValue<
|
||||||
|
Table<string, TFields>
|
||||||
|
>;
|
||||||
|
|
||||||
export function defineCollection<TFields extends z.input<typeof collectionSchema>['fields']>(
|
export function defineCollection<TFields extends z.input<typeof collectionSchema>['fields']>(
|
||||||
userConfig: CollectionConfig<TFields, false>
|
userConfig: CollectionConfig<TFields, false>
|
||||||
): ResolvedCollectionConfig<TFields, false> {
|
): ResolvedCollectionConfig<TFields, false> {
|
||||||
|
let db: SqliteDB | undefined;
|
||||||
|
let table: Table<string, TFields> | undefined;
|
||||||
|
function _setEnv(env: { db: SqliteDB; table: Table<string, TFields> }) {
|
||||||
|
db = env.db;
|
||||||
|
table = env.table;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...userConfig,
|
...userConfig,
|
||||||
writable: false,
|
writable: false,
|
||||||
|
// @ts-expect-error keep private
|
||||||
|
_setEnv,
|
||||||
|
set: async (values: SetData<TFields>) => {
|
||||||
|
if (!db || !table) {
|
||||||
|
throw new Error('Collection `.set()` can only be called during `data()` seeding.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Array.isArray(values)
|
||||||
|
? await db.insert(table).values(values).returning()
|
||||||
|
: await db.insert(table).values(values).returning().get();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +100,9 @@ export function defineWritableCollection<
|
||||||
return {
|
return {
|
||||||
...userConfig,
|
...userConfig,
|
||||||
writable: true,
|
writable: true,
|
||||||
|
set: () => {
|
||||||
|
throw new Error('TODO: implement for writable');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
type JsonField,
|
type JsonField,
|
||||||
type NumberField,
|
type NumberField,
|
||||||
type TextField,
|
type TextField,
|
||||||
|
type MaybePromise,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { type LibSQLDatabase, drizzle } from 'drizzle-orm/libsql';
|
import { type LibSQLDatabase, drizzle } from 'drizzle-orm/libsql';
|
||||||
import { bold } from 'kleur/colors';
|
import { bold } from 'kleur/colors';
|
||||||
|
@ -43,10 +44,6 @@ export function hasPrimaryKey(field: DBField) {
|
||||||
return 'primaryKey' in field && !!field.primaryKey;
|
return 'primaryKey' in field && !!field.primaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isReadableCollection(collection: DBCollection): collection is ReadableDBCollection {
|
|
||||||
return !collection.writable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteTable) {
|
function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteTable) {
|
||||||
const tableName = getTableName(Table);
|
const tableName = getTableName(Table);
|
||||||
const collection = collections[tableName];
|
const collection = collections[tableName];
|
||||||
|
@ -95,7 +92,7 @@ export async function setupDbTables({
|
||||||
mode,
|
mode,
|
||||||
}: {
|
}: {
|
||||||
db: LibSQLDatabase;
|
db: LibSQLDatabase;
|
||||||
data?: (...params: any) => any;
|
data?: () => MaybePromise<void>;
|
||||||
collections: DBCollections;
|
collections: DBCollections;
|
||||||
logger: AstroIntegrationLogger;
|
logger: AstroIntegrationLogger;
|
||||||
mode: 'dev' | 'build';
|
mode: 'dev' | 'build';
|
||||||
|
@ -110,26 +107,14 @@ export async function setupDbTables({
|
||||||
await db.run(q);
|
await db.run(q);
|
||||||
}
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
const ormObjects = Object.fromEntries(
|
for (const [name, collection] of Object.entries(collections)) {
|
||||||
Object.entries(collections).map(([name, collection]) => {
|
(collection as any)._setEnv({ db, table: collectionToTable(name, collection) });
|
||||||
const table = collectionToTable(name, collection, false);
|
}
|
||||||
return [name, table];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await data({ db, ...ormObjects, mode });
|
|
||||||
}
|
|
||||||
// TODO: decide on removing collection-level data
|
|
||||||
for (const [name, collection] of Object.entries(collections)) {
|
|
||||||
if (!isReadableCollection(collection) || !collection.data) continue;
|
|
||||||
|
|
||||||
const table = collectionToTable(name, collection);
|
|
||||||
try {
|
try {
|
||||||
await db.insert(table).values(await collection.data());
|
await data();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to seed ${bold(
|
`Failed to seed data. Did you update to match recent schema changes? Full error:\n\n${e}`
|
||||||
name
|
|
||||||
)} data. Did you update to match recent schema changes? Full error:\n\n${e}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,23 +52,17 @@ const fieldSchema = z.union([
|
||||||
]);
|
]);
|
||||||
const fieldsSchema = z.record(fieldSchema);
|
const fieldsSchema = z.record(fieldSchema);
|
||||||
|
|
||||||
const dataResponse = z.array(z.record(z.unknown()));
|
|
||||||
|
|
||||||
export const readableCollectionSchema = z.object({
|
export const readableCollectionSchema = z.object({
|
||||||
fields: fieldsSchema,
|
fields: fieldsSchema,
|
||||||
data: z
|
set: z.function(),
|
||||||
.function()
|
_setEnv: z.function(),
|
||||||
.returns(z.union([dataResponse, z.promise(dataResponse)]))
|
|
||||||
.optional(),
|
|
||||||
writable: z.literal(false),
|
writable: z.literal(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const writableCollectionSchema = z.object({
|
export const writableCollectionSchema = z.object({
|
||||||
fields: fieldsSchema,
|
fields: fieldsSchema,
|
||||||
seed: z
|
set: z.function(),
|
||||||
.function()
|
_setEnv: z.function(),
|
||||||
.returns(z.union([dataResponse, z.promise(dataResponse)]))
|
|
||||||
.optional(),
|
|
||||||
writable: z.literal(true),
|
writable: z.literal(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,7 +93,7 @@ export type DBCollection = z.infer<
|
||||||
export type DBCollections = Record<string, DBCollection>;
|
export type DBCollections = Record<string, DBCollection>;
|
||||||
export type DBSnapshot = {
|
export type DBSnapshot = {
|
||||||
schema: Record<string, DBCollection>;
|
schema: Record<string, DBCollection>;
|
||||||
/**
|
/**
|
||||||
* Snapshot version. Breaking changes to the snapshot format increment this number.
|
* Snapshot version. Breaking changes to the snapshot format increment this number.
|
||||||
* @todo Rename to "version" once closer to release.
|
* @todo Rename to "version" once closer to release.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,7 +3,7 @@ import db, { defineCollection, field } from '@astrojs/db';
|
||||||
|
|
||||||
const Recipe = defineCollection({
|
const Recipe = defineCollection({
|
||||||
fields: {
|
fields: {
|
||||||
id: field.number({ primaryKey: true }),
|
id: field.number({ primaryKey: true, optional: true }),
|
||||||
title: field.text(),
|
title: field.text(),
|
||||||
description: field.text(),
|
description: field.text(),
|
||||||
},
|
},
|
||||||
|
@ -11,7 +11,7 @@ const Recipe = defineCollection({
|
||||||
|
|
||||||
const Ingredient = defineCollection({
|
const Ingredient = defineCollection({
|
||||||
fields: {
|
fields: {
|
||||||
id: field.number({ primaryKey: true }),
|
id: field.number({ primaryKey: true, optional: true }),
|
||||||
name: field.text(),
|
name: field.text(),
|
||||||
quantity: field.number(),
|
quantity: field.number(),
|
||||||
recipeId: field.text(),
|
recipeId: field.text(),
|
||||||
|
@ -22,25 +22,57 @@ export default defineConfig({
|
||||||
integrations: [db()],
|
integrations: [db()],
|
||||||
db: {
|
db: {
|
||||||
collections: { Recipe, Ingredient },
|
collections: { Recipe, Ingredient },
|
||||||
async data({ db, Recipe, Ingredient }) {
|
async data() {
|
||||||
const pancakes = await db
|
const pancakes = await Recipe.set({
|
||||||
.insert(Recipe)
|
title: 'Pancakes',
|
||||||
.values({ title: 'Pancakes', description: 'A delicious breakfast' })
|
description: 'A delicious breakfast',
|
||||||
.returning()
|
});
|
||||||
.get();
|
|
||||||
await db.insert(Ingredient).values({ name: 'Flour', quantity: 1, recipeId: pancakes.id });
|
|
||||||
await db.insert(Ingredient).values({ name: 'Eggs', quantity: 2, recipeId: pancakes.id });
|
|
||||||
await db.insert(Ingredient).values({ name: 'Milk', quantity: 1, recipeId: pancakes.id });
|
|
||||||
|
|
||||||
const pizza = await db
|
Ingredient.set([
|
||||||
.insert(Recipe)
|
{
|
||||||
.values({ title: 'Pizza', description: 'A delicious dinner' })
|
name: 'Flour',
|
||||||
.returning()
|
quantity: 1,
|
||||||
.get();
|
recipeId: pancakes.id,
|
||||||
await db.insert(Ingredient).values({ name: 'Flour', quantity: 1, recipeId: pizza.id });
|
},
|
||||||
await db.insert(Ingredient).values({ name: 'Eggs', quantity: 2, recipeId: pizza.id });
|
{
|
||||||
await db.insert(Ingredient).values({ name: 'Milk', quantity: 1, recipeId: pizza.id });
|
name: 'Eggs',
|
||||||
await db.insert(Ingredient).values({ name: 'Tomato Sauce', quantity: 1, recipeId: pizza.id });
|
quantity: 2,
|
||||||
|
recipeId: pancakes.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Milk',
|
||||||
|
quantity: 1,
|
||||||
|
recipeId: pancakes.id,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pizza = await Recipe.set({
|
||||||
|
title: 'Pizza',
|
||||||
|
description: 'A delicious dinner',
|
||||||
|
});
|
||||||
|
|
||||||
|
Ingredient.set([
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue