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

Move field schema only a schema object

This commit is contained in:
Matthew Phillips 2024-02-09 14:44:36 -05:00
parent 2a2539fbd2
commit 1d42585f24
6 changed files with 86 additions and 69 deletions

View file

@ -434,16 +434,16 @@ function isEmpty(obj: Record<string, unknown>) {
* @see https://www.sqlite.org/lang_altertable.html#alter_table_add_column
*/
function canAlterTableAddColumn(field: DBField) {
if (field.unique) return false;
if (field.schema.unique) return false;
if (hasRuntimeDefault(field)) return false;
if (!field.optional && !hasDefault(field)) return false;
if (!field.schema.optional && !hasDefault(field)) return false;
if (hasPrimaryKey(field)) return false;
if (getReferencesConfig(field)) return false;
return true;
}
function canAlterTableDropColumn(field: DBField) {
if (field.unique) return false;
if (field.schema.unique) return false;
if (hasPrimaryKey(field)) return false;
return true;
}
@ -461,10 +461,10 @@ function canRecreateTableWithoutDataLoss(
if (hasPrimaryKey(a) && a.type !== 'number' && !hasDefault(a)) {
return { dataLoss: true, fieldName, reason: 'added-required' };
}
if (!a.optional && !hasDefault(a)) {
if (!a.schema.optional && !hasDefault(a)) {
return { dataLoss: true, fieldName, reason: 'added-required' };
}
if (!a.optional && a.unique) {
if (!a.schema.optional && a.schema.unique) {
return { dataLoss: true, fieldName, reason: 'added-unique' };
}
}
@ -546,7 +546,7 @@ function canChangeTypeWithoutQuery(oldField: DBField, newField: DBField) {
// Using `DBField` will not narrow `default` based on the column `type`
// Handle each field separately
type WithDefaultDefined<T extends DBField> = T & Required<Pick<T, 'default'>>;
type WithDefaultDefined<T extends DBField> = T & Required<Pick<T['schema'], 'default'>>;
type DBFieldWithDefault =
| WithDefaultDefined<TextField>
| WithDefaultDefined<DateField>
@ -555,5 +555,5 @@ type DBFieldWithDefault =
| WithDefaultDefined<JsonField>;
function hasRuntimeDefault(field: DBField): field is DBFieldWithDefault {
return !!(field.default && isSerializedSQL(field.default));
return !!(field.schema.default && isSerializedSQL(field.schema.default));
}

View file

@ -35,8 +35,8 @@ function generateTableType(name: string, collection: DBCollection): string {
{
// Only select fields Drizzle needs for inference
type: field.type,
optional: field.optional,
default: field.default,
optional: field.schema.optional,
default: field.schema.default,
},
])
)

View file

@ -123,7 +123,7 @@ export function getCreateForeignKeyQueries(collectionName: string, collection: D
`Foreign key on ${collectionName} is misconfigured. \`fields\` and \`references\` must be the same length.`
);
}
const referencedCollection = references[0]?.collection;
const referencedCollection = references[0]?.schema.collection;
if (!referencedCollection) {
throw new Error(
`Foreign key on ${collectionName} is misconfigured. \`references\` cannot be empty.`
@ -132,7 +132,7 @@ export function getCreateForeignKeyQueries(collectionName: string, collection: D
const query = `FOREIGN KEY (${fields
.map((f) => sqlite.escapeName(f))
.join(', ')}) REFERENCES ${sqlite.escapeName(referencedCollection)}(${references
.map((r) => sqlite.escapeName(r.name!))
.map((r) => sqlite.escapeName(r.schema.name!))
.join(', ')})`;
queries.push(query);
}
@ -160,10 +160,10 @@ export function getModifiers(fieldName: string, field: DBField) {
if (hasPrimaryKey(field)) {
return ' PRIMARY KEY';
}
if (!field.optional) {
if (!field.schema.optional) {
modifiers += ' NOT NULL';
}
if (field.unique) {
if (field.schema.unique) {
modifiers += ' UNIQUE';
}
if (hasDefault(field)) {
@ -171,7 +171,7 @@ export function getModifiers(fieldName: string, field: DBField) {
}
const references = getReferencesConfig(field);
if (references) {
const { collection, name } = references;
const { collection, name } = references.schema;
if (!collection || !name) {
throw new Error(
`Invalid reference for field ${fieldName}. This is an unexpected error that should be reported to the Astro team.`
@ -186,12 +186,14 @@ 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.schema.references;
}
// Using `DBField` will not narrow `default` based on the column `type`
// Handle each field separately
type WithDefaultDefined<T extends DBField> = T & Required<Pick<T, 'default'>>;
type WithDefaultDefined<T extends DBField> = T & {
schema: Required<Pick<T['schema'], 'default'>>
};
type DBFieldWithDefault =
| WithDefaultDefined<TextField>
| WithDefaultDefined<DateField>
@ -201,7 +203,7 @@ type DBFieldWithDefault =
// Type narrowing the default fails on union types, so use a type guard
export function hasDefault(field: DBField): field is DBFieldWithDefault {
if (field.default !== undefined) {
if (field.schema.default !== undefined) {
return true;
}
if (hasPrimaryKey(field) && field.type === 'number') {
@ -222,8 +224,8 @@ function toDefault<T>(def: T | SQL<any>): string {
}
function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): string {
if (isSerializedSQL(column.default)) {
return column.default.sql;
if (isSerializedSQL(column.schema.default)) {
return column.schema.default.sql;
}
switch (column.type) {
@ -231,11 +233,11 @@ function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): str
case 'number':
case 'text':
case 'date':
return toDefault(column.default);
return toDefault(column.schema.default);
case 'json': {
let stringified = '';
try {
stringified = JSON.stringify(column.default);
stringified = JSON.stringify(column.schema.default);
} catch (e) {
// eslint-disable-next-line no-console
console.log(

View file

@ -29,9 +29,11 @@ const baseFieldSchema = z.object({
collection: z.string().optional(),
});
const booleanFieldSchema = baseFieldSchema.extend({
const booleanFieldSchema = z.object({
type: z.literal('boolean'),
schema: baseFieldSchema.extend({
default: z.union([z.boolean(), sqlSchema]).optional(),
}),
});
const numberFieldBaseSchema = baseFieldSchema.omit({ optional: true }).and(
@ -71,11 +73,10 @@ const numberFieldOptsSchema: z.ZodType<
})
);
const numberFieldSchema = numberFieldOptsSchema.and(
z.object({
const numberFieldSchema = z.object({
type: z.literal('number'),
})
);
schema: numberFieldOptsSchema
});
const textFieldBaseSchema = baseFieldSchema
.omit({ optional: true })
@ -119,14 +120,14 @@ const textFieldOptsSchema: z.ZodType<
})
);
const textFieldSchema = textFieldOptsSchema.and(
z.object({
const textFieldSchema = z.object({
type: z.literal('text'),
})
);
schema: textFieldOptsSchema
});
const dateFieldSchema = baseFieldSchema.extend({
const dateFieldSchema = z.object({
type: z.literal('date'),
schema: baseFieldSchema.extend({
default: z
.union([
sqlSchema,
@ -135,11 +136,14 @@ const dateFieldSchema = baseFieldSchema.extend({
z.coerce.date().transform((d) => d.toISOString()),
])
.optional(),
})
});
const jsonFieldSchema = baseFieldSchema.extend({
const jsonFieldSchema = z.object({
type: z.literal('json'),
schema: baseFieldSchema.extend({
default: z.unknown().optional(),
})
});
const fieldSchema = z.union([
@ -150,6 +154,7 @@ const fieldSchema = z.union([
jsonFieldSchema,
]);
export const referenceableFieldSchema = z.union([textFieldSchema, numberFieldSchema]);
const fieldsSchema = z.record(fieldSchema);
export const indexSchema = z.object({
@ -350,20 +355,30 @@ type FieldOpts<T extends DBFieldInput> = Omit<T, 'type'>;
type NumberFieldOpts = z.input<typeof numberFieldOptsSchema>;
type TextFieldOpts = z.input<typeof textFieldOptsSchema>;
function createField<S extends string, T extends Record<string, unknown>>(type: S, schema: T) {
return {
type,
/**
* @internal
*/
schema
};
}
export const field = {
number: <T extends NumberFieldOpts>(opts: T = {} as T) => {
return { type: 'number', ...opts } satisfies T & { type: 'number' };
return createField('number', opts) satisfies { type: 'number' };
},
boolean: <T extends FieldOpts<BooleanFieldInput>>(opts: T = {} as T) => {
return { type: 'boolean', ...opts } satisfies T & { type: 'boolean' };
return createField('boolean', opts) satisfies { type: 'boolean' }
},
text: <T extends TextFieldOpts>(opts: T = {} as T) => {
return { type: 'text', ...opts } satisfies T & { type: 'text' };
return createField('text', opts) satisfies { type: 'text' };
},
date<T extends FieldOpts<DateFieldInput>>(opts: T) {
return { type: 'date', ...opts } satisfies T & { type: 'date' };
date<T extends FieldOpts<DateFieldInput>>(opts: T = {} as T) {
return createField('date', opts) satisfies { type: 'date' };
},
json<T extends FieldOpts<JsonFieldInput>>(opts: T) {
return { type: 'json', ...opts } satisfies T & { type: 'json' };
json<T extends FieldOpts<JsonFieldInput>>(opts: T = {} as T) {
return createField('json', opts) satisfies { type: 'json' };
},
};

View file

@ -19,7 +19,7 @@ export type { Table } from './types.js';
export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js';
export function hasPrimaryKey(field: DBField) {
return 'primaryKey' in field && !!field.primaryKey;
return 'primaryKey' in field.schema && !!field.schema.primaryKey;
}
// Exports a few common expressions
@ -99,42 +99,42 @@ function columnMapper(fieldName: string, field: DBField, isJsonSerializable: boo
c = text(fieldName);
// Duplicate default logic across cases to preserve type inference.
// No clean generic for every column builder.
if (field.default !== undefined) c = c.default(handleSerializedSQL(field.default));
if (field.primaryKey === true) c = c.primaryKey();
if (field.schema.default !== undefined) c = c.default(handleSerializedSQL(field.schema.default));
if (field.schema.primaryKey === true) c = c.primaryKey();
break;
}
case 'number': {
c = integer(fieldName);
if (field.default !== undefined) c = c.default(handleSerializedSQL(field.default));
if (field.primaryKey === true) c = c.primaryKey();
if (field.schema.default !== undefined) c = c.default(handleSerializedSQL(field.schema.default));
if (field.schema.primaryKey === true) c = c.primaryKey();
break;
}
case 'boolean': {
c = integer(fieldName, { mode: 'boolean' });
if (field.default !== undefined) c = c.default(handleSerializedSQL(field.default));
if (field.schema.default !== undefined) c = c.default(handleSerializedSQL(field.schema.default));
break;
}
case 'json':
c = jsonType(fieldName);
if (field.default !== undefined) c = c.default(field.default);
if (field.schema.default !== undefined) c = c.default(field.schema.default);
break;
case 'date': {
// Parse dates as strings when in JSON serializable mode
if (isJsonSerializable) {
c = text(fieldName);
if (field.default !== undefined) {
c = c.default(handleSerializedSQL(field.default));
if (field.schema.default !== undefined) {
c = c.default(handleSerializedSQL(field.schema.default));
}
} else {
c = dateType(fieldName);
if (field.default !== undefined) {
const def = handleSerializedSQL(field.default);
if (field.schema.default !== undefined) {
const def = handleSerializedSQL(field.schema.default);
c = c.default(
def instanceof SQL
? def
: // default comes pre-transformed to an ISO string for D1 storage.
// parse back to a Date for Drizzle.
z.coerce.date().parse(field.default)
z.coerce.date().parse(field.schema.default)
);
}
}
@ -142,8 +142,8 @@ function columnMapper(fieldName: string, field: DBField, isJsonSerializable: boo
}
}
if (!field.optional) c = c.notNull();
if (field.unique) c = c.unique();
if (!field.schema.optional) c = c.notNull();
if (field.schema.unique) c = c.unique();
return c;
}

View file

@ -76,7 +76,7 @@ export type Column<T extends DBField['type'], S extends GeneratedConfig> = T ext
export type Table<
TTableName extends string,
TFields extends Record<string, Pick<DBField, 'type' | 'default' | 'optional'>>,
TFields extends Record<string, Pick<DBField, 'type' | 'schema'>>,
> = SQLiteTableWithColumns<{
name: TTableName;
schema: undefined;
@ -92,7 +92,7 @@ export type Table<
: TFields[K] extends { primaryKey: true }
? true
: false;
notNull: TFields[K]['optional'] extends true ? false : true;
notNull: TFields[K]['schema']['optional'] extends true ? false : true;
}
>;
};