zod 4.6.2 → 4.6.4

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.
Files changed (267) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/v4/classic/checks.ts +1 -0
  4. package/src/v4/classic/schemas.ts +12 -29
  5. package/src/v4/classic/tests/assignability.test.ts +1 -0
  6. package/src/v4/classic/tests/cyclic-data.test.ts +2 -11
  7. package/src/v4/classic/tests/firstparty.test.ts +0 -4
  8. package/src/v4/classic/tests/instance-footprint.test.ts +22 -0
  9. package/src/v4/classic/tests/instanceof.test.ts +1 -1
  10. package/src/v4/classic/tests/number.test.ts +39 -0
  11. package/src/v4/classic/tests/properties.test.ts +34 -137
  12. package/src/v4/classic/tests/string.test.ts +70 -1
  13. package/src/v4/classic/tests/to-json-schema.test.ts +87 -35
  14. package/src/v4/core/api.ts +21 -29
  15. package/src/v4/core/checks.ts +61 -1
  16. package/src/v4/core/compile.ts +35 -24
  17. package/src/v4/core/core.ts +1 -3
  18. package/src/v4/core/json-schema-processors.ts +9 -63
  19. package/src/v4/core/memoizer.ts +2 -5
  20. package/src/v4/core/regexes.ts +4 -0
  21. package/src/v4/core/schemas.ts +36 -109
  22. package/src/v4/core/tests/compile-differential.test.ts +35 -0
  23. package/src/v4/core/tests/compile.test.ts +177 -0
  24. package/src/v4/core/tests/url-no-canparse.test.ts +118 -16
  25. package/src/v4/core/to-json-schema.ts +1 -1
  26. package/src/v4/core/util.ts +0 -4
  27. package/src/v4/core/versions.ts +1 -1
  28. package/src/v4/core/visit.ts +0 -11
  29. package/src/v4/locales/ar.ts +1 -0
  30. package/src/v4/locales/az.ts +1 -0
  31. package/src/v4/locales/be.ts +1 -0
  32. package/src/v4/locales/bg.ts +1 -0
  33. package/src/v4/locales/bn.ts +1 -0
  34. package/src/v4/locales/ca.ts +1 -0
  35. package/src/v4/locales/ckb.ts +1 -0
  36. package/src/v4/locales/cs.ts +1 -0
  37. package/src/v4/locales/da.ts +1 -0
  38. package/src/v4/locales/de.ts +1 -0
  39. package/src/v4/locales/el.ts +1 -0
  40. package/src/v4/locales/en.ts +1 -0
  41. package/src/v4/locales/eo.ts +1 -0
  42. package/src/v4/locales/es.ts +1 -0
  43. package/src/v4/locales/fa.ts +1 -0
  44. package/src/v4/locales/fi.ts +1 -0
  45. package/src/v4/locales/fr-CA.ts +1 -0
  46. package/src/v4/locales/fr.ts +1 -0
  47. package/src/v4/locales/gu.ts +1 -0
  48. package/src/v4/locales/he.ts +1 -0
  49. package/src/v4/locales/hi.ts +1 -0
  50. package/src/v4/locales/hr.ts +1 -0
  51. package/src/v4/locales/hu.ts +1 -0
  52. package/src/v4/locales/hy.ts +1 -0
  53. package/src/v4/locales/id.ts +1 -0
  54. package/src/v4/locales/is.ts +1 -0
  55. package/src/v4/locales/it.ts +1 -0
  56. package/src/v4/locales/ja.ts +1 -0
  57. package/src/v4/locales/ka.ts +1 -0
  58. package/src/v4/locales/km.ts +1 -0
  59. package/src/v4/locales/kn.ts +1 -0
  60. package/src/v4/locales/ko.ts +1 -0
  61. package/src/v4/locales/lt.ts +1 -0
  62. package/src/v4/locales/mk.ts +1 -0
  63. package/src/v4/locales/ms.ts +1 -0
  64. package/src/v4/locales/ne.ts +1 -0
  65. package/src/v4/locales/nl.ts +1 -0
  66. package/src/v4/locales/nn.ts +1 -0
  67. package/src/v4/locales/no.ts +1 -0
  68. package/src/v4/locales/ota.ts +1 -0
  69. package/src/v4/locales/pl.ts +1 -0
  70. package/src/v4/locales/ps.ts +1 -0
  71. package/src/v4/locales/pt-BR.ts +1 -0
  72. package/src/v4/locales/pt.ts +1 -0
  73. package/src/v4/locales/ro.ts +1 -0
  74. package/src/v4/locales/ru.ts +1 -0
  75. package/src/v4/locales/sk.ts +1 -0
  76. package/src/v4/locales/sl.ts +1 -0
  77. package/src/v4/locales/sv.ts +1 -0
  78. package/src/v4/locales/ta.ts +1 -0
  79. package/src/v4/locales/tg.ts +1 -0
  80. package/src/v4/locales/th.ts +1 -0
  81. package/src/v4/locales/tk.ts +1 -0
  82. package/src/v4/locales/tr.ts +1 -0
  83. package/src/v4/locales/uk.ts +1 -0
  84. package/src/v4/locales/ur.ts +1 -0
  85. package/src/v4/locales/uz.ts +1 -0
  86. package/src/v4/locales/vi.ts +1 -0
  87. package/src/v4/locales/yo.ts +1 -0
  88. package/src/v4/locales/zh-CN.ts +1 -0
  89. package/src/v4/locales/zh-TW.ts +1 -0
  90. package/src/v4/mini/checks.ts +1 -0
  91. package/src/v4/mini/schemas.ts +12 -23
  92. package/src/v4/mini/tests/index.test.ts +56 -0
  93. package/src/v4/mini/tests/string.test.ts +7 -0
  94. package/v4/classic/checks.cjs +2 -1
  95. package/v4/classic/checks.d.cts +1 -1
  96. package/v4/classic/checks.d.ts +1 -1
  97. package/v4/classic/checks.js +1 -1
  98. package/v4/classic/schemas.cjs +11 -15
  99. package/v4/classic/schemas.d.cts +2 -6
  100. package/v4/classic/schemas.d.ts +2 -6
  101. package/v4/classic/schemas.js +9 -13
  102. package/v4/core/api.cjs +11 -23
  103. package/v4/core/api.d.cts +8 -4
  104. package/v4/core/api.d.ts +8 -4
  105. package/v4/core/api.js +11 -23
  106. package/v4/core/checks.cjs +32 -1
  107. package/v4/core/checks.d.cts +16 -0
  108. package/v4/core/checks.d.ts +16 -0
  109. package/v4/core/checks.js +31 -0
  110. package/v4/core/compile.cjs +12 -17
  111. package/v4/core/compile.js +13 -18
  112. package/v4/core/core.cjs +1 -1
  113. package/v4/core/core.js +1 -1
  114. package/v4/core/json-schema-processors.cjs +8 -44
  115. package/v4/core/json-schema-processors.d.cts +0 -1
  116. package/v4/core/json-schema-processors.d.ts +0 -1
  117. package/v4/core/json-schema-processors.js +8 -43
  118. package/v4/core/memoizer.cjs +2 -5
  119. package/v4/core/memoizer.js +2 -5
  120. package/v4/core/regexes.cjs +4 -2
  121. package/v4/core/regexes.d.cts +1 -0
  122. package/v4/core/regexes.d.ts +1 -0
  123. package/v4/core/regexes.js +2 -0
  124. package/v4/core/schemas.cjs +33 -73
  125. package/v4/core/schemas.d.cts +7 -22
  126. package/v4/core/schemas.d.ts +7 -22
  127. package/v4/core/schemas.js +30 -72
  128. package/v4/core/to-json-schema.cjs +0 -1
  129. package/v4/core/to-json-schema.d.cts +0 -3
  130. package/v4/core/to-json-schema.d.ts +0 -3
  131. package/v4/core/to-json-schema.js +1 -1
  132. package/v4/core/util.cjs +0 -4
  133. package/v4/core/util.js +0 -4
  134. package/v4/core/versions.cjs +1 -1
  135. package/v4/core/versions.js +1 -1
  136. package/v4/core/visit.cjs +0 -12
  137. package/v4/core/visit.js +0 -12
  138. package/v4/locales/ar.cjs +1 -0
  139. package/v4/locales/ar.js +1 -0
  140. package/v4/locales/az.cjs +1 -0
  141. package/v4/locales/az.js +1 -0
  142. package/v4/locales/be.cjs +1 -0
  143. package/v4/locales/be.js +1 -0
  144. package/v4/locales/bg.cjs +1 -0
  145. package/v4/locales/bg.js +1 -0
  146. package/v4/locales/bn.cjs +1 -0
  147. package/v4/locales/bn.js +1 -0
  148. package/v4/locales/ca.cjs +1 -0
  149. package/v4/locales/ca.js +1 -0
  150. package/v4/locales/ckb.cjs +1 -0
  151. package/v4/locales/ckb.js +1 -0
  152. package/v4/locales/cs.cjs +1 -0
  153. package/v4/locales/cs.js +1 -0
  154. package/v4/locales/da.cjs +1 -0
  155. package/v4/locales/da.js +1 -0
  156. package/v4/locales/de.cjs +1 -0
  157. package/v4/locales/de.js +1 -0
  158. package/v4/locales/el.cjs +1 -0
  159. package/v4/locales/el.js +1 -0
  160. package/v4/locales/en.cjs +1 -0
  161. package/v4/locales/en.js +1 -0
  162. package/v4/locales/eo.cjs +1 -0
  163. package/v4/locales/eo.js +1 -0
  164. package/v4/locales/es.cjs +1 -0
  165. package/v4/locales/es.js +1 -0
  166. package/v4/locales/fa.cjs +1 -0
  167. package/v4/locales/fa.js +1 -0
  168. package/v4/locales/fi.cjs +1 -0
  169. package/v4/locales/fi.js +1 -0
  170. package/v4/locales/fr-CA.cjs +1 -0
  171. package/v4/locales/fr-CA.js +1 -0
  172. package/v4/locales/fr.cjs +1 -0
  173. package/v4/locales/fr.js +1 -0
  174. package/v4/locales/gu.cjs +1 -0
  175. package/v4/locales/gu.js +1 -0
  176. package/v4/locales/he.cjs +1 -0
  177. package/v4/locales/he.js +1 -0
  178. package/v4/locales/hi.cjs +1 -0
  179. package/v4/locales/hi.js +1 -0
  180. package/v4/locales/hr.cjs +1 -0
  181. package/v4/locales/hr.js +1 -0
  182. package/v4/locales/hu.cjs +1 -0
  183. package/v4/locales/hu.js +1 -0
  184. package/v4/locales/hy.cjs +1 -0
  185. package/v4/locales/hy.js +1 -0
  186. package/v4/locales/id.cjs +1 -0
  187. package/v4/locales/id.js +1 -0
  188. package/v4/locales/is.cjs +1 -0
  189. package/v4/locales/is.js +1 -0
  190. package/v4/locales/it.cjs +1 -0
  191. package/v4/locales/it.js +1 -0
  192. package/v4/locales/ja.cjs +1 -0
  193. package/v4/locales/ja.js +1 -0
  194. package/v4/locales/ka.cjs +1 -0
  195. package/v4/locales/ka.js +1 -0
  196. package/v4/locales/km.cjs +1 -0
  197. package/v4/locales/km.js +1 -0
  198. package/v4/locales/kn.cjs +1 -0
  199. package/v4/locales/kn.js +1 -0
  200. package/v4/locales/ko.cjs +1 -0
  201. package/v4/locales/ko.js +1 -0
  202. package/v4/locales/lt.cjs +1 -0
  203. package/v4/locales/lt.js +1 -0
  204. package/v4/locales/mk.cjs +1 -0
  205. package/v4/locales/mk.js +1 -0
  206. package/v4/locales/ms.cjs +1 -0
  207. package/v4/locales/ms.js +1 -0
  208. package/v4/locales/ne.cjs +1 -0
  209. package/v4/locales/ne.js +1 -0
  210. package/v4/locales/nl.cjs +1 -0
  211. package/v4/locales/nl.js +1 -0
  212. package/v4/locales/nn.cjs +1 -0
  213. package/v4/locales/nn.js +1 -0
  214. package/v4/locales/no.cjs +1 -0
  215. package/v4/locales/no.js +1 -0
  216. package/v4/locales/ota.cjs +1 -0
  217. package/v4/locales/ota.js +1 -0
  218. package/v4/locales/pl.cjs +1 -0
  219. package/v4/locales/pl.js +1 -0
  220. package/v4/locales/ps.cjs +1 -0
  221. package/v4/locales/ps.js +1 -0
  222. package/v4/locales/pt-BR.cjs +1 -0
  223. package/v4/locales/pt-BR.js +1 -0
  224. package/v4/locales/pt.cjs +1 -0
  225. package/v4/locales/pt.js +1 -0
  226. package/v4/locales/ro.cjs +1 -0
  227. package/v4/locales/ro.js +1 -0
  228. package/v4/locales/ru.cjs +1 -0
  229. package/v4/locales/ru.js +1 -0
  230. package/v4/locales/sk.cjs +1 -0
  231. package/v4/locales/sk.js +1 -0
  232. package/v4/locales/sl.cjs +1 -0
  233. package/v4/locales/sl.js +1 -0
  234. package/v4/locales/sv.cjs +1 -0
  235. package/v4/locales/sv.js +1 -0
  236. package/v4/locales/ta.cjs +1 -0
  237. package/v4/locales/ta.js +1 -0
  238. package/v4/locales/tg.cjs +1 -0
  239. package/v4/locales/tg.js +1 -0
  240. package/v4/locales/th.cjs +1 -0
  241. package/v4/locales/th.js +1 -0
  242. package/v4/locales/tk.cjs +1 -0
  243. package/v4/locales/tk.js +1 -0
  244. package/v4/locales/tr.cjs +1 -0
  245. package/v4/locales/tr.js +1 -0
  246. package/v4/locales/uk.cjs +1 -0
  247. package/v4/locales/uk.js +1 -0
  248. package/v4/locales/ur.cjs +1 -0
  249. package/v4/locales/ur.js +1 -0
  250. package/v4/locales/uz.cjs +1 -0
  251. package/v4/locales/uz.js +1 -0
  252. package/v4/locales/vi.cjs +1 -0
  253. package/v4/locales/vi.js +1 -0
  254. package/v4/locales/yo.cjs +1 -0
  255. package/v4/locales/yo.js +1 -0
  256. package/v4/locales/zh-CN.cjs +1 -0
  257. package/v4/locales/zh-CN.js +1 -0
  258. package/v4/locales/zh-TW.cjs +1 -0
  259. package/v4/locales/zh-TW.js +1 -0
  260. package/v4/mini/checks.cjs +2 -1
  261. package/v4/mini/checks.d.cts +1 -1
  262. package/v4/mini/checks.d.ts +1 -1
  263. package/v4/mini/checks.js +1 -1
  264. package/v4/mini/schemas.cjs +11 -13
  265. package/v4/mini/schemas.d.cts +1 -4
  266. package/v4/mini/schemas.d.ts +1 -4
  267. package/v4/mini/schemas.js +9 -11
