zod 4.5.4 → 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 +274 -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 +78 -28
- 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 +114 -23
- package/v4/core/memoizer.js +72 -23
- 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
2
|
import type { $ZodMemoizer, $ZodType, $ZodTypeDef, ParseContextInternal, ParsePayload } from "./schemas.js";
|
|
3
|
-
import
|
|
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,28 +40,59 @@ 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
84
|
const kind = def.type as $ZodTypeDef["type"];
|
|
52
85
|
switch (kind) {
|
|
53
86
|
case "object": {
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
56
90
|
check(def.catchall);
|
|
57
91
|
break;
|
|
58
92
|
}
|
|
93
|
+
case "properties":
|
|
94
|
+
merge(shape(def.shape, false));
|
|
95
|
+
break;
|
|
59
96
|
case "array":
|
|
60
97
|
check(def.element);
|
|
61
98
|
break;
|
|
@@ -97,10 +134,13 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
|
|
|
97
134
|
check(def.input);
|
|
98
135
|
check(def.output);
|
|
99
136
|
break;
|
|
100
|
-
//
|
|
101
|
-
case "lazy":
|
|
102
|
-
|
|
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);
|
|
103
142
|
break;
|
|
143
|
+
}
|
|
104
144
|
// a leaf by choice: `parts` are regex fragments, not data positions
|
|
105
145
|
case "template_literal":
|
|
106
146
|
// leaves
|
|
@@ -140,8 +180,13 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
|
|
|
140
180
|
}
|
|
141
181
|
|
|
142
182
|
stack.delete(inst);
|
|
143
|
-
|
|
144
|
-
|
|
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;
|
|
145
190
|
}
|
|
146
191
|
|
|
147
192
|
/**
|
|
@@ -151,20 +196,21 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
|
|
|
151
196
|
* generated fast path has no context to key on.
|
|
152
197
|
*/
|
|
153
198
|
export function isRecursiveSchema(inst: $ZodType): boolean {
|
|
154
|
-
|
|
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;
|
|
155
201
|
}
|
|
156
202
|
|
|
157
|
-
function bucketFor(state: State, inst: $ZodType):
|
|
203
|
+
function bucketFor(state: State, inst: $ZodType): WeakMap<object, Entry> {
|
|
158
204
|
let bucket = state.buckets.get(inst);
|
|
159
205
|
if (!bucket) {
|
|
160
|
-
bucket = new
|
|
206
|
+
bucket = new WeakMap();
|
|
161
207
|
state.buckets.set(inst, bucket);
|
|
162
208
|
}
|
|
163
209
|
return bucket;
|
|
164
210
|
}
|
|
165
211
|
|
|
166
212
|
// Set immediately before delegating to core and cleared immediately after, so `alloc` registers only for a visit this module is driving.
|
|
167
|
-
let handoff:
|
|
213
|
+
let handoff: WeakMap<object, Entry> | undefined;
|
|
168
214
|
|
|
169
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.
|
|
170
216
|
const open: Entry[] = [];
|
|
@@ -196,9 +242,10 @@ const memo: $ZodMemoizer = {
|
|
|
196
242
|
|
|
197
243
|
attach(inst) {
|
|
198
244
|
let isRecursiveInst: boolean | undefined;
|
|
199
|
-
|
|
245
|
+
let rechecked = false;
|
|
246
|
+
// a recursive schema is re-entered many times per parse and its bucket never changes
|
|
200
247
|
let lastCtx: object | undefined;
|
|
201
|
-
let lastBucket:
|
|
248
|
+
let lastBucket: WeakMap<object, Entry> | undefined;
|
|
202
249
|
|
|
203
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.
|
|
204
251
|
inst._zod.deferred ??= [];
|
|
@@ -207,25 +254,28 @@ const memo: $ZodMemoizer = {
|
|
|
207
254
|
|
|
208
255
|
const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): util.MaybeAsync<ParsePayload> => {
|
|
209
256
|
if (isRecursiveInst === undefined) {
|
|
210
|
-
|
|
211
|
-
if (
|
|
257
|
+
const walked = isRecursive(inst, new Set(), false);
|
|
258
|
+
if (walked === NONE) {
|
|
212
259
|
// Nothing here can ever fire, so take it back out.
|
|
213
260
|
inst._zod.parse = base;
|
|
214
261
|
if (inst._zod.run === wrapped) inst._zod.run = base;
|
|
215
262
|
return base(payload, ctx);
|
|
216
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;
|
|
217
267
|
}
|
|
218
268
|
|
|
219
269
|
const input = payload.value;
|
|
220
|
-
if (input
|
|
270
|
+
if (!isRef(input)) return base(payload, ctx);
|
|
221
271
|
|
|
222
272
|
let state = (ctx as WithState)[STATE];
|
|
223
273
|
if (!state) {
|
|
224
|
-
state = { buckets: new
|
|
274
|
+
state = { buckets: new WeakMap(), backEdges: undefined };
|
|
225
275
|
(ctx as WithState)[STATE] = state;
|
|
226
276
|
}
|
|
227
277
|
|
|
228
|
-
let bucket:
|
|
278
|
+
let bucket: WeakMap<object, Entry>;
|
|
229
279
|
if (lastCtx === ctx) {
|
|
230
280
|
bucket = lastBucket!;
|
|
231
281
|
} else {
|
|
@@ -242,7 +292,7 @@ const memo: $ZodMemoizer = {
|
|
|
242
292
|
} else {
|
|
243
293
|
// Still being parsed: its own checks cover it, so skip them here.
|
|
244
294
|
payload.memo = true;
|
|
245
|
-
state.backEdges ??= new
|
|
295
|
+
state.backEdges ??= new WeakSet();
|
|
246
296
|
state.backEdges.add(hit.value as object);
|
|
247
297
|
}
|
|
248
298
|
return payload;
|
|
@@ -280,5 +330,5 @@ export function memoizer(): $ZodMemoizer {
|
|
|
280
330
|
/** Whether this value is a node a back-edge resolved to before it finished. */
|
|
281
331
|
export function isBackEdge(ctx: object, value: unknown): boolean {
|
|
282
332
|
const backEdges = (ctx as WithState)[STATE]?.backEdges;
|
|
283
|
-
return backEdges !== undefined && value
|
|
333
|
+
return backEdges !== undefined && isRef(value) && backEdges.has(value);
|
|
284
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}$`);
|