zodselect 1.2.7 → 1.2.9

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.
@@ -12,7 +12,7 @@ type Push<T extends any[], V> = [...T, V];
12
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
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;
14
14
  export type RefinedTypeSchema<T extends object, TShape, TDepth extends number = 0> = TDepth extends keyof DepthLimit ? ZodObject<{
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 ? 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;
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 ? 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;
16
16
  }, core.$strict> : any;
17
17
  export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options'] extends Readonly<[infer A, ...infer Rest]> ? Rest extends ZodType[] ? [
18
18
  ReapplyOptionalNullable<A, A extends ZodType<object> ? RefinedTypeSchema<z.infer<A>, TShape> : Extract<A, ZodType>>,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { getInnerType, isZodArray, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
2
+ import { isZodArray, isZodCatch, isZodDefault, isZodLazy, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
3
3
  // Main function to refine the schema
4
4
  export function refineSchema(schema, shape) {
5
5
  if (isZodObject(schema)) {
@@ -45,12 +45,12 @@ function refineSchemaField(fieldSchema, fieldShape) {
45
45
  else if (isZodNullable(fieldSchema)) {
46
46
  return refineSchemaField(fieldSchema.def.innerType, fieldShape).nullable();
47
47
  }
48
- else {
49
- const innerType = getInnerType(fieldSchema);
50
- // There are wrapper types we can't forward because the underlying object structures are changing.
51
- if (innerType !== fieldSchema) {
52
- return refineSchemaField(innerType, fieldShape);
53
- }
48
+ else if (isZodDefault(fieldSchema) || isZodCatch(fieldSchema)) {
49
+ // A default or catch is a whole value, which a picked part no longer fits, so it gives way to what it wraps.
50
+ return refineSchemaField(fieldSchema.def.innerType, fieldShape);
51
+ }
52
+ else if (isZodLazy(fieldSchema)) {
53
+ return refineSchemaField(fieldSchema.def.getter(), fieldShape);
54
54
  }
55
55
  return fieldSchema;
56
56
  }
@@ -1,6 +1,8 @@
1
- import { ZodArray, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRecord, ZodType, ZodUnion, ZodUnknown } from 'zod';
1
+ import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodObject, ZodOptional, 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
+ export declare function isZodCatch(obj: unknown): obj is ZodCatch<ZodType>;
5
+ export declare function isZodDefault(obj: unknown): obj is ZodDefault<ZodType>;
4
6
  export declare function isZodLazy(obj: unknown): obj is ZodLazy;
5
7
  export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>;
6
8
  export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
package/dist/utilities.js CHANGED
@@ -17,6 +17,12 @@ export function getInnerType(schema, unwrapLazy = true) {
17
17
  export function isZodArray(obj) {
18
18
  return isZodType(obj) && (obj.def.type === 'array');
19
19
  }
20
+ export function isZodCatch(obj) {
21
+ return isZodType(obj) && (obj.def.type === 'catch');
22
+ }
23
+ export function isZodDefault(obj) {
24
+ return isZodType(obj) && (obj.def.type === 'default');
25
+ }
20
26
  export function isZodLazy(obj) {
21
27
  return isZodType(obj) && (obj.def.type === 'lazy');
22
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
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,12 +25,13 @@
25
25
  }
26
26
  },
27
27
  "devDependencies": {
28
- "@temelyos/eslint-config": "^1.0.38",
28
+ "@temelyos/oxlint-config": "^1.1.0",
29
29
  "@temelyos/tsconfig": "^1.0.6",
30
30
  "@types/node": "^26.1.1",
31
+ "oxlint": "^1.88.0",
32
+ "oxlint-tsgolint": "^7.0.2004",
31
33
  "tsx": "^4.23.1",
32
- "typescript": "npm:@typescript/typescript6@^6.0.2",
33
- "typescript-native": "npm:typescript@^7.0.2"
34
+ "typescript": "^7.0.2"
34
35
  },
35
36
  "dependencies": {
36
37
  "zod": "^4.4.3"
@@ -39,8 +40,8 @@
39
40
  "build": "node scripts/build.mjs && tsc --project tsconfig.build.json",
40
41
  "clean": "node scripts/clean.mjs",
41
42
  "deploy": "npm run build && node ./scripts/publish.mjs",
42
- "lint": "eslint . --c eslint.config.mjs",
43
- "lint:fix": "eslint . --c eslint.config.mjs --fix",
43
+ "lint": "oxlint",
44
+ "lint:fix": "temelyos-oxlint-fix",
44
45
  "prune": "node scripts/prune.mjs",
45
46
  "test": "tsx --tsconfig ./tsconfig.build.json --test ./src/**/*.{spec,test}.ts ./test/**/*.{spec,test}.ts"
46
47
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,211 +0,0 @@
1
- import { strict as assert } from 'assert';
2
- import { describe, it } from 'node:test';
3
- import { z } from 'zod';
4
- import { buildSelectObject } from './build-select-object.js';
5
- describe('.buildSelectObject()', () => {
6
- it('should return all keys set to true for a flat object', () => {
7
- const schema = z.object({
8
- age: z.number(),
9
- name: z.string()
10
- });
11
- assert.deepStrictEqual(buildSelectObject(schema), { age: true, name: true });
12
- });
13
- it('should merge keys from all union members', () => {
14
- const schema = z.union([
15
- z.object({ type: z.literal('a'), valueA: z.string() }),
16
- z.object({ type: z.literal('b'), valueB: z.number() })
17
- ]);
18
- assert.deepStrictEqual(buildSelectObject(schema), {
19
- type: true,
20
- valueA: true,
21
- valueB: true
22
- });
23
- });
24
- it('should merge keys from an intersection', () => {
25
- const schema = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
26
- assert.deepStrictEqual(buildSelectObject(schema), { a: true, b: true });
27
- });
28
- it('should unwrap optional/nullable wrappers', () => {
29
- const inner = z.object({ id: z.string(), value: z.number() });
30
- const schema = inner.optional();
31
- assert.deepStrictEqual(buildSelectObject(schema), { id: true, value: true });
32
- });
33
- it('should unwrap lazy schemas', () => {
34
- const schema = z.lazy(() => z.object({ bar: z.number(), foo: z.string() }));
35
- assert.deepStrictEqual(buildSelectObject(schema), { bar: true, foo: true });
36
- });
37
- it('should handle nested unions inside intersection', () => {
38
- const schema = z.intersection(z.union([
39
- z.object({ a: z.string() }),
40
- z.object({ b: z.number() })
41
- ]), z.object({ c: z.boolean() }));
42
- assert.deepStrictEqual(buildSelectObject(schema), {
43
- a: true,
44
- b: true,
45
- c: true
46
- });
47
- });
48
- it('should recursively expand nested objects', () => {
49
- const schema = z.object({
50
- active: z.boolean(),
51
- user: z.object({
52
- age: z.number(),
53
- name: z.string()
54
- })
55
- });
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 expand the items of an array 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
- const select = buildSelectObject(schema);
103
- const isTyped = true;
104
- assert.ok(isTyped);
105
- assert.deepStrictEqual(select, { items: { id: true, value: true }, name: true });
106
- });
107
- it('should expand an optional array of optional objects', () => {
108
- const schema = z.object({
109
- items: z.array(z.object({ id: z.string() }).optional()).optional()
110
- });
111
- const select = buildSelectObject(schema);
112
- const isTyped = true;
113
- assert.ok(isTyped);
114
- assert.deepStrictEqual(select, { items: { id: true } });
115
- });
116
- it('should expand arrays of objects inside arrays of objects', () => {
117
- const schema = z.object({
118
- attachments: z.array(z.object({
119
- history: z.array(z.object({ note: z.string(), uploadedBy: z.object({ _id: z.string(), username: z.string() }) })),
120
- title: z.string()
121
- }))
122
- });
123
- const select = buildSelectObject(schema);
124
- const isTyped = true;
125
- assert.ok(isTyped);
126
- assert.deepStrictEqual(select, {
127
- attachments: { history: { note: true, uploadedBy: { _id: true, username: true } }, title: true }
128
- });
129
- });
130
- it('should type nested objects as nested selects', () => {
131
- const schema = z.object({
132
- client: z.object({ _id: z.string(), name: z.string() }).optional(),
133
- title: z.string()
134
- });
135
- const select = buildSelectObject(schema);
136
- const isTyped = true;
137
- assert.ok(isTyped);
138
- assert.deepStrictEqual(select, { client: { _id: true, name: true }, title: true });
139
- });
140
- it('should not expand records, or arrays of anything but objects', () => {
141
- const schema = z.object({
142
- byKey: z.record(z.string(), z.object({ id: z.string() })),
143
- matrix: z.array(z.array(z.number())),
144
- tags: z.array(z.string())
145
- });
146
- const select = buildSelectObject(schema);
147
- const isTyped = true;
148
- assert.ok(isTyped);
149
- assert.deepStrictEqual(select, { byKey: true, matrix: true, tags: true });
150
- });
151
- it('should select a union field whole when a variant is not an object', () => {
152
- const schema = z.object({
153
- file: z.union([z.object({ _id: z.string(), filename: z.string() }), z.string()]),
154
- name: z.union([z.string(), z.object({ first: z.string(), last: z.string() })])
155
- });
156
- const select = buildSelectObject(schema);
157
- const isTyped = true;
158
- assert.ok(isTyped);
159
- assert.deepStrictEqual(select, { file: 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 be a select the schema accepts', () => {
187
- const schema = z.object({
188
- client: z.object({ _id: z.string(), name: z.string() }),
189
- items: z.array(z.object({ id: z.string() }))
190
- });
191
- const select = buildSelectObject(schema);
192
- assert.deepStrictEqual(select, { client: { _id: true, name: true }, items: { id: true } });
193
- });
194
- it('should handle recursive schemas without infinite loop', () => {
195
- ;
196
- const node = z.lazy(() => z.object({
197
- children: z.array(node).optional(),
198
- name: z.string(),
199
- parent: node.optional()
200
- }));
201
- const schema = z.object({
202
- name: z.string(),
203
- root: node
204
- });
205
- // Should not throw or hang — recursive objects hit depth limit
206
- const result = buildSelectObject(schema);
207
- assert.strictEqual(result.name, true);
208
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
209
- assert.ok(typeof result.root === 'object' && result.root !== null);
210
- });
211
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,242 +0,0 @@
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('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
- });
75
- it('should support intersections of objects', () => {
76
- const schema = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
77
- const select = buildSelectSchema(schema);
78
- assert.strictEqual(getInnerType(select.shape.a).def.type, 'boolean');
79
- assert.strictEqual(getInnerType(select.shape.b).def.type, 'boolean');
80
- });
81
- it('should support tuples of objects', () => {
82
- const schema = z.tuple([
83
- z.object({ foo: z.string() }),
84
- z.object({ bar: z.number() })
85
- ]);
86
- const select = buildSelectSchema(schema);
87
- assert.strictEqual(getInnerType(select.shape.foo).def.type, 'boolean');
88
- assert.strictEqual(getInnerType(select.shape.bar).def.type, 'boolean');
89
- });
90
- it('should return a strict schema if any member is strict', () => {
91
- const schema = z.union([
92
- z.object({ strictA: z.string() }).strict(),
93
- z.object({ strictB: z.number() }).strict()
94
- ]);
95
- const select = buildSelectSchema(schema);
96
- assert.strictEqual(isZodObjectLoose(select), false);
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
- });
108
- it('should allow optional keys in the select schema', () => {
109
- const schema = z.object({
110
- optionalField: z.string().optional()
111
- });
112
- const select = buildSelectSchema(schema);
113
- assert.strictEqual(getInnerType(select.shape.optionalField).def.type, 'boolean');
114
- });
115
- it('should handle direct recursion', () => {
116
- const roleSchema = z.lazy(() => {
117
- return (z.object({
118
- name: z.string(),
119
- roles: z.array(roleSchema)
120
- }));
121
- });
122
- const select = buildSelectSchema(roleSchema);
123
- assert.deepStrictEqual(Object.keys(select.shape), ['name', 'roles']);
124
- const nameSelect = select.shape.name;
125
- assert.strictEqual(nameSelect.def.type, 'optional');
126
- assert.strictEqual(nameSelect.def.innerType.def.type, 'boolean');
127
- const rolesSelect = select.shape.roles;
128
- assert.strictEqual(rolesSelect.def.type, 'lazy');
129
- const innerUnion = rolesSelect.def.getter().def.innerType;
130
- assert.strictEqual(innerUnion.def.type, 'union');
131
- const booleanItem = innerUnion.def.options[0];
132
- assert.strictEqual(booleanItem.def.type, 'boolean');
133
- const roleItem = innerUnion.def.options[1];
134
- assert.strictEqual(roleItem.def.type, 'object');
135
- });
136
- it('should support mutual recursion (cross-recursive)', () => {
137
- const schemaA = z.lazy(() => z.object({
138
- b: schemaB
139
- }));
140
- const schemaB = z.lazy(() => z.object({
141
- a: schemaA
142
- }));
143
- const select = buildSelectSchema(schemaA);
144
- assert.strictEqual(select.shape.b.def.type, 'lazy');
145
- const bUnion = select.shape.b.def.getter();
146
- assert.strictEqual(bUnion.def.innerType.def.type, 'union');
147
- const bObj = bUnion.def.innerType.def.options[1];
148
- assert.strictEqual(bObj.shape.a.def.type, 'lazy');
149
- });
150
- it('should share the same instance in recursion (no infinite loop)', () => {
151
- const role = z.lazy(() => z.object({
152
- name: z.string(),
153
- roles: z.array(role)
154
- }));
155
- const select = buildSelectSchema(role);
156
- const rolesSelect = select.shape.roles;
157
- const unionSelect = rolesSelect.def.getter().def.innerType;
158
- const nestedRoleSelect = unionSelect.def.options[1].shape.roles;
159
- // Should be the same object (by reference)
160
- assert.strictEqual(rolesSelect, nestedRoleSelect);
161
- });
162
- it('should support record types', () => {
163
- const schema = z.object({
164
- tags: z.record(z.string(), z.string())
165
- });
166
- const select = buildSelectSchema(schema);
167
- const tagsSelect = select.shape.tags;
168
- assert.strictEqual(tagsSelect.type, 'optional');
169
- const union = tagsSelect.def.innerType;
170
- assert.strictEqual(union.type, 'union');
171
- assert.strictEqual(union.def.options[0].type, 'boolean');
172
- assert.strictEqual(union.def.options[1].type, 'record');
173
- assert.strictEqual(union.def.options[1].def.keyType.type, 'string');
174
- assert.strictEqual(union.def.options[1].def.valueType.type, 'optional');
175
- assert.strictEqual(union.def.options[1].def.valueType.def.innerType.type, 'boolean');
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
- });
190
- it('should support unknown types', () => {
191
- const schema = z.object({
192
- tags: z.unknown()
193
- });
194
- const select = buildSelectSchema(schema);
195
- const result = select.safeParse({
196
- tags: {
197
- any: {
198
- damn: true
199
- },
200
- thing: true
201
- }
202
- });
203
- assert.strictEqual(result.success, true);
204
- assert.deepStrictEqual(result.data, {
205
- tags: {
206
- any: {
207
- damn: true
208
- },
209
- thing: true
210
- }
211
- });
212
- });
213
- it('should allow nested object sub-selection in union members', () => {
214
- const account = z.object({
215
- _id: z.string(),
216
- name: z.string()
217
- });
218
- const individual = z.object({
219
- account: account.optional(),
220
- name: z.object({ first: z.string(), last: z.string() }),
221
- type: z.literal('individual')
222
- });
223
- const organization = z.object({
224
- account: account.optional(),
225
- name: z.string(),
226
- type: z.literal('organization')
227
- });
228
- const contact = z.union([individual, organization]);
229
- const selectSchema = buildSelectSchema(contact);
230
- // Runtime: nested sub-selection on a shared nested object field should parse
231
- const result = selectSchema.parse({
232
- account: { _id: true, name: true },
233
- type: true
234
- });
235
- assert.deepStrictEqual(result, {
236
- account: { _id: true, name: true },
237
- type: true
238
- });
239
- const nested = { account: { _id: true, name: true }, type: true };
240
- void nested;
241
- });
242
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,401 +0,0 @@
1
- import { strict as assert, deepStrictEqual, throws } from 'assert';
2
- import { describe, it } from 'node:test';
3
- import { z, ZodError } from 'zod';
4
- import { refineSchema } from './refine-schema.js';
5
- describe('.refineSchema()', () => {
6
- it('should retain required fields', () => {
7
- const userSchema = z.object({
8
- age: z.number().optional(),
9
- firstName: z.string(),
10
- lastName: z.string()
11
- });
12
- const refinedSchema = refineSchema(userSchema, {
13
- firstName: true,
14
- lastName: true
15
- });
16
- const parsedData = refinedSchema.parse({
17
- firstName: 'John',
18
- lastName: 'Doe'
19
- });
20
- assert.equal(parsedData.firstName, 'John');
21
- assert.equal(parsedData.lastName, 'Doe');
22
- });
23
- it('should handle optional fields', () => {
24
- const userSchema = z.object({
25
- age: z.number().optional(),
26
- firstName: z.string(),
27
- lastName: z.string()
28
- });
29
- const refinedSchema = refineSchema(userSchema, {
30
- age: true
31
- });
32
- const parsedData = refinedSchema.parse({ age: 25 });
33
- assert.equal(parsedData.age, 25);
34
- const parsedWithoutAge = refinedSchema.parse({});
35
- assert.equal(parsedWithoutAge.age, undefined);
36
- });
37
- it('should handle nullable fields', () => {
38
- const roleSchema = z.object({
39
- name: z.string()
40
- });
41
- const userSchema = z.object({
42
- firstName: z.string(),
43
- role: roleSchema.nullable()
44
- });
45
- const refinedSchema = refineSchema(userSchema, {
46
- role: true
47
- });
48
- const parsedWithNullRole = refinedSchema.parse({
49
- firstName: 'Alice',
50
- role: null
51
- });
52
- assert.equal(parsedWithNullRole.role, null);
53
- });
54
- it('should handle both optional and nullable fields', () => {
55
- const roleSchema = z.object({
56
- name: z.string()
57
- });
58
- const userSchema = z.object({
59
- firstName: z.string(),
60
- role: roleSchema.nullable().optional()
61
- });
62
- const refinedSchema = refineSchema(userSchema, {
63
- role: {
64
- name: true
65
- }
66
- });
67
- const parsedWithRole = refinedSchema.parse({
68
- firstName: 'Alice',
69
- role: { name: 'Admin' }
70
- });
71
- assert.deepStrictEqual(parsedWithRole.role, { name: 'Admin' });
72
- const parsedWithNullRole = refinedSchema.parse({
73
- firstName: 'Alice',
74
- role: null
75
- });
76
- assert.strictEqual(parsedWithNullRole.role, null);
77
- const parsedWithoutRole = refinedSchema.parse({ firstName: 'Alice' });
78
- assert.strictEqual(parsedWithoutRole.role, undefined);
79
- });
80
- it('should apply custom schema transformations', () => {
81
- const roleSchema = z.object({
82
- name: z.string()
83
- });
84
- const userSchema = z.object({
85
- firstName: z.string(),
86
- role: roleSchema
87
- });
88
- const refinedSchema = refineSchema(userSchema, {
89
- role: schema => schema.extend({ permissions: z.array(z.string()) })
90
- });
91
- const parsedData = refinedSchema.parse({
92
- firstName: 'Bob',
93
- role: { name: 'User', permissions: ['read', 'write'] }
94
- });
95
- assert.deepEqual(parsedData.role, {
96
- name: 'User',
97
- permissions: ['read', 'write']
98
- });
99
- });
100
- it('should handle nested arrays', () => {
101
- const roleSchema = z.object({
102
- level: z.number(),
103
- title: z.string()
104
- });
105
- const userSchema = z.object({
106
- age: z.number().optional(),
107
- firstName: z.string(),
108
- lastName: z.string(),
109
- nestedArray: z.array(z.object({ name: z.string(), value: z.number() })).optional().nullable(),
110
- role: roleSchema.nullable().optional(),
111
- tags: z.array(z.string()).optional()
112
- });
113
- const refinedSchema = refineSchema(userSchema, {
114
- nestedArray: {
115
- name: true
116
- }
117
- });
118
- const parsedData = refinedSchema.parse({
119
- firstName: 'Alice',
120
- lastName: 'Smith',
121
- nestedArray: [{ name: 'Feature1', value: 10 }, { name: 'Feature2', value: 20 }]
122
- });
123
- assert.deepStrictEqual(parsedData.nestedArray, [{ name: 'Feature1' }, { name: 'Feature2' }]);
124
- // Test optional array
125
- const parsedWithoutNestedArray = refinedSchema.parse({
126
- firstName: 'Alice',
127
- lastName: 'Smith'
128
- });
129
- assert.equal(parsedWithoutNestedArray.nestedArray, undefined);
130
- });
131
- it('should handle custom fields', () => {
132
- const userSchema = z.object({
133
- address: z.custom().optional(),
134
- firstName: z.string(),
135
- lastName: z.string()
136
- });
137
- const refinedSchema = refineSchema(userSchema, {
138
- address: true
139
- });
140
- const parsedData = refinedSchema.parse({
141
- address: {
142
- city: 'Austin',
143
- state: 'TX',
144
- street: '123 Main Street'
145
- },
146
- firstName: 'Alice',
147
- lastName: 'Smith'
148
- });
149
- assert.deepStrictEqual(parsedData, {
150
- address: {
151
- city: 'Austin',
152
- state: 'TX',
153
- street: '123 Main Street'
154
- }
155
- });
156
- });
157
- it('should support unions of objects', () => {
158
- const person = z.object({
159
- firstName: z.string(),
160
- lastName: z.string()
161
- });
162
- const address = z.object({
163
- address: z.string(),
164
- city: z.string()
165
- });
166
- const schema = refineSchema(z.union([person, address]), {
167
- address: true,
168
- firstName: true
169
- });
170
- let parseResult = schema.parse({
171
- address: '123 Main Street',
172
- firstName: 'Jerry',
173
- lastName: 'Springer'
174
- });
175
- deepStrictEqual(parseResult, { firstName: 'Jerry' });
176
- parseResult = schema.parse({
177
- address: '123 Main Street',
178
- city: 'Austin'
179
- });
180
- deepStrictEqual(parseResult, { address: '123 Main Street' });
181
- });
182
- it('should error if none of the types are matched', () => {
183
- const person = z.object({
184
- firstName: z.string(),
185
- lastName: z.string()
186
- });
187
- const address = z.object({
188
- address: z.string(),
189
- city: z.string()
190
- });
191
- const schema = refineSchema(z.union([person, address]), {
192
- address: true,
193
- firstName: true
194
- });
195
- throws(() => {
196
- schema.parse({
197
- lastName: 'Springer'
198
- });
199
- }, (err) => {
200
- if (!(err instanceof ZodError) || err.issues[0].code !== 'invalid_union') {
201
- return false;
202
- }
203
- return true;
204
- });
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
- });
219
- it('should support unions of objects and simple types', () => {
220
- const person = z.object({
221
- firstName: z.string(),
222
- lastName: z.string()
223
- });
224
- const schema = refineSchema(z.union([person, z.string()]), {
225
- address: true,
226
- firstName: true
227
- });
228
- let parseResult = schema.parse({
229
- firstName: 'Jerry',
230
- lastName: 'Springer'
231
- });
232
- deepStrictEqual(parseResult, { firstName: 'Jerry' });
233
- parseResult = schema.parse('123 Main Street');
234
- deepStrictEqual(parseResult, '123 Main Street');
235
- });
236
- it('should accept selecting a subfield of a union member with an optional array-of-objects field', () => {
237
- // Regression test for a bug in `UnionFieldType` (refine-schema.ts).
238
- //
239
- // `UnionFieldType` chose a union member field's refine shape with
240
- // `z.infer<T> extends Array<infer U>` but, unlike `RefineType`, it did not
241
- // strip `| undefined` first. For an OPTIONAL array-of-objects member
242
- // (`field: Obj.array().optional()`), `z.infer<T>` is `Obj[] | undefined`,
243
- // which is NOT assignable to `Array<infer U>`, so the field's refine shape
244
- // collapsed to `RefineObject<Obj[]>` (the array's own keys) instead of
245
- // `RefineObject<Obj>`. The runtime was unaffected, so this only broke the
246
- // type-check (`tsc`), not `tsx --test`.
247
- const accessLevel = z.object({
248
- _id: z.string(),
249
- name: z.string()
250
- });
251
- const organizationInvitation = z.object({
252
- accessLevels: accessLevel.array().optional(),
253
- organizationId: z.string(),
254
- type: z.literal('organization')
255
- });
256
- const platformInvitation = z.object({
257
- accessLevels: accessLevel.array().optional(),
258
- type: z.literal('platform')
259
- });
260
- const invitation = z.discriminatedUnion('type', [organizationInvitation, platformInvitation]);
261
- // Type-level assertion: drilling into a subfield of the optional array
262
- // member is a valid select and MUST be assignable to the refine shape.
263
- // With the bug present, this `satisfies` fails to compile.
264
- const select = {
265
- accessLevels: { name: true },
266
- type: true
267
- };
268
- const refined = refineSchema(invitation, select);
269
- const parsed = refined.parse({
270
- accessLevels: [{ name: 'Admin' }],
271
- type: 'organization'
272
- });
273
- assert.equal(parsed.type, 'organization');
274
- assert.deepEqual(parsed.accessLevels, [{ name: 'Admin' }]);
275
- });
276
- it('allows selecting a nested subfield of a declared looseObject field', () => {
277
- // Regression test for `RefineObject` collapsing a `z.looseObject`'s declared fields.
278
- //
279
- // A `z.looseObject` carries a catchall index signature (`[k: string]: unknown`).
280
- // `RefineObject` maps over `keyof T`, and when `T` has an index signature `keyof T`
281
- // collapses to `string | number`, so every declared field's value type came from the
282
- // catchall (`unknown` -> scalar `boolean`). Declared fields could be marked `true` but
283
- // could NOT be drilled into. The runtime was unaffected, so this only broke the
284
- // type-check (`tsc`), not `tsx --test`.
285
- const personName = z.object({
286
- first: z.string().optional(),
287
- last: z.string().optional()
288
- });
289
- const user = z.looseObject({
290
- _id: z.string(),
291
- name: personName.optional(),
292
- username: z.string()
293
- });
294
- const root = z.object({
295
- invitedBy: user
296
- });
297
- // Type-level assertion: drilling into `invitedBy.name.first` is valid and MUST be
298
- // assignable to the refine shape. With the bug present this `satisfies` fails to
299
- // compile because `invitedBy`'s declared fields collapse to an index signature with
300
- // `boolean` values.
301
- const select = {
302
- invitedBy: { name: { first: true } }
303
- };
304
- const refined = refineSchema(root, select);
305
- const parsed = refined.parse({
306
- invitedBy: { name: { first: 'Alice' } }
307
- });
308
- assert.equal(parsed.invitedBy.name?.first, 'Alice');
309
- });
310
- it('keeps every option of a small enum in the refined type (control)', () => {
311
- const stage = z.enum(['one', 'two', 'three']);
312
- const project = z.object({ name: z.string(), stage });
313
- const refined = refineSchema(project, { stage: true });
314
- // Type-level assertion: every option must remain assignable to the refined type.
315
- const selected = [
316
- { stage: 'one' },
317
- { stage: 'two' },
318
- { stage: 'three' }
319
- ];
320
- deepStrictEqual(selected.map(s => refined.parse(s).stage), stage.options);
321
- });
322
- it('keeps every option of an enum wider than nine options in the refined type', () => {
323
- // Regression test for `TuplifyUnion` (refine-schema.ts) truncating wide enums.
324
- //
325
- // `TuplifyUnion` spends one recursion level per union member and stops at its
326
- // depth cap, so an enum field refined with `true` silently dropped every option
327
- // past nine from the refined TYPE — and which options vanished varied with
328
- // TypeScript's internal union ordering. Fixed by the ZodLiteral shortcut in
329
- // `Zodify`. The runtime was unaffected, so this only broke the type-check
330
- // (`tsc`), not `tsx --test`.
331
- const stage = z.enum([
332
- 'draft', 'interviewing', 'interview-complete',
333
- 'previewing', 'preview-review',
334
- 'plan-review', 'plan-approved', 'local',
335
- 'spec-review', 'spec-approved',
336
- 'implementing', 'module-ready'
337
- ]);
338
- const project = z.object({ name: z.string(), stage });
339
- const refined = refineSchema(project, { stage: true });
340
- // Type-level assertion: each of the twelve options must remain assignable to
341
- // the refined type. With the bug present, three of these assignments fail TS2322.
342
- const selected = [
343
- { stage: 'draft' },
344
- { stage: 'interviewing' },
345
- { stage: 'interview-complete' },
346
- { stage: 'previewing' },
347
- { stage: 'preview-review' },
348
- { stage: 'plan-review' },
349
- { stage: 'plan-approved' },
350
- { stage: 'local' },
351
- { stage: 'spec-review' },
352
- { stage: 'spec-approved' },
353
- { stage: 'implementing' },
354
- { stage: 'module-ready' }
355
- ];
356
- deepStrictEqual(selected.map(s => refined.parse(s).stage), stage.options);
357
- });
358
- });
359
- describe('.refineSchema() literal and unknown field types', () => {
360
- // Regression tests for `Zodify` (refine-schema.ts) when a schema's fields are listed
361
- // (e.g. via `buildSelectObject`) instead of selected whole. Before the fix a numeric
362
- // literal union widened to `number` and a required `z.unknown()` field became
363
- // optional, so the selected type no longer fit the source type. Runtime was
364
- // unaffected; these assignments fail the type-check (`tsc`).
365
- const question = z.object({
366
- answer: z.object({ value: z.unknown() }),
367
- flag: z.literal(true),
368
- note: z.unknown().optional(),
369
- scale: z.union([z.literal(5), z.literal(10)]),
370
- steps: z.literal([3, 7]),
371
- value: z.unknown()
372
- });
373
- const survey = z.object({
374
- name: z.string(),
375
- pages: z.array(z.object({ questions: z.array(question) }))
376
- });
377
- it('keeps a numeric literal union instead of widening it to number', () => {
378
- const refined = refineSchema(survey, { pages: { questions: { scale: true, steps: true } } });
379
- // With the bug these resolve to `number`, so the source type can't take them.
380
- const scale = {}.scale;
381
- const steps = {}.steps;
382
- void scale;
383
- void steps;
384
- const parsed = refined.parse({ pages: [{ questions: [{ scale: 10, steps: 3 }] }] });
385
- assert.equal(parsed.pages[0]?.questions[0]?.scale, 10);
386
- throws(() => refined.parse({ pages: [{ questions: [{ scale: 7, steps: 3 }] }] }), ZodError);
387
- });
388
- it('keeps a boolean literal instead of widening it to boolean', () => {
389
- const refined = refineSchema(survey, { pages: { questions: { flag: true } } });
390
- const isFlag = {}.flag;
391
- void isFlag;
392
- throws(() => refined.parse({ pages: [{ questions: [{ flag: false }] }] }), ZodError);
393
- });
394
- it('keeps a required unknown field required', () => {
395
- const refined = refineSchema(survey, { pages: { questions: { answer: true, note: true, value: true } } });
396
- // With the bug `value` became an optional key; `note` must stay optional.
397
- const selected = { answer: { value: 1 }, value: 'x' };
398
- const questions = [selected];
399
- deepStrictEqual(refined.parse({ pages: [{ questions }] }).pages[0]?.questions, questions);
400
- });
401
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,108 +0,0 @@
1
- import { strict as assert } from 'assert';
2
- import { describe, it } from 'node:test';
3
- import { z } from 'zod';
4
- import { buildSelectObject } from './build-select-object.js';
5
- import { refineSchema } from './refine-schema.js';
6
- const internalService = z.object({
7
- name: z.string(),
8
- type: z.literal('internal')
9
- }).strict();
10
- const externalService = z.object({
11
- iconUrl: z.string().optional(),
12
- name: z.string(),
13
- type: z.literal('external')
14
- }).strict();
15
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
- const serviceType = z.union([internalService, externalService]);
17
- describe('InferMergedType', () => {
18
- it('should resolve to z.infer<T> when used in a generic function with TSelect = undefined', () => {
19
- function getServices(options) {
20
- void options;
21
- return {};
22
- }
23
- // When called without select, TSelect defaults to undefined
24
- // InferMergedType<typeof Service, undefined> should resolve to z.infer<typeof Service>
25
- const services = getServices({});
26
- const service = Object.values(services)[0];
27
- // This is a compile-time test: the assertion below must type-check (all
28
- // union members have `name: string`). The impl returns `{}`, so guard the
29
- // dereference — the block never runs but TS still checks it.
30
- if (service) {
31
- const name = service.name;
32
- void name;
33
- }
34
- });
35
- it('should resolve to projected type when used in a generic function with a select', () => {
36
- function getServices(options) {
37
- void options;
38
- return {};
39
- }
40
- // When called with a select, the return type should be the projected type
41
- const services = getServices({ select: { name: true } });
42
- const service = Object.values(services)[0];
43
- // This is a compile-time test: the assertion below must type-check (name
44
- // was selected). The impl returns `{}`, so guard the dereference — the
45
- // block never runs but TS still checks it.
46
- if (service) {
47
- const name = service.name;
48
- void name;
49
- }
50
- });
51
- });
52
- describe('a field whose value is a union of objects', () => {
53
- const channel = z.object({ _id: z.string(), key: z.string(), name: z.string() });
54
- const effect = z.discriminatedUnion('kind', [
55
- z.object({ channel, kind: z.literal('route') }),
56
- z.object({ field: z.string(), kind: z.literal('require') })
57
- ]);
58
- const policy = z.object({
59
- _id: z.string(),
60
- effect,
61
- effects: z.array(effect).optional(),
62
- name: z.union([z.string(), z.object({ en: z.string() })])
63
- });
64
- it('accepts a nested select into the variants, and refines each variant by it', () => {
65
- const select = { effect: { channel: { _id: true, name: true }, field: true, kind: true } };
66
- const parsed = refineSchema(policy, select).parse({ effect: { channel: { _id: 'c1', key: 'rpo', name: 'RPO' }, kind: 'route' } });
67
- assert.deepEqual(parsed, { effect: { channel: { _id: 'c1', name: 'RPO' }, kind: 'route' } });
68
- });
69
- it('types the result variant by variant, so checking the kind narrows to what that variant selected', () => {
70
- const select = { effect: { channel: { _id: true, name: true }, field: true, kind: true } };
71
- const isTyped = true;
72
- assert.ok(isTyped);
73
- assert.deepEqual(refineSchema(policy, select).parse({ effect: { field: 'rationale', kind: 'require' } }), { effect: { field: 'rationale', kind: 'require' } });
74
- });
75
- it('does the same for an array of such a union', () => {
76
- const select = { effects: { channel: { name: true }, kind: true } };
77
- const isTyped = true;
78
- const effects = [{ channel: { _id: 'c1', key: 'rpo', name: 'RPO' }, kind: 'route' }, { field: 'rationale', kind: 'require' }];
79
- assert.ok(isTyped);
80
- assert.deepEqual(refineSchema(policy, select).parse({ effects }), { effects: [{ channel: { name: 'RPO' }, kind: 'route' }, { kind: 'require' }] });
81
- });
82
- it('accepts the select buildSelectObject builds for it', () => {
83
- const select = buildSelectObject(policy);
84
- assert.deepEqual(select, {
85
- _id: true,
86
- effect: { channel: { _id: true, key: true, name: true }, field: true, kind: true },
87
- effects: { channel: { _id: true, key: true, name: true }, field: true, kind: true },
88
- name: true
89
- });
90
- });
91
- it('accepts a nested select into the object variants of a union with a plain one, as a join read as its record is', () => {
92
- const select = { name: { en: true } };
93
- const isTyped = true;
94
- assert.ok(isTyped);
95
- assert.deepEqual(refineSchema(policy, select).parse({ name: { en: 'Software' } }), { name: { en: 'Software' } });
96
- });
97
- it('accepts the select a screen builds by declaring a joined id as its record', () => {
98
- const file = z.object({ _id: z.string(), filename: z.string() });
99
- const attachment = z.object({ file: z.union([file, z.string()]), title: z.string().optional() });
100
- const stored = z.object({ attachments: z.array(attachment).optional() });
101
- const read = z.object({ attachments: z.array(attachment.extend({ file })).optional() });
102
- const select = buildSelectObject(read);
103
- const isTyped = true;
104
- assert.ok(isTyped);
105
- assert.deepEqual(select, { attachments: { file: { _id: true, filename: true }, title: true } });
106
- assert.deepEqual(refineSchema(stored, select).parse({ attachments: [{ file: { _id: 'f1', filename: 'a.pdf' } }] }), { attachments: [{ file: { _id: 'f1', filename: 'a.pdf' } }] });
107
- });
108
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,94 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { z } from 'zod';
4
- import { getInnerType, isZodObject, isZodOptional, isZodRecord, isZodUnion, isZodUnknown } from './utilities.js';
5
- describe('.utilities()', () => {
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
- });
38
- });
39
- it('isZodObject detects ZodObject', () => {
40
- const objectSchema = z.object({ name: z.string() });
41
- const stringSchema = z.string();
42
- assert.ok(isZodObject(objectSchema));
43
- assert.ok(!isZodObject(stringSchema));
44
- });
45
- it('isZodObject should detect loose objects when isLoose is passed', () => {
46
- assert.ok(isZodObject(z.looseObject({}), true));
47
- assert.ok(!isZodObject(z.looseObject({}), false));
48
- assert.ok(!isZodObject(z.strictObject({}), true));
49
- assert.ok(isZodObject(z.strictObject({}), false));
50
- });
51
- it('isZodOptional detects ZodOptional', () => {
52
- const optionalSchema = z.string().optional();
53
- const stringSchema = z.string();
54
- assert.ok(isZodOptional(optionalSchema));
55
- assert.ok(!isZodOptional(stringSchema));
56
- });
57
- it('isZodRecord detects complex record schemas with optional fields', () => {
58
- // Create a sample RecordAttachmentSchema
59
- const recordAttachmentSchema = z.object({
60
- name: z.string(),
61
- size: z.number(),
62
- type: z.string()
63
- });
64
- // Create a record schema with optional field
65
- const attachmentsSchema = z.record(z.string(), recordAttachmentSchema).optional();
66
- const nonRecordSchema = z.object({ field: z.string() });
67
- // Test record detection
68
- assert.ok(isZodRecord(z.record(z.string(), recordAttachmentSchema)), 'Should detect record schema');
69
- assert.ok(isZodRecord(attachmentsSchema.unwrap()), 'Should detect optional record schema when unwrapped');
70
- assert.ok(!isZodRecord(nonRecordSchema), 'Should not detect object schema as record');
71
- // Test with actual record data
72
- const validRecord = z.record(z.string(), recordAttachmentSchema).parse({
73
- 'file1': { name: 'test.pdf', size: 1024, type: 'application/pdf' },
74
- 'file2': { name: 'image.jpg', size: 2048, type: 'image/jpeg' }
75
- });
76
- assert.ok(validRecord, 'Should successfully parse valid record data');
77
- });
78
- it('isZodRecord detects ZodRecord', () => {
79
- const recordSchema = z.record(z.string(), z.string());
80
- const stringSchema = z.string();
81
- assert.ok(isZodRecord(recordSchema));
82
- assert.ok(!isZodRecord(stringSchema));
83
- });
84
- it('isZodUnion detects ZodUnion', () => {
85
- const unionSchema = z.union([z.string(), z.number()]);
86
- const stringSchema = z.string();
87
- assert.ok(isZodUnion(unionSchema));
88
- assert.ok(!isZodUnion(stringSchema));
89
- });
90
- it('isZodUnknown detects ZodUnknown', () => {
91
- assert.ok(isZodUnknown(z.unknown()));
92
- assert.ok(!isZodUnknown(z.string()));
93
- });
94
- });