zod 4.6.0 → 4.6.2
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/package.json +1 -1
- package/src/v4/classic/tests/discriminated-unions.test.ts +112 -6
- package/src/v4/classic/tests/prefault.test.ts +70 -0
- package/src/v4/classic/tests/recursive-types.test.ts +45 -0
- package/src/v4/core/compile.ts +9 -7
- package/src/v4/core/schemas.ts +45 -39
- package/src/v4/core/tests/locales/tg.test.ts +85 -0
- package/src/v4/core/versions.ts +1 -1
- package/src/v4/locales/index.ts +1 -0
- package/src/v4/locales/tg.ts +134 -0
- package/src/v4/mini/schemas.ts +1 -1
- package/src/v4/mini/tests/index.test.ts +22 -0
- package/src/v4/mini/tests/recursive-types.test.ts +16 -0
- package/v4/core/compile.cjs +8 -7
- package/v4/core/compile.js +8 -7
- package/v4/core/schemas.cjs +46 -37
- package/v4/core/schemas.d.cts +9 -5
- package/v4/core/schemas.d.ts +9 -5
- package/v4/core/schemas.js +46 -37
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
- package/v4/locales/index.cjs +3 -1
- package/v4/locales/index.d.cts +1 -0
- package/v4/locales/index.d.ts +1 -0
- package/v4/locales/index.js +1 -0
- package/v4/locales/tg.cjs +143 -0
- package/v4/locales/tg.d.cts +5 -0
- package/v4/locales/tg.d.ts +4 -0
- package/v4/locales/tg.js +116 -0
- package/v4/mini/schemas.d.cts +1 -1
- package/v4/mini/schemas.d.ts +1 -1
package/package.json
CHANGED
|
@@ -740,6 +740,26 @@ test("encode with codec discriminator", () => {
|
|
|
740
740
|
expect(encoded).toEqual({ type: 1, value: "hello" });
|
|
741
741
|
});
|
|
742
742
|
|
|
743
|
+
test("nested encoding does not select a sibling from forward discriminator values", async () => {
|
|
744
|
+
const tag = (value: "a" | "b") =>
|
|
745
|
+
z.codec(z.literal(value).default(value), z.undefined(), {
|
|
746
|
+
decode: () => undefined,
|
|
747
|
+
encode: () => value,
|
|
748
|
+
});
|
|
749
|
+
const inner = z.discriminatedUnion("type", [
|
|
750
|
+
z.object({ type: tag("a"), value: z.literal("a") }),
|
|
751
|
+
z.object({ type: tag("b"), value: z.literal("b") }),
|
|
752
|
+
]);
|
|
753
|
+
const outer = z.discriminatedUnion("type", [inner, z.object({ type: z.undefined(), value: z.string() })]);
|
|
754
|
+
for (const value of ["a", "b"] as const) {
|
|
755
|
+
const input = { type: undefined, value };
|
|
756
|
+
const expected = { type: value, value };
|
|
757
|
+
expect(z.encode(inner, input)).toEqual(expected);
|
|
758
|
+
expect(z.encode(outer, input)).toEqual(expected);
|
|
759
|
+
expect(await z.encodeAsync(outer, input)).toEqual(expected);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
743
763
|
test("getDiscriminatedOption", () => {
|
|
744
764
|
const fruit = z.object({ type: z.literal("fruit"), seeds: z.boolean() });
|
|
745
765
|
const veg = z.object({ type: z.literal("vegetable"), leafy: z.boolean() });
|
|
@@ -811,9 +831,14 @@ test.each(["__proto__", "constructor", "toString", "hasOwnProperty", "valueOf"])
|
|
|
811
831
|
}
|
|
812
832
|
);
|
|
813
833
|
|
|
814
|
-
// An omittable discriminator reads back as undefined at the lookup, exactly as TypeScript sees it: `{ k?: "a" } | { k?: "c" }` does not narrow on `k === undefined`.
|
|
815
834
|
test("an omittable discriminator claims undefined", () => {
|
|
816
|
-
const omittable = [
|
|
835
|
+
const omittable = [
|
|
836
|
+
z.exactOptional(z.literal("a")),
|
|
837
|
+
z.optional(z.literal("a")),
|
|
838
|
+
z.literal("a").default("a"),
|
|
839
|
+
z.literal("a").prefault("a"),
|
|
840
|
+
z.literal("a").catch("a"),
|
|
841
|
+
];
|
|
817
842
|
for (const k of omittable) {
|
|
818
843
|
expect(z.object({ k })._zod.propValues.k).toEqual(new Set(["a", undefined]));
|
|
819
844
|
}
|
|
@@ -827,10 +852,91 @@ test("an omittable discriminator claims undefined", () => {
|
|
|
827
852
|
expect(z.union(options).safeParse({ x: "s" }).success).toEqual(true);
|
|
828
853
|
expect(z.discriminatedUnion("k", options).safeParse({ k: "b", y: 1 }).success).toEqual(true);
|
|
829
854
|
|
|
830
|
-
//
|
|
855
|
+
// ambiguous absence does not prevent explicit tags from routing
|
|
831
856
|
for (const k of omittable) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
).
|
|
857
|
+
const schema = z.discriminatedUnion("k", [z.object({ k }), z.object({ k: z.exactOptional(z.literal("c")) })]);
|
|
858
|
+
expect(schema.parse({ k: "a" })).toEqual({ k: "a" });
|
|
859
|
+
expect(schema.parse({ k: "c" })).toEqual({ k: "c" });
|
|
860
|
+
expect(schema.safeParse({}).success).toBe(false);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("defaulted discriminators preserve tagged parsing without guessing a member", async () => {
|
|
865
|
+
const a = z.object({ type: z.literal("a").default("a"), x: z.number().positive() });
|
|
866
|
+
const b = z.object({ type: z.literal("b").default("b"), y: z.string() });
|
|
867
|
+
const c = z.object({ type: z.literal("c").default("c"), z: z.boolean() });
|
|
868
|
+
for (const options of [
|
|
869
|
+
[a, b, c],
|
|
870
|
+
[c, b, a],
|
|
871
|
+
] as const) {
|
|
872
|
+
const schema = z.discriminatedUnion("type", options);
|
|
873
|
+
for (const input of [a.parse({ x: 1 }), b.parse({ y: "s" }), c.parse({ z: true })]) {
|
|
874
|
+
expect(schema.parse(input)).toEqual(input);
|
|
875
|
+
expect((await schema.safeParseAsync(input)).success).toBe(true);
|
|
876
|
+
}
|
|
877
|
+
for (const input of [{ x: 1, y: "s", z: true }, { type: undefined }, { type: "other" }, { type: "a", x: -1 }]) {
|
|
878
|
+
expect(schema.safeParse(input).success).toBe(false);
|
|
879
|
+
expect((await schema.safeParseAsync(input)).success).toBe(false);
|
|
880
|
+
}
|
|
881
|
+
const result = schema.safeParse({});
|
|
882
|
+
expect(result.error?.issues[0]).toMatchObject({
|
|
883
|
+
code: "invalid_union",
|
|
884
|
+
path: ["type"],
|
|
885
|
+
options: options.map((o) => o.shape.type.unwrap().value),
|
|
886
|
+
});
|
|
887
|
+
expect(z.getDiscriminatedOption(schema, "a")).toBe(a);
|
|
888
|
+
}
|
|
889
|
+
const unique = z.discriminatedUnion("type", [a, b.safeExtend({ type: b.shape.type.unwrap() })]);
|
|
890
|
+
expect(unique.parse({ x: 1 })).toEqual({ type: "a", x: 1 });
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
test("undefined collisions are value-based and discriminator lookup rejects ambiguity", () => {
|
|
894
|
+
const a = z.object({ type: z.literal("a").optional() });
|
|
895
|
+
const absent = z.object({ type: z.undefined() });
|
|
896
|
+
for (const options of [
|
|
897
|
+
[a, absent],
|
|
898
|
+
[absent, a],
|
|
899
|
+
[absent, absent, a],
|
|
900
|
+
] as const) {
|
|
901
|
+
const schema = z.discriminatedUnion("type", options);
|
|
902
|
+
expect(schema.parse({ type: "a" })).toEqual({ type: "a" });
|
|
903
|
+
expect(schema.safeParse({}).success).toBe(false);
|
|
904
|
+
expect(() => z.getDiscriminatedOption(schema, undefined)).toThrow('Ambiguous discriminator value "undefined"');
|
|
905
|
+
}
|
|
906
|
+
const unique = z.discriminatedUnion("type", [absent, z.object({ type: z.literal("a") })]);
|
|
907
|
+
expect(unique.parse({ type: undefined })).toEqual({ type: undefined });
|
|
908
|
+
expect(z.getDiscriminatedOption(unique, undefined)).toBe(absent);
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
test("non-undefined discriminator collisions remain schema errors", () => {
|
|
912
|
+
for (const tag of [z.literal("a").default("a"), z.literal(["a", "b"]), z.literal("a").nullable()]) {
|
|
913
|
+
const schema = z.discriminatedUnion("type", [z.object({ type: tag }), z.object({ type: tag })]);
|
|
914
|
+
expect(() => schema.safeParse({ type: "a" })).toThrow(/Duplicate discriminator value/);
|
|
915
|
+
expect(() => z.encode(schema, { type: "a" })).toThrow(/Duplicate discriminator value/);
|
|
916
|
+
expect(() => z.getDiscriminatedOption(schema, "a")).toThrow(/Duplicate discriminator value/);
|
|
917
|
+
}
|
|
918
|
+
const nullable = z.discriminatedUnion("type", [
|
|
919
|
+
z.object({ type: z.literal("a").nullable() }),
|
|
920
|
+
z.object({ type: z.literal("b").nullable() }),
|
|
921
|
+
]);
|
|
922
|
+
expect(() => nullable.safeParse({ type: "a" })).toThrow('Duplicate discriminator value "null"');
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
test("nested defaulted unions only advertise routable discriminator values", () => {
|
|
926
|
+
const a = z.object({ type: z.literal("a").default("a"), group: z.literal("inner") });
|
|
927
|
+
const b = z.object({ type: z.literal("b").default("b"), group: z.literal("inner") });
|
|
928
|
+
const inner = z.discriminatedUnion("type", [a, b]);
|
|
929
|
+
const c = z.object({ type: z.literal("c").default("c"), group: z.literal("outer") });
|
|
930
|
+
expect(inner._zod.propValues.type).toEqual(new Set(["a", "b"]));
|
|
931
|
+
for (const schema of [
|
|
932
|
+
z.discriminatedUnion("type", [z.lazy(() => inner), c]),
|
|
933
|
+
z.discriminatedUnion("group", [inner, c]),
|
|
934
|
+
]) {
|
|
935
|
+
expect(schema.parse({ type: "a", group: "inner" })).toEqual({ type: "a", group: "inner" });
|
|
936
|
+
expect(schema.parse({ group: "outer" })).toEqual({ type: "c", group: "outer" });
|
|
937
|
+
expect(schema.safeParse({ group: "inner" }).success).toBe(false);
|
|
835
938
|
}
|
|
939
|
+
const fallback = z.discriminatedUnion("type", [a, b], { unionFallback: true });
|
|
940
|
+
expect(fallback._zod.propValues.type.has(undefined)).toBe(true);
|
|
941
|
+
expect(fallback.parse({ group: "inner" })).toEqual({ type: "a", group: "inner" });
|
|
836
942
|
});
|
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
import { expect, expectTypeOf, test } from "vitest";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
3
|
|
|
4
|
+
test.each([false, true])("undefined prefault preserves object keys (jitless: %s)", async (jitless) => {
|
|
5
|
+
const previous = z.config().jitless;
|
|
6
|
+
z.config({ jitless });
|
|
7
|
+
try {
|
|
8
|
+
let calls = 0;
|
|
9
|
+
const field = z.union([z.string(), z.undefined()]).prefault(() => {
|
|
10
|
+
calls++;
|
|
11
|
+
return undefined;
|
|
12
|
+
});
|
|
13
|
+
const schema = z.object({ a: field });
|
|
14
|
+
expectTypeOf<z.output<typeof field>>().toEqualTypeOf<string | undefined>();
|
|
15
|
+
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<{ a?: string | undefined }>();
|
|
16
|
+
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<{ a: string | undefined }>();
|
|
17
|
+
expect(schema.parse({})).toStrictEqual({ a: undefined });
|
|
18
|
+
expect(calls).toBe(1);
|
|
19
|
+
expect(schema.parse({ a: undefined })).toStrictEqual({ a: undefined });
|
|
20
|
+
expect(calls).toBe(2);
|
|
21
|
+
expect(await schema.parseAsync({})).toStrictEqual({ a: undefined });
|
|
22
|
+
expect(calls).toBe(3);
|
|
23
|
+
expect(schema.parse({ a: "value" })).toStrictEqual({ a: "value" });
|
|
24
|
+
expect(schema.safeParse({ a: 123 }).success).toBe(false);
|
|
25
|
+
expect(calls).toBe(3);
|
|
26
|
+
|
|
27
|
+
const literal = z.prefault(z.string().optional(), undefined);
|
|
28
|
+
expect(z.core.compileFn(z.object({ a: literal }))({})).toStrictEqual({ a: undefined });
|
|
29
|
+
for (const wrapped of [literal, literal.readonly(), literal.nullable(), z.lazy(() => literal)]) {
|
|
30
|
+
expect(z.object({ a: wrapped }).parse({})).toStrictEqual({ a: undefined });
|
|
31
|
+
}
|
|
32
|
+
expect(z.object({ a: literal.optional() }).parse({})).toStrictEqual({});
|
|
33
|
+
expect(z.object({ a: literal.optional() }).parse({ a: undefined })).toStrictEqual({ a: undefined });
|
|
34
|
+
expect(z.tuple([literal]).parse([])).toStrictEqual([undefined]);
|
|
35
|
+
} finally {
|
|
36
|
+
z.config({ jitless: previous });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("prefault preserves transformed undefined output", async () => {
|
|
41
|
+
const field = z
|
|
42
|
+
.string()
|
|
43
|
+
.transform(() => undefined)
|
|
44
|
+
.prefault("fallback");
|
|
45
|
+
expectTypeOf<z.output<typeof field>>().toEqualTypeOf<undefined>();
|
|
46
|
+
expect(field.parse(undefined)).toBeUndefined();
|
|
47
|
+
const schema = z.object({ a: field, optional: z.string().optional() });
|
|
48
|
+
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<{ a: undefined; optional?: string | undefined }>();
|
|
49
|
+
expect(schema.parse({})).toStrictEqual({ a: undefined });
|
|
50
|
+
expect(z.core.compileFn(schema)({})).toStrictEqual({ a: undefined });
|
|
51
|
+
expect(z.compile(schema).parse({})).toStrictEqual({ a: undefined });
|
|
52
|
+
expect(
|
|
53
|
+
await z
|
|
54
|
+
.object({
|
|
55
|
+
a: z
|
|
56
|
+
.string()
|
|
57
|
+
.transform(async () => undefined)
|
|
58
|
+
.prefault("fallback"),
|
|
59
|
+
})
|
|
60
|
+
.parseAsync({})
|
|
61
|
+
).toStrictEqual({ a: undefined });
|
|
62
|
+
expect(
|
|
63
|
+
z
|
|
64
|
+
.object({
|
|
65
|
+
a: z
|
|
66
|
+
.string()
|
|
67
|
+
.prefault("fallback")
|
|
68
|
+
.transform(() => undefined),
|
|
69
|
+
})
|
|
70
|
+
.parse({})
|
|
71
|
+
).toStrictEqual({ a: undefined });
|
|
72
|
+
});
|
|
73
|
+
|
|
4
74
|
test("basic prefault", () => {
|
|
5
75
|
const a = z.prefault(z.string().trim(), " default ");
|
|
6
76
|
expect(a).toBeInstanceOf(z.ZodPrefault);
|
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
import { expect, expectTypeOf, test } from "vitest";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
3
|
|
|
4
|
+
test("recursive object schema type aliases", () => {
|
|
5
|
+
type ObjectField = z.ZodObject<{ [k: string]: ObjectField }>;
|
|
6
|
+
type MixedField = z.ZodString | z.ZodObject<{ [k: string]: MixedField }>;
|
|
7
|
+
expectTypeOf<ObjectField>().toExtend<z.ZodType>();
|
|
8
|
+
expectTypeOf<MixedField>().toExtend<z.ZodType>();
|
|
9
|
+
|
|
10
|
+
type ObjectValue = { [k: string]: ObjectValue };
|
|
11
|
+
type MixedValue = string | { [k: string]: MixedValue };
|
|
12
|
+
expectTypeOf<z.input<ObjectField>>().toEqualTypeOf<ObjectValue>();
|
|
13
|
+
expectTypeOf<z.output<ObjectField>>().toEqualTypeOf<ObjectValue>();
|
|
14
|
+
expectTypeOf<z.input<MixedField>>().toEqualTypeOf<MixedValue>();
|
|
15
|
+
expectTypeOf<z.output<MixedField>>().toEqualTypeOf<MixedValue>();
|
|
16
|
+
|
|
17
|
+
const schema: MixedField = z.object({ name: z.string(), nested: z.object({ name: z.string() }) });
|
|
18
|
+
expect(schema.parse({ name: "a", nested: { name: "b", extra: true } })).toEqual({
|
|
19
|
+
name: "a",
|
|
20
|
+
nested: { name: "b" },
|
|
21
|
+
});
|
|
22
|
+
expect(schema.safeParse({ name: "a", nested: { name: 123 } }).success).toBe(false);
|
|
23
|
+
|
|
24
|
+
type CoreField = z.core.$ZodString<string> | z.core.$ZodObject<{ [k: string]: CoreField }>;
|
|
25
|
+
expectTypeOf<z.input<CoreField>>().toEqualTypeOf<MixedValue>();
|
|
26
|
+
expectTypeOf<z.output<CoreField>>().toEqualTypeOf<MixedValue>();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("recursive object aliases preserve distinct input and output", () => {
|
|
30
|
+
type Field = z.ZodPipe<z.ZodString, z.ZodTransform<number, string>> | z.ZodObject<{ [k: string]: Field }>;
|
|
31
|
+
type Input = string | { [k: string]: Input };
|
|
32
|
+
type Output = number | { [k: string]: Output };
|
|
33
|
+
expectTypeOf<z.input<Field>>().toEqualTypeOf<Input>();
|
|
34
|
+
expectTypeOf<z.output<Field>>().toEqualTypeOf<Output>();
|
|
35
|
+
const schema: Field = z.object({ nested: z.object({ length: z.string().transform((s) => s.length) }) });
|
|
36
|
+
expect(schema.parse({ nested: { length: "abc" } })).toEqual({ nested: { length: 3 } });
|
|
37
|
+
expect(schema.safeParse({ nested: { length: 3 } }).success).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("indexed object inference preserves string keys", () => {
|
|
41
|
+
type Indexed = z.ZodObject<Record<string, z.ZodString>>;
|
|
42
|
+
type Recursive = z.ZodObject<Record<string, Recursive>>;
|
|
43
|
+
expectTypeOf<keyof z.input<Indexed>>().toEqualTypeOf<string>();
|
|
44
|
+
expectTypeOf<keyof z.output<Indexed>>().toEqualTypeOf<string>();
|
|
45
|
+
expectTypeOf<keyof z.input<Recursive>>().toEqualTypeOf<string>();
|
|
46
|
+
expectTypeOf<keyof z.output<Recursive>>().toEqualTypeOf<string>();
|
|
47
|
+
});
|
|
48
|
+
|
|
4
49
|
test("recursion with z.lazy", () => {
|
|
5
50
|
const data = {
|
|
6
51
|
name: "I",
|
package/src/v4/core/compile.ts
CHANGED
|
@@ -1270,9 +1270,9 @@ function generateObjectCheck(
|
|
|
1270
1270
|
}
|
|
1271
1271
|
// else: strip mode (no catchall) - unknown keys ignored, only include known keys
|
|
1272
1272
|
|
|
1273
|
-
//
|
|
1273
|
+
// defaulted required outputs keep their keys even when undefined
|
|
1274
1274
|
const outputVar = newVar(ctx);
|
|
1275
|
-
const hasConditionalKeys = allKeys.some((k) =>
|
|
1275
|
+
const hasConditionalKeys = allKeys.some((k) => mayOmitUndefined(propShape[k]!) || dropsWhenAbsent(propShape[k]!));
|
|
1276
1276
|
|
|
1277
1277
|
// Assert mode: every declared key is validated above, so the output literal and the unknown-key copy are pure waste. A `never` catchall already emitted its rejection loop; a schema catchall still has to validate the values it would otherwise have stored.
|
|
1278
1278
|
if (!buildsValue) {
|
|
@@ -1301,7 +1301,7 @@ function generateObjectCheck(
|
|
|
1301
1301
|
const out = propOutputs.get(k);
|
|
1302
1302
|
if (dropsWhenAbsent(propShape[k]!)) {
|
|
1303
1303
|
doc.write(`if (${kx} in ${accessor}) ${outputVar}[${kx}] = ${out};`);
|
|
1304
|
-
} else if (
|
|
1304
|
+
} else if (mayOmitUndefined(propShape[k]!)) {
|
|
1305
1305
|
doc.write(`if (${out} !== undefined || ${kx} in ${accessor}) ${outputVar}[${kx}] = ${out};`);
|
|
1306
1306
|
} else {
|
|
1307
1307
|
doc.write(`${outputVar}[${kx}] = ${out};`);
|
|
@@ -1464,9 +1464,11 @@ function dropsWhenAbsent(schema: SomeType): boolean {
|
|
|
1464
1464
|
return schema._zod.optin === "optional" && schema._zod.optout === "optional";
|
|
1465
1465
|
}
|
|
1466
1466
|
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1467
|
+
function mayOmitUndefined(schema: SomeType): boolean {
|
|
1468
|
+
return (schema._zod.optin !== "defaulted" || schema._zod.optout === "optional") && mayOutputUndefined(schema);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// whether a schema's success-path output can be undefined
|
|
1470
1472
|
function mayOutputUndefined(schema: SomeType): boolean {
|
|
1471
1473
|
const def = schema._zod.def as {
|
|
1472
1474
|
type: string;
|
|
@@ -1869,7 +1871,7 @@ function generateDiscriminatedUnionCheck(
|
|
|
1869
1871
|
throw new ZodCompileUnsupportedError("discriminated union option without static discriminator values");
|
|
1870
1872
|
}
|
|
1871
1873
|
|
|
1872
|
-
//
|
|
1874
|
+
// let the interpreter handle collisions instead of compiling first-match dispatch
|
|
1873
1875
|
for (const value of values) {
|
|
1874
1876
|
if (claimed.has(value)) {
|
|
1875
1877
|
throw new ZodCompileUnsupportedError(`duplicate discriminator value ${String(value)}`);
|
package/src/v4/core/schemas.ts
CHANGED
|
@@ -1893,7 +1893,7 @@ type OptionalInSchema = { _zod: { optin: "optional" | "defaulted" } };
|
|
|
1893
1893
|
export type $InferObjectOutput<T extends $ZodLooseShape, Extra extends Record<string, unknown>> = string extends keyof T
|
|
1894
1894
|
? util.IsAny<T[keyof T]> extends true
|
|
1895
1895
|
? Record<string, unknown>
|
|
1896
|
-
:
|
|
1896
|
+
: { [k in string]: core.output<T[keyof T]> }
|
|
1897
1897
|
: keyof (T & Extra) extends never
|
|
1898
1898
|
? Record<string, never>
|
|
1899
1899
|
: util.Prettify<
|
|
@@ -1937,7 +1937,7 @@ export type $InferObjectOutput<T extends $ZodLooseShape, Extra extends Record<st
|
|
|
1937
1937
|
export type $InferObjectInput<T extends $ZodLooseShape, Extra extends Record<string, unknown>> = string extends keyof T
|
|
1938
1938
|
? util.IsAny<T[keyof T]> extends true
|
|
1939
1939
|
? Record<string, unknown>
|
|
1940
|
-
:
|
|
1940
|
+
: { [k in string]: core.input<T[keyof T]> }
|
|
1941
1941
|
: keyof (T & Extra) extends never
|
|
1942
1942
|
? Record<string, never>
|
|
1943
1943
|
: util.Prettify<
|
|
@@ -1983,7 +1983,7 @@ function handlePropertyResult(
|
|
|
1983
1983
|
}
|
|
1984
1984
|
|
|
1985
1985
|
if (result.value === undefined) {
|
|
1986
|
-
if (isPresent) {
|
|
1986
|
+
if (isPresent || (optin === "defaulted" && !isOptionalOut)) {
|
|
1987
1987
|
(final.value as any)[key] = undefined;
|
|
1988
1988
|
}
|
|
1989
1989
|
} else {
|
|
@@ -2314,16 +2314,16 @@ export const $ZodObjectJIT: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$
|
|
|
2314
2314
|
doc.write(`
|
|
2315
2315
|
if (${id}.issues.length) {${prefixStr(id, k)}
|
|
2316
2316
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
}
|
|
2317
|
+
`);
|
|
2318
|
+
if (optin === "defaulted") {
|
|
2319
|
+
doc.write(`newResult[${k}] = ${id}.value;`);
|
|
2320
|
+
} else {
|
|
2321
|
+
doc.write(`
|
|
2322
|
+
if (${id}.value !== undefined || ${isPresent}) {
|
|
2323
2323
|
newResult[${k}] = ${id}.value;
|
|
2324
2324
|
}
|
|
2325
|
-
|
|
2326
2325
|
`);
|
|
2326
|
+
}
|
|
2327
2327
|
}
|
|
2328
2328
|
}
|
|
2329
2329
|
|
|
@@ -2600,7 +2600,7 @@ export interface $ZodDiscriminatedUnionInternals<
|
|
|
2600
2600
|
def: $ZodDiscriminatedUnionDef<Options, Disc>;
|
|
2601
2601
|
propValues: util.PropValues;
|
|
2602
2602
|
bag: util.LoosePartial<{
|
|
2603
|
-
optionsMap: Map<util.Primitive, $ZodType>;
|
|
2603
|
+
optionsMap: Map<util.Primitive, $ZodType | null>;
|
|
2604
2604
|
}>;
|
|
2605
2605
|
}
|
|
2606
2606
|
|
|
@@ -2624,7 +2624,7 @@ export type $DiscriminatedOption<Options extends readonly SomeType[], Disc exten
|
|
|
2624
2624
|
: never;
|
|
2625
2625
|
}[number];
|
|
2626
2626
|
|
|
2627
|
-
/** Returns the option
|
|
2627
|
+
/** Returns the option whose discriminator claims `value`, or throws if ambiguous. */
|
|
2628
2628
|
export function getDiscriminatedOption<
|
|
2629
2629
|
Options extends readonly SomeType[],
|
|
2630
2630
|
Disc extends string,
|
|
@@ -2633,15 +2633,31 @@ export function getDiscriminatedOption<
|
|
|
2633
2633
|
const internals = union._zod;
|
|
2634
2634
|
let map = internals.bag.optionsMap;
|
|
2635
2635
|
if (!map) {
|
|
2636
|
-
map =
|
|
2637
|
-
const { options, discriminator } = internals.def;
|
|
2638
|
-
for (const option of options as unknown as readonly $ZodType[]) {
|
|
2639
|
-
// First declaration wins, matching the order the parse path resolves a duplicate in.
|
|
2640
|
-
for (const v of option._zod.propValues?.[discriminator] ?? []) if (!map.has(v)) map.set(v, option);
|
|
2641
|
-
}
|
|
2636
|
+
map = discriminatorMap(internals.def);
|
|
2642
2637
|
internals.bag.optionsMap = map;
|
|
2643
2638
|
}
|
|
2644
|
-
|
|
2639
|
+
const option = map.get(value as util.Primitive);
|
|
2640
|
+
if (option === null) throw new Error(`Ambiguous discriminator value "${String(value)}"`);
|
|
2641
|
+
return option as any;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
function discriminatorMap(def: $ZodDiscriminatedUnionDef<readonly SomeType[]>): Map<util.Primitive, $ZodType | null> {
|
|
2645
|
+
const map = new Map<util.Primitive, $ZodType | null>();
|
|
2646
|
+
for (const option of def.options as readonly $ZodType[]) {
|
|
2647
|
+
const values = option._zod.propValues?.[def.discriminator];
|
|
2648
|
+
if (!values || values.size === 0)
|
|
2649
|
+
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
|
|
2650
|
+
for (const value of values) {
|
|
2651
|
+
if (map.has(value)) {
|
|
2652
|
+
if (value !== undefined) throw new Error(`Duplicate discriminator value "${String(value)}"`);
|
|
2653
|
+
// keep the collision marked so a later member cannot reclaim it
|
|
2654
|
+
map.set(value, null);
|
|
2655
|
+
} else {
|
|
2656
|
+
map.set(value, option);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return map;
|
|
2645
2661
|
}
|
|
2646
2662
|
|
|
2647
2663
|
export interface $ZodDiscriminatedUnion<
|
|
@@ -2661,10 +2677,12 @@ export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
|
|
|
2661
2677
|
const _super = inst._zod.parse;
|
|
2662
2678
|
util.defineLazyInternal(inst, "propValues", (zod) => {
|
|
2663
2679
|
const propValues: util.PropValues = {};
|
|
2680
|
+
let undefinedCount = 0;
|
|
2664
2681
|
for (const option of zod.def.options) {
|
|
2665
2682
|
const pv = option._zod.propValues;
|
|
2666
2683
|
if (!pv || Object.keys(pv).length === 0)
|
|
2667
2684
|
throw new Error(`Invalid discriminated union option at index "${zod.def.options.indexOf(option)}"`);
|
|
2685
|
+
if (pv[zod.def.discriminator]?.has(undefined)) undefinedCount++;
|
|
2668
2686
|
for (const [k, v] of Object.entries(pv!)) {
|
|
2669
2687
|
if (!Object.prototype.hasOwnProperty.call(propValues, k)) {
|
|
2670
2688
|
util.assignProp(propValues, k, new Set());
|
|
@@ -2674,6 +2692,7 @@ export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
|
|
|
2674
2692
|
}
|
|
2675
2693
|
}
|
|
2676
2694
|
}
|
|
2695
|
+
if (!zod.def.unionFallback && undefinedCount > 1) propValues[zod.def.discriminator]?.delete(undefined);
|
|
2677
2696
|
return propValues;
|
|
2678
2697
|
});
|
|
2679
2698
|
|
|
@@ -2685,22 +2704,7 @@ export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
|
|
|
2685
2704
|
}
|
|
2686
2705
|
});
|
|
2687
2706
|
|
|
2688
|
-
const disc = util.cached(() =>
|
|
2689
|
-
const opts = def.options;
|
|
2690
|
-
const map: Map<util.Primitive, $ZodType> = new Map();
|
|
2691
|
-
for (const o of opts) {
|
|
2692
|
-
const values = o._zod.propValues?.[def.discriminator];
|
|
2693
|
-
if (!values || values.size === 0)
|
|
2694
|
-
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o)}"`);
|
|
2695
|
-
for (const v of values) {
|
|
2696
|
-
if (map.has(v)) {
|
|
2697
|
-
throw new Error(`Duplicate discriminator value "${String(v)}"`);
|
|
2698
|
-
}
|
|
2699
|
-
map.set(v, o);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
return map;
|
|
2703
|
-
});
|
|
2707
|
+
const disc = util.cached(() => discriminatorMap(def));
|
|
2704
2708
|
|
|
2705
2709
|
inst._zod.parse = (payload, ctx) => {
|
|
2706
2710
|
const input = payload.value;
|
|
@@ -2715,8 +2719,10 @@ export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
|
|
|
2715
2719
|
return payload;
|
|
2716
2720
|
}
|
|
2717
2721
|
|
|
2718
|
-
const
|
|
2719
|
-
|
|
2722
|
+
const value = input?.[def.discriminator];
|
|
2723
|
+
const opt = disc.value.get(value as util.Primitive);
|
|
2724
|
+
// forward metadata cannot choose an encoder for an absent tag
|
|
2725
|
+
if (opt && (value !== undefined || ctx.direction !== "backward")) {
|
|
2720
2726
|
return opt._zod.run(payload, ctx) as any;
|
|
2721
2727
|
}
|
|
2722
2728
|
|
|
@@ -2733,7 +2739,7 @@ export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
|
|
|
2733
2739
|
errors: [],
|
|
2734
2740
|
note: "No matching discriminator",
|
|
2735
2741
|
discriminator: def.discriminator,
|
|
2736
|
-
options: Array.from(disc.value.keys()),
|
|
2742
|
+
options: Array.from(disc.value.keys()).filter((value) => disc.value.get(value) !== null),
|
|
2737
2743
|
input,
|
|
2738
2744
|
path: [def.discriminator],
|
|
2739
2745
|
inst,
|
|
@@ -4071,7 +4077,7 @@ export interface $ZodPrefaultDef<T extends SomeType = $ZodType> extends $ZodType
|
|
|
4071
4077
|
}
|
|
4072
4078
|
|
|
4073
4079
|
export interface $ZodPrefaultInternals<T extends SomeType = $ZodType>
|
|
4074
|
-
extends $ZodTypeInternals<
|
|
4080
|
+
extends $ZodTypeInternals<core.output<T>, core.input<T> | undefined> {
|
|
4075
4081
|
def: $ZodPrefaultDef<T>;
|
|
4076
4082
|
optin: "defaulted";
|
|
4077
4083
|
optout?: "optional" | undefined;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
|
|
4
|
+
test("locales - tg", () => {
|
|
5
|
+
z.config(z.locales.tg());
|
|
6
|
+
|
|
7
|
+
const invalidType = z.number().safeParse("a");
|
|
8
|
+
expect(invalidType.error!.issues[0].code).toBe("invalid_type");
|
|
9
|
+
expect(invalidType.error!.issues[0].message).toBe("Вуруди нодуруст: рақам интизор мерафт, сатр гирифта шуд");
|
|
10
|
+
|
|
11
|
+
const invalidType2 = z.string().safeParse(1);
|
|
12
|
+
expect(invalidType2.error!.issues[0].code).toBe("invalid_type");
|
|
13
|
+
expect(invalidType2.error!.issues[0].message).toBe("Вуруди нодуруст: сатр интизор мерафт, рақам гирифта шуд");
|
|
14
|
+
|
|
15
|
+
const invalidValue = z.enum(["a", "b"]).safeParse(1);
|
|
16
|
+
expect(invalidValue.error!.issues[0].code).toBe("invalid_value");
|
|
17
|
+
expect(invalidValue.error!.issues[0].message).toBe('Интихоби нодуруст: яке аз "a"|"b" интизор мерафт');
|
|
18
|
+
|
|
19
|
+
const tooBig = z.number().max(10).safeParse(15);
|
|
20
|
+
expect(tooBig.error!.issues[0].code).toBe("too_big");
|
|
21
|
+
expect(tooBig.error!.issues[0].message).toBe("Хеле калон: number бояд <=10 бошад");
|
|
22
|
+
|
|
23
|
+
const tooSmall = z.number().min(10).safeParse(5);
|
|
24
|
+
expect(tooSmall.error!.issues[0].code).toBe("too_small");
|
|
25
|
+
expect(tooSmall.error!.issues[0].message).toBe("Хеле хурд: number бояд >=10 бошад");
|
|
26
|
+
|
|
27
|
+
// singular units after numerals in Tajik
|
|
28
|
+
const tooShort = z.string().min(5).safeParse("hi");
|
|
29
|
+
expect(tooShort.error!.issues[0].message).toBe("Хеле хурд: string бояд >=5 аломат дошта бошад");
|
|
30
|
+
|
|
31
|
+
const tooFewItems = z.array(z.number()).min(2).safeParse([1]);
|
|
32
|
+
expect(tooFewItems.error!.issues[0].message).toBe("Хеле хурд: array бояд >=2 унсур дошта бошад");
|
|
33
|
+
|
|
34
|
+
const invalidEmail = z.string().email().safeParse("nope");
|
|
35
|
+
expect(invalidEmail.error!.issues[0].code).toBe("invalid_format");
|
|
36
|
+
expect(invalidEmail.error!.issues[0].message).toBe("суроғаи email-и нодуруст");
|
|
37
|
+
|
|
38
|
+
const invalidStart = z.string().startsWith("ab").safeParse("xy");
|
|
39
|
+
expect(invalidStart.error!.issues[0].message).toBe('Сатри нодуруст: бояд бо "ab" оғоз шавад');
|
|
40
|
+
|
|
41
|
+
const invalidRegex = z.string().regex(/abcd/).safeParse("xy");
|
|
42
|
+
expect(invalidRegex.error!.issues[0].message).toContain("мувофиқат кунад");
|
|
43
|
+
|
|
44
|
+
const notMultipleOf = z.number().multipleOf(5).safeParse(7);
|
|
45
|
+
expect(notMultipleOf.error!.issues[0].message).toBe("Рақами нодуруст: бояд ба 5 бе бақия тақсим шавад");
|
|
46
|
+
|
|
47
|
+
const oneUnknownKey = z.strictObject({ a: z.string() }).safeParse({ a: "x", b: 1 });
|
|
48
|
+
expect(oneUnknownKey.error!.issues[0].message).toBe('Калиди номаълум: "b"');
|
|
49
|
+
|
|
50
|
+
const twoUnknownKeys = z.strictObject({ a: z.string() }).safeParse({ a: "x", b: 1, c: 2 });
|
|
51
|
+
expect(twoUnknownKeys.error!.issues[0].message).toBe('Калидҳои номаълум: "b", "c"');
|
|
52
|
+
|
|
53
|
+
const cases: [z.ZodType, unknown, string][] = [
|
|
54
|
+
[z.literal("a"), "b", 'Вуруди нодуруст: "a" интизор мерафт'],
|
|
55
|
+
[z.boolean(), null, "Вуруди нодуруст: boolean интизор мерафт, null гирифта шуд"],
|
|
56
|
+
[z.number(), Number.NaN, "Вуруди нодуруст: рақам интизор мерафт, NaN гирифта шуд"],
|
|
57
|
+
[z.number().lt(10), 10, "Хеле калон: number бояд <10 бошад"],
|
|
58
|
+
[z.number().gt(10), 10, "Хеле хурд: number бояд >10 бошад"],
|
|
59
|
+
[z.string().max(1), "ab", "Хеле калон: string бояд <=1 аломат дошта бошад"],
|
|
60
|
+
[z.set(z.string()).min(1), new Set(), "Хеле хурд: set бояд >=1 унсур дошта бошад"],
|
|
61
|
+
[z.map(z.string(), z.number()).min(1), new Map(), "Хеле хурд: map бояд >=1 сабт дошта бошад"],
|
|
62
|
+
[z.file().min(1), new File([], "empty"), "Хеле хурд: file бояд >=1 байт дошта бошад"],
|
|
63
|
+
[z.string().endsWith("ab"), "xy", 'Сатри нодуруст: бояд бо "ab" анҷом ёбад'],
|
|
64
|
+
[z.string().includes("ab"), "xy", 'Сатри нодуруст: бояд "ab"-ро дар бар гирад'],
|
|
65
|
+
[z.stringFormat("custom", () => false), "xy", "custom-и нодуруст"],
|
|
66
|
+
[z.union([z.string(), z.number()]), null, "Вуруди нодуруст"],
|
|
67
|
+
[
|
|
68
|
+
z.discriminatedUnion("kind", [z.object({ kind: z.literal("a") }), z.object({ kind: z.literal("b") })]),
|
|
69
|
+
{ kind: "c" },
|
|
70
|
+
"Қимати нодурусти дискриминатор: 'a' | 'b' интизор мерафт",
|
|
71
|
+
],
|
|
72
|
+
[z.record(z.string().min(2), z.number()), { a: 1 }, "Калиди нодуруст дар record"],
|
|
73
|
+
[z.map(z.string(), z.number()), new Map([[{}, 1]]), "Калиди нодуруст дар map"],
|
|
74
|
+
[z.map(z.object({}), z.number()), new Map([[{}, "a"]]), "Қимати нодуруст дар map"],
|
|
75
|
+
[z.custom(() => false), "a", "Вуруди нодуруст"],
|
|
76
|
+
];
|
|
77
|
+
for (const [schema, input, message] of cases) {
|
|
78
|
+
expect(schema.safeParse(input).error!.issues[0].message).toBe(message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
expect(z.object({ name: z.string().min(1), age: z.number().min(0) }).parse({ name: "Ali", age: 30 })).toEqual({
|
|
82
|
+
name: "Ali",
|
|
83
|
+
age: 30,
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/v4/core/versions.ts
CHANGED
package/src/v4/locales/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export { default as sk } from "./sk.js";
|
|
|
49
49
|
export { default as sl } from "./sl.js";
|
|
50
50
|
export { default as sv } from "./sv.js";
|
|
51
51
|
export { default as ta } from "./ta.js";
|
|
52
|
+
export { default as tg } from "./tg.js";
|
|
52
53
|
export { default as th } from "./th.js";
|
|
53
54
|
export { default as tk } from "./tk.js";
|
|
54
55
|
export { default as tr } from "./tr.js";
|