feat: fork drizzle-typebox as @alkdev/drizzlebox

- Rebrand package from drizzle-typebox to @alkdev/drizzlebox
- Replace @sinclair/typebox with @alkdev/typebox in all source and test files
- Replace @sinclair/typebox with @alkdev/typebox in rollup externals
- Convert tsconfig.json from monorepo extends to standalone config
- Fix build script monorepo remnant (dist.new -> dist)
- Add missing devDependencies (recast, tsx, typescript, resolve-tspaths)
- Replace monorepo link dependency for drizzle-orm with ^0.38.4
- Add .gitignore, LICENSE (Apache-2.0 with attribution), and README
- Initialize git repo with remote at git.alk.dev:alkdev/drizzlebox
This commit is contained in:
2026-04-25 09:45:14 +00:00
commit d0a0de766b
24 changed files with 3190 additions and 0 deletions

316
src/column.ts Normal file
View File

@@ -0,0 +1,316 @@
import { Kind, Type as t, TypeRegistry } from '@alkdev/typebox';
import type { StringOptions, TSchema, Type as typebox } from '@alkdev/typebox';
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,
type SingleStoreDouble,
type SingleStoreFloat,
type SingleStoreInt,
type SingleStoreMediumInt,
type SingleStoreReal,
type SingleStoreSerial,
type SingleStoreSmallInt,
SingleStoreText,
type SingleStoreTinyInt,
SingleStoreVarChar,
SingleStoreYear,
} from 'drizzle-orm/singlestore-core';
import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core';
import { CONSTANTS } from './constants.ts';
import { isColumnType, isWithEnum } from './utils.ts';
import type { BufferSchema, JsonSchema } from './utils.ts';
export const literalSchema = t.Union([t.String(), t.Number(), t.Boolean(), t.Null()]);
export const jsonSchema: JsonSchema = t.Recursive((self) =>
t.Union([literalSchema, t.Array(self), t.Record(t.String(), self)])
) as any;
TypeRegistry.Set('Buffer', (_, value) => value instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof
export const bufferSchema: BufferSchema = { [Kind]: 'Buffer', type: 'buffer' } as any;
export function mapEnumValues(values: string[]) {
return Object.fromEntries(values.map((value) => [value, value]));
}
export function columnToSchema(column: Column, t: typeof typebox): TSchema {
let schema!: TSchema;
if (isWithEnum(column)) {
schema = column.enumValues.length ? t.Enum(mapEnumValues(column.enumValues)) : t.String();
}
if (!schema) {
// Handle specific types
if (isColumnType<PgGeometry<any> | PgPointTuple<any>>(column, ['PgGeometry', 'PgPointTuple'])) {
schema = t.Tuple([t.Number(), t.Number()]);
} else if (
isColumnType<PgPointObject<any> | PgGeometryObject<any>>(column, ['PgGeometryObject', 'PgPointObject'])
) {
schema = t.Object({ x: t.Number(), y: t.Number() });
} else if (isColumnType<PgHalfVector<any> | PgVector<any>>(column, ['PgHalfVector', 'PgVector'])) {
schema = t.Array(
t.Number(),
column.dimensions
? {
minItems: column.dimensions,
maxItems: column.dimensions,
}
: undefined,
);
} else if (isColumnType<PgLineTuple<any>>(column, ['PgLine'])) {
schema = t.Tuple([t.Number(), t.Number(), t.Number()]);
} else if (isColumnType<PgLineABC<any>>(column, ['PgLineABC'])) {
schema = t.Object({
a: t.Number(),
b: t.Number(),
c: t.Number(),
});
} // Handle other types
else if (isColumnType<PgArray<any, any>>(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: Column, t: typeof typebox): TSchema {
let unsigned = column.getSQLType().includes('unsigned');
let min!: number;
let max!: number;
let integer = false;
if (isColumnType<MySqlTinyInt<any> | SingleStoreTinyInt<any>>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) {
min = unsigned ? 0 : CONSTANTS.INT8_MIN;
max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX;
integer = true;
} else if (
isColumnType<PgSmallInt<any> | PgSmallSerial<any> | MySqlSmallInt<any> | SingleStoreSmallInt<any>>(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<any> | MySqlFloat<any> | MySqlMediumInt<any> | SingleStoreFloat<any> | SingleStoreMediumInt<any>
>(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<PgInteger<any> | PgSerial<any> | MySqlInt<any> | SingleStoreInt<any>>(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<any>
| MySqlReal<any>
| MySqlDouble<any>
| SingleStoreReal<any>
| SingleStoreDouble<any>
| SQLiteReal<any>
>(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<any>
| PgBigSerial53<any>
| MySqlBigInt53<any>
| MySqlSerial<any>
| SingleStoreBigInt53<any>
| SingleStoreSerial<any>
| SQLiteInteger<any>
>(
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<MySqlYear<any> | SingleStoreYear<any>>(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: Column, t: typeof typebox): TSchema {
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: Column, t: typeof typebox): TSchema {
if (isColumnType<PgUUID<ColumnBaseConfig<'string', 'PgUUID'>>>(column, ['PgUUID'])) {
return t.String({ format: 'uuid' });
} else if (
isColumnType<PgBinaryVector<ColumnBaseConfig<'string', 'PgBinaryVector'> & { dimensions: number }>>(column, [
'PgBinaryVector',
])
) {
return t.RegExp(/^[01]+$/, column.dimensions ? { maxLength: column.dimensions } : undefined);
}
let max: number | undefined;
let fixed = false;
if (isColumnType<PgVarchar<any> | SQLiteText<any>>(column, ['PgVarchar', 'SQLiteText'])) {
max = column.length;
} else if (
isColumnType<MySqlVarChar<any> | SingleStoreVarChar<any>>(column, ['MySqlVarChar', 'SingleStoreVarChar'])
) {
max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX;
} else if (isColumnType<MySqlText<any> | SingleStoreText<any>>(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<PgChar<any> | MySqlChar<any> | SingleStoreChar<any>>(column, [
'PgChar',
'MySqlChar',
'SingleStoreChar',
])
) {
max = column.length;
fixed = true;
}
const options: Partial<StringOptions> = {};
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);
}

107
src/column.types.ts Normal file
View File

@@ -0,0 +1,107 @@
import type * as t from '@alkdev/typebox';
import type { Assume, Column } from 'drizzle-orm';
import type { ArrayHasAtLeastOneValue, BufferSchema, ColumnIsGeneratedAlwaysAs, IsNever, JsonSchema } from './utils.ts';
export type GetEnumValuesFromColumn<TColumn extends Column> = TColumn['_'] extends { enumValues: [string, ...string[]] }
? TColumn['_']['enumValues']
: undefined;
export type GetBaseColumn<TColumn extends Column> = TColumn['_'] extends { baseColumn: Column | never | undefined }
? IsNever<TColumn['_']['baseColumn']> extends false ? TColumn['_']['baseColumn']
: undefined
: undefined;
export type EnumValuesToEnum<TEnumValues extends [string, ...string[]]> = { [K in TEnumValues[number]]: K };
export type GetTypeboxType<
TData,
TDataType extends string,
TColumnType extends string,
TEnumValues extends [string, ...string[]] | undefined,
TBaseColumn extends Column | undefined,
> = TColumnType extends
| 'MySqlTinyInt'
| 'SingleStoreTinyInt'
| 'PgSmallInt'
| 'PgSmallSerial'
| 'MySqlSmallInt'
| 'MySqlMediumInt'
| 'SingleStoreSmallInt'
| 'SingleStoreMediumInt'
| 'PgInteger'
| 'PgSerial'
| 'MySqlInt'
| 'SingleStoreInt'
| 'PgBigInt53'
| 'PgBigSerial53'
| 'MySqlBigInt53'
| 'MySqlSerial'
| 'SingleStoreBigInt53'
| 'SingleStoreSerial'
| 'SQLiteInteger'
| 'MySqlYear'
| 'SingleStoreYear' ? t.TInteger
: TColumnType extends 'PgBinaryVector' ? t.TRegExp
: TBaseColumn extends Column ? t.TArray<
GetTypeboxType<
TBaseColumn['_']['data'],
TBaseColumn['_']['dataType'],
TBaseColumn['_']['columnType'],
GetEnumValuesFromColumn<TBaseColumn>,
GetBaseColumn<TBaseColumn>
>
>
: ArrayHasAtLeastOneValue<TEnumValues> extends true
? t.TEnum<EnumValuesToEnum<Assume<TEnumValues, [string, ...string[]]>>>
: TData extends infer TTuple extends [any, ...any[]] ? t.TTuple<
Assume<{ [K in keyof TTuple]: GetTypeboxType<TTuple[K], string, string, undefined, undefined> }, [any, ...any[]]>
>
: TData extends Date ? t.TDate
: TData extends Buffer ? BufferSchema
: TDataType extends 'array'
? t.TArray<GetTypeboxType<Assume<TData, any[]>[number], string, string, undefined, undefined>>
: TData extends infer TDict extends Record<string, any>
? t.TObject<{ [K in keyof TDict]: GetTypeboxType<TDict[K], string, string, undefined, undefined> }>
: TDataType extends 'json' ? JsonSchema
: TData extends number ? t.TNumber
: TData extends bigint ? t.TBigInt
: TData extends boolean ? t.TBoolean
: TData extends string ? t.TString
: t.TAny;
type HandleSelectColumn<
TSchema extends t.TSchema,
TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? TSchema
: t.Union<[TSchema, t.TNull]>;
type HandleInsertColumn<
TSchema extends t.TSchema,
TColumn extends Column,
> = ColumnIsGeneratedAlwaysAs<TColumn> extends true ? never
: TColumn['_']['notNull'] extends true ? TColumn['_']['hasDefault'] extends true ? t.TOptional<TSchema>
: TSchema
: t.TOptional<t.Union<[TSchema, t.TNull]>>;
type HandleUpdateColumn<
TSchema extends t.TSchema,
TColumn extends Column,
> = ColumnIsGeneratedAlwaysAs<TColumn> extends true ? never
: TColumn['_']['notNull'] extends true ? t.TOptional<TSchema>
: t.TOptional<t.Union<[TSchema, t.TNull]>>;
export type HandleColumn<
TType extends 'select' | 'insert' | 'update',
TColumn extends Column,
> = GetTypeboxType<
TColumn['_']['data'],
TColumn['_']['dataType'],
TColumn['_']['columnType'],
GetEnumValuesFromColumn<TColumn>,
GetBaseColumn<TColumn>
> extends infer TSchema extends t.TSchema ? TSchema extends t.TAny ? t.TAny
: TType extends 'select' ? HandleSelectColumn<TSchema, TColumn>
: TType extends 'insert' ? HandleInsertColumn<TSchema, TColumn>
: TType extends 'update' ? HandleUpdateColumn<TSchema, TColumn>
: TSchema
: t.TAny;

20
src/constants.ts Normal file
View File

@@ -0,0 +1,20 @@
export 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,
};

2
src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './schema.ts';
export * from './schema.types.ts';

144
src/schema.ts Normal file
View File

@@ -0,0 +1,144 @@
import { Type as t } from '@alkdev/typebox';
import type { TSchema } from '@alkdev/typebox';
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 { columnToSchema, mapEnumValues } 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,
): TSchema {
const columnSchemas: Record<string, TSchema> = {};
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?.typeboxInstance ?? t) : t.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] = t.Union([columnSchemas[key]!, t.Null()]);
}
if (conditions.optional(column)) {
columnSchemas[key] = t.Optional(columnSchemas[key]!);
}
}
}
return t.Object(columnSchemas) as any;
}
function handleEnum(enum_: PgEnum<any>, factory?: CreateSchemaFactoryOptions) {
const typebox: typeof t = factory?.typeboxInstance ?? t;
return typebox.Enum(mapEnumValues(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 = (
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 = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, insertConditions) as any;
};
export const createUpdateSchema: CreateUpdateSchema = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, updateConditions) as any;
};
export function createSchemaFactory(options?: CreateSchemaFactoryOptions) {
const createSelectSchema: CreateSelectSchema = (
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 = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, insertConditions, options) as any;
};
const createUpdateSchema: CreateUpdateSchema = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, updateConditions, options) as any;
};
return { createSelectSchema, createInsertSchema, createUpdateSchema };
}

View File

@@ -0,0 +1,94 @@
import type * as t from '@alkdev/typebox';
import type { Assume, Column, DrizzleTypeError, SelectedFieldsFlat, Simplify, Table, View } from 'drizzle-orm';
import type { GetBaseColumn, GetEnumValuesFromColumn, GetTypeboxType, HandleColumn } from './column.types.ts';
import type { GetSelection, RemoveNever } from './utils.ts';
export interface Conditions {
never: (column?: Column) => boolean;
optional: (column: Column) => boolean;
nullable: (column: Column) => boolean;
}
export type BuildRefineColumns<
TColumns extends Record<string, any>,
> = Simplify<
RemoveNever<
{
[K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column ? GetTypeboxType<
TColumn['_']['data'],
TColumn['_']['dataType'],
TColumn['_']['columnType'],
GetEnumValuesFromColumn<TColumn>,
GetBaseColumn<TColumn>
> extends infer TSchema extends t.TSchema ? TSchema
: t.TAny
: TColumns[K] extends infer TObject extends SelectedFieldsFlat<Column> | Table | View
? BuildRefineColumns<GetSelection<TObject>>
: TColumns[K];
}
>
>;
export type BuildRefine<
TColumns extends Record<string, any>,
> = BuildRefineColumns<TColumns> extends infer TBuildColumns ? {
[K in keyof TBuildColumns]?: TBuildColumns[K] extends t.TSchema
? ((schema: TBuildColumns[K]) => t.TSchema) | t.TSchema
: TBuildColumns[K] extends Record<string, any> ? Simplify<BuildRefine<TBuildColumns[K]>>
: never;
}
: never;
type HandleRefinement<
TType extends 'select' | 'insert' | 'update',
TRefinement extends t.TSchema | ((schema: t.TSchema) => t.TSchema),
TColumn extends Column,
> = TRefinement extends (schema: any) => t.TSchema ? (TColumn['_']['notNull'] extends true ? ReturnType<TRefinement>
: t.TUnion<[ReturnType<TRefinement>, t.TNull]>) extends infer TSchema
? TType extends 'update' ? t.TOptional<Assume<TSchema, t.TSchema>> : TSchema
: t.TSchema
: TRefinement;
type IsRefinementDefined<TRefinements, TKey extends string> = TKey extends keyof TRefinements
? TRefinements[TKey] extends t.TSchema | ((schema: any) => any) ? true
: false
: false;
export type BuildSchema<
TType extends 'select' | 'insert' | 'update',
TColumns extends Record<string, any>,
TRefinements extends Record<string, any> | undefined,
> = t.TObject<
Simplify<
RemoveNever<
{
[K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column
? TRefinements extends object
? IsRefinementDefined<TRefinements, Assume<K, string>> extends true
? HandleRefinement<TType, TRefinements[Assume<K, keyof TRefinements>], TColumn>
: HandleColumn<TType, TColumn>
: HandleColumn<TType, TColumn>
: TColumns[K] extends infer TObject extends SelectedFieldsFlat<Column> | Table | View ? BuildSchema<
TType,
GetSelection<TObject>,
TRefinements extends object
? TRefinements[Assume<K, keyof TRefinements>] extends infer TNestedRefinements extends object
? TNestedRefinements
: undefined
: undefined
>
: t.TAny;
}
>
>
>;
export type NoUnknownKeys<
TRefinement extends Record<string, any>,
TCompare extends Record<string, any>,
> = {
[K in keyof TRefinement]: K extends keyof TCompare ? TRefinement[K] extends t.TSchema ? TRefinement[K]
: TRefinement[K] extends Record<string, t.TSchema> ? NoUnknownKeys<TRefinement[K], TCompare[K]>
: TRefinement[K]
: DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>;
};

53
src/schema.types.ts Normal file
View File

@@ -0,0 +1,53 @@
import type * as t from '@alkdev/typebox';
import type { Table, View } from 'drizzle-orm';
import type { PgEnum } from 'drizzle-orm/pg-core';
import type { EnumValuesToEnum } from './column.types.ts';
import type { BuildRefine, BuildSchema, NoUnknownKeys } from './schema.types.internal.ts';
export interface CreateSelectSchema {
<TTable extends Table>(table: TTable): BuildSchema<'select', TTable['_']['columns'], undefined>;
<
TTable extends Table,
TRefine extends BuildRefine<TTable['_']['columns']>,
>(
table: TTable,
refine?: NoUnknownKeys<TRefine, TTable['$inferSelect']>,
): BuildSchema<'select', TTable['_']['columns'], TRefine>;
<TView extends View>(view: TView): BuildSchema<'select', TView['_']['selectedFields'], undefined>;
<
TView extends View,
TRefine extends BuildRefine<TView['_']['selectedFields']>,
>(
view: TView,
refine: NoUnknownKeys<TRefine, TView['$inferSelect']>,
): BuildSchema<'select', TView['_']['selectedFields'], TRefine>;
<TEnum extends PgEnum<any>>(enum_: TEnum): t.TEnum<EnumValuesToEnum<TEnum['enumValues']>>;
}
export interface CreateInsertSchema {
<TTable extends Table>(table: TTable): BuildSchema<'insert', TTable['_']['columns'], undefined>;
<
TTable extends Table,
TRefine extends BuildRefine<Pick<TTable['_']['columns'], keyof TTable['$inferInsert']>>,
>(
table: TTable,
refine?: NoUnknownKeys<TRefine, TTable['$inferInsert']>,
): BuildSchema<'insert', TTable['_']['columns'], TRefine>;
}
export interface CreateUpdateSchema {
<TTable extends Table>(table: TTable): BuildSchema<'update', TTable['_']['columns'], undefined>;
<
TTable extends Table,
TRefine extends BuildRefine<Pick<TTable['_']['columns'], keyof TTable['$inferInsert']>>,
>(
table: TTable,
refine?: TRefine,
): BuildSchema<'update', TTable['_']['columns'], TRefine>;
}
export interface CreateSchemaFactoryOptions {
typeboxInstance?: any;
}

50
src/utils.ts Normal file
View File

@@ -0,0 +1,50 @@
import type { Kind, Static, TSchema } from '@alkdev/typebox';
import type { Column, SelectedFieldsFlat, Table, View } from 'drizzle-orm';
import type { PgEnum } from 'drizzle-orm/pg-core';
import type { literalSchema } from './column.ts';
export function isColumnType<T extends Column>(column: Column, columnTypes: string[]): column is T {
return columnTypes.includes(column.columnType);
}
export function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } {
return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0;
}
export const isPgEnum: (entity: any) => entity is PgEnum<[string, ...string[]]> = isWithEnum as any;
type Literal = Static<typeof literalSchema>;
export type Json = Literal | { [key: string]: Json } | Json[];
export interface JsonSchema extends TSchema {
[Kind]: 'Union';
static: Json;
anyOf: Json;
}
export interface BufferSchema extends TSchema {
[Kind]: 'Buffer';
static: Buffer;
type: 'buffer';
}
export type IsNever<T> = [T] extends [never] ? true : false;
export type ArrayHasAtLeastOneValue<TEnum extends [any, ...any[]] | undefined> = TEnum extends [infer TString, ...any[]]
? TString extends `${infer TLiteral}` ? TLiteral extends any ? true
: false
: false
: false;
export type ColumnIsGeneratedAlwaysAs<TColumn extends Column> = TColumn['_']['identity'] extends 'always' ? true
: TColumn['_']['generated'] extends undefined ? false
: TColumn['_']['generated'] extends infer TGenerated extends { type: string }
? TGenerated['type'] extends 'byDefault' ? false
: true
: true;
export type RemoveNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
};
export type GetSelection<T extends SelectedFieldsFlat<Column> | Table | View> = T extends Table ? T['_']['columns']
: T extends View ? T['_']['selectedFields']
: T;