zodselect 1.3.1 → 1.4.0

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,19 +1,25 @@
1
- import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodType, ZodUnion } from 'zod';
2
- import { UnionToIntersection } from './refine-schema.js';
1
+ import { ZodArray, ZodCatch, ZodDefault, ZodIntersection, ZodLazy, ZodNonOptional, ZodNullable, ZodObject, ZodOptional, ZodPipe, ZodPrefault, ZodRawShape, ZodReadonly, ZodTransform, ZodType, ZodUnion } from 'zod';
3
2
  import { ZodSelect } from './select.js';
4
3
  type Depth = [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
5
4
  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> ? [NotObject<TOptions[number]>] extends [never] ? UnionShape<TOptions> extends infer TShape extends ZodRawShape ? ObjectSelectAll<TShape, TDepth> : true : true : true;
5
+ type ItemSelectAll<T, TDepth extends number> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, TDepth> : Unwrap<T> extends ZodUnion<infer TOptions> ? [NotObject<TOptions[number]>] extends [never] ? VariantsSelectAll<TOptions[number], TDepth> : true : true;
6
+ /** Every key of every member of the union `T`. */
7
+ type KeysOfUnion<T> = T extends unknown ? keyof T : never;
8
+ /** Select-all objects merged as {@link buildSelectObject} merges them: a key `true` in any of them is `true`, objects merge by key. */
9
+ type MergeSelectAll<T> = [T] extends [never] ? never : true extends T ? true : {
10
+ [K in KeysOfUnion<T>]-?: MergeSelectAll<T extends unknown ? K extends keyof T ? T[K] : never : never>;
11
+ };
7
12
  type NotObject<T> = T extends unknown ? Unwrap<T> extends ZodObject<any, any> ? never : T : never;
8
13
  type ObjectSelectAll<TShape extends ZodRawShape, TDepth extends number> = {
9
14
  [K in keyof TShape]-?: FieldSelectAll<TShape[K], TDepth>;
10
15
  };
11
- type ShapeOf<T> = T extends unknown ? Unwrap<T> extends ZodObject<infer TShape, any> ? TShape : never : never;
12
- /** The shapes of a union's object variants, merged; `unknown` when it has none. */
13
- type UnionShape<TOptions> = UnionToIntersection<TOptions extends readonly (infer TOption)[] ? ShapeOf<TOption> : never>;
14
- 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;
16
+ type Unwrap<T> = T extends ZodCatch<infer I> | ZodDefault<infer I> | ZodNonOptional<infer I> | ZodNullable<infer I> | ZodOptional<infer I> | ZodPrefault<infer I> | ZodReadonly<infer I> ? Unwrap<I> : T extends ZodLazy<infer I> ? Unwrap<I> : T extends ZodPipe<any, infer O> ? O extends ZodTransform ? T : Unwrap<O> : T;
17
+ /** The select-all of each object variant in `T`, through intersections, merged. */
18
+ type VariantsSelectAll<T, TDepth extends number> = MergeSelectAll<T extends unknown ? Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, TDepth> : Unwrap<T> extends ZodIntersection<infer L, infer R> ? VariantsSelectAll<L | R, TDepth> : Unwrap<T> extends ZodUnion<infer TOptions> ? VariantsSelectAll<TOptions[number], TDepth> : never : never>;
15
19
  /** What {@link buildSelectObject} answers: nested for objects, unions of objects, and arrays of either; `true` for anything else. */
16
- export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, 10> : {
20
+ export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, 10> : Unwrap<T> extends ZodIntersection | ZodUnion ? [VariantsSelectAll<T, 10>] extends [never] ? {
21
+ [K in keyof ZodSelect<T, true>]-?: true;
22
+ } : VariantsSelectAll<T, 10> : {
17
23
  [K in keyof ZodSelect<T, true>]-?: true;
18
24
  };
19
25
  /**
@@ -25,7 +31,9 @@ export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> exten
25
31
  * with any other variant, such as a name that is a string or a structured
26
32
  * name, remains `true`: its sub-fields would miss the plain value. Other arrays
27
33
  * and records remain `true`.
28
- * For union schemas the result is the merged set of keys across all variants.
34
+ * For union schemas the result is the merged set of keys across all variants,
35
+ * a key nested in one variant and plain in another selected whole.
36
+ * Past the depth limit, a field is selected whole.
29
37
  *
30
38
  * @example
31
39
  * ```ts
@@ -1,5 +1,5 @@
1
- import { ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
2
- import { getInnerType, isZodArray, isZodObject, isZodUnion } from './utilities.js';
1
+ import { ZodLazy } from 'zod';
2
+ import { getInnerType, isZodArray, isZodObject, isZodUnion, unwrapWrapper } from './utilities.js';
3
3
  /**
4
4
  * Produces a runtime select-all object for the given Zod schema.
5
5
  *
@@ -9,7 +9,9 @@ import { getInnerType, isZodArray, isZodObject, isZodUnion } from './utilities.j
9
9
  * with any other variant, such as a name that is a string or a structured
10
10
  * name, remains `true`: its sub-fields would miss the plain value. Other arrays
11
11
  * and records remain `true`.
12
- * For union schemas the result is the merged set of keys across all variants.
12
+ * For union schemas the result is the merged set of keys across all variants,
13
+ * a key nested in one variant and plain in another selected whole.
14
+ * Past the depth limit, a field is selected whole.
13
15
  *
14
16
  * @example
15
17
  * ```ts
@@ -23,14 +25,12 @@ export function buildSelectObject(schema) {
23
25
  return result;
24
26
  }
25
27
  /**
26
- * Unwraps optional/nullable/default/catch/lazy wrappers but NOT arrays.
28
+ * Unwraps optional/nullable/default/catch/readonly/pipe/lazy wrappers but NOT arrays.
27
29
  */
28
30
  function unwrapField(schema) {
29
- if (schema instanceof ZodOptional
30
- || schema instanceof ZodNullable
31
- || schema instanceof ZodDefault
32
- || schema instanceof ZodCatch) {
33
- return unwrapField(schema.def.innerType);
31
+ const unwrapped = unwrapWrapper(schema);
32
+ if (unwrapped) {
33
+ return unwrapField(unwrapped);
34
34
  }
35
35
  if (schema instanceof ZodLazy) {
36
36
  return unwrapField(schema.def.getter());
@@ -39,22 +39,19 @@ function unwrapField(schema) {
39
39
  }
40
40
  const MAX_DEPTH = 10;
41
41
  function collectKeys(schema, result, depth = 0) {
42
- if (depth >= MAX_DEPTH) {
43
- return;
44
- }
45
42
  schema = getInnerType(schema);
46
43
  if (isZodObject(schema)) {
47
44
  for (const key in schema.shape) {
48
45
  const field = unwrapField(schema.shape[key]);
49
46
  const fieldInner = isZodArray(field) ? unwrapField(field.def.element) : field;
50
- if (isZodObject(fieldInner) || isUnionOfObjects(fieldInner)) {
47
+ let value = true;
48
+ if ((depth < MAX_DEPTH) && (isZodObject(fieldInner) || isUnionOfObjects(fieldInner))) {
51
49
  const nested = {};
52
50
  collectKeys(fieldInner, nested, depth + 1);
53
- result[key] = nested;
54
- }
55
- else {
56
- result[key] = true;
51
+ value = nested;
57
52
  }
53
+ const existing = result[key];
54
+ result[key] = (existing === undefined) ? value : mergeSelectValue(existing, value);
58
55
  }
59
56
  }
60
57
  else if (isZodUnion(schema)) {
@@ -70,3 +67,15 @@ function collectKeys(schema, result, depth = 0) {
70
67
  function isUnionOfObjects(schema) {
71
68
  return isZodUnion(schema) && schema.options.every(option => isZodObject(unwrapField(option)));
72
69
  }
70
+ /** Two variants' selects of one key: `true` if either is, as sub-fields would miss a plain value; else their keys merged. */
71
+ function mergeSelectValue(a, b) {
72
+ if ((a === true) || (b === true)) {
73
+ return true;
74
+ }
75
+ const merged = { ...a };
76
+ for (const [key, value] of Object.entries(b)) {
77
+ const existing = merged[key];
78
+ merged[key] = (existing === undefined) ? value : mergeSelectValue(existing, value);
79
+ }
80
+ return merged;
81
+ }
@@ -15,6 +15,9 @@ function buildSelect(schema, cache = new Map()) {
15
15
  if (isZodObject(schema)) {
16
16
  const shape = {};
17
17
  let select;
18
+ // Stands in for this object while its fields are built, so a field that comes back to it, as a getter can,
19
+ // resolves. It is only read once parsing starts, after `result` below is set.
20
+ cache.set(schema, z.lazy(() => result));
18
21
  for (const key in schema.shape) {
19
22
  shape[key] = buildSelect(schema.shape[key], cache);
20
23
  }
@@ -58,48 +61,55 @@ function buildSelect(schema, cache = new Map()) {
58
61
  types = schema.def.options;
59
62
  }
60
63
  if (types) {
61
- const hasLazy = types.some(item => isZodLazy(item));
62
- if (hasLazy) {
63
- return z.lazy(() => {
64
- return buildSelectMerge(types, cache);
65
- });
66
- }
67
- return buildSelectMerge(types, cache);
64
+ return mergeSelects(types.map(type => buildSelect(type, cache)));
68
65
  }
69
66
  return z.boolean().optional();
70
67
  }
71
- function buildSelectMerge(types, cache) {
72
- const shape = {};
68
+ export function buildSelectSchema(schema) {
69
+ const result = buildSelect(schema);
70
+ if (isZodLazy(result)) {
71
+ return result.def.getter().def.innerType.def.options[1];
72
+ }
73
+ return result.def.innerType.def.options[1];
74
+ }
75
+ /** A select for a value of any of the clauses' types: their object selects' keys, a key two of them share merged in turn. */
76
+ function mergeClauses(clauses) {
77
+ const keySelects = {};
73
78
  let isStrict = false;
74
- for (const cur of types) {
75
- const select = buildSelect(cur, cache);
76
- let clause;
77
- if (isZodLazy(select)) {
78
- clause = select.def.getter();
79
- }
80
- else {
81
- clause = select;
82
- }
79
+ for (const clause of clauses) {
83
80
  if (clause.def.innerType.def.type === 'union') {
84
81
  const obj = getInnerType(clause.def.innerType.def.options[1]);
85
82
  for (const key in obj.shape) {
86
- shape[key] = obj.shape[key];
83
+ (keySelects[key] ??= []).push(obj.shape[key]);
87
84
  }
88
85
  if (!isZodObjectLoose(obj)) {
89
86
  isStrict = true;
90
87
  }
91
88
  }
92
89
  }
90
+ const shape = {};
91
+ for (const [key, selects] of Object.entries(keySelects)) {
92
+ const distinct = [...new Set(selects)];
93
+ shape[key] = (distinct.length === 1) ? distinct[0] : mergeKeySelects(distinct);
94
+ }
93
95
  let result = z.object(shape);
94
96
  if (isStrict) {
95
97
  result = result.strict();
96
98
  }
97
99
  return z.union([z.boolean(), result]).optional();
98
100
  }
99
- export function buildSelectSchema(schema) {
100
- const result = buildSelect(schema);
101
- if (isZodLazy(result)) {
102
- return result.def.getter().def.innerType.def.options[1];
101
+ /** The selects of one key that several variants share: a plain boolean when none selects into an object. */
102
+ function mergeKeySelects(selects) {
103
+ const isNested = (select) => isZodLazy(select) || (select.def.innerType.def.type === 'union');
104
+ if (!selects.some(isNested)) {
105
+ return selects[0];
103
106
  }
104
- return result.def.innerType.def.options[1];
107
+ return mergeSelects(selects.filter(isNested));
108
+ }
109
+ /** Selects merged into one, lazily when any is lazy, as a recursive one may not be built yet. */
110
+ function mergeSelects(selects) {
111
+ if (selects.some(select => isZodLazy(select))) {
112
+ return z.lazy(() => mergeClauses(selects.map(select => (isZodLazy(select) ? select.def.getter() : select))));
113
+ }
114
+ return mergeClauses(selects);
105
115
  }
@@ -26,7 +26,7 @@ type Push<T extends any[], V> = [...T, V];
26
26
  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;
27
27
  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;
28
28
  export type RefinedTypeSchema<T extends object, TShape, TDepth extends number = 0> = TDepth extends keyof DepthLimit ? ZodObject<{
29
- [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 ? ZodifyField<T, K> : TShape[K] extends object ? Exclude<T[K], null | undefined> extends Array<infer U> ? [Extract<U, object>] extends [never] ? Zodify<T[K]> : ApplyOptionalNullable<T[K], z.ZodArray<RefinedVariants<Extract<U, object>, TShape[K], NextDepth<TDepth>>>> : [Extract<Exclude<T[K], null | undefined>, object>] extends [never] ? ZodNever : ApplyOptionalNullable<T[K], RefinedVariants<Extract<Exclude<T[K], null | undefined>, object>, TShape[K], NextDepth<TDepth>>> : ZodNever;
29
+ [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 ? ZodifyField<T, K> : TShape[K] extends object ? Exclude<T[K], null | undefined> extends ReadonlyArray<infer U> ? [Extract<U, object>] extends [never] ? Zodify<T[K]> : ApplyOptionalNullable<T[K], z.ZodArray<RefinedVariants<Extract<U, object>, TShape[K], NextDepth<TDepth>>>> : [Extract<Exclude<T[K], null | undefined>, object>] extends [never] ? ZodNever : ApplyOptionalNullable<T[K], RefinedVariants<Extract<Exclude<T[K], null | undefined>, object>, TShape[K], NextDepth<TDepth>>> : ZodNever;
30
30
  }, core.$strip> : any;
31
31
  export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options'] extends Readonly<[infer A, ...infer Rest]> ? Rest extends ZodType[] ? [
32
32
  ReapplyOptionalNullable<A, A extends ZodType<object> ? RefinedTypeSchema<z.infer<A>, TShape> : Extract<A, ZodType>>,
@@ -43,7 +43,7 @@ export type RefineObject<T extends object, TIsSimple extends boolean> = {
43
43
  [K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]?: RefineType<T[K], TIsSimple>;
44
44
  };
45
45
  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;
46
- type RefineType<T, TIsSimple extends boolean> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, ([Extract<Exclude<T, null | undefined>, object>] extends [never] ? never : RefineObject<Extract<Exclude<T, null | undefined>, object>, TIsSimple>) | 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> ? [Extract<U, object>] extends [never] ? Refinement<Zodify<T>, TIsSimple> : Refinement<Zodify<T>, TIsSimple> | RefineObject<Extract<U, object>, 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>;
46
+ type RefineType<T, TIsSimple extends boolean> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, ([Extract<Exclude<T, null | undefined>, object>] extends [never] ? never : RefineObject<Extract<Exclude<T, null | undefined>, object>, TIsSimple>) | 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 ReadonlyArray<infer U> ? [Extract<U, object>] extends [never] ? Refinement<Zodify<T>, TIsSimple> : Refinement<Zodify<T>, TIsSimple> | RefineObject<Extract<U, object>, 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>;
47
47
  export type RefineZodTuple<T extends ZodTuple> = {
48
48
  [K in keyof UnionToIntersection<T['def']['items'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
49
49
  };
@@ -51,7 +51,7 @@ export type RefineZodUnion<T extends ZodUnion> = {
51
51
  [K in keyof UnionMergedShape<T>]?: UnionMergedShape<T>[K] extends ZodType ? UnionFieldType<UnionMergedShape<T>[K]> : boolean;
52
52
  };
53
53
  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>>;
54
- type UnionFieldType<T extends ZodType> = Exclude<z.infer<T>, null | undefined> 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;
54
+ type UnionFieldType<T extends ZodType> = Exclude<z.infer<T>, null | undefined> extends ReadonlyArray<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;
55
55
  type UnionMergedShape<T extends ZodUnion> = UnionToIntersection<T['options'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>;
56
56
  export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
57
57
  export type Zodify<T, TDepth extends number = 0> = ApplyOptionalNullable<T, IsAny<T, ZodAny, ZodifyLiteral<Exclude<T, null | undefined>, IsTuple<TuplifyUnion<Exclude<T, null | undefined>, TDepth>, ZodUnion<TuplifyUnion<Exclude<T, null | undefined>, TDepth>>, InternalZodify<Exclude<T, null | undefined>, TDepth>>>>>;
@@ -1,5 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { isZodArray, isZodCatch, isZodDefault, isZodLazy, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
2
+ import { getInnerType, isZodArray, isZodCatch, isZodDefault, isZodLazy, isZodNonOptional, isZodNullable, isZodObject, isZodOptional, isZodPipe, isZodPrefault, isZodReadonly, isZodTransform, isZodType, isZodUnion } from './utilities.js';
3
+ /** Whether a value of `schema` can be an object, so that a nested select could pick within it. */
4
+ function holdsObject(schema) {
5
+ const inner = getInnerType(schema);
6
+ return isZodObject(inner) || (isZodUnion(inner) && inner.options.some(option => holdsObject(option)));
7
+ }
3
8
  /** `shape`'s fields of `schema`. A key the schema doesn't have, at any depth, is a type error. */
4
9
  export function refineSchema(schema, shape) {
5
10
  return refineSchemaUnchecked(schema, shape);
@@ -31,10 +36,26 @@ function refineSchemaField(fieldSchema, fieldShape) {
31
36
  else if (isZodNullable(fieldSchema)) {
32
37
  return refineSchemaField(fieldSchema.def.innerType, fieldShape).nullable();
33
38
  }
34
- else if (isZodDefault(fieldSchema) || isZodCatch(fieldSchema)) {
39
+ else if (isZodDefault(fieldSchema) || isZodPrefault(fieldSchema) || isZodCatch(fieldSchema)) {
35
40
  // A default or catch is a whole value, which a picked part no longer fits, so it gives way to what it wraps.
36
41
  return refineSchemaField(fieldSchema.def.innerType, fieldShape);
37
42
  }
43
+ else if (isZodNonOptional(fieldSchema)) {
44
+ return refineSchemaField(fieldSchema.def.innerType, fieldShape).nonoptional();
45
+ }
46
+ else if (isZodReadonly(fieldSchema)) {
47
+ return refineSchemaField(fieldSchema.def.innerType, fieldShape).readonly();
48
+ }
49
+ else if (isZodPipe(fieldSchema)) {
50
+ if (!isZodTransform(fieldSchema.def.out)) {
51
+ return fieldSchema.def.in.pipe(refineSchemaField(fieldSchema.def.out, fieldShape));
52
+ }
53
+ // A transform's output has no schema to pick from, and picking its input by the output's keys would feed it
54
+ // a part it doesn't expect. Kept whole, it would hand back fields that weren't selected, so it is refused.
55
+ if (holdsObject(fieldSchema.def.in)) {
56
+ throw new Error('Cannot select fields within a transformed object: select it with `true`, or redefine it with a schema or function.');
57
+ }
58
+ }
38
59
  else if (isZodLazy(fieldSchema)) {
39
60
  return refineSchemaField(fieldSchema.def.getter(), fieldShape);
40
61
  }
@@ -49,7 +70,7 @@ export function refineSchemaUnchecked(schema, shape) {
49
70
  const refinedShape = {};
50
71
  const schemaShape = schema.shape;
51
72
  for (const key in shape) {
52
- if (key in schemaShape && shape[key] !== false) {
73
+ if (Object.hasOwn(schemaShape, key) && shape[key] !== false) {
53
74
  refinedShape[key] = refineSchemaField(schemaShape[key], shape[key]);
54
75
  }
55
76
  }
package/dist/select.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { z, ZodTuple, ZodType, ZodUnion } from 'zod';
2
2
  import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodTuple, RefineZodUnion } from './refine-schema.js';
3
- import { DeepMergeAll, IsEqual } from './types.js';
3
+ import { DeepMergeAll } 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
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
- 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]>;
9
+ export type MergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>> = TSelect2 extends undefined ? [TSelect1, T] : TSelect2 extends [ZodSelect<T>, ...ZodSelect<T>[]] ? [TSelect1, ...TSelect2] : [TSelect1, TSelect2];
10
10
  export type MultiSelect<T extends ZodType<object> | ZodUnion> = [
11
11
  ZodSelect<T>,
12
12
  ...ZodSelect<T>[]
package/dist/types.d.ts CHANGED
@@ -1,7 +1,12 @@
1
+ /**
2
+ * `U` merged into `T`, as types: keys in both merge deeply when both sides are objects, and by their elements when
3
+ * both are arrays, kept `null` or `undefined` where a side may be; otherwise they widen to `T[K] | U[K]`.
4
+ */
1
5
  export type DeepMerge<T, U> = {
2
- [K in keyof T | keyof U]: K extends keyof T ? K extends keyof U ? T[K] extends object ? U[K] extends object ? DeepMerge<T[K], U[K]> : T[K] | U[K] : T[K] | U[K] : T[K] : K extends keyof U ? U[K] : never;
6
+ [K in keyof T | keyof U]: K extends keyof T ? K extends keyof U ? DeepMergeValue<T[K], U[K]> : T[K] : K extends keyof U ? U[K] : never;
3
7
  };
4
8
  export type DeepMergeAll<T> = T extends readonly any[] ? T extends [infer First, ...infer Rest] ? Rest extends [] ? First : DeepMerge<First, DeepMergeAll<Rest>> : unknown : T;
9
+ type DeepMergeValue<T, U> = [NonNullable<T>, NonNullable<U>] extends [infer A, infer B] ? [A] extends [never] ? T | U : [B] extends [never] ? T | U : [Extract<A | B, ((...args: never[]) => unknown) | Date>] extends [never] ? [A, B] extends [readonly (infer AItem)[], readonly (infer BItem)[]] ? DeepMergeValue<AItem, BItem>[] | Extract<T | U, null | undefined> : [A, B] extends [object, object] ? [Extract<A | B, readonly unknown[]>] extends [never] ? DeepMerge<A, B> | Extract<T | U, null | undefined> : T | U : T | U : T | U : never;
5
10
  export type IsAny<T, TTrue = true, TFalse = false> = 0 extends (1 & T) ? TTrue : TFalse;
6
11
  export type IsEqual<T, U, TTrue = true, TFalse = false> = (<V>() => V extends T ? 1 : 2) extends (<V>() => V extends U ? 1 : 2) ? TTrue : TFalse;
7
12
  export type IsJsonType<T, TDeep extends boolean = true, TTrue = true, TFalse = false> = T extends JsonType ? TTrue : T extends Function ? TFalse : T extends Array<infer U> ? TDeep extends false ? TTrue : IsJsonType<U, TDeep, TTrue, TFalse> : T extends object ? TDeep extends false ? TTrue : {
@@ -1,14 +1,24 @@
1
- import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRecord, ZodType, ZodUnion, ZodUnknown } from 'zod';
1
+ import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNonOptional, ZodNullable, ZodObject, ZodOptional, ZodPipe, ZodPrefault, ZodReadonly, ZodRecord, ZodType, ZodUnion, ZodUnknown } from 'zod';
2
2
  export declare function getInnerType(schema: ZodType, unwrapLazy?: boolean): ZodType;
3
3
  export declare function isZodArray(obj: unknown): obj is ZodArray<ZodType>;
4
4
  export declare function isZodCatch(obj: unknown): obj is ZodCatch<ZodType>;
5
5
  export declare function isZodDefault(obj: unknown): obj is ZodDefault<ZodType>;
6
6
  export declare function isZodLazy(obj: unknown): obj is ZodLazy;
7
+ export declare function isZodNonOptional(obj: unknown): obj is ZodNonOptional<ZodType>;
7
8
  export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>;
8
9
  export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
9
10
  export declare function isZodObjectLoose(obj: ZodObject): boolean;
10
11
  export declare function isZodOptional(obj: unknown): obj is ZodOptional<ZodType>;
12
+ export declare function isZodPipe(obj: unknown): obj is ZodPipe;
13
+ export declare function isZodPrefault(obj: unknown): obj is ZodPrefault<ZodType>;
14
+ export declare function isZodReadonly(obj: unknown): obj is ZodReadonly<ZodType>;
11
15
  export declare function isZodRecord(obj: unknown): obj is ZodRecord;
16
+ export declare function isZodTransform(obj: unknown): boolean;
12
17
  export declare function isZodType(obj: unknown): obj is ZodType;
13
18
  export declare function isZodUnion(obj: unknown): obj is ZodUnion<ZodType[]>;
14
19
  export declare function isZodUnknown(obj: unknown): obj is ZodUnknown;
20
+ /**
21
+ * The schema a value-preserving wrapper holds: optional, nullable, default, prefault, catch, nonoptional, readonly,
22
+ * or a pipe's output side. `undefined` for anything else, including a pipe into a transform, whose output has no schema.
23
+ */
24
+ export declare function unwrapWrapper(schema: ZodType): undefined | ZodType;
package/dist/utilities.js CHANGED
@@ -1,10 +1,8 @@
1
- import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
1
+ import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNonOptional, ZodNullable, ZodOptional, ZodPipe, ZodPrefault, ZodReadonly } from 'zod';
2
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);
3
+ const unwrapped = unwrapWrapper(schema);
4
+ if (unwrapped) {
5
+ return getInnerType(unwrapped, unwrapLazy);
8
6
  }
9
7
  if (schema instanceof ZodArray) {
10
8
  return getInnerType(schema.def.element, unwrapLazy);
@@ -26,6 +24,9 @@ export function isZodDefault(obj) {
26
24
  export function isZodLazy(obj) {
27
25
  return isZodType(obj) && (obj.def.type === 'lazy');
28
26
  }
27
+ export function isZodNonOptional(obj) {
28
+ return isZodType(obj) && (obj.def.type === 'nonoptional');
29
+ }
29
30
  export function isZodNullable(obj) {
30
31
  return isZodType(obj) && (obj.def.type === 'nullable');
31
32
  }
@@ -44,9 +45,21 @@ export function isZodObjectLoose(obj) {
44
45
  export function isZodOptional(obj) {
45
46
  return isZodType(obj) && (obj.def.type === 'optional');
46
47
  }
48
+ export function isZodPipe(obj) {
49
+ return isZodType(obj) && (obj.def.type === 'pipe');
50
+ }
51
+ export function isZodPrefault(obj) {
52
+ return isZodType(obj) && (obj.def.type === 'prefault');
53
+ }
54
+ export function isZodReadonly(obj) {
55
+ return isZodType(obj) && (obj.def.type === 'readonly');
56
+ }
47
57
  export function isZodRecord(obj) {
48
58
  return isZodType(obj) && (obj.def.type === 'record');
49
59
  }
60
+ export function isZodTransform(obj) {
61
+ return isZodType(obj) && (obj.def.type === 'transform');
62
+ }
50
63
  export function isZodType(obj) {
51
64
  return typeof obj === 'object' && obj !== null && 'def' in obj && 'safeParse' in obj && typeof obj.safeParse === 'function';
52
65
  }
@@ -56,3 +69,22 @@ export function isZodUnion(obj) {
56
69
  export function isZodUnknown(obj) {
57
70
  return isZodType(obj) && (obj.def.type === 'unknown');
58
71
  }
72
+ /**
73
+ * The schema a value-preserving wrapper holds: optional, nullable, default, prefault, catch, nonoptional, readonly,
74
+ * or a pipe's output side. `undefined` for anything else, including a pipe into a transform, whose output has no schema.
75
+ */
76
+ export function unwrapWrapper(schema) {
77
+ if (schema instanceof ZodOptional
78
+ || schema instanceof ZodNullable
79
+ || schema instanceof ZodDefault
80
+ || schema instanceof ZodPrefault
81
+ || schema instanceof ZodCatch
82
+ || schema instanceof ZodNonOptional
83
+ || schema instanceof ZodReadonly) {
84
+ return schema.def.innerType;
85
+ }
86
+ if (schema instanceof ZodPipe && !isZodTransform(schema.def.out)) {
87
+ return schema.def.out;
88
+ }
89
+ return undefined;
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
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",