zod 4.2.0-canary.20251118T054932 → 4.2.0-canary.20251118T055142
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/registries.test.ts +4 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +48 -0
- package/src/v4/core/registries.ts +12 -1
- package/src/v4/core/to-json-schema.ts +9 -2
- package/v4/core/registries.cjs +3 -1
- package/v4/core/registries.js +3 -1
- package/v4/core/to-json-schema.cjs +10 -2
- package/v4/core/to-json-schema.js +10 -2
package/package.json
CHANGED
|
@@ -19,6 +19,10 @@ test("globalRegistry", () => {
|
|
|
19
19
|
expect(z.globalRegistry.has(a)).toEqual(false);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
test("globalRegistry is singleton and attached to globalThis", () => {
|
|
23
|
+
expect(z.globalRegistry).toBe((globalThis as any).__zod_globalRegistry);
|
|
24
|
+
});
|
|
25
|
+
|
|
22
26
|
test("z.registry", () => {
|
|
23
27
|
const fieldRegistry = z.registry<{ name: string; description: string }>();
|
|
24
28
|
|
|
@@ -706,6 +706,54 @@ describe("toJSONSchema", () => {
|
|
|
706
706
|
`);
|
|
707
707
|
});
|
|
708
708
|
|
|
709
|
+
test("discriminated unions", () => {
|
|
710
|
+
const schema = z.discriminatedUnion("type", [
|
|
711
|
+
z.object({ type: z.literal("success"), data: z.string() }),
|
|
712
|
+
z.object({ type: z.literal("error"), message: z.string() }),
|
|
713
|
+
]);
|
|
714
|
+
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
715
|
+
{
|
|
716
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
717
|
+
"oneOf": [
|
|
718
|
+
{
|
|
719
|
+
"additionalProperties": false,
|
|
720
|
+
"properties": {
|
|
721
|
+
"data": {
|
|
722
|
+
"type": "string",
|
|
723
|
+
},
|
|
724
|
+
"type": {
|
|
725
|
+
"const": "success",
|
|
726
|
+
"type": "string",
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
"required": [
|
|
730
|
+
"type",
|
|
731
|
+
"data",
|
|
732
|
+
],
|
|
733
|
+
"type": "object",
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
"additionalProperties": false,
|
|
737
|
+
"properties": {
|
|
738
|
+
"message": {
|
|
739
|
+
"type": "string",
|
|
740
|
+
},
|
|
741
|
+
"type": {
|
|
742
|
+
"const": "error",
|
|
743
|
+
"type": "string",
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
"required": [
|
|
747
|
+
"type",
|
|
748
|
+
"message",
|
|
749
|
+
],
|
|
750
|
+
"type": "object",
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
}
|
|
754
|
+
`);
|
|
755
|
+
});
|
|
756
|
+
|
|
709
757
|
test("intersections", () => {
|
|
710
758
|
const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));
|
|
711
759
|
|
|
@@ -94,4 +94,15 @@ export function registry<T extends MetadataType = MetadataType, S extends $ZodTy
|
|
|
94
94
|
return new $ZodRegistry<T, S>();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
interface GlobalThisWithRegistry {
|
|
98
|
+
/**
|
|
99
|
+
* The globalRegistry instance shared across both CommonJS and ESM builds.
|
|
100
|
+
* By attaching the registry to `globalThis`, this property ensures a single, deduplicated instance
|
|
101
|
+
* is used regardless of whether the package is loaded via `require` (CJS) or `import` (ESM).
|
|
102
|
+
* This prevents dual package hazards and keeps registry state consistent.
|
|
103
|
+
*/
|
|
104
|
+
__zod_globalRegistry?: $ZodRegistry<GlobalMeta>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
(globalThis as GlobalThisWithRegistry).__zod_globalRegistry ??= registry<GlobalMeta>();
|
|
108
|
+
export const globalRegistry: $ZodRegistry<GlobalMeta> = (globalThis as GlobalThisWithRegistry).__zod_globalRegistry!;
|
|
@@ -330,13 +330,20 @@ export class JSONSchemaGenerator {
|
|
|
330
330
|
}
|
|
331
331
|
case "union": {
|
|
332
332
|
const json: JSONSchema.BaseSchema = _json as any;
|
|
333
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
334
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
335
|
+
const isDiscriminated = (def as any).discriminator !== undefined;
|
|
333
336
|
const options = def.options.map((x, i) =>
|
|
334
337
|
this.process(x, {
|
|
335
338
|
...params,
|
|
336
|
-
path: [...params.path, "anyOf", i],
|
|
339
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
337
340
|
})
|
|
338
341
|
);
|
|
339
|
-
|
|
342
|
+
if (isDiscriminated) {
|
|
343
|
+
json.oneOf = options;
|
|
344
|
+
} else {
|
|
345
|
+
json.anyOf = options;
|
|
346
|
+
}
|
|
340
347
|
break;
|
|
341
348
|
}
|
|
342
349
|
case "intersection": {
|
package/v4/core/registries.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var _a;
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.globalRegistry = exports.$ZodRegistry = exports.$input = exports.$output = void 0;
|
|
4
5
|
exports.registry = registry;
|
|
@@ -54,4 +55,5 @@ exports.$ZodRegistry = $ZodRegistry;
|
|
|
54
55
|
function registry() {
|
|
55
56
|
return new $ZodRegistry();
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
59
|
+
exports.globalRegistry = globalThis.__zod_globalRegistry;
|
package/v4/core/registries.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
export const $output = Symbol("ZodOutput");
|
|
2
3
|
export const $input = Symbol("ZodInput");
|
|
3
4
|
export class $ZodRegistry {
|
|
@@ -49,4 +50,5 @@ export class $ZodRegistry {
|
|
|
49
50
|
export function registry() {
|
|
50
51
|
return new $ZodRegistry();
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
+
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
54
|
+
export const globalRegistry = globalThis.__zod_globalRegistry;
|
|
@@ -252,11 +252,19 @@ class JSONSchemaGenerator {
|
|
|
252
252
|
}
|
|
253
253
|
case "union": {
|
|
254
254
|
const json = _json;
|
|
255
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
256
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
257
|
+
const isDiscriminated = def.discriminator !== undefined;
|
|
255
258
|
const options = def.options.map((x, i) => this.process(x, {
|
|
256
259
|
...params,
|
|
257
|
-
path: [...params.path, "anyOf", i],
|
|
260
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
258
261
|
}));
|
|
259
|
-
|
|
262
|
+
if (isDiscriminated) {
|
|
263
|
+
json.oneOf = options;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
json.anyOf = options;
|
|
267
|
+
}
|
|
260
268
|
break;
|
|
261
269
|
}
|
|
262
270
|
case "intersection": {
|
|
@@ -248,11 +248,19 @@ export class JSONSchemaGenerator {
|
|
|
248
248
|
}
|
|
249
249
|
case "union": {
|
|
250
250
|
const json = _json;
|
|
251
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
252
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
253
|
+
const isDiscriminated = def.discriminator !== undefined;
|
|
251
254
|
const options = def.options.map((x, i) => this.process(x, {
|
|
252
255
|
...params,
|
|
253
|
-
path: [...params.path, "anyOf", i],
|
|
256
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
254
257
|
}));
|
|
255
|
-
|
|
258
|
+
if (isDiscriminated) {
|
|
259
|
+
json.oneOf = options;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
json.anyOf = options;
|
|
263
|
+
}
|
|
256
264
|
break;
|
|
257
265
|
}
|
|
258
266
|
case "intersection": {
|