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.
- package/compile.cjs +19 -0
- package/index.cjs +19 -0
- package/locales/index.cjs +19 -0
- package/mini/index.cjs +19 -0
- package/package.json +8 -7
- package/src/v3/helpers/errorUtil.ts +1 -1
- package/src/v4/classic/checks.ts +0 -1
- package/src/v4/classic/external.ts +2 -0
- package/src/v4/classic/from-json-schema.ts +248 -25
- package/src/v4/classic/schemas.ts +255 -164
- package/src/v4/classic/tests/assignability.test.ts +59 -9
- package/src/v4/classic/tests/bigint.test.ts +6 -0
- package/src/v4/classic/tests/continuability.test.ts +2 -2
- package/src/v4/classic/tests/cyclic-data.test.ts +316 -0
- package/src/v4/classic/tests/default.test.ts +46 -0
- package/src/v4/classic/tests/enum.test.ts +9 -0
- package/src/v4/classic/tests/error.test.ts +130 -1
- package/src/v4/classic/tests/firstparty.test.ts +4 -0
- package/src/v4/classic/tests/from-json-schema.test.ts +157 -0
- package/src/v4/classic/tests/iban.test.ts +81 -0
- package/src/v4/classic/tests/instance-footprint.test.ts +18 -2
- package/src/v4/classic/tests/instanceof.test.ts +27 -7
- package/src/v4/classic/tests/issue-schema.test.ts +88 -56
- package/src/v4/classic/tests/object.test.ts +56 -0
- package/src/v4/classic/tests/optin-ladder.test.ts +3 -2
- package/src/v4/classic/tests/properties.test.ts +225 -0
- package/src/v4/classic/tests/recursive-types.test.ts +1 -1
- package/src/v4/classic/tests/standard-schema.test.ts +19 -0
- package/src/v4/classic/tests/string.test.ts +25 -0
- package/src/v4/classic/tests/template-literal.test.ts +33 -1
- package/src/v4/classic/tests/to-json-schema.test.ts +147 -3
- package/src/v4/classic/tests/validate.test.ts +569 -0
- package/src/v4/core/api.ts +30 -6
- package/src/v4/core/checks.ts +1 -101
- package/src/v4/core/compile.ts +193 -81
- package/src/v4/core/doc.ts +6 -2
- package/src/v4/core/errors.ts +3 -6
- package/src/v4/core/json-schema-generator.ts +2 -2
- package/src/v4/core/json-schema-processors.ts +226 -68
- package/src/v4/core/memoizer.ts +162 -36
- package/src/v4/core/parse.ts +37 -16
- package/src/v4/core/regexes.ts +10 -4
- package/src/v4/core/schemas.ts +314 -83
- package/src/v4/core/tests/cached.test.ts +54 -0
- package/src/v4/core/tests/compile-generative.test.ts +445 -0
- package/src/v4/core/tests/compile.test.ts +222 -2
- package/src/v4/core/tests/email-regex.test.ts +140 -0
- package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
- package/src/v4/core/to-json-schema.ts +10 -8
- package/src/v4/core/util.ts +222 -147
- package/src/v4/core/versions.ts +2 -2
- package/src/v4/core/visit.ts +11 -0
- package/src/v4/locales/ar.ts +1 -0
- package/src/v4/locales/az.ts +1 -0
- package/src/v4/locales/be.ts +1 -0
- package/src/v4/locales/bg.ts +1 -0
- package/src/v4/locales/bn.ts +1 -0
- package/src/v4/locales/ca.ts +1 -0
- package/src/v4/locales/ckb.ts +1 -0
- package/src/v4/locales/cs.ts +1 -0
- package/src/v4/locales/da.ts +1 -0
- package/src/v4/locales/de.ts +1 -0
- package/src/v4/locales/el.ts +1 -0
- package/src/v4/locales/en.ts +1 -0
- package/src/v4/locales/eo.ts +1 -0
- package/src/v4/locales/es.ts +1 -0
- package/src/v4/locales/fa.ts +1 -0
- package/src/v4/locales/fi.ts +1 -0
- package/src/v4/locales/fr-CA.ts +1 -0
- package/src/v4/locales/fr.ts +1 -0
- package/src/v4/locales/gu.ts +1 -0
- package/src/v4/locales/he.ts +1 -0
- package/src/v4/locales/hi.ts +1 -0
- package/src/v4/locales/hr.ts +1 -0
- package/src/v4/locales/hu.ts +1 -0
- package/src/v4/locales/hy.ts +1 -0
- package/src/v4/locales/id.ts +1 -0
- package/src/v4/locales/is.ts +1 -0
- package/src/v4/locales/it.ts +1 -0
- package/src/v4/locales/ja.ts +1 -0
- package/src/v4/locales/ka.ts +1 -0
- package/src/v4/locales/km.ts +1 -0
- package/src/v4/locales/kn.ts +1 -0
- package/src/v4/locales/ko.ts +1 -0
- package/src/v4/locales/lt.ts +1 -0
- package/src/v4/locales/mk.ts +1 -0
- package/src/v4/locales/ms.ts +1 -0
- package/src/v4/locales/ne.ts +1 -0
- package/src/v4/locales/nl.ts +1 -0
- package/src/v4/locales/nn.ts +1 -0
- package/src/v4/locales/no.ts +1 -0
- package/src/v4/locales/ota.ts +1 -0
- package/src/v4/locales/pl.ts +1 -0
- package/src/v4/locales/ps.ts +1 -0
- package/src/v4/locales/pt-BR.ts +1 -0
- package/src/v4/locales/pt.ts +1 -0
- package/src/v4/locales/ro.ts +1 -0
- package/src/v4/locales/ru.ts +1 -0
- package/src/v4/locales/sk.ts +1 -0
- package/src/v4/locales/sl.ts +1 -0
- package/src/v4/locales/sv.ts +1 -0
- package/src/v4/locales/ta.ts +1 -0
- package/src/v4/locales/th.ts +1 -0
- package/src/v4/locales/tk.ts +1 -0
- package/src/v4/locales/tr.ts +1 -0
- package/src/v4/locales/uk.ts +1 -0
- package/src/v4/locales/ur.ts +1 -0
- package/src/v4/locales/uz.ts +1 -0
- package/src/v4/locales/vi.ts +1 -0
- package/src/v4/locales/yo.ts +1 -0
- package/src/v4/locales/zh-CN.ts +1 -0
- package/src/v4/locales/zh-TW.ts +1 -0
- package/src/v4/mini/checks.ts +0 -1
- package/src/v4/mini/external.ts +2 -0
- package/src/v4/mini/schemas.ts +38 -2
- package/src/v4/mini/tests/checks.test.ts +1 -1
- package/src/v4/mini/tests/computed.test.ts +11 -17
- package/src/v4/mini/tests/index.test.ts +8 -0
- package/src/v4/mini/tests/string.test.ts +16 -0
- package/v3/ZodError.cjs +19 -0
- package/v3/errors.cjs +19 -0
- package/v3/external.cjs +19 -0
- package/v3/helpers/enumUtil.cjs +19 -0
- package/v3/helpers/errorUtil.cjs +20 -1
- package/v3/helpers/errorUtil.js +1 -1
- package/v3/helpers/parseUtil.cjs +19 -0
- package/v3/helpers/partialUtil.cjs +19 -0
- package/v3/helpers/typeAliases.cjs +19 -0
- package/v3/helpers/util.cjs +19 -0
- package/v3/index.cjs +19 -0
- package/v3/standard-schema.cjs +19 -0
- package/v3/types.cjs +19 -0
- package/v4/classic/checks.cjs +20 -2
- package/v4/classic/checks.d.cts +1 -1
- package/v4/classic/checks.d.ts +1 -1
- package/v4/classic/checks.js +1 -1
- package/v4/classic/coerce.cjs +19 -0
- package/v4/classic/compat.cjs +19 -0
- package/v4/classic/deep-partial.cjs +19 -0
- package/v4/classic/errors.cjs +19 -0
- package/v4/classic/external.cjs +22 -1
- package/v4/classic/external.d.cts +1 -1
- package/v4/classic/external.d.ts +1 -1
- package/v4/classic/external.js +1 -1
- package/v4/classic/from-json-schema.cjs +264 -24
- package/v4/classic/from-json-schema.js +245 -24
- package/v4/classic/in-out.cjs +19 -0
- package/v4/classic/index.cjs +19 -0
- package/v4/classic/iso.cjs +19 -0
- package/v4/classic/parse.cjs +19 -0
- package/v4/classic/schemas.cjs +100 -31
- package/v4/classic/schemas.d.cts +17 -1
- package/v4/classic/schemas.d.ts +17 -1
- package/v4/classic/schemas.js +77 -29
- package/v4/core/api.cjs +37 -2
- package/v4/core/api.d.cts +6 -4
- package/v4/core/api.d.ts +6 -4
- package/v4/core/api.js +17 -2
- package/v4/core/checks.cjs +19 -96
- package/v4/core/checks.d.cts +1 -1
- package/v4/core/checks.d.ts +1 -1
- package/v4/core/checks.js +0 -96
- package/v4/core/compile.cjs +164 -65
- package/v4/core/compile.d.cts +18 -4
- package/v4/core/compile.d.ts +18 -4
- package/v4/core/compile.js +145 -66
- package/v4/core/core.cjs +19 -0
- package/v4/core/doc.cjs +26 -2
- package/v4/core/doc.js +7 -2
- package/v4/core/errors.cjs +21 -5
- package/v4/core/errors.js +2 -5
- package/v4/core/index.cjs +19 -0
- package/v4/core/json-schema-generator.cjs +20 -1
- package/v4/core/json-schema-generator.js +2 -2
- package/v4/core/json-schema-processors.cjs +215 -63
- package/v4/core/json-schema-processors.d.cts +17 -0
- package/v4/core/json-schema-processors.d.ts +17 -0
- package/v4/core/json-schema-processors.js +195 -64
- package/v4/core/json-schema.cjs +19 -0
- package/v4/core/memoizer.cjs +204 -36
- package/v4/core/memoizer.js +162 -36
- package/v4/core/parse.cjs +56 -16
- package/v4/core/parse.js +37 -16
- package/v4/core/regexes.cjs +29 -6
- package/v4/core/regexes.d.cts +2 -0
- package/v4/core/regexes.d.ts +2 -0
- package/v4/core/regexes.js +8 -4
- package/v4/core/registries.cjs +19 -0
- package/v4/core/schemas.cjs +285 -66
- package/v4/core/schemas.d.cts +34 -3
- package/v4/core/schemas.d.ts +34 -3
- package/v4/core/schemas.js +264 -65
- package/v4/core/standard-schema.cjs +19 -0
- package/v4/core/to-json-schema.cjs +29 -7
- package/v4/core/to-json-schema.d.cts +7 -3
- package/v4/core/to-json-schema.d.ts +7 -3
- package/v4/core/to-json-schema.js +8 -7
- package/v4/core/util.cjs +181 -143
- package/v4/core/util.d.cts +25 -4
- package/v4/core/util.d.ts +25 -4
- package/v4/core/util.js +160 -143
- package/v4/core/versions.cjs +21 -2
- package/v4/core/versions.d.cts +1 -1
- package/v4/core/versions.d.ts +1 -1
- package/v4/core/versions.js +2 -2
- package/v4/core/visit.cjs +31 -0
- package/v4/core/visit.js +12 -0
- package/v4/index.cjs +19 -0
- package/v4/locales/ar.cjs +1 -0
- package/v4/locales/ar.js +1 -0
- package/v4/locales/az.cjs +1 -0
- package/v4/locales/az.js +1 -0
- package/v4/locales/be.cjs +1 -0
- package/v4/locales/be.js +1 -0
- package/v4/locales/bg.cjs +1 -0
- package/v4/locales/bg.js +1 -0
- package/v4/locales/bn.cjs +1 -0
- package/v4/locales/bn.js +1 -0
- package/v4/locales/ca.cjs +1 -0
- package/v4/locales/ca.js +1 -0
- package/v4/locales/ckb.cjs +1 -0
- package/v4/locales/ckb.js +1 -0
- package/v4/locales/cs.cjs +1 -0
- package/v4/locales/cs.js +1 -0
- package/v4/locales/da.cjs +1 -0
- package/v4/locales/da.js +1 -0
- package/v4/locales/de.cjs +1 -0
- package/v4/locales/de.js +1 -0
- package/v4/locales/el.cjs +1 -0
- package/v4/locales/el.js +1 -0
- package/v4/locales/en.cjs +1 -0
- package/v4/locales/en.js +1 -0
- package/v4/locales/eo.cjs +1 -0
- package/v4/locales/eo.js +1 -0
- package/v4/locales/es.cjs +1 -0
- package/v4/locales/es.js +1 -0
- package/v4/locales/fa.cjs +1 -0
- package/v4/locales/fa.js +1 -0
- package/v4/locales/fi.cjs +1 -0
- package/v4/locales/fi.js +1 -0
- package/v4/locales/fr-CA.cjs +1 -0
- package/v4/locales/fr-CA.js +1 -0
- package/v4/locales/fr.cjs +1 -0
- package/v4/locales/fr.js +1 -0
- package/v4/locales/gu.cjs +1 -0
- package/v4/locales/gu.js +1 -0
- package/v4/locales/he.cjs +1 -0
- package/v4/locales/he.js +1 -0
- package/v4/locales/hi.cjs +1 -0
- package/v4/locales/hi.js +1 -0
- package/v4/locales/hr.cjs +1 -0
- package/v4/locales/hr.js +1 -0
- package/v4/locales/hu.cjs +1 -0
- package/v4/locales/hu.js +1 -0
- package/v4/locales/hy.cjs +1 -0
- package/v4/locales/hy.js +1 -0
- package/v4/locales/id.cjs +1 -0
- package/v4/locales/id.js +1 -0
- package/v4/locales/index.cjs +19 -0
- package/v4/locales/is.cjs +1 -0
- package/v4/locales/is.js +1 -0
- package/v4/locales/it.cjs +1 -0
- package/v4/locales/it.js +1 -0
- package/v4/locales/ja.cjs +1 -0
- package/v4/locales/ja.js +1 -0
- package/v4/locales/ka.cjs +1 -0
- package/v4/locales/ka.js +1 -0
- package/v4/locales/km.cjs +1 -0
- package/v4/locales/km.js +1 -0
- package/v4/locales/kn.cjs +1 -0
- package/v4/locales/kn.js +1 -0
- package/v4/locales/ko.cjs +1 -0
- package/v4/locales/ko.js +1 -0
- package/v4/locales/lt.cjs +1 -0
- package/v4/locales/lt.js +1 -0
- package/v4/locales/mk.cjs +1 -0
- package/v4/locales/mk.js +1 -0
- package/v4/locales/ms.cjs +1 -0
- package/v4/locales/ms.js +1 -0
- package/v4/locales/ne.cjs +1 -0
- package/v4/locales/ne.js +1 -0
- package/v4/locales/nl.cjs +1 -0
- package/v4/locales/nl.js +1 -0
- package/v4/locales/nn.cjs +1 -0
- package/v4/locales/nn.js +1 -0
- package/v4/locales/no.cjs +1 -0
- package/v4/locales/no.js +1 -0
- package/v4/locales/ota.cjs +1 -0
- package/v4/locales/ota.js +1 -0
- package/v4/locales/pl.cjs +1 -0
- package/v4/locales/pl.js +1 -0
- package/v4/locales/ps.cjs +1 -0
- package/v4/locales/ps.js +1 -0
- package/v4/locales/pt-BR.cjs +1 -0
- package/v4/locales/pt-BR.js +1 -0
- package/v4/locales/pt.cjs +1 -0
- package/v4/locales/pt.js +1 -0
- package/v4/locales/ro.cjs +1 -0
- package/v4/locales/ro.js +1 -0
- package/v4/locales/ru.cjs +1 -0
- package/v4/locales/ru.js +1 -0
- package/v4/locales/sk.cjs +1 -0
- package/v4/locales/sk.js +1 -0
- package/v4/locales/sl.cjs +1 -0
- package/v4/locales/sl.js +1 -0
- package/v4/locales/sv.cjs +1 -0
- package/v4/locales/sv.js +1 -0
- package/v4/locales/ta.cjs +1 -0
- package/v4/locales/ta.js +1 -0
- package/v4/locales/th.cjs +1 -0
- package/v4/locales/th.js +1 -0
- package/v4/locales/tk.cjs +1 -0
- package/v4/locales/tk.js +1 -0
- package/v4/locales/tr.cjs +1 -0
- package/v4/locales/tr.js +1 -0
- package/v4/locales/uk.cjs +1 -0
- package/v4/locales/uk.js +1 -0
- package/v4/locales/ur.cjs +1 -0
- package/v4/locales/ur.js +1 -0
- package/v4/locales/uz.cjs +1 -0
- package/v4/locales/uz.js +1 -0
- package/v4/locales/vi.cjs +1 -0
- package/v4/locales/vi.js +1 -0
- package/v4/locales/yo.cjs +1 -0
- package/v4/locales/yo.js +1 -0
- package/v4/locales/zh-CN.cjs +1 -0
- package/v4/locales/zh-CN.js +1 -0
- package/v4/locales/zh-TW.cjs +1 -0
- package/v4/locales/zh-TW.js +1 -0
- package/v4/mini/checks.cjs +20 -2
- package/v4/mini/checks.d.cts +1 -1
- package/v4/mini/checks.d.ts +1 -1
- package/v4/mini/checks.js +1 -1
- package/v4/mini/coerce.cjs +19 -0
- package/v4/mini/deep-partial.cjs +19 -0
- package/v4/mini/external.cjs +22 -1
- package/v4/mini/external.d.cts +1 -1
- package/v4/mini/external.d.ts +1 -1
- package/v4/mini/external.js +1 -1
- package/v4/mini/in-out.cjs +19 -0
- package/v4/mini/index.cjs +19 -0
- package/v4/mini/iso.cjs +19 -0
- package/v4/mini/parse.cjs +19 -0
- package/v4/mini/schemas.cjs +42 -4
- package/v4/mini/schemas.d.cts +8 -0
- package/v4/mini/schemas.d.ts +8 -0
- package/v4/mini/schemas.js +19 -2
- package/v4-mini/index.cjs +19 -0
package/src/v4/core/memoizer.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as errors from "./errors.js";
|
|
2
|
-
import type { $ZodMemoizer, $ZodType, ParseContextInternal, ParsePayload } from "./schemas.js";
|
|
3
|
-
import
|
|
2
|
+
import type { $ZodMemoizer, $ZodType, $ZodTypeDef, ParseContextInternal, ParsePayload } from "./schemas.js";
|
|
3
|
+
import * as util from "./util.js";
|
|
4
4
|
|
|
5
5
|
export class $ZodCyclicError extends Error {
|
|
6
6
|
constructor() {
|
|
@@ -16,9 +16,10 @@ interface Entry {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
interface State {
|
|
19
|
-
|
|
19
|
+
/** Weak throughout because `attach` caches a bucket on the schema instance, which outlives the parse. */
|
|
20
|
+
buckets: WeakMap<$ZodType, WeakMap<object, Entry>>;
|
|
20
21
|
/** Nodes a back-edge resolved to before they finished. */
|
|
21
|
-
backEdges:
|
|
22
|
+
backEdges: WeakSet<object> | undefined;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/** Keyed off the context object every schema in one parse call already shares. */
|
|
@@ -27,6 +28,11 @@ type WithState = { [STATE]?: State };
|
|
|
27
28
|
|
|
28
29
|
const NO_ISSUES: errors.$ZodRawIssue[] = [];
|
|
29
30
|
|
|
31
|
+
// a value a cycle can close through; callables count, since z.properties asserts on one
|
|
32
|
+
function isRef(value: unknown): value is object {
|
|
33
|
+
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
// Receivers prefix paths in place, so the cache and every hand-out need their own copies.
|
|
31
37
|
function cloneIssues(issues: errors.$ZodRawIssue[]): errors.$ZodRawIssue[] {
|
|
32
38
|
return issues.map((iss) => (iss.path ? { ...iss, path: iss.path.slice() } : { ...iss }));
|
|
@@ -34,38 +40,153 @@ function cloneIssues(issues: errors.$ZodRawIssue[]): errors.$ZodRawIssue[] {
|
|
|
34
40
|
|
|
35
41
|
const recursive: WeakMap<object, boolean> = /*@__PURE__*/ new WeakMap();
|
|
36
42
|
|
|
43
|
+
/** What the walk established, in order of certainty: ordered so the strongest answer among children wins. */
|
|
44
|
+
const NONE = 0;
|
|
45
|
+
const ASSUMED = 1;
|
|
46
|
+
const PROVEN = 2;
|
|
47
|
+
type Answer = typeof NONE | typeof ASSUMED | typeof PROVEN;
|
|
48
|
+
|
|
37
49
|
/** Whether this schema's subtree contains a cycle, so one parse can re-enter it. */
|
|
38
|
-
function isRecursive(inst: $ZodType, stack: Set<object
|
|
50
|
+
function isRecursive(inst: $ZodType, stack: Set<object>, resolve: boolean): Answer {
|
|
39
51
|
const cached = recursive.get(inst);
|
|
40
|
-
if (cached !== undefined) return cached;
|
|
52
|
+
if (cached !== undefined) return cached ? PROVEN : NONE;
|
|
41
53
|
// Relative to the walk in progress, so not cached.
|
|
42
|
-
if (stack.has(inst)) return
|
|
54
|
+
if (stack.has(inst)) return PROVEN;
|
|
43
55
|
stack.add(inst);
|
|
44
56
|
|
|
45
|
-
let result =
|
|
57
|
+
let result: Answer = NONE;
|
|
46
58
|
const check = (child: any) => {
|
|
47
|
-
if (
|
|
59
|
+
if (result !== PROVEN && child?._zod) {
|
|
60
|
+
const answer = isRecursive(child, stack, resolve);
|
|
61
|
+
if (answer > result) result = answer;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen
|
|
66
|
+
const shape = (sh: object, spread: boolean): Answer => {
|
|
67
|
+
let answer: Answer = NONE;
|
|
68
|
+
for (const key of Reflect.ownKeys(sh)) {
|
|
69
|
+
const desc = Object.getOwnPropertyDescriptor(sh, key)!;
|
|
70
|
+
// an object resolves its shape by spread, so a key it does not enumerate is never parsed; `z.properties` reads every own key and so keeps them all
|
|
71
|
+
if (spread && !desc.enumerable) continue;
|
|
72
|
+
// resolving runs user code, and a factory mints a fresh subtree per read, so an edge the walk can't follow counts as a cycle
|
|
73
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
|
|
74
|
+
if (child > answer) answer = child;
|
|
75
|
+
}
|
|
76
|
+
return answer;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const merge = (answer: Answer) => {
|
|
80
|
+
if (answer > result) result = answer;
|
|
48
81
|
};
|
|
49
82
|
|
|
50
83
|
const def = inst._zod.def as any;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
const kind = def.type as $ZodTypeDef["type"];
|
|
85
|
+
switch (kind) {
|
|
86
|
+
case "object": {
|
|
87
|
+
const raw = util.rawShape(def);
|
|
88
|
+
// a def with no raw shape answers `shape` from an accessor of its own, and running that can mint a whole fresh subtree
|
|
89
|
+
merge(raw ? shape(raw, true) : ASSUMED);
|
|
90
|
+
check(def.catchall);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "properties":
|
|
94
|
+
merge(shape(def.shape, false));
|
|
95
|
+
break;
|
|
96
|
+
case "array":
|
|
97
|
+
check(def.element);
|
|
98
|
+
break;
|
|
99
|
+
case "tuple":
|
|
100
|
+
for (const el of def.items) check(el);
|
|
101
|
+
check(def.rest);
|
|
102
|
+
break;
|
|
103
|
+
case "record":
|
|
104
|
+
case "map":
|
|
105
|
+
check(def.keyType);
|
|
106
|
+
check(def.valueType);
|
|
107
|
+
break;
|
|
108
|
+
case "set":
|
|
109
|
+
check(def.valueType);
|
|
110
|
+
break;
|
|
111
|
+
case "union":
|
|
112
|
+
for (const el of def.options) check(el);
|
|
113
|
+
break;
|
|
114
|
+
case "intersection":
|
|
115
|
+
check(def.left);
|
|
116
|
+
check(def.right);
|
|
117
|
+
break;
|
|
118
|
+
case "optional":
|
|
119
|
+
case "nullable":
|
|
120
|
+
case "default":
|
|
121
|
+
case "prefault":
|
|
122
|
+
case "catch":
|
|
123
|
+
case "readonly":
|
|
124
|
+
case "nonoptional":
|
|
125
|
+
case "promise":
|
|
126
|
+
case "success":
|
|
127
|
+
check(def.innerType);
|
|
128
|
+
break;
|
|
129
|
+
case "pipe":
|
|
130
|
+
check(def.in);
|
|
131
|
+
check(def.out);
|
|
132
|
+
break;
|
|
133
|
+
case "function":
|
|
134
|
+
check(def.input);
|
|
135
|
+
check(def.output);
|
|
136
|
+
break;
|
|
137
|
+
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
138
|
+
case "lazy": {
|
|
139
|
+
const inner = def._cachedInner ?? (resolve ? (inst as any)._zod.innerType : undefined);
|
|
140
|
+
// walked with resolution off: one hop sees past the deferral, and a lazy that yields only another unresolved lazy is generative, so it stops there
|
|
141
|
+
merge(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
// a leaf by choice: `parts` are regex fragments, not data positions
|
|
145
|
+
case "template_literal":
|
|
146
|
+
// leaves
|
|
147
|
+
case "string":
|
|
148
|
+
case "number":
|
|
149
|
+
case "int":
|
|
150
|
+
case "boolean":
|
|
151
|
+
case "bigint":
|
|
152
|
+
case "symbol":
|
|
153
|
+
case "undefined":
|
|
154
|
+
case "null":
|
|
155
|
+
case "void":
|
|
156
|
+
case "never":
|
|
157
|
+
case "any":
|
|
158
|
+
case "unknown":
|
|
159
|
+
case "date":
|
|
160
|
+
case "nan":
|
|
161
|
+
case "enum":
|
|
162
|
+
case "literal":
|
|
163
|
+
case "file":
|
|
164
|
+
case "transform":
|
|
165
|
+
case "custom":
|
|
166
|
+
break;
|
|
167
|
+
default: {
|
|
168
|
+
// a new built-in kind becomes a compile error here
|
|
169
|
+
kind satisfies never;
|
|
170
|
+
// a user-defined kind can still hold children, and only its author knows where, so fall back to scanning the def — skipping accessors, since reading one can run user code
|
|
171
|
+
for (const key in def) {
|
|
172
|
+
const desc = Object.getOwnPropertyDescriptor(def, key);
|
|
173
|
+
if (!desc || desc.get) continue;
|
|
174
|
+
const value = desc.value;
|
|
175
|
+
if (!value || typeof value !== "object") continue;
|
|
176
|
+
if (value._zod) check(value);
|
|
177
|
+
else if (Array.isArray(value)) for (const el of value) check(el);
|
|
178
|
+
}
|
|
63
179
|
}
|
|
64
180
|
}
|
|
65
181
|
|
|
66
182
|
stack.delete(inst);
|
|
67
|
-
|
|
68
|
-
|
|
183
|
+
return settle(inst, result);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** An assumed answer must not outlive the resolution that settles it, so only a certain one is cached. */
|
|
187
|
+
function settle(inst: $ZodType, answer: Answer): Answer {
|
|
188
|
+
if (answer !== ASSUMED) recursive.set(inst, answer === PROVEN);
|
|
189
|
+
return answer;
|
|
69
190
|
}
|
|
70
191
|
|
|
71
192
|
/**
|
|
@@ -75,20 +196,21 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
|
|
|
75
196
|
* generated fast path has no context to key on.
|
|
76
197
|
*/
|
|
77
198
|
export function isRecursiveSchema(inst: $ZodType): boolean {
|
|
78
|
-
|
|
199
|
+
// z.compile never parses, so nothing would ever resolve a lazy for it; it runs once and already treats a throw here as recursive
|
|
200
|
+
return isRecursive(inst, new Set(), true) !== NONE;
|
|
79
201
|
}
|
|
80
202
|
|
|
81
|
-
function bucketFor(state: State, inst: $ZodType):
|
|
203
|
+
function bucketFor(state: State, inst: $ZodType): WeakMap<object, Entry> {
|
|
82
204
|
let bucket = state.buckets.get(inst);
|
|
83
205
|
if (!bucket) {
|
|
84
|
-
bucket = new
|
|
206
|
+
bucket = new WeakMap();
|
|
85
207
|
state.buckets.set(inst, bucket);
|
|
86
208
|
}
|
|
87
209
|
return bucket;
|
|
88
210
|
}
|
|
89
211
|
|
|
90
212
|
// Set immediately before delegating to core and cleared immediately after, so `alloc` registers only for a visit this module is driving.
|
|
91
|
-
let handoff:
|
|
213
|
+
let handoff: WeakMap<object, Entry> | undefined;
|
|
92
214
|
|
|
93
215
|
// Allocated but unfinished entries. `alloc` and the matching pop both happen in the synchronous part of a parse, so they nest even when children are async, and one stack serves every schema.
|
|
94
216
|
const open: Entry[] = [];
|
|
@@ -120,9 +242,10 @@ const memo: $ZodMemoizer = {
|
|
|
120
242
|
|
|
121
243
|
attach(inst) {
|
|
122
244
|
let isRecursiveInst: boolean | undefined;
|
|
123
|
-
|
|
245
|
+
let rechecked = false;
|
|
246
|
+
// a recursive schema is re-entered many times per parse and its bucket never changes
|
|
124
247
|
let lastCtx: object | undefined;
|
|
125
|
-
let lastBucket:
|
|
248
|
+
let lastBucket: WeakMap<object, Entry> | undefined;
|
|
126
249
|
|
|
127
250
|
// Wraps `parse` in a deferred so it sees the container's final parse. Core's own deferred copies `parse` into `run` when there are no checks, and it ran first, so `run` is patched to match; with checks, `run` reads `parse` dynamically.
|
|
128
251
|
inst._zod.deferred ??= [];
|
|
@@ -131,25 +254,28 @@ const memo: $ZodMemoizer = {
|
|
|
131
254
|
|
|
132
255
|
const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): util.MaybeAsync<ParsePayload> => {
|
|
133
256
|
if (isRecursiveInst === undefined) {
|
|
134
|
-
|
|
135
|
-
if (
|
|
257
|
+
const walked = isRecursive(inst, new Set(), false);
|
|
258
|
+
if (walked === NONE) {
|
|
136
259
|
// Nothing here can ever fire, so take it back out.
|
|
137
260
|
inst._zod.parse = base;
|
|
138
261
|
if (inst._zod.run === wrapped) inst._zod.run = base;
|
|
139
262
|
return base(payload, ctx);
|
|
140
263
|
}
|
|
264
|
+
// this parse resolves the deferred edges on its own path, so ask once more before latching
|
|
265
|
+
if (walked === PROVEN || rechecked) isRecursiveInst = true;
|
|
266
|
+
else rechecked = true;
|
|
141
267
|
}
|
|
142
268
|
|
|
143
269
|
const input = payload.value;
|
|
144
|
-
if (input
|
|
270
|
+
if (!isRef(input)) return base(payload, ctx);
|
|
145
271
|
|
|
146
272
|
let state = (ctx as WithState)[STATE];
|
|
147
273
|
if (!state) {
|
|
148
|
-
state = { buckets: new
|
|
274
|
+
state = { buckets: new WeakMap(), backEdges: undefined };
|
|
149
275
|
(ctx as WithState)[STATE] = state;
|
|
150
276
|
}
|
|
151
277
|
|
|
152
|
-
let bucket:
|
|
278
|
+
let bucket: WeakMap<object, Entry>;
|
|
153
279
|
if (lastCtx === ctx) {
|
|
154
280
|
bucket = lastBucket!;
|
|
155
281
|
} else {
|
|
@@ -166,7 +292,7 @@ const memo: $ZodMemoizer = {
|
|
|
166
292
|
} else {
|
|
167
293
|
// Still being parsed: its own checks cover it, so skip them here.
|
|
168
294
|
payload.memo = true;
|
|
169
|
-
state.backEdges ??= new
|
|
295
|
+
state.backEdges ??= new WeakSet();
|
|
170
296
|
state.backEdges.add(hit.value as object);
|
|
171
297
|
}
|
|
172
298
|
return payload;
|
|
@@ -204,5 +330,5 @@ export function memoizer(): $ZodMemoizer {
|
|
|
204
330
|
/** Whether this value is a node a back-edge resolved to before it finished. */
|
|
205
331
|
export function isBackEdge(ctx: object, value: unknown): boolean {
|
|
206
332
|
const backEdges = (ctx as WithState)[STATE]?.backEdges;
|
|
207
|
-
return backEdges !== undefined && value
|
|
333
|
+
return backEdges !== undefined && isRef(value) && backEdges.has(value);
|
|
208
334
|
}
|
package/src/v4/core/parse.ts
CHANGED
|
@@ -75,15 +75,33 @@ export const _safeParse: (_Err: $ZodErrorClass) => $SafeParse = (_Err) => (schem
|
|
|
75
75
|
throw new core.$ZodAsyncError();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
return result.issues.length
|
|
79
|
-
? {
|
|
80
|
-
success: false,
|
|
81
|
-
error: new (_Err ?? errors.$ZodError)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
|
|
82
|
-
}
|
|
83
|
-
: ({ success: true, data: result.value } as any);
|
|
78
|
+
return result.issues.length ? failure(_Err, result.issues, ctx) : ({ success: true, data: result.value } as any);
|
|
84
79
|
};
|
|
85
80
|
export const safeParse: $SafeParse = /* @__PURE__*/ _safeParse(errors.$ZodRealError);
|
|
86
81
|
|
|
82
|
+
// the error is built on the first read of `error`: finalizing the issues and constructing the instance is most of a failing parse, and a caller that only branches on `success` never pays it. a getter in the literal keeps this small; the alternative, one shared accessor descriptor plus a hidden state slot, reads ~15% faster but costs ~75 B gzipped in every bundle
|
|
83
|
+
function failure(Err: $ZodErrorClass, issues: errors.$ZodRawIssue[], ctx: schemas.ParseContextInternal): any {
|
|
84
|
+
let error: errors.$ZodError | undefined;
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
get error() {
|
|
88
|
+
if (!error) {
|
|
89
|
+
error = new Err(issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
|
|
90
|
+
// finalizeIssue drops `input`, so the built error holds nothing; keeping the raw issues past this point pins the parsed value for the life of the result
|
|
91
|
+
issues = undefined as any;
|
|
92
|
+
ctx = undefined as any;
|
|
93
|
+
}
|
|
94
|
+
return error;
|
|
95
|
+
},
|
|
96
|
+
set error(e: errors.$ZodError) {
|
|
97
|
+
error = e;
|
|
98
|
+
// a replacement makes the getter's branch unreachable, so the captures have to go here too
|
|
99
|
+
issues = undefined as any;
|
|
100
|
+
ctx = undefined as any;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
export type $SafeParseAsync = <T extends schemas.$ZodType>(
|
|
88
106
|
schema: T,
|
|
89
107
|
value: unknown,
|
|
@@ -95,12 +113,7 @@ export const _safeParseAsync: (_Err: $ZodErrorClass) => $SafeParseAsync = (_Err)
|
|
|
95
113
|
let result = schema._zod.run({ value, issues: [] }, ctx);
|
|
96
114
|
if (result instanceof Promise) result = await result;
|
|
97
115
|
|
|
98
|
-
return result.issues.length
|
|
99
|
-
? {
|
|
100
|
-
success: false,
|
|
101
|
-
error: new _Err(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
|
|
102
|
-
}
|
|
103
|
-
: ({ success: true, data: result.value } as any);
|
|
116
|
+
return result.issues.length ? failure(_Err, result.issues, ctx) : ({ success: true, data: result.value } as any);
|
|
104
117
|
};
|
|
105
118
|
|
|
106
119
|
export const safeParseAsync: $SafeParseAsync = /* @__PURE__*/ _safeParseAsync(errors.$ZodRealError);
|
|
@@ -110,7 +123,7 @@ const COMPILE_INVALID = /* @__PURE__ */ Symbol.for("zod.compile.invalid");
|
|
|
110
123
|
const COMPILE_FALLBACK = /* @__PURE__ */ Symbol.for("zod.compile.fallback");
|
|
111
124
|
|
|
112
125
|
interface CompiledBag {
|
|
113
|
-
validator?: (input: unknown) => unknown;
|
|
126
|
+
validator?: ((input: unknown) => unknown) & { definite?: boolean | undefined };
|
|
114
127
|
fallbackRun?: (payload: schemas.ParsePayload, ctx: schemas.ParseContextInternal) => unknown;
|
|
115
128
|
}
|
|
116
129
|
|
|
@@ -127,7 +140,11 @@ export const validate: $Validate = ((
|
|
|
127
140
|
_ctx?: schemas.ParseContext<errors.$ZodIssue>
|
|
128
141
|
): boolean => {
|
|
129
142
|
const validator = (schema._zod.bag as CompiledBag).validator;
|
|
130
|
-
if (validator !== undefined
|
|
143
|
+
if (validator !== undefined) {
|
|
144
|
+
if (validator(value) !== COMPILE_INVALID) return true;
|
|
145
|
+
// a definite sentinel means the runtime would reject, so skip the re-parse; a ctx can still change the answer
|
|
146
|
+
if (validator.definite === true && _ctx === undefined) return false;
|
|
147
|
+
}
|
|
131
148
|
return validateFallback(schema, value, _ctx);
|
|
132
149
|
}) as $Validate;
|
|
133
150
|
|
|
@@ -136,7 +153,9 @@ function validateFallback(
|
|
|
136
153
|
value: unknown,
|
|
137
154
|
_ctx?: schemas.ParseContext<errors.$ZodIssue>
|
|
138
155
|
): boolean {
|
|
139
|
-
const ctx: schemas.ParseContextInternal = _ctx
|
|
156
|
+
const ctx: schemas.ParseContextInternal = _ctx
|
|
157
|
+
? { ..._ctx, async: false, abortEarly: true }
|
|
158
|
+
: { async: false, abortEarly: true };
|
|
140
159
|
const fallbackRun = (schema._zod.bag as CompiledBag).fallbackRun;
|
|
141
160
|
let result: unknown;
|
|
142
161
|
if (fallbackRun) {
|
|
@@ -160,7 +179,9 @@ export type $ValidateAsync = <T extends schemas.$ZodType>(
|
|
|
160
179
|
|
|
161
180
|
// no fast path: the compiler keeps async parses on the runtime, because a promise-returning callback that is not declared async compiles to a throw
|
|
162
181
|
export const validateAsync: $ValidateAsync = async (schema, value, _ctx) => {
|
|
163
|
-
const ctx: schemas.ParseContextInternal = _ctx
|
|
182
|
+
const ctx: schemas.ParseContextInternal = _ctx
|
|
183
|
+
? { ..._ctx, async: true, abortEarly: true }
|
|
184
|
+
: { async: true, abortEarly: true };
|
|
164
185
|
let result: unknown = schema._zod.run({ value, issues: [] }, ctx);
|
|
165
186
|
if (result instanceof Promise) result = await result;
|
|
166
187
|
return (result as schemas.ParsePayload).issues.length === 0;
|
package/src/v4/core/regexes.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const uuid7: RegExp = /*@__PURE__*/ uuid(7);
|
|
|
42
42
|
|
|
43
43
|
/** Practical email validation */
|
|
44
44
|
export const email: RegExp =
|
|
45
|
-
/^(
|
|
45
|
+
/^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
|
|
46
46
|
|
|
47
47
|
/** Equivalent to the HTML5 input[type=email] validation implemented by browsers. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email */
|
|
48
48
|
export const html5Email: RegExp =
|
|
@@ -60,8 +60,8 @@ export const browserEmail: RegExp =
|
|
|
60
60
|
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
61
61
|
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
|
|
62
62
|
|
|
63
|
-
// Single character class, not an alternation: the two properties overlap (U+1F9B0-U+1F9B3), so `(A|B)+` backtracks exponentially on a failed match.
|
|
64
|
-
const _emoji: string = `^[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
|
|
63
|
+
// Single character class, not an alternation: the two properties overlap (U+1F9B0-U+1F9B3), so `(A|B)+` backtracks exponentially on a failed match. The leading lookahead then demands one anchor — a pictograph, a regional indicator, or the enclosing keycap — because `\p{Emoji_Component}` on its own covers ASCII digits, `#`, `*`, ZWJ, variation selectors and skin tone modifiers, none of which is an emoji without a base.
|
|
64
|
+
const _emoji: string = `^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
|
|
65
65
|
export function emoji(): RegExp {
|
|
66
66
|
return new RegExp(_emoji, "u");
|
|
67
67
|
}
|
|
@@ -81,7 +81,7 @@ export const cidrv6: RegExp =
|
|
|
81
81
|
|
|
82
82
|
// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
|
|
83
83
|
export const base64: RegExp = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
84
|
-
export const base64url: RegExp = /^[A-Za-z0-9_-]
|
|
84
|
+
export const base64url: RegExp = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/;
|
|
85
85
|
|
|
86
86
|
// based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
|
87
87
|
// export const hostname: RegExp = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
@@ -98,6 +98,9 @@ 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
|
+
// iban electronic format: 2-letter country, check digits 02-98 (the only values `98 - remainder` can produce), 11-30 bban characters
|
|
102
|
+
export const iban: RegExp = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
|
|
103
|
+
|
|
101
104
|
const dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
|
|
102
105
|
|
|
103
106
|
/** Anchors a pattern source. The interpolation lives here rather than at the call site because
|
|
@@ -147,6 +150,9 @@ export function datetime(args: {
|
|
|
147
150
|
return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
|
|
148
151
|
}
|
|
149
152
|
|
|
153
|
+
// the unbounded form of `string()` as a literal, so every plain string shares one instance instead of building its own
|
|
154
|
+
export const anyString: RegExp = /^[\s\S]{0,}$/;
|
|
155
|
+
|
|
150
156
|
export const string = (params?: { minimum?: number | undefined; maximum?: number | undefined }): RegExp => {
|
|
151
157
|
const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
|
|
152
158
|
return new RegExp(`^${regex}$`);
|