zod 4.5.3 → 4.6.0

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 (348) hide show
  1. package/compile.cjs +19 -0
  2. package/index.cjs +19 -0
  3. package/locales/index.cjs +19 -0
  4. package/mini/index.cjs +19 -0
  5. package/package.json +8 -7
  6. package/src/v3/helpers/errorUtil.ts +1 -1
  7. package/src/v4/classic/checks.ts +0 -1
  8. package/src/v4/classic/external.ts +2 -0
  9. package/src/v4/classic/from-json-schema.ts +248 -25
  10. package/src/v4/classic/schemas.ts +255 -164
  11. package/src/v4/classic/tests/assignability.test.ts +59 -9
  12. package/src/v4/classic/tests/bigint.test.ts +6 -0
  13. package/src/v4/classic/tests/continuability.test.ts +2 -2
  14. package/src/v4/classic/tests/cyclic-data.test.ts +316 -0
  15. package/src/v4/classic/tests/default.test.ts +46 -0
  16. package/src/v4/classic/tests/enum.test.ts +9 -0
  17. package/src/v4/classic/tests/error.test.ts +130 -1
  18. package/src/v4/classic/tests/firstparty.test.ts +4 -0
  19. package/src/v4/classic/tests/from-json-schema.test.ts +157 -0
  20. package/src/v4/classic/tests/iban.test.ts +81 -0
  21. package/src/v4/classic/tests/instance-footprint.test.ts +18 -2
  22. package/src/v4/classic/tests/instanceof.test.ts +27 -7
  23. package/src/v4/classic/tests/issue-schema.test.ts +88 -56
  24. package/src/v4/classic/tests/object.test.ts +56 -0
  25. package/src/v4/classic/tests/optin-ladder.test.ts +3 -2
  26. package/src/v4/classic/tests/properties.test.ts +225 -0
  27. package/src/v4/classic/tests/recursive-types.test.ts +1 -1
  28. package/src/v4/classic/tests/standard-schema.test.ts +19 -0
  29. package/src/v4/classic/tests/string.test.ts +25 -0
  30. package/src/v4/classic/tests/template-literal.test.ts +33 -1
  31. package/src/v4/classic/tests/to-json-schema.test.ts +147 -3
  32. package/src/v4/classic/tests/validate.test.ts +569 -0
  33. package/src/v4/core/api.ts +30 -6
  34. package/src/v4/core/checks.ts +1 -101
  35. package/src/v4/core/compile.ts +193 -81
  36. package/src/v4/core/doc.ts +6 -2
  37. package/src/v4/core/errors.ts +3 -6
  38. package/src/v4/core/json-schema-generator.ts +2 -2
  39. package/src/v4/core/json-schema-processors.ts +226 -68
  40. package/src/v4/core/memoizer.ts +162 -36
  41. package/src/v4/core/parse.ts +37 -16
  42. package/src/v4/core/regexes.ts +10 -4
  43. package/src/v4/core/schemas.ts +314 -83
  44. package/src/v4/core/tests/cached.test.ts +54 -0
  45. package/src/v4/core/tests/compile-generative.test.ts +445 -0
  46. package/src/v4/core/tests/compile.test.ts +222 -2
  47. package/src/v4/core/tests/email-regex.test.ts +140 -0
  48. package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
  49. package/src/v4/core/to-json-schema.ts +10 -8
  50. package/src/v4/core/util.ts +222 -147
  51. package/src/v4/core/versions.ts +2 -2
  52. package/src/v4/core/visit.ts +11 -0
  53. package/src/v4/locales/ar.ts +1 -0
  54. package/src/v4/locales/az.ts +1 -0
  55. package/src/v4/locales/be.ts +1 -0
  56. package/src/v4/locales/bg.ts +1 -0
  57. package/src/v4/locales/bn.ts +1 -0
  58. package/src/v4/locales/ca.ts +1 -0
  59. package/src/v4/locales/ckb.ts +1 -0
  60. package/src/v4/locales/cs.ts +1 -0
  61. package/src/v4/locales/da.ts +1 -0
  62. package/src/v4/locales/de.ts +1 -0
  63. package/src/v4/locales/el.ts +1 -0
  64. package/src/v4/locales/en.ts +1 -0
  65. package/src/v4/locales/eo.ts +1 -0
  66. package/src/v4/locales/es.ts +1 -0
  67. package/src/v4/locales/fa.ts +1 -0
  68. package/src/v4/locales/fi.ts +1 -0
  69. package/src/v4/locales/fr-CA.ts +1 -0
  70. package/src/v4/locales/fr.ts +1 -0
  71. package/src/v4/locales/gu.ts +1 -0
  72. package/src/v4/locales/he.ts +1 -0
  73. package/src/v4/locales/hi.ts +1 -0
  74. package/src/v4/locales/hr.ts +1 -0
  75. package/src/v4/locales/hu.ts +1 -0
  76. package/src/v4/locales/hy.ts +1 -0
  77. package/src/v4/locales/id.ts +1 -0
  78. package/src/v4/locales/is.ts +1 -0
  79. package/src/v4/locales/it.ts +1 -0
  80. package/src/v4/locales/ja.ts +1 -0
  81. package/src/v4/locales/ka.ts +1 -0
  82. package/src/v4/locales/km.ts +1 -0
  83. package/src/v4/locales/kn.ts +1 -0
  84. package/src/v4/locales/ko.ts +1 -0
  85. package/src/v4/locales/lt.ts +1 -0
  86. package/src/v4/locales/mk.ts +1 -0
  87. package/src/v4/locales/ms.ts +1 -0
  88. package/src/v4/locales/ne.ts +1 -0
  89. package/src/v4/locales/nl.ts +1 -0
  90. package/src/v4/locales/nn.ts +1 -0
  91. package/src/v4/locales/no.ts +1 -0
  92. package/src/v4/locales/ota.ts +1 -0
  93. package/src/v4/locales/pl.ts +1 -0
  94. package/src/v4/locales/ps.ts +1 -0
  95. package/src/v4/locales/pt-BR.ts +1 -0
  96. package/src/v4/locales/pt.ts +1 -0
  97. package/src/v4/locales/ro.ts +1 -0
  98. package/src/v4/locales/ru.ts +1 -0
  99. package/src/v4/locales/sk.ts +1 -0
  100. package/src/v4/locales/sl.ts +1 -0
  101. package/src/v4/locales/sv.ts +1 -0
  102. package/src/v4/locales/ta.ts +1 -0
  103. package/src/v4/locales/th.ts +1 -0
  104. package/src/v4/locales/tk.ts +1 -0
  105. package/src/v4/locales/tr.ts +1 -0
  106. package/src/v4/locales/uk.ts +1 -0
  107. package/src/v4/locales/ur.ts +1 -0
  108. package/src/v4/locales/uz.ts +1 -0
  109. package/src/v4/locales/vi.ts +1 -0
  110. package/src/v4/locales/yo.ts +1 -0
  111. package/src/v4/locales/zh-CN.ts +1 -0
  112. package/src/v4/locales/zh-TW.ts +1 -0
  113. package/src/v4/mini/checks.ts +0 -1
  114. package/src/v4/mini/external.ts +2 -0
  115. package/src/v4/mini/schemas.ts +38 -2
  116. package/src/v4/mini/tests/checks.test.ts +1 -1
  117. package/src/v4/mini/tests/computed.test.ts +11 -17
  118. package/src/v4/mini/tests/index.test.ts +8 -0
  119. package/src/v4/mini/tests/string.test.ts +16 -0
  120. package/v3/ZodError.cjs +19 -0
  121. package/v3/errors.cjs +19 -0
  122. package/v3/external.cjs +19 -0
  123. package/v3/helpers/enumUtil.cjs +19 -0
  124. package/v3/helpers/errorUtil.cjs +20 -1
  125. package/v3/helpers/errorUtil.js +1 -1
  126. package/v3/helpers/parseUtil.cjs +19 -0
  127. package/v3/helpers/partialUtil.cjs +19 -0
  128. package/v3/helpers/typeAliases.cjs +19 -0
  129. package/v3/helpers/util.cjs +19 -0
  130. package/v3/index.cjs +19 -0
  131. package/v3/standard-schema.cjs +19 -0
  132. package/v3/types.cjs +19 -0
  133. package/v4/classic/checks.cjs +20 -2
  134. package/v4/classic/checks.d.cts +1 -1
  135. package/v4/classic/checks.d.ts +1 -1
  136. package/v4/classic/checks.js +1 -1
  137. package/v4/classic/coerce.cjs +19 -0
  138. package/v4/classic/compat.cjs +19 -0
  139. package/v4/classic/deep-partial.cjs +19 -0
  140. package/v4/classic/errors.cjs +19 -0
  141. package/v4/classic/external.cjs +22 -1
  142. package/v4/classic/external.d.cts +1 -1
  143. package/v4/classic/external.d.ts +1 -1
  144. package/v4/classic/external.js +1 -1
  145. package/v4/classic/from-json-schema.cjs +264 -24
  146. package/v4/classic/from-json-schema.js +245 -24
  147. package/v4/classic/in-out.cjs +19 -0
  148. package/v4/classic/index.cjs +19 -0
  149. package/v4/classic/iso.cjs +19 -0
  150. package/v4/classic/parse.cjs +19 -0
  151. package/v4/classic/schemas.cjs +100 -31
  152. package/v4/classic/schemas.d.cts +17 -1
  153. package/v4/classic/schemas.d.ts +17 -1
  154. package/v4/classic/schemas.js +77 -29
  155. package/v4/core/api.cjs +37 -2
  156. package/v4/core/api.d.cts +6 -4
  157. package/v4/core/api.d.ts +6 -4
  158. package/v4/core/api.js +17 -2
  159. package/v4/core/checks.cjs +19 -96
  160. package/v4/core/checks.d.cts +1 -1
  161. package/v4/core/checks.d.ts +1 -1
  162. package/v4/core/checks.js +0 -96
  163. package/v4/core/compile.cjs +164 -65
  164. package/v4/core/compile.d.cts +18 -4
  165. package/v4/core/compile.d.ts +18 -4
  166. package/v4/core/compile.js +145 -66
  167. package/v4/core/core.cjs +19 -0
  168. package/v4/core/doc.cjs +26 -2
  169. package/v4/core/doc.js +7 -2
  170. package/v4/core/errors.cjs +21 -5
  171. package/v4/core/errors.js +2 -5
  172. package/v4/core/index.cjs +19 -0
  173. package/v4/core/json-schema-generator.cjs +20 -1
  174. package/v4/core/json-schema-generator.js +2 -2
  175. package/v4/core/json-schema-processors.cjs +215 -63
  176. package/v4/core/json-schema-processors.d.cts +17 -0
  177. package/v4/core/json-schema-processors.d.ts +17 -0
  178. package/v4/core/json-schema-processors.js +195 -64
  179. package/v4/core/json-schema.cjs +19 -0
  180. package/v4/core/memoizer.cjs +204 -36
  181. package/v4/core/memoizer.js +162 -36
  182. package/v4/core/parse.cjs +56 -16
  183. package/v4/core/parse.js +37 -16
  184. package/v4/core/regexes.cjs +29 -6
  185. package/v4/core/regexes.d.cts +2 -0
  186. package/v4/core/regexes.d.ts +2 -0
  187. package/v4/core/regexes.js +8 -4
  188. package/v4/core/registries.cjs +19 -0
  189. package/v4/core/schemas.cjs +285 -66
  190. package/v4/core/schemas.d.cts +34 -3
  191. package/v4/core/schemas.d.ts +34 -3
  192. package/v4/core/schemas.js +264 -65
  193. package/v4/core/standard-schema.cjs +19 -0
  194. package/v4/core/to-json-schema.cjs +29 -7
  195. package/v4/core/to-json-schema.d.cts +7 -3
  196. package/v4/core/to-json-schema.d.ts +7 -3
  197. package/v4/core/to-json-schema.js +8 -7
  198. package/v4/core/util.cjs +181 -143
  199. package/v4/core/util.d.cts +25 -4
  200. package/v4/core/util.d.ts +25 -4
  201. package/v4/core/util.js +160 -143
  202. package/v4/core/versions.cjs +21 -2
  203. package/v4/core/versions.d.cts +1 -1
  204. package/v4/core/versions.d.ts +1 -1
  205. package/v4/core/versions.js +2 -2
  206. package/v4/core/visit.cjs +31 -0
  207. package/v4/core/visit.js +12 -0
  208. package/v4/index.cjs +19 -0
  209. package/v4/locales/ar.cjs +1 -0
  210. package/v4/locales/ar.js +1 -0
  211. package/v4/locales/az.cjs +1 -0
  212. package/v4/locales/az.js +1 -0
  213. package/v4/locales/be.cjs +1 -0
  214. package/v4/locales/be.js +1 -0
  215. package/v4/locales/bg.cjs +1 -0
  216. package/v4/locales/bg.js +1 -0
  217. package/v4/locales/bn.cjs +1 -0
  218. package/v4/locales/bn.js +1 -0
  219. package/v4/locales/ca.cjs +1 -0
  220. package/v4/locales/ca.js +1 -0
  221. package/v4/locales/ckb.cjs +1 -0
  222. package/v4/locales/ckb.js +1 -0
  223. package/v4/locales/cs.cjs +1 -0
  224. package/v4/locales/cs.js +1 -0
  225. package/v4/locales/da.cjs +1 -0
  226. package/v4/locales/da.js +1 -0
  227. package/v4/locales/de.cjs +1 -0
  228. package/v4/locales/de.js +1 -0
  229. package/v4/locales/el.cjs +1 -0
  230. package/v4/locales/el.js +1 -0
  231. package/v4/locales/en.cjs +1 -0
  232. package/v4/locales/en.js +1 -0
  233. package/v4/locales/eo.cjs +1 -0
  234. package/v4/locales/eo.js +1 -0
  235. package/v4/locales/es.cjs +1 -0
  236. package/v4/locales/es.js +1 -0
  237. package/v4/locales/fa.cjs +1 -0
  238. package/v4/locales/fa.js +1 -0
  239. package/v4/locales/fi.cjs +1 -0
  240. package/v4/locales/fi.js +1 -0
  241. package/v4/locales/fr-CA.cjs +1 -0
  242. package/v4/locales/fr-CA.js +1 -0
  243. package/v4/locales/fr.cjs +1 -0
  244. package/v4/locales/fr.js +1 -0
  245. package/v4/locales/gu.cjs +1 -0
  246. package/v4/locales/gu.js +1 -0
  247. package/v4/locales/he.cjs +1 -0
  248. package/v4/locales/he.js +1 -0
  249. package/v4/locales/hi.cjs +1 -0
  250. package/v4/locales/hi.js +1 -0
  251. package/v4/locales/hr.cjs +1 -0
  252. package/v4/locales/hr.js +1 -0
  253. package/v4/locales/hu.cjs +1 -0
  254. package/v4/locales/hu.js +1 -0
  255. package/v4/locales/hy.cjs +1 -0
  256. package/v4/locales/hy.js +1 -0
  257. package/v4/locales/id.cjs +1 -0
  258. package/v4/locales/id.js +1 -0
  259. package/v4/locales/index.cjs +19 -0
  260. package/v4/locales/is.cjs +1 -0
  261. package/v4/locales/is.js +1 -0
  262. package/v4/locales/it.cjs +1 -0
  263. package/v4/locales/it.js +1 -0
  264. package/v4/locales/ja.cjs +1 -0
  265. package/v4/locales/ja.js +1 -0
  266. package/v4/locales/ka.cjs +1 -0
  267. package/v4/locales/ka.js +1 -0
  268. package/v4/locales/km.cjs +1 -0
  269. package/v4/locales/km.js +1 -0
  270. package/v4/locales/kn.cjs +1 -0
  271. package/v4/locales/kn.js +1 -0
  272. package/v4/locales/ko.cjs +1 -0
  273. package/v4/locales/ko.js +1 -0
  274. package/v4/locales/lt.cjs +1 -0
  275. package/v4/locales/lt.js +1 -0
  276. package/v4/locales/mk.cjs +1 -0
  277. package/v4/locales/mk.js +1 -0
  278. package/v4/locales/ms.cjs +1 -0
  279. package/v4/locales/ms.js +1 -0
  280. package/v4/locales/ne.cjs +1 -0
  281. package/v4/locales/ne.js +1 -0
  282. package/v4/locales/nl.cjs +1 -0
  283. package/v4/locales/nl.js +1 -0
  284. package/v4/locales/nn.cjs +1 -0
  285. package/v4/locales/nn.js +1 -0
  286. package/v4/locales/no.cjs +1 -0
  287. package/v4/locales/no.js +1 -0
  288. package/v4/locales/ota.cjs +1 -0
  289. package/v4/locales/ota.js +1 -0
  290. package/v4/locales/pl.cjs +1 -0
  291. package/v4/locales/pl.js +1 -0
  292. package/v4/locales/ps.cjs +1 -0
  293. package/v4/locales/ps.js +1 -0
  294. package/v4/locales/pt-BR.cjs +1 -0
  295. package/v4/locales/pt-BR.js +1 -0
  296. package/v4/locales/pt.cjs +1 -0
  297. package/v4/locales/pt.js +1 -0
  298. package/v4/locales/ro.cjs +1 -0
  299. package/v4/locales/ro.js +1 -0
  300. package/v4/locales/ru.cjs +1 -0
  301. package/v4/locales/ru.js +1 -0
  302. package/v4/locales/sk.cjs +1 -0
  303. package/v4/locales/sk.js +1 -0
  304. package/v4/locales/sl.cjs +1 -0
  305. package/v4/locales/sl.js +1 -0
  306. package/v4/locales/sv.cjs +1 -0
  307. package/v4/locales/sv.js +1 -0
  308. package/v4/locales/ta.cjs +1 -0
  309. package/v4/locales/ta.js +1 -0
  310. package/v4/locales/th.cjs +1 -0
  311. package/v4/locales/th.js +1 -0
  312. package/v4/locales/tk.cjs +1 -0
  313. package/v4/locales/tk.js +1 -0
  314. package/v4/locales/tr.cjs +1 -0
  315. package/v4/locales/tr.js +1 -0
  316. package/v4/locales/uk.cjs +1 -0
  317. package/v4/locales/uk.js +1 -0
  318. package/v4/locales/ur.cjs +1 -0
  319. package/v4/locales/ur.js +1 -0
  320. package/v4/locales/uz.cjs +1 -0
  321. package/v4/locales/uz.js +1 -0
  322. package/v4/locales/vi.cjs +1 -0
  323. package/v4/locales/vi.js +1 -0
  324. package/v4/locales/yo.cjs +1 -0
  325. package/v4/locales/yo.js +1 -0
  326. package/v4/locales/zh-CN.cjs +1 -0
  327. package/v4/locales/zh-CN.js +1 -0
  328. package/v4/locales/zh-TW.cjs +1 -0
  329. package/v4/locales/zh-TW.js +1 -0
  330. package/v4/mini/checks.cjs +20 -2
  331. package/v4/mini/checks.d.cts +1 -1
  332. package/v4/mini/checks.d.ts +1 -1
  333. package/v4/mini/checks.js +1 -1
  334. package/v4/mini/coerce.cjs +19 -0
  335. package/v4/mini/deep-partial.cjs +19 -0
  336. package/v4/mini/external.cjs +22 -1
  337. package/v4/mini/external.d.cts +1 -1
  338. package/v4/mini/external.d.ts +1 -1
  339. package/v4/mini/external.js +1 -1
  340. package/v4/mini/in-out.cjs +19 -0
  341. package/v4/mini/index.cjs +19 -0
  342. package/v4/mini/iso.cjs +19 -0
  343. package/v4/mini/parse.cjs +19 -0
  344. package/v4/mini/schemas.cjs +42 -4
  345. package/v4/mini/schemas.d.cts +8 -0
  346. package/v4/mini/schemas.d.ts +8 -0
  347. package/v4/mini/schemas.js +19 -2
  348. package/v4-mini/index.cjs +19 -0
