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:
parent
79c5c1eacf
commit
ad70ca403a
3 changed files with 68 additions and 28 deletions
|
@ -1,6 +1,6 @@
|
||||||
import deepDiff from 'deep-diff';
|
import deepDiff from 'deep-diff';
|
||||||
import { mkdir, readFile, readdir, writeFile } from 'fs/promises';
|
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 type { AstroConfig } from 'astro';
|
||||||
import { cyan, green, yellow } from 'kleur/colors';
|
import { cyan, green, yellow } from 'kleur/colors';
|
||||||
const { applyChange, diff: generateDiff } = deepDiff;
|
const { applyChange, diff: generateDiff } = deepDiff;
|
||||||
|
@ -82,7 +82,9 @@ export async function getMigrations(): Promise<string[]> {
|
||||||
return migrationFiles;
|
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'));
|
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 {
|
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 };
|
return { experimentalVersion: 1, schema };
|
||||||
}
|
}
|
||||||
export function createEmptySnapshot(): DBSnapshot {
|
export function createEmptySnapshot(): DBSnapshot {
|
||||||
|
|
|
@ -114,7 +114,9 @@ export function getCreateForeignKeyQueries(collectionName: string, collection: D
|
||||||
let queries: string[] = [];
|
let queries: string[] = [];
|
||||||
for (const foreignKey of collection.foreignKeys ?? []) {
|
for (const foreignKey of collection.foreignKeys ?? []) {
|
||||||
const fields = asArray(foreignKey.fields);
|
const fields = asArray(foreignKey.fields);
|
||||||
const references = asArray(foreignKey.references());
|
const references = asArray(foreignKey.references);
|
||||||
|
|
||||||
|
console.log(references[0]);
|
||||||
|
|
||||||
if (fields.length !== references.length) {
|
if (fields.length !== references.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -184,7 +186,7 @@ export function getModifiers(fieldName: string, field: DBField) {
|
||||||
export function getReferencesConfig(field: DBField) {
|
export function getReferencesConfig(field: DBField) {
|
||||||
const canHaveReferences = field.type === 'number' || field.type === 'text';
|
const canHaveReferences = field.type === 'number' || field.type === 'text';
|
||||||
if (!canHaveReferences) return undefined;
|
if (!canHaveReferences) return undefined;
|
||||||
return field.references?.();
|
return field.references;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using `DBField` will not narrow `default` based on the column `type`
|
// Using `DBField` will not narrow `default` based on the column `type`
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core';
|
import type { SQLiteInsertValue } from 'drizzle-orm/sqlite-core';
|
||||||
import type { InferSelectModel } from 'drizzle-orm';
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import type { SqliteDB, Table } from '../runtime/index.js';
|
import type { SqliteDB, Table } from '../runtime/index.js';
|
||||||
import { z } from 'zod';
|
import { z, type ZodTypeDef } from 'zod';
|
||||||
import { getTableName, SQL } from 'drizzle-orm';
|
import { SQL } from 'drizzle-orm';
|
||||||
|
import { errorMap } from './integration/error-map.js';
|
||||||
|
|
||||||
export type MaybePromise<T> = T | Promise<T>;
|
export type MaybePromise<T> = T | Promise<T>;
|
||||||
export type MaybeArray<T> = T | T[];
|
export type MaybeArray<T> = T | T[];
|
||||||
|
@ -43,14 +44,19 @@ const numberFieldBaseSchema = baseFieldSchema.omit({ optional: true }).and(
|
||||||
const numberFieldOptsSchema: z.ZodType<
|
const numberFieldOptsSchema: z.ZodType<
|
||||||
z.infer<typeof numberFieldBaseSchema> & {
|
z.infer<typeof numberFieldBaseSchema> & {
|
||||||
// ReferenceableField creates a circular type. Define ZodType to resolve.
|
// ReferenceableField creates a circular type. Define ZodType to resolve.
|
||||||
references?: () => NumberField;
|
references?: NumberField;
|
||||||
|
},
|
||||||
|
ZodTypeDef,
|
||||||
|
z.input<typeof numberFieldBaseSchema> & {
|
||||||
|
references?: () => NumberFieldInput;
|
||||||
}
|
}
|
||||||
> = numberFieldBaseSchema.and(
|
> = numberFieldBaseSchema.and(
|
||||||
z.object({
|
z.object({
|
||||||
references: z
|
references: z
|
||||||
.function()
|
.function()
|
||||||
.returns(z.lazy(() => numberFieldSchema))
|
.returns(z.lazy(() => numberFieldSchema))
|
||||||
.optional(),
|
.optional()
|
||||||
|
.transform((fn) => fn?.()),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,14 +92,19 @@ const textFieldBaseSchema = baseFieldSchema
|
||||||
const textFieldOptsSchema: z.ZodType<
|
const textFieldOptsSchema: z.ZodType<
|
||||||
z.infer<typeof textFieldBaseSchema> & {
|
z.infer<typeof textFieldBaseSchema> & {
|
||||||
// ReferenceableField creates a circular type. Define ZodType to resolve.
|
// ReferenceableField creates a circular type. Define ZodType to resolve.
|
||||||
references?: () => TextField;
|
references?: TextField;
|
||||||
|
},
|
||||||
|
ZodTypeDef,
|
||||||
|
z.input<typeof textFieldBaseSchema> & {
|
||||||
|
references?: () => TextFieldInput;
|
||||||
}
|
}
|
||||||
> = textFieldBaseSchema.and(
|
> = textFieldBaseSchema.and(
|
||||||
z.object({
|
z.object({
|
||||||
references: z
|
references: z
|
||||||
.function()
|
.function()
|
||||||
.returns(z.lazy(() => textFieldSchema))
|
.returns(z.lazy(() => textFieldSchema))
|
||||||
.optional(),
|
.optional()
|
||||||
|
.transform((fn) => fn?.()),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -136,14 +147,22 @@ export const indexSchema = z.object({
|
||||||
unique: z.boolean().optional(),
|
unique: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const foreignKeysSchema: z.ZodType<{
|
type ForeignKeysInput = {
|
||||||
fields: MaybeArray<string>;
|
fields: MaybeArray<string>;
|
||||||
references: () => MaybeArray<ReferenceableField>;
|
references: () => MaybeArray<Omit<ReferenceableField, 'references'>>;
|
||||||
}> = z.object({
|
};
|
||||||
|
|
||||||
|
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())),
|
fields: z.string().or(z.array(z.string())),
|
||||||
references: z
|
references: z
|
||||||
.function()
|
.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>>;
|
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 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 BooleanField = z.infer<typeof booleanFieldSchema>;
|
||||||
export type NumberField = z.infer<typeof numberFieldSchema>;
|
export type NumberField = z.infer<typeof numberFieldSchema>;
|
||||||
|
export type NumberFieldInput = z.input<typeof numberFieldSchema>;
|
||||||
export type TextField = z.infer<typeof textFieldSchema>;
|
export type TextField = z.infer<typeof textFieldSchema>;
|
||||||
|
export type TextFieldInput = z.input<typeof textFieldSchema>;
|
||||||
export type DateField = z.infer<typeof dateFieldSchema>;
|
export type DateField = z.infer<typeof dateFieldSchema>;
|
||||||
// Type `Date` is the config input, `string` is the output for D1 storage
|
// Type `Date` is the config input, `string` is the output for D1 storage
|
||||||
export type DateFieldInput = z.input<typeof dateFieldSchema>;
|
export type DateFieldInput = z.input<typeof dateFieldSchema>;
|
||||||
|
@ -183,7 +223,12 @@ export type FieldType =
|
||||||
| JsonField['type'];
|
| JsonField['type'];
|
||||||
|
|
||||||
export type DBField = z.infer<typeof fieldSchema>;
|
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 DBFields = z.infer<typeof fieldsSchema>;
|
||||||
export type DBCollection = z.infer<
|
export type DBCollection = z.infer<
|
||||||
typeof readableCollectionSchema | typeof writableCollectionSchema
|
typeof readableCollectionSchema | typeof writableCollectionSchema
|
||||||
|
@ -268,11 +313,6 @@ function baseDefineCollection<TFields extends FieldsConfig, TWritable extends bo
|
||||||
userConfig: CollectionConfig<TFields>,
|
userConfig: CollectionConfig<TFields>,
|
||||||
writable: TWritable
|
writable: TWritable
|
||||||
): ResolvedCollectionConfig<TFields, 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! };
|
const meta: { table: Table<string, TFields> } = { table: null! };
|
||||||
/**
|
/**
|
||||||
* We need to attach the Drizzle `table` at runtime using `_setMeta`.
|
* 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> }) => {
|
const _setMeta = (values: { table: Table<string, TFields> }) => {
|
||||||
Object.assign(meta, values);
|
Object.assign(meta, values);
|
||||||
|
|
||||||
const tableName = getTableName(meta.table);
|
|
||||||
for (const fieldName in userConfig.fields) {
|
|
||||||
const field = userConfig.fields[fieldName];
|
|
||||||
field.collection = tableName;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Add table
Reference in a new issue