zodselect 1.0.14 → 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 top-level key is set to `true`, which tells the server to include
10
- * that field in the response. For union schemas the result is the merged
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 top-level key is set to `true`, which tells the server to include
6
- * that field in the response. For union schemas the result is the merged
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
- function collectKeys(schema, result) {
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
- result[key] = true;
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 only collect top-level keys (not nested)', () => {
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), { active: true, user: true });
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.0.14",
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",