@@ -149,12 +149,13 @@ function resolveRef(ref, ctx) {
149
149
  throw new Error(`Reference not found: ${ref}`);
150
150
  }
151
151
  /**
152
- * Rejects every own key that fails `keySchema`, before `objectSchema` runs. The
153
- * guard has to see the raw input: an object parse drops `__proto__` and can add
154
- * keys from a property `default`, so its output is not the set of names the
155
- * instance actually carried.
152
+ * Enforces the keywords that constrain an object's own keys — `propertyNames`,
153
+ * `minProperties`, `maxProperties` before `objectSchema` runs. The guard has
154
+ * to see the raw input: an object parse drops `__proto__` and can add keys from
155
+ * a property `default`, so its output is not the set of names the instance
156
+ * actually carried.
156
157
  */
157
- function checkPropertyNames(objectSchema, keySchema) {
158
+ function checkObjectGuards(objectSchema, guards) {
158
159
  // An identity transform, not z.any(), so `toJSONSchema` reports the object on both the input and the output side of the pipe.
159
160
  const guard = z
160
161
  .transform((value) => value)
@@ -162,22 +163,209 @@ function checkPropertyNames(objectSchema, keySchema) {
162
163
  const value = payload.value;
163
164
  if (typeof value !== "object" || value === null || Array.isArray(value))
164
165
  return;
165
- for (const key of Object.getOwnPropertyNames(value)) {
166
- const result = keySchema.safeParse(key);
167
- if (result.success)
168
- continue;
166
+ const keys = Object.getOwnPropertyNames(value);
167
+ if (guards.minProperties !== undefined && keys.length < guards.minProperties) {
169
168
  payload.issues.push({
170
- code: "invalid_key",
171
- origin: "record",
172
- issues: result.error.issues,
173
- input: key,
174
- path: [key],
169
+ origin: "object",
170
+ code: "too_small",
171
+ minimum: guards.minProperties,
172
+ inclusive: true,
173
+ message: `Too small: expected object to have >=${guards.minProperties} properties`,
174
+ input: value,
175
+ inst: objectSchema,
175
176
  continue: true,
176
177
  });
177
178
  }
179
+ if (guards.maxProperties !== undefined && keys.length > guards.maxProperties) {
180
+ payload.issues.push({
181
+ origin: "object",
182
+ code: "too_big",
183
+ maximum: guards.maxProperties,
184
+ inclusive: true,
185
+ message: `Too big: expected object to have <=${guards.maxProperties} properties`,
186
+ input: value,
187
+ inst: objectSchema,
188
+ continue: true,
189
+ });
190
+ }
191
+ if (guards.keySchema) {
192
+ for (const key of keys) {
193
+ const result = guards.keySchema.safeParse(key);
194
+ if (result.success)
195
+ continue;
196
+ payload.issues.push({
197
+ code: "invalid_key",
198
+ origin: "record",
199
+ issues: result.error.issues,
200
+ input: key,
201
+ path: [key],
202
+ continue: true,
203
+ });
204
+ }
205
+ }
178
206
  });
179
207
  return guard.pipe(objectSchema);
180
208
  }
209
+ /**
210
+ * An injective string for JSON data: two values share a key exactly when `uniqueItems` calls them
211
+ * equal, so duplicate detection is a Map lookup rather than a pairwise walk. Strings and keys are
212
+ * length-prefixed so no value can spell another one. `null` means "never equal to anything" — a
213
+ * cycle or a NaN, neither of which JSON can express.
214
+ */
215
+ function canonicalKey(value, seen) {
216
+ if (value === null)
217
+ return "z";
218
+ const type = typeof value;
219
+ if (type !== "object") {
220
+ if (type === "number" && Number.isNaN(value))
221
+ return null;
222
+ const raw = String(value);
223
+ return `${type[0]}${raw.length}:${raw}`;
224
+ }
225
+ if (seen.has(value))
226
+ return null;
227
+ seen.add(value);
228
+ try {
229
+ if (Array.isArray(value)) {
230
+ const parts = [];
231
+ for (const item of value) {
232
+ const key = canonicalKey(item, seen);
233
+ if (key === null)
234
+ return null;
235
+ parts.push(key);
236
+ }
237
+ return `a${parts.length}:[${parts.join(",")}]`;
238
+ }
239
+ const keys = Object.keys(value).sort();
240
+ const parts = [];
241
+ for (const k of keys) {
242
+ const key = canonicalKey(value[k], seen);
243
+ if (key === null)
244
+ return null;
245
+ parts.push(`${k.length}:${k}=${key}`);
246
+ }
247
+ return `o${parts.length}:{${parts.join(",")}}`;
248
+ }
249
+ finally {
250
+ seen.delete(value);
251
+ }
252
+ }
253
+ // keywords whose value is a schema or an array of them
254
+ const SCHEMA_KEYWORDS = /*@__PURE__*/ new Set([
255
+ "items",
256
+ "prefixItems",
257
+ "additionalItems",
258
+ "additionalProperties",
259
+ "contains",
260
+ "propertyNames",
261
+ "not",
262
+ "if",
263
+ "then",
264
+ "else",
265
+ "allOf",
266
+ "anyOf",
267
+ "oneOf",
268
+ "unevaluatedItems",
269
+ "unevaluatedProperties",
270
+ "contentSchema",
271
+ ]);
272
+ // keywords whose value maps names to schemas. draft-7 `dependencies` also allows an array of property names, which holds no schema to walk
273
+ const SCHEMA_MAP_KEYWORDS = /*@__PURE__*/ new Set([
274
+ "properties",
275
+ "patternProperties",
276
+ "dependentSchemas",
277
+ "dependencies",
278
+ "$defs",
279
+ "definitions",
280
+ ]);
281
+ /**
282
+ * True when a subschema holds a `$ref` anywhere a schema can appear. Only those positions are
283
+ * walked: `$ref` under `default` or an `x-` annotation is instance data with an ordinary key, and
284
+ * reading it as a reference would drop a constraint that resolves perfectly well.
285
+ */
286
+ function containsRef(value) {
287
+ if (typeof value !== "object" || value === null)
288
+ return false;
289
+ if (Array.isArray(value))
290
+ return value.some(containsRef);
291
+ if (typeof value.$ref === "string")
292
+ return true;
293
+ return Object.entries(value).some(([key, sub]) => {
294
+ if (SCHEMA_KEYWORDS.has(key))
295
+ return containsRef(sub);
296
+ if (!SCHEMA_MAP_KEYWORDS.has(key) || typeof sub !== "object" || sub === null)
297
+ return false;
298
+ return Object.values(sub).some(containsRef);
299
+ });
300
+ }
301
+ function plural(n) {
302
+ return n === 1 ? "element" : "elements";
303
+ }
304
+ /**
305
+ * Enforces `uniqueItems` and the `contains` family before `arraySchema` runs, for the same reason
306
+ * `checkObjectGuards` runs pre-pipe: an item parse can apply a nested `default`, so the parsed
307
+ * array is not the instance the keywords are defined over. A guard issue aborts the pipe, so
308
+ * `minItems`/`maxItems` are not also reported on the same parse — instance correctness is worth
309
+ * more than co-reporting two issues.
310
+ */
311
+ function checkArrayGuards(arraySchema, guards) {
312
+ // An identity transform, not z.any(), so `toJSONSchema` reports the array on both the input and the output side of the pipe.
313
+ const guard = z
314
+ .transform((value) => value)
315
+ .check((payload) => {
316
+ const items = payload.value;
317
+ if (!Array.isArray(items))
318
+ return;
319
+ if (guards.uniqueItems === true) {
320
+ const firstSeen = new Map();
321
+ for (let i = 0; i < items.length; i++) {
322
+ const key = canonicalKey(items[i], new Set());
323
+ if (key === null)
324
+ continue;
325
+ const first = firstSeen.get(key);
326
+ if (first === undefined) {
327
+ firstSeen.set(key, i);
328
+ continue;
329
+ }
330
+ payload.issues.push({
331
+ code: "custom",
332
+ message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
333
+ input: items,
334
+ path: [i],
335
+ continue: true,
336
+ });
337
+ }
338
+ }
339
+ if (guards.containsSchema) {
340
+ const minContains = guards.minContains ?? 1;
341
+ // one past the ceiling already proves the failure, and nothing above it changes the verdict
342
+ const ceiling = guards.maxContains !== undefined ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
343
+ let matches = 0;
344
+ for (const item of items) {
345
+ if (guards.containsSchema.safeParse(item).success && ++matches >= ceiling)
346
+ break;
347
+ }
348
+ if (matches < minContains) {
349
+ // the scan ran to the end, so this count is exact
350
+ payload.issues.push({
351
+ code: "custom",
352
+ message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches}`,
353
+ input: items,
354
+ continue: true,
355
+ });
356
+ }
357
+ if (guards.maxContains !== undefined && matches > guards.maxContains) {
358
+ payload.issues.push({
359
+ code: "custom",
360
+ message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
361
+ input: items,
362
+ continue: true,
363
+ });
364
+ }
365
+ }
366
+ });
367
+ return guard.pipe(arraySchema);
368
+ }
181
369
  function getTupleRest(restSchema, ctx) {
182
370
  if (restSchema === false) {
183
371
  return undefined;
@@ -342,6 +530,9 @@ function convertBaseSchema(schema, ctx) {
342
530
  else if (format === "credit_card") {
343
531
  stringSchema = stringSchema.check(z.creditCard());
344
532
  }
533
+ else if (format === "iban") {
534
+ stringSchema = stringSchema.check(z.iban());
535
+ }
345
536
  else if (format === "jwt") {
346
537
  stringSchema = stringSchema.check(z.jwt());
347
538
  }
@@ -509,17 +700,23 @@ function convertBaseSchema(schema, ctx) {
509
700
  }
510
701
  }
511
702
  // propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
512
- if (schema.propertyNames !== undefined && schema.propertyNames !== true) {
513
- // Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
514
- const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
515
- ? { type: "string", ...schema.propertyNames }
516
- : schema.propertyNames;
517
- zodSchema = checkPropertyNames(zodSchema, convertSchema(keyJSONSchema, ctx));
703
+ const hasKeyGuard = schema.propertyNames !== undefined && schema.propertyNames !== true;
704
+ const minProperties = typeof schema.minProperties === "number" ? schema.minProperties : undefined;
705
+ const maxProperties = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
706
+ if (hasKeyGuard || minProperties !== undefined || maxProperties !== undefined) {
707
+ let keySchema;
708
+ if (hasKeyGuard) {
709
+ // Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
710
+ const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
711
+ ? { type: "string", ...schema.propertyNames }
712
+ : schema.propertyNames;
713
+ keySchema = convertSchema(keyJSONSchema, ctx);
714
+ }
715
+ zodSchema = checkObjectGuards(zodSchema, { keySchema, minProperties, maxProperties });
518
716
  }
519
717
  break;
520
718
  }
521
719
  case "array": {
522
- // TODO: uniqueItems and contains/minContains/maxContains are not supported
523
720
  // Check if this is a tuple (prefixItems or items as array)
524
721
  const prefixItems = schema.prefixItems;
525
722
  const items = schema.items;
@@ -572,6 +769,15 @@ function convertBaseSchema(schema, ctx) {
572
769
  // No items specified - array of any
573
770
  zodSchema = z.array(z.any());
574
771
  }
772
+ // minContains/maxContains only constrain anything when `contains` itself is present
773
+ if (schema.uniqueItems === true || schema.contains !== undefined) {
774
+ zodSchema = checkArrayGuards(zodSchema, {
775
+ uniqueItems: schema.uniqueItems === true,
776
+ containsSchema: schema.contains !== undefined ? convertSchema(schema.contains, ctx) : undefined,
777
+ minContains: typeof schema.minContains === "number" ? schema.minContains : undefined,
778
+ maxContains: typeof schema.maxContains === "number" ? schema.maxContains : undefined,
779
+ });
780
+ }
575
781
  break;
576
782
  }
577
783
  default:
@@ -639,9 +845,24 @@ function convertSchema(schema, ctx) {
639
845
  extraMeta[key] = schema[key];
640
846
  }
641
847
  }
642
- // `propertyNames` is enforced by a key guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares.
643
- if (schema.propertyNames !== undefined && schema.type === "object" && schema.$ref === undefined) {
644
- extraMeta.propertyNames = schema.propertyNames;
848
+ // `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
849
+ if (schema.type === "object" && schema.$ref === undefined) {
850
+ if (schema.propertyNames !== undefined && !containsRef(schema.propertyNames)) {
851
+ extraMeta.propertyNames = schema.propertyNames;
852
+ }
853
+ for (const key of ["minProperties", "maxProperties"]) {
854
+ if (schema[key] !== undefined)
855
+ extraMeta[key] = schema[key];
856
+ }
857
+ }
858
+ if (schema.type === "array" && schema.$ref === undefined) {
859
+ if (schema.contains !== undefined && !containsRef(schema.contains)) {
860
+ extraMeta.contains = schema.contains;
861
+ }
862
+ for (const key of ["uniqueItems", "minContains", "maxContains"]) {
863
+ if (schema[key] !== undefined)
864
+ extraMeta[key] = schema[key];
865
+ }
645
866
  }
646
867
  for (const key of Object.keys(schema)) {
647
868
  if (!RECOGNIZED_KEYS.has(key)) {
@@ -684,3 +905,22 @@ function fromJSONSchema(schema, params) {
684
905
  };
685
906
  return convertSchema(normalized, ctx);
686
907
  }
908
+
909
+ // seal-cjs-exports
910
+ (function () {
911
+ var keys = Object.getOwnPropertyNames(exports);
912
+ for (var i = 0; i < keys.length; i++) {
913
+ var desc = Object.getOwnPropertyDescriptor(exports, keys[i]);
914
+ if (!desc || !desc.get || !desc.configurable) continue;
915
+ var value;
916
+ try {
917
+ value = desc.get();
918
+ } catch (e) {
919
+ continue;
920
+ }
921
+ // a circular require may not have settled this one yet, so leave it live
922
+ if (value === undefined) continue;
923
+ Object.defineProperty(exports, keys[i], { value: value, writable: false, enumerable: desc.enumerable, configurable: false });
924
+ }
925
+ Object.freeze(exports);
926
+ })();
@@ -123,12 +123,13 @@ function resolveRef(ref, ctx) {
123
123
  throw new Error(`Reference not found: ${ref}`);
124
124
  }
125
125
  /**
126
- * Rejects every own key that fails `keySchema`, before `objectSchema` runs. The
127
- * guard has to see the raw input: an object parse drops `__proto__` and can add
128
- * keys from a property `default`, so its output is not the set of names the
129
- * instance actually carried.
126
+ * Enforces the keywords that constrain an object's own keys — `propertyNames`,
127
+ * `minProperties`, `maxProperties` before `objectSchema` runs. The guard has
128
+ * to see the raw input: an object parse drops `__proto__` and can add keys from
129
+ * a property `default`, so its output is not the set of names the instance
130
+ * actually carried.
130
131
  */
131
- function checkPropertyNames(objectSchema, keySchema) {
132
+ function checkObjectGuards(objectSchema, guards) {
132
133
  // An identity transform, not z.any(), so `toJSONSchema` reports the object on both the input and the output side of the pipe.
133
134
  const guard = z
134
135
  .transform((value) => value)
@@ -136,22 +137,209 @@ function checkPropertyNames(objectSchema, keySchema) {
136
137
  const value = payload.value;
137
138
  if (typeof value !== "object" || value === null || Array.isArray(value))
138
139
  return;
139
- for (const key of Object.getOwnPropertyNames(value)) {
140
- const result = keySchema.safeParse(key);
141
- if (result.success)
142
- continue;
140
+ const keys = Object.getOwnPropertyNames(value);
141
+ if (guards.minProperties !== undefined && keys.length < guards.minProperties) {
143
142
  payload.issues.push({
144
- code: "invalid_key",
145
- origin: "record",
146
- issues: result.error.issues,
147
- input: key,
148
- path: [key],
143
+ origin: "object",
144
+ code: "too_small",
145
+ minimum: guards.minProperties,
146
+ inclusive: true,
147
+ message: `Too small: expected object to have >=${guards.minProperties} properties`,
148
+ input: value,
149
+ inst: objectSchema,
149
150
  continue: true,
150
151
  });
151
152
  }
153
+ if (guards.maxProperties !== undefined && keys.length > guards.maxProperties) {
154
+ payload.issues.push({
155
+ origin: "object",
156
+ code: "too_big",
157
+ maximum: guards.maxProperties,
158
+ inclusive: true,
159
+ message: `Too big: expected object to have <=${guards.maxProperties} properties`,
160
+ input: value,
161
+ inst: objectSchema,
162
+ continue: true,
163
+ });
164
+ }
165
+ if (guards.keySchema) {
166
+ for (const key of keys) {
167
+ const result = guards.keySchema.safeParse(key);
168
+ if (result.success)
169
+ continue;
170
+ payload.issues.push({
171
+ code: "invalid_key",
172
+ origin: "record",
173
+ issues: result.error.issues,
174
+ input: key,
175
+ path: [key],
176
+ continue: true,
177
+ });
178
+ }
179
+ }
152
180
  });
153
181
  return guard.pipe(objectSchema);
154
182
  }
183
+ /**
184
+ * An injective string for JSON data: two values share a key exactly when `uniqueItems` calls them
185
+ * equal, so duplicate detection is a Map lookup rather than a pairwise walk. Strings and keys are
186
+ * length-prefixed so no value can spell another one. `null` means "never equal to anything" — a
187
+ * cycle or a NaN, neither of which JSON can express.
188
+ */
189
+ function canonicalKey(value, seen) {
190
+ if (value === null)
191
+ return "z";
192
+ const type = typeof value;
193
+ if (type !== "object") {
194
+ if (type === "number" && Number.isNaN(value))
195
+ return null;
196
+ const raw = String(value);
197
+ return `${type[0]}${raw.length}:${raw}`;
198
+ }
199
+ if (seen.has(value))
200
+ return null;
201
+ seen.add(value);
202
+ try {
203
+ if (Array.isArray(value)) {
204
+ const parts = [];
205
+ for (const item of value) {
206
+ const key = canonicalKey(item, seen);
207
+ if (key === null)
208
+ return null;
209
+ parts.push(key);
210
+ }
211
+ return `a${parts.length}:[${parts.join(",")}]`;
212
+ }
213
+ const keys = Object.keys(value).sort();
214
+ const parts = [];
215
+ for (const k of keys) {
216
+ const key = canonicalKey(value[k], seen);
217
+ if (key === null)
218
+ return null;
219
+ parts.push(`${k.length}:${k}=${key}`);
220
+ }
221
+ return `o${parts.length}:{${parts.join(",")}}`;
222
+ }
223
+ finally {
224
+ seen.delete(value);
225
+ }
226
+ }
227
+ // keywords whose value is a schema or an array of them
228
+ const SCHEMA_KEYWORDS = /*@__PURE__*/ new Set([
229
+ "items",
230
+ "prefixItems",
231
+ "additionalItems",
232
+ "additionalProperties",
233
+ "contains",
234
+ "propertyNames",
235
+ "not",
236
+ "if",
237
+ "then",
238
+ "else",
239
+ "allOf",
240
+ "anyOf",
241
+ "oneOf",
242
+ "unevaluatedItems",
243
+ "unevaluatedProperties",
244
+ "contentSchema",
245
+ ]);
246
+ // keywords whose value maps names to schemas. draft-7 `dependencies` also allows an array of property names, which holds no schema to walk
247
+ const SCHEMA_MAP_KEYWORDS = /*@__PURE__*/ new Set([
248
+ "properties",
249
+ "patternProperties",
250
+ "dependentSchemas",
251
+ "dependencies",
252
+ "$defs",
253
+ "definitions",
254
+ ]);
255
+ /**
256
+ * True when a subschema holds a `$ref` anywhere a schema can appear. Only those positions are
257
+ * walked: `$ref` under `default` or an `x-` annotation is instance data with an ordinary key, and
258
+ * reading it as a reference would drop a constraint that resolves perfectly well.
259
+ */
260
+ function containsRef(value) {
261
+ if (typeof value !== "object" || value === null)
262
+ return false;
263
+ if (Array.isArray(value))
264
+ return value.some(containsRef);
265
+ if (typeof value.$ref === "string")
266
+ return true;
267
+ return Object.entries(value).some(([key, sub]) => {
268
+ if (SCHEMA_KEYWORDS.has(key))
269
+ return containsRef(sub);
270
+ if (!SCHEMA_MAP_KEYWORDS.has(key) || typeof sub !== "object" || sub === null)
271
+ return false;
272
+ return Object.values(sub).some(containsRef);
273
+ });
274
+ }
275
+ function plural(n) {
276
+ return n === 1 ? "element" : "elements";
277
+ }
278
+ /**
279
+ * Enforces `uniqueItems` and the `contains` family before `arraySchema` runs, for the same reason
280
+ * `checkObjectGuards` runs pre-pipe: an item parse can apply a nested `default`, so the parsed
281
+ * array is not the instance the keywords are defined over. A guard issue aborts the pipe, so
282
+ * `minItems`/`maxItems` are not also reported on the same parse — instance correctness is worth
283
+ * more than co-reporting two issues.
284
+ */
285
+ function checkArrayGuards(arraySchema, guards) {
286
+ // An identity transform, not z.any(), so `toJSONSchema` reports the array on both the input and the output side of the pipe.
287
+ const guard = z
288
+ .transform((value) => value)
289
+ .check((payload) => {
290
+ const items = payload.value;
291
+ if (!Array.isArray(items))
292
+ return;
293
+ if (guards.uniqueItems === true) {
294
+ const firstSeen = new Map();
295
+ for (let i = 0; i < items.length; i++) {
296
+ const key = canonicalKey(items[i], new Set());
297
+ if (key === null)
298
+ continue;
299
+ const first = firstSeen.get(key);
300
+ if (first === undefined) {
301
+ firstSeen.set(key, i);
302
+ continue;
303
+ }
304
+ payload.issues.push({
305
+ code: "custom",
306
+ message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
307
+ input: items,
308
+ path: [i],
309
+ continue: true,
310
+ });
311
+ }
312
+ }
313
+ if (guards.containsSchema) {
314
+ const minContains = guards.minContains ?? 1;
315
+ // one past the ceiling already proves the failure, and nothing above it changes the verdict
316
+ const ceiling = guards.maxContains !== undefined ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
317
+ let matches = 0;
318
+ for (const item of items) {
319
+ if (guards.containsSchema.safeParse(item).success && ++matches >= ceiling)
320
+ break;
321
+ }
322
+ if (matches < minContains) {
323
+ // the scan ran to the end, so this count is exact
324
+ payload.issues.push({
325
+ code: "custom",
326
+ message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches}`,
327
+ input: items,
328
+ continue: true,
329
+ });
330
+ }
331
+ if (guards.maxContains !== undefined && matches > guards.maxContains) {
332
+ payload.issues.push({
333
+ code: "custom",
334
+ message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
335
+ input: items,
336
+ continue: true,
337
+ });
338
+ }
339
+ }
340
+ });
341
+ return guard.pipe(arraySchema);
342
+ }
155
343
  function getTupleRest(restSchema, ctx) {
156
344
  if (restSchema === false) {
157
345
  return undefined;
@@ -316,6 +504,9 @@ function convertBaseSchema(schema, ctx) {
316
504
  else if (format === "credit_card") {
317
505
  stringSchema = stringSchema.check(z.creditCard());
318
506
  }
507
+ else if (format === "iban") {
508
+ stringSchema = stringSchema.check(z.iban());
509
+ }
319
510
  else if (format === "jwt") {
320
511
  stringSchema = stringSchema.check(z.jwt());
321
512
  }
@@ -483,17 +674,23 @@ function convertBaseSchema(schema, ctx) {
483
674
  }
484
675
  }
485
676
  // propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
486
- if (schema.propertyNames !== undefined && schema.propertyNames !== true) {
487
- // Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
488
- const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
489
- ? { type: "string", ...schema.propertyNames }
490
- : schema.propertyNames;
491
- zodSchema = checkPropertyNames(zodSchema, convertSchema(keyJSONSchema, ctx));
677
+ const hasKeyGuard = schema.propertyNames !== undefined && schema.propertyNames !== true;
678
+ const minProperties = typeof schema.minProperties === "number" ? schema.minProperties : undefined;
679
+ const maxProperties = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
680
+ if (hasKeyGuard || minProperties !== undefined || maxProperties !== undefined) {
681
+ let keySchema;
682
+ if (hasKeyGuard) {
683
+ // Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
684
+ const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
685
+ ? { type: "string", ...schema.propertyNames }
686
+ : schema.propertyNames;
687
+ keySchema = convertSchema(keyJSONSchema, ctx);
688
+ }
689
+ zodSchema = checkObjectGuards(zodSchema, { keySchema, minProperties, maxProperties });
492
690
  }
493
691
  break;
494
692
  }
495
693
  case "array": {
496
- // TODO: uniqueItems and contains/minContains/maxContains are not supported
497
694
  // Check if this is a tuple (prefixItems or items as array)
498
695
  const prefixItems = schema.prefixItems;
499
696
  const items = schema.items;
@@ -546,6 +743,15 @@ function convertBaseSchema(schema, ctx) {
546
743
  // No items specified - array of any
547
744
  zodSchema = z.array(z.any());
548
745
  }
746
+ // minContains/maxContains only constrain anything when `contains` itself is present
747
+ if (schema.uniqueItems === true || schema.contains !== undefined) {
748
+ zodSchema = checkArrayGuards(zodSchema, {
749
+ uniqueItems: schema.uniqueItems === true,
750
+ containsSchema: schema.contains !== undefined ? convertSchema(schema.contains, ctx) : undefined,
751
+ minContains: typeof schema.minContains === "number" ? schema.minContains : undefined,
752
+ maxContains: typeof schema.maxContains === "number" ? schema.maxContains : undefined,
753
+ });
754
+ }
549
755
  break;
550
756
  }
551
757
  default:
@@ -613,9 +819,24 @@ function convertSchema(schema, ctx) {
613
819
  extraMeta[key] = schema[key];
614
820
  }
615
821
  }
616
- // `propertyNames` is enforced by a key guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares.
617
- if (schema.propertyNames !== undefined && schema.type === "object" && schema.$ref === undefined) {
618
- extraMeta.propertyNames = schema.propertyNames;
822
+ // `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
823
+ if (schema.type === "object" && schema.$ref === undefined) {
824
+ if (schema.propertyNames !== undefined && !containsRef(schema.propertyNames)) {
825
+ extraMeta.propertyNames = schema.propertyNames;
826
+ }
827
+ for (const key of ["minProperties", "maxProperties"]) {
828
+ if (schema[key] !== undefined)
829
+ extraMeta[key] = schema[key];
830
+ }
831
+ }
832
+ if (schema.type === "array" && schema.$ref === undefined) {
833
+ if (schema.contains !== undefined && !containsRef(schema.contains)) {
834
+ extraMeta.contains = schema.contains;
835
+ }
836
+ for (const key of ["uniqueItems", "minContains", "maxContains"]) {
837
+ if (schema[key] !== undefined)
838
+ extraMeta[key] = schema[key];
839
+ }
619
840
  }
620
841
  for (const key of Object.keys(schema)) {
621
842
  if (!RECOGNIZED_KEYS.has(key)) {