@zeno-lib/schema 0.2.0 → 0.2.2

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.
@@ -0,0 +1,34 @@
1
+ import { Column, InferInsertModel, InferSelectModel, Table } from "drizzle-orm";
2
+ import { BuildRefine, BuildSchema, CoerceOptions, CreateSchemaFactoryOptions, NoUnknownKeys, createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema } from "drizzle-orm/zod";
3
+ import { z } from "zod";
4
+ //#region src/index.d.ts
5
+ declare const noTableSchemaOptions: unique symbol;
6
+ type NoTableSchemaOptions = {
7
+ [noTableSchemaOptions]?: never;
8
+ };
9
+ type TableColumns<TTable extends Table> = TTable["_"]["columns"];
10
+ type AuditColumnKey = "createdAt" | "createdBy" | "updatedAt" | "updatedBy";
11
+ type AuditKeys<TTable extends Table> = { [K in keyof TableColumns<TTable>]: K extends AuditColumnKey ? TableColumns<TTable>[K]["_"]["hasDefault"] extends true ? K : never : never; }[keyof TableColumns<TTable>];
12
+ type InsertColumns<TTable extends Table> = Pick<TableColumns<TTable>, Exclude<keyof InferInsertModel<TTable>, AuditKeys<TTable>>>;
13
+ type SelectRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<TableColumns<TTable>, TCoerce>;
14
+ type InsertRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<InsertColumns<TTable>, TCoerce>;
15
+ type UpdateRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<InsertColumns<TTable>, TCoerce>;
16
+ type DefineTableSchemaOptions<TTable extends Table, TCoerce extends CoerceOptions = undefined> = {
17
+ factory?: CreateSchemaFactoryOptions<TCoerce>;
18
+ insert?: NoUnknownKeys<InsertRefine<TTable, TCoerce>, InferInsertModel<TTable>>;
19
+ select?: NoUnknownKeys<SelectRefine<TTable, TCoerce>, InferSelectModel<TTable>>;
20
+ update?: UpdateRefine<TTable, TCoerce>;
21
+ };
22
+ type RefineFromOptions<TOptions, TKey extends string> = TKey extends keyof TOptions ? NonNullable<TOptions[TKey]> extends Record<string, unknown> ? NonNullable<TOptions[TKey]> : undefined : undefined;
23
+ type OmitKeys<TSchema, TKeys extends PropertyKey> = TSchema extends z.ZodObject<infer TShape, infer TConfig> ? z.ZodObject<{ [K in keyof TShape as K extends TKeys ? never : K]: TShape[K]; }, TConfig> : never;
24
+ type IsInsertOptional<TColumn> = TColumn extends Column ? TColumn["_"]["notNull"] extends true ? TColumn["_"]["hasDefault"] extends true ? true : false : true : false;
25
+ type OptionalRefinedInsert<TSchema, TTable extends Table, TRefine> = TSchema extends z.ZodObject<infer TShape, infer TConfig> ? z.ZodObject<{ [K in keyof TShape]: K extends keyof TRefine ? TRefine[K] extends ((schema: never) => unknown) ? (K extends keyof TableColumns<TTable> ? IsInsertOptional<TableColumns<TTable>[K]> : false) extends true ? TShape[K] extends z.ZodOptional ? TShape[K] : z.ZodOptional<TShape[K]> : TShape[K] : TShape[K] : TShape[K]; }, TConfig> : never;
26
+ type DefineTableSchemaResult<TTable extends Table, TCoerce extends CoerceOptions, TOptions> = {
27
+ insert: OmitKeys<OptionalRefinedInsert<BuildSchema<"insert", TableColumns<TTable>, RefineFromOptions<TOptions, "insert">, TCoerce>, TTable, RefineFromOptions<TOptions, "insert">>, AuditKeys<TTable>>;
28
+ select: BuildSchema<"select", TableColumns<TTable>, RefineFromOptions<TOptions, "select">, TCoerce>;
29
+ update: OmitKeys<BuildSchema<"update", TableColumns<TTable>, RefineFromOptions<TOptions, "update">, TCoerce>, AuditKeys<TTable>>;
30
+ };
31
+ declare function defineTableSchema<TTable extends Table>(table: TTable): DefineTableSchemaResult<TTable, undefined, NoTableSchemaOptions>;
32
+ declare function defineTableSchema<TTable extends Table, TCoerce extends CoerceOptions = undefined, TOptions extends DefineTableSchemaOptions<TTable, TCoerce> = DefineTableSchemaOptions<TTable, TCoerce>>(table: TTable, options: TOptions): DefineTableSchemaResult<TTable, TCoerce, TOptions>;
33
+ //#endregion
34
+ export { type DefineTableSchemaOptions, type DefineTableSchemaResult, createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineTableSchema };
package/dist/index.mjs ADDED
@@ -0,0 +1,52 @@
1
+ import { getColumns } from "drizzle-orm";
2
+ import { createInsertSchema, createSchemaFactory, createSchemaFactory as createSchemaFactory$1, createSelectSchema, createUpdateSchema } from "drizzle-orm/zod";
3
+ //#region src/index.ts
4
+ const AUDIT_COLUMN_KEYS = /* @__PURE__ */ new Set([
5
+ "createdAt",
6
+ "createdBy",
7
+ "updatedAt",
8
+ "updatedBy"
9
+ ]);
10
+ const auditKeys = (table) => {
11
+ const columns = getColumns(table);
12
+ const keys = [];
13
+ for (const key of Object.keys(columns)) if (AUDIT_COLUMN_KEYS.has(key) && columns[key]?.hasDefault) keys.push(key);
14
+ return keys;
15
+ };
16
+ const isPositiveIdentity = (column) => {
17
+ const identity = column.generatedIdentity;
18
+ if (identity === void 0) return false;
19
+ const { increment = 1, minValue = 1, startWith = minValue } = identity.sequenceOptions ?? {};
20
+ return Number(increment) > 0 && Number(minValue) > 0 && Number(startWith) > 0;
21
+ };
22
+ const toPositive = (schema) => typeof schema.positive === "function" ? schema.positive() : schema;
23
+ const withPositiveIdentities = (table, refinements = {}) => {
24
+ const columns = getColumns(table);
25
+ const merged = { ...refinements };
26
+ for (const key of Object.keys(columns)) {
27
+ const column = columns[key];
28
+ if (column === void 0 || !isPositiveIdentity(column)) continue;
29
+ const refinement = refinements[key];
30
+ if (refinement === void 0) merged[key] = toPositive;
31
+ else if (typeof refinement === "function") merged[key] = (schema) => refinement(toPositive(schema));
32
+ }
33
+ return merged;
34
+ };
35
+ function defineTableSchema(table, options) {
36
+ const schemaFactory = createSchemaFactory$1(options?.factory);
37
+ const audit = auditKeys(table);
38
+ const omitAudit = (schema) => {
39
+ const mask = {};
40
+ for (const key of audit) if (key in schema.shape) mask[key] = true;
41
+ return schema.omit(mask);
42
+ };
43
+ const factory = schemaFactory;
44
+ const build = (variant, create) => create(table, withPositiveIdentities(table, options?.[variant]));
45
+ return {
46
+ insert: omitAudit(build("insert", factory.createInsertSchema)),
47
+ select: build("select", factory.createSelectSchema),
48
+ update: omitAudit(build("update", factory.createUpdateSchema))
49
+ };
50
+ }
51
+ //#endregion
52
+ export { createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineTableSchema };
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@zeno-lib/schema",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "exports": {
6
- ".": "./src/index.ts"
6
+ ".": {
7
+ "types": "./dist/index.d.mts",
8
+ "import": "./dist/index.mjs",
9
+ "default": "./dist/index.mjs"
10
+ }
7
11
  },
