@es-joy/jsoe 0.24.2 → 0.25.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/.claude/settings.local.json +9 -0
- package/.ncurc.cjs +2 -2
- package/CHANGES.md +51 -0
- package/README.md +23 -13
- package/badges/coverage-badge.svg +1 -1
- package/dist/eslint.config.d.ts +3 -0
- package/dist/eslint.config.d.ts.map +1 -0
- package/dist/formatAndTypeChoices.d.ts +28 -30
- package/dist/formatAndTypeChoices.d.ts.map +1 -1
- package/dist/formats/indexedDBKey.d.ts.map +1 -1
- package/dist/formats/schema.d.ts +46 -3
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/formats/structuredCloning.d.ts +3 -0
- package/dist/formats/structuredCloning.d.ts.map +1 -1
- package/dist/formats.d.ts +24 -6
- package/dist/formats.d.ts.map +1 -1
- package/dist/fundamentalTypes/BooleanObjectType.d.ts.map +1 -1
- package/dist/fundamentalTypes/NumberObjectType.d.ts.map +1 -1
- package/dist/fundamentalTypes/StringObjectType.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayReferenceType.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts +7 -0
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/fundamentalTypes/bigintObjectType.d.ts.map +1 -1
- package/dist/fundamentalTypes/bigintType.d.ts +10 -0
- package/dist/fundamentalTypes/bigintType.d.ts.map +1 -1
- package/dist/fundamentalTypes/blobType.d.ts.map +1 -1
- package/dist/fundamentalTypes/booleanType.d.ts.map +1 -1
- package/dist/fundamentalTypes/catchType.d.ts +2 -2
- package/dist/fundamentalTypes/catchType.d.ts.map +1 -1
- package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
- package/dist/fundamentalTypes/domexceptionType.d.ts.map +1 -1
- package/dist/fundamentalTypes/enumType.d.ts +2 -2
- package/dist/fundamentalTypes/enumType.d.ts.map +1 -1
- package/dist/fundamentalTypes/errorType.d.ts.map +1 -1
- package/dist/fundamentalTypes/fileType.d.ts +6 -0
- package/dist/fundamentalTypes/fileType.d.ts.map +1 -1
- package/dist/fundamentalTypes/functionType.d.ts.map +1 -1
- package/dist/fundamentalTypes/literalType.d.ts +3 -2
- package/dist/fundamentalTypes/literalType.d.ts.map +1 -1
- package/dist/fundamentalTypes/looseRecordType.d.ts +6 -0
- package/dist/fundamentalTypes/looseRecordType.d.ts.map +1 -0
- package/dist/fundamentalTypes/nanType.d.ts.map +1 -1
- package/dist/fundamentalTypes/neverType.d.ts +2 -2
- package/dist/fundamentalTypes/neverType.d.ts.map +1 -1
- package/dist/fundamentalTypes/noneditableType.d.ts.map +1 -1
- package/dist/fundamentalTypes/nullType.d.ts.map +1 -1
- package/dist/fundamentalTypes/numberType.d.ts.map +1 -1
- package/dist/fundamentalTypes/promiseType.d.ts.map +1 -1
- package/dist/fundamentalTypes/regexpType.d.ts.map +1 -1
- package/dist/fundamentalTypes/stringType.d.ts.map +1 -1
- package/dist/fundamentalTypes/symbolType.d.ts.map +1 -1
- package/dist/fundamentalTypes/undefinedType.d.ts.map +1 -1
- package/dist/fundamentalTypes/voidType.d.ts +2 -2
- package/dist/fundamentalTypes/voidType.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/rollup.config.d.ts +30 -0
- package/dist/rollup.config.d.ts.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/src/formatAndTypeChoices.d.ts +108 -0
- package/dist/src/formatAndTypeChoices.d.ts.map +1 -0
- package/dist/src/formats/arbitraryJS.d.ts +4 -0
- package/dist/src/formats/arbitraryJS.d.ts.map +1 -0
- package/dist/src/formats/indexedDBKey.d.ts +4 -0
- package/dist/src/formats/indexedDBKey.d.ts.map +1 -0
- package/dist/src/formats/json.d.ts +4 -0
- package/dist/src/formats/json.d.ts.map +1 -0
- package/dist/src/formats/schema.d.ts +17 -0
- package/dist/src/formats/schema.d.ts.map +1 -0
- package/dist/src/formats/structuredCloning.d.ts +19 -0
- package/dist/src/formats/structuredCloning.d.ts.map +1 -0
- package/dist/src/formats.d.ts +135 -0
- package/dist/src/formats.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/BooleanObjectType.d.ts +8 -0
- package/dist/src/fundamentalTypes/BooleanObjectType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/NumberObjectType.d.ts +6 -0
- package/dist/src/fundamentalTypes/NumberObjectType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/StringObjectType.d.ts +6 -0
- package/dist/src/fundamentalTypes/StringObjectType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/arrayReferenceType.d.ts +8 -0
- package/dist/src/fundamentalTypes/arrayReferenceType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/arrayType.d.ts +29 -0
- package/dist/src/fundamentalTypes/arrayType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/bigintObjectType.d.ts +6 -0
- package/dist/src/fundamentalTypes/bigintObjectType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/bigintType.d.ts +6 -0
- package/dist/src/fundamentalTypes/bigintType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/blobType.d.ts +11 -0
- package/dist/src/fundamentalTypes/blobType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/booleanType.d.ts +8 -0
- package/dist/src/fundamentalTypes/booleanType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/catchType.d.ts +6 -0
- package/dist/src/fundamentalTypes/catchType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/dateType.d.ts +26 -0
- package/dist/src/fundamentalTypes/dateType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/domexceptionType.d.ts +6 -0
- package/dist/src/fundamentalTypes/domexceptionType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/enumType.d.ts +6 -0
- package/dist/src/fundamentalTypes/enumType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/errorType.d.ts +6 -0
- package/dist/src/fundamentalTypes/errorType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/fileType.d.ts +13 -0
- package/dist/src/fundamentalTypes/fileType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/filelistType.d.ts +6 -0
- package/dist/src/fundamentalTypes/filelistType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/functionType.d.ts +7 -0
- package/dist/src/fundamentalTypes/functionType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/literalType.d.ts +6 -0
- package/dist/src/fundamentalTypes/literalType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/mapType.d.ts +6 -0
- package/dist/src/fundamentalTypes/mapType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/nanType.d.ts +6 -0
- package/dist/src/fundamentalTypes/nanType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/neverType.d.ts +6 -0
- package/dist/src/fundamentalTypes/neverType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/noneditableType.d.ts +6 -0
- package/dist/src/fundamentalTypes/noneditableType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/nullType.d.ts +6 -0
- package/dist/src/fundamentalTypes/nullType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/numberType.d.ts +6 -0
- package/dist/src/fundamentalTypes/numberType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/objectReferenceType.d.ts +8 -0
- package/dist/src/fundamentalTypes/objectReferenceType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/objectType.d.ts +6 -0
- package/dist/src/fundamentalTypes/objectType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/promiseType.d.ts +6 -0
- package/dist/src/fundamentalTypes/promiseType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/recordType.d.ts +6 -0
- package/dist/src/fundamentalTypes/recordType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/regexpType.d.ts +8 -0
- package/dist/src/fundamentalTypes/regexpType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/setType.d.ts +6 -0
- package/dist/src/fundamentalTypes/setType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/sparseUndefinedType.d.ts +6 -0
- package/dist/src/fundamentalTypes/sparseUndefinedType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/stringType.d.ts +6 -0
- package/dist/src/fundamentalTypes/stringType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/symbolType.d.ts +8 -0
- package/dist/src/fundamentalTypes/symbolType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/tupleType.d.ts +6 -0
- package/dist/src/fundamentalTypes/tupleType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/undefinedType.d.ts +6 -0
- package/dist/src/fundamentalTypes/undefinedType.d.ts.map +1 -0
- package/dist/src/fundamentalTypes/voidType.d.ts +6 -0
- package/dist/src/fundamentalTypes/voidType.d.ts.map +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/subTypes/blobHTMLType.d.ts +20 -0
- package/dist/src/subTypes/blobHTMLType.d.ts.map +1 -0
- package/dist/src/subTypes/falseType.d.ts +8 -0
- package/dist/src/subTypes/falseType.d.ts.map +1 -0
- package/dist/src/subTypes/trueType.d.ts +8 -0
- package/dist/src/subTypes/trueType.d.ts.map +1 -0
- package/dist/src/superTypes/SpecialNumberType.d.ts +6 -0
- package/dist/src/superTypes/SpecialNumberType.d.ts.map +1 -0
- package/dist/src/superTypes/SpecialRealNumberType.d.ts +6 -0
- package/dist/src/superTypes/SpecialRealNumberType.d.ts.map +1 -0
- package/dist/src/superTypes/buffersourceType.d.ts +8 -0
- package/dist/src/superTypes/buffersourceType.d.ts.map +1 -0
- package/dist/src/superTypes/dommatrixType.d.ts +6 -0
- package/dist/src/superTypes/dommatrixType.d.ts.map +1 -0
- package/dist/src/superTypes/dompointType.d.ts +6 -0
- package/dist/src/superTypes/dompointType.d.ts.map +1 -0
- package/dist/src/superTypes/domrectType.d.ts +6 -0
- package/dist/src/superTypes/domrectType.d.ts.map +1 -0
- package/dist/src/superTypes/errorsSpecialType.d.ts +8 -0
- package/dist/src/superTypes/errorsSpecialType.d.ts.map +1 -0
- package/dist/src/typeChoices.d.ts +140 -0
- package/dist/src/typeChoices.d.ts.map +1 -0
- package/dist/src/types.d.ts +758 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils/FileList.d.ts +26 -0
- package/dist/src/utils/FileList.d.ts.map +1 -0
- package/dist/src/utils/dialogs.d.ts +111 -0
- package/dist/src/utils/dialogs.d.ts.map +1 -0
- package/dist/src/utils/jsonPointer.d.ts +46 -0
- package/dist/src/utils/jsonPointer.d.ts.map +1 -0
- package/dist/src/utils/media.d.ts +18 -0
- package/dist/src/utils/media.d.ts.map +1 -0
- package/dist/src/utils/objects.d.ts +16 -0
- package/dist/src/utils/objects.d.ts.map +1 -0
- package/dist/src/utils/templateUtils.d.ts +59 -0
- package/dist/src/utils/templateUtils.d.ts.map +1 -0
- package/dist/src/utils/types.d.ts +2 -0
- package/dist/src/utils/types.d.ts.map +1 -0
- package/dist/src/vendor-imports.d.ts +4 -0
- package/dist/src/vendor-imports.d.ts.map +1 -0
- package/dist/subTypes/blobHTMLType.d.ts.map +1 -1
- package/dist/superTypes/SpecialRealNumberType.d.ts.map +1 -1
- package/dist/superTypes/buffersourceType.d.ts.map +1 -1
- package/dist/superTypes/dommatrixType.d.ts.map +1 -1
- package/dist/superTypes/dompointType.d.ts.map +1 -1
- package/dist/superTypes/domrectType.d.ts.map +1 -1
- package/dist/superTypes/errorsSpecialType.d.ts.map +1 -1
- package/dist/typeChoices.d.ts +21 -87
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +90 -64
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/FileList.d.ts +4 -3
- package/dist/utils/FileList.d.ts.map +1 -1
- package/dist/utils/dialogs.d.ts +4 -4
- package/dist/utils/dialogs.d.ts.map +1 -1
- package/dist/utils/media.d.ts.map +1 -1
- package/dist/utils/schemaMeta.d.ts +92 -0
- package/dist/utils/schemaMeta.d.ts.map +1 -0
- package/dist/utils/templateUtils.d.ts +3 -3
- package/dist/utils/templateUtils.d.ts.map +1 -1
- package/dist/utils/timing.d.ts +2 -0
- package/dist/utils/timing.d.ts.map +1 -0
- package/dist/utils/types.d.ts +1 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/dist/vendor-imports.d.ts +3 -1
- package/docs/proposals/schema-meta.md +233 -0
- package/eslint.config.js +51 -4
- package/licenseInfo.json +1 -1
- package/package.json +52 -42
- package/pnpm-workspace.yaml +57 -0
- package/rollup.config.js +20 -18
- package/server.js +3 -3
- package/src/deepEqual.js +77 -66
- package/src/deepEqual.js.map +1 -1
- package/src/formatAndTypeChoices.js +62 -58
- package/src/formats/indexedDBKey.js +3 -4
- package/src/formats/schema.js +824 -475
- package/src/formats/structuredCloning.js +189 -80
- package/src/formats.js +17 -5
- package/src/fundamentalTypes/BooleanObjectType.js +4 -5
- package/src/fundamentalTypes/NumberObjectType.js +4 -4
- package/src/fundamentalTypes/StringObjectType.js +3 -4
- package/src/fundamentalTypes/arrayReferenceType.js +0 -2
- package/src/fundamentalTypes/arrayType.js +624 -389
- package/src/fundamentalTypes/bigintObjectType.js +3 -2
- package/src/fundamentalTypes/bigintType.js +79 -17
- package/src/fundamentalTypes/blobType.js +35 -35
- package/src/fundamentalTypes/booleanType.js +16 -8
- package/src/fundamentalTypes/dateType.js +6 -8
- package/src/fundamentalTypes/domexceptionType.js +5 -4
- package/src/fundamentalTypes/errorType.js +39 -32
- package/src/fundamentalTypes/fileType.js +238 -52
- package/src/fundamentalTypes/functionType.js +61 -41
- package/src/fundamentalTypes/nanType.js +5 -4
- package/src/fundamentalTypes/noneditableType.js +4 -3
- package/src/fundamentalTypes/nullType.js +3 -2
- package/src/fundamentalTypes/numberType.js +72 -20
- package/src/fundamentalTypes/promiseType.js +16 -40
- package/src/fundamentalTypes/regexpType.js +7 -13
- package/src/fundamentalTypes/stringType.js +176 -110
- package/src/fundamentalTypes/symbolType.js +3 -2
- package/src/fundamentalTypes/undefinedType.js +3 -2
- package/src/index.js +6 -5
- package/src/jsoe.css +107 -8
- package/src/subTypes/blobHTMLType.js +23 -10
- package/src/superTypes/SpecialNumberType.js +6 -6
- package/src/superTypes/SpecialRealNumberType.js +10 -9
- package/src/superTypes/buffersourceType.js +86 -75
- package/src/superTypes/dommatrixType.js +6 -5
- package/src/superTypes/dompointType.js +6 -5
- package/src/superTypes/domrectType.js +6 -5
- package/src/superTypes/errorsSpecialType.js +54 -35
- package/src/typeChoices.js +373 -53
- package/src/types.js +157 -97
- package/src/utils/FileList.js +3 -3
- package/src/utils/dialogs.js +5 -7
- package/src/utils/jsonPointer.js +1 -1
- package/src/utils/media.js +1 -0
- package/src/utils/schemaMeta.js +257 -0
- package/src/utils/templateUtils.js +3 -3
- package/src/utils/timing.js +12 -0
- package/src/utils/types.js +19 -0
- package/src/vendor-imports.js +10 -2
- package/tsconfig.json +2 -0
- package/vendor/acorn/dist/acorn.d.mts +46 -5
- package/vendor/acorn/dist/acorn.d.ts +46 -5
- package/vendor/acorn/dist/acorn.js +394 -117
- package/vendor/acorn/dist/acorn.mjs +394 -117
- package/vendor/acorn/dist/bin.js +1 -1
- package/vendor/jamilih/dist/{jml-es.js → jml.mjs} +590 -312
- package/vendor/jamilih/package.json +1 -1
- package/vendor/mime/dist/types/other.js +719 -0
- package/vendor/mime/dist/types/standard.js +366 -0
- package/vendor/typeson-registry/dist/index.js +519 -99
- package/vendor/zod/classic/checks.d.ts +1 -0
- package/vendor/zod/classic/checks.js +1 -0
- package/vendor/zod/classic/coerce.d.ts +17 -0
- package/vendor/zod/classic/coerce.js +17 -0
- package/vendor/zod/classic/compat.d.ts +48 -0
- package/vendor/zod/classic/compat.js +31 -0
- package/vendor/zod/classic/deep-partial.d.ts +14 -0
- package/vendor/zod/classic/deep-partial.js +13 -0
- package/vendor/zod/classic/errors.d.ts +29 -0
- package/vendor/zod/classic/errors.js +57 -0
- package/vendor/zod/classic/external.d.ts +19 -0
- package/vendor/zod/classic/external.js +17 -0
- package/vendor/zod/classic/from-json-schema.d.ts +12 -0
- package/vendor/zod/classic/from-json-schema.js +660 -0
- package/vendor/zod/classic/in-out.d.ts +8 -0
- package/vendor/zod/classic/in-out.js +36 -0
- package/vendor/zod/classic/index.d.ts +3 -0
- package/vendor/zod/classic/index.js +4 -0
- package/vendor/zod/classic/iso.d.ts +7 -0
- package/vendor/zod/classic/iso.js +15 -0
- package/vendor/zod/classic/package.json +7 -0
- package/vendor/zod/classic/parse.d.ts +44 -0
- package/vendor/zod/classic/parse.js +16 -0
- package/vendor/zod/classic/schemas.d.ts +802 -0
- package/vendor/zod/classic/schemas.js +1491 -0
- package/vendor/zod/core/api.d.ts +329 -0
- package/vendor/zod/core/api.js +1104 -0
- package/vendor/zod/core/checks.d.ts +278 -0
- package/vendor/zod/core/checks.js +581 -0
- package/vendor/zod/core/compile.d.ts +51 -0
- package/vendor/zod/core/compile.js +1783 -0
- package/vendor/zod/core/core.d.ts +82 -0
- package/vendor/zod/core/core.js +141 -0
- package/vendor/zod/core/doc.d.ts +16 -0
- package/vendor/zod/core/doc.js +33 -0
- package/vendor/zod/core/errors.d.ts +225 -0
- package/vendor/zod/core/errors.js +270 -0
- package/vendor/zod/core/index.d.ts +19 -0
- package/vendor/zod/core/index.js +19 -0
- package/vendor/zod/core/json-schema-generator.d.ts +65 -0
- package/vendor/zod/core/json-schema-generator.js +99 -0
- package/vendor/zod/core/json-schema-processors.d.ts +49 -0
- package/vendor/zod/core/json-schema-processors.js +747 -0
- package/vendor/zod/core/json-schema.d.ts +89 -0
- package/vendor/zod/core/memoizer.d.ts +15 -0
- package/vendor/zod/core/memoizer.js +265 -0
- package/vendor/zod/core/package.json +7 -0
- package/vendor/zod/core/parse.d.ts +52 -0
- package/vendor/zod/core/parse.js +154 -0
- package/vendor/zod/core/regexes.d.ts +87 -0
- package/vendor/zod/core/regexes.js +150 -0
- package/vendor/zod/core/registries.d.ts +35 -0
- package/vendor/zod/core/registries.js +51 -0
- package/vendor/zod/core/schemas.d.ts +1253 -0
- package/vendor/zod/core/schemas.js +2440 -0
- package/vendor/zod/core/standard-schema.d.ts +126 -0
- package/vendor/zod/core/to-json-schema.d.ts +153 -0
- package/vendor/zod/core/to-json-schema.js +650 -0
- package/vendor/zod/core/util.d.ts +257 -0
- package/vendor/zod/core/util.js +846 -0
- package/vendor/zod/core/versions.d.ts +5 -0
- package/vendor/zod/core/versions.js +5 -0
- package/vendor/zod/core/visit.d.ts +30 -0
- package/vendor/zod/core/visit.js +169 -0
- package/vendor/zod/index.d.ts +3 -0
- package/vendor/zod/index.js +4 -0
- package/vendor/zod/locales/ar.d.ts +4 -0
- package/vendor/zod/locales/ar.js +109 -0
- package/vendor/zod/locales/az.d.ts +4 -0
- package/vendor/zod/locales/az.js +108 -0
- package/vendor/zod/locales/be.d.ts +4 -0
- package/vendor/zod/locales/be.js +166 -0
- package/vendor/zod/locales/bg.d.ts +4 -0
- package/vendor/zod/locales/bg.js +123 -0
- package/vendor/zod/locales/bn.d.ts +4 -0
- package/vendor/zod/locales/bn.js +111 -0
- package/vendor/zod/locales/ca.d.ts +4 -0
- package/vendor/zod/locales/ca.js +110 -0
- package/vendor/zod/locales/ckb.d.ts +4 -0
- package/vendor/zod/locales/ckb.js +131 -0
- package/vendor/zod/locales/cs.d.ts +4 -0
- package/vendor/zod/locales/cs.js +114 -0
- package/vendor/zod/locales/da.d.ts +4 -0
- package/vendor/zod/locales/da.js +118 -0
- package/vendor/zod/locales/de.d.ts +4 -0
- package/vendor/zod/locales/de.js +111 -0
- package/vendor/zod/locales/el.d.ts +4 -0
- package/vendor/zod/locales/el.js +110 -0
- package/vendor/zod/locales/en.d.ts +4 -0
- package/vendor/zod/locales/en.js +123 -0
- package/vendor/zod/locales/eo.d.ts +4 -0
- package/vendor/zod/locales/eo.js +112 -0
- package/vendor/zod/locales/es.d.ts +4 -0
- package/vendor/zod/locales/es.js +134 -0
- package/vendor/zod/locales/fa.d.ts +4 -0
- package/vendor/zod/locales/fa.js +117 -0
- package/vendor/zod/locales/fi.d.ts +4 -0
- package/vendor/zod/locales/fi.js +115 -0
- package/vendor/zod/locales/fr-CA.d.ts +4 -0
- package/vendor/zod/locales/fr-CA.js +110 -0
- package/vendor/zod/locales/fr.d.ts +4 -0
- package/vendor/zod/locales/fr.js +127 -0
- package/vendor/zod/locales/gu.d.ts +4 -0
- package/vendor/zod/locales/gu.js +111 -0
- package/vendor/zod/locales/he.d.ts +4 -0
- package/vendor/zod/locales/he.js +219 -0
- package/vendor/zod/locales/hi.d.ts +4 -0
- package/vendor/zod/locales/hi.js +109 -0
- package/vendor/zod/locales/hr.d.ts +4 -0
- package/vendor/zod/locales/hr.js +124 -0
- package/vendor/zod/locales/hu.d.ts +4 -0
- package/vendor/zod/locales/hu.js +111 -0
- package/vendor/zod/locales/hy.d.ts +4 -0
- package/vendor/zod/locales/hy.js +156 -0
- package/vendor/zod/locales/id.d.ts +4 -0
- package/vendor/zod/locales/id.js +109 -0
- package/vendor/zod/locales/index.d.ts +62 -0
- package/vendor/zod/locales/index.js +62 -0
- package/vendor/zod/locales/is.d.ts +4 -0
- package/vendor/zod/locales/is.js +112 -0
- package/vendor/zod/locales/it.d.ts +4 -0
- package/vendor/zod/locales/it.js +111 -0
- package/vendor/zod/locales/ja.d.ts +4 -0
- package/vendor/zod/locales/ja.js +110 -0
- package/vendor/zod/locales/ka.d.ts +4 -0
- package/vendor/zod/locales/ka.js +115 -0
- package/vendor/zod/locales/kh.d.ts +5 -0
- package/vendor/zod/locales/kh.js +5 -0
- package/vendor/zod/locales/km.d.ts +4 -0
- package/vendor/zod/locales/km.js +113 -0
- package/vendor/zod/locales/kn.d.ts +4 -0
- package/vendor/zod/locales/kn.js +114 -0
- package/vendor/zod/locales/ko.d.ts +4 -0
- package/vendor/zod/locales/ko.js +114 -0
- package/vendor/zod/locales/lt.d.ts +4 -0
- package/vendor/zod/locales/lt.js +205 -0
- package/vendor/zod/locales/mk.d.ts +4 -0
- package/vendor/zod/locales/mk.js +112 -0
- package/vendor/zod/locales/ms.d.ts +4 -0
- package/vendor/zod/locales/ms.js +110 -0
- package/vendor/zod/locales/ne.d.ts +4 -0
- package/vendor/zod/locales/ne.js +109 -0
- package/vendor/zod/locales/nl.d.ts +4 -0
- package/vendor/zod/locales/nl.js +113 -0
- package/vendor/zod/locales/nn.d.ts +4 -0
- package/vendor/zod/locales/nn.js +111 -0
- package/vendor/zod/locales/no.d.ts +4 -0
- package/vendor/zod/locales/no.js +111 -0
- package/vendor/zod/locales/ota.d.ts +4 -0
- package/vendor/zod/locales/ota.js +112 -0
- package/vendor/zod/locales/package.json +7 -0
- package/vendor/zod/locales/pl.d.ts +4 -0
- package/vendor/zod/locales/pl.js +112 -0
- package/vendor/zod/locales/ps.d.ts +4 -0
- package/vendor/zod/locales/ps.js +117 -0
- package/vendor/zod/locales/pt-BR.d.ts +4 -0
- package/vendor/zod/locales/pt-BR.js +141 -0
- package/vendor/zod/locales/pt.d.ts +4 -0
- package/vendor/zod/locales/pt.js +140 -0
- package/vendor/zod/locales/ro.d.ts +4 -0
- package/vendor/zod/locales/ro.js +120 -0
- package/vendor/zod/locales/ru.d.ts +4 -0
- package/vendor/zod/locales/ru.js +166 -0
- package/vendor/zod/locales/sk.d.ts +4 -0
- package/vendor/zod/locales/sk.js +114 -0
- package/vendor/zod/locales/sl.d.ts +4 -0
- package/vendor/zod/locales/sl.js +112 -0
- package/vendor/zod/locales/sv.d.ts +4 -0
- package/vendor/zod/locales/sv.js +113 -0
- package/vendor/zod/locales/ta.d.ts +4 -0
- package/vendor/zod/locales/ta.js +113 -0
- package/vendor/zod/locales/th.d.ts +4 -0
- package/vendor/zod/locales/th.js +113 -0
- package/vendor/zod/locales/tk.d.ts +4 -0
- package/vendor/zod/locales/tk.js +105 -0
- package/vendor/zod/locales/tr.d.ts +4 -0
- package/vendor/zod/locales/tr.js +108 -0
- package/vendor/zod/locales/ua.d.ts +5 -0
- package/vendor/zod/locales/ua.js +5 -0
- package/vendor/zod/locales/uk.d.ts +4 -0
- package/vendor/zod/locales/uk.js +111 -0
- package/vendor/zod/locales/ur.d.ts +4 -0
- package/vendor/zod/locales/ur.js +113 -0
- package/vendor/zod/locales/uz.d.ts +4 -0
- package/vendor/zod/locales/uz.js +111 -0
- package/vendor/zod/locales/vi.d.ts +4 -0
- package/vendor/zod/locales/vi.js +111 -0
- package/vendor/zod/locales/yo.d.ts +4 -0
- package/vendor/zod/locales/yo.js +110 -0
- package/vendor/zod/locales/zh-CN.d.ts +4 -0
- package/vendor/zod/locales/zh-CN.js +112 -0
- package/vendor/zod/locales/zh-TW.d.ts +4 -0
- package/vendor/zod/locales/zh-TW.js +110 -0
- package/vendor/zod/mini/checks.d.ts +1 -0
- package/vendor/zod/mini/checks.js +1 -0
- package/vendor/zod/mini/coerce.d.ts +7 -0
- package/vendor/zod/mini/coerce.js +22 -0
- package/vendor/zod/mini/deep-partial.d.ts +14 -0
- package/vendor/zod/mini/deep-partial.js +13 -0
- package/vendor/zod/mini/external.d.ts +16 -0
- package/vendor/zod/mini/external.js +16 -0
- package/vendor/zod/mini/in-out.d.ts +8 -0
- package/vendor/zod/mini/in-out.js +36 -0
- package/vendor/zod/mini/index.d.ts +3 -0
- package/vendor/zod/mini/index.js +3 -0
- package/vendor/zod/mini/iso.d.ts +22 -0
- package/vendor/zod/mini/iso.js +34 -0
- package/vendor/zod/mini/package.json +7 -0
- package/vendor/zod/mini/parse.d.ts +1 -0
- package/vendor/zod/mini/parse.js +1 -0
- package/vendor/zod/mini/schemas.d.ts +475 -0
- package/vendor/zod/mini/schemas.js +987 -0
- package/vendor/zod/package.json +7 -0
- package/vendor/zodexy/dist/esm/index.js +1274 -0
- package/vendor/zodexy/dist/index.js +1088 -0
- package/vendor/zodexy/dist/schema.zodexy.json +1036 -0
- package/dist/fundamentalTypes/nativeEnumType.d.ts +0 -6
- package/dist/fundamentalTypes/nativeEnumType.d.ts.map +0 -1
- package/src/fundamentalTypes/catchType.js +0 -140
- package/src/fundamentalTypes/enumType.js +0 -52
- package/src/fundamentalTypes/literalType.js +0 -81
- package/src/fundamentalTypes/nativeEnumType.js +0 -157
- package/src/fundamentalTypes/neverType.js +0 -49
- package/src/fundamentalTypes/recordType.js +0 -42
- package/src/fundamentalTypes/tupleType.js +0 -39
- package/src/fundamentalTypes/voidType.js +0 -48
- package/vendor/zod/lib/index.mjs +0 -4236
- package/vendor/zodex/dist/esm/dezerialize.d.ts +0 -26
- package/vendor/zodex/dist/esm/dezerialize.js +0 -393
- package/vendor/zodex/dist/esm/index.d.ts +0 -9
- package/vendor/zodex/dist/esm/index.js +0 -4
- package/vendor/zodex/dist/esm/infer.d.ts +0 -29
- package/vendor/zodex/dist/esm/package.json +0 -1
- package/vendor/zodex/dist/esm/types.d.ts +0 -201
- package/vendor/zodex/dist/esm/types.js +0 -13
- package/vendor/zodex/dist/esm/ui.d.ts +0 -23
- package/vendor/zodex/dist/esm/ui.js +0 -10
- package/vendor/zodex/dist/esm/zerialize.d.ts +0 -50
- package/vendor/zodex/dist/esm/zerialize.js +0 -417
- package/vendor/zodex/dist/esm/zod-types.d.ts +0 -11
- /package/vendor/{zodex/dist/esm/infer.js → zod/core/json-schema.js} +0 -0
- /package/vendor/{zodex/dist/esm/zod-types.js → zod/core/standard-schema.js} +0 -0
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
import { $ZodAsyncError } from "./core.js";
|
|
2
|
+
import { Doc } from "./doc.js";
|
|
3
|
+
import { isBackEdge, isRecursiveSchema } from "./memoizer.js";
|
|
4
|
+
import * as regexes from "./regexes.js";
|
|
5
|
+
import { isValidBase64, isValidBase64URL, isValidCIDRv6, isValidCreditCard, isValidIPv6, isValidJWT, mergeValues, parseURLObject, stripTabAndNewline, urlHostnameOk, urlProtocolOk, } from "./schemas.js";
|
|
6
|
+
import * as util from "./util.js";
|
|
7
|
+
/** Sentinel value returned by the compiled fast path when validation fails. Internal. */
|
|
8
|
+
export const INVALID = Symbol.for("zod.compile.invalid");
|
|
9
|
+
// Set on the parse ctx when a compiled wrapper falls back to the runtime, so nested compiled wrappers skip their fast paths for the rest of that parse.
|
|
10
|
+
const FALLBACK_FLAG = Symbol.for("zod.compile.fallback");
|
|
11
|
+
/** Raised when the schema contains async refinements or transforms. Surfaces only under `compile(schema, { strict: true })`. */
|
|
12
|
+
export class ZodCompileAsyncError extends Error {
|
|
13
|
+
constructor(message = "z.compile does not support async refinements, transforms, or checks") {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "ZodCompileAsyncError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Raised when the schema contains a feature whose semantics the fast path
|
|
20
|
+
* can't fully model. Both the shim in `zod/compile` and the default
|
|
21
|
+
* `compile()` fall back to the runtime parser for that schema; only
|
|
22
|
+
* `compile(schema, { strict: true })` lets it surface.
|
|
23
|
+
*/
|
|
24
|
+
export class ZodCompileUnsupportedError extends Error {
|
|
25
|
+
constructor(feature, islandable = true) {
|
|
26
|
+
super(`z.compile does not support ${feature}; this schema must use the runtime parser`);
|
|
27
|
+
this.name = "ZodCompileUnsupportedError";
|
|
28
|
+
this.islandable = islandable;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the validator `validate` calls: the same codegen as the parser with the output construction
|
|
33
|
+
* dropped. A schema the flag cannot express reuses the parser, which still answers correctly — it
|
|
34
|
+
* just builds a value nothing reads.
|
|
35
|
+
*/
|
|
36
|
+
function compileValidator(schema, parser) {
|
|
37
|
+
try {
|
|
38
|
+
return compileFn(schema, { assertOnly: true });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return parser;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* AOT-compile a Zod schema. Returns a clone whose `_zod.run` calls a generated
|
|
46
|
+
* fast path first and falls back to the original runtime parser on failure.
|
|
47
|
+
*
|
|
48
|
+
* - Forward direction only. Backward (encode), async, and `skipChecks` paths
|
|
49
|
+
* bypass the fast path and use the runtime directly.
|
|
50
|
+
* - Never throws. A schema the fast path can't model is returned unchanged and
|
|
51
|
+
* keeps using the runtime parser. Pass `{ strict: true }` to get the refusal
|
|
52
|
+
* as a thrown `ZodCompileUnsupportedError` / `ZodCompileAsyncError` instead.
|
|
53
|
+
* - The original schema is unchanged. The clone shares children by reference.
|
|
54
|
+
*/
|
|
55
|
+
export function compile(schema, options) {
|
|
56
|
+
try {
|
|
57
|
+
const parser = compileFn(schema);
|
|
58
|
+
const clone = util.clone(schema);
|
|
59
|
+
// Capture the source-of-truth runtime eagerly. If schema._zod.run is itself a shim installed by global-mode (`__originalRun` set), unwrap past it. Otherwise capturing the live property lazily would let a later self- replacement of schema._zod.run feed our wrapper back into itself.
|
|
60
|
+
const liveRun = schema._zod.run;
|
|
61
|
+
const originalRun = liveRun.__originalRun ?? liveRun;
|
|
62
|
+
// Delegate to the *original* schema's run on bypass/fallback (not the
|
|
63
|
+
// clone's). The original closed over its own `inst` at construction time;
|
|
64
|
+
// issue payloads use that reference to derive things like the class name
|
|
65
|
+
// for `z.instanceof(Test)`. Calling the clone's freshly-initialized run
|
|
66
|
+
// would push issues with `inst === clone`, producing diverging error
|
|
67
|
+
// messages from the original schema.
|
|
68
|
+
const wrapped = (payload, ctx) => {
|
|
69
|
+
if (ctx?.async ||
|
|
70
|
+
ctx?.direction === "backward" ||
|
|
71
|
+
ctx?.skipChecks ||
|
|
72
|
+
ctx?.[FALLBACK_FLAG]) {
|
|
73
|
+
return originalRun(payload, ctx);
|
|
74
|
+
}
|
|
75
|
+
// A memoized back-edge: only the runtime can close a reference cycle, and a transform on one must raise $ZodCyclicError from its own parse.
|
|
76
|
+
if (ctx && isBackEdge(ctx, payload.value)) {
|
|
77
|
+
return originalRun(payload, ctx);
|
|
78
|
+
}
|
|
79
|
+
const out = parser(payload.value);
|
|
80
|
+
if (out !== INVALID) {
|
|
81
|
+
payload.value = out;
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
// Mark this parse as runtime-driven: under global mode every nested schema carries its own compiled wrapper, and without the flag the parent's runtime fallback re-enters each child's fast path, running user callbacks a third time on invalid input.
|
|
85
|
+
if (ctx)
|
|
86
|
+
ctx[FALLBACK_FLAG] = true;
|
|
87
|
+
return originalRun(payload, ctx);
|
|
88
|
+
};
|
|
89
|
+
// Let later compiles of (or through) this run unwrap to the true runtime — both the global shim and repeated z.compile calls rely on this. The bag also carries the parser and the validator, so the standalone validate can skip the payload and wrapper on the happy path.
|
|
90
|
+
wrapped.__originalRun = originalRun;
|
|
91
|
+
clone._zod.bag.fallbackRun = originalRun;
|
|
92
|
+
clone._zod.bag.validator = compileValidator(schema, parser);
|
|
93
|
+
clone._zod.run = wrapped;
|
|
94
|
+
// The fast parse/safeParse closures fall back through the source schema's methods. If the source is shim- or wrapper-managed, those methods route into a compiled run and would execute user callbacks a third time on invalid input — the plain method → wrapper path is exactly 2x, so skip.
|
|
95
|
+
if (!liveRun.__originalRun)
|
|
96
|
+
installCompiledUserMethods(clone, schema, parser);
|
|
97
|
+
return clone;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (options?.strict)
|
|
101
|
+
throw err;
|
|
102
|
+
// a schema we can't compile still has to work, so hand it back untouched on the runtime parser — the same silent fallback global mode already does
|
|
103
|
+
return schema;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function installCompiledUserMethods(target, source, parser) {
|
|
107
|
+
const targetAny = target;
|
|
108
|
+
const sourceAny = source;
|
|
109
|
+
if (typeof sourceAny.safeParse === "function") {
|
|
110
|
+
const originalSafeParse = sourceAny.safeParse;
|
|
111
|
+
targetAny.safeParse = (data, params) => {
|
|
112
|
+
const out = parser(data);
|
|
113
|
+
if (out !== INVALID) {
|
|
114
|
+
return { success: true, data: out };
|
|
115
|
+
}
|
|
116
|
+
return originalSafeParse(data, params);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (typeof sourceAny.parse === "function") {
|
|
120
|
+
const originalParse = sourceAny.parse;
|
|
121
|
+
targetAny.parse = (data, params) => {
|
|
122
|
+
const out = parser(data);
|
|
123
|
+
if (out !== INVALID) {
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
return originalParse(data, params);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate the standalone compiled function: a parser by default, a validator under
|
|
132
|
+
* `assertOnly`. Returns either the parsed value, `true` where nothing reads the output,
|
|
133
|
+
* or the `INVALID` sentinel. Internal — consumers should use `compile()`.
|
|
134
|
+
*/
|
|
135
|
+
export function compileFn(schema, options) {
|
|
136
|
+
// Cycle-breaking is keyed on the parse context, which generated code never receives. `shape` can be a getter that throws (z.pick() with an unrecognized mask key), so treat "can't tell" as recursive.
|
|
137
|
+
let recursive = true;
|
|
138
|
+
try {
|
|
139
|
+
recursive = isRecursiveSchema(schema);
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
if (recursive) {
|
|
143
|
+
throw new ZodCompileUnsupportedError("a schema whose subtree contains a reference cycle");
|
|
144
|
+
}
|
|
145
|
+
const ctx = {
|
|
146
|
+
constants: new Map(),
|
|
147
|
+
constantCounter: 0,
|
|
148
|
+
varCounter: 0,
|
|
149
|
+
};
|
|
150
|
+
const doc = new Doc(["input"]);
|
|
151
|
+
const outputAccessor = generateCheck(doc, ctx, schema, "input", !options?.assertOnly);
|
|
152
|
+
// In assert mode a root that built nothing has already returned INVALID on every failure, so reaching the end means valid.
|
|
153
|
+
doc.write(outputAccessor === null ? `return true;` : `return ${outputAccessor};`);
|
|
154
|
+
// Build the function with hoisted constants Always include INVALID as the first constant
|
|
155
|
+
const constantNames = ["INVALID", ...ctx.constants.keys()];
|
|
156
|
+
const constantValues = [INVALID, ...ctx.constants.values()];
|
|
157
|
+
const code = doc.content.join("\n");
|
|
158
|
+
const fullCode = options?.debug
|
|
159
|
+
? constantNames.length > 0
|
|
160
|
+
? `// Constants: ${constantNames.join(", ")}\n${code}`
|
|
161
|
+
: code
|
|
162
|
+
: "";
|
|
163
|
+
const F = Function;
|
|
164
|
+
const factoryCode = `return (input) => {\n${code}\n}`;
|
|
165
|
+
let fn;
|
|
166
|
+
try {
|
|
167
|
+
const factory = new F(...constantNames, factoryCode);
|
|
168
|
+
fn = factory(...constantValues);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
// Malformed generated code (or a CSP environment rejecting `new Function`) surfaces as a typed error so the global shim falls back to the runtime instead of crashing with a raw SyntaxError/EvalError.
|
|
172
|
+
throw new ZodCompileUnsupportedError(`this schema (generated code failed to evaluate: ${err.message})`);
|
|
173
|
+
}
|
|
174
|
+
if (options?.debug) {
|
|
175
|
+
fn.code = fullCode;
|
|
176
|
+
}
|
|
177
|
+
return fn;
|
|
178
|
+
}
|
|
179
|
+
function addConstant(ctx, value) {
|
|
180
|
+
// Check if we already have this constant
|
|
181
|
+
for (const [name, v] of ctx.constants) {
|
|
182
|
+
if (v === value)
|
|
183
|
+
return name;
|
|
184
|
+
}
|
|
185
|
+
const name = `c${ctx.constantCounter++}`;
|
|
186
|
+
ctx.constants.set(name, value);
|
|
187
|
+
return name;
|
|
188
|
+
}
|
|
189
|
+
function newVar(ctx) {
|
|
190
|
+
return `v${ctx.varCounter++}`;
|
|
191
|
+
}
|
|
192
|
+
// Runs a child schema as a black box: its value, or INVALID for a failure or an async run.
|
|
193
|
+
function runtimeRun(schema, value) {
|
|
194
|
+
const result = schema._zod.run({ value, issues: [] }, {});
|
|
195
|
+
if (result && typeof result.then === "function")
|
|
196
|
+
return INVALID;
|
|
197
|
+
const r = result;
|
|
198
|
+
return r.issues.length === 0 ? r.value : INVALID;
|
|
199
|
+
}
|
|
200
|
+
// `null` means the node built no value because nothing reads it. The overloads keep that case out of the 20-odd callers that always want one, so only a caller passing needsValue has to handle it.
|
|
201
|
+
function compileChild(doc, ctx, schema, accessor, needsValue = true) {
|
|
202
|
+
const contentLen = doc.content.length;
|
|
203
|
+
const constantCount = ctx.constants.size;
|
|
204
|
+
const constantCounter = ctx.constantCounter;
|
|
205
|
+
const varCounter = ctx.varCounter;
|
|
206
|
+
try {
|
|
207
|
+
return generateCheck(doc, ctx, schema, accessor, needsValue);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
if (!(err instanceof ZodCompileUnsupportedError) || !err.islandable)
|
|
211
|
+
throw err;
|
|
212
|
+
doc.content.length = contentLen;
|
|
213
|
+
if (ctx.constants.size > constantCount) {
|
|
214
|
+
const trailing = Array.from(ctx.constants.keys()).slice(constantCount);
|
|
215
|
+
for (const k of trailing)
|
|
216
|
+
ctx.constants.delete(k);
|
|
217
|
+
}
|
|
218
|
+
ctx.constantCounter = constantCounter;
|
|
219
|
+
ctx.varCounter = varCounter;
|
|
220
|
+
return emitRuntimeIsland(doc, ctx, schema, accessor);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function emitRuntimeIsland(doc, ctx, schema, accessor) {
|
|
224
|
+
const schemaConst = addConstant(ctx, schema);
|
|
225
|
+
const runConst = addConstant(ctx, runtimeRun);
|
|
226
|
+
const outVar = newVar(ctx);
|
|
227
|
+
doc.write(`const ${outVar} = ${runConst}(${schemaConst}, ${accessor});`);
|
|
228
|
+
doc.write(`if (${outVar} === INVALID) return INVALID;`);
|
|
229
|
+
return outVar;
|
|
230
|
+
}
|
|
231
|
+
// Check classes whose `when` is auto-defaulted at init (checks.ts `when ??=`).
|
|
232
|
+
const WHEN_DEFAULTED_CHECKS = new Set([
|
|
233
|
+
"max_size",
|
|
234
|
+
"min_size",
|
|
235
|
+
"size_equals",
|
|
236
|
+
"max_length",
|
|
237
|
+
"min_length",
|
|
238
|
+
"length_equals",
|
|
239
|
+
]);
|
|
240
|
+
function generateChecks(doc, ctx, schema, accessor) {
|
|
241
|
+
const schemaChecks = schema._zod.def.checks;
|
|
242
|
+
if (!schemaChecks || schemaChecks.length === 0)
|
|
243
|
+
return accessor;
|
|
244
|
+
// Track current accessor - may change if overwrite checks are encountered
|
|
245
|
+
let currentAccessor = accessor;
|
|
246
|
+
for (const check of schemaChecks) {
|
|
247
|
+
const def = check._zod.def;
|
|
248
|
+
// A custom `when` skips a check the fast path always runs; the one auto-defaulted on size/length classes matches its bail-on-first-failure.
|
|
249
|
+
if (def.when && !WHEN_DEFAULTED_CHECKS.has(def.check)) {
|
|
250
|
+
throw new ZodCompileUnsupportedError(`check with a custom "when" condition`);
|
|
251
|
+
}
|
|
252
|
+
switch (def.check) {
|
|
253
|
+
case "greater_than":
|
|
254
|
+
generateGreaterThanCheck(doc, ctx, def, currentAccessor);
|
|
255
|
+
break;
|
|
256
|
+
case "less_than":
|
|
257
|
+
generateLessThanCheck(doc, ctx, def, currentAccessor);
|
|
258
|
+
break;
|
|
259
|
+
case "multiple_of":
|
|
260
|
+
generateMultipleOfCheck(doc, ctx, def, currentAccessor);
|
|
261
|
+
break;
|
|
262
|
+
case "number_format":
|
|
263
|
+
generateNumberFormatCheck(doc, def, currentAccessor);
|
|
264
|
+
break;
|
|
265
|
+
case "min_length": {
|
|
266
|
+
const min = numericOperand(def.minimum, "min_length");
|
|
267
|
+
const len = codePointLengthVar(doc, ctx, currentAccessor, `${currentAccessor}.length >= ${min} && ${currentAccessor}.length < ${def.minimum * 2}`);
|
|
268
|
+
doc.write(`if (${len} < ${min}) return INVALID;`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "max_length": {
|
|
272
|
+
const max = numericOperand(def.maximum, "max_length");
|
|
273
|
+
const len = codePointLengthVar(doc, ctx, currentAccessor, `${currentAccessor}.length > ${max}`);
|
|
274
|
+
doc.write(`if (${len} > ${max}) return INVALID;`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "length_equals": {
|
|
278
|
+
const exact = numericOperand(def.length, "length_equals");
|
|
279
|
+
const len = codePointLengthVar(doc, ctx, currentAccessor, `${currentAccessor}.length >= ${exact} && ${currentAccessor}.length <= ${def.length * 2}`);
|
|
280
|
+
doc.write(`if (${len} !== ${exact}) return INVALID;`);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "min_size":
|
|
284
|
+
doc.write(`if (${currentAccessor}.size < ${numericOperand(def.minimum, "min_size")}) return INVALID;`);
|
|
285
|
+
break;
|
|
286
|
+
case "max_size":
|
|
287
|
+
doc.write(`if (${currentAccessor}.size > ${numericOperand(def.maximum, "max_size")}) return INVALID;`);
|
|
288
|
+
break;
|
|
289
|
+
case "size_equals":
|
|
290
|
+
doc.write(`if (${currentAccessor}.size !== ${numericOperand(def.size, "size_equals")}) return INVALID;`);
|
|
291
|
+
break;
|
|
292
|
+
case "string_format":
|
|
293
|
+
currentAccessor = generateStringFormatCheck(doc, ctx, def, currentAccessor);
|
|
294
|
+
break;
|
|
295
|
+
case "custom":
|
|
296
|
+
currentAccessor = generateCustomRefineCheck(doc, ctx, check, currentAccessor);
|
|
297
|
+
break;
|
|
298
|
+
case "bigint_format":
|
|
299
|
+
generateBigIntFormatCheck(doc, def, currentAccessor);
|
|
300
|
+
break;
|
|
301
|
+
case "mime_type":
|
|
302
|
+
generateMimeTypeCheck(doc, ctx, def, currentAccessor);
|
|
303
|
+
break;
|
|
304
|
+
case "property":
|
|
305
|
+
generatePropertyCheck(doc, ctx, def, currentAccessor);
|
|
306
|
+
break;
|
|
307
|
+
case "overwrite": {
|
|
308
|
+
// Overwrite transforms the value - create new variable for transformed result
|
|
309
|
+
const newAccessor = newVar(ctx);
|
|
310
|
+
generateOverwriteCheck(doc, ctx, check, currentAccessor, newAccessor);
|
|
311
|
+
currentAccessor = newAccessor;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
default: {
|
|
315
|
+
void def;
|
|
316
|
+
throw new ZodCompileUnsupportedError(`check type ${def.check}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return currentAccessor;
|
|
321
|
+
}
|
|
322
|
+
// Emit the length operand for a length check, mirroring `$ZodCheckMinLength` and friends: strings measure in code points, everything else in `.length`. `inDoubt` is the caller's cheap UTF-16 bound test — outside it the unit count already settles the comparison, so the scan is skipped.
|
|
323
|
+
function codePointLengthVar(doc, ctx, accessor, inDoubt) {
|
|
324
|
+
const cpLen = addConstant(ctx, util.codePointLength);
|
|
325
|
+
const v = newVar(ctx);
|
|
326
|
+
doc.write(`const ${v} = typeof ${accessor} === "string" && ${inDoubt} ? ${cpLen}(${accessor}) : ${accessor}.length;`);
|
|
327
|
+
return v;
|
|
328
|
+
}
|
|
329
|
+
// Emit a source operand for a gt/lt bound. Numbers inline; Dates hoist as a constant (relational operators compare via valueOf). NaN and Invalid Date bounds can't compile to a comparison that matches runtime semantics.
|
|
330
|
+
/**
|
|
331
|
+
* A count bound reaches generated source verbatim, so a non-number would be
|
|
332
|
+
* emitted as code rather than as a value — `min('0) {} evil(); if (0')` writes an
|
|
333
|
+
* arbitrary statement into the function body. TypeScript types these as `number`
|
|
334
|
+
* and fromJSONSchema guards them, so this is a backstop rather than a live hole,
|
|
335
|
+
* but generated source is the one place a wrong type stops being a type error.
|
|
336
|
+
*/
|
|
337
|
+
function numericOperand(value, label) {
|
|
338
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
339
|
+
throw new ZodCompileUnsupportedError(`${label} bound of type ${typeof value}`);
|
|
340
|
+
}
|
|
341
|
+
return `${value}`;
|
|
342
|
+
}
|
|
343
|
+
function comparisonOperand(ctx, value) {
|
|
344
|
+
if (typeof value === "bigint")
|
|
345
|
+
return `${value}n`;
|
|
346
|
+
if (typeof value === "number") {
|
|
347
|
+
if (Number.isNaN(value))
|
|
348
|
+
throw new ZodCompileUnsupportedError("comparison check with NaN bound");
|
|
349
|
+
return `${value}`;
|
|
350
|
+
}
|
|
351
|
+
if (value instanceof Date) {
|
|
352
|
+
if (Number.isNaN(value.getTime())) {
|
|
353
|
+
throw new ZodCompileUnsupportedError("comparison check with Invalid Date bound");
|
|
354
|
+
}
|
|
355
|
+
return addConstant(ctx, value);
|
|
356
|
+
}
|
|
357
|
+
throw new ZodCompileUnsupportedError(`comparison check bound of type ${typeof value}`);
|
|
358
|
+
}
|
|
359
|
+
function generateGreaterThanCheck(doc, ctx, def, accessor) {
|
|
360
|
+
const op = def.inclusive ? "<" : "<=";
|
|
361
|
+
doc.write(`if (${accessor} ${op} ${comparisonOperand(ctx, def.value)}) return INVALID;`);
|
|
362
|
+
}
|
|
363
|
+
function generateLessThanCheck(doc, ctx, def, accessor) {
|
|
364
|
+
const op = def.inclusive ? ">" : ">=";
|
|
365
|
+
doc.write(`if (${accessor} ${op} ${comparisonOperand(ctx, def.value)}) return INVALID;`);
|
|
366
|
+
}
|
|
367
|
+
function generateMultipleOfCheck(doc, ctx, def, accessor) {
|
|
368
|
+
if (typeof def.value === "bigint") {
|
|
369
|
+
// a zero divisor has no compiled form: `x % 0n` throws
|
|
370
|
+
if (def.value === BigInt(0))
|
|
371
|
+
throw new ZodCompileUnsupportedError("multiple_of check with a zero divisor");
|
|
372
|
+
doc.write(`if (${accessor} % ${def.value}n !== 0n) return INVALID;`);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// Float `%` has well-known precision issues for sub-integer steps
|
|
376
|
+
// (`1.5 % 0.1`, `2.5e-7 % 1e-7`). Defer to util.floatSafeRemainder so the
|
|
377
|
+
// exact tolerance logic stays in one place — single function call is fine
|
|
378
|
+
// since `multipleOf` runs at most once per number.
|
|
379
|
+
const remainder = addConstant(ctx, util.floatSafeRemainder);
|
|
380
|
+
doc.write(`if (${remainder}(${accessor}, ${numericOperand(def.value, "multiple_of")}) !== 0) return INVALID;`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function generateNumberFormatCheck(doc, def, accessor) {
|
|
384
|
+
const format = def.format;
|
|
385
|
+
switch (format) {
|
|
386
|
+
case "safeint":
|
|
387
|
+
doc.write(`if (!Number.isSafeInteger(${accessor})) return INVALID;`);
|
|
388
|
+
break;
|
|
389
|
+
case "int32":
|
|
390
|
+
doc.write(`if (!Number.isInteger(${accessor}) || ${accessor} < -2147483648 || ${accessor} > 2147483647) return INVALID;`);
|
|
391
|
+
break;
|
|
392
|
+
case "uint32":
|
|
393
|
+
doc.write(`if (!Number.isInteger(${accessor}) || ${accessor} < 0 || ${accessor} > 4294967295) return INVALID;`);
|
|
394
|
+
break;
|
|
395
|
+
case "float32":
|
|
396
|
+
// Float32 range per util.NUMBER_FORMAT_RANGES
|
|
397
|
+
doc.write(`if (!Number.isFinite(${accessor}) || ${accessor} < -3.4028234663852886e38 || ${accessor} > 3.4028234663852886e38) return INVALID;`);
|
|
398
|
+
break;
|
|
399
|
+
case "float64":
|
|
400
|
+
doc.write(`if (!Number.isFinite(${accessor})) return INVALID;`);
|
|
401
|
+
break;
|
|
402
|
+
default: {
|
|
403
|
+
void format;
|
|
404
|
+
throw new ZodCompileUnsupportedError(`number format ${format}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function generateBigIntFormatCheck(doc, def, accessor) {
|
|
409
|
+
const format = def.format;
|
|
410
|
+
if (!format)
|
|
411
|
+
return; // undefined format means no range check
|
|
412
|
+
switch (format) {
|
|
413
|
+
case "int64":
|
|
414
|
+
doc.write(`if (${accessor} < -9223372036854775808n || ${accessor} > 9223372036854775807n) return INVALID;`);
|
|
415
|
+
break;
|
|
416
|
+
case "uint64":
|
|
417
|
+
doc.write(`if (${accessor} < 0n || ${accessor} > 18446744073709551615n) return INVALID;`);
|
|
418
|
+
break;
|
|
419
|
+
default: {
|
|
420
|
+
void format;
|
|
421
|
+
throw new ZodCompileUnsupportedError(`bigint format ${format}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function generateMimeTypeCheck(doc, ctx, def, accessor) {
|
|
426
|
+
const mimeTypes = def.mime;
|
|
427
|
+
if (mimeTypes && mimeTypes.length > 0) {
|
|
428
|
+
const mimeSet = addConstant(ctx, new Set(mimeTypes));
|
|
429
|
+
doc.write(`if (!${mimeSet}.has(${accessor}.type)) return INVALID;`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function generatePropertyCheck(doc, ctx, def, accessor) {
|
|
433
|
+
const propAccessor = `${accessor}[${JSON.stringify(def.property)}]`;
|
|
434
|
+
generateCheck(doc, ctx, def.schema, propAccessor);
|
|
435
|
+
}
|
|
436
|
+
function generateOverwriteCheck(doc, ctx, check, currentAccessor, newAccessor) {
|
|
437
|
+
const tx = check._zod.def.tx;
|
|
438
|
+
if (!tx) {
|
|
439
|
+
throw new ZodCompileUnsupportedError("overwrite check without a transform function");
|
|
440
|
+
}
|
|
441
|
+
// Check for async transform
|
|
442
|
+
if (isAsyncFunction(tx)) {
|
|
443
|
+
throw new ZodCompileAsyncError("z.compile: async overwrite transforms are not supported");
|
|
444
|
+
}
|
|
445
|
+
// Hoist the transform function as a constant and apply it
|
|
446
|
+
const txConst = addConstant(ctx, tx);
|
|
447
|
+
doc.write(`const ${newAccessor} = ${txConst}(${currentAccessor});`);
|
|
448
|
+
}
|
|
449
|
+
/** A predicate that hands back a thenable is an async check reached synchronously, and the interpreter throws `$ZodAsyncError` for it. Returning INVALID instead would be a bail-out, and a union reads a bail-out as a rejected branch and answers with a later one — so the throw has to survive into generated code. */
|
|
450
|
+
function throwAsync() {
|
|
451
|
+
throw new $ZodAsyncError();
|
|
452
|
+
}
|
|
453
|
+
/** Shared `addIssue` for the spoofed payloads a refine, check or transform receives. Allocating one per call — a fresh closure plus a `this`-bound method on a fresh literal — pinned every payload-allocating schema at ~2.7M ops/sec against 135M for a plain object literal. It captures nothing per call; it only reaches `this.issues`. */
|
|
454
|
+
function pushIssue(issue) {
|
|
455
|
+
this.issues.push(issue);
|
|
456
|
+
}
|
|
457
|
+
function generateCustomRefineCheck(doc, ctx, check, accessor) {
|
|
458
|
+
const def = check._zod.def;
|
|
459
|
+
if (def.fn) {
|
|
460
|
+
// Simple predicate function (from .refine())
|
|
461
|
+
if (isAsyncFunction(def.fn)) {
|
|
462
|
+
throw new ZodCompileAsyncError("z.compile: async .refine() predicates are not supported");
|
|
463
|
+
}
|
|
464
|
+
const fnConst = addConstant(ctx, def.fn);
|
|
465
|
+
const throwAsyncConst = addConstant(ctx, throwAsync);
|
|
466
|
+
const resVar = newVar(ctx);
|
|
467
|
+
doc.write(`const ${resVar} = ${fnConst}(${accessor});`);
|
|
468
|
+
// A thenable is truthy, so it would otherwise read as a pass. It is not a rejection either: the interpreter throws, and INVALID inside a union would just hand the parse to the next branch.
|
|
469
|
+
doc.write(`if (${resVar} instanceof Promise) ${throwAsyncConst}();`);
|
|
470
|
+
doc.write(`if (!${resVar}) return INVALID;`);
|
|
471
|
+
// A `.refine()` predicate only answers yes or no; it cannot rewrite the value.
|
|
472
|
+
return accessor;
|
|
473
|
+
}
|
|
474
|
+
if (check._zod.check) {
|
|
475
|
+
if (isAsyncFunction(check._zod.check)) {
|
|
476
|
+
throw new ZodCompileAsyncError("z.compile: async .superRefine() / check functions are not supported");
|
|
477
|
+
}
|
|
478
|
+
// SuperRefine or other check function - need to spoof context Create a helper that runs the check and returns true if no issues
|
|
479
|
+
const checkFn = check._zod.check;
|
|
480
|
+
// `$RefinementCtx` extends the parse payload, so a check may rewrite
|
|
481
|
+
// `ctx.value` as well as push issues — `.superRefine((v, ctx) => { ctx.value =
|
|
482
|
+
// v.trim() })` is a value transform. Returning only a boolean discarded that
|
|
483
|
+
// write and emitted the untrimmed input. Hand the value back and thread it on.
|
|
484
|
+
const helperFn = (value) => {
|
|
485
|
+
const fakePayload = { value, issues: [], addIssue: pushIssue };
|
|
486
|
+
const result = checkFn(fakePayload);
|
|
487
|
+
// Throw rather than return INVALID: the interpreter throws here, and a union would read INVALID as a rejected branch.
|
|
488
|
+
if (result instanceof Promise)
|
|
489
|
+
throwAsync();
|
|
490
|
+
return fakePayload.issues.length === 0 ? fakePayload.value : INVALID;
|
|
491
|
+
};
|
|
492
|
+
const helperConst = addConstant(ctx, helperFn);
|
|
493
|
+
const outVar = newVar(ctx);
|
|
494
|
+
doc.write(`const ${outVar} = ${helperConst}(${accessor});`);
|
|
495
|
+
doc.write(`if (${outVar} === INVALID) return INVALID;`);
|
|
496
|
+
return outVar;
|
|
497
|
+
}
|
|
498
|
+
throw new ZodCompileUnsupportedError("custom check without a predicate or check function");
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Built-in formats that validate with nothing but `def.pattern`, so compiling
|
|
502
|
+
* the regex reproduces the runtime exactly. Deliberately an allowlist: a format
|
|
503
|
+
* missing from it loses its fast path, while a format wrongly added to it
|
|
504
|
+
* silently accepts input the runtime rejects. Formats that layer extra
|
|
505
|
+
* validation over a shape-only pattern (`credit_card`, `base64`, `ipv6`, …) are
|
|
506
|
+
* handled above by hoisting the runtime validator itself.
|
|
507
|
+
*/
|
|
508
|
+
const PATTERN_IS_COMPLETE = new Set([
|
|
509
|
+
"cidrv4",
|
|
510
|
+
"cuid",
|
|
511
|
+
"cuid2",
|
|
512
|
+
"date",
|
|
513
|
+
"datetime",
|
|
514
|
+
"duration",
|
|
515
|
+
"e164",
|
|
516
|
+
"email",
|
|
517
|
+
"emoji",
|
|
518
|
+
"ends_with",
|
|
519
|
+
"guid",
|
|
520
|
+
"includes",
|
|
521
|
+
"ipv4",
|
|
522
|
+
"ksuid",
|
|
523
|
+
"lowercase",
|
|
524
|
+
"mac",
|
|
525
|
+
"nanoid",
|
|
526
|
+
"regex",
|
|
527
|
+
"starts_with",
|
|
528
|
+
"time",
|
|
529
|
+
"ulid",
|
|
530
|
+
"uppercase",
|
|
531
|
+
"uuid",
|
|
532
|
+
"xid",
|
|
533
|
+
]);
|
|
534
|
+
// Returns the accessor holding the (possibly normalized) value after the check — url/normalize formats produce a new value like overwrite does. Never assigns to the incoming accessor: it may be a `const` or a property expression on user input.
|
|
535
|
+
function generateStringFormatCheck(doc, ctx, def, accessor) {
|
|
536
|
+
// Some string formats do runtime validation beyond their advertised pattern. For cheap pure utility checks, hoist the runtime function and call it so the fast path stays correct without cloning the utility logic into codegen.
|
|
537
|
+
const fmt = def.format;
|
|
538
|
+
if (fmt === "base64") {
|
|
539
|
+
const validator = addConstant(ctx, isValidBase64);
|
|
540
|
+
doc.write(`if (!${validator}(${accessor})) return INVALID;`);
|
|
541
|
+
return accessor;
|
|
542
|
+
}
|
|
543
|
+
if (fmt === "base64url") {
|
|
544
|
+
const validator = addConstant(ctx, isValidBase64URL);
|
|
545
|
+
doc.write(`if (!${validator}(${accessor})) return INVALID;`);
|
|
546
|
+
return accessor;
|
|
547
|
+
}
|
|
548
|
+
if (fmt === "jwt") {
|
|
549
|
+
const validator = addConstant(ctx, isValidJWT);
|
|
550
|
+
const alg = addConstant(ctx, def.alg ?? null);
|
|
551
|
+
doc.write(`if (!${validator}(${accessor}, ${alg})) return INVALID;`);
|
|
552
|
+
return accessor;
|
|
553
|
+
}
|
|
554
|
+
if (fmt === "ipv6") {
|
|
555
|
+
const validator = addConstant(ctx, isValidIPv6);
|
|
556
|
+
doc.write(`if (!${validator}(${accessor})) return INVALID;`);
|
|
557
|
+
return accessor;
|
|
558
|
+
}
|
|
559
|
+
if (fmt === "cidrv6") {
|
|
560
|
+
const validator = addConstant(ctx, isValidCIDRv6);
|
|
561
|
+
doc.write(`if (!${validator}(${accessor})) return INVALID;`);
|
|
562
|
+
return accessor;
|
|
563
|
+
}
|
|
564
|
+
if (fmt === "credit_card") {
|
|
565
|
+
const validator = addConstant(ctx, isValidCreditCard);
|
|
566
|
+
doc.write(`if (!${validator}(${accessor})) return INVALID;`);
|
|
567
|
+
return accessor;
|
|
568
|
+
}
|
|
569
|
+
const formatDef = def;
|
|
570
|
+
if (fmt === "url" ||
|
|
571
|
+
fmt === "httpurl" ||
|
|
572
|
+
formatDef.normalize ||
|
|
573
|
+
formatDef.hostname !== undefined ||
|
|
574
|
+
formatDef.protocol !== undefined) {
|
|
575
|
+
// Same three predicates the runtime calls, in the same order, so there is no second URL implementation to drift. Which options exist is known now, so the calls the runtime makes conditionally are emitted conditionally instead.
|
|
576
|
+
const parseConst = addConstant(ctx, parseURLObject);
|
|
577
|
+
const defConst = addConstant(ctx, def);
|
|
578
|
+
const trimVar = newVar(ctx);
|
|
579
|
+
const urlVar = newVar(ctx);
|
|
580
|
+
doc.write(`const ${trimVar} = ${accessor}.trim();`);
|
|
581
|
+
doc.write(`const ${urlVar} = ${parseConst}(${trimVar}, ${defConst});`);
|
|
582
|
+
doc.write(`if (typeof ${urlVar} === "number") return INVALID;`);
|
|
583
|
+
if (formatDef.hostname !== undefined) {
|
|
584
|
+
const hostnameConst = addConstant(ctx, urlHostnameOk);
|
|
585
|
+
doc.write(`if (!${hostnameConst}(${urlVar}, ${defConst}.hostname)) return INVALID;`);
|
|
586
|
+
}
|
|
587
|
+
if (formatDef.protocol !== undefined) {
|
|
588
|
+
const protocolConst = addConstant(ctx, urlProtocolOk);
|
|
589
|
+
doc.write(`if (!${protocolConst}(${urlVar}, ${defConst}.protocol)) return INVALID;`);
|
|
590
|
+
}
|
|
591
|
+
const outputVar = newVar(ctx);
|
|
592
|
+
const outputExpr = formatDef.normalize ? `${urlVar}.href` : `${addConstant(ctx, stripTabAndNewline)}(${trimVar})`;
|
|
593
|
+
doc.write(`const ${outputVar} = ${outputExpr};`);
|
|
594
|
+
return outputVar;
|
|
595
|
+
}
|
|
596
|
+
// A custom string format carries the predicate the runtime actually calls. Hoist and call it instead of testing `def.pattern`: the two only coincide when the format was built from a RegExp, and relying on that coupling is how supplemental validation gets dropped.
|
|
597
|
+
const customFn = def.fn;
|
|
598
|
+
if (customFn) {
|
|
599
|
+
if (isAsyncFunction(customFn))
|
|
600
|
+
throw new ZodCompileUnsupportedError(`async string format ${fmt}`);
|
|
601
|
+
const fnConst = addConstant(ctx, customFn);
|
|
602
|
+
doc.write(`if (!${fnConst}(${accessor})) return INVALID;`);
|
|
603
|
+
return accessor;
|
|
604
|
+
}
|
|
605
|
+
// Formats whose `pattern` IS the whole check. An allowlist rather than `if (def.pattern)`, because credit_card, base64 and ipv6 carry a shape-only pattern and validate the rest separately.
|
|
606
|
+
if (PATTERN_IS_COMPLETE.has(fmt) && def.pattern) {
|
|
607
|
+
const patternConst = addConstant(ctx, def.pattern);
|
|
608
|
+
doc.write(`${patternConst}.lastIndex = 0;`);
|
|
609
|
+
doc.write(`if (!${patternConst}.test(${accessor})) return INVALID;`);
|
|
610
|
+
return accessor;
|
|
611
|
+
}
|
|
612
|
+
const format = def.format;
|
|
613
|
+
switch (format) {
|
|
614
|
+
case "regex":
|
|
615
|
+
// A regex check with a pattern returned above. Reaching here means there is no pattern to test, and accepting unconditionally would pass every input, so hand the schema back to the runtime.
|
|
616
|
+
throw new ZodCompileUnsupportedError("regex format without a pattern");
|
|
617
|
+
case "lowercase":
|
|
618
|
+
doc.write(`if (${accessor} !== ${accessor}.toLowerCase()) return INVALID;`);
|
|
619
|
+
break;
|
|
620
|
+
case "uppercase":
|
|
621
|
+
doc.write(`if (${accessor} !== ${accessor}.toUpperCase()) return INVALID;`);
|
|
622
|
+
break;
|
|
623
|
+
case "includes":
|
|
624
|
+
doc.write(`if (!${accessor}.includes(${util.esc(def.includes)})) return INVALID;`);
|
|
625
|
+
break;
|
|
626
|
+
case "starts_with": {
|
|
627
|
+
const prefix = def.prefix;
|
|
628
|
+
doc.write(`if (${accessor}.slice(0, ${prefix.length}) !== ${util.esc(prefix)}) return INVALID;`);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case "ends_with": {
|
|
632
|
+
const suffix = def.suffix;
|
|
633
|
+
doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${util.esc(suffix)}) return INVALID;`);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
default: {
|
|
637
|
+
void format;
|
|
638
|
+
throw new ZodCompileUnsupportedError(`string format ${format}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return accessor;
|
|
642
|
+
}
|
|
643
|
+
function generateCheck(doc, ctx, schema, accessor, needsValue = true) {
|
|
644
|
+
const def = schema._zod.def;
|
|
645
|
+
const type = def.type;
|
|
646
|
+
// A coercing schema would compile to the bare type test and reject what it should convert; inside a union that reads as a rejected branch, so refuse at codegen.
|
|
647
|
+
if (def.coerce) {
|
|
648
|
+
throw new ZodCompileUnsupportedError(`coercion (z.coerce.${type}())`);
|
|
649
|
+
}
|
|
650
|
+
// A node builds its output when its caller reads one, or when it carries checks of its own, since a check reads what was built. One polarity for the whole walk: this is what the node does, and it is what its children are told they need.
|
|
651
|
+
const buildsValue = needsValue || !!def.checks?.length;
|
|
652
|
+
let typeAccessor;
|
|
653
|
+
switch (type) {
|
|
654
|
+
case "string":
|
|
655
|
+
typeAccessor = generateStringCheck(doc, ctx, schema, accessor);
|
|
656
|
+
break;
|
|
657
|
+
case "number":
|
|
658
|
+
typeAccessor = generateNumberCheck(doc, schema, accessor);
|
|
659
|
+
break;
|
|
660
|
+
case "boolean":
|
|
661
|
+
typeAccessor = generateBooleanCheck(doc, accessor);
|
|
662
|
+
break;
|
|
663
|
+
case "bigint":
|
|
664
|
+
typeAccessor = generateBigIntCheck(doc, schema, accessor);
|
|
665
|
+
break;
|
|
666
|
+
case "symbol":
|
|
667
|
+
typeAccessor = generateSymbolCheck(doc, accessor);
|
|
668
|
+
break;
|
|
669
|
+
case "undefined":
|
|
670
|
+
typeAccessor = generateUndefinedCheck(doc, accessor);
|
|
671
|
+
break;
|
|
672
|
+
case "null":
|
|
673
|
+
typeAccessor = generateNullCheck(doc, accessor);
|
|
674
|
+
break;
|
|
675
|
+
case "any":
|
|
676
|
+
case "unknown":
|
|
677
|
+
// No check needed - pass through
|
|
678
|
+
typeAccessor = accessor;
|
|
679
|
+
break;
|
|
680
|
+
case "never":
|
|
681
|
+
doc.write("return INVALID;");
|
|
682
|
+
typeAccessor = accessor;
|
|
683
|
+
break;
|
|
684
|
+
case "void":
|
|
685
|
+
typeAccessor = generateVoidCheck(doc, accessor);
|
|
686
|
+
break;
|
|
687
|
+
case "nan":
|
|
688
|
+
typeAccessor = generateNaNCheck(doc, accessor);
|
|
689
|
+
break;
|
|
690
|
+
case "date":
|
|
691
|
+
typeAccessor = generateDateCheck(doc, accessor);
|
|
692
|
+
break;
|
|
693
|
+
case "object":
|
|
694
|
+
typeAccessor = generateObjectCheck(doc, ctx, schema, accessor, buildsValue);
|
|
695
|
+
break;
|
|
696
|
+
case "optional":
|
|
697
|
+
typeAccessor = generateOptionalCheck(doc, ctx, schema, accessor, buildsValue);
|
|
698
|
+
break;
|
|
699
|
+
case "nullable":
|
|
700
|
+
typeAccessor = generateNullableCheck(doc, ctx, schema, accessor, buildsValue);
|
|
701
|
+
break;
|
|
702
|
+
case "array":
|
|
703
|
+
typeAccessor = generateArrayCheck(doc, ctx, schema, accessor, buildsValue);
|
|
704
|
+
break;
|
|
705
|
+
case "literal":
|
|
706
|
+
typeAccessor = generateLiteralCheck(doc, ctx, schema, accessor);
|
|
707
|
+
break;
|
|
708
|
+
case "enum":
|
|
709
|
+
typeAccessor = generateEnumCheck(doc, ctx, schema, accessor);
|
|
710
|
+
break;
|
|
711
|
+
case "readonly": {
|
|
712
|
+
const innerOut = generateWrapperCheck(doc, ctx, schema, accessor);
|
|
713
|
+
// Runtime freezes the parsed value (schemas.ts handleReadonlyResult).
|
|
714
|
+
const frozenVar = newVar(ctx);
|
|
715
|
+
doc.write(`const ${frozenVar} = Object.freeze(${innerOut});`);
|
|
716
|
+
typeAccessor = frozenVar;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
case "success":
|
|
720
|
+
// Runtime output is `issues.length === 0`. The fast path only reaches
|
|
721
|
+
// here when the inner check passed (failure returns INVALID and the
|
|
722
|
+
// runtime fallback reproduces the inner issues), so the output is
|
|
723
|
+
// always `true`.
|
|
724
|
+
generateWrapperCheck(doc, ctx, schema, accessor);
|
|
725
|
+
typeAccessor = "true";
|
|
726
|
+
break;
|
|
727
|
+
case "default":
|
|
728
|
+
case "prefault":
|
|
729
|
+
typeAccessor = generateDefaultCheck(doc, ctx, schema, accessor);
|
|
730
|
+
break;
|
|
731
|
+
case "nonoptional":
|
|
732
|
+
typeAccessor = generateNonOptionalCheck(doc, ctx, schema, accessor);
|
|
733
|
+
break;
|
|
734
|
+
case "tuple":
|
|
735
|
+
typeAccessor = generateTupleCheck(doc, ctx, schema, accessor);
|
|
736
|
+
break;
|
|
737
|
+
case "union":
|
|
738
|
+
typeAccessor = generateUnionCheck(doc, ctx, schema, accessor);
|
|
739
|
+
break;
|
|
740
|
+
case "intersection":
|
|
741
|
+
typeAccessor = generateIntersectionCheck(doc, ctx, schema, accessor);
|
|
742
|
+
break;
|
|
743
|
+
case "record":
|
|
744
|
+
typeAccessor = generateRecordCheck(doc, ctx, schema, accessor);
|
|
745
|
+
break;
|
|
746
|
+
case "map":
|
|
747
|
+
typeAccessor = generateMapCheck(doc, ctx, schema, accessor);
|
|
748
|
+
break;
|
|
749
|
+
case "set":
|
|
750
|
+
typeAccessor = generateSetCheck(doc, ctx, schema, accessor);
|
|
751
|
+
break;
|
|
752
|
+
case "file":
|
|
753
|
+
typeAccessor = generateFileCheck(doc, accessor);
|
|
754
|
+
break;
|
|
755
|
+
case "template_literal":
|
|
756
|
+
typeAccessor = generateTemplateLiteralCheck(doc, ctx, schema, accessor);
|
|
757
|
+
break;
|
|
758
|
+
case "lazy":
|
|
759
|
+
typeAccessor = generateLazyCheck(doc, ctx, schema, accessor);
|
|
760
|
+
break;
|
|
761
|
+
case "pipe":
|
|
762
|
+
typeAccessor = generatePipeCheck(doc, ctx, schema, accessor);
|
|
763
|
+
break;
|
|
764
|
+
case "custom":
|
|
765
|
+
typeAccessor = generateCustomCheck(doc, ctx, schema, accessor);
|
|
766
|
+
break;
|
|
767
|
+
case "transform":
|
|
768
|
+
typeAccessor = generateTransformCheck(doc, ctx, schema, accessor);
|
|
769
|
+
break;
|
|
770
|
+
case "catch":
|
|
771
|
+
typeAccessor = generateCatchCheck(doc, ctx, schema, accessor);
|
|
772
|
+
break;
|
|
773
|
+
default: {
|
|
774
|
+
void type;
|
|
775
|
+
throw new ZodCompileUnsupportedError(`schema type ${type}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// a node that built nothing has no checks to run: not building requires an empty check list
|
|
779
|
+
if (typeAccessor === null)
|
|
780
|
+
return null;
|
|
781
|
+
// Generate checks after the type-specific validation (may transform value)
|
|
782
|
+
return generateChecks(doc, ctx, schema, typeAccessor);
|
|
783
|
+
}
|
|
784
|
+
function generateStringCheck(doc, ctx, schema, accessor) {
|
|
785
|
+
doc.write(`if (typeof ${accessor} !== "string") return INVALID;`);
|
|
786
|
+
// z.email() carries its format on the def, z.string().email() in def.checks; both route here so the format table has no second copy to drift from.
|
|
787
|
+
const def = schema._zod.def;
|
|
788
|
+
if (def.format === undefined)
|
|
789
|
+
return accessor;
|
|
790
|
+
return generateStringFormatCheck(doc, ctx, def, accessor);
|
|
791
|
+
}
|
|
792
|
+
function generateNumberCheck(doc, schema, accessor) {
|
|
793
|
+
// Runtime z.number() rejects NaN and ±Infinity. Number.isFinite covers both.
|
|
794
|
+
doc.write(`if (typeof ${accessor} !== "number" || !Number.isFinite(${accessor})) return INVALID;`);
|
|
795
|
+
// Mini factories like z.int(), z.int32(), z.uint32(), z.float32() bake a number_format check into the schema def itself (not into def.checks). Apply the same constraint here.
|
|
796
|
+
const def = schema._zod.def;
|
|
797
|
+
if (def.check === "number_format" && def.format) {
|
|
798
|
+
generateNumberFormatCheck(doc, { format: def.format }, accessor);
|
|
799
|
+
}
|
|
800
|
+
return accessor;
|
|
801
|
+
}
|
|
802
|
+
function generateBooleanCheck(doc, accessor) {
|
|
803
|
+
doc.write(`if (typeof ${accessor} !== "boolean") return INVALID;`);
|
|
804
|
+
return accessor;
|
|
805
|
+
}
|
|
806
|
+
function generateBigIntCheck(doc, schema, accessor) {
|
|
807
|
+
doc.write(`if (typeof ${accessor} !== "bigint") return INVALID;`);
|
|
808
|
+
// Handle bigint format (int64, uint64) directly on the schema def
|
|
809
|
+
const def = schema._zod.def;
|
|
810
|
+
if (def.format) {
|
|
811
|
+
switch (def.format) {
|
|
812
|
+
case "int64":
|
|
813
|
+
doc.write(`if (${accessor} < -9223372036854775808n || ${accessor} > 9223372036854775807n) return INVALID;`);
|
|
814
|
+
break;
|
|
815
|
+
case "uint64":
|
|
816
|
+
doc.write(`if (${accessor} < 0n || ${accessor} > 18446744073709551615n) return INVALID;`);
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return accessor;
|
|
821
|
+
}
|
|
822
|
+
function generateSymbolCheck(doc, accessor) {
|
|
823
|
+
doc.write(`if (typeof ${accessor} !== "symbol") return INVALID;`);
|
|
824
|
+
return accessor;
|
|
825
|
+
}
|
|
826
|
+
function generateUndefinedCheck(doc, accessor) {
|
|
827
|
+
doc.write(`if (${accessor} !== undefined) return INVALID;`);
|
|
828
|
+
return accessor;
|
|
829
|
+
}
|
|
830
|
+
function generateNullCheck(doc, accessor) {
|
|
831
|
+
doc.write(`if (${accessor} !== null) return INVALID;`);
|
|
832
|
+
return accessor;
|
|
833
|
+
}
|
|
834
|
+
function generateVoidCheck(doc, accessor) {
|
|
835
|
+
doc.write(`if (${accessor} !== undefined) return INVALID;`);
|
|
836
|
+
return accessor;
|
|
837
|
+
}
|
|
838
|
+
function generateNaNCheck(doc, accessor) {
|
|
839
|
+
doc.write(`if (typeof ${accessor} !== "number" || !Number.isNaN(${accessor})) return INVALID;`);
|
|
840
|
+
return accessor;
|
|
841
|
+
}
|
|
842
|
+
function generateDateCheck(doc, accessor) {
|
|
843
|
+
doc.write(`if (!(${accessor} instanceof Date) || Number.isNaN(${accessor}.getTime())) return INVALID;`);
|
|
844
|
+
return accessor;
|
|
845
|
+
}
|
|
846
|
+
function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
847
|
+
const def = schema._zod.def;
|
|
848
|
+
// Check that input is a non-null, non-array object
|
|
849
|
+
doc.write(`if (typeof ${accessor} !== "object" || ${accessor} === null || Array.isArray(${accessor})) return INVALID;`);
|
|
850
|
+
const shape = def.shape;
|
|
851
|
+
const keys = Object.keys(shape);
|
|
852
|
+
const symbolKeys = Object.getOwnPropertySymbols(shape);
|
|
853
|
+
// a symbol has no source literal, so it is hoisted as a constant; `keys` stays string-only where the emitted code uses `for...in`
|
|
854
|
+
const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;
|
|
855
|
+
const keyExpr = (k) => (typeof k === "symbol" ? addConstant(ctx, k) : util.esc(k));
|
|
856
|
+
const propKey = (k) => (typeof k === "symbol" ? `[${keyExpr(k)}]` : util.esc(k));
|
|
857
|
+
const propShape = shape;
|
|
858
|
+
// `__proto__` as an own shape key can't be expressed in an output object literal (the literal form sets the prototype instead of an own property).
|
|
859
|
+
if (keys.includes("__proto__")) {
|
|
860
|
+
throw new ZodCompileUnsupportedError('object shape key "__proto__"');
|
|
861
|
+
}
|
|
862
|
+
// Map from key to output accessor for that property
|
|
863
|
+
const propOutputs = new Map();
|
|
864
|
+
// Validate each property and collect output accessors
|
|
865
|
+
for (const key of allKeys) {
|
|
866
|
+
const propSchema = propShape[key];
|
|
867
|
+
const kx = keyExpr(key);
|
|
868
|
+
// Always cache the property read: the runtime reads input[key] exactly once, so a getter must not be re-read by checks or output assembly.
|
|
869
|
+
const inputVar = newVar(ctx);
|
|
870
|
+
doc.write(`const ${inputVar} = ${accessor}[${kx}];`);
|
|
871
|
+
if (propSchema._zod.optin !== undefined) {
|
|
872
|
+
// Any optin rung means the key may be omitted. The runtime runs the property anyway and ignores issues only when the key is genuinely absent, which is what makes exactOptional compositional.
|
|
873
|
+
const outputVar = newVar(ctx);
|
|
874
|
+
doc.write(`let ${outputVar} = (() => {`);
|
|
875
|
+
doc.indented((d) => {
|
|
876
|
+
const outputAccessor = compileChild(d, ctx, propSchema, inputVar);
|
|
877
|
+
d.write(`return ${outputAccessor};`);
|
|
878
|
+
});
|
|
879
|
+
doc.write(`})();`);
|
|
880
|
+
if (propSchema._zod.optout === "optional") {
|
|
881
|
+
doc.write(`if (${outputVar} === INVALID) {`);
|
|
882
|
+
doc.indented((d) => {
|
|
883
|
+
d.write(`if (${kx} in ${accessor}) return INVALID;`);
|
|
884
|
+
d.write(`${outputVar} = undefined;`);
|
|
885
|
+
});
|
|
886
|
+
doc.write(`}`);
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
doc.write(`if (${outputVar} === INVALID) return INVALID;`);
|
|
890
|
+
}
|
|
891
|
+
propOutputs.set(key, outputVar);
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
if (requiresPresenceCheck(propSchema)) {
|
|
895
|
+
doc.write(`if (!(${kx} in ${accessor})) return INVALID;`);
|
|
896
|
+
}
|
|
897
|
+
// Generate check and get output accessor
|
|
898
|
+
const outputAccessor = compileChild(doc, ctx, propSchema, inputVar, buildsValue);
|
|
899
|
+
if (outputAccessor !== null)
|
|
900
|
+
propOutputs.set(key, outputAccessor);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
// Handle catchall
|
|
904
|
+
const catchall = def.catchall;
|
|
905
|
+
let unknownKeysMode = "none";
|
|
906
|
+
if (catchall) {
|
|
907
|
+
const catchallType = catchall._zod.def.type;
|
|
908
|
+
if (catchallType === "never") {
|
|
909
|
+
// Strict: one `for...in`, as the runtime does, so inherited enumerable keys count. An undeclared `__proto__` is reported here and excluded only from the output (#6221); an own-key count would wrongly reject a class instance.
|
|
910
|
+
const condition = keys.map((k) => `k !== ${util.esc(k)}`).join(" && ") || "true";
|
|
911
|
+
doc.write(`for (const k in ${accessor}) {`);
|
|
912
|
+
doc.indented((d) => {
|
|
913
|
+
d.write(`if (${condition}) return INVALID;`);
|
|
914
|
+
});
|
|
915
|
+
doc.write(`}`);
|
|
916
|
+
}
|
|
917
|
+
else if ((catchallType === "unknown" || catchallType === "any") && !catchall._zod.def.checks?.length) {
|
|
918
|
+
unknownKeysMode = "passthrough";
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
unknownKeysMode = "schema";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// else: strip mode (no catchall) - unknown keys ignored, only include known keys
|
|
925
|
+
// Shape keys in declared order, then unknown keys in for...in order. A middle-rung key is included iff present on the input, else iff its output is not undefined.
|
|
926
|
+
const outputVar = newVar(ctx);
|
|
927
|
+
const hasConditionalKeys = allKeys.some((k) => mayOutputUndefined(propShape[k]) || dropsWhenAbsent(propShape[k]));
|
|
928
|
+
// Assert mode: every declared key is validated above, so the output literal and the unknown-key copy are pure waste. A `never` catchall already emitted its rejection loop; a schema catchall still has to validate the values it would otherwise have stored.
|
|
929
|
+
if (!buildsValue) {
|
|
930
|
+
if (unknownKeysMode === "schema") {
|
|
931
|
+
const knownSet = keys.length > 0 ? addConstant(ctx, new Set(keys)) : null;
|
|
932
|
+
doc.write(`for (const k in ${accessor}) {`);
|
|
933
|
+
doc.indented((d) => {
|
|
934
|
+
d.write(`if (k === "__proto__") continue;`);
|
|
935
|
+
if (knownSet)
|
|
936
|
+
d.write(`if (${knownSet}.has(k)) continue;`);
|
|
937
|
+
const valVar = newVar(ctx);
|
|
938
|
+
d.write(`const ${valVar} = ${accessor}[k];`);
|
|
939
|
+
compileChild(d, ctx, catchall, valVar, false);
|
|
940
|
+
});
|
|
941
|
+
doc.write(`}`);
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
if (!hasConditionalKeys) {
|
|
946
|
+
const propLiterals = allKeys.map((k) => `${propKey(k)}: ${propOutputs.get(k)}`).join(", ");
|
|
947
|
+
doc.write(`const ${outputVar} = { ${propLiterals} };`);
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
doc.write(`const ${outputVar} = {};`);
|
|
951
|
+
for (const k of allKeys) {
|
|
952
|
+
const kx = keyExpr(k);
|
|
953
|
+
const out = propOutputs.get(k);
|
|
954
|
+
if (dropsWhenAbsent(propShape[k])) {
|
|
955
|
+
doc.write(`if (${kx} in ${accessor}) ${outputVar}[${kx}] = ${out};`);
|
|
956
|
+
}
|
|
957
|
+
else if (mayOutputUndefined(propShape[k])) {
|
|
958
|
+
doc.write(`if (${out} !== undefined || ${kx} in ${accessor}) ${outputVar}[${kx}] = ${out};`);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
doc.write(`${outputVar}[${kx}] = ${out};`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (unknownKeysMode !== "none") {
|
|
966
|
+
// Unknown keys are written directly into the output after shape keys — for...in (like the runtime) so inherited enumerables participate.
|
|
967
|
+
const knownSet = keys.length > 0 ? addConstant(ctx, new Set(keys)) : null;
|
|
968
|
+
doc.write(`for (const k in ${accessor}) {`);
|
|
969
|
+
doc.indented((d) => {
|
|
970
|
+
// Skip __proto__: assigning obj["__proto__"] on a plain {} replaces the prototype via the setter rather than adding an own property. Mirrors the runtime catchall fix (#5898).
|
|
971
|
+
d.write(`if (k === "__proto__") continue;`);
|
|
972
|
+
if (knownSet)
|
|
973
|
+
d.write(`if (${knownSet}.has(k)) continue;`);
|
|
974
|
+
if (unknownKeysMode === "passthrough") {
|
|
975
|
+
d.write(`${outputVar}[k] = ${accessor}[k];`);
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
const valVar = newVar(ctx);
|
|
979
|
+
d.write(`const ${valVar} = ${accessor}[k];`);
|
|
980
|
+
const catchallOut = compileChild(d, ctx, catchall, valVar);
|
|
981
|
+
d.write(`${outputVar}[k] = ${catchallOut};`);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
doc.write(`}`);
|
|
985
|
+
}
|
|
986
|
+
return outputVar;
|
|
987
|
+
}
|
|
988
|
+
function generateOptionalCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
989
|
+
const def = schema._zod.def;
|
|
990
|
+
if (isExactOptional(schema)) {
|
|
991
|
+
return generateCheck(doc, ctx, def.innerType, accessor, buildsValue);
|
|
992
|
+
}
|
|
993
|
+
// Same question $ZodOptional asks: only the top rung of the optin ladder substitutes a value for an absent input, so only it is worth running on `undefined`. Every other rung leaves the value intact, which is the skip branch below.
|
|
994
|
+
if (def.innerType._zod.optin === "defaulted") {
|
|
995
|
+
const outputVar = newVar(ctx);
|
|
996
|
+
const branchVar = newVar(ctx);
|
|
997
|
+
doc.write(`let ${outputVar};`);
|
|
998
|
+
doc.write(`if (${accessor} === undefined) {`);
|
|
999
|
+
doc.indented((d) => {
|
|
1000
|
+
d.write(`const ${branchVar} = (() => {`);
|
|
1001
|
+
d.indented((d2) => {
|
|
1002
|
+
const innerOutput = generateCheck(d2, ctx, def.innerType, accessor);
|
|
1003
|
+
d2.write(`return ${innerOutput};`);
|
|
1004
|
+
});
|
|
1005
|
+
d.write(`})();`);
|
|
1006
|
+
d.write(`if (${branchVar} !== INVALID) ${outputVar} = ${branchVar};`);
|
|
1007
|
+
});
|
|
1008
|
+
doc.write(`} else {`);
|
|
1009
|
+
doc.indented((d) => {
|
|
1010
|
+
const innerOutput = generateCheck(d, ctx, def.innerType, accessor);
|
|
1011
|
+
d.write(`${outputVar} = ${innerOutput};`);
|
|
1012
|
+
});
|
|
1013
|
+
doc.write(`}`);
|
|
1014
|
+
return outputVar;
|
|
1015
|
+
}
|
|
1016
|
+
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
1017
|
+
if (outputVar)
|
|
1018
|
+
doc.write(`let ${outputVar};`);
|
|
1019
|
+
doc.write(`if (${accessor} !== undefined) {`);
|
|
1020
|
+
doc.indented((d) => {
|
|
1021
|
+
const innerOutput = generateCheck(d, ctx, def.innerType, accessor, buildsValue);
|
|
1022
|
+
if (outputVar && innerOutput !== null)
|
|
1023
|
+
d.write(`${outputVar} = ${innerOutput};`);
|
|
1024
|
+
});
|
|
1025
|
+
doc.write(`}`);
|
|
1026
|
+
return outputVar;
|
|
1027
|
+
}
|
|
1028
|
+
function isExactOptional(schema) {
|
|
1029
|
+
return schema._zod.traits?.has("$ZodExactOptional") === true;
|
|
1030
|
+
}
|
|
1031
|
+
// A value-level fast path reads an absent key as `undefined`, so z.undefined(), z.any() and unions containing undefined would accept a missing property the runtime rejects.
|
|
1032
|
+
function requiresPresenceCheck(schema) {
|
|
1033
|
+
return schema._zod.optin === undefined && fastPathAcceptsAbsence(schema);
|
|
1034
|
+
}
|
|
1035
|
+
function fastPathAcceptsAbsence(schema) {
|
|
1036
|
+
// An island is handed `input[key]` and cannot tell absent from explicitly undefined, so report absence-accepting and let the object emit the presence guard (#6405).
|
|
1037
|
+
if (schema._zod.def.coerce)
|
|
1038
|
+
return true;
|
|
1039
|
+
const def = schema._zod.def;
|
|
1040
|
+
switch (def.type) {
|
|
1041
|
+
case "any":
|
|
1042
|
+
case "unknown":
|
|
1043
|
+
case "undefined":
|
|
1044
|
+
case "void":
|
|
1045
|
+
case "default":
|
|
1046
|
+
case "prefault":
|
|
1047
|
+
case "transform":
|
|
1048
|
+
case "custom":
|
|
1049
|
+
case "lazy":
|
|
1050
|
+
return true;
|
|
1051
|
+
case "string":
|
|
1052
|
+
case "number":
|
|
1053
|
+
case "boolean":
|
|
1054
|
+
case "bigint":
|
|
1055
|
+
case "symbol":
|
|
1056
|
+
case "null":
|
|
1057
|
+
case "never":
|
|
1058
|
+
case "nan":
|
|
1059
|
+
case "date":
|
|
1060
|
+
case "object":
|
|
1061
|
+
case "array":
|
|
1062
|
+
case "tuple":
|
|
1063
|
+
case "record":
|
|
1064
|
+
case "map":
|
|
1065
|
+
case "set":
|
|
1066
|
+
case "file":
|
|
1067
|
+
case "template_literal":
|
|
1068
|
+
return false;
|
|
1069
|
+
case "nonoptional":
|
|
1070
|
+
// Normally rejects an absent key, but not when something inside supplies a value for it: `.default(v).optional().nonoptional()` accepts absence and yields v. Defer to the inner — over-reporting here only costs a presence check, and a fast path that wrongly rejects still falls back to the runtime.
|
|
1071
|
+
return def.innerType ? fastPathAcceptsAbsence(def.innerType) : false;
|
|
1072
|
+
case "literal":
|
|
1073
|
+
return !!def.values?.includes(undefined);
|
|
1074
|
+
case "enum":
|
|
1075
|
+
return !!schema._zod.values?.has(undefined);
|
|
1076
|
+
case "optional":
|
|
1077
|
+
case "nullable":
|
|
1078
|
+
case "readonly":
|
|
1079
|
+
case "success":
|
|
1080
|
+
return def.innerType ? fastPathAcceptsAbsence(def.innerType) : true;
|
|
1081
|
+
case "catch":
|
|
1082
|
+
// catch always produces a value (inner may fail → catchValue substitutes), so it accepts an absent key regardless of inner.
|
|
1083
|
+
return true;
|
|
1084
|
+
case "union":
|
|
1085
|
+
return def.options ? def.options.some(fastPathAcceptsAbsence) : true;
|
|
1086
|
+
case "intersection":
|
|
1087
|
+
if (!def.left || !def.right)
|
|
1088
|
+
return true;
|
|
1089
|
+
return fastPathAcceptsAbsence(def.left) && fastPathAcceptsAbsence(def.right);
|
|
1090
|
+
case "pipe":
|
|
1091
|
+
return def.in ? fastPathAcceptsAbsence(def.in) : true;
|
|
1092
|
+
default:
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
/** The middle rung permits absence without supplying anything in its place, so an absent key contributes nothing — mirrors the leading gate in `handlePropertyResult`. */
|
|
1097
|
+
function dropsWhenAbsent(schema) {
|
|
1098
|
+
return schema._zod.optin === "optional" && schema._zod.optout === "optional";
|
|
1099
|
+
}
|
|
1100
|
+
// Whether a schema's success-path output can be `undefined`. Object output
|
|
1101
|
+
// assembly gives such props the runtime's value-or-presence inclusion rule;
|
|
1102
|
+
// everything else keeps the unconditional object-literal slot.
|
|
1103
|
+
function mayOutputUndefined(schema) {
|
|
1104
|
+
const def = schema._zod.def;
|
|
1105
|
+
switch (def.type) {
|
|
1106
|
+
case "string":
|
|
1107
|
+
case "number":
|
|
1108
|
+
case "boolean":
|
|
1109
|
+
case "bigint":
|
|
1110
|
+
case "symbol":
|
|
1111
|
+
case "null":
|
|
1112
|
+
case "nan":
|
|
1113
|
+
case "date":
|
|
1114
|
+
case "object":
|
|
1115
|
+
case "array":
|
|
1116
|
+
case "tuple":
|
|
1117
|
+
case "record":
|
|
1118
|
+
case "map":
|
|
1119
|
+
case "set":
|
|
1120
|
+
case "file":
|
|
1121
|
+
case "template_literal":
|
|
1122
|
+
case "never":
|
|
1123
|
+
case "success":
|
|
1124
|
+
return false;
|
|
1125
|
+
case "literal":
|
|
1126
|
+
return !!def.values?.includes(undefined);
|
|
1127
|
+
case "enum":
|
|
1128
|
+
return !!schema._zod.values?.has(undefined);
|
|
1129
|
+
case "optional":
|
|
1130
|
+
return true;
|
|
1131
|
+
case "nullable":
|
|
1132
|
+
case "readonly":
|
|
1133
|
+
case "nonoptional":
|
|
1134
|
+
return def.innerType ? mayOutputUndefined(def.innerType) : true;
|
|
1135
|
+
case "union":
|
|
1136
|
+
return def.options ? def.options.some(mayOutputUndefined) : true;
|
|
1137
|
+
case "intersection":
|
|
1138
|
+
return !def.left || !def.right || mayOutputUndefined(def.left) || mayOutputUndefined(def.right);
|
|
1139
|
+
case "pipe":
|
|
1140
|
+
return def.out ? mayOutputUndefined(def.out) : true;
|
|
1141
|
+
default:
|
|
1142
|
+
// any/unknown/undefined/void/default/prefault/transform/custom/lazy/catch
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
1147
|
+
const def = schema._zod.def;
|
|
1148
|
+
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
1149
|
+
if (outputVar)
|
|
1150
|
+
doc.write(`let ${outputVar} = null;`);
|
|
1151
|
+
doc.write(`if (${accessor} !== null) {`);
|
|
1152
|
+
doc.indented((d) => {
|
|
1153
|
+
const innerOutput = generateCheck(d, ctx, def.innerType, accessor, buildsValue);
|
|
1154
|
+
if (outputVar && innerOutput !== null)
|
|
1155
|
+
d.write(`${outputVar} = ${innerOutput};`);
|
|
1156
|
+
});
|
|
1157
|
+
doc.write(`}`);
|
|
1158
|
+
return outputVar;
|
|
1159
|
+
}
|
|
1160
|
+
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
1161
|
+
const def = schema._zod.def;
|
|
1162
|
+
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
1163
|
+
// Build a new array with validated/transformed elements.
|
|
1164
|
+
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
1165
|
+
const iVar = newVar(ctx);
|
|
1166
|
+
const elemVar = newVar(ctx);
|
|
1167
|
+
if (outputVar)
|
|
1168
|
+
doc.write(`const ${outputVar} = new Array(${accessor}.length);`);
|
|
1169
|
+
doc.write(`for (let ${iVar} = 0; ${iVar} < ${accessor}.length; ${iVar}++) {`);
|
|
1170
|
+
doc.indented((d) => {
|
|
1171
|
+
d.write(`const ${elemVar} = ${accessor}[${iVar}];`);
|
|
1172
|
+
const elemOutput = compileChild(d, ctx, def.element, elemVar, buildsValue);
|
|
1173
|
+
if (outputVar && elemOutput !== null)
|
|
1174
|
+
d.write(`${outputVar}[${iVar}] = ${elemOutput};`);
|
|
1175
|
+
});
|
|
1176
|
+
doc.write(`}`);
|
|
1177
|
+
return outputVar;
|
|
1178
|
+
}
|
|
1179
|
+
function generateLiteralCheck(doc, ctx, schema, accessor) {
|
|
1180
|
+
const def = schema._zod.def;
|
|
1181
|
+
const values = def.values;
|
|
1182
|
+
// Anything but a single value goes through Set.has: multi-value so every value participates, empty so nothing does — values[0] would be undefined and compile to an `!== undefined` check that accepts it. Single-value stays inlined for speed.
|
|
1183
|
+
if (values.length !== 1) {
|
|
1184
|
+
const literalSet = addConstant(ctx, new Set(values));
|
|
1185
|
+
doc.write(`if (!${literalSet}.has(${accessor})) return INVALID;`);
|
|
1186
|
+
return accessor;
|
|
1187
|
+
}
|
|
1188
|
+
const value = values[0];
|
|
1189
|
+
// `$ZodLiteral` matches with `values.has`, i.e. SameValueZero, so NaN matches itself. `x !== NaN` is true for every input, which would reject everything — hand it to the same Set form the multi-value path uses.
|
|
1190
|
+
if (typeof value === "number" && Number.isNaN(value)) {
|
|
1191
|
+
const literalSet = addConstant(ctx, new Set(values));
|
|
1192
|
+
doc.write(`if (!${literalSet}.has(${accessor})) return INVALID;`);
|
|
1193
|
+
return accessor;
|
|
1194
|
+
}
|
|
1195
|
+
if (typeof value === "string") {
|
|
1196
|
+
doc.write(`if (${accessor} !== ${util.esc(value)}) return INVALID;`);
|
|
1197
|
+
}
|
|
1198
|
+
else if (typeof value === "number" || typeof value === "boolean") {
|
|
1199
|
+
doc.write(`if (${accessor} !== ${value}) return INVALID;`);
|
|
1200
|
+
}
|
|
1201
|
+
else if (value === null) {
|
|
1202
|
+
doc.write(`if (${accessor} !== null) return INVALID;`);
|
|
1203
|
+
}
|
|
1204
|
+
else if (value === undefined) {
|
|
1205
|
+
doc.write(`if (${accessor} !== undefined) return INVALID;`);
|
|
1206
|
+
}
|
|
1207
|
+
else if (typeof value === "bigint") {
|
|
1208
|
+
doc.write(`if (${accessor} !== ${value}n) return INVALID;`);
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
throw new ZodCompileUnsupportedError(`literal type ${typeof value}`);
|
|
1212
|
+
}
|
|
1213
|
+
return accessor;
|
|
1214
|
+
}
|
|
1215
|
+
function generateEnumCheck(doc, ctx, schema, accessor) {
|
|
1216
|
+
const values = schema._zod.values;
|
|
1217
|
+
// z.partialRecord and friends clear `_zod.values`, leaving no set to test membership against. Throw rather than emit INVALID so a union falls back whole instead of counting a false rejection.
|
|
1218
|
+
if (!values) {
|
|
1219
|
+
throw new ZodCompileUnsupportedError("enum schema without enumerated values");
|
|
1220
|
+
}
|
|
1221
|
+
const enumSet = addConstant(ctx, values);
|
|
1222
|
+
doc.write(`if (!${enumSet}.has(${accessor})) return INVALID;`);
|
|
1223
|
+
return accessor;
|
|
1224
|
+
}
|
|
1225
|
+
function generateWrapperCheck(doc, ctx, schema, accessor) {
|
|
1226
|
+
const def = schema._zod.def;
|
|
1227
|
+
return generateCheck(doc, ctx, def.innerType, accessor);
|
|
1228
|
+
}
|
|
1229
|
+
function generateDefaultCheck(doc, ctx, schema, accessor) {
|
|
1230
|
+
const def = schema._zod.def;
|
|
1231
|
+
// `defaultValue` is an accessor on schemas built through the classic/mini
|
|
1232
|
+
// factories and a plain data property on ones rebuilt programmatically
|
|
1233
|
+
// (deepPartial and friends). Read it off the def either way — taking only
|
|
1234
|
+
// `descriptor.get` silently dropped the default for the second kind, so
|
|
1235
|
+
// `.default(v)` compiled to `undefined` instead of `v`.
|
|
1236
|
+
const descriptor = Object.getOwnPropertyDescriptor(schema._zod.def, "defaultValue");
|
|
1237
|
+
const defaultGetter = descriptor
|
|
1238
|
+
? () => schema._zod.def.defaultValue
|
|
1239
|
+
: undefined;
|
|
1240
|
+
// prefault differs from default: undefined-input is first replaced with the prefault value, then run through the inner schema.
|
|
1241
|
+
if (schema._zod.def.type === "prefault") {
|
|
1242
|
+
if (!defaultGetter) {
|
|
1243
|
+
return generateCheck(doc, ctx, def.innerType, accessor);
|
|
1244
|
+
}
|
|
1245
|
+
const defaultFn = addConstant(ctx, defaultGetter);
|
|
1246
|
+
const inputVar = newVar(ctx);
|
|
1247
|
+
doc.write(`let ${inputVar} = ${accessor};`);
|
|
1248
|
+
doc.write(`if (${accessor} === undefined) ${inputVar} = ${defaultFn}();`);
|
|
1249
|
+
return generateCheck(doc, ctx, def.innerType, inputVar);
|
|
1250
|
+
}
|
|
1251
|
+
const outputVar = newVar(ctx);
|
|
1252
|
+
// Default allows undefined (replaces with default value), otherwise validates inner type
|
|
1253
|
+
if (defaultGetter) {
|
|
1254
|
+
const defaultFn = addConstant(ctx, defaultGetter);
|
|
1255
|
+
const cloneFn = addConstant(ctx, util.shallowClone);
|
|
1256
|
+
doc.write(`let ${outputVar};`);
|
|
1257
|
+
doc.write(`if (${accessor} === undefined) {`);
|
|
1258
|
+
doc.indented((d) => {
|
|
1259
|
+
// Shallow-clone the default so callers can mutate the result without affecting subsequent parses (#5855 — also covers Map/Set).
|
|
1260
|
+
d.write(`${outputVar} = ${cloneFn}(${defaultFn}());`);
|
|
1261
|
+
});
|
|
1262
|
+
doc.write(`} else {`);
|
|
1263
|
+
doc.indented((d) => {
|
|
1264
|
+
const innerOutput = generateCheck(d, ctx, def.innerType, accessor);
|
|
1265
|
+
d.write(`${outputVar} = ${innerOutput} === undefined ? ${cloneFn}(${defaultFn}()) : ${innerOutput};`);
|
|
1266
|
+
});
|
|
1267
|
+
doc.write(`}`);
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
doc.write(`let ${outputVar};`);
|
|
1271
|
+
doc.write(`if (${accessor} !== undefined) {`);
|
|
1272
|
+
doc.indented((d) => {
|
|
1273
|
+
const innerOutput = generateCheck(d, ctx, def.innerType, accessor);
|
|
1274
|
+
d.write(`${outputVar} = ${innerOutput};`);
|
|
1275
|
+
});
|
|
1276
|
+
doc.write(`}`);
|
|
1277
|
+
}
|
|
1278
|
+
return outputVar;
|
|
1279
|
+
}
|
|
1280
|
+
function generateNonOptionalCheck(doc, ctx, schema, accessor) {
|
|
1281
|
+
const def = schema._zod.def;
|
|
1282
|
+
// The runtime inspects what the inner produced, not what it was given: a catch can turn a defined input into undefined, a default an absent one into a value.
|
|
1283
|
+
const innerOutput = generateCheck(doc, ctx, def.innerType, accessor);
|
|
1284
|
+
const outputVar = newVar(ctx);
|
|
1285
|
+
doc.write(`const ${outputVar} = ${innerOutput};`);
|
|
1286
|
+
doc.write(`if (${outputVar} === undefined) return INVALID;`);
|
|
1287
|
+
return outputVar;
|
|
1288
|
+
}
|
|
1289
|
+
function generateTupleCheck(doc, ctx, schema, accessor) {
|
|
1290
|
+
const def = schema._zod.def;
|
|
1291
|
+
const items = def.items;
|
|
1292
|
+
const rest = def.rest;
|
|
1293
|
+
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
1294
|
+
// Mirror the runtime's getTupleOptStart: find the first index where every subsequent slot accepts `undefined` (i.e. is optional on input). Anything shorter than this is too_small; trailing absent slots are legal.
|
|
1295
|
+
const optinStart = getTupleOptStart(items, "optin");
|
|
1296
|
+
const optoutStart = getTupleOptStart(items, "optout");
|
|
1297
|
+
// Length bounds
|
|
1298
|
+
if (rest) {
|
|
1299
|
+
// With rest: minimum length is the last required input slot
|
|
1300
|
+
doc.write(`if (${accessor}.length < ${optinStart}) return INVALID;`);
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
// No rest: input must be in [optinStart, items.length]
|
|
1304
|
+
doc.write(`if (${accessor}.length < ${optinStart} || ${accessor}.length > ${items.length}) return INVALID;`);
|
|
1305
|
+
}
|
|
1306
|
+
// Build the output in assignment order so absent optional-output tail slots can truncate while default/prefault slots still fill missing positions.
|
|
1307
|
+
const outputVar = newVar(ctx);
|
|
1308
|
+
doc.write(`const ${outputVar} = [];`);
|
|
1309
|
+
// Validate and collect each fixed item
|
|
1310
|
+
for (let i = 0; i < items.length; i++) {
|
|
1311
|
+
const itemSchema = items[i];
|
|
1312
|
+
if (i >= optoutStart) {
|
|
1313
|
+
doc.write(`if (${outputVar}.length === ${i}) {`);
|
|
1314
|
+
doc.indented((d) => {
|
|
1315
|
+
d.write(`if (${i} < ${accessor}.length) {`);
|
|
1316
|
+
d.indented((d2) => {
|
|
1317
|
+
const elemVar = newVar(ctx);
|
|
1318
|
+
d2.write(`const ${elemVar} = ${accessor}[${i}];`);
|
|
1319
|
+
const elemOutput = compileChild(d2, ctx, itemSchema, elemVar);
|
|
1320
|
+
d2.write(`${outputVar}[${i}] = ${elemOutput};`);
|
|
1321
|
+
});
|
|
1322
|
+
d.write(`} else {`);
|
|
1323
|
+
d.indented((d2) => {
|
|
1324
|
+
// Middle rung: absence supplies nothing in its place, so truncate rather than running the item on `undefined` and keeping what it invents. Mirrors the leading gate in `handleTupleResults`.
|
|
1325
|
+
if (dropsWhenAbsent(itemSchema)) {
|
|
1326
|
+
d2.write(`${outputVar}.length = ${i};`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const elemVar = newVar(ctx);
|
|
1330
|
+
const branchVar = newVar(ctx);
|
|
1331
|
+
d2.write(`const ${elemVar} = undefined;`);
|
|
1332
|
+
d2.write(`const ${branchVar} = (() => {`);
|
|
1333
|
+
d2.indented((d3) => {
|
|
1334
|
+
const elemOutput = compileChild(d3, ctx, itemSchema, elemVar);
|
|
1335
|
+
d3.write(`return ${elemOutput};`);
|
|
1336
|
+
});
|
|
1337
|
+
d2.write(`})();`);
|
|
1338
|
+
d2.write(`if (${branchVar} === INVALID || ${branchVar} === undefined) ${outputVar}.length = ${i};`);
|
|
1339
|
+
d2.write(`else ${outputVar}[${i}] = ${branchVar};`);
|
|
1340
|
+
});
|
|
1341
|
+
d.write(`}`);
|
|
1342
|
+
});
|
|
1343
|
+
doc.write(`}`);
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
const elemVar = newVar(ctx);
|
|
1347
|
+
doc.write(`const ${elemVar} = ${accessor}[${i}];`);
|
|
1348
|
+
const elemOutput = compileChild(doc, ctx, itemSchema, elemVar);
|
|
1349
|
+
doc.write(`${outputVar}[${i}] = ${elemOutput};`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
// Validate and collect rest elements if present
|
|
1353
|
+
if (rest) {
|
|
1354
|
+
const iVar = newVar(ctx);
|
|
1355
|
+
const elemVar = newVar(ctx);
|
|
1356
|
+
doc.write(`for (let ${iVar} = ${items.length}; ${iVar} < ${accessor}.length; ${iVar}++) {`);
|
|
1357
|
+
doc.indented((d) => {
|
|
1358
|
+
d.write(`const ${elemVar} = ${accessor}[${iVar}];`);
|
|
1359
|
+
const elemOutput = compileChild(d, ctx, rest, elemVar);
|
|
1360
|
+
d.write(`${outputVar}[${iVar}] = ${elemOutput};`);
|
|
1361
|
+
});
|
|
1362
|
+
doc.write(`}`);
|
|
1363
|
+
}
|
|
1364
|
+
return outputVar;
|
|
1365
|
+
}
|
|
1366
|
+
function getTupleOptStart(items, key) {
|
|
1367
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
1368
|
+
// Mirrors the runtime: optin is a three-rung ladder so any rung above `undefined` permits an absent slot; optout stays two-valued.
|
|
1369
|
+
const omittable = key === "optin" ? items[i]._zod.optin !== undefined : items[i]._zod.optout === "optional";
|
|
1370
|
+
if (!omittable)
|
|
1371
|
+
return i + 1;
|
|
1372
|
+
}
|
|
1373
|
+
return 0;
|
|
1374
|
+
}
|
|
1375
|
+
function generateUnionCheck(doc, ctx, schema, accessor) {
|
|
1376
|
+
const def = schema._zod.def;
|
|
1377
|
+
const options = def.options;
|
|
1378
|
+
if (def.discriminator) {
|
|
1379
|
+
return generateDiscriminatedUnionCheck(doc, ctx, def, accessor);
|
|
1380
|
+
}
|
|
1381
|
+
// z.xor requires *exactly one* option to match. Match-counting in the fast path is only sound if every branch is exactly as strict as the runtime — any falsely-rejecting branch silently turns a multi-match rejection into an accept. Force the runtime for this rare combinator.
|
|
1382
|
+
if (def.inclusive === false) {
|
|
1383
|
+
throw new ZodCompileUnsupportedError("exclusive unions (z.xor)");
|
|
1384
|
+
}
|
|
1385
|
+
if (options.length === 0) {
|
|
1386
|
+
doc.write("return INVALID;");
|
|
1387
|
+
return accessor;
|
|
1388
|
+
}
|
|
1389
|
+
if (options.length === 1) {
|
|
1390
|
+
return generateCheck(doc, ctx, options[0], accessor);
|
|
1391
|
+
}
|
|
1392
|
+
// Check if all options are bare literals - use Set optimization. A literal option carrying checks (e.g. .refine) must take the general path or the Set would accept values its checks reject.
|
|
1393
|
+
const allLiterals = options.every((opt) => opt._zod.def.type === "literal" && !opt._zod.def.checks?.length);
|
|
1394
|
+
if (allLiterals) {
|
|
1395
|
+
const values = new Set(options.flatMap((opt) => opt._zod.def.values));
|
|
1396
|
+
const valuesConst = addConstant(ctx, values);
|
|
1397
|
+
doc.write(`if (!${valuesConst}.has(${accessor})) return INVALID;`);
|
|
1398
|
+
return accessor;
|
|
1399
|
+
}
|
|
1400
|
+
// General case: try each option until one succeeds Use IIFEs that return the output or INVALID
|
|
1401
|
+
const outputVar = newVar(ctx);
|
|
1402
|
+
doc.write(`let ${outputVar};`);
|
|
1403
|
+
for (let i = 0; i < options.length; i++) {
|
|
1404
|
+
const opt = options[i];
|
|
1405
|
+
if (i === 0) {
|
|
1406
|
+
doc.write(`${outputVar} = (() => {`);
|
|
1407
|
+
}
|
|
1408
|
+
else {
|
|
1409
|
+
doc.write(`if (${outputVar} === INVALID) ${outputVar} = (() => {`);
|
|
1410
|
+
}
|
|
1411
|
+
doc.indented((d) => {
|
|
1412
|
+
// Generate check inside IIFE - returns INVALID on failure
|
|
1413
|
+
const branchOutput = generateCheck(d, ctx, opt, accessor);
|
|
1414
|
+
d.write(`return ${branchOutput};`);
|
|
1415
|
+
});
|
|
1416
|
+
doc.write(`})();`);
|
|
1417
|
+
}
|
|
1418
|
+
doc.write(`if (${outputVar} === INVALID) return INVALID;`);
|
|
1419
|
+
return outputVar;
|
|
1420
|
+
}
|
|
1421
|
+
function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
|
|
1422
|
+
if (def.unionFallback) {
|
|
1423
|
+
throw new ZodCompileUnsupportedError("discriminated union with unionFallback");
|
|
1424
|
+
}
|
|
1425
|
+
if (def.options.length === 0) {
|
|
1426
|
+
doc.write("return INVALID;");
|
|
1427
|
+
return accessor;
|
|
1428
|
+
}
|
|
1429
|
+
const discVar = newVar(ctx);
|
|
1430
|
+
const outputVar = newVar(ctx);
|
|
1431
|
+
doc.write(`const ${discVar} = ${accessor}?.[${util.esc(def.discriminator)}];`);
|
|
1432
|
+
doc.write(`let ${outputVar};`);
|
|
1433
|
+
let firstBranch = true;
|
|
1434
|
+
const claimed = new Set();
|
|
1435
|
+
for (const option of def.options) {
|
|
1436
|
+
const values = option._zod.propValues?.[def.discriminator];
|
|
1437
|
+
if (!values || values.size === 0) {
|
|
1438
|
+
throw new ZodCompileUnsupportedError("discriminated union option without static discriminator values");
|
|
1439
|
+
}
|
|
1440
|
+
// Two options claiming one value are not discriminable, and the branch chain below would silently give it to the first. Declining to compile hands that back to the interpreter, whose own map build reports it.
|
|
1441
|
+
for (const value of values) {
|
|
1442
|
+
if (claimed.has(value)) {
|
|
1443
|
+
throw new ZodCompileUnsupportedError(`duplicate discriminator value ${String(value)}`);
|
|
1444
|
+
}
|
|
1445
|
+
claimed.add(value);
|
|
1446
|
+
}
|
|
1447
|
+
const conditions = Array.from(values, (value) => literalEquality(ctx, discVar, value));
|
|
1448
|
+
const prefix = firstBranch ? "if" : "else if";
|
|
1449
|
+
doc.write(`${prefix} (${conditions.join(" || ")}) {`);
|
|
1450
|
+
doc.indented((d) => {
|
|
1451
|
+
const branchOutput = generateCheck(d, ctx, option, accessor);
|
|
1452
|
+
d.write(`${outputVar} = ${branchOutput};`);
|
|
1453
|
+
});
|
|
1454
|
+
doc.write(`}`);
|
|
1455
|
+
firstBranch = false;
|
|
1456
|
+
}
|
|
1457
|
+
doc.write(`else { return INVALID; }`);
|
|
1458
|
+
return outputVar;
|
|
1459
|
+
}
|
|
1460
|
+
function literalEquality(ctx, accessor, value) {
|
|
1461
|
+
if (typeof value === "string")
|
|
1462
|
+
return `${accessor} === ${util.esc(value)}`;
|
|
1463
|
+
if (typeof value === "number") {
|
|
1464
|
+
if (Number.isNaN(value))
|
|
1465
|
+
return `Number.isNaN(${accessor})`;
|
|
1466
|
+
return `${accessor} === ${value}`;
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof value === "boolean")
|
|
1469
|
+
return `${accessor} === ${value}`;
|
|
1470
|
+
if (value === null)
|
|
1471
|
+
return `${accessor} === null`;
|
|
1472
|
+
if (value === undefined)
|
|
1473
|
+
return `${accessor} === undefined`;
|
|
1474
|
+
if (typeof value === "bigint")
|
|
1475
|
+
return `${accessor} === ${value}n`;
|
|
1476
|
+
if (typeof value === "symbol") {
|
|
1477
|
+
const symbolConst = addConstant(ctx, value);
|
|
1478
|
+
return `${accessor} === ${symbolConst}`;
|
|
1479
|
+
}
|
|
1480
|
+
throw new ZodCompileUnsupportedError(`literal discriminator value ${String(value)}`);
|
|
1481
|
+
}
|
|
1482
|
+
function generateIntersectionCheck(doc, ctx, schema, accessor) {
|
|
1483
|
+
const def = schema._zod.def;
|
|
1484
|
+
const leftOutput = compileChild(doc, ctx, def.left, accessor);
|
|
1485
|
+
const rightOutput = compileChild(doc, ctx, def.right, accessor);
|
|
1486
|
+
// Hoist the runtime merge helper so recursive object/array merge semantics stay in one place. If the merge is invalid, return INVALID and let the runtime fallback construct canonical errors.
|
|
1487
|
+
const mergeConst = addConstant(ctx, mergeValues);
|
|
1488
|
+
const mergedVar = newVar(ctx);
|
|
1489
|
+
doc.write(`const ${mergedVar} = ${mergeConst}(${leftOutput}, ${rightOutput});`);
|
|
1490
|
+
doc.write(`if (!${mergedVar}.valid) return INVALID;`);
|
|
1491
|
+
return `${mergedVar}.data`;
|
|
1492
|
+
}
|
|
1493
|
+
function generateRecordCheck(doc, ctx, schema, accessor) {
|
|
1494
|
+
const def = schema._zod.def;
|
|
1495
|
+
// Use util.isPlainObject (rejects Date, Map, Set, class instances, etc.) to match runtime behavior. Hoisted call instead of inline so this stays a single source of truth with the runtime parser.
|
|
1496
|
+
const isPlainObjectConst = addConstant(ctx, util.isPlainObject);
|
|
1497
|
+
doc.write(`if (!${isPlainObjectConst}(${accessor})) return INVALID;`);
|
|
1498
|
+
const outputVar = newVar(ctx);
|
|
1499
|
+
const kVar = newVar(ctx);
|
|
1500
|
+
const valVar = newVar(ctx);
|
|
1501
|
+
doc.write(`const ${outputVar} = {};`);
|
|
1502
|
+
// Exhaustive record. The runtime gates this on `values && !def.partial`, since z.partialRecord keeps its value set but makes every key optional; a loose record passes unrecognized keys through, which the per-key scan cannot express.
|
|
1503
|
+
const recordDef = def;
|
|
1504
|
+
const keyValues = recordDef.partial ? undefined : def.keyType._zod.values;
|
|
1505
|
+
if (keyValues) {
|
|
1506
|
+
const inputKeys = [];
|
|
1507
|
+
for (const key of keyValues) {
|
|
1508
|
+
if (!(typeof key === "string" || typeof key === "number" || typeof key === "symbol")) {
|
|
1509
|
+
throw new ZodCompileUnsupportedError(`record key value ${String(key)}`);
|
|
1510
|
+
}
|
|
1511
|
+
const inputKey = typeof key === "number" ? key.toString() : key;
|
|
1512
|
+
if (inputKey === "__proto__") {
|
|
1513
|
+
// `out["__proto__"] = v` would hit the prototype setter on the output.
|
|
1514
|
+
throw new ZodCompileUnsupportedError('record key "__proto__"');
|
|
1515
|
+
}
|
|
1516
|
+
inputKeys.push(inputKey);
|
|
1517
|
+
const keyConst = addConstant(ctx, key);
|
|
1518
|
+
const outKey = generateCheck(doc, ctx, def.keyType, keyConst);
|
|
1519
|
+
// Read the property once. Passing the raw expression let compileChild validate one read while the output write performed a second, so a getter could hand back a value nothing had checked.
|
|
1520
|
+
const valueVar = newVar(ctx);
|
|
1521
|
+
doc.write(`const ${valueVar} = ${accessor}[${literalPropertyKey(ctx, inputKey)}];`);
|
|
1522
|
+
const valOutput = compileChild(doc, ctx, def.valueType, valueVar);
|
|
1523
|
+
doc.write(`${outputVar}[${outKey}] = ${valOutput};`);
|
|
1524
|
+
}
|
|
1525
|
+
// `mode: "loose"` changes only what happens to unrecognized keys, so it belongs here rather than the per-present-key scan. Passing them through matches the runtime, which skips `__proto__`.
|
|
1526
|
+
const knownKeysConst = addConstant(ctx, new Set(inputKeys));
|
|
1527
|
+
doc.write(`for (const ${kVar} in ${accessor}) {`);
|
|
1528
|
+
doc.indented((d) => {
|
|
1529
|
+
d.write(`if (${knownKeysConst}.has(${kVar})) continue;`);
|
|
1530
|
+
if (recordDef.mode === "loose") {
|
|
1531
|
+
d.write(`if (${kVar} !== "__proto__") ${outputVar}[${kVar}] = ${accessor}[${kVar}];`);
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
d.write(`return INVALID;`);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
doc.write(`}`);
|
|
1538
|
+
return outputVar;
|
|
1539
|
+
}
|
|
1540
|
+
// The bare-string shortcut below only tests `typeof key === "string"`, so it
|
|
1541
|
+
// is correct exclusively for a `z.string()` carrying nothing else. A string
|
|
1542
|
+
// *format* lives on the def rather than in `checks` — `z.record(z.email(), …)`
|
|
1543
|
+
// reads as a plain string here — and coercion rewrites the key, so both have
|
|
1544
|
+
// to take the general path or the shortcut accepts keys the runtime rejects.
|
|
1545
|
+
const keyDef = def.keyType._zod.def;
|
|
1546
|
+
const keyIsBareString = keyDef.type === "string" && keyDef.format === undefined && !keyDef.coerce && (keyDef.checks?.length ?? 0) === 0;
|
|
1547
|
+
if (!keyIsBareString) {
|
|
1548
|
+
// A key schema with no value set is a constraint every key must satisfy
|
|
1549
|
+
// (`z.email()`, `z.string().min(3)`, `z.number()`, a template literal).
|
|
1550
|
+
// The runtime runs it against each own enumerable key and writes the value
|
|
1551
|
+
// under the key it produced, so compile it once and call it per key.
|
|
1552
|
+
const isLoose = def.mode === "loose";
|
|
1553
|
+
const keyFast = addConstant(ctx, compileFn(def.keyType));
|
|
1554
|
+
const numericConst = addConstant(ctx, regexes.number);
|
|
1555
|
+
const propIsEnumerableConst = addConstant(ctx, Object.prototype.propertyIsEnumerable);
|
|
1556
|
+
const outKeyVar = newVar(ctx);
|
|
1557
|
+
doc.write(`for (const ${kVar} of Reflect.ownKeys(${accessor})) {`);
|
|
1558
|
+
doc.indented((d) => {
|
|
1559
|
+
d.write(`if (${kVar} === "__proto__") continue;`);
|
|
1560
|
+
d.write(`if (!${propIsEnumerableConst}.call(${accessor}, ${kVar})) continue;`);
|
|
1561
|
+
d.write(`let ${outKeyVar} = ${keyFast}(${kVar});`);
|
|
1562
|
+
// Numeric-string retry, mirroring the runtime: a key the schema rejects as a string is tried again as a number, so z.record(z.number(), …) matches the numeric keys JavaScript stringified on the way in.
|
|
1563
|
+
d.write(`if (${outKeyVar} === INVALID && typeof ${kVar} === "string" && ${numericConst}.test(${kVar})) ${outKeyVar} = ${keyFast}(Number(${kVar}));`);
|
|
1564
|
+
if (isLoose) {
|
|
1565
|
+
// A loose record keeps a key its schema rejects, copying the value across unvalidated rather than failing the parse.
|
|
1566
|
+
d.write(`if (${outKeyVar} === INVALID) { ${outputVar}[${kVar}] = ${accessor}[${kVar}]; continue; }`);
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
d.write(`if (${outKeyVar} === INVALID) return INVALID;`);
|
|
1570
|
+
}
|
|
1571
|
+
// The guard above tested the input key, but the schema can normalize an ordinary key into __proto__; re-check the one actually written under.
|
|
1572
|
+
d.write(`if (${outKeyVar} === "__proto__") continue;`);
|
|
1573
|
+
// Read once: the raw expression would be evaluated again by the output write below, so an accessor could return an unvalidated second value.
|
|
1574
|
+
const valueVar = newVar(ctx);
|
|
1575
|
+
d.write(`const ${valueVar} = ${accessor}[${kVar}];`);
|
|
1576
|
+
const valOutput = compileChild(d, ctx, def.valueType, valueVar);
|
|
1577
|
+
d.write(`${outputVar}[${outKeyVar}] = ${valOutput};`);
|
|
1578
|
+
});
|
|
1579
|
+
doc.write(`}`);
|
|
1580
|
+
return outputVar;
|
|
1581
|
+
}
|
|
1582
|
+
// Plain z.string() keys: iterate enumerable own keys and validate each value. Runtime uses Reflect.ownKeys so symbol keys participate in validation; matching that here prevents silently accepting objects with enumerable Symbol keys under z.record(z.string(), ...).
|
|
1583
|
+
const propIsEnumerable = addConstant(ctx, Object.prototype.propertyIsEnumerable);
|
|
1584
|
+
doc.write(`for (const ${kVar} of Reflect.ownKeys(${accessor})) {`);
|
|
1585
|
+
doc.indented((d) => {
|
|
1586
|
+
d.write(`if (${kVar} === "__proto__") continue;`);
|
|
1587
|
+
d.write(`if (!${propIsEnumerable}.call(${accessor}, ${kVar})) continue;`);
|
|
1588
|
+
d.write(`if (typeof ${kVar} !== "string") return INVALID;`);
|
|
1589
|
+
d.write(`const ${valVar} = ${accessor}[${kVar}];`);
|
|
1590
|
+
const valOutput = compileChild(d, ctx, def.valueType, valVar);
|
|
1591
|
+
d.write(`${outputVar}[${kVar}] = ${valOutput};`);
|
|
1592
|
+
});
|
|
1593
|
+
doc.write(`}`);
|
|
1594
|
+
return outputVar;
|
|
1595
|
+
}
|
|
1596
|
+
function literalPropertyKey(ctx, key) {
|
|
1597
|
+
if (typeof key === "string")
|
|
1598
|
+
return util.esc(key);
|
|
1599
|
+
return addConstant(ctx, key);
|
|
1600
|
+
}
|
|
1601
|
+
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
1602
|
+
const def = schema._zod.def;
|
|
1603
|
+
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
1604
|
+
const outputVar = newVar(ctx);
|
|
1605
|
+
const kVar = newVar(ctx);
|
|
1606
|
+
const valVar = newVar(ctx);
|
|
1607
|
+
doc.write(`const ${outputVar} = new Map();`);
|
|
1608
|
+
doc.write(`for (const [${kVar}, ${valVar}] of ${accessor}) {`);
|
|
1609
|
+
doc.indented((d) => {
|
|
1610
|
+
const keyOutput = generateCheck(d, ctx, def.keyType, kVar);
|
|
1611
|
+
const valOutput = generateCheck(d, ctx, def.valueType, valVar);
|
|
1612
|
+
d.write(`${outputVar}.set(${keyOutput}, ${valOutput});`);
|
|
1613
|
+
});
|
|
1614
|
+
doc.write(`}`);
|
|
1615
|
+
return outputVar;
|
|
1616
|
+
}
|
|
1617
|
+
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
1618
|
+
const def = schema._zod.def;
|
|
1619
|
+
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
1620
|
+
const outputVar = newVar(ctx);
|
|
1621
|
+
const valVar = newVar(ctx);
|
|
1622
|
+
doc.write(`const ${outputVar} = new Set();`);
|
|
1623
|
+
doc.write(`for (const ${valVar} of ${accessor}) {`);
|
|
1624
|
+
doc.indented((d) => {
|
|
1625
|
+
const valOutput = generateCheck(d, ctx, def.valueType, valVar);
|
|
1626
|
+
d.write(`${outputVar}.add(${valOutput});`);
|
|
1627
|
+
});
|
|
1628
|
+
doc.write(`}`);
|
|
1629
|
+
return outputVar;
|
|
1630
|
+
}
|
|
1631
|
+
function generateFileCheck(doc, accessor) {
|
|
1632
|
+
// Runtime $ZodFile is a bare `instanceof File`, including the implicit global lookup. The previous duck-typed fallback accepted arbitrary {name, size} objects in File-less environments — behavior the runtime never had.
|
|
1633
|
+
doc.write(`if (!(${accessor} instanceof File)) return INVALID;`);
|
|
1634
|
+
return accessor;
|
|
1635
|
+
}
|
|
1636
|
+
function generateTemplateLiteralCheck(doc, ctx, schema, accessor) {
|
|
1637
|
+
doc.write(`if (typeof ${accessor} !== "string") return INVALID;`);
|
|
1638
|
+
// Template literal schemas have a pre-computed pattern in _zod.pattern
|
|
1639
|
+
const pattern = schema._zod.pattern;
|
|
1640
|
+
if (pattern) {
|
|
1641
|
+
const patternConst = addConstant(ctx, pattern);
|
|
1642
|
+
doc.write(`${patternConst}.lastIndex = 0;`);
|
|
1643
|
+
doc.write(`if (!${patternConst}.test(${accessor})) return INVALID;`);
|
|
1644
|
+
}
|
|
1645
|
+
return accessor;
|
|
1646
|
+
}
|
|
1647
|
+
function generateLazyCheck(doc, ctx, schema, accessor) {
|
|
1648
|
+
// For lazy schemas, we use a cached parser that falls back to runtime Zod parsing This handles recursive schemas correctly by avoiding infinite compilation loops
|
|
1649
|
+
const def = schema._zod.def;
|
|
1650
|
+
const getterConst = addConstant(ctx, def.getter);
|
|
1651
|
+
const cacheConst = addConstant(ctx, { parser: null });
|
|
1652
|
+
doc.write(`if (!${cacheConst}.parser) {`);
|
|
1653
|
+
doc.indented((d) => {
|
|
1654
|
+
d.write(`const inner = ${getterConst}();`);
|
|
1655
|
+
d.write(`${cacheConst}.parser = function(input) {`);
|
|
1656
|
+
d.indented((d2) => {
|
|
1657
|
+
// Use runtime Zod parsing - this correctly handles recursive schemas Pass an empty ctx like runtimeRun does — runtime parsers read ctx.skipChecks/ctx.direction unconditionally and crash on undefined.
|
|
1658
|
+
d2.write(`const result = inner._zod.run({ value: input, issues: [] }, {});`);
|
|
1659
|
+
d2.write(`return result.issues.length === 0 ? result.value : INVALID;`);
|
|
1660
|
+
});
|
|
1661
|
+
d.write(`};`);
|
|
1662
|
+
});
|
|
1663
|
+
doc.write(`}`);
|
|
1664
|
+
const outputVar = newVar(ctx);
|
|
1665
|
+
doc.write(`const ${outputVar} = ${cacheConst}.parser(${accessor});`);
|
|
1666
|
+
doc.write(`if (${outputVar} === INVALID) return INVALID;`);
|
|
1667
|
+
return outputVar;
|
|
1668
|
+
}
|
|
1669
|
+
function generatePipeCheck(doc, ctx, schema, accessor) {
|
|
1670
|
+
const def = schema._zod.def;
|
|
1671
|
+
// Validate input type first
|
|
1672
|
+
const inputOutput = generateCheck(doc, ctx, def.in, accessor);
|
|
1673
|
+
if (def.transform) {
|
|
1674
|
+
// Apply transform and validate output. The transform may read its second `payload` argument (codec transforms like z.stringbool() push issues there) so wrap the call in a helper that spoofs a payload. Pushed issues signal INVALID and the wrapper falls back to the runtime.
|
|
1675
|
+
if (isAsyncFunction(def.transform)) {
|
|
1676
|
+
throw new ZodCompileAsyncError("z.compile: async transforms in pipes are not supported");
|
|
1677
|
+
}
|
|
1678
|
+
const transformFn = def.transform;
|
|
1679
|
+
const helperFn = (value) => {
|
|
1680
|
+
// `addIssue` has to be here: a transform reporting through it is reporting, not failing. Without it the call threw a TypeError that the old catch swallowed into a fallback, so every ctx.addIssue transform quietly lost its fast path and the real error was never visible.
|
|
1681
|
+
const fakePayload = { value, issues: [], addIssue: pushIssue };
|
|
1682
|
+
// A throw is deliberately not caught. The interpreter lets one propagate out of the whole parse; swallowing it into INVALID turned a thrown error into a merely-rejected union branch, so a later branch answered instead.
|
|
1683
|
+
const result = transformFn(value, fakePayload);
|
|
1684
|
+
if (result instanceof Promise)
|
|
1685
|
+
return INVALID;
|
|
1686
|
+
return fakePayload.issues.length === 0 ? result : INVALID;
|
|
1687
|
+
};
|
|
1688
|
+
const helperConst = addConstant(ctx, helperFn);
|
|
1689
|
+
const transformedVar = newVar(ctx);
|
|
1690
|
+
doc.write(`const ${transformedVar} = ${helperConst}(${inputOutput});`);
|
|
1691
|
+
doc.write(`if (${transformedVar} === INVALID) return INVALID;`);
|
|
1692
|
+
return generateCheck(doc, ctx, def.out, transformedVar);
|
|
1693
|
+
}
|
|
1694
|
+
else {
|
|
1695
|
+
// No transform - validate output type on same value
|
|
1696
|
+
return generateCheck(doc, ctx, def.out, inputOutput);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function isAsyncFunction(fn) {
|
|
1700
|
+
return (typeof fn === "function" &&
|
|
1701
|
+
(fn.constructor.name === "AsyncFunction" ||
|
|
1702
|
+
fn[Symbol.toStringTag] === "AsyncFunction"));
|
|
1703
|
+
}
|
|
1704
|
+
function generateCustomCheck(doc, ctx, schema, accessor) {
|
|
1705
|
+
const def = schema._zod.def;
|
|
1706
|
+
if (def.fn) {
|
|
1707
|
+
// Check for async function
|
|
1708
|
+
if (isAsyncFunction(def.fn)) {
|
|
1709
|
+
throw new ZodCompileAsyncError("z.compile: async custom predicates are not supported");
|
|
1710
|
+
}
|
|
1711
|
+
// Custom schema with a predicate function (e.g. z.instanceof). `isAsyncFunction` above is syntactic, so a plain function returning a promise reaches here, and a promise is truthy — it would read as a pass where the interpreter throws.
|
|
1712
|
+
const fnConst = addConstant(ctx, def.fn);
|
|
1713
|
+
const throwAsyncConst = addConstant(ctx, throwAsync);
|
|
1714
|
+
const resVar = newVar(ctx);
|
|
1715
|
+
doc.write(`const ${resVar} = ${fnConst}(${accessor});`);
|
|
1716
|
+
doc.write(`if (${resVar} instanceof Promise) ${throwAsyncConst}();`);
|
|
1717
|
+
doc.write(`if (!${resVar}) return INVALID;`);
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
throw new ZodCompileUnsupportedError("custom schema without a predicate function");
|
|
1721
|
+
}
|
|
1722
|
+
return accessor;
|
|
1723
|
+
}
|
|
1724
|
+
// Runtime helper for a compiled `catch`: runs the inner schema once and returns its value when it succeeded. Anything else — a failure the catch would handle, or an async inner — returns INVALID so the interpreter takes over.
|
|
1725
|
+
function runtimeCatch(innerSchema, catchValue, value) {
|
|
1726
|
+
const result = innerSchema._zod.run({ value, issues: [] }, {});
|
|
1727
|
+
if (result && typeof result.then === "function")
|
|
1728
|
+
return INVALID;
|
|
1729
|
+
const r = result;
|
|
1730
|
+
if (r.issues.length === 0)
|
|
1731
|
+
return r.value;
|
|
1732
|
+
// Only reached for a catch value that ignores the parse context — codegen refuses the rest — so there are no issues to finalize and nothing here needs the caller's error map.
|
|
1733
|
+
return catchValue();
|
|
1734
|
+
}
|
|
1735
|
+
function generateCatchCheck(doc, ctx, schema, accessor) {
|
|
1736
|
+
const def = schema._zod.def;
|
|
1737
|
+
// An untagged catchValue is a user callback, and one reading `ctx.error` needs the caller's per-parse error map. Catch *succeeds*, so a wrong message there is unobservable — refuse at codegen, and not islandable either, since `runtimeRun` has no context to hand it.
|
|
1738
|
+
if (!def.catchValue[util.CONSTANT_CATCH]) {
|
|
1739
|
+
throw new ZodCompileUnsupportedError("catch with a callback (only a constant catch value compiles)", false);
|
|
1740
|
+
}
|
|
1741
|
+
const outputVar = newVar(ctx);
|
|
1742
|
+
doc.write(`let ${outputVar} = (() => {`);
|
|
1743
|
+
doc.indented((d) => {
|
|
1744
|
+
const innerOut = compileChild(d, ctx, def.innerType, accessor);
|
|
1745
|
+
d.write(`return ${innerOut};`);
|
|
1746
|
+
});
|
|
1747
|
+
doc.write(`})();`);
|
|
1748
|
+
const innerConst = addConstant(ctx, def.innerType);
|
|
1749
|
+
const catchConst = addConstant(ctx, def.catchValue);
|
|
1750
|
+
const catchHelperConst = addConstant(ctx, runtimeCatch);
|
|
1751
|
+
doc.write(`if (${outputVar} === INVALID) {`);
|
|
1752
|
+
doc.indented((d) => {
|
|
1753
|
+
d.write(`${outputVar} = ${catchHelperConst}(${innerConst}, ${catchConst}, ${accessor});`);
|
|
1754
|
+
d.write(`if (${outputVar} === INVALID) return INVALID;`);
|
|
1755
|
+
});
|
|
1756
|
+
doc.write(`}`);
|
|
1757
|
+
return outputVar;
|
|
1758
|
+
}
|
|
1759
|
+
function generateTransformCheck(doc, ctx, schema, accessor) {
|
|
1760
|
+
const def = schema._zod.def;
|
|
1761
|
+
if (def.transform) {
|
|
1762
|
+
// Check for async transform
|
|
1763
|
+
if (isAsyncFunction(def.transform)) {
|
|
1764
|
+
throw new ZodCompileAsyncError("z.compile: async transforms are not supported");
|
|
1765
|
+
}
|
|
1766
|
+
// Create a helper that runs the transform and returns the result or INVALID on error
|
|
1767
|
+
const transformFn = def.transform;
|
|
1768
|
+
const helperFn = (value) => {
|
|
1769
|
+
const fakePayload = { value, issues: [], addIssue: pushIssue };
|
|
1770
|
+
// As in the pipe helper: a throw propagates, because the interpreter lets it out of the whole parse rather than treating it as a failed branch.
|
|
1771
|
+
const result = transformFn(value, fakePayload);
|
|
1772
|
+
if (result instanceof Promise)
|
|
1773
|
+
return INVALID;
|
|
1774
|
+
return fakePayload.issues.length === 0 ? result : INVALID;
|
|
1775
|
+
};
|
|
1776
|
+
const helperConst = addConstant(ctx, helperFn);
|
|
1777
|
+
const outputVar = newVar(ctx);
|
|
1778
|
+
doc.write(`const ${outputVar} = ${helperConst}(${accessor});`);
|
|
1779
|
+
doc.write(`if (${outputVar} === INVALID) return INVALID;`);
|
|
1780
|
+
return outputVar;
|
|
1781
|
+
}
|
|
1782
|
+
return accessor;
|
|
1783
|
+
}
|