zodselect 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { ZodType, ZodUnion } from 'zod';
|
|
1
|
+
import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodType, ZodUnion } from 'zod';
|
|
2
2
|
import { ZodSelect } from './select.js';
|
|
3
|
-
|
|
3
|
+
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 ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, Depth[TDepth]> : Unwrap<T> extends ZodArray<infer TItem> ? Unwrap<TItem> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, Depth[TDepth]> : true : true;
|
|
5
|
+
type ObjectSelectAll<TShape extends ZodRawShape, TDepth extends number> = {
|
|
6
|
+
[K in keyof TShape]-?: FieldSelectAll<TShape[K], TDepth>;
|
|
7
|
+
};
|
|
8
|
+
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 objects, `true` for anything else. */
|
|
10
|
+
export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = Unwrap<T> extends ZodObject<infer TShape, any> ? ObjectSelectAll<TShape, 10> : {
|
|
4
11
|
[K in keyof ZodSelect<T, true>]-?: true;
|
|
5
12
|
};
|
|
6
13
|
/**
|
|
7
14
|
* Produces a runtime select-all object for the given Zod schema.
|
|
8
15
|
*
|
|
9
|
-
* Every scalar key is set to `true`. Nested object fields
|
|
10
|
-
* expanded into nested select objects.
|
|
16
|
+
* Every scalar key is set to `true`. Nested object fields, and arrays of
|
|
17
|
+
* objects, are recursively expanded into nested select objects. Other arrays
|
|
18
|
+
* and records remain `true`.
|
|
11
19
|
* For union schemas the result is the merged set of keys across all variants.
|
|
12
20
|
*
|
|
13
21
|
* @example
|
|
@@ -17,3 +25,4 @@ export type ZodSelectAll<T extends ZodType<object> | ZodUnion> = {
|
|
|
17
25
|
* ```
|
|
18
26
|
*/
|
|
19
27
|
export declare function buildSelectObject<T extends ZodType<object> | ZodUnion>(schema: T): ZodSelectAll<T>;
|
|
28
|
+
export {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod';
|
|
2
|
-
import { getInnerType, isZodObject, isZodUnion } from './utilities.js';
|
|
2
|
+
import { getInnerType, isZodArray, isZodObject, isZodUnion } from './utilities.js';
|
|
3
3
|
/**
|
|
4
4
|
* Produces a runtime select-all object for the given Zod schema.
|
|
5
5
|
*
|
|
6
|
-
* Every scalar key is set to `true`. Nested object fields
|
|
7
|
-
* expanded into nested select objects.
|
|
6
|
+
* Every scalar key is set to `true`. Nested object fields, and arrays of
|
|
7
|
+
* objects, are recursively expanded into nested select objects. Other arrays
|
|
8
|
+
* and records remain `true`.
|
|
8
9
|
* For union schemas the result is the merged set of keys across all variants.
|
|
9
10
|
*
|
|
10
11
|
* @example
|
|
@@ -41,7 +42,8 @@ function collectKeys(schema, result, depth = 0) {
|
|
|
41
42
|
schema = getInnerType(schema);
|
|
42
43
|
if (isZodObject(schema)) {
|
|
43
44
|
for (const key in schema.shape) {
|
|
44
|
-
const
|
|
45
|
+
const field = unwrapField(schema.shape[key]);
|
|
46
|
+
const fieldInner = isZodArray(field) ? unwrapField(field.def.element) : field;
|
|
45
47
|
if (isZodObject(fieldInner)) {
|
|
46
48
|
const nested = {};
|
|
47
49
|
collectKeys(fieldInner, nested, depth + 1);
|
|
@@ -94,12 +94,68 @@ describe('.buildSelectObject()', () => {
|
|
|
94
94
|
});
|
|
95
95
|
assert.deepStrictEqual(buildSelectObject(schema), { name: true, tags: true });
|
|
96
96
|
});
|
|
97
|
-
it('should
|
|
97
|
+
it('should expand the items of an array of objects', () => {
|
|
98
98
|
const schema = z.object({
|
|
99
99
|
items: z.array(z.object({ id: z.string(), value: z.number() })),
|
|
100
100
|
name: z.string()
|
|
101
101
|
});
|
|
102
|
-
|
|
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
|
+
options: z.array(z.union([z.object({ a: z.string() }), z.object({ b: z.string() })])),
|
|
145
|
+
tags: z.array(z.string())
|
|
146
|
+
});
|
|
147
|
+
const select = buildSelectObject(schema);
|
|
148
|
+
const isTyped = true;
|
|
149
|
+
assert.ok(isTyped);
|
|
150
|
+
assert.deepStrictEqual(select, { byKey: true, matrix: true, options: true, tags: true });
|
|
151
|
+
});
|
|
152
|
+
it('should be a select the schema accepts', () => {
|
|
153
|
+
const schema = z.object({
|
|
154
|
+
client: z.object({ _id: z.string(), name: z.string() }),
|
|
155
|
+
items: z.array(z.object({ id: z.string() }))
|
|
156
|
+
});
|
|
157
|
+
const select = buildSelectObject(schema);
|
|
158
|
+
assert.deepStrictEqual(select, { client: { _id: true, name: true }, items: { id: true } });
|
|
103
159
|
});
|
|
104
160
|
it('should handle recursive schemas without infinite loop', () => {
|
|
105
161
|
;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zodselect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Type-safe selection utility built on top of Zod. Allows for strongly typed field selection, schema merging, and runtime refinement for frontend or backend use.",
|
|
5
5
|
"author": "SecureDataSystems",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/temelyos/zod-select.git"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
28
|
+
"@temelyos/eslint-config": "^1.0.38",
|
|
29
|
+
"@temelyos/tsconfig": "^1.0.6",
|
|
30
30
|
"@types/node": "^26.1.1",
|
|
31
31
|
"tsx": "^4.23.1",
|
|
32
32
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|