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

495
tests/mysql.test.ts Normal file
View File

@@ -0,0 +1,495 @@
import { Type as t } from '@alkdev/typebox';
import { type Equal, sql } from 'drizzle-orm';
import { customType, int, mysqlSchema, mysqlTable, mysqlView, serial, text } from 'drizzle-orm/mysql-core';
import { test } from 'vitest';
import { jsonSchema } from '~/column.ts';
import { CONSTANTS } from '~/constants.ts';
import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src';
import { Expect, expectSchemaShape } from './utils.ts';
const intSchema = t.Integer({
minimum: CONSTANTS.INT32_MIN,
maximum: CONSTANTS.INT32_MAX,
});
const serialNumberModeSchema = t.Integer({
minimum: 0,
maximum: Number.MAX_SAFE_INTEGER,
});
const textSchema = t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX });
test('table - select', (tc) => {
const table = mysqlTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table in schema - select', (tc) => {
const schema = mysqlSchema('test');
const table = schema.table('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - insert', (tc) => {
const table = mysqlTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
age: int(),
});
const result = createInsertSchema(table);
const expected = t.Object({
id: t.Optional(serialNumberModeSchema),
name: textSchema,
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - update', (tc) => {
const table = mysqlTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
age: int(),
});
const result = createUpdateSchema(table);
const expected = t.Object({
id: t.Optional(serialNumberModeSchema),
name: t.Optional(textSchema),
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view qb - select', (tc) => {
const table = mysqlTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table));
const result = createSelectSchema(view);
const expected = t.Object({ id: serialNumberModeSchema, age: t.Any() });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view columns - select', (tc) => {
const view = mysqlView('test', {
id: serial().primaryKey(),
name: text().notNull(),
}).as(sql``);
const result = createSelectSchema(view);
const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view with nested fields - select', (tc) => {
const table = mysqlTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const view = mysqlView('test').as((qb) =>
qb.select({
id: table.id,
nested: {
name: table.name,
age: sql``.as('age'),
},
table,
}).from(table)
);
const result = createSelectSchema(view);
const expected = t.Object({
id: serialNumberModeSchema,
nested: t.Object({ name: textSchema, age: t.Any() }),
table: t.Object({ id: serialNumberModeSchema, name: textSchema }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - select', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
});
const result = createSelectSchema(table);
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: intSchema,
c3: t.Union([intSchema, t.Null()]),
c4: intSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - insert', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: intSchema,
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - update', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(intSchema),
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
});
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select with custom data type', (tc) => {
const customText = customType({ dataType: () => 'text' });
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: customText(),
});
const customTextSchema = t.String({ minLength: 1, maxLength: 100 });
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - insert', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - update', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine view - select', (tc) => {
const table = mysqlTable('test', {
c1: int(),
c2: int(),
c3: int(),
c4: int(),
c5: int(),
c6: int(),
});
const view = mysqlView('test').as((qb) =>
qb.select({
c1: table.c1,
c2: table.c2,
c3: table.c3,
nested: {
c4: table.c4,
c5: table.c5,
c6: table.c6,
},
table,
}).from(table)
);
const result = createSelectSchema(view, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: {
c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c6: t.Integer({ minimum: 1, maximum: 10 }),
},
table: {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
},
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: t.Object({
c4: t.Union([intSchema, t.Null()]),
c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c6: t.Integer({ minimum: 1, maximum: 10 }),
}),
table: t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: t.Union([intSchema, t.Null()]),
c5: t.Union([intSchema, t.Null()]),
c6: t.Union([intSchema, t.Null()]),
}),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('all data types', (tc) => {
const table = mysqlTable('test', ({
bigint,
binary,
boolean,
char,
date,
datetime,
decimal,
double,
float,
int,
json,
mediumint,
mysqlEnum,
real,
serial,
smallint,
text,
time,
timestamp,
tinyint,
varchar,
varbinary,
year,
longtext,
mediumtext,
tinytext,
}) => ({
bigint1: bigint({ mode: 'number' }).notNull(),
bigint2: bigint({ mode: 'bigint' }).notNull(),
bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(),
bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(),
binary: binary({ length: 10 }).notNull(),
boolean: boolean().notNull(),
char1: char({ length: 10 }).notNull(),
char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
date1: date({ mode: 'date' }).notNull(),
date2: date({ mode: 'string' }).notNull(),
datetime1: datetime({ mode: 'date' }).notNull(),
datetime2: datetime({ mode: 'string' }).notNull(),
decimal1: decimal().notNull(),
decimal2: decimal({ unsigned: true }).notNull(),
double1: double().notNull(),
double2: double({ unsigned: true }).notNull(),
float1: float().notNull(),
float2: float({ unsigned: true }).notNull(),
int1: int().notNull(),
int2: int({ unsigned: true }).notNull(),
json: json().notNull(),
mediumint1: mediumint().notNull(),
mediumint2: mediumint({ unsigned: true }).notNull(),
enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(),
real: real().notNull(),
serial: serial().notNull(),
smallint1: smallint().notNull(),
smallint2: smallint({ unsigned: true }).notNull(),
text1: text().notNull(),
text2: text({ enum: ['a', 'b', 'c'] }).notNull(),
time: time().notNull(),
timestamp1: timestamp({ mode: 'date' }).notNull(),
timestamp2: timestamp({ mode: 'string' }).notNull(),
tinyint1: tinyint().notNull(),
tinyint2: tinyint({ unsigned: true }).notNull(),
varchar1: varchar({ length: 10 }).notNull(),
varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
varbinary: varbinary({ length: 10 }).notNull(),
year: year().notNull(),
longtext1: longtext().notNull(),
longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(),
mediumtext1: mediumtext().notNull(),
mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(),
tinytext1: tinytext().notNull(),
tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(),
}));
const result = createSelectSchema(table);
const expected = t.Object({
bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }),
bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }),
bigint3: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
bigint4: t.BigInt({ minimum: 0n, maximum: CONSTANTS.INT64_UNSIGNED_MAX }),
binary: t.String(),
boolean: t.Boolean(),
char1: t.String({ minLength: 10, maxLength: 10 }),
char2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
date1: t.Date(),
date2: t.String(),
datetime1: t.Date(),
datetime2: t.String(),
decimal1: t.String(),
decimal2: t.String(),
double1: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
double2: t.Number({ minimum: 0, maximum: CONSTANTS.INT48_UNSIGNED_MAX }),
float1: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }),
float2: t.Number({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }),
int1: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }),
int2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT32_UNSIGNED_MAX }),
json: jsonSchema,
mediumint1: t.Integer({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }),
mediumint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }),
enum: t.Enum({ a: 'a', b: 'b', c: 'c' }),
real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
serial: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
smallint1: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }),
smallint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT16_UNSIGNED_MAX }),
text1: t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }),
text2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
time: t.String(),
timestamp1: t.Date(),
timestamp2: t.String(),
tinyint1: t.Integer({ minimum: CONSTANTS.INT8_MIN, maximum: CONSTANTS.INT8_MAX }),
tinyint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT8_UNSIGNED_MAX }),
varchar1: t.String({ maxLength: 10 }),
varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
varbinary: t.String(),
year: t.Integer({ minimum: 1901, maximum: 2155 }),
longtext1: t.String({ maxLength: CONSTANTS.INT32_UNSIGNED_MAX }),
longtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
mediumtext1: t.String({ maxLength: CONSTANTS.INT24_UNSIGNED_MAX }),
mediumtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
tinytext1: t.String({ maxLength: CONSTANTS.INT8_UNSIGNED_MAX }),
tinytext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
/* Disallow unknown keys in table refinement - select */ {
const table = mysqlTable('test', { id: int() });
// @ts-expect-error
createSelectSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - insert */ {
const table = mysqlTable('test', { id: int() });
// @ts-expect-error
createInsertSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - update */ {
const table = mysqlTable('test', { id: int() });
// @ts-expect-error
createUpdateSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in view qb - select */ {
const table = mysqlTable('test', { id: int() });
const view = mysqlView('test').as((qb) => qb.select().from(table));
const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table));
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
// @ts-expect-error
createSelectSchema(nestedSelect, { table: { unknown: t.String() } });
}
/* Disallow unknown keys in view columns - select */ {
const view = mysqlView('test', { id: int() }).as(sql``);
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
}

540
tests/pg.test.ts Normal file
View File

@@ -0,0 +1,540 @@
import { Type as t } from '@alkdev/typebox';
import { type Equal, sql } from 'drizzle-orm';
import {
customType,
integer,
pgEnum,
pgMaterializedView,
pgSchema,
pgTable,
pgView,
serial,
text,
} from 'drizzle-orm/pg-core';
import { test } from 'vitest';
import { jsonSchema } from '~/column.ts';
import { CONSTANTS } from '~/constants.ts';
import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src';
import { Expect, expectEnumValues, expectSchemaShape } from './utils.ts';
const integerSchema = t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX });
const textSchema = t.String();
test('table - select', (tc) => {
const table = pgTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: integerSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table in schema - select', (tc) => {
const schema = pgSchema('test');
const table = schema.table('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: integerSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - insert', (tc) => {
const table = pgTable('test', {
id: integer().generatedAlwaysAsIdentity().primaryKey(),
name: text().notNull(),
age: integer(),
});
const result = createInsertSchema(table);
const expected = t.Object({ name: textSchema, age: t.Optional(t.Union([integerSchema, t.Null()])) });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - update', (tc) => {
const table = pgTable('test', {
id: integer().generatedAlwaysAsIdentity().primaryKey(),
name: text().notNull(),
age: integer(),
});
const result = createUpdateSchema(table);
const expected = t.Object({
name: t.Optional(textSchema),
age: t.Optional(t.Union([integerSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view qb - select', (tc) => {
const table = pgTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const view = pgView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table));
const result = createSelectSchema(view);
const expected = t.Object({ id: integerSchema, age: t.Any() });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view columns - select', (tc) => {
const view = pgView('test', {
id: serial().primaryKey(),
name: text().notNull(),
}).as(sql``);
const result = createSelectSchema(view);
const expected = t.Object({ id: integerSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('materialized view qb - select', (tc) => {
const table = pgTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const view = pgMaterializedView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table));
const result = createSelectSchema(view);
const expected = t.Object({ id: integerSchema, age: t.Any() });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('materialized view columns - select', (tc) => {
const view = pgView('test', {
id: serial().primaryKey(),
name: text().notNull(),
}).as(sql``);
const result = createSelectSchema(view);
const expected = t.Object({ id: integerSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view with nested fields - select', (tc) => {
const table = pgTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const view = pgMaterializedView('test').as((qb) =>
qb.select({
id: table.id,
nested: {
name: table.name,
age: sql``.as('age'),
},
table,
}).from(table)
);
const result = createSelectSchema(view);
const expected = t.Object({
id: integerSchema,
nested: t.Object({ name: textSchema, age: t.Any() }),
table: t.Object({ id: integerSchema, name: textSchema }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('enum - select', (tc) => {
const enum_ = pgEnum('test', ['a', 'b', 'c']);
const result = createSelectSchema(enum_);
const expected = t.Enum({ a: 'a', b: 'b', c: 'c' });
expectEnumValues(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - select', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().default(1),
c4: integer().notNull().default(1),
});
const result = createSelectSchema(table);
const expected = t.Object({
c1: t.Union([integerSchema, t.Null()]),
c2: integerSchema,
c3: t.Union([integerSchema, t.Null()]),
c4: integerSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - insert', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().default(1),
c4: integer().notNull().default(1),
c5: integer().generatedAlwaysAs(1),
c6: integer().generatedAlwaysAsIdentity(),
c7: integer().generatedByDefaultAsIdentity(),
});
const result = createInsertSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([integerSchema, t.Null()])),
c2: integerSchema,
c3: t.Optional(t.Union([integerSchema, t.Null()])),
c4: t.Optional(integerSchema),
c7: t.Optional(integerSchema),
});
expectSchemaShape(tc, expected).from(result);
});
test('nullability - update', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().default(1),
c4: integer().notNull().default(1),
c5: integer().generatedAlwaysAs(1),
c6: integer().generatedAlwaysAsIdentity(),
c7: integer().generatedByDefaultAsIdentity(),
});
const result = createUpdateSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([integerSchema, t.Null()])),
c2: t.Optional(integerSchema),
c3: t.Optional(t.Union([integerSchema, t.Null()])),
c4: t.Optional(integerSchema),
c7: t.Optional(integerSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().notNull(),
});
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Union([integerSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select with custom data type', (tc) => {
const customText = customType({ dataType: () => 'text' });
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().notNull(),
c4: customText(),
});
const customTextSchema = t.String({ minLength: 1, maxLength: 100 });
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
const expected = t.Object({
c1: t.Union([integerSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - insert', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().notNull(),
c4: integer().generatedAlwaysAs(1),
});
const result = createInsertSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([integerSchema, t.Null()])),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - update', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer().notNull(),
c3: integer().notNull(),
c4: integer().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([integerSchema, t.Null()])),
c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine view - select', (tc) => {
const table = pgTable('test', {
c1: integer(),
c2: integer(),
c3: integer(),
c4: integer(),
c5: integer(),
c6: integer(),
});
const view = pgView('test').as((qb) =>
qb.select({
c1: table.c1,
c2: table.c2,
c3: table.c3,
nested: {
c4: table.c4,
c5: table.c5,
c6: table.c6,
},
table,
}).from(table)
);
const result = createSelectSchema(view, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: {
c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c6: t.Integer({ minimum: 1, maximum: 10 }),
},
table: {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
},
});
const expected = t.Object({
c1: t.Union([integerSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: t.Object({
c4: t.Union([integerSchema, t.Null()]),
c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c6: t.Integer({ minimum: 1, maximum: 10 }),
}),
table: t.Object({
c1: t.Union([integerSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: t.Union([integerSchema, t.Null()]),
c5: t.Union([integerSchema, t.Null()]),
c6: t.Union([integerSchema, t.Null()]),
}),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('all data types', (tc) => {
const table = pgTable('test', ({
bigint,
bigserial,
bit,
boolean,
date,
char,
cidr,
doublePrecision,
geometry,
halfvec,
inet,
integer,
interval,
json,
jsonb,
line,
macaddr,
macaddr8,
numeric,
point,
real,
serial,
smallint,
smallserial,
text,
sparsevec,
time,
timestamp,
uuid,
varchar,
vector,
}) => ({
bigint1: bigint({ mode: 'number' }).notNull(),
bigint2: bigint({ mode: 'bigint' }).notNull(),
bigserial1: bigserial({ mode: 'number' }).notNull(),
bigserial2: bigserial({ mode: 'bigint' }).notNull(),
bit: bit({ dimensions: 5 }).notNull(),
boolean: boolean().notNull(),
date1: date({ mode: 'date' }).notNull(),
date2: date({ mode: 'string' }).notNull(),
char1: char({ length: 10 }).notNull(),
char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
cidr: cidr().notNull(),
doublePrecision: doublePrecision().notNull(),
geometry1: geometry({ type: 'point', mode: 'tuple' }).notNull(),
geometry2: geometry({ type: 'point', mode: 'xy' }).notNull(),
halfvec: halfvec({ dimensions: 3 }).notNull(),
inet: inet().notNull(),
integer: integer().notNull(),
interval: interval().notNull(),
json: json().notNull(),
jsonb: jsonb().notNull(),
line1: line({ mode: 'abc' }).notNull(),
line2: line({ mode: 'tuple' }).notNull(),
macaddr: macaddr().notNull(),
macaddr8: macaddr8().notNull(),
numeric: numeric().notNull(),
point1: point({ mode: 'xy' }).notNull(),
point2: point({ mode: 'tuple' }).notNull(),
real: real().notNull(),
serial: serial().notNull(),
smallint: smallint().notNull(),
smallserial: smallserial().notNull(),
text1: text().notNull(),
text2: text({ enum: ['a', 'b', 'c'] }).notNull(),
sparsevec: sparsevec({ dimensions: 3 }).notNull(),
time: time().notNull(),
timestamp1: timestamp({ mode: 'date' }).notNull(),
timestamp2: timestamp({ mode: 'string' }).notNull(),
uuid: uuid().notNull(),
varchar1: varchar({ length: 10 }).notNull(),
varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
vector: vector({ dimensions: 3 }).notNull(),
array1: integer().array().notNull(),
array2: integer().array().array(2).notNull(),
array3: varchar({ length: 10 }).array().array(2).notNull(),
}));
const result = createSelectSchema(table);
const expected = t.Object({
bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }),
bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }),
bigserial1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }),
bigserial2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }),
bit: t.RegExp(/^[01]+$/, { maxLength: 5 }),
boolean: t.Boolean(),
date1: t.Date(),
date2: t.String(),
char1: t.String({ minLength: 10, maxLength: 10 }),
char2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
cidr: t.String(),
doublePrecision: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
geometry1: t.Tuple([t.Number(), t.Number()]),
geometry2: t.Object({ x: t.Number(), y: t.Number() }),
halfvec: t.Array(t.Number(), { minItems: 3, maxItems: 3 }),
inet: t.String(),
integer: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }),
interval: t.String(),
json: jsonSchema,
jsonb: jsonSchema,
line1: t.Object({ a: t.Number(), b: t.Number(), c: t.Number() }),
line2: t.Tuple([t.Number(), t.Number(), t.Number()]),
macaddr: t.String(),
macaddr8: t.String(),
numeric: t.String(),
point1: t.Object({ x: t.Number(), y: t.Number() }),
point2: t.Tuple([t.Number(), t.Number()]),
real: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }),
serial: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }),
smallint: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }),
smallserial: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }),
text1: t.String(),
text2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
sparsevec: t.String(),
time: t.String(),
timestamp1: t.Date(),
timestamp2: t.String(),
uuid: t.String({ format: 'uuid' }),
varchar1: t.String({ maxLength: 10 }),
varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
vector: t.Array(t.Number(), { minItems: 3, maxItems: 3 }),
array1: t.Array(integerSchema),
array2: t.Array(t.Array(integerSchema), { minItems: 2, maxItems: 2 }),
array3: t.Array(t.Array(t.String({ maxLength: 10 })), { minItems: 2, maxItems: 2 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
/* Disallow unknown keys in table refinement - select */ {
const table = pgTable('test', { id: integer() });
// @ts-expect-error
createSelectSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - insert */ {
const table = pgTable('test', { id: integer() });
// @ts-expect-error
createInsertSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - update */ {
const table = pgTable('test', { id: integer() });
// @ts-expect-error
createUpdateSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in view qb - select */ {
const table = pgTable('test', { id: integer() });
const view = pgView('test').as((qb) => qb.select().from(table));
const mView = pgMaterializedView('test').as((qb) => qb.select().from(table));
const nestedSelect = pgView('test').as((qb) => qb.select({ table }).from(table));
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
// @ts-expect-error
createSelectSchema(mView, { unknown: t.String() });
// @ts-expect-error
createSelectSchema(nestedSelect, { table: { unknown: t.String() } });
}
/* Disallow unknown keys in view columns - select */ {
const view = pgView('test', { id: integer() }).as(sql``);
const mView = pgView('test', { id: integer() }).as(sql``);
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
// @ts-expect-error
createSelectSchema(mView, { unknown: t.String() });
}

497
tests/singlestore.test.ts Normal file
View File

@@ -0,0 +1,497 @@
import { Type as t } from '@alkdev/typebox';
import { type Equal, sql } from 'drizzle-orm';
import { customType, int, serial, singlestoreSchema, singlestoreTable, text } from 'drizzle-orm/singlestore-core';
import { test } from 'vitest';
import { jsonSchema } from '~/column.ts';
import { CONSTANTS } from '~/constants.ts';
import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src';
import { Expect, expectSchemaShape } from './utils.ts';
const intSchema = t.Integer({
minimum: CONSTANTS.INT32_MIN,
maximum: CONSTANTS.INT32_MAX,
});
const serialNumberModeSchema = t.Integer({
minimum: 0,
maximum: Number.MAX_SAFE_INTEGER,
});
const textSchema = t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX });
test('table - select', (tc) => {
const table = singlestoreTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table in schema - select', (tc) => {
const schema = singlestoreSchema('test');
const table = schema.table('test', {
id: serial().primaryKey(),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - insert', (tc) => {
const table = singlestoreTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
age: int(),
});
const result = createInsertSchema(table);
const expected = t.Object({
id: t.Optional(serialNumberModeSchema),
name: textSchema,
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - update', (tc) => {
const table = singlestoreTable('test', {
id: serial().primaryKey(),
name: text().notNull(),
age: int(),
});
const result = createUpdateSchema(table);
const expected = t.Object({
id: t.Optional(serialNumberModeSchema),
name: t.Optional(textSchema),
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
// TODO: SingleStore doesn't support views yet. Add these tests when they're added
// test('view qb - select', (tc) => {
// const table = singlestoreTable('test', {
// id: serial().primaryKey(),
// name: text().notNull(),
// });
// const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table));
// const result = createSelectSchema(view);
// const expected = t.Object({ id: serialNumberModeSchema, age: t.Any() });
// expectSchemaShape(tc, expected).from(result);
// Expect<Equal<typeof result, typeof expected>>();
// });
// test('view columns - select', (tc) => {
// const view = mysqlView('test', {
// id: serial().primaryKey(),
// name: text().notNull(),
// }).as(sql``);
// const result = createSelectSchema(view);
// const expected = t.Object({ id: serialNumberModeSchema, name: textSchema });
// expectSchemaShape(tc, expected).from(result);
// Expect<Equal<typeof result, typeof expected>>();
// });
// test('view with nested fields - select', (tc) => {
// const table = singlestoreTable('test', {
// id: serial().primaryKey(),
// name: text().notNull(),
// });
// const view = mysqlView('test').as((qb) =>
// qb.select({
// id: table.id,
// nested: {
// name: table.name,
// age: sql``.as('age'),
// },
// table,
// }).from(table)
// );
// const result = createSelectSchema(view);
// const expected = t.Object({
// id: serialNumberModeSchema,
// nested: t.Object({ name: textSchema, age: t.Any() }),
// table: t.Object({ id: serialNumberModeSchema, name: textSchema }),
// });
// expectSchemaShape(tc, expected).from(result);
// Expect<Equal<typeof result, typeof expected>>();
// });
test('nullability - select', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
});
const result = createSelectSchema(table);
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: intSchema,
c3: t.Union([intSchema, t.Null()]),
c4: intSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - insert', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: intSchema,
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - update', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(intSchema),
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
});
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select with custom data type', (tc) => {
const customText = customType({ dataType: () => 'text' });
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: customText(),
});
const customTextSchema = t.String({ minLength: 1, maxLength: 100 });
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - insert', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - update', (tc) => {
const table = singlestoreTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
// test('refine view - select', (tc) => {
// const table = singlestoreTable('test', {
// c1: int(),
// c2: int(),
// c3: int(),
// c4: int(),
// c5: int(),
// c6: int(),
// });
// const view = mysqlView('test').as((qb) =>
// qb.select({
// c1: table.c1,
// c2: table.c2,
// c3: table.c3,
// nested: {
// c4: table.c4,
// c5: table.c5,
// c6: table.c6,
// },
// table,
// }).from(table)
// );
// const result = createSelectSchema(view, {
// c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
// c3: t.Integer({ minimum: 1, maximum: 10 }),
// nested: {
// c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
// c6: t.Integer({ minimum: 1, maximum: 10 }),
// },
// table: {
// c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
// c3: t.Integer({ minimum: 1, maximum: 10 }),
// },
// });
// const expected = t.Object({
// c1: t.Union([intSchema, t.Null()]),
// c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
// c3: t.Integer({ minimum: 1, maximum: 10 }),
// nested: t.Object({
// c4: t.Union([intSchema, t.Null()]),
// c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
// c6: t.Integer({ minimum: 1, maximum: 10 }),
// }),
// table: t.Object({
// c1: t.Union([intSchema, t.Null()]),
// c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]),
// c3: t.Integer({ minimum: 1, maximum: 10 }),
// c4: t.Union([intSchema, t.Null()]),
// c5: t.Union([intSchema, t.Null()]),
// c6: t.Union([intSchema, t.Null()]),
// }),
// });
// expectSchemaShape(tc, expected).from(result);
// Expect<Equal<typeof result, typeof expected>>();
// });
test('all data types', (tc) => {
const table = singlestoreTable('test', ({
bigint,
binary,
boolean,
char,
date,
datetime,
decimal,
double,
float,
int,
json,
mediumint,
singlestoreEnum,
real,
serial,
smallint,
text,
time,
timestamp,
tinyint,
varchar,
varbinary,
year,
longtext,
mediumtext,
tinytext,
}) => ({
bigint1: bigint({ mode: 'number' }).notNull(),
bigint2: bigint({ mode: 'bigint' }).notNull(),
bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(),
bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(),
binary: binary({ length: 10 }).notNull(),
boolean: boolean().notNull(),
char1: char({ length: 10 }).notNull(),
char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
date1: date({ mode: 'date' }).notNull(),
date2: date({ mode: 'string' }).notNull(),
datetime1: datetime({ mode: 'date' }).notNull(),
datetime2: datetime({ mode: 'string' }).notNull(),
decimal1: decimal().notNull(),
decimal2: decimal({ unsigned: true }).notNull(),
double1: double().notNull(),
double2: double({ unsigned: true }).notNull(),
float1: float().notNull(),
float2: float({ unsigned: true }).notNull(),
int1: int().notNull(),
int2: int({ unsigned: true }).notNull(),
json: json().notNull(),
mediumint1: mediumint().notNull(),
mediumint2: mediumint({ unsigned: true }).notNull(),
enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(),
real: real().notNull(),
serial: serial().notNull(),
smallint1: smallint().notNull(),
smallint2: smallint({ unsigned: true }).notNull(),
text1: text().notNull(),
text2: text({ enum: ['a', 'b', 'c'] }).notNull(),
time: time().notNull(),
timestamp1: timestamp({ mode: 'date' }).notNull(),
timestamp2: timestamp({ mode: 'string' }).notNull(),
tinyint1: tinyint().notNull(),
tinyint2: tinyint({ unsigned: true }).notNull(),
varchar1: varchar({ length: 10 }).notNull(),
varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(),
varbinary: varbinary({ length: 10 }).notNull(),
year: year().notNull(),
longtext1: longtext().notNull(),
longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(),
mediumtext1: mediumtext().notNull(),
mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(),
tinytext1: tinytext().notNull(),
tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(),
}));
const result = createSelectSchema(table);
const expected = t.Object({
bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }),
bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }),
bigint3: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
bigint4: t.BigInt({ minimum: 0n, maximum: CONSTANTS.INT64_UNSIGNED_MAX }),
binary: t.String(),
boolean: t.Boolean(),
char1: t.String({ minLength: 10, maxLength: 10 }),
char2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
date1: t.Date(),
date2: t.String(),
datetime1: t.Date(),
datetime2: t.String(),
decimal1: t.String(),
decimal2: t.String(),
double1: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
double2: t.Number({ minimum: 0, maximum: CONSTANTS.INT48_UNSIGNED_MAX }),
float1: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }),
float2: t.Number({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }),
int1: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }),
int2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT32_UNSIGNED_MAX }),
json: jsonSchema,
mediumint1: t.Integer({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }),
mediumint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }),
enum: t.Enum({ a: 'a', b: 'b', c: 'c' }),
real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
serial: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
smallint1: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }),
smallint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT16_UNSIGNED_MAX }),
text1: t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }),
text2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
time: t.String(),
timestamp1: t.Date(),
timestamp2: t.String(),
tinyint1: t.Integer({ minimum: CONSTANTS.INT8_MIN, maximum: CONSTANTS.INT8_MAX }),
tinyint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT8_UNSIGNED_MAX }),
varchar1: t.String({ maxLength: 10 }),
varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
varbinary: t.String(),
year: t.Integer({ minimum: 1901, maximum: 2155 }),
longtext1: t.String({ maxLength: CONSTANTS.INT32_UNSIGNED_MAX }),
longtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
mediumtext1: t.String({ maxLength: CONSTANTS.INT24_UNSIGNED_MAX }),
mediumtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
tinytext1: t.String({ maxLength: CONSTANTS.INT8_UNSIGNED_MAX }),
tinytext2: t.Enum({ a: 'a', b: 'b', c: 'c' }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
/* Disallow unknown keys in table refinement - select */ {
const table = singlestoreTable('test', { id: int() });
// @ts-expect-error
createSelectSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - insert */ {
const table = singlestoreTable('test', { id: int() });
// @ts-expect-error
createInsertSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - update */ {
const table = singlestoreTable('test', { id: int() });
// @ts-expect-error
createUpdateSchema(table, { unknown: t.String() });
}
// /* Disallow unknown keys in view qb - select */ {
// const table = singlestoreTable('test', { id: int() });
// const view = mysqlView('test').as((qb) => qb.select().from(table));
// const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table));
// // @ts-expect-error
// createSelectSchema(view, { unknown: t.String() });
// // @ts-expect-error
// createSelectSchema(nestedSelect, { table: { unknown: t.String() } });
// }
// /* Disallow unknown keys in view columns - select */ {
// const view = mysqlView('test', { id: int() }).as(sql``);
// // @ts-expect-error
// createSelectSchema(view, { unknown: t.String() });
// }

389
tests/sqlite.test.ts Normal file
View File

@@ -0,0 +1,389 @@
import { Type as t } from '@alkdev/typebox';
import { type Equal, sql } from 'drizzle-orm';
import { customType, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core';
import { test } from 'vitest';
import { bufferSchema, jsonSchema } from '~/column.ts';
import { CONSTANTS } from '~/constants.ts';
import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src';
import { Expect, expectSchemaShape } from './utils.ts';
const intSchema = t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
const textSchema = t.String();
test('table - select', (tc) => {
const table = sqliteTable('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
});
const result = createSelectSchema(table);
const expected = t.Object({ id: intSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - insert', (tc) => {
const table = sqliteTable('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
age: int(),
});
const result = createInsertSchema(table);
const expected = t.Object({
id: t.Optional(intSchema),
name: textSchema,
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('table - update', (tc) => {
const table = sqliteTable('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
age: int(),
});
const result = createUpdateSchema(table);
const expected = t.Object({
id: t.Optional(intSchema),
name: t.Optional(textSchema),
age: t.Optional(t.Union([intSchema, t.Null()])),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view qb - select', (tc) => {
const table = sqliteTable('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
});
const view = sqliteView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table));
const result = createSelectSchema(view);
const expected = t.Object({ id: intSchema, age: t.Any() });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view columns - select', (tc) => {
const view = sqliteView('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
}).as(sql``);
const result = createSelectSchema(view);
const expected = t.Object({ id: intSchema, name: textSchema });
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('view with nested fields - select', (tc) => {
const table = sqliteTable('test', {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
});
const view = sqliteView('test').as((qb) =>
qb.select({
id: table.id,
nested: {
name: table.name,
age: sql``.as('age'),
},
table,
}).from(table)
);
const result = createSelectSchema(view);
const expected = t.Object({
id: intSchema,
nested: t.Object({ name: textSchema, age: t.Any() }),
table: t.Object({ id: intSchema, name: textSchema }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - select', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
});
const result = createSelectSchema(table);
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: intSchema,
c3: t.Union([intSchema, t.Null()]),
c4: intSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - insert', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: intSchema,
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('nullability - update', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().default(1),
c4: int().notNull().default(1),
c5: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table);
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(intSchema),
c3: t.Optional(t.Union([intSchema, t.Null()])),
c4: t.Optional(intSchema),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
});
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - select with custom data type', (tc) => {
const customText = customType({ dataType: () => 'text' });
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: customText(),
});
const customTextSchema = t.String({ minLength: 1, maxLength: 100 });
const result = createSelectSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: customTextSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - insert', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createInsertSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine table - update', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int().notNull(),
c3: int().notNull(),
c4: int().generatedAlwaysAs(1),
});
const result = createUpdateSchema(table, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
const expected = t.Object({
c1: t.Optional(t.Union([intSchema, t.Null()])),
c2: t.Optional(t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 })),
c3: t.Integer({ minimum: 1, maximum: 10 }),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('refine view - select', (tc) => {
const table = sqliteTable('test', {
c1: int(),
c2: int(),
c3: int(),
c4: int(),
c5: int(),
c6: int(),
});
const view = sqliteView('test').as((qb) =>
qb.select({
c1: table.c1,
c2: table.c2,
c3: table.c3,
nested: {
c4: table.c4,
c5: table.c5,
c6: table.c6,
},
table,
}).from(table)
);
const result = createSelectSchema(view, {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: {
c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c6: t.Integer({ minimum: 1, maximum: 10 }),
},
table: {
c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }),
c3: t.Integer({ minimum: 1, maximum: 10 }),
},
});
const expected = t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
nested: t.Object({
c4: t.Union([intSchema, t.Null()]),
c5: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]),
c6: t.Integer({ minimum: 1, maximum: 10 }),
}),
table: t.Object({
c1: t.Union([intSchema, t.Null()]),
c2: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]),
c3: t.Integer({ minimum: 1, maximum: 10 }),
c4: t.Union([intSchema, t.Null()]),
c5: t.Union([intSchema, t.Null()]),
c6: t.Union([intSchema, t.Null()]),
}),
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
test('all data types', (tc) => {
const table = sqliteTable('test', ({
blob,
integer,
numeric,
real,
text,
}) => ({
blob1: blob({ mode: 'buffer' }).notNull(),
blob2: blob({ mode: 'bigint' }).notNull(),
blob3: blob({ mode: 'json' }).notNull(),
integer1: integer({ mode: 'number' }).notNull(),
integer2: integer({ mode: 'boolean' }).notNull(),
integer3: integer({ mode: 'timestamp' }).notNull(),
integer4: integer({ mode: 'timestamp_ms' }).notNull(),
numeric: numeric().notNull(),
real: real().notNull(),
text1: text({ mode: 'text' }).notNull(),
text2: text({ mode: 'text', length: 10 }).notNull(),
text3: text({ mode: 'text', enum: ['a', 'b', 'c'] }).notNull(),
text4: text({ mode: 'json' }).notNull(),
}));
const result = createSelectSchema(table);
const expected = t.Object({
blob1: bufferSchema,
blob2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }),
blob3: jsonSchema,
integer1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }),
integer2: t.Boolean(),
integer3: t.Date(),
integer4: t.Date(),
numeric: t.String(),
real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }),
text1: t.String(),
text2: t.String({ maxLength: 10 }),
text3: t.Enum({ a: 'a', b: 'b', c: 'c' }),
text4: jsonSchema,
});
expectSchemaShape(tc, expected).from(result);
Expect<Equal<typeof result, typeof expected>>();
});
/* Disallow unknown keys in table refinement - select */ {
const table = sqliteTable('test', { id: int() });
// @ts-expect-error
createSelectSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - insert */ {
const table = sqliteTable('test', { id: int() });
// @ts-expect-error
createInsertSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in table refinement - update */ {
const table = sqliteTable('test', { id: int() });
// @ts-expect-error
createUpdateSchema(table, { unknown: t.String() });
}
/* Disallow unknown keys in view qb - select */ {
const table = sqliteTable('test', { id: int() });
const view = sqliteView('test').as((qb) => qb.select().from(table));
const nestedSelect = sqliteView('test').as((qb) => qb.select({ table }).from(table));
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
// @ts-expect-error
createSelectSchema(nestedSelect, { table: { unknown: t.String() } });
}
/* Disallow unknown keys in view columns - select */ {
const view = sqliteView('test', { id: int() }).as(sql``);
// @ts-expect-error
createSelectSchema(view, { unknown: t.String() });
}

11
tests/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"noEmit": true,
"rootDir": "..",
"outDir": "./.cache"
},
"include": [".", "../src"]
}

34
tests/utils.ts Normal file
View File

@@ -0,0 +1,34 @@
import type * as t from '@alkdev/typebox';
import { expect, type TaskContext } from 'vitest';
function removeKeysFromObject(obj: Record<string, any>, keys: string[]) {
for (const key of keys) {
delete obj[key];
}
return obj;
}
export function expectSchemaShape<T extends t.TObject>(t: TaskContext, expected: T) {
return {
from(actual: T) {
expect(Object.keys(actual.properties)).toStrictEqual(Object.keys(expected.properties));
const keys = ['$id', '$schema', 'title', 'description', 'default', 'examples', 'readOnly', 'writeOnly'];
for (const key of Object.keys(actual.properties)) {
expect(removeKeysFromObject(actual.properties[key]!, keys)).toStrictEqual(
removeKeysFromObject(expected.properties[key]!, keys),
);
}
},
};
}
export function expectEnumValues<T extends t.TEnum<any>>(t: TaskContext, expected: T) {
return {
from(actual: T) {
expect(actual.anyOf).toStrictEqual(expected.anyOf);
},
};
}
export function Expect<_ extends true>() {}