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