zod 4.2.0-canary.20250825T235836 → 4.2.0-canary.20250826T000512

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.2.0-canary.20250825T235836",
3
+ "version": "4.2.0-canary.20250826T000512",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -735,6 +735,95 @@ describe("toJSONSchema", () => {
735
735
  `);
736
736
  });
737
737
 
738
+ test("tuple draft-7", () => {
739
+ const schema = z.tuple([z.string(), z.number()]);
740
+ expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
741
+ {
742
+ "$schema": "http://json-schema.org/draft-07/schema#",
743
+ "items": [
744
+ {
745
+ "type": "string",
746
+ },
747
+ {
748
+ "type": "number",
749
+ },
750
+ ],
751
+ "type": "array",
752
+ }
753
+ `);
754
+ });
755
+
756
+ test("tuple with rest draft-7", () => {
757
+ const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
758
+ expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
759
+ {
760
+ "$schema": "http://json-schema.org/draft-07/schema#",
761
+ "additionalItems": {
762
+ "type": "boolean",
763
+ },
764
+ "items": [
765
+ {
766
+ "type": "string",
767
+ },
768
+ {
769
+ "type": "number",
770
+ },
771
+ ],
772
+ "type": "array",
773
+ }
774
+ `);
775
+ });
776
+
777
+ test("tuple with rest draft-7 - issue #5151 regression test", () => {
778
+ // This test addresses issue #5151: tuple with rest elements and ids
779
+ // in draft-7 had incorrect internal path handling affecting complex scenarios
780
+ const primarySchema = z.string().meta({ id: "primary" });
781
+ const restSchema = z.number().meta({ id: "rest" });
782
+ const testSchema = z.tuple([primarySchema], restSchema);
783
+
784
+ // Test both final output structure AND internal path handling
785
+ const capturedPaths: string[] = [];
786
+ const result = z.toJSONSchema(testSchema, {
787
+ target: "draft-7",
788
+ override: (ctx) => capturedPaths.push(ctx.path.join("/")),
789
+ });
790
+
791
+ // Verify correct draft-7 structure with metadata extraction
792
+ expect(result).toMatchInlineSnapshot(`
793
+ {
794
+ "$schema": "http://json-schema.org/draft-07/schema#",
795
+ "additionalItems": {
796
+ "$ref": "#/definitions/rest",
797
+ },
798
+ "definitions": {
799
+ "primary": {
800
+ "id": "primary",
801
+ "type": "string",
802
+ },
803
+ "rest": {
804
+ "id": "rest",
805
+ "type": "number",
806
+ },
807
+ },
808
+ "items": [
809
+ {
810
+ "$ref": "#/definitions/primary",
811
+ },
812
+ ],
813
+ "type": "array",
814
+ }
815
+ `);
816
+
817
+ // Verify internal paths are correct (this was the actual bug)
818
+ expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
819
+ expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
820
+ expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths
821
+
822
+ // Structural validations
823
+ expect(Array.isArray(result.items)).toBe(true);
824
+ expect(result.additionalItems).toBeDefined();
825
+ });
826
+
738
827
  test("promise", () => {
739
828
  const schema = z.promise(z.string());
740
829
  expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
@@ -1558,7 +1647,9 @@ test("unrepresentable literal values are ignored", () => {
1558
1647
  }
1559
1648
  `);
1560
1649
 
1561
- const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), { unrepresentable: "any" });
1650
+ const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
1651
+ unrepresentable: "any",
1652
+ });
1562
1653
  expect(b).toMatchInlineSnapshot(`
1563
1654
  {
1564
1655
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1570,7 +1661,9 @@ test("unrepresentable literal values are ignored", () => {
1570
1661
  }
1571
1662
  `);
1572
1663
 
1573
- const c = z.z.toJSONSchema(z.literal([undefined]), { unrepresentable: "any" });
1664
+ const c = z.z.toJSONSchema(z.literal([undefined]), {
1665
+ unrepresentable: "any",
1666
+ });
1574
1667
  expect(c).toMatchInlineSnapshot(`
1575
1668
  {
1576
1669
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1853,7 +1946,9 @@ test("basic registry", () => {
1853
1946
  myRegistry.add(User, { id: "User" });
1854
1947
  myRegistry.add(Post, { id: "Post" });
1855
1948
 
1856
- const result = z.z.toJSONSchema(myRegistry, { uri: (id) => `https://example.com/${id}.json` });
1949
+ const result = z.z.toJSONSchema(myRegistry, {
1950
+ uri: (id) => `https://example.com/${id}.json`,
1951
+ });
1857
1952
  expect(result).toMatchInlineSnapshot(`
1858
1953
  {
1859
1954
  "schemas": {
@@ -368,13 +368,21 @@ export class JSONSchemaGenerator {
368
368
  case "tuple": {
369
369
  const json: JSONSchema.ArraySchema = _json as any;
370
370
  json.type = "array";
371
+
372
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
373
+ const restPath =
374
+ this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
375
+
371
376
  const prefixItems = def.items.map((x, i) =>
372
- this.process(x, { ...params, path: [...params.path, "prefixItems", i] })
377
+ this.process(x, {
378
+ ...params,
379
+ path: [...params.path, prefixPath, i],
380
+ })
373
381
  );
374
382
  const rest = def.rest
375
383
  ? this.process(def.rest, {
376
384
  ...params,
377
- path: [...params.path, "items"],
385
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
378
386
  })
379
387
  : null;
380
388
 
@@ -288,11 +288,16 @@ class JSONSchemaGenerator {
288
288
  case "tuple": {
289
289
  const json = _json;
290
290
  json.type = "array";
291
- const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
291
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
292
+ const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
293
+ const prefixItems = def.items.map((x, i) => this.process(x, {
294
+ ...params,
295
+ path: [...params.path, prefixPath, i],
296
+ }));
292
297
  const rest = def.rest
293
298
  ? this.process(def.rest, {
294
299
  ...params,
295
- path: [...params.path, "items"],
300
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
296
301
  })
297
302
  : null;
298
303
  if (this.target === "draft-2020-12") {
@@ -284,11 +284,16 @@ export class JSONSchemaGenerator {
284
284
  case "tuple": {
285
285
  const json = _json;
286
286
  json.type = "array";
287
- const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
287
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
288
+ const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
289
+ const prefixItems = def.items.map((x, i) => this.process(x, {
290
+ ...params,
291
+ path: [...params.path, prefixPath, i],
292
+ }));
288
293
  const rest = def.rest
289
294
  ? this.process(def.rest, {
290
295
  ...params,
291
- path: [...params.path, "items"],
296
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
292
297
  })
293
298
  : null;
294
299
  if (this.target === "draft-2020-12") {