import type { Column, ColumnBaseConfig } from 'drizzle-orm'; import type { MySqlBigInt53, MySqlChar, MySqlDouble, MySqlFloat, MySqlInt, MySqlMediumInt, MySqlReal, MySqlSerial, MySqlSmallInt, MySqlText, MySqlTinyInt, MySqlVarChar, MySqlYear, } from 'drizzle-orm/mysql-core'; import type { PgArray, PgBigInt53, PgBigSerial53, PgBinaryVector, PgChar, PgDoublePrecision, PgGeometry, PgGeometryObject, PgHalfVector, PgInteger, PgLineABC, PgLineTuple, PgPointObject, PgPointTuple, PgReal, PgSerial, PgSmallInt, PgSmallSerial, PgUUID, PgVarchar, PgVector, } from 'drizzle-orm/pg-core'; import type { SingleStoreBigInt53, SingleStoreChar, SingleStoreDouble, SingleStoreFloat, SingleStoreInt, SingleStoreMediumInt, SingleStoreReal, SingleStoreSerial, SingleStoreSmallInt, SingleStoreText, SingleStoreTinyInt, SingleStoreVarChar, SingleStoreYear, } from 'drizzle-orm/singlestore-core'; import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core'; import { z as zod } from 'zod/v4'; import { CONSTANTS } from './constants.ts'; import type { CreateSchemaFactoryOptions } from './schema.types.ts'; import { isColumnType, isWithEnum } from './utils.ts'; import type { Json } from './utils.ts'; export const literalSchema = zod.union([zod.string(), zod.number(), zod.boolean(), zod.null()]); export const jsonSchema: zod.ZodType = zod.union([ literalSchema, zod.record(zod.string(), zod.any()), zod.array(zod.any()), ]); export const bufferSchema: zod.ZodType = zod.custom((v) => v instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof export function columnToSchema( column: Column, factory: | CreateSchemaFactoryOptions< Partial> | true | undefined > | undefined, ): zod.ZodType { const z: typeof zod = factory?.zodInstance ?? zod; const coerce = factory?.coerce ?? {}; let schema!: zod.ZodType; if (isWithEnum(column)) { schema = column.enumValues.length ? z.enum(column.enumValues) : z.string(); } if (!schema) { // Handle specific types if (isColumnType | PgPointTuple>(column, ['PgGeometry', 'PgPointTuple'])) { schema = z.tuple([z.number(), z.number()]); } else if ( isColumnType | PgGeometryObject>(column, ['PgGeometryObject', 'PgPointObject']) ) { schema = z.object({ x: z.number(), y: z.number() }); } else if (isColumnType | PgVector>(column, ['PgHalfVector', 'PgVector'])) { schema = z.array(z.number()); schema = column.dimensions ? (schema as zod.ZodArray).length(column.dimensions) : schema; } else if (isColumnType>(column, ['PgLine'])) { schema = z.tuple([z.number(), z.number(), z.number()]); } else if (isColumnType>(column, ['PgLineABC'])) { schema = z.object({ a: z.number(), b: z.number(), c: z.number(), }); } // Handle other types else if (isColumnType>(column, ['PgArray'])) { schema = z.array(columnToSchema(column.baseColumn, factory)); schema = column.size ? (schema as zod.ZodArray).length(column.size) : schema; } else if (column.dataType === 'array') { schema = z.array(z.any()); } else if (column.dataType === 'number') { schema = numberColumnToSchema(column, z, coerce); } else if (column.dataType === 'bigint') { schema = bigintColumnToSchema(column, z, coerce); } else if (column.dataType === 'boolean') { schema = coerce === true || coerce.boolean ? z.coerce.boolean() : z.boolean(); } else if (column.dataType === 'date') { schema = coerce === true || coerce.date ? z.coerce.date() : z.date(); } else if (column.dataType === 'string') { schema = stringColumnToSchema(column, z, coerce); } else if (column.dataType === 'json') { schema = jsonSchema; } else if (column.dataType === 'custom') { schema = z.any(); } else if (column.dataType === 'buffer') { schema = bufferSchema; } } if (!schema) { schema = z.any(); } return schema; } function numberColumnToSchema( column: Column, z: typeof zod, coerce: CreateSchemaFactoryOptions< Partial> | true | undefined >['coerce'], ): zod.ZodType { let unsigned = column.getSQLType().includes('unsigned'); let min!: number; let max!: number; let integer = false; if (isColumnType | SingleStoreTinyInt>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { min = unsigned ? 0 : CONSTANTS.INT8_MIN; max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; integer = true; } else if ( isColumnType | PgSmallSerial | MySqlSmallInt | SingleStoreSmallInt>(column, [ 'PgSmallInt', 'PgSmallSerial', 'MySqlSmallInt', 'SingleStoreSmallInt', ]) ) { min = unsigned ? 0 : CONSTANTS.INT16_MIN; max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX; integer = true; } else if ( isColumnType< PgReal | MySqlFloat | MySqlMediumInt | SingleStoreMediumInt | SingleStoreFloat >(column, [ 'PgReal', 'MySqlFloat', 'MySqlMediumInt', 'SingleStoreMediumInt', 'SingleStoreFloat', ]) ) { min = unsigned ? 0 : CONSTANTS.INT24_MIN; max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); } else if ( isColumnType | PgSerial | MySqlInt | SingleStoreInt>(column, [ 'PgInteger', 'PgSerial', 'MySqlInt', 'SingleStoreInt', ]) ) { min = unsigned ? 0 : CONSTANTS.INT32_MIN; max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX; integer = true; } else if ( isColumnType< | PgDoublePrecision | MySqlReal | MySqlDouble | SingleStoreReal | SingleStoreDouble | SQLiteReal >(column, [ 'PgDoublePrecision', 'MySqlReal', 'MySqlDouble', 'SingleStoreReal', 'SingleStoreDouble', 'SQLiteReal', ]) ) { min = unsigned ? 0 : CONSTANTS.INT48_MIN; max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX; } else if ( isColumnType< | PgBigInt53 | PgBigSerial53 | MySqlBigInt53 | MySqlSerial | SingleStoreBigInt53 | SingleStoreSerial | SQLiteInteger >( column, [ 'PgBigInt53', 'PgBigSerial53', 'MySqlBigInt53', 'MySqlSerial', 'SingleStoreBigInt53', 'SingleStoreSerial', 'SQLiteInteger', ], ) ) { unsigned = unsigned || isColumnType(column, ['MySqlSerial', 'SingleStoreSerial']); min = unsigned ? 0 : Number.MIN_SAFE_INTEGER; max = Number.MAX_SAFE_INTEGER; integer = true; } else if (isColumnType | SingleStoreYear>(column, ['MySqlYear', 'SingleStoreYear'])) { min = 1901; max = 2155; integer = true; } else { min = Number.MIN_SAFE_INTEGER; max = Number.MAX_SAFE_INTEGER; } let schema = coerce === true || coerce?.number ? integer ? z.coerce.number() : z.coerce.number().int() : integer ? z.int() : z.number(); schema = schema.gte(min).lte(max); return schema; } function bigintColumnToSchema( column: Column, z: typeof zod, coerce: CreateSchemaFactoryOptions< Partial> | true | undefined >['coerce'], ): zod.ZodType { const unsigned = column.getSQLType().includes('unsigned'); const min = unsigned ? 0n : CONSTANTS.INT64_MIN; const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX; const schema = coerce === true || coerce?.bigint ? z.coerce.bigint() : z.bigint(); return schema.gte(min).lte(max); } function stringColumnToSchema( column: Column, z: typeof zod, coerce: CreateSchemaFactoryOptions< Partial> | true | undefined >['coerce'], ): zod.ZodType { if (isColumnType>>(column, ['PgUUID'])) { return z.uuid(); } let max: number | undefined; let regex: RegExp | undefined; let fixed = false; if (isColumnType | SQLiteText>(column, ['PgVarchar', 'SQLiteText'])) { max = column.length; } else if ( isColumnType | SingleStoreVarChar>(column, ['MySqlVarChar', 'SingleStoreVarChar']) ) { max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; } else if (isColumnType | SingleStoreText>(column, ['MySqlText', 'SingleStoreText'])) { if (column.textType === 'longtext') { max = CONSTANTS.INT32_UNSIGNED_MAX; } else if (column.textType === 'mediumtext') { max = CONSTANTS.INT24_UNSIGNED_MAX; } else if (column.textType === 'text') { max = CONSTANTS.INT16_UNSIGNED_MAX; } else { max = CONSTANTS.INT8_UNSIGNED_MAX; } } if ( isColumnType | MySqlChar | SingleStoreChar>(column, [ 'PgChar', 'MySqlChar', 'SingleStoreChar', ]) ) { max = column.length; fixed = true; } if (isColumnType>(column, ['PgBinaryVector'])) { regex = /^[01]+$/; max = column.dimensions; } let schema = coerce === true || coerce?.string ? z.coerce.string() : z.string(); schema = regex ? schema.regex(regex) : schema; return max && fixed ? schema.length(max) : max ? schema.max(max) : schema; }