zodselect 1.0.8 → 1.0.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.
- package/dist/build-select-schema.spec.js +11 -11
- package/dist/refine-schema.spec.js +47 -47
- package/dist/select.d.ts +4 -1
- package/dist/utilities.spec.js +6 -6
- package/package.json +7 -7
|
@@ -91,13 +91,13 @@ describe('.buildSelectSchema()', () => {
|
|
|
91
91
|
assert.strictEqual(getInnerType(select.shape.optionalField).def.type, 'boolean');
|
|
92
92
|
});
|
|
93
93
|
it('should handle direct recursion', () => {
|
|
94
|
-
const
|
|
94
|
+
const roleSchema = z.lazy(() => {
|
|
95
95
|
return (z.object({
|
|
96
96
|
name: z.string(),
|
|
97
|
-
roles: z.array(
|
|
97
|
+
roles: z.array(roleSchema)
|
|
98
98
|
}));
|
|
99
99
|
});
|
|
100
|
-
const select = buildSelectSchema(
|
|
100
|
+
const select = buildSelectSchema(roleSchema);
|
|
101
101
|
assert.deepStrictEqual(Object.keys(select.shape), ['name', 'roles']);
|
|
102
102
|
const nameSelect = select.shape.name;
|
|
103
103
|
assert.strictEqual(nameSelect.def.type, 'optional');
|
|
@@ -112,13 +112,13 @@ describe('.buildSelectSchema()', () => {
|
|
|
112
112
|
assert.strictEqual(roleItem.def.type, 'object');
|
|
113
113
|
});
|
|
114
114
|
it('should support mutual recursion (cross-recursive)', () => {
|
|
115
|
-
const
|
|
116
|
-
b:
|
|
115
|
+
const schemaA = z.lazy(() => z.object({
|
|
116
|
+
b: schemaB
|
|
117
117
|
}));
|
|
118
|
-
const
|
|
119
|
-
a:
|
|
118
|
+
const schemaB = z.lazy(() => z.object({
|
|
119
|
+
a: schemaA
|
|
120
120
|
}));
|
|
121
|
-
const select = buildSelectSchema(
|
|
121
|
+
const select = buildSelectSchema(schemaA);
|
|
122
122
|
assert.strictEqual(select.shape.b.def.type, 'lazy');
|
|
123
123
|
const bUnion = select.shape.b.def.getter();
|
|
124
124
|
assert.strictEqual(bUnion.def.innerType.def.type, 'union');
|
|
@@ -126,11 +126,11 @@ describe('.buildSelectSchema()', () => {
|
|
|
126
126
|
assert.strictEqual(bObj.shape.a.def.type, 'lazy');
|
|
127
127
|
});
|
|
128
128
|
it('should share the same instance in recursion (no infinite loop)', () => {
|
|
129
|
-
const
|
|
129
|
+
const role = z.lazy(() => z.object({
|
|
130
130
|
name: z.string(),
|
|
131
|
-
roles: z.array(
|
|
131
|
+
roles: z.array(role)
|
|
132
132
|
}));
|
|
133
|
-
const select = buildSelectSchema(
|
|
133
|
+
const select = buildSelectSchema(role);
|
|
134
134
|
const rolesSelect = select.shape.roles;
|
|
135
135
|
const unionSelect = rolesSelect.def.getter().def.innerType;
|
|
136
136
|
const nestedRoleSelect = unionSelect.def.options[1].shape.roles;
|
|
@@ -4,16 +4,16 @@ import { z, ZodError } from 'zod';
|
|
|
4
4
|
import { refineSchema } from './refine-schema.js';
|
|
5
5
|
describe('.refineSchema()', () => {
|
|
6
6
|
it('should retain required fields', () => {
|
|
7
|
-
const
|
|
7
|
+
const userSchema = z.object({
|
|
8
8
|
age: z.number().optional(),
|
|
9
9
|
firstName: z.string(),
|
|
10
10
|
lastName: z.string()
|
|
11
11
|
});
|
|
12
|
-
const
|
|
12
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
13
13
|
firstName: true,
|
|
14
14
|
lastName: true
|
|
15
15
|
});
|
|
16
|
-
const parsedData =
|
|
16
|
+
const parsedData = refinedSchema.parse({
|
|
17
17
|
firstName: 'John',
|
|
18
18
|
lastName: 'Doe'
|
|
19
19
|
});
|
|
@@ -21,74 +21,74 @@ describe('.refineSchema()', () => {
|
|
|
21
21
|
assert.equal(parsedData.lastName, 'Doe');
|
|
22
22
|
});
|
|
23
23
|
it('should handle optional fields', () => {
|
|
24
|
-
const
|
|
24
|
+
const userSchema = z.object({
|
|
25
25
|
age: z.number().optional(),
|
|
26
26
|
firstName: z.string(),
|
|
27
27
|
lastName: z.string()
|
|
28
28
|
});
|
|
29
|
-
const
|
|
29
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
30
30
|
age: true
|
|
31
31
|
});
|
|
32
|
-
const parsedData =
|
|
32
|
+
const parsedData = refinedSchema.parse({ age: 25 });
|
|
33
33
|
assert.equal(parsedData.age, 25);
|
|
34
|
-
const parsedWithoutAge =
|
|
34
|
+
const parsedWithoutAge = refinedSchema.parse({});
|
|
35
35
|
assert.equal(parsedWithoutAge.age, undefined);
|
|
36
36
|
});
|
|
37
37
|
it('should handle nullable fields', () => {
|
|
38
|
-
const
|
|
38
|
+
const roleSchema = z.object({
|
|
39
39
|
name: z.string()
|
|
40
40
|
});
|
|
41
|
-
const
|
|
41
|
+
const userSchema = z.object({
|
|
42
42
|
firstName: z.string(),
|
|
43
|
-
role:
|
|
43
|
+
role: roleSchema.nullable()
|
|
44
44
|
});
|
|
45
|
-
const
|
|
45
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
46
46
|
role: true
|
|
47
47
|
});
|
|
48
|
-
const parsedWithNullRole =
|
|
48
|
+
const parsedWithNullRole = refinedSchema.parse({
|
|
49
49
|
firstName: 'Alice',
|
|
50
50
|
role: null
|
|
51
51
|
});
|
|
52
52
|
assert.equal(parsedWithNullRole.role, null);
|
|
53
53
|
});
|
|
54
54
|
it('should handle both optional and nullable fields', () => {
|
|
55
|
-
const
|
|
55
|
+
const roleSchema = z.object({
|
|
56
56
|
name: z.string()
|
|
57
57
|
});
|
|
58
|
-
const
|
|
58
|
+
const userSchema = z.object({
|
|
59
59
|
firstName: z.string(),
|
|
60
|
-
role:
|
|
60
|
+
role: roleSchema.nullable().optional()
|
|
61
61
|
});
|
|
62
|
-
const
|
|
62
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
63
63
|
role: {
|
|
64
64
|
name: true
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
const parsedWithRole =
|
|
67
|
+
const parsedWithRole = refinedSchema.parse({
|
|
68
68
|
firstName: 'Alice',
|
|
69
69
|
role: { name: 'Admin' }
|
|
70
70
|
});
|
|
71
71
|
assert.deepStrictEqual(parsedWithRole.role, { name: 'Admin' });
|
|
72
|
-
const parsedWithNullRole =
|
|
72
|
+
const parsedWithNullRole = refinedSchema.parse({
|
|
73
73
|
firstName: 'Alice',
|
|
74
74
|
role: null
|
|
75
75
|
});
|
|
76
76
|
assert.strictEqual(parsedWithNullRole.role, null);
|
|
77
|
-
const parsedWithoutRole =
|
|
77
|
+
const parsedWithoutRole = refinedSchema.parse({ firstName: 'Alice' });
|
|
78
78
|
assert.strictEqual(parsedWithoutRole.role, undefined);
|
|
79
79
|
});
|
|
80
80
|
it('should apply custom schema transformations', () => {
|
|
81
|
-
const
|
|
81
|
+
const roleSchema = z.object({
|
|
82
82
|
name: z.string()
|
|
83
83
|
});
|
|
84
|
-
const
|
|
84
|
+
const userSchema = z.object({
|
|
85
85
|
firstName: z.string(),
|
|
86
|
-
role:
|
|
86
|
+
role: roleSchema
|
|
87
87
|
});
|
|
88
|
-
const
|
|
89
|
-
role:
|
|
88
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
89
|
+
role: schema => schema.extend({ permissions: z.array(z.string()) })
|
|
90
90
|
});
|
|
91
|
-
const parsedData =
|
|
91
|
+
const parsedData = refinedSchema.parse({
|
|
92
92
|
firstName: 'Bob',
|
|
93
93
|
role: { name: 'User', permissions: ['read', 'write'] }
|
|
94
94
|
});
|
|
@@ -98,46 +98,46 @@ describe('.refineSchema()', () => {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
it('should handle nested arrays', () => {
|
|
101
|
-
const
|
|
101
|
+
const roleSchema = z.object({
|
|
102
102
|
level: z.number(),
|
|
103
103
|
title: z.string()
|
|
104
104
|
});
|
|
105
|
-
const
|
|
105
|
+
const userSchema = z.object({
|
|
106
106
|
age: z.number().optional(),
|
|
107
107
|
firstName: z.string(),
|
|
108
108
|
lastName: z.string(),
|
|
109
109
|
nestedArray: z.array(z.object({ name: z.string(), value: z.number() })).optional().nullable(),
|
|
110
|
-
role:
|
|
110
|
+
role: roleSchema.nullable().optional(),
|
|
111
111
|
tags: z.array(z.string()).optional()
|
|
112
112
|
});
|
|
113
|
-
const
|
|
113
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
114
114
|
nestedArray: {
|
|
115
115
|
name: true
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
|
-
const parsedData =
|
|
118
|
+
const parsedData = refinedSchema.parse({
|
|
119
119
|
firstName: 'Alice',
|
|
120
120
|
lastName: 'Smith',
|
|
121
121
|
nestedArray: [{ name: 'Feature1', value: 10 }, { name: 'Feature2', value: 20 }]
|
|
122
122
|
});
|
|
123
123
|
assert.deepStrictEqual(parsedData.nestedArray, [{ name: 'Feature1' }, { name: 'Feature2' }]);
|
|
124
124
|
// Test optional array
|
|
125
|
-
const parsedWithoutNestedArray =
|
|
125
|
+
const parsedWithoutNestedArray = refinedSchema.parse({
|
|
126
126
|
firstName: 'Alice',
|
|
127
127
|
lastName: 'Smith'
|
|
128
128
|
});
|
|
129
129
|
assert.equal(parsedWithoutNestedArray.nestedArray, undefined);
|
|
130
130
|
});
|
|
131
131
|
it('should handle custom fields', () => {
|
|
132
|
-
const
|
|
132
|
+
const userSchema = z.object({
|
|
133
133
|
address: z.custom().optional(),
|
|
134
134
|
firstName: z.string(),
|
|
135
135
|
lastName: z.string()
|
|
136
136
|
});
|
|
137
|
-
const
|
|
137
|
+
const refinedSchema = refineSchema(userSchema, {
|
|
138
138
|
address: true
|
|
139
139
|
});
|
|
140
|
-
const parsedData =
|
|
140
|
+
const parsedData = refinedSchema.parse({
|
|
141
141
|
address: {
|
|
142
142
|
city: 'Austin',
|
|
143
143
|
state: 'TX',
|
|
@@ -155,45 +155,45 @@ describe('.refineSchema()', () => {
|
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
it('should support unions of objects', () => {
|
|
158
|
-
const
|
|
158
|
+
const person = z.object({
|
|
159
159
|
firstName: z.string(),
|
|
160
160
|
lastName: z.string()
|
|
161
161
|
});
|
|
162
|
-
const
|
|
162
|
+
const address = z.object({
|
|
163
163
|
address: z.string(),
|
|
164
164
|
city: z.string()
|
|
165
165
|
});
|
|
166
|
-
const
|
|
166
|
+
const schema = refineSchema(z.union([person, address]), {
|
|
167
167
|
address: true,
|
|
168
168
|
firstName: true
|
|
169
169
|
});
|
|
170
|
-
let parseResult =
|
|
170
|
+
let parseResult = schema.parse({
|
|
171
171
|
address: '123 Main Street',
|
|
172
172
|
firstName: 'Jerry',
|
|
173
173
|
lastName: 'Springer'
|
|
174
174
|
});
|
|
175
175
|
deepStrictEqual(parseResult, { firstName: 'Jerry' });
|
|
176
|
-
parseResult =
|
|
176
|
+
parseResult = schema.parse({
|
|
177
177
|
address: '123 Main Street',
|
|
178
178
|
city: 'Austin'
|
|
179
179
|
});
|
|
180
180
|
deepStrictEqual(parseResult, { address: '123 Main Street' });
|
|
181
181
|
});
|
|
182
182
|
it('should error if none of the types are matched', () => {
|
|
183
|
-
const
|
|
183
|
+
const person = z.object({
|
|
184
184
|
firstName: z.string(),
|
|
185
185
|
lastName: z.string()
|
|
186
186
|
});
|
|
187
|
-
const
|
|
187
|
+
const address = z.object({
|
|
188
188
|
address: z.string(),
|
|
189
189
|
city: z.string()
|
|
190
190
|
});
|
|
191
|
-
const
|
|
191
|
+
const schema = refineSchema(z.union([person, address]), {
|
|
192
192
|
address: true,
|
|
193
193
|
firstName: true
|
|
194
194
|
});
|
|
195
195
|
throws(() => {
|
|
196
|
-
|
|
196
|
+
schema.parse({
|
|
197
197
|
lastName: 'Springer'
|
|
198
198
|
});
|
|
199
199
|
}, (err) => {
|
|
@@ -204,20 +204,20 @@ describe('.refineSchema()', () => {
|
|
|
204
204
|
});
|
|
205
205
|
});
|
|
206
206
|
it('should support unions of objects and simple types', () => {
|
|
207
|
-
const
|
|
207
|
+
const person = z.object({
|
|
208
208
|
firstName: z.string(),
|
|
209
209
|
lastName: z.string()
|
|
210
210
|
});
|
|
211
|
-
const
|
|
211
|
+
const schema = refineSchema(z.union([person, z.string()]), {
|
|
212
212
|
address: true,
|
|
213
213
|
firstName: true
|
|
214
214
|
});
|
|
215
|
-
let parseResult =
|
|
215
|
+
let parseResult = schema.parse({
|
|
216
216
|
firstName: 'Jerry',
|
|
217
217
|
lastName: 'Springer'
|
|
218
218
|
});
|
|
219
219
|
deepStrictEqual(parseResult, { firstName: 'Jerry' });
|
|
220
|
-
parseResult =
|
|
220
|
+
parseResult = schema.parse('123 Main Street');
|
|
221
221
|
deepStrictEqual(parseResult, '123 Main Street');
|
|
222
222
|
});
|
|
223
223
|
});
|
package/dist/select.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ type InferTuple<T extends ZodType<object> | ZodUnion, U extends readonly ZodSele
|
|
|
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
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> = [
|
|
10
|
+
export type MultiSelect<T extends ZodType<object> | ZodUnion> = [
|
|
11
|
+
ZodSelect<T>,
|
|
12
|
+
...ZodSelect<T>[]
|
|
13
|
+
] | undefined | ZodSelect<T>;
|
|
11
14
|
export type ZodSelect<T extends ZodTuple | ZodType<object> | ZodUnion, TIsSimple extends boolean = false> = (T extends ZodUnion ? (RefineZodUnion<T> | ZodUnion) : T extends ZodTuple ? (RefineZodTuple<T> | ZodTuple) : T extends ZodType<object> ? (RefineObject<z.infer<T>, TIsSimple> | ZodType<object>) : never);
|
|
12
15
|
export {};
|
package/dist/utilities.spec.js
CHANGED
|
@@ -30,8 +30,8 @@ describe('.utilities()', () => {
|
|
|
30
30
|
assert.strictEqual(inner.def.type, 'string');
|
|
31
31
|
});
|
|
32
32
|
it('should unwrap lazy inside nested wrappers', () => {
|
|
33
|
-
const
|
|
34
|
-
const wrapped =
|
|
33
|
+
const lazySchema = z.lazy(() => z.string());
|
|
34
|
+
const wrapped = lazySchema.optional().nullable().default('abc');
|
|
35
35
|
const inner = getInnerType(wrapped);
|
|
36
36
|
assert.strictEqual(inner.def.type, 'string');
|
|
37
37
|
});
|
|
@@ -56,20 +56,20 @@ describe('.utilities()', () => {
|
|
|
56
56
|
});
|
|
57
57
|
it('isZodRecord detects complex record schemas with optional fields', () => {
|
|
58
58
|
// Create a sample RecordAttachmentSchema
|
|
59
|
-
const
|
|
59
|
+
const recordAttachmentSchema = z.object({
|
|
60
60
|
name: z.string(),
|
|
61
61
|
size: z.number(),
|
|
62
62
|
type: z.string()
|
|
63
63
|
});
|
|
64
64
|
// Create a record schema with optional field
|
|
65
|
-
const attachmentsSchema = z.record(z.string(),
|
|
65
|
+
const attachmentsSchema = z.record(z.string(), recordAttachmentSchema).optional();
|
|
66
66
|
const nonRecordSchema = z.object({ field: z.string() });
|
|
67
67
|
// Test record detection
|
|
68
|
-
assert.ok(isZodRecord(z.record(z.string(),
|
|
68
|
+
assert.ok(isZodRecord(z.record(z.string(), recordAttachmentSchema)), 'Should detect record schema');
|
|
69
69
|
assert.ok(isZodRecord(attachmentsSchema.unwrap()), 'Should detect optional record schema when unwrapped');
|
|
70
70
|
assert.ok(!isZodRecord(nonRecordSchema), 'Should not detect object schema as record');
|
|
71
71
|
// Test with actual record data
|
|
72
|
-
const validRecord = z.record(z.string(),
|
|
72
|
+
const validRecord = z.record(z.string(), recordAttachmentSchema).parse({
|
|
73
73
|
'file1': { name: 'test.pdf', size: 1024, type: 'application/pdf' },
|
|
74
74
|
'file2': { name: 'image.jpg', size: 2048, type: 'image/jpeg' }
|
|
75
75
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zodselect",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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,21 +25,21 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@secure-data-systems/eslint-config": "^1.0.
|
|
29
|
-
"@secure-data-systems/tsconfig": "^1.0.
|
|
28
|
+
"@secure-data-systems/eslint-config": "^1.0.33",
|
|
29
|
+
"@secure-data-systems/tsconfig": "^1.0.3",
|
|
30
30
|
"@types/node": "^24.10.1",
|
|
31
|
-
"
|
|
32
|
-
"tsx": "^4.20.6",
|
|
31
|
+
"tsx": "^4.21.0",
|
|
33
32
|
"typescript": "^5.9.3"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
|
-
"zod": "^4.1.
|
|
35
|
+
"zod": "^4.1.13"
|
|
37
36
|
},
|
|
38
37
|
"scripts": {
|
|
39
38
|
"build": "node scripts/build.mjs && tsc --project tsconfig.build.json",
|
|
39
|
+
"clean": "node scripts/clean.mjs",
|
|
40
40
|
"deploy": "npm run build && node ./scripts/publish.mjs",
|
|
41
41
|
"lint": "eslint . --c eslint.config.mjs",
|
|
42
42
|
"lint:fix": "eslint . --c eslint.config.mjs --fix",
|
|
43
|
-
"test": "
|
|
43
|
+
"test": "tsx --tsconfig ./tsconfig.build.json --test ./src/**/*.{spec,test}.ts ./test/**/*.{spec,test}.ts"
|
|
44
44
|
}
|
|
45
45
|
}
|