@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.
- package/dist/index.d.mts +34 -0
- package/dist/index.mjs +52 -0
- package/package.json +9 -2
- package/src/index.test-d.ts +26 -0
- package/src/index.test.ts +19 -0
- package/src/index.ts +42 -5
package/dist/index.d.mts
ADDED
|
@@ -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.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".":
|
|
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"
|
package/src/index.test-d.ts
CHANGED
|
@@ -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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
>
|