0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-10 22:38:53 -05:00

fix: transform collection config to be JSON serializable

This commit is contained in:
bholmesdev 2024-02-07 17:35:58 -05:00
parent 79c5c1eacf
commit ad70ca403a
3 changed files with 68 additions and 28 deletions

View file

@ -1,6 +1,6 @@
import deepDiff from 'deep-diff';
import { mkdir, readFile, readdir, writeFile } from 'fs/promises';
import type { DBSnapshot } from '../types.js';
import { collectionsSchema, type DBSnapshot } from '../types.js';
import type { AstroConfig } from 'astro';
import { cyan, green, yellow } from 'kleur/colors';
const { applyChange, diff: generateDiff } = deepDiff;
@ -82,7 +82,9 @@ export async function getMigrations(): Promise<string[]> {
return migrationFiles;
}
export async function loadMigration(migration: string): Promise<{ diff: any[]; db: string[], confirm?: string[] }> {
export async function loadMigration(
migration: string
): Promise<{ diff: any[]; db: string[]; confirm?: string[] }> {
return JSON.parse(await readFile(`./migrations/${migration}`, 'utf-8'));
}
@ -117,7 +119,9 @@ export async function initializeFromMigrations(allMigrationFiles: string[]): Pro
}
export function createCurrentSnapshot(config: AstroConfig): DBSnapshot {
const schema = JSON.parse(JSON.stringify(config.db?.collections ?? {}));
// Parse to resolve non-serializable types like () => references
const collectionsConfig = collectionsSchema.parse(config.db?.collections ?? {});
const schema = JSON.parse(JSON.stringify(collectionsConfig));
return { experimentalVersion: 1, schema };
}
export function createEmptySnapshot(): DBSnapshot {

View file

@ -114,7 +114,9 @@ export function getCreateForeignKeyQueries(collectionName: string, collection: D
let queries: string[] = [];
for (const foreignKey of collection.foreignKeys ?? []) {
const fields = asArray(foreignKey.fields);
const references = asArray(foreignKey.references());
const references = asArray(foreignKey.references);
console.log(references[0]);
if (fields.length !== references.length) {
throw new Error(
@ -184,7 +186,7 @@ export function getModifiers(fieldName: string, field: DBField) {
export function getReferencesConfig(field: DBField) {
const canHaveReferences = field.type === 'number' || field.type === 'text';
if (!canHaveReferences) return undefined;
return field.references?.();
return field.references;
}
// Using `DBField` will not narrow `default` based on the column `type`

View file

@ -1,8 +1,9 @@
import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core';
import type { InferSelectModel } from 'drizzle-orm';
import type { SqliteDB, Table } from '../runtime/index.js';
import { z } from 'zod';
import { getTableName, SQL } from 'drizzle-orm';
import { z, type ZodTypeDef } from 'zod';
import { SQL } from 'drizzle-orm';
import { errorMap } from './integration/error-map.js';
export type MaybePromise<T> = T | Promise<T>;
export type MaybeArray<T> = T | T[];
@ -43,14 +44,19 @@ const numberFieldBaseSchema = baseFieldSchema.omit({ optional: true }).and(
const numberFieldOptsSchema: z.ZodType<
z.infer<typeof numberFieldBaseSchema> & {
// ReferenceableField creates a circular type. Define ZodType to resolve.
references?: () => NumberField;
references?: NumberField;
},
ZodTypeDef,
z.input<typeof numberFieldBaseSchema> & {
references?: () => NumberFieldInput;
}
> = numberFieldBaseSchema.and(
z.object({
references: z
.function()
.returns(z.lazy(() => numberFieldSchema))
.optional(),
.optional()
.transform((fn) => fn?.()),
})
);
@ -86,14 +92,19 @@ const textFieldBaseSchema = baseFieldSchema
const textFieldOptsSchema: z.ZodType<
z.infer<typeof textFieldBaseSchema> & {
// ReferenceableField creates a circular type. Define ZodType to resolve.
references?: () => TextField;
references?: TextField;
},
ZodTypeDef,
z.input<typeof textFieldBaseSchema> & {
references?: () => TextFieldInput;
}
> = textFieldBaseSchema.and(
z.object({
references: z
.function()
.returns(z.lazy(() => textFieldSchema))
.optional(),
.optional()
.transform((fn) => fn?.()),
})
);
@ -136,14 +147,22 @@ export const indexSchema = z.object({
unique: z.boolean().optional(),
});
const foreignKeysSchema: z.ZodType<{
type ForeignKeysInput = {
fields: MaybeArray<string>;
references: () => MaybeArray<ReferenceableField>;
}> = z.object({
references: () => MaybeArray<Omit<ReferenceableField, 'references'>>;
};
type ForeignKeysOutput = Omit<ForeignKeysInput, 'references'> & {
// reference fn called in `transform`. Ensures output is JSON serializable.
references: MaybeArray<Omit<ReferenceableField, 'references'>>;
};
const foreignKeysSchema: z.ZodType<ForeignKeysOutput, ZodTypeDef, ForeignKeysInput> = z.object({
fields: z.string().or(z.array(z.string())),
references: z
.function()
.returns(z.lazy(() => referenceableFieldSchema.or(z.array(referenceableFieldSchema)))),
.returns(z.lazy(() => referenceableFieldSchema.or(z.array(referenceableFieldSchema))))
.transform((fn) => fn()),
});
export type Indexes = Record<string, z.infer<typeof indexSchema>>;
@ -165,11 +184,32 @@ export const writableCollectionSchema = baseCollectionSchema.extend({
});
export const collectionSchema = z.union([readableCollectionSchema, writableCollectionSchema]);
export const collectionsSchema = z.record(collectionSchema);
export const collectionsSchema = z.preprocess((rawCollections) => {
// Preprocess collections to append collection and field names to fields.
// Used to track collection info for references.
// Use minimum parsing to ensure collection has a `fields` record.
const collections = z
.record(
z.object({
fields: z.record(z.any()),
})
)
.parse(rawCollections, { errorMap });
for (const [collectionName, collection] of Object.entries(collections)) {
for (const [fieldName, field] of Object.entries(collection.fields)) {
field.name = fieldName;
field.collection = collectionName;
}
}
return rawCollections;
}, z.record(collectionSchema));
export type BooleanField = z.infer<typeof booleanFieldSchema>;
export type NumberField = z.infer<typeof numberFieldSchema>;
export type NumberFieldInput = z.input<typeof numberFieldSchema>;
export type TextField = z.infer<typeof textFieldSchema>;
export type TextFieldInput = z.input<typeof textFieldSchema>;
export type DateField = z.infer<typeof dateFieldSchema>;
// Type `Date` is the config input, `string` is the output for D1 storage
export type DateFieldInput = z.input<typeof dateFieldSchema>;
@ -183,7 +223,12 @@ export type FieldType =
| JsonField['type'];
export type DBField = z.infer<typeof fieldSchema>;
export type DBFieldInput = DateFieldInput | BooleanField | NumberField | TextField | JsonField;
export type DBFieldInput =
| DateFieldInput
| BooleanField
| NumberFieldInput
| TextFieldInput
| JsonField;
export type DBFields = z.infer<typeof fieldsSchema>;
export type DBCollection = z.infer<
typeof readableCollectionSchema | typeof writableCollectionSchema
@ -268,11 +313,6 @@ function baseDefineCollection<TFields extends FieldsConfig, TWritable extends bo
userConfig: CollectionConfig<TFields>,
writable: TWritable
): ResolvedCollectionConfig<TFields, TWritable> {
for (const fieldName in userConfig.fields) {
const field = userConfig.fields[fieldName];
// Store field name within the field itself to track references
field.name = fieldName;
}
const meta: { table: Table<string, TFields> } = { table: null! };
/**
* We need to attach the Drizzle `table` at runtime using `_setMeta`.
@ -281,12 +321,6 @@ function baseDefineCollection<TFields extends FieldsConfig, TWritable extends bo
*/
const _setMeta = (values: { table: Table<string, TFields> }) => {
Object.assign(meta, values);
const tableName = getTableName(meta.table);
for (const fieldName in userConfig.fields) {
const field = userConfig.fields[fieldName];
field.collection = tableName;
}
};
return {