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:
parent
2a2539fbd2
commit
1d42585f24
6 changed files with 86 additions and 69 deletions
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
])
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -29,9 +29,11 @@ const baseFieldSchema = z.object({
|
|||
collection: z.string().optional(),
|
||||
});
|
||||
|
||||
const booleanFieldSchema = baseFieldSchema.extend({
|
||||
const booleanFieldSchema = z.object({
|
||||
type: z.literal('boolean'),
|
||||
default: z.union([z.boolean(), sqlSchema]).optional(),
|
||||
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({
|
||||
type: z.literal('number'),
|
||||
})
|
||||
);
|
||||
const numberFieldSchema = z.object({
|
||||
type: z.literal('number'),
|
||||
schema: numberFieldOptsSchema
|
||||
});
|
||||
|
||||
const textFieldBaseSchema = baseFieldSchema
|
||||
.omit({ optional: true })
|
||||
|
@ -119,27 +120,30 @@ const textFieldOptsSchema: z.ZodType<
|
|||
})
|
||||
);
|
||||
|
||||
const textFieldSchema = textFieldOptsSchema.and(
|
||||
z.object({
|
||||
type: z.literal('text'),
|
||||
})
|
||||
);
|
||||
|
||||
const dateFieldSchema = baseFieldSchema.extend({
|
||||
type: z.literal('date'),
|
||||
default: z
|
||||
.union([
|
||||
sqlSchema,
|
||||
// allow date-like defaults in user config,
|
||||
// transform to ISO string for D1 storage
|
||||
z.coerce.date().transform((d) => d.toISOString()),
|
||||
])
|
||||
.optional(),
|
||||
const textFieldSchema = z.object({
|
||||
type: z.literal('text'),
|
||||
schema: textFieldOptsSchema
|
||||
});
|
||||
|
||||
const jsonFieldSchema = baseFieldSchema.extend({
|
||||
const dateFieldSchema = z.object({
|
||||
type: z.literal('date'),
|
||||
schema: baseFieldSchema.extend({
|
||||
default: z
|
||||
.union([
|
||||
sqlSchema,
|
||||
// allow date-like defaults in user config,
|
||||
// transform to ISO string for D1 storage
|
||||
z.coerce.date().transform((d) => d.toISOString()),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
});
|
||||
|
||||
const jsonFieldSchema = z.object({
|
||||
type: z.literal('json'),
|
||||
default: z.unknown().optional(),
|
||||
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' };
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue