zodselect 1.0.13 → 1.0.15
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.
|
@@ -6,9 +6,9 @@ export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = {
|
|
|
6
6
|
/**
|
|
7
7
|
* Produces a runtime select-all object for the given Zod schema.
|
|
8
8
|
*
|
|
9
|
-
* Every
|
|
10
|
-
*
|
|
11
|
-
* set of keys across all variants.
|
|
9
|
+
* Every scalar key is set to `true`. Nested object fields are recursively
|
|
10
|
+
* expanded into nested select objects. Array fields remain `true`.
|
|
11
|
+
* For union schemas the result is the merged set of keys across all variants.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
|
|
1
2
|
import { getInnerType, isZodObject, isZodUnion } from './utilities.js';
|
|
2
3
|
/**
|
|
3
4
|
* Produces a runtime select-all object for the given Zod schema.
|
|
4
5
|
*
|
|
5
|
-
* Every
|
|
6
|
-
*
|
|
7
|
-
* set of keys across all variants.
|
|
6
|
+
* Every scalar key is set to `true`. Nested object fields are recursively
|
|
7
|
+
* expanded into nested select objects. Array fields remain `true`.
|
|
8
|
+
* For union schemas the result is the merged set of keys across all variants.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* ```ts
|
|
@@ -17,20 +18,47 @@ export function buildSelectObject(schema) {
|
|
|
17
18
|
collectKeys(schema, result);
|
|
18
19
|
return result;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Unwraps optional/nullable/default/catch/lazy wrappers but NOT arrays.
|
|
23
|
+
*/
|
|
24
|
+
function unwrapField(schema) {
|
|
25
|
+
if (schema instanceof ZodOptional
|
|
26
|
+
|| schema instanceof ZodNullable
|
|
27
|
+
|| schema instanceof ZodDefault
|
|
28
|
+
|| schema instanceof ZodCatch) {
|
|
29
|
+
return unwrapField(schema.def.innerType);
|
|
30
|
+
}
|
|
31
|
+
if (schema instanceof ZodLazy) {
|
|
32
|
+
return unwrapField(schema.def.getter());
|
|
33
|
+
}
|
|
34
|
+
return schema;
|
|
35
|
+
}
|
|
36
|
+
const MAX_DEPTH = 10;
|
|
37
|
+
function collectKeys(schema, result, depth = 0) {
|
|
38
|
+
if (depth >= MAX_DEPTH) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
21
41
|
schema = getInnerType(schema);
|
|
22
42
|
if (isZodObject(schema)) {
|
|
23
43
|
for (const key in schema.shape) {
|
|
24
|
-
|
|
44
|
+
const fieldInner = unwrapField(schema.shape[key]);
|
|
45
|
+
if (isZodObject(fieldInner)) {
|
|
46
|
+
const nested = {};
|
|
47
|
+
collectKeys(fieldInner, nested, depth + 1);
|
|
48
|
+
result[key] = nested;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
result[key] = true;
|
|
52
|
+
}
|
|
25
53
|
}
|
|
26
54
|
}
|
|
27
55
|
else if (isZodUnion(schema)) {
|
|
28
56
|
for (const option of schema.options) {
|
|
29
|
-
collectKeys(option, result);
|
|
57
|
+
collectKeys(option, result, depth);
|
|
30
58
|
}
|
|
31
59
|
}
|
|
32
60
|
else if (schema.def.type === 'intersection') {
|
|
33
|
-
collectKeys(schema.def.left, result);
|
|
34
|
-
collectKeys(schema.def.right, result);
|
|
61
|
+
collectKeys(schema.def.left, result, depth);
|
|
62
|
+
collectKeys(schema.def.right, result, depth);
|
|
35
63
|
}
|
|
36
64
|
}
|
|
@@ -45,7 +45,7 @@ describe('.buildSelectObject()', () => {
|
|
|
45
45
|
c: true
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
|
-
it('should
|
|
48
|
+
it('should recursively expand nested objects', () => {
|
|
49
49
|
const schema = z.object({
|
|
50
50
|
active: z.boolean(),
|
|
51
51
|
user: z.object({
|
|
@@ -53,6 +53,69 @@ describe('.buildSelectObject()', () => {
|
|
|
53
53
|
name: z.string()
|
|
54
54
|
})
|
|
55
55
|
});
|
|
56
|
-
assert.deepStrictEqual(buildSelectObject(schema), {
|
|
56
|
+
assert.deepStrictEqual(buildSelectObject(schema), {
|
|
57
|
+
active: true,
|
|
58
|
+
user: { age: true, name: true }
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('should recursively expand deeply nested objects', () => {
|
|
62
|
+
const schema = z.object({
|
|
63
|
+
name: z.string(),
|
|
64
|
+
organization: z.object({
|
|
65
|
+
_id: z.string(),
|
|
66
|
+
address: z.object({
|
|
67
|
+
city: z.string(),
|
|
68
|
+
country: z.string()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
assert.deepStrictEqual(buildSelectObject(schema), {
|
|
73
|
+
name: true,
|
|
74
|
+
organization: { _id: true, address: { city: true, country: true } }
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it('should expand optional nested objects', () => {
|
|
78
|
+
const schema = z.object({
|
|
79
|
+
name: z.string(),
|
|
80
|
+
organization: z.object({
|
|
81
|
+
_id: z.string(),
|
|
82
|
+
name: z.string()
|
|
83
|
+
}).optional()
|
|
84
|
+
});
|
|
85
|
+
assert.deepStrictEqual(buildSelectObject(schema), {
|
|
86
|
+
name: true,
|
|
87
|
+
organization: { _id: true, name: true }
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it('should not expand arrays', () => {
|
|
91
|
+
const schema = z.object({
|
|
92
|
+
name: z.string(),
|
|
93
|
+
tags: z.array(z.string())
|
|
94
|
+
});
|
|
95
|
+
assert.deepStrictEqual(buildSelectObject(schema), { name: true, tags: true });
|
|
96
|
+
});
|
|
97
|
+
it('should not expand arrays of objects', () => {
|
|
98
|
+
const schema = z.object({
|
|
99
|
+
items: z.array(z.object({ id: z.string(), value: z.number() })),
|
|
100
|
+
name: z.string()
|
|
101
|
+
});
|
|
102
|
+
assert.deepStrictEqual(buildSelectObject(schema), { items: true, name: true });
|
|
103
|
+
});
|
|
104
|
+
it('should handle recursive schemas without infinite loop', () => {
|
|
105
|
+
;
|
|
106
|
+
const node = z.lazy(() => z.object({
|
|
107
|
+
children: z.array(node).optional(),
|
|
108
|
+
name: z.string(),
|
|
109
|
+
parent: node.optional()
|
|
110
|
+
}));
|
|
111
|
+
const schema = z.object({
|
|
112
|
+
name: z.string(),
|
|
113
|
+
root: node
|
|
114
|
+
});
|
|
115
|
+
// Should not throw or hang — recursive objects hit depth limit
|
|
116
|
+
const result = buildSelectObject(schema);
|
|
117
|
+
assert.strictEqual(result.name, true);
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
119
|
+
assert.ok(typeof result.root === 'object' && result.root !== null);
|
|
57
120
|
});
|
|
58
121
|
});
|
|
@@ -210,4 +210,33 @@ describe('.buildSelectSchema()', () => {
|
|
|
210
210
|
}
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
|
+
it('should allow nested object sub-selection in union members', () => {
|
|
214
|
+
const account = z.object({
|
|
215
|
+
_id: z.string(),
|
|
216
|
+
name: z.string()
|
|
217
|
+
});
|
|
218
|
+
const individual = z.object({
|
|
219
|
+
account: account.optional(),
|
|
220
|
+
name: z.object({ first: z.string(), last: z.string() }),
|
|
221
|
+
type: z.literal('individual')
|
|
222
|
+
});
|
|
223
|
+
const organization = z.object({
|
|
224
|
+
account: account.optional(),
|
|
225
|
+
name: z.string(),
|
|
226
|
+
type: z.literal('organization')
|
|
227
|
+
});
|
|
228
|
+
const contact = z.union([individual, organization]);
|
|
229
|
+
const selectSchema = buildSelectSchema(contact);
|
|
230
|
+
// Runtime: nested sub-selection on a shared nested object field should parse
|
|
231
|
+
const result = selectSchema.parse({
|
|
232
|
+
account: { _id: true, name: true },
|
|
233
|
+
type: true
|
|
234
|
+
});
|
|
235
|
+
assert.deepStrictEqual(result, {
|
|
236
|
+
account: { _id: true, name: true },
|
|
237
|
+
type: true
|
|
238
|
+
});
|
|
239
|
+
const nested = { account: { _id: true, name: true }, type: true };
|
|
240
|
+
void nested;
|
|
241
|
+
});
|
|
213
242
|
});
|
package/dist/refine-schema.d.ts
CHANGED
|
@@ -30,9 +30,11 @@ export type RefineZodTuple<T extends ZodTuple> = {
|
|
|
30
30
|
[K in keyof UnionToIntersection<T['def']['items'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
|
|
31
31
|
};
|
|
32
32
|
export type RefineZodUnion<T extends ZodUnion> = {
|
|
33
|
-
[K in keyof
|
|
33
|
+
[K in keyof UnionMergedShape<T>]?: UnionMergedShape<T>[K] extends ZodType ? UnionFieldType<UnionMergedShape<T>[K]> : boolean;
|
|
34
34
|
};
|
|
35
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>>;
|
|
36
|
+
type UnionFieldType<T extends ZodType> = z.infer<T> extends Array<infer U> ? Exclude<U, null | undefined> extends object ? boolean | RefineObject<Extract<U, object>, true> : boolean : Exclude<z.infer<T>, null | undefined> extends object ? boolean | RefineObject<Exclude<z.infer<T>, null | undefined>, true> : boolean;
|
|
37
|
+
type UnionMergedShape<T extends ZodUnion> = UnionToIntersection<T['options'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>;
|
|
36
38
|
export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
37
39
|
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>>>;
|
|
38
40
|
export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T, false>>(schema: T, shape: TShape): RefinedSchema<T, TShape, false>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zodselect",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@secure-data-systems/tsconfig": "^1.0.5",
|
|
30
30
|
"@types/node": "^25.6.0",
|
|
31
31
|
"tsx": "^4.21.0",
|
|
32
|
-
"typescript": "^6.0.
|
|
32
|
+
"typescript": "^6.0.3"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"zod": "^4.3.6"
|