8
12
  "files": [
13
+ "dist",
9
14
  "src"
10
15
  ],
11
16
  "publishConfig": {
@@ -21,12 +26,14 @@
21
26
  },
22
27
  "devDependencies": {
23
28
  "drizzle-orm": "1.0.0-rc.3",
29
+ "tsdown": "^0.22.14",
24
30
  "typescript": "7.0.2",
25
31
  "vitest": "4.1.11",
26
32
  "zod": "^4.4.3",
27
33
  "@zeno-lib/typescript": "^1.1.0"
28
34
  },
29
35
  "scripts": {
36
+ "build": "tsdown",
30
37
  "test": "vitest --run",
31
38
  "test:watch": "vitest watch",
32
39
  "types:check": "tsc --noEmit"
@@ -80,3 +80,29 @@ test("defaulted audit columns leave the insert and update types", () => {
80
80
  insert: { createdBy: (schema) => schema },
81
81
  })
82
82
  })
83
+
84
+ test("a function refinement keeps a nullable or defaulted insert column optional", () => {
85
+ const drafts = pgTable("drafts", {
86
+ note: text("note"),
87
+ status: text("status").notNull().default("open"),
88
+ title: text("title").notNull(),
89
+ })
90
+ const schemas = defineTableSchema(drafts, {
91
+ insert: {
92
+ note: (schema) => schema.min(1),
93
+ status: (schema) => schema.min(1),
94
+ title: (schema) => schema.min(1),
95
+ },
96
+ })
97
+
98
+ expectTypeOf<z.infer<typeof schemas.insert>>().toEqualTypeOf<{
99
+ note?: string | null | undefined
100
+ status?: string | undefined
101
+ title: string
102
+ }>()
103
+ expectTypeOf<z.input<typeof schemas.insert>>().toEqualTypeOf<{
104
+ note?: string | null | undefined
105
+ status?: string | undefined
106
+ title: string
107
+ }>()
108
+ })
package/src/index.test.ts CHANGED
@@ -210,4 +210,23 @@ describe("defineTableSchema", () => {
210
210
  expect(schemas.insert.safeParse({ id: 11 }).success).toBe(false)
211
211
  expect(schemas.insert.safeParse({ id: 5 }).success).toBe(true)
212
212
  })
