UNPKG

zod

Version:

TypeScript-first schema declaration and validation library with static type inference

860 lines (859 loc) • 34.4 kB
import { globalConfig } from "./core.js"; // functions export function assertEqual(val) { return val; } export function assertNotEqual(val) { return val; } export function toZod() { return (schema) => schema; } export function assertIs(_arg) { } export function assertNever(_x) { throw new Error("Unexpected value in exhaustive check"); } export function assert(_) { } export function getEnumValues(entries) { const numericValues = Object.values(entries).filter((v) => typeof v === "number"); const values = Object.entries(entries) .filter(([k, _]) => numericValues.indexOf(+k) === -1) .map(([_, v]) => v); return values; } export function joinValues(array, separator = "|") { return array.map((val) => stringifyPrimitive(val)).join(separator); } export function jsonStringifyReplacer(_, value) { if (typeof value === "bigint") return value.toString(); return value; } // the accessor lives on a shared prototype: an own accessor makes every box a dictionary-mode object (~360 B and a slow load per read against ~100 B and an inlined getter here) class Cached { constructor(getter) { this._getter = getter; this._value = undefined; } get value() { const getter = this._getter; if (getter !== undefined) { this._value = getter(); this._getter = undefined; } return this._value; } } export function cached(getter) { return new Cached(getter); } export function nullish(input) { return input === null || input === undefined; } export function cleanRegex(source) { const start = source.startsWith("^") ? 1 : 0; const end = source.endsWith("$") ? source.length - 1 : source.length; return source.slice(start, end); } export function floatSafeRemainder(val, step) { const ratio = val / step; const roundedRatio = Math.round(ratio); // `val` and `step` each round to a double before the division rounds again, so a true decimal multiple's quotient can sit up to 1.5 of these scaled epsilons from the integer. A 1x tolerance therefore rejected 2.03 as a multiple of 0.07; 4x covers the worst case with margin. const tolerance = 4 * Number.EPSILON * Math.max(Math.abs(ratio), 1); if (Math.abs(ratio - roundedRatio) < tolerance) return 0; return ratio - roundedRatio; } const EVALUATING = /* @__PURE__*/ Symbol("evaluating"); export function defineLazy(object, key, getter) { let value = undefined; Object.defineProperty(object, key, { get() { if (value === EVALUATING) { // Circular reference detected, return undefined to break the cycle return undefined; } if (value === undefined) { value = EVALUATING; value = getter(); } return value; }, set(v) { Object.defineProperty(object, key, { value: v, // configurable: true, }); // object[key] = v; }, configurable: true, }); } export function objectClone(obj) { return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); } export function assignProp(target, prop, value) { Object.defineProperty(target, prop, { value, writable: true, enumerable: true, configurable: true, }); } /** * Whichever object a def's `shape` currently answers from: the one the caller passed until the first read, the frozen copy after it. * * Its keys and descriptors read without invoking anything, which is what lets a discriminated union check its discriminator, and the cycle walk read a shape, without resolving a getter that references the schema being constructed. A def that answers `shape` from an accessor of its own has none. */ export function rawShape(def) { const desc = Object.getOwnPropertyDescriptor(def, "shape"); return desc?.get ? desc.get.raw : desc?.value; } // where a builder reads its source's keys and descriptors, resolving only a shape a def answers for itself. A shape resolves by object spread, so only its enumerable keys are ever part of it. function sourceShape(schema) { return rawShape(schema._zod.def) ?? schema._zod.def.shape; } // a key whose value is not settled yet, self-caching so every read after the first gets the same one function deferProp(target, key, getter) { Object.defineProperty(target, key, { get() { const value = getter(); assignProp(this, key, value); return value; }, enumerable: true, configurable: true, }); } // Writes a settled key. A plain assignment is much cheaper than `defineProperty` and produces the same descriptor, but it runs whatever setter already answers to the key — an accessor this shape deferred, or an inherited one, which `__proto__` has on every object and prototype pollution can add for any name. function putProp(target, key, value) { if (key in target) assignProp(target, key, value); else target[key] = value; } /** * Copies `keys` of `source`'s shape onto `target`, each value passed through `wrap`. * * A key the source has resolved is copied through now, so the derived shape states it outright and nothing has to resolve it to learn what it holds. A key the source still defers stays deferred, and reads back through the source's own `shape`, so it resolves once and both shapes get that one schema. */ function mirrorShape(target, source, keys, wrap) { const raw = sourceShape(source); for (const key of keys) { const desc = Object.getOwnPropertyDescriptor(raw, key); if (!desc.enumerable) continue; if (desc.get) { deferProp(target, key, () => { const value = source._zod.def.shape[key]; return wrap ? wrap(value, key) : value; }); } else putProp(target, key, wrap ? wrap(desc.value, key) : desc.value); } } // same, for a plain shape a caller passed rather than a schema's function mirrorProps(target, source) { for (const key of Reflect.ownKeys(source)) { const desc = Object.getOwnPropertyDescriptor(source, key); if (!desc.enumerable) continue; if (desc.get) deferProp(target, key, () => source[key]); else putProp(target, key, desc.value); } } export function mergeDefs(...defs) { const mergedDescriptors = {}; for (const def of defs) { const descriptors = Object.getOwnPropertyDescriptors(def); Object.assign(mergedDescriptors, descriptors); } return Object.defineProperties({}, mergedDescriptors); } export function cloneDef(schema) { return mergeDefs(schema._zod.def); } export function getElementAtPath(obj, path) { if (!path) return obj; return path.reduce((acc, key) => acc?.[key], obj); } export function promiseAllObject(promisesObj) { const keys = Object.keys(promisesObj); const promises = keys.map((key) => promisesObj[key]); return Promise.all(promises).then((results) => { const resolvedObj = {}; for (let i = 0; i < keys.length; i++) { resolvedObj[keys[i]] = results[i]; } return resolvedObj; }); } export function randomString(length = 10) { const chars = "abcdefghijklmnopqrstuvwxyz"; let str = ""; for (let i = 0; i < length; i++) { str += chars[Math.floor(Math.random() * chars.length)]; } return str; } export function esc(str) { return JSON.stringify(str); } export function slugify(input) { return input .toLowerCase() .trim() .replace(/[^\w\s-]/g, "") .replace(/[\s_-]+/g, "-") .replace(/^-+|-+$/g, ""); } export const captureStackTrace = ("captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => { }); export function isObject(data) { return typeof data === "object" && data !== null && !Array.isArray(data); } export const allowsEval = /* @__PURE__*/ cached(() => { // Skip the probe under `jitless`: strict CSPs report the caught `new Function` as a `securitypolicyviolation` even though the throw is swallowed. if (globalConfig.jitless) { return false; } // @ts-ignore if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) { return false; } try { const F = Function; new F(""); return true; } catch (_) { return false; } }); export function isPlainObject(o) { if (isObject(o) === false) return false; // modified constructor const ctor = o.constructor; if (ctor === undefined) return true; if (typeof ctor !== "function") return true; // modified prototype const prot = ctor.prototype; if (isObject(prot) === false) return false; // ctor doesn't have static `isPrototypeOf` if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) { return false; } return true; } export function shallowClone(o) { if (isPlainObject(o)) return { ...o }; if (Array.isArray(o)) return [...o]; if (o instanceof Map) return new Map(o); if (o instanceof Set) return new Set(o); return o; } export function numKeys(data) { let keyCount = 0; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { keyCount++; } } return keyCount; } export const getParsedType = (data) => { const t = typeof data; switch (t) { case "undefined": return "undefined"; case "string": return "string"; case "number": return Number.isNaN(data) ? "nan" : "number"; case "boolean": return "boolean"; case "function": return "function"; case "bigint": return "bigint"; case "symbol": return "symbol"; case "object": if (Array.isArray(data)) { return "array"; } if (data === null) { return "null"; } if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { return "promise"; } if (typeof Map !== "undefined" && data instanceof Map) { return "map"; } if (typeof Set !== "undefined" && data instanceof Set) { return "set"; } if (typeof Date !== "undefined" && data instanceof Date) { return "date"; } // @ts-ignore if (typeof File !== "undefined" && data instanceof File) { return "file"; } return "object"; default: throw new Error(`Unknown data type: ${t}`); } }; export const propertyKeyTypes = /* @__PURE__*/ new Set(["string", "number", "symbol"]); export const primitiveTypes = /* @__PURE__*/ new Set([ "string", "number", "bigint", "boolean", "symbol", "undefined", ]); export function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } // zod-specific utils export function clone(inst, def, params) { const cl = new inst._zod.constr(def ?? inst._zod.def); if (!def || params?.parent) cl._zod.parent = inst; return cl; } export function normalizeParams(_params) { const params = _params; if (!params) return {}; if (typeof params === "string") return { error: () => params }; if (params?.message !== undefined) { if (params?.error !== undefined) throw new Error("Cannot specify both `message` and `error` params"); params.error = params.message; } delete params.message; if (typeof params.error === "string") return { ...params, error: () => params.error }; return params; } export function createTransparentProxy(getter) { let target; return new Proxy({}, { get(_, prop, receiver) { target ?? (target = getter()); return Reflect.get(target, prop, receiver); }, set(_, prop, value, receiver) { target ?? (target = getter()); return Reflect.set(target, prop, value, receiver); }, has(_, prop) { target ?? (target = getter()); return Reflect.has(target, prop); }, deleteProperty(_, prop) { target ?? (target = getter()); return Reflect.deleteProperty(target, prop); }, ownKeys(_) { target ?? (target = getter()); return Reflect.ownKeys(target); }, getOwnPropertyDescriptor(_, prop) { target ?? (target = getter()); return Reflect.getOwnPropertyDescriptor(target, prop); }, defineProperty(_, prop, descriptor) { target ?? (target = getter()); return Reflect.defineProperty(target, prop, descriptor); }, }); } export function stringifyPrimitive(value) { if (typeof value === "bigint") return value.toString() + "n"; if (typeof value === "string") return `"${value}"`; return `${value}`; } export function optionalKeys(shape) { return Object.keys(shape).filter((k) => { return shape[k]._zod.optin !== undefined && shape[k]._zod.optout === "optional"; }); } // Wrapped in a `@__PURE__` IIFE: esbuild never tree-shakes a top-level initializer that contains a member access on `Number`, so the bare object literal survived into every bundle. export const NUMBER_FORMAT_RANGES = /*@__PURE__*/ (() => ({ safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], int32: [-2147483648, 2147483647], uint32: [0, 4294967295], float32: [-3.4028234663852886e38, 3.4028234663852886e38], float64: [-Number.MAX_VALUE, Number.MAX_VALUE], }))(); export const BIGINT_FORMAT_RANGES = { int64: [/* @__PURE__*/ BigInt("-9223372036854775808"), /* @__PURE__*/ BigInt("9223372036854775807")], uint64: [/* @__PURE__*/ BigInt(0), /* @__PURE__*/ BigInt("18446744073709551615")], }; export function pick(schema, mask) { const currDef = schema._zod.def; const checks = currDef.checks; const hasChecks = checks && checks.length > 0; if (hasChecks) { throw new Error(".pick() cannot be used on object schemas containing refinements"); } const newShape = {}; mirrorShape(newShape, schema, maskedKeys(schema, mask)); return clone(schema, mergeDefs(currDef, { shape: newShape, checks: [] })); } // the mask keys that select something, checked against the source's shape without resolving it function maskedKeys(schema, mask) { const raw = sourceShape(schema); const keys = []; // `for...in` skips symbols, so a symbol in the mask would select nothing for (const key of Reflect.ownKeys(mask)) { if (!Object.getOwnPropertyDescriptor(raw, key)?.enumerable) { throw new Error(`Unrecognized key: "${String(key)}"`); } if (mask[key]) keys.push(key); } return keys; } export function omit(schema, mask) { const currDef = schema._zod.def; const checks = currDef.checks; const hasChecks = checks && checks.length > 0; if (hasChecks) { throw new Error(".omit() cannot be used on object schemas containing refinements"); } const omitted = new Set(maskedKeys(schema, mask)); const newShape = {}; mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)).filter((key) => !omitted.has(key))); return clone(schema, mergeDefs(currDef, { shape: newShape, checks: [] })); } export function extend(schema, shape) { if (!isPlainObject(shape)) { throw new Error("Invalid input to extend: expected a plain object"); } const checks = schema._zod.def.checks; const hasChecks = checks && checks.length > 0; if (hasChecks) { // Only throw if new shape overlaps with existing shape. Use getOwnPropertyDescriptor to check key existence without accessing values const existingShape = sourceShape(schema); for (const key of Reflect.ownKeys(shape)) { if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) { throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead."); } } } return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) })); } // the source's keys, then the caller's overlaid on top function extended(schema, shape) { const newShape = {}; mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema))); mirrorProps(newShape, shape); return newShape; } export function safeExtend(schema, shape) { if (!isPlainObject(shape)) { throw new Error("Invalid input to safeExtend: expected a plain object"); } return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) })); } export function merge(a, b) { if (!b?._zod?.def) { throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`."); } if (a._zod.def.checks?.length) { throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead."); } const newShape = {}; mirrorShape(newShape, a, Reflect.ownKeys(sourceShape(a))); mirrorShape(newShape, b, Reflect.ownKeys(sourceShape(b))); const def = mergeDefs(a._zod.def, { shape: newShape, get catchall() { return b._zod.def.catchall; }, checks: b._zod.def.checks ?? [], }); return clone(a, def); } export function partial(Class, schema, mask, name = "partial") { const currDef = schema._zod.def; const checks = currDef.checks; const hasChecks = checks && checks.length > 0; if (hasChecks) { throw new Error(`.${name}() cannot be used on object schemas containing refinements`); } const selected = mask ? new Set(maskedKeys(schema, mask)) : undefined; const newShape = {}; mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), Class && ((value, key) => (selected && !selected.has(key) ? value : new Class({ type: "optional", innerType: value })))); return clone(schema, mergeDefs(schema._zod.def, { shape: newShape, checks: [] })); } export function required(Class, schema, mask) { const selected = mask ? new Set(maskedKeys(schema, mask)) : undefined; const newShape = {}; mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), (value, key) => // overwrite with non-optional selected && !selected.has(key) ? value : new Class({ type: "nonoptional", innerType: value })); return clone(schema, mergeDefs(schema._zod.def, { shape: newShape })); } // invalid_type | too_big | too_small | invalid_format | not_multiple_of | unrecognized_keys | invalid_union | invalid_key | invalid_element | invalid_value | custom export function aborted(x, startIndex = 0) { if (x.aborted === true) return true; for (let i = startIndex; i < x.issues.length; i++) { if (x.issues[i]?.continue !== true) { return true; } } return false; } // Checks for explicit abort (continue === false), as opposed to implicit abort (continue === undefined). Used to respect `abort: true` in .refine() even for checks that have a `when` function. export function explicitlyAborted(x, startIndex = 0) { if (x.aborted === true) return true; for (let i = startIndex; i < x.issues.length; i++) { if (x.issues[i]?.continue === false) { return true; } } return false; } export function prefixIssues(path, issues) { return issues.map((iss) => { var _a; (_a = iss).path ?? (_a.path = []); iss.path.unshift(path); return iss; }); } export function unwrapMessage(message) { return typeof message === "string" ? message : message?.message; } /* A check holds no link back to the schema it is attached to — the same check instance is shared by every clone of that schema — so the owner is stamped onto the issues a check just raised, at the only point where both are in scope. Runs on the failure path only; `start` is the issue count from before the check ran. */ export function attachSchema(issues, start, inst) { var _a; for (let i = start; i < issues.length; i++) { (_a = issues[i]).schema ?? (_a.schema = inst); } } export function finalizeIssue(iss, ctx, config) { var _a; // A schema that raised an issue itself owns it outright, and outranks any stamp an enclosing check left in `attachSchema`. String formats and z.custom() are schema and check at once, so when they act as a check they defer to that stamp instead. const traits = iss.inst?._zod?.traits; if (traits?.has("$ZodType")) { if (traits.has("$ZodCheck")) (_a = iss).schema ?? (_a.schema = iss.inst); else iss.schema = iss.inst; } // Decreasing specificity, first map to return a message wins. `inst` is whatever raised the issue, so a check's own map outranks the owning schema's. const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : undefined; const message = iss.message ? iss.message : (unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input"); // an explicit own-key copy beats object rest with excluded keys, which v8 routes through a generic runtime call; Object.keys rather than for-in so an issue pushed with a prototype does not leak inherited keys, and an own __proto__ key is dropped rather than assigned through the setter const full = {}; for (const k of Object.keys(iss)) { if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__") continue; full[k] = iss[k]; } full.path ?? (full.path = []); full.message = message; if (ctx?.reportInput) { full.input = iss.input; } return full; } export function getSizableOrigin(input) { if (input instanceof Set) return "set"; if (input instanceof Map) return "map"; // @ts-ignore if (input instanceof File) return "file"; return "unknown"; } const highSurrogate = /[\uD800-\uDBFF]/; // Code points in `str`: a surrogate pair counts once, a lone surrogate as itself. Hand-rolled because the string iterator allocates and runs ~250x slower on this path; the regex probe exits ~50x quicker for a string with no astral characters. export function codePointLength(str) { const units = str.length; if (!highSurrogate.test(str)) return units; let count = units; for (let i = 0; i < units - 1; i++) { if ((str.charCodeAt(i) & 0xfc00) === 0xd800 && (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00) { count--; i++; } } return count; } export function getLengthableOrigin(input) { if (Array.isArray(input)) return "array"; if (typeof input === "string") return "string"; return "unknown"; } export function parsedType(data) { const t = typeof data; switch (t) { case "number": { return Number.isNaN(data) ? "nan" : "number"; } case "object": { if (data === null) { return "null"; } if (Array.isArray(data)) { return "array"; } const obj = data; if (obj && Object.getPrototypeOf(obj) !== Object.prototype && "constructor" in obj && obj.constructor) { return obj.constructor.name; } } } return t; } export function issue(...args) { const [iss, input, inst] = args; if (typeof iss === "string") { return { message: iss, code: "custom", input, inst, }; } return { ...iss }; } export function cleanEnum(obj) { return Object.entries(obj) .filter(([k, _]) => { // return true if NaN, meaning it's not a number, thus a string key return Number.isNaN(Number.parseInt(k, 10)); }) .map((el) => el[1]); } // Codec utility functions export function base64ToUint8Array(base64) { const binaryString = atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } export function uint8ArrayToBase64(bytes) { let binaryString = ""; for (let i = 0; i < bytes.length; i++) { binaryString += String.fromCharCode(bytes[i]); } return btoa(binaryString); } export function base64urlToUint8Array(base64url) { const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); const padding = "=".repeat((4 - (base64.length % 4)) % 4); return base64ToUint8Array(base64 + padding); } export function uint8ArrayToBase64url(bytes) { return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } export function hexToUint8Array(hex) { const cleanHex = hex.replace(/^0x/, ""); if (cleanHex.length % 2 !== 0) { throw new Error("Invalid hex string length"); } const bytes = new Uint8Array(cleanHex.length / 2); for (let i = 0; i < cleanHex.length; i += 2) { bytes[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16); } return bytes; } export function uint8ArrayToHex(bytes) { return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } // instanceof export class Class { constructor(..._args) { } } ////////// PROTOTYPE INSTALLERS ////////// // // Members live on the prototype and materialize per instance on first read, which keeps own-property count under the step where V8 stops using inline slots. Changing anything here means re-measuring runtime, memory and bundle size together — see "The three axes" in AGENTS.md. /** * Installs a trait's members on its prototype. Each value builds that member for the instance on first read; the built value shadows the accessor as an own property, so a detached `const { parse } = schema` keeps working. * * Call this from a `proto` initializer, which runs once per prototype — never per instance. */ export function members(proto, table) { for (const key in table) { const desc = Object.getOwnPropertyDescriptor(table, key); // a getter installs as written, so it stays live: `description` reads through to the registry on every access. not enumerable: an object literal's is, and a prototype member never was if (desc.get) Object.defineProperty(proto, key, { ...desc, enumerable: false }); // a method materializes bound on first read, which is what keeps a detached member working: `const opt = schema.optional; opt()` else defineBound(proto, key, desc.value); } } /** Shadows a prototype member with an own value, so a getter that builds from the instance runs once. */ export function own(inst, key, value, enumerable = true) { Object.defineProperty(inst, key, { configurable: true, writable: true, enumerable, value }); return value; } /** Like {@link own}, for a member that was never an own data property and has to stay out of `Object.keys`. */ export function hide(inst, key, value) { return own(inst, key, value, false); } /** Adds members a table derives from the instance: each builds on first read and shadows as own data, and assignment shadows the same way, as when these were own properties. */ export /*@__NO_SIDE_EFFECTS__*/ function derived(computes, table) { for (const key in computes) { const compute = computes[key]; // an object literal's accessor is configurable and enumerable, and `members` copies the descriptor as written Object.defineProperty(table, key, { configurable: true, enumerable: true, get() { return own(this, key, compute(this)); }, set(value) { own(this, key, value); }, }); } return table; } function defineBound(proto, key, fn) { Object.defineProperty(proto, key, { configurable: true, get() { // vitest's spyOn calls a prototype getter bare to find the function it wraps, so a nullish receiver answers the raw method return this == null ? fn : own(this, key, fn.bind(this)); }, set(value) { own(this, key, value); }, }); } /** Returns the prototype to install on, or `undefined` if this group is already installed on it. */ function claim(inst, sentinel) { const proto = Object.getPrototypeOf(inst); // Runs on every construction, so `in` rather than the costlier `hasOwnProperty.call`. Sentinels are keys the group itself defines. return sentinel in proto ? undefined : proto; } // The internals whose init chain is installing. A second call for the same one is a derived constructor overriding its base, so it must not construct another schema in between or the override is dropped. let installing; // Set while a getter is running, so a value that resolved through a recursion break is not memoized. One shared descriptor shadows the key for the duration, which costs no per-key allocation. let broke = false; const breaker = { configurable: true, get() { broke = true; return undefined; }, }; /** * Installs a lazily-derived internal on the `_zod` prototype of `inst`'s * constructor, computed from the internals object itself and cached there on * first read. One accessor per constructor rather than one per instance. */ export function defineLazyInternal(inst, key, compute) { const proto = Object.getPrototypeOf(inst._zod); if (key in proto && installing !== inst._zod) { // A repeat construction: everything is installed already. Cleared here so the reference is not held past the first construction of every type. installing = undefined; return; } installing = inst._zod; Object.defineProperty(proto, key, { configurable: true, get() { // Shadowed before computing so a re-entrant read from a recursive schema resolves to undefined instead of running the getter again. Object.defineProperty(this, key, breaker); const outer = broke; broke = false; try { const value = compute(this); // A result that resolved through a recursion break is recomputed once the graph is complete; everything else memoizes, undefined included. if (broke) delete this[key]; else Object.defineProperty(this, key, { configurable: true, writable: true, value }); broke = broke || outer; return value; } catch (err) { // A compute that threw memoizes nothing, so a later read runs it again and fails the same way. The shadow goes with it, since leaving it installed would answer undefined for every later read. delete this[key]; broke = broke || outer; throw err; } }, set(value) { Object.defineProperty(this, key, { configurable: true, writable: true, value }); }, }); } /** * Installs `key` on `inst`'s prototype, computed by `make` on first read and cached there as an own * data property. One accessor per constructor rather than one per instance, because an own accessor * puts every instance after the first into v8 dictionary mode. The key doubles as the sentinel. */ export function installLazyProp(inst, key, make, enumerable) { const proto = claim(inst, key); if (!proto) return; Object.defineProperty(proto, key, { configurable: true, get() { // Shadowed before computing, so a re-entrant read from a self-referential shape resolves to undefined instead of running the getter again. A data property rather than an accessor: an own accessor is the dictionary-mode transition this exists to avoid. const desc = { configurable: true, writable: true, enumerable, value: undefined }; Object.defineProperty(this, key, desc); // a compute that throws leaves the shadow behind, so later reads answer undefined instead of re-throwing; `defineLazy` did the same, and `defineLazyInternal`'s delete-on-catch would cost bytes in every bundle for a case only a throwing user getter reaches desc.value = make(this); Object.defineProperty(this, key, desc); return desc.value; }, set(value) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable, value }); }, }); } /** Marks the thunk `_catch` synthesises for a constant catch value. `Function.length` cannot tell that thunk from a user callback — rest and defaulted parameters both report arity 0 — and a user callback reads `ctx.error`, whose issues only finalize correctly against the caller's per-parse error map. Provenance can say what arity cannot. A plain string key rather than `Symbol.for`, whose call at module scope no bundler can prove pure — the same shape that anchored `urlCanParse` into every build. */ export const CONSTANT_CATCH = "~constantCatch"; /** Wraps a constant catch value in a thunk tagged with {@link CONSTANT_CATCH}. */ export function constantCatch(value) { const fn = () => value; fn[CONSTANT_CATCH] = true; return fn; }