UNPKG

drizzle-typebox

Version:

Generate Typebox schemas from Drizzle ORM schemas

357 lines (350 loc) • 12.7 kB
'use strict'; var typebox = require('@sinclair/typebox'); var drizzleOrm = require('drizzle-orm'); const CONSTANTS = { INT8_MIN: -128, INT8_MAX: 127, INT8_UNSIGNED_MAX: 255, INT16_MIN: -32768, INT16_MAX: 32767, INT16_UNSIGNED_MAX: 65535, INT24_MIN: -8388608, INT24_MAX: 8388607, INT24_UNSIGNED_MAX: 16777215, INT32_MIN: -2147483648, INT32_MAX: 2147483647, INT32_UNSIGNED_MAX: 4294967295, INT48_MIN: -140737488355328, INT48_MAX: 140737488355327, INT48_UNSIGNED_MAX: 281474976710655, INT64_MIN: -9223372036854775808n, INT64_MAX: 9223372036854775807n, INT64_UNSIGNED_MAX: 18446744073709551615n, }; function isColumnType(column, columnTypes) { return columnTypes.includes(column.columnType); } function isWithEnum(column) { return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0; } const isPgEnum = isWithEnum; const literalSchema = typebox.Type.Union([typebox.Type.String(), typebox.Type.Number(), typebox.Type.Boolean(), typebox.Type.Null()]); const jsonSchema = typebox.Type.Union([literalSchema, typebox.Type.Array(typebox.Type.Any()), typebox.Type.Record(typebox.Type.String(), typebox.Type.Any())]); typebox.TypeRegistry.Set('Buffer', (_, value) => value instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof const bufferSchema = { [typebox.Kind]: 'Buffer', type: 'buffer' }; function mapEnumValues(values) { return Object.fromEntries(values.map((value) => [value, value])); } function columnToSchema(column, t) { let schema; if (isWithEnum(column)) { schema = column.enumValues.length ? t.Enum(mapEnumValues(column.enumValues)) : t.String(); } if (!schema) { // Handle specific types if (isColumnType(column, ['PgGeometry', 'PgPointTuple'])) { schema = t.Tuple([t.Number(), t.Number()]); } else if (isColumnType(column, ['PgGeometryObject', 'PgPointObject'])) { schema = t.Object({ x: t.Number(), y: t.Number() }); } else if (isColumnType(column, ['PgHalfVector', 'PgVector'])) { schema = t.Array(t.Number(), column.dimensions ? { minItems: column.dimensions, maxItems: column.dimensions, } : undefined); } else if (isColumnType(column, ['PgLine'])) { schema = t.Tuple([t.Number(), t.Number(), t.Number()]); } else if (isColumnType(column, ['PgLineABC'])) { schema = t.Object({ a: t.Number(), b: t.Number(), c: t.Number(), }); } // Handle other types else if (isColumnType(column, ['PgArray'])) { schema = t.Array(columnToSchema(column.baseColumn, t), column.size ? { minItems: column.size, maxItems: column.size, } : undefined); } else if (column.dataType === 'array') { schema = t.Array(t.Any()); } else if (column.dataType === 'number') { schema = numberColumnToSchema(column, t); } else if (column.dataType === 'bigint') { schema = bigintColumnToSchema(column, t); } else if (column.dataType === 'boolean') { schema = t.Boolean(); } else if (column.dataType === 'date') { schema = t.Date(); } else if (column.dataType === 'string') { schema = stringColumnToSchema(column, t); } else if (column.dataType === 'json') { schema = jsonSchema; } else if (column.dataType === 'custom') { schema = t.Any(); } else if (column.dataType === 'buffer') { schema = bufferSchema; } } if (!schema) { schema = t.Any(); } return schema; } function numberColumnToSchema(column, t) { let unsigned = column.getSQLType().includes('unsigned'); let min; let max; let integer = false; if (isColumnType(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { min = unsigned ? 0 : CONSTANTS.INT8_MIN; max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; integer = true; } else if (isColumnType(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(column, [ 'PgReal', 'MySqlFloat', 'MySqlMediumInt', 'SingleStoreFloat', 'SingleStoreMediumInt', ])) { min = unsigned ? 0 : CONSTANTS.INT24_MIN; max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); } else if (isColumnType(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(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(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(column, ['MySqlYear', 'SingleStoreYear'])) { min = 1901; max = 2155; integer = true; } else { min = Number.MIN_SAFE_INTEGER; max = Number.MAX_SAFE_INTEGER; } const key = integer ? 'Integer' : 'Number'; return t[key]({ minimum: min, maximum: max, }); } function bigintColumnToSchema(column, t) { const unsigned = column.getSQLType().includes('unsigned'); const min = unsigned ? 0n : CONSTANTS.INT64_MIN; const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX; return t.BigInt({ minimum: min, maximum: max, }); } function stringColumnToSchema(column, t) { if (isColumnType(column, ['PgUUID'])) { return t.String({ format: 'uuid' }); } else if (isColumnType(column, [ 'PgBinaryVector', ])) { return t.RegExp(/^[01]+$/, column.dimensions ? { maxLength: column.dimensions } : undefined); } let max; let fixed = false; if (isColumnType(column, ['PgVarchar', 'SQLiteText'])) { max = column.length; } else if (isColumnType(column, ['MySqlVarChar', 'SingleStoreVarChar'])) { max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; } else if (isColumnType(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(column, [ 'PgChar', 'MySqlChar', 'SingleStoreChar', ])) { max = column.length; fixed = true; } const options = {}; if (max !== undefined && fixed) { options.minLength = max; options.maxLength = max; } else if (max !== undefined) { options.maxLength = max; } return t.String(Object.keys(options).length > 0 ? options : undefined); } function getColumns(tableLike) { return drizzleOrm.isTable(tableLike) ? drizzleOrm.getTableColumns(tableLike) : drizzleOrm.getViewSelectedFields(tableLike); } function handleColumns(columns, refinements, conditions, factory) { const columnSchemas = {}; for (const [key, selected] of Object.entries(columns)) { if (!drizzleOrm.is(selected, drizzleOrm.Column) && !drizzleOrm.is(selected, drizzleOrm.SQL) && !drizzleOrm.is(selected, drizzleOrm.SQL.Aliased) && typeof selected === 'object') { const columns = drizzleOrm.isTable(selected) || drizzleOrm.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 = drizzleOrm.is(selected, drizzleOrm.Column) ? selected : undefined; const schema = column ? columnToSchema(column, factory?.typeboxInstance ?? typebox.Type) : typebox.Type.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] = typebox.Type.Union([columnSchemas[key], typebox.Type.Null()]); } if (conditions.optional(column)) { columnSchemas[key] = typebox.Type.Optional(columnSchemas[key]); } } } return typebox.Type.Object(columnSchemas); } function handleEnum(enum_, factory) { const typebox$1 = factory?.typeboxInstance ?? typebox.Type; return typebox$1.Enum(mapEnumValues(enum_.enumValues)); } const selectConditions = { never: () => false, optional: () => false, nullable: (column) => !column.notNull, }; const insertConditions = { never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', optional: (column) => !column.notNull || (column.notNull && column.hasDefault), nullable: (column) => !column.notNull, }; const updateConditions = { never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', optional: () => true, nullable: (column) => !column.notNull, }; const createSelectSchema = (entity, refine) => { if (isPgEnum(entity)) { return handleEnum(entity); } const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, selectConditions); }; const createInsertSchema = (entity, refine) => { const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, insertConditions); }; const createUpdateSchema = (entity, refine) => { const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, updateConditions); }; function createSchemaFactory(options) { const createSelectSchema = (entity, refine) => { if (isPgEnum(entity)) { return handleEnum(entity, options); } const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, selectConditions, options); }; const createInsertSchema = (entity, refine) => { const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, insertConditions, options); }; const createUpdateSchema = (entity, refine) => { const columns = getColumns(entity); return handleColumns(columns, refine ?? {}, updateConditions, options); }; return { createSelectSchema, createInsertSchema, createUpdateSchema }; } exports.bufferSchema = bufferSchema; exports.createInsertSchema = createInsertSchema; exports.createSchemaFactory = createSchemaFactory; exports.createSelectSchema = createSelectSchema; exports.createUpdateSchema = createUpdateSchema; exports.getColumns = getColumns; exports.handleColumns = handleColumns; exports.handleEnum = handleEnum; exports.isColumnType = isColumnType; exports.isPgEnum = isPgEnum; exports.isWithEnum = isWithEnum; exports.jsonSchema = jsonSchema; exports.literalSchema = literalSchema; //# sourceMappingURL=index.cjs.map