@@ -1,5 +1,5 @@
1
1
  import { Validator } from "@seriousme/openapi-schema-validator";
2
- import { describe, expect, test } from "vitest";
2
+ import { describe, expect, test, vi } from "vitest";
3
3
  import * as z from "zod";
4
4
  // import * as zCore from "zod/v4/core";
5
5
 
@@ -3235,11 +3235,31 @@ test("basic registry", () => {
3235
3235
  `);
3236
3236
  });
3237
3237
 
3238
- test("large registry converts in linear time", () => {
3239
- const count = 2000;
3240
- const convert = (withIntersection: boolean) => {
3238
+ function countMapScanWork<T>(convert: () => T): { result: T; work: number } {
3239
+ const entries = Map.prototype.entries;
3240
+ const values = Map.prototype.values;
3241
+ let work = 0;
3242
+ const entriesSpy = vi.spyOn(Map.prototype, "entries").mockImplementation(function (this: Map<unknown, unknown>) {
3243
+ work += this.size;
3244
+ return entries.call(this);
3245
+ } as typeof Map.prototype.entries);
3246
+ const valuesSpy = vi.spyOn(Map.prototype, "values").mockImplementation(function (this: Map<unknown, unknown>) {
3247
+ work += this.size;
3248
+ return values.call(this);
3249
+ } as typeof Map.prototype.values);
3250
+ try {
3251
+ return { result: convert(), work };
3252
+ } finally {
3253
+ entriesSpy.mockRestore();
3254
+ valuesSpy.mockRestore();
3255
+ }
3256
+ }
3257
+
3258
+ test("large registry conversion performs linear map-scan work", () => {
3259
+ const count = 64;
3260
+ const convert = (size: number, withIntersection: boolean) => {
3241
3261
  const registry = z.registry<{ id: string }>();
3242
- for (let i = 0; i < count; i++) {
3262
+ for (let i = 0; i < size; i++) {
3243
3263
  registry.add(
3244
3264
  z.object({ id: z.string(), name: z.string(), count: z.number(), nested: z.object({ a: z.boolean() }) }),
3245
3265
  { id: `Type${i}` }
@@ -3247,55 +3267,53 @@ test("large registry converts in linear time", () => {
3247
3267
  }
3248
3268
  if (withIntersection) registry.add(z.object({ a: z.string() }).and(z.object({ b: z.string() })), { id: "Inter" });
3249
3269
 
3250
- const start = performance.now();
3251
- const { schemas } = z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` });
3252
- return { schemas, elapsed: performance.now() - start };
3270
+ return countMapScanWork(() => z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` }));
3253
3271
  };
3254
3272
 
3255
- const plain = convert(false);
3256
- expect(Object.keys(plain.schemas)).toHaveLength(count);
3257
- expect(plain.schemas.Type0).toMatchObject({
3273
+ const plain = convert(count, false);
3274
+ const plainDouble = convert(count * 2, false);
3275
+ expect(Object.keys(plain.result.schemas)).toHaveLength(count);
3276
+ expect(plain.result.schemas.Type0).toMatchObject({
3258
3277
  $id: "https://example.com/Type0.json",
3259
3278
  type: "object",
3260
3279
  properties: { nested: { type: "object" } },
3261
3280
  });
3262
- expect(plain.schemas[`Type${count - 1}`]!.$id).toBe(`https://example.com/Type${count - 1}.json`);
3263
-
3264
- // The whole-map passes in extractDefs/finalize used to re-run once per registered schema, which made this quadratic: ~9s of CPU at this size before the passes were hoisted, ~50ms after.
3265
- expect(plain.elapsed).toBeLessThan(5000);
3281
+ expect(plain.result.schemas[`Type${count - 1}`]!.$id).toBe(`https://example.com/Type${count - 1}.json`);
3282
+ expect(plain.work).toBeGreaterThan(0);
3283
+ expect(plainDouble.work).toBeLessThanOrEqual(plain.work * 2);
3266
3284
 
3267
- // The intersection fold walks the whole map as well, so it has to run inside the same guard. Hoisting it back out costs ~10x at this size. Comparing the two conversions rather than asserting a fixed budget keeps this independent of how fast the machine is.
3268
- const folded = convert(true);
3269
- expect(folded.schemas.Inter).toMatchObject({ type: "object", properties: { a: {}, b: {} } });
3270
- expect(folded.elapsed).toBeLessThan(plain.elapsed * 4 + 100);
3285
+ const folded = convert(count, true);
3286
+ const foldedDouble = convert(count * 2, true);
3287
+ expect(folded.result.schemas.Inter).toMatchObject({ type: "object", properties: { a: {}, b: {} } });
3288
+ expect(folded.work).toBeGreaterThan(0);
3289
+ expect(foldedDouble.work).toBeLessThanOrEqual(folded.work * 2);
3271
3290
  });
