import { type Equal, sql } from 'drizzle-orm'; import { blob, customType, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core'; import type { TopLevelCondition } from 'json-rules-engine'; import * as v from 'valibot'; 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 = v.pipe( v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer(), ); const textSchema = v.string(); test('table - select', (t) => { const table = sqliteTable('test', { id: int().primaryKey({ autoIncrement: true }), name: text().notNull(), }); const result = createSelectSchema(table); const expected = v.object({ id: intSchema, name: textSchema }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('table - insert', (t) => { const table = sqliteTable('test', { id: int().primaryKey({ autoIncrement: true }), name: text().notNull(), age: int(), }); const result = createInsertSchema(table); const expected = v.object({ id: v.optional(intSchema), name: textSchema, age: v.optional(v.nullable(intSchema)) }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('table - update', (t) => { const table = sqliteTable('test', { id: int().primaryKey({ autoIncrement: true }), name: text().notNull(), age: int(), }); const result = createUpdateSchema(table); const expected = v.object({ id: v.optional(intSchema), name: v.optional(textSchema), age: v.optional(v.nullable(intSchema)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view qb - select', (t) => { 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 = v.object({ id: intSchema, age: v.any() }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view columns - select', (t) => { const view = sqliteView('test', { id: int().primaryKey({ autoIncrement: true }), name: text().notNull(), }).as(sql``); const result = createSelectSchema(view); const expected = v.object({ id: intSchema, name: textSchema }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view with nested fields - select', (t) => { 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 = v.object({ id: intSchema, nested: v.object({ name: textSchema, age: v.any() }), table: v.object({ id: intSchema, name: textSchema }), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('nullability - select', (t) => { const table = sqliteTable('test', { c1: int(), c2: int().notNull(), c3: int().default(1), c4: int().notNull().default(1), }); const result = createSelectSchema(table); const expected = v.object({ c1: v.nullable(intSchema), c2: intSchema, c3: v.nullable(intSchema), c4: intSchema, }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('nullability - insert', (t) => { 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 = v.object({ c1: v.optional(v.nullable(intSchema)), c2: intSchema, c3: v.optional(v.nullable(intSchema)), c4: v.optional(intSchema), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('nullability - update', (t) => { 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 = v.object({ c1: v.optional(v.nullable(intSchema)), c2: v.optional(intSchema), c3: v.optional(v.nullable(intSchema)), c4: v.optional(intSchema), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - select', (t) => { const table = sqliteTable('test', { c1: int(), c2: int().notNull(), c3: int().notNull(), }); const result = createSelectSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.nullable(intSchema), c2: v.pipe(intSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - select with custom data type', (t) => { const customText = customType({ dataType: () => 'text' }); const table = sqliteTable('test', { c1: int(), c2: int().notNull(), c3: int().notNull(), c4: customText(), }); const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); const result = createSelectSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), c4: customTextSchema, }); const expected = v.object({ c1: v.nullable(intSchema), c2: v.pipe(intSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), c4: customTextSchema, }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - insert', (t) => { const table = sqliteTable('test', { c1: int(), c2: int().notNull(), c3: int().notNull(), c4: int().generatedAlwaysAs(1), }); const result = createInsertSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.optional(v.nullable(intSchema)), c2: v.pipe(intSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - update', (t) => { const table = sqliteTable('test', { c1: int(), c2: int().notNull(), c3: int().notNull(), c4: int().generatedAlwaysAs(1), }); const result = createUpdateSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.optional(v.nullable(intSchema)), c2: v.optional(v.pipe(intSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine view - select', (t) => { 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) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), nested: { c5: (schema) => v.pipe(schema, v.maxValue(1000)), c6: v.pipe(v.string(), v.transform(Number)), }, table: { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }, }); const expected = v.object({ c1: v.nullable(intSchema), c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), nested: v.object({ c4: v.nullable(intSchema), c5: v.nullable(v.pipe(intSchema, v.maxValue(1000))), c6: v.pipe(v.string(), v.transform(Number)), }), table: v.object({ c1: v.nullable(intSchema), c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), c4: v.nullable(intSchema), c5: v.nullable(intSchema), c6: v.nullable(intSchema), }), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('all data types', (t) => { 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 = v.object({ blob1: bufferSchema, blob2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), blob3: jsonSchema, integer1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), integer2: v.boolean(), integer3: v.date(), integer4: v.date(), numeric: v.string(), real: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), text1: v.string(), text2: v.pipe(v.string(), v.maxLength(10 as number)), text3: v.enum({ a: 'a', b: 'b', c: 'c' }), text4: jsonSchema, }); expectSchemaShape(t, expected).from(result); Expect>(); }); /* Infinitely recursive type */ { const TopLevelCondition: v.GenericSchema = v.custom(() => true); const table = sqliteTable('test', { json1: text({ mode: 'json' }).$type().notNull(), json2: blob({ mode: 'json' }).$type(), }); const result = createSelectSchema(table); const expected = v.object({ json1: TopLevelCondition, json2: v.nullable(TopLevelCondition), }); Expect, v.InferOutput>>(); } /* Disallow unknown keys in table refinement - select */ { const table = sqliteTable('test', { id: int() }); // @ts-expect-error createSelectSchema(table, { unknown: v.string() }); } /* Disallow unknown keys in table refinement - insert */ { const table = sqliteTable('test', { id: int() }); // @ts-expect-error createInsertSchema(table, { unknown: v.string() }); } /* Disallow unknown keys in table refinement - update */ { const table = sqliteTable('test', { id: int() }); // @ts-expect-error createUpdateSchema(table, { unknown: v.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: v.string() }); // @ts-expect-error createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); } /* Disallow unknown keys in view columns - select */ { const view = sqliteView('test', { id: int() }).as(sql``); // @ts-expect-error createSelectSchema(view, { unknown: v.string() }); }