@zeno-lib/schema 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@zeno-lib/schema",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "files": [
9
+ "src"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "repository": {
15
+ "url": "https://github.com/zeno-lib/zeno.git",
16
+ "directory": "packages/schema"
17
+ },
18
+ "scripts": {
19
+ "test": "vitest --run",
20
+ "test:watch": "vitest watch",
21
+ "types:check": "tsc --noEmit"
22
+ },
23
+ "peerDependencies": {
24
+ "drizzle-orm": "1.0.0-rc.3",
25
+ "zod": ">=4"
26
+ },
27
+ "devDependencies": {
28
+ "@zeno-lib/typescript": "workspace:^",
29
+ "drizzle-orm": "1.0.0-rc.3",
30
+ "typescript": "6.0.3",
31
+ "vitest": "4.1.0",
32
+ "zod": "^4.4.3"
33
+ }
34
+ }
@@ -0,0 +1,54 @@
1
+ import { integer, pgTable, text } from "drizzle-orm/pg-core"
2
+ import { expectTypeOf, test } from "vitest"
3
+ import { z } from "zod"
4
+
5
+ import { defineTableSchema } from "./index"
6
+
7
+ type StandardSchema<T> = {
8
+ readonly "~standard": {
9
+ readonly types?: { readonly input: T; readonly output: T }
10
+ readonly validate: (value: unknown) => unknown
11
+ }
12
+ }
13
+
14
+ function acceptsFormsSchema<TFormData>(
15
+ _schema: StandardSchema<TFormData>
16
+ ): void {
17
+ return
18
+ }
19
+
20
+ const posts = pgTable("posts", {
21
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
22
+ slug: text("slug").notNull(),
23
+ title: text("title").notNull(),
24
+ })
25
+
26
+ test("insert and update schemas infer form-compatible Standard Schema data", () => {
27
+ const schemas = defineTableSchema(posts)
28
+
29
+ acceptsFormsSchema(schemas.insert)
30
+ acceptsFormsSchema(schemas.update)
31
+
32
+ expectTypeOf<z.infer<typeof schemas.insert>>().toEqualTypeOf<{
33
+ slug: string
34
+ title: string
35
+ }>()
36
+
37
+ expectTypeOf<z.infer<typeof schemas.update>>().toEqualTypeOf<{
38
+ slug?: string | undefined
39
+ title?: string | undefined
40
+ }>()
41
+ })
42
+
43
+ test("refined schemas preserve refinement-specific output types", () => {
44
+ const schemas = defineTableSchema(posts, {
45
+ insert: {
46
+ title: z.literal("fixed"),
47
+ },
48
+ })
49
+
50
+ expectTypeOf<z.infer<typeof schemas.insert>>().toEqualTypeOf<{
51
+ slug: string
52
+ title: "fixed"
53
+ }>()
54
+ })
@@ -0,0 +1,119 @@
1
+ import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import { defineTableSchema } from "./index"
5
+
6
+ const posts = pgTable("posts", {
7
+ createdAt: timestamp("created_at", { mode: "date" }).notNull().defaultNow(),
8
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
9
+ slug: text("slug").notNull(),
10
+ summary: text("summary"),
11
+ title: text("title").notNull(),
12
+ })
13
+
14
+ describe("defineTableSchema", () => {
15
+ it("returns select, insert, and update Zod schemas for a Drizzle table", () => {
16
+ const schemas = defineTableSchema(posts)
17
+
18
+ expect(
19
+ schemas.select.parse({
20
+ createdAt: new Date("2026-06-08T00:00:00.000Z"),
21
+ id: 1,
22
+ slug: "hello-world",
23
+ summary: null,
24
+ title: "Hello world",
25
+ })
26
+ ).toMatchObject({
27
+ id: 1,
28
+ slug: "hello-world",
29
+ summary: null,
30
+ title: "Hello world",
31
+ })
32
+
33
+ expect(
34
+ schemas.insert.parse({
35
+ slug: "hello-world",
36
+ title: "Hello world",
37
+ })
38
+ ).toEqual({
39
+ slug: "hello-world",
40
+ title: "Hello world",
41
+ })
42
+
43
+ expect(
44
+ schemas.update.parse({
45
+ title: "Updated title",
46
+ })
47
+ ).toEqual({
48
+ title: "Updated title",
49
+ })
50
+ })
51
+
52
+ it("omits generated columns from insert and update schemas", () => {
53
+ const schemas = defineTableSchema(posts)
54
+
55
+ expect(
56
+ schemas.insert.parse({
57
+ id: 1,
58
+ slug: "hello-world",
59
+ title: "Hello world",
60
+ })
61
+ ).toEqual({
62
+ slug: "hello-world",
63
+ title: "Hello world",
64
+ })
65
+
66
+ expect(
67
+ schemas.update.parse({
68
+ id: 1,
69
+ title: "Updated title",
70
+ })
71
+ ).toEqual({
72
+ title: "Updated title",
73
+ })
74
+ })
75
+
76
+ it("applies per-variant refinements only to the targeted schema", () => {
77
+ const schemas = defineTableSchema(posts, {
78
+ insert: {
79
+ title: (schema) => schema.min(3),
80
+ },
81
+ select: {
82
+ slug: (schema) => schema.startsWith("post-"),
83
+ },
84
+ update: {
85
+ title: (schema) => schema.min(10),
86
+ },
87
+ })
88
+
89
+ expect(
90
+ schemas.insert.safeParse({
91
+ slug: "hello-world",
92
+ title: "Hi",
93
+ }).success
94
+ ).toBe(false)
95
+
96
+ expect(
97
+ schemas.update.safeParse({
98
+ title: "Short",
99
+ }).success
100
+ ).toBe(false)
101
+
102
+ expect(
103
+ schemas.select.safeParse({
104
+ createdAt: new Date("2026-06-08T00:00:00.000Z"),
105
+ id: 1,
106
+ slug: "hello-world",
107
+ summary: null,
108
+ title: "Hello world",
109
+ }).success
110
+ ).toBe(false)
111
+
112
+ expect(
113
+ schemas.insert.safeParse({
114
+ slug: "hello-world",
115
+ title: "Okay",
116
+ }).success
117
+ ).toBe(true)
118
+ })
119
+ })
package/src/index.ts ADDED
@@ -0,0 +1,135 @@
1
+ /* biome-ignore-all lint/performance/noBarrelFile: package root intentionally re-exports the public schema surface. */
2
+ import type { InferInsertModel, InferSelectModel, Table } from "drizzle-orm"
3
+ import {
4
+ type BuildRefine,
5
+ type BuildSchema,
6
+ type CoerceOptions,
7
+ type CreateSchemaFactoryOptions,
8
+ createSchemaFactory as createDrizzleSchemaFactory,
9
+ type NoUnknownKeys,
10
+ } from "drizzle-orm/zod"
11
+
12
+ export {
13
+ createInsertSchema,
14
+ createSchemaFactory,
15
+ createSelectSchema,
16
+ createUpdateSchema,
17
+ } from "drizzle-orm/zod"
18
+
19
+ declare const noTableSchemaOptions: unique symbol
20
+ type NoTableSchemaOptions = { [noTableSchemaOptions]?: never }
21
+
22
+ type TableColumns<TTable extends Table> = TTable["_"]["columns"]
23
+ type InsertColumns<TTable extends Table> = Pick<
24
+ TableColumns<TTable>,
25
+ keyof InferInsertModel<TTable>
26
+ >
27
+
28
+ type SelectRefine<
29
+ TTable extends Table,
30
+ TCoerce extends CoerceOptions,
31
+ > = BuildRefine<TableColumns<TTable>, TCoerce>
32
+ type InsertRefine<
33
+ TTable extends Table,
34
+ TCoerce extends CoerceOptions,
35
+ > = BuildRefine<InsertColumns<TTable>, TCoerce>
36
+ type UpdateRefine<
37
+ TTable extends Table,
38
+ TCoerce extends CoerceOptions,
39
+ > = BuildRefine<InsertColumns<TTable>, TCoerce>
40
+
41
+ type DefineTableSchemaOptions<
42
+ TTable extends Table,
43
+ TCoerce extends CoerceOptions = undefined,
44
+ > = {
45
+ factory?: CreateSchemaFactoryOptions<TCoerce>
46
+ insert?: NoUnknownKeys<
47
+ InsertRefine<TTable, TCoerce>,
48
+ InferInsertModel<TTable>
49
+ >
50
+ select?: NoUnknownKeys<
51
+ SelectRefine<TTable, TCoerce>,
52
+ InferSelectModel<TTable>
53
+ >
54
+ update?: UpdateRefine<TTable, TCoerce>
55
+ }
56
+
57
+ type RefineFromOptions<
58
+ TOptions,
59
+ TKey extends string,
60
+ > = TKey extends keyof TOptions
61
+ ? NonNullable<TOptions[TKey]> extends Record<string, unknown>
62
+ ? NonNullable<TOptions[TKey]>
63
+ : undefined
64
+ : undefined
65
+
66
+ type DefineTableSchemaResult<
67
+ TTable extends Table,
68
+ TCoerce extends CoerceOptions,
69
+ TOptions,
70
+ > = {
71
+ insert: BuildSchema<
72
+ "insert",
73
+ TableColumns<TTable>,
74
+ RefineFromOptions<TOptions, "insert">,
75
+ TCoerce
76
+ >
77
+ select: BuildSchema<
78
+ "select",
79
+ TableColumns<TTable>,
80
+ RefineFromOptions<TOptions, "select">,
81
+ TCoerce
82
+ >
83
+ update: BuildSchema<
84
+ "update",
85
+ TableColumns<TTable>,
86
+ RefineFromOptions<TOptions, "update">,
87
+ TCoerce
88
+ >
89
+ }
90
+
91
+ function defineTableSchema<TTable extends Table>(
92
+ table: TTable
93
+ ): DefineTableSchemaResult<TTable, undefined, NoTableSchemaOptions>
94
+ function defineTableSchema<
95
+ TTable extends Table,
96
+ TCoerce extends CoerceOptions = undefined,
97
+ TOptions extends DefineTableSchemaOptions<
98
+ TTable,
99
+ TCoerce
100
+ > = DefineTableSchemaOptions<TTable, TCoerce>,
101
+ >(
102
+ table: TTable,
103
+ options: TOptions
104
+ ): DefineTableSchemaResult<TTable, TCoerce, TOptions>
105
+ function defineTableSchema<
106
+ TTable extends Table,
107
+ TCoerce extends CoerceOptions = undefined,
108
+ TOptions extends DefineTableSchemaOptions<
109
+ TTable,
110
+ TCoerce
111
+ > = DefineTableSchemaOptions<TTable, TCoerce>,
112
+ >(
113
+ table: TTable,
114
+ options?: TOptions
115
+ ): DefineTableSchemaResult<TTable, TCoerce, TOptions> {
116
+ const schemaFactory = createDrizzleSchemaFactory(options?.factory)
117
+
118
+ return {
119
+ insert: schemaFactory.createInsertSchema(
120
+ table,
121
+ options?.insert
122
+ ) as DefineTableSchemaResult<TTable, TCoerce, TOptions>["insert"],
123
+ select: schemaFactory.createSelectSchema(
124
+ table,
125
+ options?.select
126
+ ) as DefineTableSchemaResult<TTable, TCoerce, TOptions>["select"],
127
+ update: schemaFactory.createUpdateSchema(
128
+ table,
129
+ options?.update
130
+ ) as DefineTableSchemaResult<TTable, TCoerce, TOptions>["update"],
131
+ }
132
+ }
133
+
134
+ export type { DefineTableSchemaOptions, DefineTableSchemaResult }
135
+ export { defineTableSchema }