zodselect 1.2.4 → 1.2.6
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/refine-schema.d.ts +4 -2
- package/dist/select.spec.js +48 -0
- package/package.json +1 -1
package/dist/refine-schema.d.ts
CHANGED
|
@@ -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> ? U extends object ? ApplyOptionalNullable<T[K], z.ZodArray<
|
|
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> ? [U] extends [object] ? ApplyOptionalNullable<T[K], z.ZodArray<RefinedVariants<U, TShape[K], NextDepth<TDepth>>>> : Zodify<T[K]> : Exclude<T[K], null | undefined> extends object ? ApplyOptionalNullable<T[K], RefinedVariants<Exclude<T[K], null | undefined>, TShape[K], NextDepth<TDepth>>> : ZodNever : 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>>,
|
|
@@ -20,12 +20,14 @@ export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options
|
|
|
20
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>;
|
|
21
21
|
}
|
|
22
22
|
] : never : never>;
|
|
23
|
+
/** One refined schema per variant of a union of objects (a plain object is its own one variant), so the result narrows by variant. */
|
|
24
|
+
type RefinedVariants<T, TShape, TDepth extends number> = T extends object ? RefinedTypeSchema<T, TShape, TDepth> : never;
|
|
23
25
|
type Refinement<T extends ZodType, TIsSimple extends boolean> = TIsSimple extends true ? boolean : ((schema: T) => ZodType) | boolean | ZodType;
|
|
24
26
|
export type RefineObject<T extends object, TIsSimple extends boolean> = {
|
|
25
27
|
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]?: RefineType<T[K], TIsSimple>;
|
|
26
28
|
};
|
|
27
29
|
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>>,
|
|
30
|
+
type RefineType<T, TIsSimple extends boolean> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, ([Exclude<Exclude<T, null | undefined>, object>] extends [never] ? RefineObject<Extract<Exclude<T, null | undefined>, object>, TIsSimple> : never) | 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
31
|
export type RefineZodTuple<T extends ZodTuple> = {
|
|
30
32
|
[K in keyof UnionToIntersection<T['def']['items'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
|
|
31
33
|
};
|
package/dist/select.spec.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { strict as assert } from 'assert';
|
|
1
2
|
import { describe, it } from 'node:test';
|
|
2
3
|
import { z } from 'zod';
|
|
4
|
+
import { buildSelectObject } from './build-select-object.js';
|
|
5
|
+
import { refineSchema } from './refine-schema.js';
|
|
3
6
|
const internalService = z.object({
|
|
4
7
|
name: z.string(),
|
|
5
8
|
type: z.literal('internal')
|
|
@@ -46,3 +49,48 @@ describe('InferMergedType', () => {
|
|
|
46
49
|
}
|
|
47
50
|
});
|
|
48
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('still refuses a nested select into a union with a plain variant, which would miss the plain value', () => {
|
|
92
|
+
// @ts-expect-error `name` may be a string, so only the whole value can be selected.
|
|
93
|
+
const select = { name: { en: true } };
|
|
94
|
+
assert.ok(select);
|
|
95
|
+
});
|
|
96
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zodselect",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
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",
|