zodselect 1.0.9 → 1.0.11

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.
@@ -5,5 +5,5 @@ export type BasicSelect = ZodObject<Readonly<{
5
5
  [k: string]: Clause | ZodLazy<Clause>;
6
6
  }>> | ZodRecord<$ZodRecordKey, Clause | ZodLazy<Clause>>;
7
7
  type Clause = ZodOptional<ZodBoolean> | ZodOptional<ZodUnion<readonly [ZodBoolean, BasicSelect]>>;
8
- export declare function buildSelectSchema<T extends ZodType<object> | ZodUnion>(schema: T): ZodType<ZodSelect<T, true>>;
8
+ export declare function buildSelectSchema<T extends ZodType<object> | ZodUnion>(schema: T): ZodType<ZodSelect<T, true>, ZodSelect<T, true>>;
9
9
  export {};
@@ -61,18 +61,18 @@ function buildSelect(schema, cache = new Map()) {
61
61
  const hasLazy = types.some(item => isZodLazy(item));
62
62
  if (hasLazy) {
63
63
  return z.lazy(() => {
64
- return buildSelectMerge(types);
64
+ return buildSelectMerge(types, cache);
65
65
  });
66
66
  }
67
- return buildSelectMerge(types);
67
+ return buildSelectMerge(types, cache);
68
68
  }
69
69
  return z.boolean().optional();
70
70
  }
