zodselect 1.2.0 → 1.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.
@@ -1,12 +1,17 @@
1
1
  import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodType, ZodUnion } from 'zod';
2
+ import { UnionToIntersection } from './refine-schema.js';
2
3
  import { ZodSelect } from './select.js';
3
4
  type Depth = [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
4
- type FieldSelectAll<T, TDepth extends number> = TDepth extends 0 ? true : Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, Depth[TDepth]> : Unwrap<T> extends ZodArray<infer TItem> ? Unwrap<TItem> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, Depth[TDepth]> : true : true;
5
+ type FieldSelectAll<T, TDepth extends number> = TDepth extends 0 ? true : Unwrap<T> extends ZodArray<infer TItem> ? ItemSelectAll<TItem, Depth[TDepth]> : ItemSelectAll<T, Depth[TDepth]>;
6
+ type ItemSelectAll<T, TDepth extends number> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, TDepth> : Unwrap<T> extends ZodUnion<infer TOptions> ? UnionShape<TOptions> extends infer TShape extends ZodRawShape ? ObjectSelectAll<TShape, TDepth> : true : true;
5
7
  type ObjectSelectAll<TShape extends ZodRawShape, TDepth extends number> = {
6
8
  [K in keyof TShape]-?: FieldSelectAll<TShape[K], TDepth>;
7
9
  };
10
+ type ShapeOf<T> = T extends unknown ? Unwrap<T> extends ZodObject<infer TShape, any> ? TShape : never : never;
11
+ /** The shapes of a union's object variants, merged; `unknown` when it has none. */
12
+ type UnionShape<TOptions> = UnionToIntersection<TOptions extends readonly (infer TOption)[] ? ShapeOf<TOption> : never>;
8
13
  type Unwrap<T> = T extends ZodCatch<infer I> | ZodDefault<infer I> | ZodNullable<infer I> | ZodOptional<infer I> ? Unwrap<I> : T extends ZodLazy<infer I> ? Unwrap<I> : T;
9
- /** What {@link buildSelectObject} answers: nested for objects and arrays of objects, `true` for anything else. */
14
+ /** What {@link buildSelectObject} answers: nested for objects, unions with an object variant, and arrays of either; `true` for anything else. */
10
15
  export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, 10> : {
11
16
  [K in keyof ZodSelect<T, true>]-?: true;
12
17
  };
@@ -14,8 +19,10 @@ export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> exten
14
19
  * Produces a runtime select-all object for the given Zod schema.
15
20
  *
16
21
  * Every scalar key is set to `true`. Nested object fields, and arrays of
17
- * objects, are recursively expanded into nested select objects. Other arrays
18
- * and records remain `true`.
22
+ * objects, are recursively expanded into nested select objects. A union field
23
+ * with an object variant, such as a file stored as an id and read as the file,
24
+ * expands into the merged keys of its object variants. Other arrays and
25
+ * records remain `true`.
19
26
  * For union schemas the result is the merged set of keys across all variants.
20
27
  *
21
28
  * @example
@@ -4,8 +4,10 @@ import { getInnerType, isZodArray, isZodObject, isZodUnion } from './utilities.j
4
4
  * Produces a runtime select-all object for the given Zod schema.
5
5
  *
6
6
  * Every scalar key is set to `true`. Nested object fields, and arrays of
7
- * objects, are recursively expanded into nested select objects. Other arrays
8
- * and records remain `true`.
7
+ * objects, are recursively expanded into nested select objects. A union field
8
+ * with an object variant, such as a file stored as an id and read as the file,
9
+ * expands into the merged keys of its object variants. Other arrays and
10
+ * records remain `true`.
9
11
  * For union schemas the result is the merged set of keys across all variants.
10
12
  *
11
13
  * @example
@@ -44,7 +46,7 @@ function collectKeys(schema, result, depth = 0) {
44
46
  for (const key in schema.shape) {
45
47
  const field = unwrapField(schema.shape[key]);
46
48
  const fieldInner = isZodArray(field) ? unwrapField(field.def.element) : field;
47
- if (isZodObject(fieldInner)) {
49
+ if (isZodObject(fieldInner) || hasObjectVariant(fieldInner)) {
48
50
  const nested = {};
49
51
  collectKeys(fieldInner, nested, depth + 1);
50
52
  result[key] = nested;
@@ -64,3 +66,6 @@ function collectKeys(schema, result, depth = 0) {
64
66
  collectKeys(schema.def.right, result, depth);
65
67
  }
66
68
  }
69
+ function hasObjectVariant(schema) {
70
+ return isZodUnion(schema) && schema.options.some(option => isZodObject(unwrapField(option)));
71
+ }
@@ -141,13 +141,57 @@ describe('.buildSelectObject()', () => {
141
141
  const schema = z.object({
142
142
  byKey: z.record(z.string(), z.object({ id: z.string() })),
143
143
  matrix: z.array(z.array(z.number())),
144
- options: z.array(z.union([z.object({ a: z.string() }), z.object({ b: z.string() })])),
145
144
  tags: z.array(z.string())
146
145
  });
147
146
  const select = buildSelectObject(schema);
148
147
  const isTyped = true;
149
148
  assert.ok(isTyped);
150
- assert.deepStrictEqual(select, { byKey: true, matrix: true, options: true, tags: true });
149
+ assert.deepStrictEqual(select, { byKey: true, matrix: true, tags: true });
150
+ });
151
+ it('should expand a union field into the keys of its object variants', () => {
152
+ const schema = z.object({
153
+ file: z.union([z.object({ _id: z.string(), filename: z.string() }), z.string()]),
154
+ name: z.string()
155
+ });
156
+ const select = buildSelectObject(schema);
157
+ const isTyped = true;
158
+ assert.ok(isTyped);
159
+ assert.deepStrictEqual(select, { file: { _id: true, filename: true }, name: true });
160
+ });
161
+ it('should merge the keys of every object variant of a union field', () => {
162
+ const schema = z.object({
163
+ party: z.union([z.object({ name: z.string(), type: z.literal('company') }), z.object({ first: z.string(), type: z.literal('person') })]).optional()
164
+ });
165
+ const select = buildSelectObject(schema);
166
+ const isTyped = true;
167
+ assert.ok(isTyped);
168
+ assert.deepStrictEqual(select, { party: { first: true, name: true, type: true } });
169
+ });
170
+ it('should not expand a union field with no object variant', () => {
171
+ const schema = z.object({ value: z.union([z.string(), z.number()]).nullable() });
172
+ const select = buildSelectObject(schema);
173
+ const isTyped = true;
174
+ assert.ok(isTyped);
175
+ assert.deepStrictEqual(select, { value: true });
176
+ });
177
+ it('should expand an array of a union of objects', () => {
178
+ const schema = z.object({
179
+ options: z.array(z.union([z.object({ a: z.string() }), z.object({ b: z.string() })]))
180
+ });
181
+ const select = buildSelectObject(schema);
182
+ const isTyped = true;
183
+ assert.ok(isTyped);
184
+ assert.deepStrictEqual(select, { options: { a: true, b: true } });
185
+ });
186
+ it('should expand an attachment whose file is stored as an id and read as the file', () => {
187
+ const file = z.object({ _id: z.string(), contentType: z.string().optional(), filename: z.string() });
188
+ const revision = z.object({ file: z.union([file, z.string()]), uploadedBy: z.object({ _id: z.string(), username: z.string() }) });
189
+ const schema = z.object({ attachments: z.array(revision.extend({ history: z.array(revision).optional(), title: z.string().optional() })).optional() });
190
+ const select = buildSelectObject(schema);
191
+ const revisionSelect = { file: { _id: true, contentType: true, filename: true }, uploadedBy: { _id: true, username: true } };
192
+ const isTyped = true;
193
+ assert.ok(isTyped);
194
+ assert.deepStrictEqual(select, { attachments: { ...revisionSelect, history: revisionSelect, title: true } });
151
195
  });
152
196
  it('should be a select the schema accepts', () => {
153
197
  const schema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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",