zodselect 1.0.3 → 1.0.4
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/build-select-schema.d.ts +8 -0
- package/dist/build-select-schema.js +84 -0
- package/dist/build-select-schema.spec.d.ts +1 -0
- package/dist/build-select-schema.spec.js +140 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/merge-select.d.ts +1 -1
- package/dist/refine-schema.d.ts +11 -8
- package/dist/refine-schema.js +4 -4
- package/dist/refine-schema.spec.js +1 -1
- package/dist/select.d.ts +4 -4
- package/dist/utilities.d.ts +4 -2
- package/dist/utilities.js +18 -10
- package/dist/utilities.spec.js +33 -15
- package/package.json +4 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ZodBoolean, ZodLazy, ZodObject, ZodOptional, ZodType, ZodUnion } from 'zod';
|
|
2
|
+
import { ZodSelect } from './select.js';
|
|
3
|
+
export type BasicSelect = ZodObject<Readonly<{
|
|
4
|
+
[k: string]: Clause | ZodLazy<Clause>;
|
|
5
|
+
}>>;
|
|
6
|
+
type Clause = ZodOptional<ZodBoolean> | ZodOptional<ZodUnion<readonly [ZodBoolean, BasicSelect]>>;
|
|
7
|
+
export declare function buildSelectSchema<T extends ZodType<object> | ZodUnion>(schema: T): ZodType<ZodSelect<T, true>>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getInnerType, isZodLazy, isZodObject, isZodObjectLoose } from './utilities.js';
|
|
3
|
+
function buildSelect(schema, cache = new Map()) {
|
|
4
|
+
schema = getInnerType(schema, false);
|
|
5
|
+
if (cache.has(schema)) {
|
|
6
|
+
return cache.get(schema);
|
|
7
|
+
}
|
|
8
|
+
if (isZodLazy(schema)) {
|
|
9
|
+
const select = z.lazy(() => {
|
|
10
|
+
return buildSelect(schema.def.getter(), cache);
|
|
11
|
+
});
|
|
12
|
+
cache.set(schema, select);
|
|
13
|
+
return select;
|
|
14
|
+
}
|
|
15
|
+
if (isZodObject(schema)) {
|
|
16
|
+
const shape = {};
|
|
17
|
+
let select;
|
|
18
|
+
for (const key in schema.shape) {
|
|
19
|
+
shape[key] = buildSelect(schema.shape[key], cache);
|
|
20
|
+
}
|
|
21
|
+
select = z.object(shape);
|
|
22
|
+
if (!isZodObjectLoose(schema)) {
|
|
23
|
+
select = select.strict();
|
|
24
|
+
}
|
|
25
|
+
const result = z.union([z.boolean(), select]).optional();
|
|
26
|
+
cache.set(schema, result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
let types;
|
|
30
|
+
if (schema.def.type === 'intersection') {
|
|
31
|
+
types = [schema.def.left, schema.def.right];
|
|
32
|
+
}
|
|
33
|
+
if (schema.def.type === 'tuple') {
|
|
34
|
+
types = schema.def.items;
|
|
35
|
+
}
|
|
36
|
+
if (schema.def.type === 'union') {
|
|
37
|
+
types = schema.def.options;
|
|
38
|
+
}
|
|
39
|
+
if (types) {
|
|
40
|
+
const hasLazy = types.some(item => isZodLazy(item));
|
|
41
|
+
if (hasLazy) {
|
|
42
|
+
return z.lazy(() => {
|
|
43
|
+
return buildSelectMerge(types);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return buildSelectMerge(types);
|
|
47
|
+
}
|
|
48
|
+
return z.boolean().optional();
|
|
49
|
+
}
|
|
50
|
+
function buildSelectMerge(types) {
|
|
51
|
+
const shape = {};
|
|
52
|
+
let isStrict = false;
|
|
53
|
+
for (const cur of types) {
|
|
54
|
+
const select = buildSelect(cur);
|
|
55
|
+
let clause;
|
|
56
|
+
if (isZodLazy(select)) {
|
|
57
|
+
clause = select.def.getter();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
clause = select;
|
|
61
|
+
}
|
|
62
|
+
if (clause.def.innerType.def.type === 'union') {
|
|
63
|
+
const obj = getInnerType(clause.def.innerType.def.options[1]);
|
|
64
|
+
for (const key in obj.shape) {
|
|
65
|
+
shape[key] = obj.shape[key];
|
|
66
|
+
}
|
|
67
|
+
if (!isZodObjectLoose(obj)) {
|
|
68
|
+
isStrict = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let result = z.object(shape);
|
|
73
|
+
if (isStrict) {
|
|
74
|
+
result = result.strict();
|
|
75
|
+
}
|
|
76
|
+
return z.union([z.boolean(), result]).optional();
|
|
77
|
+
}
|
|
78
|
+
export function buildSelectSchema(schema) {
|
|
79
|
+
const result = buildSelect(schema);
|
|
80
|
+
if (isZodLazy(result)) {
|
|
81
|
+
return result.def.getter().def.innerType.def.options[1];
|
|
82
|
+
}
|
|
83
|
+
return result.def.innerType.def.options[1];
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { strict as assert } from 'assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { z, ZodObject } from 'zod';
|
|
4
|
+
import { buildSelectSchema } from './build-select-schema.js';
|
|
5
|
+
import { getInnerType, isZodObjectLoose } from './utilities.js';
|
|
6
|
+
describe('.buildSelectSchema()', () => {
|
|
7
|
+
it('should build select schema for a flat object', () => {
|
|
8
|
+
const schema = z.object({
|
|
9
|
+
age: z.number(),
|
|
10
|
+
name: z.string()
|
|
11
|
+
});
|
|
12
|
+
const selectSchema = buildSelectSchema(schema);
|
|
13
|
+
assert(selectSchema instanceof ZodObject);
|
|
14
|
+
assert.deepEqual(selectSchema.parse({ name: true }), { name: true });
|
|
15
|
+
assert.deepEqual(selectSchema.parse({}), {});
|
|
16
|
+
assert.throws(() => selectSchema.parse({ name: 'yes' }), /expected boolean/);
|
|
17
|
+
assert.throws(() => selectSchema.parse({ unknown: true }), /Unrecognized key/);
|
|
18
|
+
});
|
|
19
|
+
it('should handle nested objects', () => {
|
|
20
|
+
const schema = z.object({
|
|
21
|
+
user: z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
profile: z.object({
|
|
24
|
+
displayName: z.string()
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
const selectSchema = buildSelectSchema(schema);
|
|
29
|
+
const result = selectSchema.parse({
|
|
30
|
+
user: {
|
|
31
|
+
id: true,
|
|
32
|
+
profile: {
|
|
33
|
+
displayName: true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
assert.deepEqual(result, {
|
|
38
|
+
user: {
|
|
39
|
+
id: true,
|
|
40
|
+
profile: {
|
|
41
|
+
displayName: true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('should build select schema for a union of objects', () => {
|
|
47
|
+
const schema = z.union([
|
|
48
|
+
z.object({ type: z.literal('a'), valueA: z.string() }).strict(),
|
|
49
|
+
z.object({ type: z.literal('b'), valueB: z.number() }).strict()
|
|
50
|
+
]);
|
|
51
|
+
const selectSchema = buildSelectSchema(schema);
|
|
52
|
+
assert(selectSchema instanceof ZodObject);
|
|
53
|
+
const result = selectSchema.parse({
|
|
54
|
+
type: true,
|
|
55
|
+
valueA: true
|
|
56
|
+
});
|
|
57
|
+
assert.deepEqual(result, {
|
|
58
|
+
type: true,
|
|
59
|
+
valueA: true
|
|
60
|
+
});
|
|
61
|
+
assert.throws(() => selectSchema.parse({ valueC: true }), /Unrecognized key/);
|
|
62
|
+
});
|
|
63
|
+
it('should support intersections of objects', () => {
|
|
64
|
+
const schema = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
|
|
65
|
+
const select = buildSelectSchema(schema);
|
|
66
|
+
assert.strictEqual(getInnerType(select.shape.a).def.type, 'boolean');
|
|
67
|
+
assert.strictEqual(getInnerType(select.shape.b).def.type, 'boolean');
|
|
68
|
+
});
|
|
69
|
+
it('should support tuples of objects', () => {
|
|
70
|
+
const schema = z.tuple([
|
|
71
|
+
z.object({ foo: z.string() }),
|
|
72
|
+
z.object({ bar: z.number() })
|
|
73
|
+
]);
|
|
74
|
+
const select = buildSelectSchema(schema);
|
|
75
|
+
assert.strictEqual(getInnerType(select.shape.foo).def.type, 'boolean');
|
|
76
|
+
assert.strictEqual(getInnerType(select.shape.bar).def.type, 'boolean');
|
|
77
|
+
});
|
|
78
|
+
it('should return a strict schema if all members are strict', () => {
|
|
79
|
+
const schema = z.union([
|
|
80
|
+
z.object({ strictA: z.string() }).strict(),
|
|
81
|
+
z.object({ strictB: z.number() }).strict()
|
|
82
|
+
]);
|
|
83
|
+
const select = buildSelectSchema(schema);
|
|
84
|
+
assert.strictEqual(isZodObjectLoose(select), false);
|
|
85
|
+
});
|
|
86
|
+
it('should allow optional keys in the select schema', () => {
|
|
87
|
+
const schema = z.object({
|
|
88
|
+
optionalField: z.string().optional()
|
|
89
|
+
});
|
|
90
|
+
const select = buildSelectSchema(schema);
|
|
91
|
+
assert.strictEqual(getInnerType(select.shape.optionalField).def.type, 'boolean');
|
|
92
|
+
});
|
|
93
|
+
it('should handle direct recursion', () => {
|
|
94
|
+
const RoleSchema = z.lazy(() => {
|
|
95
|
+
return (z.object({
|
|
96
|
+
name: z.string(),
|
|
97
|
+
roles: z.array(RoleSchema)
|
|
98
|
+
}));
|
|
99
|
+
});
|
|
100
|
+
const select = buildSelectSchema(RoleSchema);
|
|
101
|
+
assert.deepStrictEqual(Object.keys(select.shape), ['name', 'roles']);
|
|
102
|
+
const nameSelect = select.shape.name;
|
|
103
|
+
assert.strictEqual(nameSelect.def.type, 'optional');
|
|
104
|
+
assert.strictEqual(nameSelect.def.innerType.def.type, 'boolean');
|
|
105
|
+
const rolesSelect = select.shape.roles;
|
|
106
|
+
assert.strictEqual(rolesSelect.def.type, 'lazy');
|
|
107
|
+
const innerUnion = rolesSelect.def.getter().def.innerType;
|
|
108
|
+
assert.strictEqual(innerUnion.def.type, 'union');
|
|
109
|
+
const booleanItem = innerUnion.def.options[0];
|
|
110
|
+
assert.strictEqual(booleanItem.def.type, 'boolean');
|
|
111
|
+
const roleItem = innerUnion.def.options[1];
|
|
112
|
+
assert.strictEqual(roleItem.def.type, 'object');
|
|
113
|
+
});
|
|
114
|
+
it('should support mutual recursion (cross-recursive)', () => {
|
|
115
|
+
const A = z.lazy(() => z.object({
|
|
116
|
+
b: B
|
|
117
|
+
}));
|
|
118
|
+
const B = z.lazy(() => z.object({
|
|
119
|
+
a: A
|
|
120
|
+
}));
|
|
121
|
+
const select = buildSelectSchema(A);
|
|
122
|
+
assert.strictEqual(select.shape.b.def.type, 'lazy');
|
|
123
|
+
const bUnion = select.shape.b.def.getter();
|
|
124
|
+
assert.strictEqual(bUnion.def.innerType.def.type, 'union');
|
|
125
|
+
const bObj = bUnion.def.innerType.def.options[1];
|
|
126
|
+
assert.strictEqual(bObj.shape.a.def.type, 'lazy');
|
|
127
|
+
});
|
|
128
|
+
it('should share the same instance in recursion (no infinite loop)', () => {
|
|
129
|
+
const Role = z.lazy(() => z.object({
|
|
130
|
+
name: z.string(),
|
|
131
|
+
roles: z.array(Role)
|
|
132
|
+
}));
|
|
133
|
+
const select = buildSelectSchema(Role);
|
|
134
|
+
const rolesSelect = select.shape.roles;
|
|
135
|
+
const unionSelect = rolesSelect.def.getter().def.innerType;
|
|
136
|
+
const nestedRoleSelect = unionSelect.def.options[1].shape.roles;
|
|
137
|
+
// Should be the same object (by reference)
|
|
138
|
+
assert.strictEqual(rolesSelect, nestedRoleSelect);
|
|
139
|
+
});
|
|
140
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/merge-select.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { ZodType, ZodUnion } from 'zod
|
|
1
|
+
import { ZodType, ZodUnion } from 'zod';
|
|
2
2
|
import { MergeSelect, MultiSelect, ZodSelect } from './select.js';
|
|
3
3
|
export declare function mergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>>(schema: T, select1: TSelect1, select2?: TSelect2): MergeSelect<T, TSelect1, TSelect2>;
|
package/dist/refine-schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { core, z, ZodAny, ZodArray, ZodBoolean, ZodDate, ZodLiteral, ZodNever, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, ZodString, ZodType, ZodUnion, ZodUnknown } from 'zod
|
|
1
|
+
import { core, z, ZodAny, ZodArray, ZodBoolean, ZodDate, ZodLiteral, ZodNever, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, ZodString, ZodTuple, ZodType, ZodUnion, ZodUnknown } from 'zod';
|
|
2
2
|
import { IsAny, IsJsonType } from './types.js';
|
|
3
3
|
type ApplyOptionalNullable<T, U extends ZodType> = IsAny<T, U, undefined extends T ? null extends T ? ZodOptional<ZodNullable<U>> : ZodOptional<U> : null extends T ? ZodNullable<U> : U>;
|
|
4
4
|
type DepthLimit = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
@@ -10,7 +10,7 @@ type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends ()
|
|
|
10
10
|
type NextDepth<T extends number> = T extends 0 ? 1 : T extends 1 ? 2 : T extends 2 ? 3 : T extends 3 ? 4 : T extends 4 ? 5 : T extends 5 ? 6 : T extends 6 ? 7 : T extends 7 ? 8 : T extends 8 ? 9 : 9;
|
|
11
11
|
type Push<T extends any[], V> = [...T, V];
|
|
12
12
|
type ReapplyOptionalNullable<T, U extends z.ZodType> = T extends ZodOptional<ZodNullable<ZodType>> ? ZodOptional<ZodNullable<U>> : T extends ZodNullable<ZodOptional<ZodType>> ? ZodNullable<ZodOptional<U>> : T extends ZodOptional<ZodType> ? ZodOptional<U> : T extends ZodNullable<ZodType> ? ZodNullable<U> : U;
|
|
13
|
-
export type RefinedSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T
|
|
13
|
+
export type RefinedSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T, TIsSimple>, TIsSimple extends boolean> = T extends ZodUnion ? RefinedUnionSchema<T, TShape> : T extends ZodType<object> ? RefinedTypeSchema<z.infer<T>, TShape> : never;
|
|
14
14
|
export type RefinedTypeSchema<T extends object, TShape, TDepth extends number = 0> = TDepth extends keyof DepthLimit ? ZodObject<{
|
|
15
15
|
[K in keyof T & keyof TShape]: TShape[K] extends (schema: infer S) => ZodType ? ReturnType<TShape[K]> : TShape[K] extends ZodType ? TShape[K] : TShape[K] extends boolean ? Zodify<T[K]> : TShape[K] extends object ? Exclude<T[K], null | undefined> extends Array<infer U> ? U extends object ? ApplyOptionalNullable<T[K], z.ZodArray<RefinedTypeSchema<U, TShape[K], NextDepth<TDepth>>>> : Zodify<T[K]> : Exclude<T[K], null | undefined> extends object ? ApplyOptionalNullable<T[K], RefinedTypeSchema<Exclude<T[K], null | undefined>, TShape[K], NextDepth<TDepth>>> : ZodNever : ZodNever;
|
|
16
16
|
}, core.$strict> : any;
|
|
@@ -20,17 +20,20 @@ export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options
|
|
|
20
20
|
[K in keyof Rest]: Rest[K] extends ZodType ? z.infer<Rest[K]> extends object ? RefinedTypeSchema<z.infer<Rest[K]>, TShape> : Extract<Rest[K], ZodType> : Extract<Rest[K], ZodType>;
|
|
21
21
|
}
|
|
22
22
|
] : never : never>;
|
|
23
|
-
type Refinement<T extends ZodType> = ((schema: T) => ZodType) | boolean | ZodType;
|
|
24
|
-
export type RefineObject<T extends object> = {
|
|
25
|
-
[K in keyof T]?: RefineType<T[K]>;
|
|
23
|
+
type Refinement<T extends ZodType, TIsSimple extends boolean> = TIsSimple extends true ? boolean : ((schema: T) => ZodType) | boolean | ZodType;
|
|
24
|
+
export type RefineObject<T extends object, TIsSimple extends boolean> = {
|
|
25
|
+
[K in keyof T]?: RefineType<T[K], TIsSimple>;
|
|
26
|
+
};
|
|
27
|
+
export type RefineSchema<T extends ZodType<object> | ZodUnion, TIsSimple extends boolean> = T extends ZodUnion ? RefineZodUnion<T> : T extends ZodType<object> ? RefineObject<z.infer<T>, TIsSimple> : never;
|
|
28
|
+
type RefineType<T, TIsSimple extends boolean> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, Refinement<Zodify<T>, TIsSimple>, Exclude<T, null | undefined> extends string ? Refinement<ApplyOptionalNullable<T, ZodString>, TIsSimple> : Exclude<T, null | undefined> extends number ? Refinement<ApplyOptionalNullable<T, ZodNumber>, TIsSimple> : Exclude<T, null | undefined> extends boolean ? Refinement<ApplyOptionalNullable<T, ZodBoolean>, TIsSimple> : Exclude<T, null | undefined> extends Date ? Refinement<ApplyOptionalNullable<T, ZodDate>, TIsSimple> : Exclude<T, null | undefined> extends Array<infer U> ? Exclude<U, null | undefined> extends object ? Refinement<Zodify<T>, TIsSimple> | RefineObject<Extract<U, object>, TIsSimple> : Refinement<Zodify<T>, TIsSimple> : Exclude<T, null | undefined> extends object ? IsJsonType<Exclude<T, null | undefined>, false, Refinement<Zodify<T>, TIsSimple> | RefineObject<Extract<T, object>, TIsSimple>, Refinement<Zodify<T>, TIsSimple>> : unknown extends Exclude<T, null | undefined> ? Refinement<Zodify<T>, TIsSimple> : never>;
|
|
29
|
+
export type RefineZodTuple<T extends ZodTuple> = {
|
|
30
|
+
[K in keyof UnionToIntersection<T['def']['items'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
|
|
26
31
|
};
|
|
27
|
-
export type RefineSchema<T extends ZodType<object> | ZodUnion> = T extends ZodUnion ? RefineZodUnion<T> : T extends ZodType<object> ? RefineObject<z.infer<T>> : never;
|
|
28
|
-
type RefineType<T> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, Refinement<Zodify<T>>, Exclude<T, null | undefined> extends string ? Refinement<ApplyOptionalNullable<T, ZodString>> : Exclude<T, null | undefined> extends number ? Refinement<ApplyOptionalNullable<T, ZodNumber>> : Exclude<T, null | undefined> extends boolean ? Refinement<ApplyOptionalNullable<T, ZodBoolean>> : Exclude<T, null | undefined> extends Date ? Refinement<ApplyOptionalNullable<T, ZodDate>> : Exclude<T, null | undefined> extends Array<infer U> ? Exclude<U, null | undefined> extends object ? Refinement<Zodify<T>> | RefineObject<Extract<U, object>> : Refinement<Zodify<T>> : Exclude<T, null | undefined> extends object ? IsJsonType<Exclude<T, null | undefined>, false, Refinement<Zodify<T>> | RefineObject<Extract<T, object>>, Refinement<Zodify<T>>> : unknown extends Exclude<T, null | undefined> ? Refinement<Zodify<T>> : never>;
|
|
29
32
|
export type RefineZodUnion<T extends ZodUnion> = {
|
|
30
33
|
[K in keyof UnionToIntersection<T['options'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
|
|
31
34
|
};
|
|
32
35
|
export type TuplifyUnion<T, TDepth extends number = 0, L = LastOf<T>, N = [T] extends [never] ? true : false> = TDepth extends 9 ? [] : true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>, NextDepth<TDepth>>, InternalZodify<L>>;
|
|
33
36
|
export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
34
37
|
export type Zodify<T, TDepth extends number = 0> = ApplyOptionalNullable<T, IsTuple<TuplifyUnion<Exclude<T, null | undefined>, TDepth>, ZodUnion<TuplifyUnion<Exclude<T, null | undefined>, TDepth>>, InternalZodify<Exclude<T, null | undefined>, TDepth>>>;
|
|
35
|
-
export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T>>(schema: T, shape: TShape): RefinedSchema<T, TShape>;
|
|
38
|
+
export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T, false>>(schema: T, shape: TShape): RefinedSchema<T, TShape, false>;
|
|
36
39
|
export {};
|
package/dist/refine-schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from 'zod
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import { getInnerType, isZodArray, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
|
|
3
3
|
// Main function to refine the schema
|
|
4
4
|
export function refineSchema(schema, shape) {
|
|
@@ -40,15 +40,15 @@ function refineSchemaField(fieldSchema, fieldShape) {
|
|
|
40
40
|
return z.array(refineSchemaField(fieldSchema.element, fieldShape));
|
|
41
41
|
}
|
|
42
42
|
else if (isZodOptional(fieldSchema)) {
|
|
43
|
-
return refineSchemaField(fieldSchema.
|
|
43
|
+
return refineSchemaField(fieldSchema.def.innerType, fieldShape).optional();
|
|
44
44
|
}
|
|
45
45
|
else if (isZodNullable(fieldSchema)) {
|
|
46
|
-
return refineSchemaField(fieldSchema.
|
|
46
|
+
return refineSchemaField(fieldSchema.def.innerType, fieldShape).nullable();
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
49
|
const innerType = getInnerType(fieldSchema);
|
|
50
50
|
// There are wrapper types we can't forward because the underlying object structures are changing.
|
|
51
|
-
if (innerType) {
|
|
51
|
+
if (innerType !== fieldSchema) {
|
|
52
52
|
return refineSchemaField(innerType, fieldShape);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { strict as assert, deepStrictEqual, throws } from 'assert';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
|
-
import { z, ZodError } from 'zod
|
|
3
|
+
import { z, ZodError } from 'zod';
|
|
4
4
|
import { refineSchema } from './refine-schema.js';
|
|
5
5
|
describe('.refineSchema()', () => {
|
|
6
6
|
it('should retain required fields', () => {
|
package/dist/select.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { z, ZodType, ZodUnion } from 'zod
|
|
2
|
-
import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodUnion } from './refine-schema.js';
|
|
1
|
+
import { z, ZodTuple, ZodType, ZodUnion } from 'zod';
|
|
2
|
+
import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodTuple, RefineZodUnion } from './refine-schema.js';
|
|
3
3
|
import { DeepMergeAll, IsEqual } from './types.js';
|
|
4
4
|
export type InferMergedType<T extends ZodType<object> | ZodUnion, U extends readonly ZodSelect<T>[] | undefined | ZodSelect<T>> = U extends undefined ? z.infer<T> : U extends readonly ZodSelect<T>[] ? DeepMergeAll<InferTuple<T, U>> : U extends ZodSelect<T> ? InferType<T, U> : never;
|
|
5
5
|
type InferTuple<T extends ZodType<object> | ZodUnion, U extends readonly ZodSelect<T>[]> = {
|
|
6
6
|
[K in keyof U]: InferType<T, U[K]>;
|
|
7
7
|
};
|
|
8
|
-
export type InferType<T extends ZodType<object> | ZodUnion, U extends ZodSelect<T>> = U extends ZodType<object> ? z.infer<U> : U extends ZodUnion ? z.infer<U> : T extends ZodUnion ? (U extends RefineZodUnion<T> ? z.infer<RefinedUnionSchema<T, U>> : z.infer<T>) : T extends ZodType<object> ? (U extends RefineObject<z.infer<T
|
|
8
|
+
export type InferType<T extends ZodType<object> | ZodUnion, U extends ZodSelect<T>> = U extends ZodType<object> ? z.infer<U> : U extends ZodUnion ? z.infer<U> : T extends ZodUnion ? (U extends RefineZodUnion<T> ? z.infer<RefinedUnionSchema<T, U>> : z.infer<T>) : T extends ZodType<object> ? (U extends RefineObject<z.infer<T>, false> ? z.infer<RefinedTypeSchema<z.infer<T>, U>> : z.infer<T>) : z.infer<T>;
|
|
9
9
|
export type MergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>> = TSelect2 extends undefined ? [TSelect1, T] : IsEqual<TSelect2, T, TSelect1, TSelect2 extends [ZodSelect<T>, ...ZodSelect<T>[]] ? [TSelect1, ...TSelect2] : [TSelect1, TSelect2]>;
|
|
10
10
|
export type MultiSelect<T extends ZodType<object> | ZodUnion> = [ZodSelect<T>, ...ZodSelect<T>[]] | undefined | ZodSelect<T>;
|
|
11
|
-
export type ZodSelect<T extends ZodType<object> | ZodUnion> = (T extends ZodUnion ? (RefineZodUnion<T> | ZodUnion) : T extends ZodType<object> ? (RefineObject<z.infer<T
|
|
11
|
+
export type ZodSelect<T extends ZodTuple | ZodType<object> | ZodUnion, TIsSimple extends boolean = false> = (T extends ZodUnion ? (RefineZodUnion<T> | ZodUnion) : T extends ZodTuple ? (RefineZodTuple<T> | ZodTuple) : T extends ZodType<object> ? (RefineObject<z.infer<T>, TIsSimple> | ZodType<object>) : never);
|
|
12
12
|
export {};
|
package/dist/utilities.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod
|
|
2
|
-
export declare function getInnerType(schema: ZodType):
|
|
1
|
+
import { ZodArray, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod';
|
|
2
|
+
export declare function getInnerType(schema: ZodType, unwrapLazy?: boolean): ZodType;
|
|
3
3
|
export declare function isZodArray(obj: unknown): obj is ZodArray<ZodType>;
|
|
4
|
+
export declare function isZodLazy(obj: unknown): obj is ZodLazy;
|
|
4
5
|
export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>;
|
|
5
6
|
export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
|
|
7
|
+
export declare function isZodObjectLoose(obj: ZodObject): boolean;
|
|
6
8
|
export declare function isZodOptional(obj: unknown): obj is ZodOptional<ZodType>;
|
|
7
9
|
export declare function isZodType(obj: unknown): obj is ZodType;
|
|
8
10
|
export declare function isZodUnion(obj: unknown): obj is ZodUnion<ZodType[]>;
|
package/dist/utilities.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod
|
|
2
|
-
export function getInnerType(schema) {
|
|
3
|
-
if (schema instanceof ZodOptional
|
|
4
|
-
|
|
1
|
+
import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
|
|
2
|
+
export function getInnerType(schema, unwrapLazy = true) {
|
|
3
|
+
if (schema instanceof ZodOptional
|
|
4
|
+
|| schema instanceof ZodNullable
|
|
5
|
+
|| schema instanceof ZodDefault
|
|
6
|
+
|| schema instanceof ZodCatch) {
|
|
7
|
+
return getInnerType(schema.def.innerType, unwrapLazy);
|
|
5
8
|
}
|
|
6
|
-
|
|
7
|
-
return schema.def.
|
|
9
|
+
if (schema instanceof ZodArray) {
|
|
10
|
+
return getInnerType(schema.def.element, unwrapLazy);
|
|
8
11
|
}
|
|
9
|
-
|
|
10
|
-
return schema.
|
|
12
|
+
if (unwrapLazy && schema instanceof ZodLazy) {
|
|
13
|
+
return getInnerType(schema.def.getter());
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
return undefined;
|
|
15
|
+
return schema;
|
|
14
16
|
}
|
|
15
17
|
export function isZodArray(obj) {
|
|
16
18
|
return isZodType(obj) && (obj.def.type === 'array');
|
|
17
19
|
}
|
|
20
|
+
export function isZodLazy(obj) {
|
|
21
|
+
return isZodType(obj) && (obj.def.type === 'lazy');
|
|
22
|
+
}
|
|
18
23
|
export function isZodNullable(obj) {
|
|
19
24
|
return isZodType(obj) && (obj.def.type === 'nullable');
|
|
20
25
|
}
|
|
@@ -27,6 +32,9 @@ export function isZodObject(obj, isLoose) {
|
|
|
27
32
|
}
|
|
28
33
|
return isLoose === isZodUnknown(obj.def.catchall);
|
|
29
34
|
}
|
|
35
|
+
export function isZodObjectLoose(obj) {
|
|
36
|
+
return !!isZodUnknown(obj.def.catchall);
|
|
37
|
+
}
|
|
30
38
|
export function isZodOptional(obj) {
|
|
31
39
|
return isZodType(obj) && (obj.def.type === 'optional');
|
|
32
40
|
}
|
package/dist/utilities.spec.js
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
|
-
import { z } from 'zod
|
|
3
|
+
import { z } from 'zod';
|
|
4
4
|
import { getInnerType, isZodObject, isZodOptional, isZodUnion, isZodUnknown } from './utilities.js';
|
|
5
5
|
describe('.utilities()', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
describe('.getInnerType()', () => {
|
|
7
|
+
it('getInnerType identifies inner types', () => {
|
|
8
|
+
const optionalString = z.string().optional();
|
|
9
|
+
const nullableString = z.string().nullable();
|
|
10
|
+
const defaultString = z.string().default('default');
|
|
11
|
+
const catchString = z.string().catch('fallback');
|
|
12
|
+
const lazyString = z.lazy(() => z.string());
|
|
13
|
+
const arrayString = z.array(z.string());
|
|
14
|
+
assert.strictEqual(getInnerType(optionalString).def.type, optionalString.def.innerType.def.type);
|
|
15
|
+
assert.strictEqual(getInnerType(nullableString).def.type, nullableString.def.innerType.def.type);
|
|
16
|
+
assert.strictEqual(getInnerType(defaultString).def.type, defaultString.def.innerType.def.type);
|
|
17
|
+
assert.strictEqual(getInnerType(catchString).def.type, catchString.def.innerType.def.type);
|
|
18
|
+
assert.strictEqual(getInnerType(lazyString).def.type, lazyString.def.getter().def.type);
|
|
19
|
+
assert.strictEqual(getInnerType(arrayString).def.type, arrayString.element.def.type);
|
|
20
|
+
assert.strictEqual(getInnerType(z.string()).def.type, 'string'); // Not a wrapper type
|
|
21
|
+
});
|
|
22
|
+
it('should return base type from array', () => {
|
|
23
|
+
const schema = z.array(z.number());
|
|
24
|
+
const inner = getInnerType(schema);
|
|
25
|
+
assert.strictEqual(inner.def.type, 'number');
|
|
26
|
+
});
|
|
27
|
+
it('should unwrap nested optional -> nullable -> default -> string', () => {
|
|
28
|
+
const schema = z.string().default('x').nullable().optional();
|
|
29
|
+
const inner = getInnerType(schema);
|
|
30
|
+
assert.strictEqual(inner.def.type, 'string');
|
|
31
|
+
});
|
|
32
|
+
it('should unwrap lazy inside nested wrappers', () => {
|
|
33
|
+
const LazySchema = z.lazy(() => z.string());
|
|
34
|
+
const wrapped = LazySchema.optional().nullable().default('abc');
|
|
35
|
+
const inner = getInnerType(wrapped);
|
|
36
|
+
assert.strictEqual(inner.def.type, 'string');
|
|
37
|
+
});
|
|
20
38
|
});
|
|
21
39
|
it('isZodObject detects ZodObject', () => {
|
|
22
40
|
const objectSchema = z.object({ name: z.string() });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zodselect",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Type-safe selection utility built on top of Zod. Allows for strongly typed field selection, schema merging, and runtime refinement for frontend or backend use.",
|
|
5
5
|
"author": "SecureDataSystems",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@secure-data-systems/eslint-config": "^1.0.
|
|
28
|
+
"@secure-data-systems/eslint-config": "^1.0.7",
|
|
29
29
|
"@secure-data-systems/tsconfig": "^1.0.1",
|
|
30
|
-
"@types/node": "^24.0.
|
|
30
|
+
"@types/node": "^24.0.13",
|
|
31
31
|
"glob": "^11.0.3",
|
|
32
32
|
"tsx": "^4.20.3",
|
|
33
33
|
"typescript": "^5.8.3"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"zod": "^
|
|
36
|
+
"zod": "^4.0.5"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs && tsc --project tsconfig.build.json",
|