Files
drizzle-team--drizzle-orm/drizzle-zod/src/schema.ts
T
2026-07-13 12:10:05 +08:00

153 lines
4.8 KiB
TypeScript

import { Column, getTableColumns, getViewSelectedFields, is, isTable, isView, SQL } from 'drizzle-orm';
import type { Table, View } from 'drizzle-orm';
import type { PgEnum } from 'drizzle-orm/pg-core';
import { z } from 'zod/v4';
import { columnToSchema } from './column.ts';
import type { Conditions } from './schema.types.internal.ts';
import type {
CreateInsertSchema,
CreateSchemaFactoryOptions,
CreateSelectSchema,
CreateUpdateSchema,
} from './schema.types.ts';
import { isPgEnum } from './utils.ts';
function getColumns(tableLike: Table | View) {
return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike);
}
function handleColumns(
columns: Record<string, any>,
refinements: Record<string, any>,
conditions: Conditions,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
): z.ZodType {
const columnSchemas: Record<string, z.ZodType> = {};
for (const [key, selected] of Object.entries(columns)) {
if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') {
const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected;
columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory);
continue;
}
const refinement = refinements[key];
if (refinement !== undefined && typeof refinement !== 'function') {
columnSchemas[key] = refinement;
continue;
}
const column = is(selected, Column) ? selected : undefined;
const schema = column ? columnToSchema(column, factory) : z.any();
const refined = typeof refinement === 'function' ? refinement(schema) : schema;
if (conditions.never(column)) {
continue;
} else {
columnSchemas[key] = refined;
}
if (column) {
if (conditions.nullable(column)) {
columnSchemas[key] = columnSchemas[key]!.nullable();
}
if (conditions.optional(column)) {
columnSchemas[key] = columnSchemas[key]!.optional();
}
}
}
return z.object(columnSchemas) as any;
}
function handleEnum(
enum_: PgEnum<any>,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
) {
const zod: typeof z = factory?.zodInstance ?? z;
return zod.enum(enum_.enumValues);
}
const selectConditions: Conditions = {
never: () => false,
optional: () => false,
nullable: (column) => !column.notNull,
};
const insertConditions: Conditions = {
never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
optional: (column) => !column.notNull || (column.notNull && column.hasDefault),
nullable: (column) => !column.notNull,
};
const updateConditions: Conditions = {
never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
optional: () => true,
nullable: (column) => !column.notNull,
};
export const createSelectSchema: CreateSelectSchema<undefined> = (
entity: Table | View | PgEnum<[string, ...string[]]>,
refine?: Record<string, any>,
) => {
if (isPgEnum(entity)) {
return handleEnum(entity);
}
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, selectConditions) as any;
};
export const createInsertSchema: CreateInsertSchema<undefined> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, insertConditions) as any;
};
export const createUpdateSchema: CreateUpdateSchema<undefined> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, updateConditions) as any;
};
export function createSchemaFactory<
TCoerce extends Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined,
>(options?: CreateSchemaFactoryOptions<TCoerce>) {
const createSelectSchema: CreateSelectSchema<TCoerce> = (
entity: Table | View | PgEnum<[string, ...string[]]>,
refine?: Record<string, any>,
) => {
if (isPgEnum(entity)) {
return handleEnum(entity, options);
}
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, selectConditions, options) as any;
};
const createInsertSchema: CreateInsertSchema<TCoerce> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, insertConditions, options) as any;
};
const createUpdateSchema: CreateUpdateSchema<TCoerce> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, updateConditions, options) as any;
};
return { createSelectSchema, createInsertSchema, createUpdateSchema };
}