71
- function buildSelectMerge(types) {
71
+ function buildSelectMerge(types, cache) {
72
72
  const shape = {};
73
73
  let isStrict = false;
74
74
  for (const cur of types) {
75
- const select = buildSelect(cur);
75
+ const select = buildSelect(cur, cache);
76
76
  let clause;
77
77
  if (isZodLazy(select)) {
78
78
  clause = select.def.getter();
@@ -60,6 +60,18 @@ describe('.buildSelectSchema()', () => {
60
60
  });
61
61
  assert.throws(() => selectSchema.parse({ valueC: true }), /Unrecognized key/);
62
62
  });
63
+ it('z.infer of a union select schema should be a plain select type', () => {
64
+ const schema = z.union([
65
+ z.object({ shared: z.string(), type: z.literal('a'), valueA: z.string() }).strict(),
66
+ z.object({ shared: z.string(), type: z.literal('b'), valueB: z.number() }).strict()
67
+ ]);
68
+ const selectSchema = buildSelectSchema(schema);
69
+ // Runtime: the schema correctly parses a plain select object
70
+ assert.deepEqual(selectSchema.parse({ shared: true, type: true, valueA: true }), { shared: true, type: true, valueA: true });
71
+ const plain = {};
72
+ void plain;
73
+ assert.ok(true);
74
+ });
63
75
  it('should support intersections of objects', () => {
64
76
  const schema = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
65
77
  const select = buildSelectSchema(schema);
@@ -75,7 +87,7 @@ describe('.buildSelectSchema()', () => {
75
87
  assert.strictEqual(getInnerType(select.shape.foo).def.type, 'boolean');
76
88
  assert.strictEqual(getInnerType(select.shape.bar).def.type, 'boolean');
77
89
  });
78
- it('should return a strict schema if all members are strict', () => {
90
+ it('should return a strict schema if any member is strict', () => {
79
91
  const schema = z.union([
80
92
  z.object({ strictA: z.string() }).strict(),
81
93
  z.object({ strictB: z.number() }).strict()
@@ -83,6 +95,16 @@ describe('.buildSelectSchema()', () => {
83
95
  const select = buildSelectSchema(schema);
84
96
  assert.strictEqual(isZodObjectLoose(select), false);
85
97
  });
98
+ it('should return a strict schema when only one union member is strict', () => {
99
+ const schema = z.union([
100
+ z.object({ strictA: z.string() }).strict(),
101
+ z.looseObject({ looseB: z.number() })
102
+ ]);
103
+ const select = buildSelectSchema(schema);
104
+ // Any strict member makes the merged select strict so callers cannot
105
+ // accidentally reference fields outside any member's defined shape.
106
+ assert.strictEqual(isZodObjectLoose(select), false);
107
+ });
86
108
  it('should allow optional keys in the select schema', () => {
87
109
  const schema = z.object({
88
110
  optionalField: z.string().optional()
@@ -152,6 +174,19 @@ describe('.buildSelectSchema()', () => {
152
174
  assert.strictEqual(union.def.options[1].def.valueType.type, 'optional');
153
175
  assert.strictEqual(union.def.options[1].def.valueType.def.innerType.type, 'boolean');
154
176
  });
177
+ it('should share the same lazy instance for a recursive schema referenced by multiple union members', () => {
178
+ const nested = z.lazy(() => z.object({ children: z.array(nested) }));
179
+ const schema = z.union([
180
+ z.object({ a: nested }),
181
+ z.object({ b: nested })
182
+ ]);
183
+ const select = buildSelectSchema(schema);
184
+ // Both 'a' and 'b' reference the same `nested` schema.
185
+ // Their select schemas must be the same lazy instance (shared cache).
186
+ const aSelect = select.shape.a;
187
+ const bSelect = select.shape.b;
188
+ assert.strictEqual(aSelect, bSelect);
189
+ });
155
190
  it('should support unknown types', () => {
156
191
  const schema = z.object({
157
192
  tags: z.unknown()
@@ -6,7 +6,7 @@ export function refineSchema(schema, shape) {
6
6
  const refinedShape = {};
7
7
  const schemaShape = schema.shape;
8
8
  for (const key in shape) {
9
- if (key in schemaShape) {
9
+ if (key in schemaShape && shape[key] !== false) {
10
10
  refinedShape[key] = refineSchemaField(schemaShape[key], shape[key]);
11
11
  }
12
12
  }
@@ -203,6 +203,19 @@ describe('.refineSchema()', () => {
203
203
  return true;
204
204
  });
205
205
  });
206
+ it('should exclude fields specified as false in the shape', () => {
207
+ const schema = z.object({
208
+ firstName: z.string(),
209
+ lastName: z.string()
210
+ });
211
+ const refined = refineSchema(schema, {
212
+ firstName: true,
213
+ lastName: false
214
+ });
215
+ // lastName was specified as false — it should be excluded from the refined schema
216
+ const result = refined.parse({ firstName: 'Alice', lastName: 'Smith' });
217
+ assert.deepStrictEqual(result, { firstName: 'Alice' });
218
+ });
206
219
  it('should support unions of objects and simple types', () => {
207
220
  const person = z.object({
208
221
  firstName: z.string(),
package/dist/select.d.ts CHANGED
@@ -11,5 +11,5 @@ export type MultiSelect<T extends ZodType<object> | ZodUnion> = [
11
11
  ZodSelect<T>,
12
12
  ...ZodSelect<T>[]
13
13
  ] | undefined | ZodSelect<T>;
14
- 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);
14
+ export type ZodSelect<T extends ZodTuple | ZodType<object> | ZodUnion, TIsSimple extends boolean = false> = (T extends ZodUnion ? (RefineZodUnion<T> | (TIsSimple extends true ? never : ZodUnion)) : T extends ZodTuple ? (RefineZodTuple<T> | (TIsSimple extends true ? never : ZodTuple)) : T extends ZodType<object> ? (RefineObject<z.infer<T>, TIsSimple> | (TIsSimple extends true ? never : ZodType<object>)) : never);
15
15
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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,14 +25,14 @@
25
25
  }
26
26
  },
27
27
  "devDependencies": {
28
- "@secure-data-systems/eslint-config": "^1.0.33",
29
- "@secure-data-systems/tsconfig": "^1.0.3",
30
- "@types/node": "^24.10.1",
28
+ "@secure-data-systems/eslint-config": "^1.0.36",
29
+ "@secure-data-systems/tsconfig": "^1.0.5",
30
+ "@types/node": "^25.6.0",
31
31
  "tsx": "^4.21.0",
32
- "typescript": "^5.9.3"
32
+ "typescript": "^6.0.2"
33
33
  },
34
34
  "dependencies": {
35
- "zod": "^4.1.13"
35
+ "zod": "^4.3.6"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node scripts/build.mjs && tsc --project tsconfig.build.json",
@@ -40,6 +40,7 @@
40
40
  "deploy": "npm run build && node ./scripts/publish.mjs",
41
41
  "lint": "eslint . --c eslint.config.mjs",
42
42
  "lint:fix": "eslint . --c eslint.config.mjs --fix",
43
+ "prune": "node scripts/prune.mjs",
43
44
  "test": "tsx --tsconfig ./tsconfig.build.json --test ./src/**/*.{spec,test}.ts ./test/**/*.{spec,test}.ts"
44
45
  }
45
46
  }