213
+
214
+ it("keeps a function-refined nullable insert column optional", () => {
215
+ const drafts = pgTable("drafts", {
216
+ note: text("note"),
217
+ title: text("title").notNull(),
218
+ })
219
+ const schemas = defineTableSchema(drafts, {
220
+ insert: { note: (schema) => schema.min(1) },
221
+ })
222
+
223
+ expect(schemas.insert.parse({ title: "x" })).toEqual({ title: "x" })
224
+ expect(schemas.insert.parse({ note: null, title: "x" })).toEqual({
225
+ note: null,
226
+ title: "x",
227
+ })
228
+ expect(schemas.insert.safeParse({ note: "", title: "x" }).success).toBe(
229
+ false
230
+ )
231
+ })
213
232
  })
package/src/index.ts CHANGED
@@ -99,17 +99,54 @@ type OmitKeys<TSchema, TKeys extends PropertyKey> =
99
99
  >
100
100
  : never
101
101
 
102
+ // Drizzle types a function refinement on an insert column as required, even
103
+ // when the column is nullable or defaulted and the runtime wraps it in
104
+ // `.optional()`. This puts the wrapper back into the type.
105
+ type IsInsertOptional<TColumn> = TColumn extends Column
106
+ ? TColumn["_"]["notNull"] extends true
107
+ ? TColumn["_"]["hasDefault"] extends true
108
+ ? true
109
+ : false
110
+ : true
111
+ : false
112
+
113
+ type OptionalRefinedInsert<TSchema, TTable extends Table, TRefine> =
114
+ TSchema extends z.ZodObject<infer TShape, infer TConfig>
115
+ ? z.ZodObject<
116
+ {
117
+ [K in keyof TShape]: K extends keyof TRefine
118
+ ? TRefine[K] extends (schema: never) => unknown
119
+ ? (
120
+ K extends keyof TableColumns<TTable>
121
+ ? IsInsertOptional<TableColumns<TTable>[K]>
122
+ : false
123
+ ) extends true
124
+ ? TShape[K] extends z.ZodOptional
125
+ ? TShape[K]
126
+ : z.ZodOptional<TShape[K]>
127
+ : TShape[K]
128
+ : TShape[K]
129
+ : TShape[K]
130
+ },
131
+ TConfig
132
+ >
133
+ : never
134
+
102
135
  type DefineTableSchemaResult<
103
136
  TTable extends Table,
104
137
  TCoerce extends CoerceOptions,
105
138
  TOptions,
106
139
  > = {
107
140
  insert: OmitKeys<
108
- BuildSchema<
109
- "insert",
110
- TableColumns<TTable>,
111
- RefineFromOptions<TOptions, "insert">,
112
- TCoerce
141
+ OptionalRefinedInsert<
142
+ BuildSchema<
143
+ "insert",
144
+ TableColumns<TTable>,
145
+ RefineFromOptions<TOptions, "insert">,
146
+ TCoerce
147
+ >,
148
+ TTable,
149
+ RefineFromOptions<TOptions, "insert">
113
150
  >,
114
151
  AuditKeys<TTable>
115
152
  >