zodselect 1.0.2 → 1.0.4

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.
@@ -0,0 +1,8 @@
1
+ import { ZodBoolean, ZodLazy, ZodObject, ZodOptional, ZodType, ZodUnion } from 'zod';
2
+ import { ZodSelect } from './select.js';
3
+ export type BasicSelect = ZodObject<Readonly<{
4
+ [k: string]: Clause | ZodLazy<Clause>;
5
+ }>>;
6
+ type Clause = ZodOptional<ZodBoolean> | ZodOptional<ZodUnion<readonly [ZodBoolean, BasicSelect]>>;
7
+ export declare function buildSelectSchema<T extends ZodType<object> | ZodUnion>(schema: T): ZodType<ZodSelect<T, true>>;
8
+ export {};
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ import { getInnerType, isZodLazy, isZodObject, isZodObjectLoose } from './utilities.js';
3
+ function buildSelect(schema, cache = new Map()) {
4
+ schema = getInnerType(schema, false);
5
+ if (cache.has(schema)) {
6
+ return cache.get(schema);
7
+ }
8
+ if (isZodLazy(schema)) {
9
+ const select = z.lazy(() => {
10
+ return buildSelect(schema.def.getter(), cache);
11
+ });
12
+ cache.set(schema, select);
13
+ return select;
14
+ }
15
+ if (isZodObject(schema)) {
16
+ const shape = {};
17
+ let select;
18
+ for (const key in schema.shape) {
19
+ shape[key] = buildSelect(schema.shape[key], cache);
20
+ }
21
+ select = z.object(shape);
22
+ if (!isZodObjectLoose(schema)) {
23
+ select = select.strict();
24
+ }
25
+ const result = z.union([z.boolean(), select]).optional();
26
+ cache.set(schema, result);
27
+ return result;
28
+ }
29
+ let types;
30
+ if (schema.def.type === 'intersection') {
31
+ types = [schema.def.left, schema.def.right];
32
+ }
33
+ if (schema.def.type === 'tuple') {
34
+ types = schema.def.items;
35
+ }
36
+ if (schema.def.type === 'union') {
37
+ types = schema.def.options;
38
+ }
39
+ if (types) {
40
+ const hasLazy = types.some(item => isZodLazy(item));
41
+ if (hasLazy) {
42
+ return z.lazy(() => {
43
+ return buildSelectMerge(types);
44
+ });
45
+ }
46
+ return buildSelectMerge(types);
47
+ }
48
+ return z.boolean().optional();
49
+ }
50
+ function buildSelectMerge(types) {
51
+ const shape = {};
52
+ let isStrict = false;
53
+ for (const cur of types) {
54
+ const select = buildSelect(cur);
55
+ let clause;
56
+ if (isZodLazy(select)) {
57
+ clause = select.def.getter();
58
+ }
59
+ else {
60
+ clause = select;
61
+ }
62
+ if (clause.def.innerType.def.type === 'union') {
63
+ const obj = getInnerType(clause.def.innerType.def.options[1]);
64
+ for (const key in obj.shape) {
65
+ shape[key] = obj.shape[key];
66
+ }
67
+ if (!isZodObjectLoose(obj)) {
68
+ isStrict = true;
69
+ }
70
+ }
71
+ }
72
+ let result = z.object(shape);
73
+ if (isStrict) {
74
+ result = result.strict();
75
+ }
76
+ return z.union([z.boolean(), result]).optional();
77
+ }
78
+ export function buildSelectSchema(schema) {
79
+ const result = buildSelect(schema);
80
+ if (isZodLazy(result)) {
81
+ return result.def.getter().def.innerType.def.options[1];
82
+ }
83
+ return result.def.innerType.def.options[1];
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ import { strict as assert } from 'assert';
2
+ import { describe, it } from 'node:test';
3
+ import { z, ZodObject } from 'zod';
4
+ import { buildSelectSchema } from './build-select-schema.js';
5
+ import { getInnerType, isZodObjectLoose } from './utilities.js';
6
+ describe('.buildSelectSchema()', () => {
7
+ it('should build select schema for a flat object', () => {
8
+ const schema = z.object({
9
+ age: z.number(),
10
+ name: z.string()
11
+ });
12
+ const selectSchema = buildSelectSchema(schema);
13
+ assert(selectSchema instanceof ZodObject);
14
+ assert.deepEqual(selectSchema.parse({ name: true }), { name: true });
15
+ assert.deepEqual(selectSchema.parse({}), {});
16
+ assert.throws(() => selectSchema.parse({ name: 'yes' }), /expected boolean/);
17
+ assert.throws(() => selectSchema.parse({ unknown: true }), /Unrecognized key/);
18
+ });
19
+ it('should handle nested objects', () => {
20
+ const schema = z.object({
21
+ user: z.object({
22
+ id: z.string(),
23
+ profile: z.object({
24
+ displayName: z.string()
25
+ })
26
+ })
27
+ });
28
+ const selectSchema = buildSelectSchema(schema);
29
+ const result = selectSchema.parse({
30
+ user: {
31
+ id: true,
32
+ profile: {
33
+ displayName: true
34
+ }
35
+ }
36
+ });
37
+ assert.deepEqual(result, {
38
+ user: {
39
+ id: true,
40
+ profile: {
41
+ displayName: true
42
+ }
43
+ }
44
+ });
45
+ });
46
+ it('should build select schema for a union of objects', () => {
47
+ const schema = z.union([
48
+ z.object({ type: z.literal('a'), valueA: z.string() }).strict(),
49
+ z.object({ type: z.literal('b'), valueB: z.number() }).strict()
50
+ ]);
51
+ const selectSchema = buildSelectSchema(schema);
52
+ assert(selectSchema instanceof ZodObject);
53
+ const result = selectSchema.parse({
54
+ type: true,
55
+ valueA: true
56
+ });
57
+ assert.deepEqual(result, {
58
+ type: true,
59
+ valueA: true
60
+ });
61
+ assert.throws(() => selectSchema.parse({ valueC: true }), /Unrecognized key/);
62
+ });
63
+ it('should support intersections of objects', () => {
64
+ const schema = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
65
+ const select = buildSelectSchema(schema);
66
+ assert.strictEqual(getInnerType(select.shape.a).def.type, 'boolean');
67
+ assert.strictEqual(getInnerType(select.shape.b).def.type, 'boolean');
68
+ });
69
+ it('should support tuples of objects', () => {
70
+ const schema = z.tuple([
71
+ z.object({ foo: z.string() }),
72
+ z.object({ bar: z.number() })
73
+ ]);
74
+ const select = buildSelectSchema(schema);
75
+ assert.strictEqual(getInnerType(select.shape.foo).def.type, 'boolean');
76
+ assert.strictEqual(getInnerType(select.shape.bar).def.type, 'boolean');
77
+ });
78
+ it('should return a strict schema if all members are strict', () => {
79
+ const schema = z.union([
80
+ z.object({ strictA: z.string() }).strict(),
81
+ z.object({ strictB: z.number() }).strict()
82
+ ]);
83
+ const select = buildSelectSchema(schema);
84
+ assert.strictEqual(isZodObjectLoose(select), false);
85
+ });
86
+ it('should allow optional keys in the select schema', () => {
87
+ const schema = z.object({
88
+ optionalField: z.string().optional()
89
+ });
90
+ const select = buildSelectSchema(schema);
91
+ assert.strictEqual(getInnerType(select.shape.optionalField).def.type, 'boolean');
92
+ });
93
+ it('should handle direct recursion', () => {
94
+ const RoleSchema = z.lazy(() => {
95
+ return (z.object({
96
+ name: z.string(),
97
+ roles: z.array(RoleSchema)
98
+ }));
99
+ });
100
+ const select = buildSelectSchema(RoleSchema);
101
+ assert.deepStrictEqual(Object.keys(select.shape), ['name', 'roles']);
102
+ const nameSelect = select.shape.name;
103
+ assert.strictEqual(nameSelect.def.type, 'optional');
104
+ assert.strictEqual(nameSelect.def.innerType.def.type, 'boolean');
105
+ const rolesSelect = select.shape.roles;
106
+ assert.strictEqual(rolesSelect.def.type, 'lazy');
107
+ const innerUnion = rolesSelect.def.getter().def.innerType;
108
+ assert.strictEqual(innerUnion.def.type, 'union');
109
+ const booleanItem = innerUnion.def.options[0];
110
+ assert.strictEqual(booleanItem.def.type, 'boolean');
111
+ const roleItem = innerUnion.def.options[1];
112
+ assert.strictEqual(roleItem.def.type, 'object');
113
+ });
114
+ it('should support mutual recursion (cross-recursive)', () => {
115
+ const A = z.lazy(() => z.object({
116
+ b: B
117
+ }));
118
+ const B = z.lazy(() => z.object({
119
+ a: A
120
+ }));
121
+ const select = buildSelectSchema(A);
122
+ assert.strictEqual(select.shape.b.def.type, 'lazy');
123
+ const bUnion = select.shape.b.def.getter();
124
+ assert.strictEqual(bUnion.def.innerType.def.type, 'union');
125
+ const bObj = bUnion.def.innerType.def.options[1];
126
+ assert.strictEqual(bObj.shape.a.def.type, 'lazy');
127
+ });
128
+ it('should share the same instance in recursion (no infinite loop)', () => {
129
+ const Role = z.lazy(() => z.object({
130
+ name: z.string(),
131
+ roles: z.array(Role)
132
+ }));
133
+ const select = buildSelectSchema(Role);
134
+ const rolesSelect = select.shape.roles;
135
+ const unionSelect = rolesSelect.def.getter().def.innerType;
136
+ const nestedRoleSelect = unionSelect.def.options[1].shape.roles;
137
+ // Should be the same object (by reference)
138
+ assert.strictEqual(rolesSelect, nestedRoleSelect);
139
+ });
140
+ });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './build-select-schema.js';
1
2
  export * from './merge-select.js';
2
3
  export * from './refine-schema.js';
3
4
  export * from './select.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './build-select-schema.js';
1
2
  export * from './merge-select.js';
2
3
  export * from './refine-schema.js';
3
4
  export * from './select.js';
@@ -1,3 +1,3 @@
1
- import { ZodType, ZodUnion } from 'zod/v4';
1
+ import { ZodType, ZodUnion } from 'zod';
2
2
  import { MergeSelect, MultiSelect, ZodSelect } from './select.js';
3
3
  export declare function mergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>>(schema: T, select1: TSelect1, select2?: TSelect2): MergeSelect<T, TSelect1, TSelect2>;
@@ -1,6 +1,16 @@
1
- import { core, z, ZodAny, ZodArray, ZodBoolean, ZodDate, ZodLiteral, ZodNever, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, ZodString, ZodType, ZodUnion, ZodUnknown } from 'zod/v4';
1
+ import { core, z, ZodAny, ZodArray, ZodBoolean, ZodDate, ZodLiteral, ZodNever, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, ZodString, ZodTuple, ZodType, ZodUnion, ZodUnknown } from 'zod';
2
2
  import { IsAny, IsJsonType } from './types.js';
3
- export type RefinedSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T>> = T extends ZodUnion ? RefinedUnionSchema<T, TShape> : T extends ZodType<object> ? RefinedTypeSchema<z.infer<T>, TShape> : never;
3
+ type ApplyOptionalNullable<T, U extends ZodType> = IsAny<T, U, undefined extends T ? null extends T ? ZodOptional<ZodNullable<U>> : ZodOptional<U> : null extends T ? ZodNullable<U> : U>;
4
+ type DepthLimit = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
5
+ type InternalZodify<T, TDepth extends number = 0> = TDepth extends 9 ? ZodType : IsAny<T, ZodAny, T extends string ? string extends T ? ZodString : ZodLiteral<T> : T extends number ? ZodNumber : T extends boolean ? ZodBoolean : T extends Date ? ZodDate : unknown extends T ? ZodUnknown : T extends Array<infer U> ? ZodArray<Zodify<U, NextDepth<TDepth>>> : T extends object ? IsJsonType<T, true, ZodObject<{
6
+ [K in keyof T]-?: Zodify<T[K], NextDepth<TDepth>>;
7
+ }, core.$strict>, ZodType<T>> : never>;
8
+ type IsTuple<T, TTrue = true, TFalse = false> = T extends [infer TFirst, infer TSecond, ...infer TRest] ? TTrue : TFalse;
9
+ type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
10
+ type NextDepth<T extends number> = T extends 0 ? 1 : T extends 1 ? 2 : T extends 2 ? 3 : T extends 3 ? 4 : T extends 4 ? 5 : T extends 5 ? 6 : T extends 6 ? 7 : T extends 7 ? 8 : T extends 8 ? 9 : 9;
11
+ type Push<T extends any[], V> = [...T, V];
12
+ 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;
13
+ 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;
4
14
  export type RefinedTypeSchema<T extends object, TShape, TDepth extends number = 0> = TDepth extends keyof DepthLimit ? ZodObject<{
5
15
  [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 ? Zodify<T[K]> : TShape[K] extends object ? Exclude<T[K], null | undefined> extends Array<infer U> ? U extends object ? ApplyOptionalNullable<T[K], z.ZodArray<RefinedTypeSchema<U, TShape[K], NextDepth<TDepth>>>> : Zodify<T[K]> : Exclude<T[K], null | undefined> extends object ? ApplyOptionalNullable<T[K], RefinedTypeSchema<Exclude<T[K], null | undefined>, TShape[K], NextDepth<TDepth>>> : ZodNever : ZodNever;
6
16
  }, core.$strict> : any;
@@ -10,27 +20,20 @@ export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options
10
20
  [K in keyof Rest]: Rest[K] extends ZodType ? z.infer<Rest[K]> extends object ? RefinedTypeSchema<z.infer<Rest[K]>, TShape> : Extract<Rest[K], ZodType> : Extract<Rest[K], ZodType>;
11
21
  }
12
22
  ] : never : never>;
