"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.allProcessors = exports.lazyProcessor = exports.optionalProcessor = exports.promiseProcessor = exports.readonlyProcessor = exports.pipeProcessor = exports.catchProcessor = exports.prefaultProcessor = exports.defaultProcessor = exports.nonoptionalProcessor = exports.nullableProcessor = exports.recordProcessor = exports.tupleProcessor = exports.intersectionProcessor = exports.unionProcessor = exports.objectProcessor = exports.arrayProcessor = exports.setProcessor = exports.mapProcessor = exports.transformProcessor = exports.functionProcessor = exports.customProcessor = exports.successProcessor = exports.fileProcessor = exports.templateLiteralProcessor = exports.nanProcessor = exports.literalProcessor = exports.enumProcessor = exports.dateProcessor = exports.unknownProcessor = exports.anyProcessor = exports.neverProcessor = exports.voidProcessor = exports.undefinedProcessor = exports.nullProcessor = exports.symbolProcessor = exports.bigintProcessor = exports.booleanProcessor = exports.numberProcessor = exports.stringProcessor = void 0; exports.aggregateChecks = aggregateChecks; exports.toJSONSchema = toJSONSchema; const regexes = __importStar(require("./regexes.cjs")); const schemas_js_1 = require("./schemas.cjs"); const to_json_schema_js_1 = require("./to-json-schema.cjs"); const util_js_1 = require("./util.cjs"); const narrowMin = (agg, key, value) => { if (agg[key] === undefined || value > agg[key]) agg[key] = value; }; const narrowMax = (agg, key, value) => { if (agg[key] === undefined || value < agg[key]) agg[key] = value; }; const narrowBoth = (agg, value) => { narrowMin(agg, "minimum", value); narrowMax(agg, "maximum", value); }; const addDivisor = (agg, value) => { agg.multipleOf ?? (agg.multipleOf = []); if (!agg.multipleOf.includes(value)) agg.multipleOf.push(value); }; const addPattern = (agg, pattern) => { agg.patterns ?? (agg.patterns = new Set()); agg.patterns.add(pattern); }; const intersectMime = (agg, mime) => { agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime]; }; // last-wins, matching the bag's historical write order; the flag keeps an integer format from being lost to a later float one const setFormat = (agg, format) => { agg.format = format; if (format.includes("int")) agg.isInt = true; }; const minContributor = (agg, def) => narrowMin(agg, "minimum", def.minimum); const maxContributor = (agg, def) => narrowMax(agg, "maximum", def.maximum); const formatContributor = (ranges) => (agg, def) => { setFormat(agg, def.format); const [minimum, maximum] = ranges[def.format]; narrowMin(agg, "minimum", minimum); narrowMax(agg, "maximum", maximum); }; const contributors = { greater_than: (agg, def) => narrowMin(agg, def.inclusive ? "minimum" : "exclusiveMinimum", def.value), less_than: (agg, def) => narrowMax(agg, def.inclusive ? "maximum" : "exclusiveMaximum", def.value), multiple_of: (agg, def) => addDivisor(agg, def.value), number_format: formatContributor(util_js_1.NUMBER_FORMAT_RANGES), bigint_format: formatContributor(util_js_1.BIGINT_FORMAT_RANGES), min_length: minContributor, max_length: maxContributor, length_equals: (agg, def) => narrowBoth(agg, def.length), min_size: minContributor, max_size: maxContributor, size_equals: (agg, def) => narrowBoth(agg, def.size), string_format: (agg, def) => { setFormat(agg, def.format); if (def.pattern) addPattern(agg, def.pattern); if (def.format === "base64" || def.format === "base64url") agg.contentEncoding = def.format; if (def.local || def.precision === -1) agg.laxFormat = true; }, mime_type: (agg, def) => intersectMime(agg, def.mime), }; function aggregateChecks(schema) { const agg = {}; const def = schema._zod.def; // a format schema is its own first check, same rule as $ZodType init const list = schema._zod.traits.has("$ZodCheck") ? [schema, ...(def.checks ?? [])] : (def.checks ?? []); for (const ch of list) contributors[ch._zod.def.check]?.(agg, ch._zod.def); // reconcile with the bag so third-party onattach contributions still land; first-party residue is never tighter than the fold, so merging it back is idempotent for one and additive for the other const bag = schema._zod.bag; if (bag.minimum !== undefined) narrowMin(agg, "minimum", bag.minimum); if (bag.exclusiveMinimum !== undefined) narrowMin(agg, "exclusiveMinimum", bag.exclusiveMinimum); if (bag.maximum !== undefined) narrowMax(agg, "maximum", bag.maximum); if (bag.exclusiveMaximum !== undefined) narrowMax(agg, "exclusiveMaximum", bag.exclusiveMaximum); if (bag.multipleOf !== undefined) addDivisor(agg, bag.multipleOf); if (bag.format !== undefined) { agg.format ?? (agg.format = bag.format); if (bag.format.includes("int")) agg.isInt = true; } if (bag.mime) intersectMime(agg, bag.mime); for (const pattern of bag.patterns ?? []) addPattern(agg, pattern); return agg; } const formatMap = { guid: "uuid", url: "uri", datetime: "date-time", json_string: "json-string", regex: "", // do not set }; // ==================== SIMPLE TYPE PROCESSORS ==================== // the runtime patterns are lax so parse paths never overflow the regex stack; the emitted schema swaps in the exact block forms, which zod itself never executes const exactPatterns = new Map([ [schemas_js_1.base64Charset, regexes.base64], [schemas_js_1.base64urlCharset, regexes.base64url], ]); const exactPattern = (p) => exactPatterns.get(p) ?? p; const stringProcessor = (schema, ctx, _json, _params) => { const json = _json; json.type = "string"; const { minimum, maximum, format, patterns, contentEncoding, laxFormat } = aggregateChecks(schema); if (typeof minimum === "number") json.minLength = minimum; if (typeof maximum === "number") json.maxLength = maximum; // custom pattern overrides format if (format) { json.format = formatMap[format] ?? format; if (json.format === "") delete json.format; // empty format is not valid // `z.iso.time()` is never full-time, and `laxFormat` carries the datetime shapes that also accept what their keyword forbids if (format === "time" || laxFormat) { delete json.format; } } if (contentEncoding) json.contentEncoding = contentEncoding; if (patterns && patterns.size > 0) { const patternList = [...patterns].map(exactPattern); if (patternList.length === 1) json.pattern = patternList[0].source; else if (patternList.length > 1) { json.allOf = [ ...patternList.map((regex) => ({ ...(ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0" ? { type: "string" } : {}), pattern: regex.source, })), ]; } } }; exports.stringProcessor = stringProcessor; const numberProcessor = (schema, ctx, _json, params) => { const json = _json; const { minimum, maximum, multipleOf, exclusiveMaximum, exclusiveMinimum, isInt } = aggregateChecks(schema); json.type = isInt ? "integer" : "number"; // when both minimum and exclusiveMinimum exist, pick the more restrictive one const exMin = typeof exclusiveMinimum === "number" && exclusiveMinimum >= (minimum ?? Number.NEGATIVE_INFINITY); const exMax = typeof exclusiveMaximum === "number" && exclusiveMaximum <= (maximum ?? Number.POSITIVE_INFINITY); const legacy = ctx.target === "draft-04" || ctx.target === "openapi-3.0"; if (exMin) { if (legacy) { json.minimum = exclusiveMinimum; json.exclusiveMinimum = true; } else { json.exclusiveMinimum = exclusiveMinimum; } } else if (typeof minimum === "number") { json.minimum = minimum; } if (exMax) { if (legacy) { json.maximum = exclusiveMaximum; json.exclusiveMaximum = true; } else { json.exclusiveMaximum = exclusiveMaximum; } } else if (typeof maximum === "number") { json.maximum = maximum; } if (multipleOf) { // JSON Schema requires a divisor strictly greater than zero, and a non-finite one does not survive JSON at all. A negative divisor accepts exactly what its absolute value accepts, so it still maps; zero, NaN and Infinity have no keyword form. const divisors = new Set(); for (const divisor of multipleOf) { if (Number.isFinite(divisor) && divisor !== 0) divisors.add(Math.abs(divisor)); else (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`); } // chained divisors are a conjunction the keyword cannot carry alone, so extras ride an allOf, same as stacked patterns const [first, ...rest] = divisors; if (first !== undefined) json.multipleOf = first; if (rest.length) json.allOf = [...(json.allOf ?? []), ...rest.map((m) => ({ multipleOf: m }))]; } }; exports.numberProcessor = numberProcessor; const booleanProcessor = (_schema, _ctx, json, _params) => { json.type = "boolean"; }; exports.booleanProcessor = booleanProcessor; const bigintProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "BigInt cannot be represented in JSON Schema"); }; exports.bigintProcessor = bigintProcessor; const symbolProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Symbols cannot be represented in JSON Schema"); }; exports.symbolProcessor = symbolProcessor; const nullProcessor = (_schema, ctx, json, _params) => { if (ctx.target === "openapi-3.0") { json.type = "string"; json.nullable = true; json.enum = [null]; } else { json.type = "null"; } }; exports.nullProcessor = nullProcessor; const undefinedProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Undefined cannot be represented in JSON Schema"); }; exports.undefinedProcessor = undefinedProcessor; const voidProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Void cannot be represented in JSON Schema"); }; exports.voidProcessor = voidProcessor; const neverProcessor = (_schema, _ctx, json, _params) => { json.not = {}; }; exports.neverProcessor = neverProcessor; const anyProcessor = (_schema, _ctx, _json, _params) => { // empty schema accepts anything }; exports.anyProcessor = anyProcessor; const unknownProcessor = (_schema, _ctx, _json, _params) => { // empty schema accepts anything }; exports.unknownProcessor = unknownProcessor; const dateProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Date cannot be represented in JSON Schema"); }; exports.dateProcessor = dateProcessor; const enumProcessor = (schema, _ctx, json, _params) => { const def = schema._zod.def; const values = (0, util_js_1.getEnumValues)(def.entries); // an empty enum accepts nothing, same as z.never() if (values.length === 0) { json.not = {}; return; } // Number enums can have both string and number values if (values.every((v) => typeof v === "number")) json.type = "number"; if (values.every((v) => typeof v === "string")) json.type = "string"; json.enum = values; }; exports.enumProcessor = enumProcessor; const literalProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; // a literal with no values accepts nothing, same as z.never() if (def.values.length === 0) { json.not = {}; return; } const vals = []; for (const val of def.values) { if (val === undefined) { // a custom schema replaces the whole literal, so there is nothing left to accumulate if ((0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Literal `undefined` cannot be represented in JSON Schema")) return; // otherwise do not add to vals } else if (typeof val === "bigint") { if ((0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "BigInt literals cannot be represented in JSON Schema")) return; vals.push(Number(val)); } else { vals.push(val); } } if (vals.length === 0) { // do nothing (an undefined literal was stripped) } else if (vals.length === 1) { const val = vals[0]; json.type = val === null ? "null" : typeof val; if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") { json.enum = [val]; } else { json.const = val; } } else { if (vals.every((v) => typeof v === "number")) json.type = "number"; if (vals.every((v) => typeof v === "string")) json.type = "string"; if (vals.every((v) => typeof v === "boolean")) json.type = "boolean"; if (vals.every((v) => v === null)) json.type = "null"; json.enum = vals; } }; exports.literalProcessor = literalProcessor; const nanProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "NaN cannot be represented in JSON Schema"); }; exports.nanProcessor = nanProcessor; const templateLiteralProcessor = (schema, _ctx, json, _params) => { const _json = json; const pattern = schema._zod.pattern; if (!pattern) throw new Error("Pattern not found in template literal"); _json.type = "string"; _json.pattern = pattern.source; }; exports.templateLiteralProcessor = templateLiteralProcessor; const fileProcessor = (schema, _ctx, json, _params) => { const _json = json; _json.type = "string"; _json.format = "binary"; _json.contentEncoding = "binary"; const { minimum, maximum, mime } = aggregateChecks(schema); if (minimum !== undefined) _json.minLength = minimum; if (maximum !== undefined) _json.maxLength = maximum; if (!mime) return; // an empty intersection means the mime checks share no value, so nothing passes at runtime; `anyOf` must be non-empty, so the false schema is `not: {}` if (mime.length === 0) _json.not = {}; else if (mime.length === 1) _json.contentMediaType = mime[0]; // only contentMediaType differs, so the shared props stay at the root else _json.anyOf = mime.map((m) => ({ contentMediaType: m })); }; exports.fileProcessor = fileProcessor; const successProcessor = (_schema, _ctx, json, _params) => { json.type = "boolean"; }; exports.successProcessor = successProcessor; const customProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema"); }; exports.customProcessor = customProcessor; const functionProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Function types cannot be represented in JSON Schema"); }; exports.functionProcessor = functionProcessor; const transformProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Transforms cannot be represented in JSON Schema"); }; exports.transformProcessor = transformProcessor; const mapProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Map cannot be represented in JSON Schema"); }; exports.mapProcessor = mapProcessor; const setProcessor = (schema, ctx, json, params) => { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Set cannot be represented in JSON Schema"); }; exports.setProcessor = setProcessor; // ==================== COMPOSITE TYPE PROCESSORS ==================== const arrayProcessor = (schema, ctx, _json, params) => { const json = _json; const def = schema._zod.def; const { minimum, maximum } = aggregateChecks(schema); if (typeof minimum === "number") json.minItems = minimum; if (typeof maximum === "number") json.maxItems = maximum; json.type = "array"; json.items = (0, to_json_schema_js_1.processSchema)(def.element, ctx, { ...params, path: [...params.path, "items"], }); }; exports.arrayProcessor = arrayProcessor; // Transform and catch set `optin = "optional"` at runtime so the parser lets them observe an // absent key, but their declared input type stays required. An input JSON Schema describes the // declared type, so resolve past them to the schema that actually carries the optionality. // Used by both `objectProcessor` (for `required`) and `tupleProcessor` (for `minItems`); see // wiki/optionality.md, "The JSON Schema emitter reads the *static* value". function inputOptin(schema) { const def = schema._zod.def; if (def.type === "pipe" && def.in._zod.traits.has("$ZodTransform")) { return inputOptin(def.out); } if (def.type === "catch") { return inputOptin(def.innerType); } return schema._zod.optin; } const objectProcessor = (schema, ctx, _json, params) => { const json = _json; const def = schema._zod.def; const shape = def.shape; // dropping it while still emitting `additionalProperties: false` would emit a schema that rejects data this one requires const symbolKeys = Object.getOwnPropertySymbols(shape); if (symbolKeys.length && (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Symbol keys cannot be represented in JSON Schema")) { return; } json.type = "object"; json.properties = {}; for (const key in shape) { // assignProp so a __proto__ key becomes an own property instead of hitting the inherited setter on the plain {} we build into (0, util_js_1.assignProp)(json.properties, key, (0, to_json_schema_js_1.processSchema)(shape[key], ctx, { ...params, path: [...params.path, "properties", key], })); } // required keys const requiredKeys = []; for (const key of Object.keys(shape)) { const field = def.shape[key]; if (ctx.io === "input" ? inputOptin(field) === undefined : field._zod.optout === undefined) { requiredKeys.push(key); } } if (requiredKeys.length > 0) { json.required = requiredKeys; } // catchall if (def.catchall?._zod.def.type === "never") { // strict json.additionalProperties = false; } else if (!def.catchall) { // regular if (ctx.io === "output") json.additionalProperties = false; } else if (def.catchall) { json.additionalProperties = (0, to_json_schema_js_1.processSchema)(def.catchall, ctx, { ...params, path: [...params.path, "additionalProperties"], }); } }; exports.objectProcessor = objectProcessor; const unionProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; // Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches). This includes both z.xor() and discriminated unions const isExclusive = def.inclusive === false; const options = def.options.map((x, i) => (0, to_json_schema_js_1.processSchema)(x, ctx, { ...params, path: [...params.path, isExclusive ? "oneOf" : "anyOf", i], })); if (isExclusive) { json.oneOf = options; } else { json.anyOf = options; } }; exports.unionProcessor = unionProcessor; const intersectionProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; const a = (0, to_json_schema_js_1.processSchema)(def.left, ctx, { ...params, path: [...params.path, "allOf", 0], }); const b = (0, to_json_schema_js_1.processSchema)(def.right, ctx, { ...params, path: [...params.path, "allOf", 1], }); const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1; const allOf = [ ...(isSimpleIntersection(a) ? a.allOf : [a]), ...(isSimpleIntersection(b) ? b.allOf : [b]), ]; json.allOf = allOf; // Recorded innermost first, so a nested intersection has already folded by the time this one is considered. The array is the handle rather than the schema, because a wrapper that inherits this schema shares the same array; `finalize` folds every object holding it. See `foldIntersection`. ctx.intersections.push(allOf); }; exports.intersectionProcessor = intersectionProcessor; const tupleProcessor = (schema, ctx, _json, params) => { const json = _json; const def = schema._zod.def; json.type = "array"; const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items"; const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems"; const prefixItems = def.items.map((x, i) => (0, to_json_schema_js_1.processSchema)(x, ctx, { ...params, path: [...params.path, prefixPath, i], })); const rest = def.rest ? (0, to_json_schema_js_1.processSchema)(def.rest, ctx, { ...params, path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])], }) : null; let minItems = def.items.length; while (minItems > 0) { const item = def.items[minItems - 1]; const optional = ctx.io === "input" ? inputOptin(item) !== undefined : item._zod.optout === "optional"; if (!optional) break; minItems--; } const maxItems = def.items.length; const isClosed = !def.rest; if (ctx.target === "draft-2020-12") { json.prefixItems = prefixItems; if (isClosed) { json.items = false; } else if (rest) { json.items = rest; } if (minItems > 0) json.minItems = minItems; if (isClosed) json.maxItems = maxItems; } else if (ctx.target === "openapi-3.0") { json.items = { anyOf: prefixItems, }; if (rest) { json.items.anyOf.push(rest); } if (minItems > 0) json.minItems = minItems; if (isClosed) json.maxItems = maxItems; } else { json.items = prefixItems; if (isClosed) { json.additionalItems = false; } else if (rest) { json.additionalItems = rest; } if (minItems > 0) json.minItems = minItems; if (isClosed) json.maxItems = maxItems; } // explicit user-defined length checks take precedence const { minimum, maximum } = aggregateChecks(schema); if (typeof minimum === "number") json.minItems = minimum; if (typeof maximum === "number") json.maxItems = maximum; }; exports.tupleProcessor = tupleProcessor; /** JSON object keys are always strings, so a numeric record key schema is re-expressed over the * numeric-string form the record parser matches. Deferred to `finalize`, after the flatten: a key * behind a wrapper only carries its own `type` before then, and a union key only has its branches. * * A numeric bound cannot apply to a property name, so `minimum` and its siblings are dropped rather * than carried over: keeping them beside `type: "string"` reproduces the match-nothing schema this * exists to fix. A key that carries one therefore emits wider than the record parses — `z.record(z.number().min(5), V)` * accepts `"3"` — which is the deliberate trade, since throwing on it would reject an ordinary schema * outright. */ function stringifyKeyNames(bySchema, json, visited) { // an extracted key that rewrites cannot go on sharing its definition — the string form a key position needs is not the number form every other reference wants — so it inlines. One that does not rewrite keeps the `$ref`. if (json.$ref) { // a recursive key holds its own reference inside its definition, so a node already on the path is left alone rather than resolved again if (visited.has(json)) return json; visited.add(json); const def = bySchema.get(json)?.def; if (!def) return json; const inlined = stringifyKeyNames(bySchema, def, visited); return inlined === def ? json : inlined; } for (const keyword of ["anyOf", "oneOf"]) { const branches = json[keyword]; if (!Array.isArray(branches)) continue; const mapped = branches.map((branch) => stringifyKeyNames(bySchema, branch, visited)); // rebuilding regardless would detach a key that had nothing to re-express, dropping its `$ref` and leaking the internal `id` if (mapped.some((branch, i) => branch !== branches[i])) json = { ...json, [keyword]: mapped }; } // a member that already admits a string leaves the key unconstrained, so the node's own type re-expresses only when every member is numeric const types = Array.isArray(json.type) ? json.type : [json.type]; const numericType = !types.includes("string") && types.some((t) => t === "number" || t === "integer"); // a heterogeneous key carries no type at all, so its numeric members are caught here instead const values = json.enum ?? (json.const !== undefined ? [json.const] : undefined); if (!numericType && !values?.some((v) => typeof v === "number")) return json; const { minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, format, id, ...rest } = json; if (rest.enum) rest.enum = rest.enum.map((v) => (typeof v === "number" ? String(v) : v)); else if (typeof rest.const === "number") rest.const = String(rest.const); // a heterogeneous key keeps its absent type: the stringified members already say what a key may be if (!numericType) return rest; rest.type = "string"; if (!values) rest.pattern = (types.includes("number") ? regexes.number : regexes.integer).source; return rest; } /** Every record of one conversion, so the carriers are found in a single pass rather than once per record. */ const pendingRecords = new WeakMap(); function rewriteKeyNames(ctx) { // an extracted key is resolved by the object `extractToDef` left in its place, so the map is built once rather than searched per reference. `_zod.toJSONSchema` can hand the same object to two schemas, so the first entry carrying a body wins, as a search would have found it. const bySchema = new Map(); for (const entry of ctx.seen.values()) { if (entry.def && !bySchema.has(entry.schema)) bySchema.set(entry.schema, entry); } const rewrites = new Map(); for (const record of pendingRecords.get(ctx) ?? []) { const seen = ctx.seen.get(record); const names = (seen?.def ?? seen?.schema)?.propertyNames; if (!names || names === true || rewrites.has(names)) continue; const rewritten = stringifyKeyNames(bySchema, names, new Set()); if (rewritten !== names) rewrites.set(names, rewritten); } if (!rewrites.size) return; // the flatten has already copied each record's own properties onto every wrapper by reference, and an extracted body is another such copy, so every carrier holding a rewritten key is updated together for (const entry of ctx.seen.values()) { for (const carrier of [entry.schema, entry.def]) { const rewritten = carrier && rewrites.get(carrier.propertyNames); if (rewritten) carrier.propertyNames = rewritten; } } } const recordProcessor = (schema, ctx, _json, params) => { const json = _json; const def = schema._zod.def; json.type = "object"; // For looseRecord with regex patterns, use patternProperties. This correctly represents "only validate keys matching the pattern" semantics and composes well with allOf (intersections) const keyType = def.keyType; const patterns = aggregateChecks(keyType).patterns; if (def.mode === "loose" && patterns && patterns.size > 0) { // Use patternProperties for looseRecord with regex patterns const valueSchema = (0, to_json_schema_js_1.processSchema)(def.valueType, ctx, { ...params, path: [...params.path, "patternProperties", "*"], }); json.patternProperties = {}; for (const pattern of patterns) { (0, util_js_1.assignProp)(json.patternProperties, exactPattern(pattern).source, valueSchema); } } else { // Default behavior: use propertyNames + additionalProperties if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") { json.propertyNames = (0, to_json_schema_js_1.processSchema)(def.keyType, ctx, { ...params, path: [...params.path, "propertyNames"], }); let pending = pendingRecords.get(ctx); if (!pending) { pending = []; pendingRecords.set(ctx, pending); ctx.deferred.push(() => rewriteKeyNames(ctx)); } pending.push(schema); } json.additionalProperties = (0, to_json_schema_js_1.processSchema)(def.valueType, ctx, { ...params, path: [...params.path, "additionalProperties"], }); } // Add required for keys with discrete values (enum, literal, etc.) const keyValues = keyType._zod.values; // Every key shares one value schema, so an optional-in value makes the whole key set omittable on input. Output keeps them: the exhaustive branch assigns every key, even one whose value came back undefined. const omittableOnInput = ctx.io === "input" && inputOptin(def.valueType) !== undefined; if (keyValues && !def.partial && !omittableOnInput) { const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number"); if (validKeyValues.length > 0) { json.required = validKeyValues.map(String); } } }; exports.recordProcessor = recordProcessor; const nullableProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; const inner = (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); if (ctx.target === "openapi-3.0") { seen.ref = def.innerType; json.nullable = true; } else { json.anyOf = [inner, { type: "null" }]; } }; exports.nullableProcessor = nullableProcessor; const nonoptionalProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; }; exports.nonoptionalProcessor = nonoptionalProcessor; /** Round-trips a default value through JSON so the emitted schema is guaranteed to be valid JSON. * A BigInt has no reliable encoding, so it goes through `unrepresentable` like any other * unrepresentable value. Returns a sentinel when the caller must not write a default of its own. */ const UNREPRESENTABLE_DEFAULT = Symbol(); function serializeDefaultValue(value, schema, ctx, json, params) { let unrepresentable = false; const serialized = JSON.stringify(value, (_, val) => { if (typeof val !== "bigint") return val; unrepresentable = true; return null; }); if (!unrepresentable) return JSON.parse(serialized); (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "BigInt defaults cannot be represented in JSON Schema"); return UNREPRESENTABLE_DEFAULT; } const defaultProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params); if (value !== UNREPRESENTABLE_DEFAULT) json.default = value; }; exports.defaultProcessor = defaultProcessor; const prefaultProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; if (ctx.io !== "input") return; const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params); if (value !== UNREPRESENTABLE_DEFAULT) json._prefault = value; }; exports.prefaultProcessor = prefaultProcessor; const catchProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; let catchValue; try { catchValue = def.catchValue(undefined); } catch { (0, to_json_schema_js_1.handleUnrepresentable)(schema, ctx, json, params, "Dynamic catch values are not supported in JSON Schema"); return; } json.default = catchValue; }; exports.catchProcessor = catchProcessor; const pipeProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; const inIsTransform = def.in._zod.traits.has("$ZodTransform"); const innerType = ctx.io === "input" ? (inIsTransform ? def.out : def.in) : def.out; (0, to_json_schema_js_1.processSchema)(innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = innerType; }; exports.pipeProcessor = pipeProcessor; const readonlyProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; json.readOnly = true; }; exports.readonlyProcessor = readonlyProcessor; const promiseProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; }; exports.promiseProcessor = promiseProcessor; const optionalProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; (0, to_json_schema_js_1.processSchema)(def.innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = def.innerType; }; exports.optionalProcessor = optionalProcessor; const lazyProcessor = (schema, ctx, _json, params) => { const innerType = schema._zod.innerType; (0, to_json_schema_js_1.processSchema)(innerType, ctx, params); const seen = ctx.seen.get(schema); seen.ref = innerType; }; exports.lazyProcessor = lazyProcessor; // ==================== ALL PROCESSORS ==================== exports.allProcessors = { string: exports.stringProcessor, number: exports.numberProcessor, boolean: exports.booleanProcessor, bigint: exports.bigintProcessor, symbol: exports.symbolProcessor, null: exports.nullProcessor, undefined: exports.undefinedProcessor, void: exports.voidProcessor, never: exports.neverProcessor, any: exports.anyProcessor, unknown: exports.unknownProcessor, date: exports.dateProcessor, enum: exports.enumProcessor, literal: exports.literalProcessor, nan: exports.nanProcessor, template_literal: exports.templateLiteralProcessor, file: exports.fileProcessor, success: exports.successProcessor, custom: exports.customProcessor, function: exports.functionProcessor, transform: exports.transformProcessor, map: exports.mapProcessor, set: exports.setProcessor, array: exports.arrayProcessor, object: exports.objectProcessor, union: exports.unionProcessor, intersection: exports.intersectionProcessor, tuple: exports.tupleProcessor, record: exports.recordProcessor, nullable: exports.nullableProcessor, nonoptional: exports.nonoptionalProcessor, default: exports.defaultProcessor, prefault: exports.prefaultProcessor, catch: exports.catchProcessor, pipe: exports.pipeProcessor, readonly: exports.readonlyProcessor, promise: exports.promiseProcessor, optional: exports.optionalProcessor, lazy: exports.lazyProcessor, }; function toJSONSchema(input, params) { if ("_idmap" in input) { // Registry case const registry = input; const ctx = (0, to_json_schema_js_1.initializeContext)({ ...params, processors: exports.allProcessors }); const defs = {}; // First pass: process all schemas to build the seen map for (const entry of registry._idmap.entries()) { const [_, schema] = entry; (0, to_json_schema_js_1.processSchema)(schema, ctx); } const schemas = {}; const external = { registry, uri: params?.uri, defs, }; // Update the context with external configuration ctx.external = external; // Second pass: emit each schema for (const entry of registry._idmap.entries()) { const [key, schema] = entry; (0, to_json_schema_js_1.extractDefs)(ctx, schema); (0, util_js_1.assignProp)(schemas, key, (0, to_json_schema_js_1.finalize)(ctx, schema)); } if (Object.keys(defs).length > 0) { const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions"; schemas.__shared = { [defsSegment]: defs, }; } return { schemas }; } // Single schema case const ctx = (0, to_json_schema_js_1.initializeContext)({ ...params, processors: exports.allProcessors }); (0, to_json_schema_js_1.processSchema)(input, ctx); (0, to_json_schema_js_1.extractDefs)(ctx, input); return (0, to_json_schema_js_1.finalize)(ctx, input); } // seal-cjs-exports (function () { var keys = Object.getOwnPropertyNames(exports); for (var i = 0; i < keys.length; i++) { var desc = Object.getOwnPropertyDescriptor(exports, keys[i]); if (!desc || !desc.get || !desc.configurable) continue; var value; try { value = desc.get(); } catch (e) { continue; } // a circular require may not have settled this one yet, so leave it live if (value === undefined) continue; Object.defineProperty(exports, keys[i], { value: value, writable: false, enumerable: desc.enumerable, configurable: false }); } Object.freeze(exports); })();