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
|
* @see https://www.sqlite.org/lang_altertable.html#alter_table_add_column
|
||||||
*/
|
*/
|
||||||
function canAlterTableAddColumn(field: DBField) {
|
function canAlterTableAddColumn(field: DBField) {
|
||||||
if (field.unique) return false;
|
if (field.schema.unique) return false;
|
||||||
if (hasRuntimeDefault(field)) 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 (hasPrimaryKey(field)) return false;
|
||||||
if (getReferencesConfig(field)) return false;
|
if (getReferencesConfig(field)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function canAlterTableDropColumn(field: DBField) {
|
function canAlterTableDropColumn(field: DBField) {
|
||||||
if (field.unique) return false;
|
if (field.schema.unique) return false;
|
||||||
if (hasPrimaryKey(field)) return false;
|
if (hasPrimaryKey(field)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -461,10 +461,10 @@ function canRecreateTableWithoutDataLoss(
|
||||||
if (hasPrimaryKey(a) && a.type !== 'number' && !hasDefault(a)) {
|
if (hasPrimaryKey(a) && a.type !== 'number' && !hasDefault(a)) {
|
||||||
return { dataLoss: true, fieldName, reason: 'added-required' };
|
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' };
|
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' };
|
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`
|
// Using `DBField` will not narrow `default` based on the column `type`
|
||||||
// Handle each field separately
|
// 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 =
|
type DBFieldWithDefault =
|
||||||
| WithDefaultDefined<TextField>
|
| WithDefaultDefined<TextField>
|
||||||
| WithDefaultDefined<DateField>
|
| WithDefaultDefined<DateField>
|
||||||
|
@ -555,5 +555,5 @@ type DBFieldWithDefault =
|
||||||
| WithDefaultDefined<JsonField>;
|
| WithDefaultDefined<JsonField>;
|
||||||
|
|
||||||
function hasRuntimeDefault(field: DBField): field is DBFieldWithDefault {
|
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
|
// Only select fields Drizzle needs for inference
|
||||||
type: field.type,
|
type: field.type,
|
||||||
optional: field.optional,
|
optional: field.schema.optional,
|
||||||
default: field.default,
|
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.`
|
`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) {
|
if (!referencedCollection) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Foreign key on ${collectionName} is misconfigured. \`references\` cannot be empty.`
|
`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
|
const query = `FOREIGN KEY (${fields
|
||||||
.map((f) => sqlite.escapeName(f))
|
.map((f) => sqlite.escapeName(f))
|
||||||
.join(', ')}) REFERENCES ${sqlite.escapeName(referencedCollection)}(${references
|
.join(', ')}) REFERENCES ${sqlite.escapeName(referencedCollection)}(${references
|
||||||
.map((r) => sqlite.escapeName(r.name!))
|
.map((r) => sqlite.escapeName(r.schema.name!))
|
||||||
.join(', ')})`;
|
.join(', ')})`;
|
||||||
queries.push(query);
|
queries.push(query);
|
||||||
}
|
}
|
||||||
|
@ -160,10 +160,10 @@ export function getModifiers(fieldName: string, field: DBField) {
|
||||||
if (hasPrimaryKey(field)) {
|
if (hasPrimaryKey(field)) {
|
||||||
return ' PRIMARY KEY';
|
return ' PRIMARY KEY';
|
||||||
}
|
}
|
||||||
if (!field.optional) {
|
if (!field.schema.optional) {
|
||||||
modifiers += ' NOT NULL';
|
modifiers += ' NOT NULL';
|
||||||
}
|
}
|
||||||
if (field.unique) {
|
if (field.schema.unique) {
|
||||||
modifiers += ' UNIQUE';
|
modifiers += ' UNIQUE';
|
||||||
}
|
}
|
||||||
if (hasDefault(field)) {
|
if (hasDefault(field)) {
|
||||||
|
@ -171,7 +171,7 @@ export function getModifiers(fieldName: string, field: DBField) {
|
||||||
}
|
}
|
||||||
const references = getReferencesConfig(field);
|
const references = getReferencesConfig(field);
|
||||||
if (references) {
|
if (references) {
|
||||||
const { collection, name } = references;
|
const { collection, name } = references.schema;
|
||||||
if (!collection || !name) {
|
if (!collection || !name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid reference for field ${fieldName}. This is an unexpected error that should be reported to the Astro team.`
|
`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) {
|
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.schema.references;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using `DBField` will not narrow `default` based on the column `type`
|
// Using `DBField` will not narrow `default` based on the column `type`
|
||||||
// Handle each field separately
|
// 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 =
|
type DBFieldWithDefault =
|
||||||
| WithDefaultDefined<TextField>
|
| WithDefaultDefined<TextField>
|
||||||
| WithDefaultDefined<DateField>
|
| WithDefaultDefined<DateField>
|
||||||
|
@ -201,7 +203,7 @@ type DBFieldWithDefault =
|
||||||
|
|
||||||
// Type narrowing the default fails on union types, so use a type guard
|
// Type narrowing the default fails on union types, so use a type guard
|
||||||
export function hasDefault(field: DBField): field is DBFieldWithDefault {
|
export function hasDefault(field: DBField): field is DBFieldWithDefault {
|
||||||
if (field.default !== undefined) {
|
if (field.schema.default !== undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (hasPrimaryKey(field) && field.type === 'number') {
|
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 {
|
function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): string {
|
||||||
if (isSerializedSQL(column.default)) {
|
if (isSerializedSQL(column.schema.default)) {
|
||||||
return column.default.sql;
|
return column.schema.default.sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (column.type) {
|
switch (column.type) {
|
||||||
|
@ -231,11 +233,11 @@ function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): str
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'date':
|
case 'date':
|
||||||
return toDefault(column.default);
|
return toDefault(column.schema.default);
|
||||||
case 'json': {
|
case 'json': {
|
||||||
let stringified = '';
|
let stringified = '';
|
||||||
try {
|
try {
|
||||||
stringified = JSON.stringify(column.default);
|
stringified = JSON.stringify(column.schema.default);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -29,9 +29,11 @@ const baseFieldSchema = z.object({
|
||||||
collection: z.string().optional(),
|
collection: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const booleanFieldSchema = baseFieldSchema.extend({
|
const booleanFieldSchema = z.object({
|
||||||
type: z.literal('boolean'),
|
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(
|
const numberFieldBaseSchema = baseFieldSchema.omit({ optional: true }).and(
|
||||||
|
@ -71,11 +73,10 @@ const numberFieldOptsSchema: z.ZodType<
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const numberFieldSchema = numberFieldOptsSchema.and(
|
const numberFieldSchema = z.object({
|
||||||
z.object({
|
type: z.literal('number'),
|
||||||
type: z.literal('number'),
|
schema: numberFieldOptsSchema
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const textFieldBaseSchema = baseFieldSchema
|
const textFieldBaseSchema = baseFieldSchema
|
||||||
.omit({ optional: true })
|
.omit({ optional: true })
|
||||||
|
@ -119,27 +120,30 @@ const textFieldOptsSchema: z.ZodType<
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const textFieldSchema = textFieldOptsSchema.and(
|
const textFieldSchema = z.object({
|
||||||
z.object({
|
type: z.literal('text'),
|
||||||
type: z.literal('text'),
|
schema: textFieldOptsSchema
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
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 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'),
|
type: z.literal('json'),
|
||||||
default: z.unknown().optional(),
|
schema: baseFieldSchema.extend({
|
||||||
|
default: z.unknown().optional(),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSchema = z.union([
|
const fieldSchema = z.union([
|
||||||
|
@ -150,6 +154,7 @@ const fieldSchema = z.union([
|
||||||
jsonFieldSchema,
|
jsonFieldSchema,
|
||||||
]);
|
]);
|
||||||
export const referenceableFieldSchema = z.union([textFieldSchema, numberFieldSchema]);
|
export const referenceableFieldSchema = z.union([textFieldSchema, numberFieldSchema]);
|
||||||
|
|
||||||
const fieldsSchema = z.record(fieldSchema);
|
const fieldsSchema = z.record(fieldSchema);
|
||||||
|
|
||||||
export const indexSchema = z.object({
|
export const indexSchema = z.object({
|
||||||
|
@ -350,20 +355,30 @@ type FieldOpts<T extends DBFieldInput> = Omit<T, 'type'>;
|
||||||
type NumberFieldOpts = z.input<typeof numberFieldOptsSchema>;
|
type NumberFieldOpts = z.input<typeof numberFieldOptsSchema>;
|
||||||
type TextFieldOpts = z.input<typeof textFieldOptsSchema>;
|
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 = {
|
export const field = {
|
||||||
number: <T extends NumberFieldOpts>(opts: T = {} as T) => {
|
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) => {
|
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) => {
|
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) {
|
date<T extends FieldOpts<DateFieldInput>>(opts: T = {} as T) {
|
||||||
return { type: 'date', ...opts } satisfies T & { type: 'date' };
|
return createField('date', opts) satisfies { type: 'date' };
|
||||||
},
|
},
|
||||||
json<T extends FieldOpts<JsonFieldInput>>(opts: T) {
|
json<T extends FieldOpts<JsonFieldInput>>(opts: T = {} as T) {
|
||||||
return { type: 'json', ...opts } satisfies T & { type: 'json' };
|
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 { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js';
|
||||||
|
|
||||||
export function hasPrimaryKey(field: DBField) {
|
export function hasPrimaryKey(field: DBField) {
|
||||||
return 'primaryKey' in field && !!field.primaryKey;
|
return 'primaryKey' in field.schema && !!field.schema.primaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exports a few common expressions
|
// Exports a few common expressions
|
||||||
|
@ -99,42 +99,42 @@ function columnMapper(fieldName: string, field: DBField, isJsonSerializable: boo
|
||||||
c = text(fieldName);
|
c = text(fieldName);
|
||||||
// Duplicate default logic across cases to preserve type inference.
|
// Duplicate default logic across cases to preserve type inference.
|
||||||
// No clean generic for every column builder.
|
// No clean generic for every column builder.
|
||||||
if (field.default !== undefined) c = c.default(handleSerializedSQL(field.default));
|
if (field.schema.default !== undefined) c = c.default(handleSerializedSQL(field.schema.default));
|
||||||
if (field.primaryKey === true) c = c.primaryKey();
|
if (field.schema.primaryKey === true) c = c.primaryKey();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'number': {
|
case 'number': {
|
||||||
c = integer(fieldName);
|
c = integer(fieldName);
|
||||||
if (field.default !== undefined) c = c.default(handleSerializedSQL(field.default));
|
if (field.schema.default !== undefined) c = c.default(handleSerializedSQL(field.schema.default));
|
||||||
if (field.primaryKey === true) c = c.primaryKey();
|
if (field.schema.primaryKey === true) c = c.primaryKey();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'boolean': {
|
case 'boolean': {
|
||||||
c = integer(fieldName, { mode: '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;
|
break;
|
||||||
}
|
}
|
||||||
case 'json':
|
case 'json':
|
||||||
c = jsonType(fieldName);
|
c = jsonType(fieldName);
|
||||||
if (field.default !== undefined) c = c.default(field.default);
|
if (field.schema.default !== undefined) c = c.default(field.schema.default);
|
||||||
break;
|
break;
|
||||||
case 'date': {
|
case 'date': {
|
||||||
// Parse dates as strings when in JSON serializable mode
|
// Parse dates as strings when in JSON serializable mode
|
||||||
if (isJsonSerializable) {
|
if (isJsonSerializable) {
|
||||||
c = text(fieldName);
|
c = text(fieldName);
|
||||||
if (field.default !== undefined) {
|
if (field.schema.default !== undefined) {
|
||||||
c = c.default(handleSerializedSQL(field.default));
|
c = c.default(handleSerializedSQL(field.schema.default));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c = dateType(fieldName);
|
c = dateType(fieldName);
|
||||||
if (field.default !== undefined) {
|
if (field.schema.default !== undefined) {
|
||||||
const def = handleSerializedSQL(field.default);
|
const def = handleSerializedSQL(field.schema.default);
|
||||||
c = c.default(
|
c = c.default(
|
||||||
def instanceof SQL
|
def instanceof SQL
|
||||||
? def
|
? def
|
||||||
: // default comes pre-transformed to an ISO string for D1 storage.
|
: // default comes pre-transformed to an ISO string for D1 storage.
|
||||||
// parse back to a Date for Drizzle.
|
// 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.schema.optional) c = c.notNull();
|
||||||
if (field.unique) c = c.unique();
|
if (field.schema.unique) c = c.unique();
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ export type Column<T extends DBField['type'], S extends GeneratedConfig> = T ext
|
||||||
|
|
||||||
export type Table<
|
export type Table<
|
||||||
TTableName extends string,
|
TTableName extends string,
|
||||||
TFields extends Record<string, Pick<DBField, 'type' | 'default' | 'optional'>>,
|
TFields extends Record<string, Pick<DBField, 'type' | 'schema'>>,
|
||||||
> = SQLiteTableWithColumns<{
|
> = SQLiteTableWithColumns<{
|
||||||
name: TTableName;
|
name: TTableName;
|
||||||
schema: undefined;
|
schema: undefined;
|
||||||
|
@ -92,7 +92,7 @@ export type Table<
|
||||||
: TFields[K] extends { primaryKey: true }
|
: TFields[K] extends { primaryKey: true }
|
||||||
? true
|
? true
|
||||||
: false;
|
: 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