13
- export type RefineObject<T extends object> = {
14
- [K in keyof T]?: RefineType<T[K]>;
23
+ type Refinement<T extends ZodType, TIsSimple extends boolean> = TIsSimple extends true ? boolean : ((schema: T) => ZodType) | boolean | ZodType;
24
+ export type RefineObject<T extends object, TIsSimple extends boolean> = {
25
+ [K in keyof T]?: RefineType<T[K], TIsSimple>;
26
+ };
27
+ 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;
28
+ type RefineType<T, TIsSimple extends boolean> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, 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> ? Exclude<U, null | undefined> extends object ? Refinement<Zodify<T>, TIsSimple> | RefineObject<Extract<U, object>, TIsSimple> : Refinement<Zodify<T>, 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>;
29
+ export type RefineZodTuple<T extends ZodTuple> = {
30
+ [K in keyof UnionToIntersection<T['def']['items'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
15
31
  };
16
- export type RefineSchema<T extends ZodType<object> | ZodUnion> = T extends ZodUnion ? RefineZodUnion<T> : T extends ZodType<object> ? RefineObject<z.infer<T>> : never;
17
32
  export type RefineZodUnion<T extends ZodUnion> = {
18
33
  [K in keyof UnionToIntersection<T['options'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
19
34
  };
20
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>>;
21
36
  export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
22
37
  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>>>;
23
- type ApplyOptionalNullable<T, U extends ZodType> = IsAny<T, U, undefined extends T ? null extends T ? ZodOptional<ZodNullable<U>> : ZodOptional<U> : null extends T ? ZodNullable<U> : U>;
24
- type DepthLimit = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
25
- type InternalZodify<T, TDepth extends number = 0> = TDepth extends 9 ? ZodType : IsAny<T, ZodAny, T extends string ? string extends T ? ZodString : ZodLiteral<T> : T extends number ? ZodNumber : T extends boolean ? ZodBoolean : T extends Date ? ZodDate : unknown extends T ? ZodUnknown : T extends Array<infer U> ? ZodArray<Zodify<U, NextDepth<TDepth>>> : T extends object ? IsJsonType<T, true, ZodObject<{
26
- [K in keyof T]-?: Zodify<T[K], NextDepth<TDepth>>;
27
- }, core.$strict>, ZodType<T>> : never>;
28
- type IsTuple<T, TTrue = true, TFalse = false> = T extends [infer TFirst, infer TSecond, ...infer TRest] ? TTrue : TFalse;
29
- type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
30
- type NextDepth<T extends number> = T extends 0 ? 1 : T extends 1 ? 2 : T extends 2 ? 3 : T extends 3 ? 4 : T extends 4 ? 5 : T extends 5 ? 6 : T extends 6 ? 7 : T extends 7 ? 8 : T extends 8 ? 9 : 9;
31
- type Push<T extends any[], V> = [...T, V];
32
- 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;
33
- type Refinement<T extends ZodType> = ((schema: T) => ZodType) | boolean | ZodType;
34
- type RefineType<T> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, Refinement<Zodify<T>>, Exclude<T, null | undefined> extends string ? Refinement<ApplyOptionalNullable<T, ZodString>> : Exclude<T, null | undefined> extends number ? Refinement<ApplyOptionalNullable<T, ZodNumber>> : Exclude<T, null | undefined> extends boolean ? Refinement<ApplyOptionalNullable<T, ZodBoolean>> : Exclude<T, null | undefined> extends Date ? Refinement<ApplyOptionalNullable<T, ZodDate>> : Exclude<T, null | undefined> extends Array<infer U> ? Exclude<U, null | undefined> extends object ? Refinement<Zodify<T>> | RefineObject<Extract<U, object>> : Refinement<Zodify<T>> : Exclude<T, null | undefined> extends object ? IsJsonType<Exclude<T, null | undefined>, false, Refinement<Zodify<T>> | RefineObject<Extract<T, object>>, Refinement<Zodify<T>>> : unknown extends Exclude<T, null | undefined> ? Refinement<Zodify<T>> : never>;
35
- export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T>>(schema: T, shape: TShape): RefinedSchema<T, TShape>;
38
+ export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T, false>>(schema: T, shape: TShape): RefinedSchema<T, TShape, false>;
36
39
  export {};
@@ -1,4 +1,4 @@
1
- import { z } from 'zod/v4';
1
+ import { z } from 'zod';
2
2
  import { getInnerType, isZodArray, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
3
3
  // Main function to refine the schema
4
4
  export function refineSchema(schema, shape) {
@@ -40,15 +40,15 @@ function refineSchemaField(fieldSchema, fieldShape) {
40
40
  return z.array(refineSchemaField(fieldSchema.element, fieldShape));
41
41
  }
42
42
  else if (isZodOptional(fieldSchema)) {
43
- return refineSchemaField(fieldSchema._def.innerType, fieldShape).optional();
43
+ return refineSchemaField(fieldSchema.def.innerType, fieldShape).optional();
44
44
  }
45
45
  else if (isZodNullable(fieldSchema)) {
46
- return refineSchemaField(fieldSchema._def.innerType, fieldShape).nullable();
46
+ return refineSchemaField(fieldSchema.def.innerType, fieldShape).nullable();
47
47
  }
48
48
  else {
49
49
  const innerType = getInnerType(fieldSchema);
50
50
  // There are wrapper types we can't forward because the underlying object structures are changing.
51
- if (innerType) {
51
+ if (innerType !== fieldSchema) {
52
52
  return refineSchemaField(innerType, fieldShape);
53
53
  }
54
54
  }
@@ -1,6 +1,6 @@
1
1
  import { strict as assert, deepStrictEqual, throws } from 'assert';
2
2
  import { describe, it } from 'node:test';
3
- import { z, ZodError } from 'zod/v4';
3
+ import { z, ZodError } from 'zod';
4
4
  import { refineSchema } from './refine-schema.js';
5
5
  describe('.refineSchema()', () => {
6
6
  it('should retain required fields', () => {
package/dist/select.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { z, ZodType, ZodUnion } from 'zod/v4';
2
- import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodUnion } from './refine-schema.js';
1
+ import { z, ZodTuple, ZodType, ZodUnion } from 'zod';
2
+ import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodTuple, RefineZodUnion } from './refine-schema.js';
3
3
  import { DeepMergeAll, IsEqual } 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
- 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>> ? z.infer<RefinedTypeSchema<z.infer<T>, U>> : z.infer<T>) : z.infer<T>;
6
- 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]>;
7
- export type MultiSelect<T extends ZodType<object> | ZodUnion> = [ZodSelect<T>, ...ZodSelect<T>[]] | undefined | ZodSelect<T>;
8
- export type ZodSelect<T extends ZodType<object> | ZodUnion> = (T extends ZodUnion ? (RefineZodUnion<T> | ZodUnion) : T extends ZodType<object> ? (RefineObject<z.infer<T>> | ZodType<object>) : never);
9
5
  type InferTuple<T extends ZodType<object> | ZodUnion, U extends readonly ZodSelect<T>[]> = {
10
6
  [K in keyof U]: InferType<T, U[K]>;
11
7
  };
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]>;
10
+ export type MultiSelect<T extends ZodType<object> | ZodUnion> = [ZodSelect<T>, ...ZodSelect<T>[]] | undefined | ZodSelect<T>;
11
+ 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);
12
12
  export {};
package/dist/types.d.ts CHANGED
@@ -9,8 +9,8 @@ export type IsJsonType<T, TDeep extends boolean = true, TTrue = true, TFalse = f
9
9
  } extends {
10
10
  [K in keyof T]: true;
11
11
  } ? TTrue : TFalse : TFalse;
12
+ type JsonType = boolean | null | number | string | undefined;
12
13
  export type Mutable<T> = {
13
14
  -readonly [P in keyof T]: T[P];
14
15
  };
15
- type JsonType = boolean | null | number | string | undefined;
16
16
  export {};
@@ -1,8 +1,10 @@
1
- import { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod/v4';
2
- export declare function getInnerType(schema: ZodType): undefined | ZodType;
1
+ import { ZodArray, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod';
2
+ export declare function getInnerType(schema: ZodType, unwrapLazy?: boolean): ZodType;
3
3
  export declare function isZodArray(obj: unknown): obj is ZodArray<ZodType>;
4
+ export declare function isZodLazy(obj: unknown): obj is ZodLazy;
4
5
  export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>;
5
6
  export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
7
+ export declare function isZodObjectLoose(obj: ZodObject): boolean;
6
8
  export declare function isZodOptional(obj: unknown): obj is ZodOptional<ZodType>;
7
9
  export declare function isZodType(obj: unknown): obj is ZodType;
8
10
  export declare function isZodUnion(obj: unknown): obj is ZodUnion<ZodType[]>;
package/dist/utilities.js CHANGED
@@ -1,20 +1,25 @@
1
- import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod/v4';
2
- export function getInnerType(schema) {
3
- if (schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodCatch) {
4
- return schema.def.innerType;
1
+ import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
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);
5
8
  }
6
- else if (schema instanceof ZodLazy) {
7
- return schema.def.getter();
9
+ if (schema instanceof ZodArray) {
10
+ return getInnerType(schema.def.element, unwrapLazy);
8
11
  }
9
- else if (schema instanceof ZodArray) {
10
- return schema.element;
12
+ if (unwrapLazy && schema instanceof ZodLazy) {
13
+ return getInnerType(schema.def.getter());
11
14
  }
12
- // Not a recognized wrapper type
13
- return undefined;
15
+ return schema;
14
16
  }
15
17
  export function isZodArray(obj) {
16
18
  return isZodType(obj) && (obj.def.type === 'array');
17
19
  }
20
+ export function isZodLazy(obj) {
21
+ return isZodType(obj) && (obj.def.type === 'lazy');
22
+ }
18
23
  export function isZodNullable(obj) {
19
24
  return isZodType(obj) && (obj.def.type === 'nullable');
20
25
  }
@@ -27,6 +32,9 @@ export function isZodObject(obj, isLoose) {
27
32
  }
28
33
  return isLoose === isZodUnknown(obj.def.catchall);
29
34
  }
35
+ export function isZodObjectLoose(obj) {
36
+ return !!isZodUnknown(obj.def.catchall);
37
+ }
30
38
  export function isZodOptional(obj) {
31
39
  return isZodType(obj) && (obj.def.type === 'optional');
32
40
  }
@@ -1,22 +1,40 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
- import { z } from 'zod/v4';
3
+ import { z } from 'zod';
4
4
  import { getInnerType, isZodObject, isZodOptional, isZodUnion, isZodUnknown } from './utilities.js';
5
5
  describe('.utilities()', () => {
6
- it('getInnerType identifies inner types', () => {
7
- const optionalString = z.string().optional();
8
- const nullableString = z.string().nullable();
9
- const defaultString = z.string().default('default');
10
- const catchString = z.string().catch('fallback');
11
- const lazyString = z.lazy(() => z.string());
12
- const arrayString = z.array(z.string());
13
- assert.strictEqual(getInnerType(optionalString).def.type, optionalString.def.innerType.def.type);
14
- assert.strictEqual(getInnerType(nullableString).def.type, nullableString.def.innerType.def.type);
15
- assert.strictEqual(getInnerType(defaultString).def.type, defaultString.def.innerType.def.type);
16
- assert.strictEqual(getInnerType(catchString).def.type, catchString.def.innerType.def.type);
17
- assert.strictEqual(getInnerType(lazyString).def.type, lazyString.def.getter().def.type);
18
- assert.strictEqual(getInnerType(arrayString).def.type, arrayString.element.def.type);
19
- assert.strictEqual(getInnerType(z.string()), undefined); // Not a wrapper type
6
+ describe('.getInnerType()', () => {
7
+ it('getInnerType identifies inner types', () => {
8
+ const optionalString = z.string().optional();
9
+ const nullableString = z.string().nullable();
10
+ const defaultString = z.string().default('default');
11
+ const catchString = z.string().catch('fallback');
12
+ const lazyString = z.lazy(() => z.string());
13
+ const arrayString = z.array(z.string());
14
+ assert.strictEqual(getInnerType(optionalString).def.type, optionalString.def.innerType.def.type);
15
+ assert.strictEqual(getInnerType(nullableString).def.type, nullableString.def.innerType.def.type);
16
+ assert.strictEqual(getInnerType(defaultString).def.type, defaultString.def.innerType.def.type);
17
+ assert.strictEqual(getInnerType(catchString).def.type, catchString.def.innerType.def.type);
18
+ assert.strictEqual(getInnerType(lazyString).def.type, lazyString.def.getter().def.type);
19
+ assert.strictEqual(getInnerType(arrayString).def.type, arrayString.element.def.type);
20
+ assert.strictEqual(getInnerType(z.string()).def.type, 'string'); // Not a wrapper type
21
+ });
22
+ it('should return base type from array', () => {
23
+ const schema = z.array(z.number());
24
+ const inner = getInnerType(schema);
25
+ assert.strictEqual(inner.def.type, 'number');
26
+ });
27
+ it('should unwrap nested optional -> nullable -> default -> string', () => {
28
+ const schema = z.string().default('x').nullable().optional();
29
+ const inner = getInnerType(schema);
30
+ assert.strictEqual(inner.def.type, 'string');
31
+ });
32
+ it('should unwrap lazy inside nested wrappers', () => {
33
+ const LazySchema = z.lazy(() => z.string());
34
+ const wrapped = LazySchema.optional().nullable().default('abc');
35
+ const inner = getInnerType(wrapped);
36
+ assert.strictEqual(inner.def.type, 'string');
37
+ });
20
38
  });
21
39
  it('isZodObject detects ZodObject', () => {
22
40
  const objectSchema = z.object({ name: z.string() });
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/secure-data-systems/zod-select.git"
10
+ },
7
11
  "type": "module",
8
12
  "main": "dist/index.js",
9
13
  "types": "dist/index.d.ts",
@@ -21,15 +25,15 @@
21
25
  }
22
26
  },
23
27
  "devDependencies": {
24
- "@secure-data-systems/eslint-config": "^1.0.2",
25
- "@secure-data-systems/tsconfig": "^1.0.0",
26
- "@types/node": "^24.0.0",
28
+ "@secure-data-systems/eslint-config": "^1.0.7",
29
+ "@secure-data-systems/tsconfig": "^1.0.1",
30
+ "@types/node": "^24.0.13",
27
31
  "glob": "^11.0.3",
28
32
  "tsx": "^4.20.3",
29
33
  "typescript": "^5.8.3"
30
34
  },
31
35
  "dependencies": {
32
- "zod": "^3.25.58"
36
+ "zod": "^4.0.5"
33
37
  },
34
38
  "scripts": {
35
39
  "build": "node scripts/build.mjs && tsc --project tsconfig.build.json",