3272
3291
 
3273
- test("a registry of records with numeric keys converts in linear time", () => {
3274
- const count = 2000;
3275
- const convert = (key: (i: number) => z.core.$ZodType) => {
3292
+ test("a registry of records with numeric keys performs linear map-scan work", () => {
3293
+ const count = 64;
3294
+ const convert = (size: number, key: (i: number) => z.core.$ZodType) => {
3276
3295
  const registry = z.registry<{ id: string }>();
3277
- for (let i = 0; i < count; i++) {
3296
+ for (let i = 0; i < size; i++) {
3278
3297
  registry.add(z.object({ m: z.record(key(i) as z.core.$ZodRecordKey, z.boolean()) }), { id: `Type${i}` });
3279
3298
  }
3280
- const start = performance.now();
3281
- const { schemas } = z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` });
3282
- return { schemas, elapsed: performance.now() - start };
3299
+ return countMapScanWork(() => z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` }));
3283
3300
  };
3284
3301
 
3285
- const string = convert(() => z.string());
3286
- const numeric = convert(() => z.number());
3287
- expect(numeric.schemas.Type0).toMatchObject({
3302
+ const numeric = convert(count, () => z.number());
3303
+ const numericDouble = convert(count * 2, () => z.number());
3304
+ expect(numeric.result.schemas.Type0).toMatchObject({
3288
3305
  properties: { m: { propertyNames: { type: "string", pattern: "^-?\\d+(?:\\.\\d+)?$" } } },
3289
3306
  });
3307
+ expect(numeric.work).toBeGreaterThan(0);
3308
+ expect(numericDouble.work).toBeLessThanOrEqual(numeric.work * 2);
3290
3309
 
3291
- // The rewrite has to find every carrier the flatten copied `propertyNames` onto, which means a pass over the whole seen map. Running that once per record rather than once per conversion cost ~10x at this size. A string key needs no rewrite at all, so comparing against it keeps this independent of how fast the machine is.
3292
- expect(numeric.elapsed).toBeLessThan(string.elapsed * 2 + 50);
3293
-
3294
- // An extracted key resolves through a map built once per conversion rather than a search per reference. There is no stable timing control for that — extraction has its own $defs cost, which swamps the difference — so this only pins the shape.
3295
- const extracted = convert((i) => z.number().meta({ id: `Key${i}` }));
3296
- expect(extracted.schemas.Type0).toMatchObject({
3310
+ const extracted = convert(count, (i) => z.number().meta({ id: `Key${i}` }));
3311
+ const extractedDouble = convert(count * 2, (i) => z.number().meta({ id: `Key${i}` }));
3312
+ expect(extracted.result.schemas.Type0).toMatchObject({
3297
3313
  properties: { m: { propertyNames: { type: "string", pattern: "^-?\\d+(?:\\.\\d+)?$" } } },
3298
3314
  });
3315
+ expect(extracted.work).toBeGreaterThan(0);
3316
+ expect(extractedDouble.work).toBeLessThanOrEqual(extracted.work * 2);
3299
3317
  });
3300
3318
 
3301
3319
  test("registry extracts unregistered subschemas into __shared", () => {
@@ -3849,6 +3867,31 @@ test("input JSON schema resolves requiredness past transform and catch wrappers"
3849
3867
  expect(required(z.object({ a: z.transform((v: unknown) => String(v)).pipe(z.string()) }))).toEqual(["a"]);
3850
3868
  });
3851
3869
 
3870
+ test("object required keys preserve shape order and direction", () => {
3871
+ const symbol = Symbol("symbol");
3872
+ const shape: z.ZodRawShape = {
3873
+ 10: z.string(),
3874
+ 2: z.string().optional(),
3875
+ plain: z.string(),
3876
+ optional: z.string().optional(),
3877
+ defaulted: z.string().default("x"),
3878
+ caught: z.string().catch("x"),
3879
+ [symbol]: z.string(),
3880
+ };
3881
+ Object.defineProperty(shape, "__proto__", { value: z.string(), enumerable: true });
3882
+ Object.defineProperty(shape, "hidden", { value: z.string(), enumerable: false });
3883
+
3884
+ const input = z.toJSONSchema(z.object(shape), { io: "input", unrepresentable: "any" });
3885
+ const output = z.toJSONSchema(z.object(shape), { io: "output", unrepresentable: "any" });
3886
+
3887
+ expect(Object.keys(input.properties!)).toEqual(["2", "10", "plain", "optional", "defaulted", "caught", "__proto__"]);
3888
+ expect(input.required).toEqual(["10", "plain", "caught", "__proto__"]);
3889
+ expect(output.required).toEqual(["10", "plain", "defaulted", "caught", "__proto__"]);
3890
+ expect(Object.prototype.hasOwnProperty.call(input.properties!, "__proto__")).toBe(true);
3891
+ expect(input.properties).not.toHaveProperty("hidden");
3892
+ expect(Object.getOwnPropertySymbols(input.properties!)).toEqual([]);
3893
+ });
3894
+
3852
3895
  test("strip output-side examples from input JSON schema for codec", () => {
3853
3896
  const codec = z
3854
3897
  .codec(z.string(), z.number(), { decode: (s) => Number(s), encode: (n) => String(n) })
@@ -4679,3 +4722,12 @@ describe("intersection folding declines", () => {
4679
4722
  expect(allOf(schema)).toHaveLength(3);
4680
4723
  });
4681
4724
  });
4725
+
4726
+ test("currency code", () => {
4727
+ expect(z.toJSONSchema(z.currencyCode())).toEqual({
4728
+ $schema: "https://json-schema.org/draft/2020-12/schema",
4729
+ type: "string",
4730
+ format: "currency_code",
4731
+ pattern: z.regexes.currencyCode.source,
4732
+ });
4733
+ });
@@ -58,16 +58,20 @@ export type CheckTypeParams<
58
58
  > = Params<T, NonNullable<T["_zod"]["isst"] | T["_zod"]["issc"]>, "type" | "checks" | "error" | "check" | AlsoOmit>;
59
59
 
60
60
  // String
61
- export type $ZodStringParams = TypeParams<schemas.$ZodString<string>, "coerce">;
61
+ export type $ZodStringParams = TypeParams<schemas.$ZodString<string>, "coerce"> & {
62
+ checks?: readonly checks.$ZodCheck<string>[];
63
+ };
64
+
65
+ function snapshotChecks<T extends { checks?: readonly checks.$ZodCheck<never>[] }>(def: T) {
66
+ if (def.checks) def.checks = [...def.checks];
67
+ return def as T & { checks?: NonNullable<T["checks"]>[number][] };
68
+ }
62
69
  // @__NO_SIDE_EFFECTS__
63
70
  export function _string<T extends schemas.$ZodString>(
64
71
  Class: util.SchemaClass<T>,
65
72
  params?: string | $ZodStringParams
66
73
  ): T {
67
- return new Class({
68
- type: "string",
69
- ...util.normalizeParams(params),
70
- });
74
+ return new Class(snapshotChecks({ type: "string" as const, ...util.normalizeParams(params) }));
71
75
  }
72
76
 
73
77
  // @__NO_SIDE_EFFECTS__
@@ -75,11 +79,7 @@ export function _coercedString<T extends schemas.$ZodString>(
75
79
  Class: util.SchemaClass<T>,
76
80
  params?: string | $ZodStringParams
77
81
  ): T {
78
- return new Class({
79
- type: "string",
80
- coerce: true,
81
- ...util.normalizeParams(params),
82
- });
82
+ return new Class(snapshotChecks({ type: "string" as const, coerce: true, ...util.normalizeParams(params) }));
83
83
  }
84
84
 
85
85
  export type $ZodStringFormatParams = CheckTypeParams<
@@ -608,7 +608,9 @@ export function _isoDuration<T extends schemas.$ZodISODuration>(
608
608
  }
609
609
 
610
610
  // Number
611
- export type $ZodNumberParams = TypeParams<schemas.$ZodNumber<number>, "coerce">;
611
+ export type $ZodNumberParams = TypeParams<schemas.$ZodNumber<number>, "coerce"> & {
612
+ checks?: readonly checks.$ZodCheck<number>[];
613
+ };
612
614
  export type $ZodNumberFormatParams = CheckTypeParams<schemas.$ZodNumberFormat, "format" | "coerce">;
613
615
  export type $ZodCheckNumberFormatParams = CheckParams<checks.$ZodCheckNumberFormat, "format" | "when">;
614
616
  // @__NO_SIDE_EFFECTS__
@@ -616,11 +618,7 @@ export function _number<T extends schemas.$ZodNumber>(
616
618
  Class: util.SchemaClass<T>,
617
619
  params?: string | $ZodNumberParams
618
620
  ): T {
619
- return new Class({
620
- type: "number",
621
- checks: [],
622
- ...util.normalizeParams(params),
623
- });
621
+ return new Class(snapshotChecks({ type: "number" as const, checks: [], ...util.normalizeParams(params) }));
624
622
  }
625
623
 
626
624
  // @__NO_SIDE_EFFECTS__
@@ -628,12 +626,9 @@ export function _coercedNumber<T extends schemas.$ZodNumber>(
628
626
  Class: util.SchemaClass<T>,
629
627
  params?: string | $ZodNumberParams
630
628
  ): T {
631
- return new Class({
632
- type: "number",
633
- coerce: true,
634
- checks: [],
635
- ...util.normalizeParams(params),
636
- });
629
+ return new Class(
630
+ snapshotChecks({ type: "number" as const, coerce: true, checks: [], ...util.normalizeParams(params) })
631
+ );
637
632
  }
638
633
 
639
634
  // @__NO_SIDE_EFFECTS__
@@ -1144,16 +1139,13 @@ export function _property<K extends string, T extends schemas.$ZodType>(
1144
1139
  });
1145
1140
  }
1146
1141
 
1147
- // `when` is omitted: it gates a check inside the run loop, so it means nothing when this is parsed as a schema, and honoring it in one role only would diverge silently
1148
- export type $ZodPropertiesParams = CheckTypeParams<schemas.$ZodProperties, "shape" | "when">;
1142
+ export type $ZodCheckPropertiesParams = CheckParams<checks.$ZodCheckProperties, "shape" | "when">;
1149
1143
  // @__NO_SIDE_EFFECTS__
1150
1144
  export function _properties<Shape extends schemas.$ZodShape>(
1151
- Class: util.SchemaClass<schemas.$ZodProperties>,
1152
1145
  shape: Shape,
1153
- params?: string | $ZodPropertiesParams
1154
- ): schemas.$ZodProperties<Shape> {
1155
- return new Class({
1156
- type: "properties",
1146
+ params?: string | $ZodCheckPropertiesParams
1147
+ ): checks.$ZodCheckProperties<Shape> {
1148
+ return new checks.$ZodCheckProperties({
1157
1149
  check: "properties",
1158
1150
  shape,
1159
1151
  ...util.normalizeParams(params),
@@ -1000,7 +1000,7 @@ export const $ZodCheckEndsWith: core.$constructor<$ZodCheckEndsWith> = /*@__PURE
1000
1000
  function handleCheckPropertyResult(
1001
1001
  result: schemas.ParsePayload<unknown>,
1002
1002
  payload: schemas.ParsePayload<unknown>,
1003
- property: string
1003
+ property: string | symbol
1004
1004
  ) {
1005
1005
  if (result.issues.length) {
1006
1006
  payload.issues.push(...util.prefixIssues(property, result.issues));
@@ -1045,6 +1045,66 @@ export const $ZodCheckProperty: core.$constructor<$ZodCheckProperty> = /*@__PURE
1045
1045
  }
1046
1046
  );
1047
1047
 
1048
+ /////////////////////////////////////
1049
+ ///// $ZodCheckProperties /////
1050
+ /////////////////////////////////////
1051
+ export interface $ZodCheckPropertiesDef<Shape extends schemas.$ZodShape = schemas.$ZodShape> extends $ZodCheckDef {
1052
+ check: "properties";
1053
+ shape: Shape;
1054
+ }
1055
+
1056
+ // typed over the input side, since each child's result is discarded; a literal widens to its primitive so spreading over a `string` property still type-checks (#6520)
1057
+ export type $ZodCheckPropertiesInput<Shape extends schemas.$ZodShape = schemas.$ZodShape> = {
1058
+ -readonly [k in keyof Shape]: util.Widen<core.input<Shape[k]>>;
1059
+ };
1060
+
1061
+ export interface $ZodCheckPropertiesInternals<Shape extends schemas.$ZodShape = schemas.$ZodShape>
1062
+ extends $ZodCheckInternals<$ZodCheckPropertiesInput<Shape>> {
1063
+ def: $ZodCheckPropertiesDef<Shape>;
1064
+ issc: errors.$ZodIssue;
1065
+ }
1066
+
1067
+ export interface $ZodCheckProperties<Shape extends schemas.$ZodShape = schemas.$ZodShape>
1068
+ extends $ZodCheck<$ZodCheckPropertiesInput<Shape>> {
1069
+ _zod: $ZodCheckPropertiesInternals<Shape>;
1070
+ // yields the check itself, so `.check(...z.properties(shape))` spread call sites keep working
1071
+ [Symbol.iterator](): Iterator<this>;
1072
+ }
1073
+
1074
+ export const $ZodCheckProperties: core.$constructor<$ZodCheckProperties> = /*@__PURE__*/ core.$constructor(
1075
+ "$ZodCheckProperties",
1076
+ (inst, def) => {
1077
+ $ZodCheck.init(inst, def);
1078
+ util.hide(inst, Symbol.iterator, function* () {
1079
+ yield inst;
1080
+ });
1081
+
1082
+ // key and schema snapshotted together: reading one live and the other cached lets a later mutation of the caller's shape object pair a stale key with a missing schema
1083
+ let entries!: [string | symbol, schemas.$ZodType][];
1084
+ inst._zod.check = (payload) => {
1085
+ // the base schema already typed the value, so only a nullish one is rejected here: the properties read on a primitive too, matching z.property() on a string's length
1086
+ if (payload.value == null) {
1087
+ payload.issues.push({ expected: "object", code: "invalid_type", input: payload.value, inst });
1088
+ return undefined;
1089
+ }
1090
+ entries ??= Reflect.ownKeys(def.shape).map((key) => [key, (def.shape as any)[key] as schemas.$ZodType]);
1091
+ const input = payload.value as any;
1092
+ let proms: Promise<any>[] | undefined;
1093
+ for (const [key, schema] of entries) {
1094
+ const result = schema._zod.run({ value: input[key], issues: [] }, {});
1095
+ if (result instanceof Promise) {
1096
+ proms ??= [];
1097
+ proms.push(result.then((result) => handleCheckPropertyResult(result, payload, key)));
1098
+ } else {
1099
+ handleCheckPropertyResult(result, payload, key);
1100
+ }
1101
+ }
1102
+ if (proms) return Promise.all(proms).then(() => undefined);
1103
+ return undefined;
1104
+ };
1105
+ }
1106
+ );
1107
+
1048
1108
  ///////////////////////////////////
1049
1109
  ///// $ZodCheckMimeType /////
1050
1110
  ///////////////////////////////////
@@ -13,12 +13,12 @@ import {
13
13
  isValidIPv6,
14
14
  isValidJWT,
15
15
  mergeValues,
16
- parseURLObject,
17
16
  stripTabAndNewline,
18
17
  urlHostnameOk,
19
18
  urlProtocolOk,
19
+ validateURL,
20
20
  } from "./schemas.js";
21
- import type { $ZodProperties, $ZodPropertiesDef, ParseContextInternal, ParsePayload, SomeType } from "./schemas.js";
21
+ import type { ParseContextInternal, ParsePayload, SomeType } from "./schemas.js";
22
22
  import * as util from "./util.js";
23
23
 
24
24
  /** @internal Sentinel the compiled fast path returns when validation fails. */
@@ -89,7 +89,7 @@ type SupportedCheck =
89
89
  | checks.$ZodCheckLengthEquals
90
90
  | checks.$ZodCheckStringFormat
91
91
  | checks.$ZodCheckProperty
92
- | $ZodProperties
92
+ | checks.$ZodCheckProperties
93
93
  | checks.$ZodCheckMimeType
94
94
  | checks.$ZodCheckOverwrite
95
95
  | { _zod: { def: { check: "custom"; fn?: (value: unknown) => boolean }; check?: (payload: unknown) => unknown } };
@@ -455,7 +455,7 @@ function generateChecks(doc: Doc, ctx: CompileContext, schema: SomeType, accesso
455
455
  generatePropertyCheck(doc, ctx, def, currentAccessor);
456
456
  break;
457
457
  case "properties":
458
- generatePropertiesChecks(doc, ctx, def, currentAccessor, false);
458
+ generatePropertiesChecks(doc, ctx, def, currentAccessor);
459
459
  break;
460
460
  case "overwrite": {
461
461
  // Overwrite transforms the value - create new variable for transformed result
@@ -616,20 +616,15 @@ function generateMimeTypeCheck(
616
616
  function generatePropertiesChecks(
617
617
  doc: Doc,
618
618
  ctx: CompileContext,
619
- def: $ZodPropertiesDef,
620
- accessor: string,
621
- schemaRole: boolean
619
+ def: checks.$ZodCheckPropertiesDef,
620
+ accessor: string
622
621
  ): void {
623
- // a custom `when` gates the assertion at runtime; inside a union a wrongly-run branch is absorbed as a branch failure rather than falling back, so refuse at codegen the way the check role does
622
+ // a custom `when` gates the assertion at runtime; inside a union a wrongly-run branch is absorbed as a branch failure rather than falling back, so refuse at codegen
624
623
  if (def.when) {
625
624
  throw new ZodCompileUnsupportedError(`check with a custom "when" condition`);
626
625
  }
627
- // matches the runtime gate for whichever role this is: a schema rejects a primitive outright, a check only a nullish value
628
- doc.write(
629
- schemaRole
630
- ? `if (${accessor} === null || (typeof ${accessor} !== "object" && typeof ${accessor} !== "function")) return INVALID;`
631
- : `if (${accessor} == null) return INVALID;`
632
- );
626
+ // matches the runtime gate: the base schema already typed the value, so only a nullish one is rejected
627
+ doc.write(`if (${accessor} == null) return INVALID;`);
633
628
  const shape = def.shape as Record<string | symbol, SomeType>;
634
629
  for (const key of Reflect.ownKeys(shape)) {
635
630
  // a symbol has no source literal, so it is hoisted as a constant
@@ -777,7 +772,21 @@ const PATTERN_IS_COMPLETE: Set<string> = new Set([
777
772
  ]);
778
773
 
779
774
  // Returns the accessor holding the (possibly normalized) value after the check — url/normalize formats produce a new value like overwrite does. Never assigns to the incoming accessor: it may be a `const` or a property expression on user input.
780
- function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFormatDef, accessor: string): string {
775
+ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFormatDef, accessor: string): string;
776
+ function generateStringFormatCheck(
777
+ doc: Doc,
778
+ ctx: CompileContext,
779
+ def: StringFormatDef,
780
+ accessor: string,
781
+ needsValue: boolean
782
+ ): string | null;
783
+ function generateStringFormatCheck(
784
+ doc: Doc,
785
+ ctx: CompileContext,
786
+ def: StringFormatDef,
787
+ accessor: string,
788
+ needsValue = true
789
+ ): string | null {
781
790
  // Some string formats do runtime validation beyond their advertised pattern. For cheap pure utility checks, hoist the runtime function and call it so the fast path stays correct without cloning the utility logic into codegen.
782
791
  const fmt = def.format;
783
792
  if (fmt === "base64") {
@@ -825,7 +834,7 @@ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFor
825
834
  formatDef.protocol !== undefined
826
835
  ) {
827
836
  // Same three predicates the runtime calls, in the same order, so there is no second URL implementation to drift. Which options exist is known now, so the calls the runtime makes conditionally are emitted conditionally instead.
828
- const parseConst = addConstant(ctx, parseURLObject);
837
+ const parseConst = addConstant(ctx, validateURL);
829
838
  const defConst = addConstant(ctx, def);
830
839
  const trimVar = newVar(ctx);
831
840
  const urlVar = newVar(ctx);
@@ -840,6 +849,7 @@ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFor
840
849
  const protocolConst = addConstant(ctx, urlProtocolOk);
841
850
  doc.write(`if (!${protocolConst}(${urlVar}, ${defConst}.protocol)) return INVALID;`);
842
851
  }
852
+ if (!needsValue) return null;
843
853
  const outputVar = newVar(ctx);
844
854
  const outputExpr = formatDef.normalize ? `${urlVar}.href` : `${addConstant(ctx, stripTabAndNewline)}(${trimVar})`;
845
855
  doc.write(`const ${outputVar} = ${outputExpr};`);
@@ -934,7 +944,6 @@ type SupportedSchemaType =
934
944
  | "lazy"
935
945
  | "pipe"
936
946
  | "custom"
937
- | "properties"
938
947
  | "transform"
939
948
  | "catch";
940
949
 
@@ -967,7 +976,7 @@ function generateCheck(
967
976
 
968
977
  switch (type) {
969
978
  case "string":
970
- typeAccessor = generateStringCheck(doc, ctx, schema, accessor);
979
+ typeAccessor = generateStringCheck(doc, ctx, schema, accessor, buildsValue);
971
980
  break;
972
981
  case "number":
973
982
  typeAccessor = generateNumberCheck(doc, schema, accessor);
@@ -1079,10 +1088,6 @@ function generateCheck(
1079
1088
  case "custom":
1080
1089
  typeAccessor = generateCustomCheck(doc, ctx, schema, accessor);
1081
1090
  break;
1082
- case "properties":
1083
- generatePropertiesChecks(doc, ctx, (schema as $ZodProperties)._zod.def, accessor, true);
1084
- typeAccessor = accessor;
1085
- break;
1086
1091
  case "transform":
1087
1092
  typeAccessor = generateTransformCheck(doc, ctx, schema, accessor);
1088
1093
  break;
@@ -1102,13 +1107,19 @@ function generateCheck(
1102
1107
  return generateChecks(doc, ctx, schema, typeAccessor);
1103
1108
  }
1104
1109
 
1105
- function generateStringCheck(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string {
1110
+ function generateStringCheck(
1111
+ doc: Doc,
1112
+ ctx: CompileContext,
1113
+ schema: SomeType,
1114
+ accessor: string,
1115
+ needsValue = true
1116
+ ): string | null {
1106
1117
  doc.write(`if (typeof ${accessor} !== "string") return INVALID;`);
1107
1118
 
1108
1119
  // z.email() carries its format on the def, z.string().email() in def.checks; both route here so the format table has no second copy to drift from.
1109
1120
  const def = schema._zod.def as unknown as StringFormatDef & { format?: string };
1110
1121
  if (def.format === undefined) return accessor;
1111
- return generateStringFormatCheck(doc, ctx, def, accessor);
1122
+ return generateStringFormatCheck(doc, ctx, def, accessor, needsValue);
1112
1123
  }
1113
1124
 
1114
1125
  function generateNumberCheck(doc: Doc, schema: SomeType, accessor: string): string {
@@ -79,9 +79,7 @@ export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T[
79
79
  // Cleared even on throw, so the shared descriptor never leaks one instance's internals into the next.
80
80
  _zodDesc.value = undefined;
81
81
  }
82
- }
83
-
84
- if (inst._zod.traits.has(name)) {
82
+ } else if (inst._zod.traits.has(name)) {
85
83
  return;
86
84
  }
87
85
 
@@ -16,7 +16,6 @@ import {
16
16
  finalize,
17
17
  handleUnrepresentable,
18
18
  initializeContext,
19
- isTransforming,
20
19
  processSchema,
21
20
  } from "./to-json-schema.js";
22
21
  import { BIGINT_FORMAT_RANGES, NUMBER_FORMAT_RANGES, assignProp, getEnumValues } from "./util.js";
@@ -450,20 +449,16 @@ export const objectProcessor: Processor<schemas.$ZodObject> = (schema, ctx, _jso
450
449
  }
451
450
 
452
451
  // required keys
453
- const allKeys = new Set(Object.keys(shape));
454
- const requiredKeys = new Set(
455
- [...allKeys].filter((key) => {
456
- const field = def.shape[key]!;
457
- if (ctx.io === "input") {
458
- return inputOptin(field) === undefined;
459
- } else {
460
- return field._zod.optout === undefined;
461
- }
462
- })
463
- );
452
+ const requiredKeys: string[] = [];
453
+ for (const key of Object.keys(shape)) {
454
+ const field = def.shape[key]!;
455
+ if (ctx.io === "input" ? inputOptin(field) === undefined : field._zod.optout === undefined) {
456
+ requiredKeys.push(key);
457
+ }
458
+ }
464
459
 
465
- if (requiredKeys.size > 0) {
466
- json.required = Array.from(requiredKeys);
460
+ if (requiredKeys.length > 0) {
461
+ json.required = requiredKeys;
467
462
  }
468
463
 
469
464
  // catchall
@@ -481,54 +476,6 @@ export const objectProcessor: Processor<schemas.$ZodObject> = (schema, ctx, _jso
481
476
  }
482
477
  };
483
478
 
484
- // asserts named properties in place and passes everything else through, so no additionalProperties constraint is emitted
485
- export const propertiesProcessor: Processor<schemas.$ZodProperties> = (schema, ctx, _json, params) => {
486
- const json = _json as JSONSchema.ObjectSchema;
487
- const def = schema._zod.def;
488
-
489
- // dropping a symbol key silently would emit a schema that asserts less than this one does
490
- if (
491
- Object.getOwnPropertySymbols(def.shape).length &&
492
- handleUnrepresentable(schema, ctx, json, params, "Symbol keys cannot be represented in JSON Schema")
493
- ) {
494
- return;
495
- }
496
-
497
- // the parsed value is the input, so an output-mode emission would describe a transformed value this schema never returns
498
- if (ctx.io === "output") {
499
- for (const key in def.shape) {
500
- if (
501
- isTransforming(def.shape[key]!) &&
502
- handleUnrepresentable(
503
- schema,
504
- ctx,
505
- json,
506
- params,
507
- `z.properties() returns its input, so the output of a transforming schema at key "${key}" cannot be represented in JSON Schema`
508
- )
509
- ) {
510
- return;
511
- }
512
- }
513
- }
514
-
515
- json.type = "object";
516
- json.properties = {};
517
- for (const key in def.shape) {
518
- assignProp(
519
- json.properties,
520
- key,
521
- processSchema(def.shape[key]!, ctx as any, {
522
- ...params,
523
- path: [...params.path, "properties", key],
524
- })
525
- );
526
- }
527
- // input-side optionality in both modes: the parsed value is the input, so a defaulted key that stays absent must not be required of the output either
528
- const required = Object.keys(def.shape).filter((key) => inputOptin(def.shape[key]!) === undefined);
529
- if (required.length > 0) json.required = required;
530
- };
531
-
532
479
  export const unionProcessor: Processor<schemas.$ZodUnion> = (schema, ctx, json, params) => {
533
480
  const def = schema._zod.def as schemas.$ZodUnionDef;
534
481
  // Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches). This includes both z.xor() and discriminated unions
@@ -904,7 +851,6 @@ export const allProcessors: Record<string, Processor<any>> = {
904
851
  file: fileProcessor,
905
852
  success: successProcessor,
906
853
  custom: customProcessor,
907
- properties: propertiesProcessor,
908
854
  function: functionProcessor,
909
855
  transform: transformProcessor,
910
856
  map: mapProcessor,
@@ -28,9 +28,9 @@ type WithState = { [STATE]?: State };
28
28
 
29
29
  const NO_ISSUES: errors.$ZodRawIssue[] = [];
30
30
 
31
- // a value a cycle can close through; callables count, since z.properties asserts on one
31
+ // a value a cycle can close through
32
32
  function isRef(value: unknown): value is object {
33
- return value !== null && (typeof value === "object" || typeof value === "function");
33
+ return value !== null && typeof value === "object";
34
34
  }
35
35
 
36
36
  // Receivers prefix paths in place, so the cache and every hand-out need their own copies.
@@ -90,9 +90,6 @@ function isRecursive(inst: $ZodType, stack: Set<object>, resolve: boolean): Answ
90
90
  check(def.catchall);
91
91
  break;
92
92
  }
93
- case "properties":
94
- merge(shape(def.shape, false));
95
- break;
96
93
  case "array":
97
94
  check(def.element);
98
95
  break;
@@ -98,6 +98,10 @@ export const e164: RegExp = /^\+[1-9]\d{6,14}$/;
98
98
  // Credit card shape: 12–19 digits, optionally separated by single spaces or single hyphens. ISO/IEC 7812 caps the PAN at 19 digits; 12 is the shortest issued length (Maestro).
99
99
  export const creditCard: RegExp = /^\d(?:[ -]?\d){11,18}$/;
100
100
 
101
+ // ISO 4217 alpha codes from the SIX list, regenerated by scripts/update-iso-4217.ts
102
+ export const currencyCode: RegExp =
103
+ /^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/;
104
+
101
105
  // iban electronic format: 2-letter country, check digits 02-98 (the only values `98 - remainder` can produce), 11-30 bban characters
102
106
  export const iban: RegExp = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
103
107