schema-components 1.20.0 → 1.21.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/dist/core/adapter.d.mts +9 -2
- package/dist/core/adapter.mjs +220 -64
- package/dist/core/constraints.d.mts +1 -1
- package/dist/core/constraints.mjs +0 -2
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +9 -15
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/merge.d.mts +10 -1
- package/dist/core/merge.mjs +11 -0
- package/dist/core/normalise.d.mts +7 -1
- package/dist/core/normalise.mjs +1 -1
- package/dist/core/openapi30.d.mts +24 -1
- package/dist/core/openapi30.mjs +2 -2
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/ref.mjs +34 -9
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +1 -1
- package/dist/core/types.d.mts +1 -1
- package/dist/core/walkBuilders.d.mts +12 -4
- package/dist/core/walkBuilders.mjs +11 -3
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +32 -25
- package/dist/{errors-C2iABcn9.d.mts → errors-QEwOtQAA.d.mts} +7 -11
- package/dist/html/a11y.d.mts +2 -2
- package/dist/html/renderToHtml.d.mts +2 -2
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderers.d.mts +2 -2
- package/dist/html/renderers.mjs +1 -1
- package/dist/html/streamRenderers.d.mts +2 -2
- package/dist/{normalise-CMMEl4cd.mjs → normalise-DaSrnr8g.mjs} +325 -28
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/ApiSecurity.mjs +113 -7
- package/dist/openapi/components.d.mts +32 -10
- package/dist/openapi/components.mjs +22 -12
- package/dist/openapi/parser.d.mts +1 -1
- package/dist/openapi/parser.mjs +39 -4
- package/dist/openapi/resolve.d.mts +60 -9
- package/dist/openapi/resolve.mjs +86 -23
- package/dist/react/SchemaComponent.d.mts +4 -4
- package/dist/react/SchemaComponent.mjs +32 -4
- package/dist/react/SchemaView.d.mts +2 -2
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +2 -2
- package/dist/react/headlessRenderers.mjs +1 -1
- package/dist/{ref-C8JbwfiS.d.mts → ref-si8ViYun.d.mts} +6 -1
- package/dist/{renderer-SOIbJBtk.d.mts → renderer-DI6ZYf7a.d.mts} +1 -1
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/{typeInference-CDoD_LZ_.d.mts → typeInference-Bxw3NOG1.d.mts} +162 -48
- package/dist/{types-C9zw9wbX.d.mts → types-BnxPEElk.d.mts} +12 -2
- package/package.json +1 -1
package/dist/core/adapter.d.mts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { T as SchemaMeta, m as JsonObject } from "../types-
|
|
1
|
+
import { T as SchemaMeta, m as JsonObject } from "../types-BnxPEElk.mjs";
|
|
2
2
|
import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/adapter.d.ts
|
|
5
5
|
type SchemaInput = Record<string, unknown>;
|
|
6
6
|
type SchemaKind = "zod4" | "zod3" | "jsonSchema" | "openapi";
|
|
7
7
|
declare function detectSchemaKind(input: unknown): SchemaKind;
|
|
8
|
+
/**
|
|
9
|
+
* Exposed for unit testing — lets the contract test enumerate every rule's
|
|
10
|
+
* `prefix` value and assert mutual non-prefixing.
|
|
11
|
+
*/
|
|
12
|
+
declare const __CLASSIFIER_RULES_FOR_TEST: readonly {
|
|
13
|
+
readonly prefix: string;
|
|
14
|
+
}[];
|
|
8
15
|
interface NormalisedSchema {
|
|
9
16
|
/** JSON Schema object — the authoritative schema for rendering. */
|
|
10
17
|
jsonSchema: JsonObject;
|
|
@@ -21,4 +28,4 @@ interface NormaliseOptions {
|
|
|
21
28
|
}
|
|
22
29
|
declare function normaliseSchema(input: unknown, ref?: string, options?: NormaliseOptions): NormalisedSchema;
|
|
23
30
|
//#endregion
|
|
24
|
-
export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
|
|
31
|
+
export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
|
package/dist/core/adapter.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { SchemaNormalisationError } from "./errors.mjs";
|
|
|
3
3
|
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
4
4
|
import { dereference } from "./ref.mjs";
|
|
5
5
|
import { detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2, matchJsonSchemaDraftUri } from "./version.mjs";
|
|
6
|
-
import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-
|
|
6
|
+
import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-DaSrnr8g.mjs";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
//#region src/core/adapter.ts
|
|
9
9
|
/**
|
|
@@ -36,19 +36,33 @@ function detectSchemaKind(input) {
|
|
|
36
36
|
* SchemaNormalisationError so the caller does not have to re-parse error
|
|
37
37
|
* message strings. The classification covers:
|
|
38
38
|
*
|
|
39
|
-
* - Nested Zod 3 schemas inside a Zod 4 tree
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* - Nested Zod 3 schemas inside a Zod 4 tree → zod3-unsupported.
|
|
40
|
+
* Detected structurally (presence of `_def.typeName` markers anywhere
|
|
41
|
+
* in the schema tree) so the check works across V8, JavaScriptCore,
|
|
42
|
+
* and SpiderMonkey, none of which agree on the wording of
|
|
43
|
+
* "Cannot read properties of undefined".
|
|
44
|
+
* - Transforms → zod-transform-unsupported. This also catches `z.codec(…)`
|
|
45
|
+
* because Zod implements codecs as a pipe + transform internally, so
|
|
46
|
+
* they trip the same processor when round-tripping is forced. (Plain
|
|
47
|
+
* `z.toJSONSchema(codec)` itself does NOT throw because Zod picks one
|
|
48
|
+
* side of the codec; the static rejection in `typeInference.ts` is the
|
|
49
|
+
* compile-time guard.)
|
|
50
|
+
* - Dynamic catch values whose handler throws → zod-type-unrepresentable
|
|
51
|
+
* with zodType "dynamic-catch".
|
|
44
52
|
* - Unrepresentable types — bigint, date, map, set, symbol, function, custom,
|
|
45
53
|
* undefined, void, NaN, and the literal-only forms `z.literal(undefined)`
|
|
46
54
|
* ("undefined-literal") and `z.literal(<bigint>)` ("bigint-literal") →
|
|
47
|
-
* zod-type-unrepresentable
|
|
55
|
+
* zod-type-unrepresentable.
|
|
48
56
|
* - The catch-all "Non-representable type encountered: <type>" fallback Zod
|
|
49
57
|
* emits for any new schema kind without a registered processor →
|
|
50
|
-
* zod-type-unrepresentable with zodType set to the offending def.type
|
|
51
|
-
* -
|
|
58
|
+
* zod-type-unrepresentable with zodType set to the offending def.type.
|
|
59
|
+
* - Cycle detected (`cycles: "throw"`) → zod-cycle-detected.
|
|
60
|
+
* - Duplicate schema id → zod-duplicate-id.
|
|
61
|
+
* - "Unprocessed schema. This is a bug in Zod." → zod-conversion-bug.
|
|
62
|
+
* - "Error converting schema to JSON." → zod-conversion-failed (explicit
|
|
63
|
+
* classification rather than the generic fallback so the contract test
|
|
64
|
+
* protects the prefix from drift).
|
|
65
|
+
* - Anything else → zod-conversion-failed.
|
|
52
66
|
*
|
|
53
67
|
* The original error is preserved on each classified error via the `cause`
|
|
54
68
|
* field so consumers can still inspect the Zod stack trace.
|
|
@@ -61,76 +75,218 @@ function callToJsonSchema(schema) {
|
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
78
|
+
* Escape a string for inclusion in a `RegExp`. Required because Zod
|
|
79
|
+
* messages contain `[`, `]`, `.`, `(`, and `)` characters which have regex
|
|
80
|
+
* meaning. The set covers every character with special meaning in a
|
|
81
|
+
* JavaScript regular-expression source — RegExp.escape is not yet widely
|
|
82
|
+
* available so we escape manually.
|
|
83
|
+
*/
|
|
84
|
+
function escapeRegExp(literal) {
|
|
85
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compile a prefix into an anchored regex that captures any trailing text
|
|
89
|
+
* (used by rules that need to extract dynamic data such as the duplicate id
|
|
90
|
+
* or the def.type that tripped the non-representable fallback).
|
|
91
|
+
*/
|
|
92
|
+
function anchored(prefix) {
|
|
93
|
+
return new RegExp(`^${escapeRegExp(prefix)}(.*)$`, "s");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build the message body shared by every unrepresentable-type rule.
|
|
97
|
+
*/
|
|
98
|
+
function unrepresentableMessage(typeName, fullMessage) {
|
|
99
|
+
return `Zod type ${typeName} cannot be represented in JSON Schema and is not supported by schema-components. Original message: ${fullMessage}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Classifier rules ordered most-specific first. Order is load-bearing:
|
|
103
|
+
* `Literal \`undefined\` cannot be represented` must precede the broader
|
|
104
|
+
* `Undefined cannot be represented` so the literal classification wins
|
|
105
|
+
* even when both share a leading word. A consistency check in the unit
|
|
106
|
+
* test suite asserts no two `prefix` values are prefixes of each other —
|
|
107
|
+
* any future rule that breaks the invariant fails the build.
|
|
67
108
|
*
|
|
68
|
-
*
|
|
109
|
+
* Verbatim sources (kept aligned with `tests/zod-error-wording-contract.unit.test.ts`):
|
|
69
110
|
* - zod/src/v4/core/json-schema-processors.ts L104 (bigint), L110 (symbol),
|
|
70
111
|
* L126 (undefined), L132 (void), L150 (date), L169 (literal-undefined),
|
|
71
112
|
* L175 (literal-bigint), L204 (NaN), L246 (custom), L252 (function),
|
|
72
|
-
* L264 (map), L270 (set), L521 (dynamic catch).
|
|
73
|
-
* - zod/src/v4/core/to-json-schema.ts L182 (non-representable type fallback)
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* the source — the test in tests/zod-error-wording-contract.unit.test.ts
|
|
77
|
-
* asserts each prefix is still present in the live Zod output so a Zod
|
|
78
|
-
* patch upgrade that changes wording fails the build.
|
|
79
|
-
*
|
|
80
|
-
* Note: the more specific literal-* prefixes precede the generic "BigInt"
|
|
81
|
-
* prefix so the literal classifications win. JavaScript object iteration
|
|
82
|
-
* order preserves insertion order, and the loop short-circuits on first
|
|
83
|
-
* match, so ordering here is load-bearing.
|
|
113
|
+
* L258 (transforms), L264 (map), L270 (set), L521 (dynamic catch).
|
|
114
|
+
* - zod/src/v4/core/to-json-schema.ts L182 (non-representable type fallback),
|
|
115
|
+
* L225 + L364 (unprocessed schema), L235 (duplicate id), L307 (cycle),
|
|
116
|
+
* L522 (error converting).
|
|
84
117
|
*/
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
const CLASSIFIER_RULES = [
|
|
119
|
+
{
|
|
120
|
+
prefix: "Literal `undefined` cannot be represented",
|
|
121
|
+
kind: "zod-type-unrepresentable",
|
|
122
|
+
zodType: "undefined-literal",
|
|
123
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined-literal", full), schema, "zod-type-unrepresentable", "undefined-literal", cause)
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
prefix: "BigInt literals cannot be represented",
|
|
127
|
+
kind: "zod-type-unrepresentable",
|
|
128
|
+
zodType: "bigint-literal",
|
|
129
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint-literal", full), schema, "zod-type-unrepresentable", "bigint-literal", cause)
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
prefix: "BigInt cannot be represented",
|
|
133
|
+
kind: "zod-type-unrepresentable",
|
|
134
|
+
zodType: "bigint",
|
|
135
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint", full), schema, "zod-type-unrepresentable", "bigint", cause)
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
prefix: "Date cannot be represented",
|
|
139
|
+
kind: "zod-type-unrepresentable",
|
|
140
|
+
zodType: "date",
|
|
141
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("date", full), schema, "zod-type-unrepresentable", "date", cause)
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
prefix: "Map cannot be represented",
|
|
145
|
+
kind: "zod-type-unrepresentable",
|
|
146
|
+
zodType: "map",
|
|
147
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("map", full), schema, "zod-type-unrepresentable", "map", cause)
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
prefix: "Set cannot be represented",
|
|
151
|
+
kind: "zod-type-unrepresentable",
|
|
152
|
+
zodType: "set",
|
|
153
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("set", full), schema, "zod-type-unrepresentable", "set", cause)
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
prefix: "Symbols cannot be represented",
|
|
157
|
+
kind: "zod-type-unrepresentable",
|
|
158
|
+
zodType: "symbol",
|
|
159
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("symbol", full), schema, "zod-type-unrepresentable", "symbol", cause)
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
prefix: "Function types cannot be represented",
|
|
163
|
+
kind: "zod-type-unrepresentable",
|
|
164
|
+
zodType: "function",
|
|
165
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("function", full), schema, "zod-type-unrepresentable", "function", cause)
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
prefix: "Custom types cannot be represented",
|
|
169
|
+
kind: "zod-type-unrepresentable",
|
|
170
|
+
zodType: "custom",
|
|
171
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("custom", full), schema, "zod-type-unrepresentable", "custom", cause)
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
prefix: "Undefined cannot be represented",
|
|
175
|
+
kind: "zod-type-unrepresentable",
|
|
176
|
+
zodType: "undefined",
|
|
177
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined", full), schema, "zod-type-unrepresentable", "undefined", cause)
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
prefix: "Void cannot be represented",
|
|
181
|
+
kind: "zod-type-unrepresentable",
|
|
182
|
+
zodType: "void",
|
|
183
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("void", full), schema, "zod-type-unrepresentable", "void", cause)
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
prefix: "NaN cannot be represented",
|
|
187
|
+
kind: "zod-type-unrepresentable",
|
|
188
|
+
zodType: "nan",
|
|
189
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("nan", full), schema, "zod-type-unrepresentable", "nan", cause)
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
prefix: "Transforms cannot be represented",
|
|
193
|
+
kind: "zod-transform-unsupported",
|
|
194
|
+
build: (_m, cause, schema) => new SchemaNormalisationError("Zod transforms cannot be represented in JSON Schema. Remove the .transform() call, or pre-transform the input before passing it to the component. (Note: z.codec(...) is implemented as a transform internally — codecs that force round-tripping trip this same rule.)", schema, "zod-transform-unsupported", void 0, cause)
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
prefix: "Dynamic catch values are not supported",
|
|
198
|
+
kind: "zod-type-unrepresentable",
|
|
199
|
+
zodType: "dynamic-catch",
|
|
200
|
+
build: (_m, cause, schema) => new SchemaNormalisationError("Zod catch values that depend on runtime computation cannot be represented in JSON Schema. Provide a static catch value or remove the .catch() call.", schema, "zod-type-unrepresentable", "dynamic-catch", cause)
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
prefix: "[toJSONSchema]: Non-representable type encountered:",
|
|
204
|
+
kind: "zod-type-unrepresentable",
|
|
205
|
+
build: (match, cause, schema, full) => {
|
|
206
|
+
const trailing = match[1]?.trim() ?? "";
|
|
207
|
+
const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
|
|
208
|
+
return new SchemaNormalisationError(`Zod encountered a schema kind${typeName !== void 0 ? ` "${typeName}"` : ""} with no JSON Schema processor registered. This usually means Zod added a new schema type that schema-components does not yet support. Original message: ${full}`, schema, "zod-type-unrepresentable", typeName, cause);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
prefix: "Cycle detected: ",
|
|
213
|
+
kind: "zod-cycle-detected",
|
|
214
|
+
build: (match, cause, schema, full) => {
|
|
215
|
+
return new SchemaNormalisationError(`Zod detected a cycle in the schema graph at ${(match[1] ?? "").split(/\s+/)[0] ?? ""}. Cycles can only be converted when z.toJSONSchema is called with { cycles: "ref" } — schema-components calls it without options for cache safety, so the cycle surfaces as an error. Restructure the schema to break the cycle, or use a $ref-based definition. Original message: ${full}`, schema, "zod-cycle-detected", void 0, cause);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
prefix: "Duplicate schema id \"",
|
|
220
|
+
kind: "zod-duplicate-id",
|
|
221
|
+
build: (match, cause, schema, full) => {
|
|
222
|
+
const trailing = match[1] ?? "";
|
|
223
|
+
const closing = trailing.indexOf("\"");
|
|
224
|
+
return new SchemaNormalisationError(`Two different Zod schemas share the same id "${closing === -1 ? trailing : trailing.slice(0, closing)}". JSON Schema requires distinct ids when multiple schemas are bundled together. Give each schema its own .meta({ id: ... }) or remove the duplicate. Original message: ${full}`, schema, "zod-duplicate-id", void 0, cause);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
prefix: "Unprocessed schema. This is a bug in Zod.",
|
|
229
|
+
kind: "zod-conversion-bug",
|
|
230
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(`Zod failed to process this schema during JSON Schema conversion and reports it as an internal bug. File an issue on the Zod tracker with a reproduction. Original message: ${full}`, schema, "zod-conversion-bug", void 0, cause)
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
prefix: "Error converting schema to JSON.",
|
|
234
|
+
kind: "zod-conversion-failed",
|
|
235
|
+
build: (_m, cause, schema, full) => new SchemaNormalisationError(`z.toJSONSchema() failed to produce a Standard Schema payload. Inspect the underlying cause for the original error. Original message: ${full}`, schema, "zod-conversion-failed", void 0, cause)
|
|
236
|
+
}
|
|
98
237
|
];
|
|
99
238
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* Source: zod/src/v4/core/to-json-schema.ts L182
|
|
104
|
-
* `[toJSONSchema]: Non-representable type encountered: ${def.type}`
|
|
239
|
+
* Compiled regex form of {@link CLASSIFIER_RULES} — built once at module
|
|
240
|
+
* load. Avoids per-error compilation.
|
|
105
241
|
*/
|
|
106
|
-
const
|
|
242
|
+
const COMPILED_CLASSIFIER_RULES = CLASSIFIER_RULES.map((rule) => ({
|
|
243
|
+
rule,
|
|
244
|
+
pattern: anchored(rule.prefix)
|
|
245
|
+
}));
|
|
107
246
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
247
|
+
* Walk an arbitrary value looking for Zod 3 markers (`_def.typeName`).
|
|
248
|
+
* Zod 4 schemas always carry a `_zod.def`; Zod 3 schemas carry `_def`
|
|
249
|
+
* with a `typeName` field. Presence of the latter anywhere in the tree
|
|
250
|
+
* means a Zod 3 schema was nested inside a Zod 4 input, which is what
|
|
251
|
+
* trips the V8 `"Cannot read properties of undefined"` failure.
|
|
110
252
|
*
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
253
|
+
* Engine-agnostic by construction — the detector inspects schema shape
|
|
254
|
+
* instead of pattern-matching against the runtime's TypeError message,
|
|
255
|
+
* so it works equivalently under V8, JavaScriptCore (Bun/Safari), and
|
|
256
|
+
* SpiderMonkey (Firefox) — none of which agree on the wording.
|
|
257
|
+
*
|
|
258
|
+
* The walk is bounded by an explicit `visited` set so cyclical references
|
|
259
|
+
* cannot cause stack overflow. The recursion follows both array elements
|
|
260
|
+
* and own enumerable properties of every object encountered.
|
|
118
261
|
*/
|
|
119
|
-
|
|
262
|
+
function containsNestedZod3(value, visited) {
|
|
263
|
+
if (value === null || typeof value !== "object") return false;
|
|
264
|
+
if (visited.has(value)) return false;
|
|
265
|
+
visited.add(value);
|
|
266
|
+
if (Array.isArray(value)) {
|
|
267
|
+
for (const item of value) if (containsNestedZod3(item, visited)) return true;
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (!isObject(value)) return false;
|
|
271
|
+
const def = value._def;
|
|
272
|
+
if (value._zod === void 0 && isObject(def) && typeof def.typeName === "string") return true;
|
|
273
|
+
for (const key of Object.keys(value)) if (containsNestedZod3(value[key], visited)) return true;
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
120
276
|
function classifyZodConversionError(err, schema) {
|
|
121
277
|
const message = err instanceof Error ? err.message : String(err);
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const nonReprIndex = message.indexOf(NON_REPRESENTABLE_TYPE_MARKER);
|
|
127
|
-
if (nonReprIndex !== -1) {
|
|
128
|
-
const trailing = message.slice(nonReprIndex + 51).trim();
|
|
129
|
-
const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
|
|
130
|
-
return new SchemaNormalisationError(`Zod encountered a schema kind${typeName !== void 0 ? ` "${typeName}"` : ""} with no JSON Schema processor registered. This usually means Zod added a new schema type that schema-components does not yet support. Original message: ${message}`, schema, "zod-type-unrepresentable", typeName, err);
|
|
278
|
+
if (containsNestedZod3(schema, /* @__PURE__ */ new Set())) return new SchemaNormalisationError("A nested Zod 3 schema was found inside a Zod 4 schema. schema-components requires Zod 4 throughout the schema tree. See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", schema, "zod3-unsupported", void 0, err);
|
|
279
|
+
for (const { rule, pattern } of COMPILED_CLASSIFIER_RULES) {
|
|
280
|
+
const match = pattern.exec(message);
|
|
281
|
+
if (match !== null) return rule.build(match, err, schema, message);
|
|
131
282
|
}
|
|
132
283
|
return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed", void 0, err);
|
|
133
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Exposed for unit testing — lets the contract test enumerate every rule's
|
|
287
|
+
* `prefix` value and assert mutual non-prefixing.
|
|
288
|
+
*/
|
|
289
|
+
const __CLASSIFIER_RULES_FOR_TEST = CLASSIFIER_RULES;
|
|
134
290
|
function normaliseSchema(input, ref, options) {
|
|
135
291
|
if (ref === void 0 && isObject(input)) {
|
|
136
292
|
const cached = schemaCache.get(input);
|
|
@@ -294,4 +450,4 @@ function extractRootMetaFromJson(jsonSchema) {
|
|
|
294
450
|
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
295
451
|
}
|
|
296
452
|
//#endregion
|
|
297
|
-
export { detectSchemaKind, normaliseSchema };
|
|
453
|
+
export { __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-
|
|
1
|
+
import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-BnxPEElk.mjs";
|
|
2
2
|
import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/constraints.d.ts
|
|
@@ -61,8 +61,6 @@ function extractArrayConstraints(schema) {
|
|
|
61
61
|
const maxItems = getNumber(schema, "maxItems");
|
|
62
62
|
if (maxItems !== void 0) c.maxItems = maxItems;
|
|
63
63
|
if (schema.uniqueItems === true) c.uniqueItems = true;
|
|
64
|
-
const contains = getObject(schema, "contains");
|
|
65
|
-
if (contains !== void 0) c.contains = contains;
|
|
66
64
|
const minContains = getNumber(schema, "minContains");
|
|
67
65
|
if (minContains !== void 0) c.minContains = minContains;
|
|
68
66
|
const maxContains = getNumber(schema, "maxContains");
|
package/dist/core/errors.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-
|
|
1
|
+
import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-QEwOtQAA.mjs";
|
|
2
2
|
export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
|
package/dist/core/errors.mjs
CHANGED
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* Base class for all schema-components errors.
|
|
16
16
|
* Catch this to handle any library error uniformly.
|
|
17
|
+
*
|
|
18
|
+
* Forwards the optional `cause` to the native ES2022 `Error` constructor so
|
|
19
|
+
* `error.cause` is wired up by the runtime and rendered correctly by
|
|
20
|
+
* `util.inspect` ("Caused by: ..."). Subclasses that need a typed `cause`
|
|
21
|
+
* field still get it via the platform's own `Error.cause` getter.
|
|
17
22
|
*/
|
|
18
23
|
var SchemaError = class extends Error {
|
|
19
24
|
/** The schema input that caused the error. */
|
|
20
25
|
schema;
|
|
21
|
-
constructor(message, schema) {
|
|
22
|
-
super(message);
|
|
26
|
+
constructor(message, schema, cause) {
|
|
27
|
+
super(message, cause !== void 0 ? { cause } : void 0);
|
|
23
28
|
this.name = "SchemaError";
|
|
24
29
|
this.schema = schema;
|
|
25
30
|
}
|
|
@@ -37,19 +42,11 @@ var SchemaNormalisationError = class extends SchemaError {
|
|
|
37
42
|
* (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
|
|
38
43
|
*/
|
|
39
44
|
zodType;
|
|
40
|
-
/**
|
|
41
|
-
* The original underlying error, when this normalisation error wraps
|
|
42
|
-
* another exception (typically the error thrown by `z.toJSONSchema()`).
|
|
43
|
-
* Preserves the source stack trace so the root cause is not lost when
|
|
44
|
-
* the classifier translates the message.
|
|
45
|
-
*/
|
|
46
|
-
cause;
|
|
47
45
|
constructor(message, schema, kind, zodType, cause) {
|
|
48
|
-
super(message, schema);
|
|
46
|
+
super(message, schema, cause);
|
|
49
47
|
this.name = "SchemaNormalisationError";
|
|
50
48
|
this.kind = kind;
|
|
51
49
|
this.zodType = zodType;
|
|
52
|
-
this.cause = cause;
|
|
53
50
|
}
|
|
54
51
|
};
|
|
55
52
|
/**
|
|
@@ -60,13 +57,10 @@ var SchemaNormalisationError = class extends SchemaError {
|
|
|
60
57
|
var SchemaRenderError = class extends SchemaError {
|
|
61
58
|
/** The schema type being rendered when the error occurred. */
|
|
62
59
|
schemaType;
|
|
63
|
-
/** The original error from the render function. */
|
|
64
|
-
cause;
|
|
65
60
|
constructor(message, schema, schemaType, cause) {
|
|
66
|
-
super(message, schema);
|
|
61
|
+
super(message, schema, cause);
|
|
67
62
|
this.name = "SchemaRenderError";
|
|
68
63
|
this.schemaType = schemaType;
|
|
69
|
-
this.cause = cause;
|
|
70
64
|
}
|
|
71
65
|
};
|
|
72
66
|
/**
|
package/dist/core/merge.d.mts
CHANGED
|
@@ -25,8 +25,17 @@ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedM
|
|
|
25
25
|
* When a later branch redefines a keyword with a non-equal value the
|
|
26
26
|
* later value is silently dropped — an `allof-conflict` diagnostic is
|
|
27
27
|
* emitted so the loss is visible to consumers.
|
|
28
|
+
*
|
|
29
|
+
* Boolean branches (valid per Draft 06+) collapse the composite:
|
|
30
|
+
* - `false` makes the entire \`allOf\` unsatisfiable — return \`false\`,
|
|
31
|
+
* which the walker turns into a \`NeverField\`.
|
|
32
|
+
* - \`true\` is the always-valid schema and contributes no constraints —
|
|
33
|
+
* skip silently.
|
|
34
|
+
*
|
|
35
|
+
* Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
|
|
36
|
+
* inputs that cannot represent a schema; skip them as before.
|
|
28
37
|
*/
|
|
29
|
-
declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown
|
|
38
|
+
declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown> | false;
|
|
30
39
|
interface NormalisedAnyOf {
|
|
31
40
|
inner: Record<string, unknown>;
|
|
32
41
|
isNullable: boolean;
|
package/dist/core/merge.mjs
CHANGED
|
@@ -109,12 +109,23 @@ function mergeRefSiblings(referencer, resolvedMeta) {
|
|
|
109
109
|
* When a later branch redefines a keyword with a non-equal value the
|
|
110
110
|
* later value is silently dropped — an `allof-conflict` diagnostic is
|
|
111
111
|
* emitted so the loss is visible to consumers.
|
|
112
|
+
*
|
|
113
|
+
* Boolean branches (valid per Draft 06+) collapse the composite:
|
|
114
|
+
* - `false` makes the entire \`allOf\` unsatisfiable — return \`false\`,
|
|
115
|
+
* which the walker turns into a \`NeverField\`.
|
|
116
|
+
* - \`true\` is the always-valid schema and contributes no constraints —
|
|
117
|
+
* skip silently.
|
|
118
|
+
*
|
|
119
|
+
* Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
|
|
120
|
+
* inputs that cannot represent a schema; skip them as before.
|
|
112
121
|
*/
|
|
113
122
|
function mergeAllOf(schemas, diagnostics, pointer = "") {
|
|
114
123
|
const merged = {};
|
|
115
124
|
const properties = {};
|
|
116
125
|
const required = [];
|
|
117
126
|
for (const entry of schemas) {
|
|
127
|
+
if (entry === false) return false;
|
|
128
|
+
if (entry === true) continue;
|
|
118
129
|
if (!isObject(entry)) continue;
|
|
119
130
|
const props = getObject(entry, "properties");
|
|
120
131
|
if (props !== void 0) for (const [key, value] of Object.entries(props)) properties[key] = value;
|
|
@@ -6,8 +6,14 @@ type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
|
|
|
6
6
|
/**
|
|
7
7
|
* Deep-normalise a JSON Schema object by applying a per-node transform
|
|
8
8
|
* and recursing into every sub-schema location.
|
|
9
|
+
*
|
|
10
|
+
* The optional `visited` set guards against shared object references and
|
|
11
|
+
* cycles introduced upstream (e.g. by the OpenAPI bundler's
|
|
12
|
+
* `structuredClone`-based inlining of external refs). The walk skips
|
|
13
|
+
* already-seen nodes by returning the original reference rather than
|
|
14
|
+
* recursing forever.
|
|
9
15
|
*/
|
|
10
|
-
declare function deepNormalise(schema: Record<string, unknown>, transform: NodeTransform): Record<string, unknown>;
|
|
16
|
+
declare function deepNormalise(schema: Record<string, unknown>, transform: NodeTransform, visited?: WeakSet<object>): Record<string, unknown>;
|
|
11
17
|
/**
|
|
12
18
|
* Per-node context threaded through `deepNormaliseWithContext`.
|
|
13
19
|
*
|
package/dist/core/normalise.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-
|
|
1
|
+
import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-DaSrnr8g.mjs";
|
|
2
2
|
export { deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
|
|
@@ -23,6 +23,29 @@ declare function normaliseOpenApi30Node(node: Record<string, unknown>): Record<s
|
|
|
23
23
|
* `mapping` or infers them from `$ref` fragment names.
|
|
24
24
|
*/
|
|
25
25
|
declare function normaliseOpenApi30Discriminator(node: Record<string, unknown>): Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Document-level pre-pass for OpenAPI discriminators that are declared
|
|
28
|
+
* on a base schema and inherited by subtypes via `allOf`.
|
|
29
|
+
*
|
|
30
|
+
* The per-node {@link normaliseOpenApi30Discriminator} only handles
|
|
31
|
+
* discriminators that already sit alongside `oneOf`/`anyOf`. For the
|
|
32
|
+
* canonical "Cat extends Pet" pattern — where `Pet` carries the
|
|
33
|
+
* discriminator and `Cat`/`Dog` reference `Pet` via `allOf` — the
|
|
34
|
+
* discriminator is silently lost. This pre-pass:
|
|
35
|
+
*
|
|
36
|
+
* 1. Injects the discriminator `const` on each subtype's local
|
|
37
|
+
* `properties` (so a direct render of the subtype validates the
|
|
38
|
+
* discriminator value correctly).
|
|
39
|
+
* 2. Synthesises a `oneOf` on the base whenever it lacks one, listing
|
|
40
|
+
* each subtype as `{ $ref, properties: { propertyName: { const } } }`.
|
|
41
|
+
* The per-node discriminator transform then sees `oneOf` and clears
|
|
42
|
+
* the `discriminator` keyword, and the walker's
|
|
43
|
+
* `detectDiscriminated` finds the per-option `const`s.
|
|
44
|
+
*
|
|
45
|
+
* Mutates a shallow clone of `components/schemas` — the input document
|
|
46
|
+
* is never modified.
|
|
47
|
+
*/
|
|
48
|
+
declare function applyDiscriminatorAllOfPrepass(doc: Record<string, unknown>): Record<string, unknown>;
|
|
26
49
|
/**
|
|
27
50
|
* Combined OpenAPI 3.0.x node transform: Draft 04 + nullable + discriminator.
|
|
28
51
|
* Applied to every schema node in an OpenAPI 3.0 document.
|
|
@@ -67,4 +90,4 @@ declare function deepNormaliseOpenApiDoc(doc: Record<string, unknown>, normalise
|
|
|
67
90
|
*/
|
|
68
91
|
declare function deepNormaliseOpenApi30Doc(doc: Record<string, unknown>, deepNormalise: (schema: Record<string, unknown>, transform: NodeTransform) => Record<string, unknown>): Record<string, unknown>;
|
|
69
92
|
//#endregion
|
|
70
|
-
export { deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
|
|
93
|
+
export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
|
package/dist/core/openapi30.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
export { deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
|
|
1
|
+
import { c as deepNormaliseOpenApi30Doc, d as normaliseOpenApi30Discriminator, f as normaliseOpenApi30Node, l as deepNormaliseOpenApiDoc, s as applyDiscriminatorAllOfPrepass, u as normaliseOpenApi30Combined } from "../normalise-DaSrnr8g.mjs";
|
|
2
|
+
export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
|
package/dist/core/ref.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-
|
|
1
|
+
import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-si8ViYun.mjs";
|
|
2
2
|
export { ExternalResolver, RefOptions, countDistinctRefs, dereference, findAnchor, resolveRef };
|
package/dist/core/ref.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isObject } from "./guards.mjs";
|
|
2
2
|
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
3
|
+
import { isPrototypePollutingKey } from "./uri.mjs";
|
|
3
4
|
//#region src/core/ref.ts
|
|
4
5
|
/**
|
|
5
6
|
* $ref resolution for JSON Schema.
|
|
@@ -18,15 +19,27 @@ function getString(obj, key) {
|
|
|
18
19
|
*/
|
|
19
20
|
function countDistinctRefs(root) {
|
|
20
21
|
const refs = /* @__PURE__ */ new Set();
|
|
21
|
-
collectRefs(root, refs);
|
|
22
|
+
collectRefs(root, refs, /* @__PURE__ */ new WeakSet());
|
|
22
23
|
return Math.max(refs.size, 1);
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The OpenAPI bundler (`bundleOpenApiDoc`) inlines external refs via
|
|
27
|
+
* `structuredClone`, which preserves shared object references and cycles.
|
|
28
|
+
* Without the `visited` set this walk would recurse forever on cyclic or
|
|
29
|
+
* diamond-shaped input. The set is a no-op for tree-shaped documents.
|
|
30
|
+
*/
|
|
31
|
+
function collectRefs(node, refs, visited) {
|
|
25
32
|
if (!isObject(node)) return;
|
|
33
|
+
if (visited.has(node)) return;
|
|
34
|
+
visited.add(node);
|
|
26
35
|
const ref = node.$ref;
|
|
27
36
|
if (typeof ref === "string") refs.add(ref);
|
|
28
|
-
for (const value of Object.values(node)) if (isObject(value)) collectRefs(value, refs);
|
|
29
|
-
else if (Array.isArray(value))
|
|
37
|
+
for (const value of Object.values(node)) if (isObject(value)) collectRefs(value, refs, visited);
|
|
38
|
+
else if (Array.isArray(value)) {
|
|
39
|
+
if (visited.has(value)) continue;
|
|
40
|
+
visited.add(value);
|
|
41
|
+
for (const item of value) collectRefs(item, refs, visited);
|
|
42
|
+
}
|
|
30
43
|
}
|
|
31
44
|
/**
|
|
32
45
|
* Resolve a `$ref` in a schema against a root document.
|
|
@@ -134,6 +147,7 @@ function dereference(ref, root) {
|
|
|
134
147
|
for (const part of parts) {
|
|
135
148
|
if (!isObject(current)) return void 0;
|
|
136
149
|
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
150
|
+
if (isPrototypePollutingKey(decoded)) return void 0;
|
|
137
151
|
current = current[decoded];
|
|
138
152
|
}
|
|
139
153
|
return isObject(current) ? current : void 0;
|
|
@@ -146,18 +160,29 @@ function dereference(ref, root) {
|
|
|
146
160
|
/**
|
|
147
161
|
* Recursively scan a schema document for a `$anchor` matching the given name.
|
|
148
162
|
* Returns the schema object containing the anchor, or undefined.
|
|
163
|
+
*
|
|
164
|
+
* The optional `visited` set guards against shared object references and
|
|
165
|
+
* cycles introduced by the OpenAPI bundler's `structuredClone`-based
|
|
166
|
+
* inlining of external refs. Without it a recursive document would stack
|
|
167
|
+
* overflow before reaching the matching anchor.
|
|
149
168
|
*/
|
|
150
|
-
function findAnchor(node, anchorName) {
|
|
169
|
+
function findAnchor(node, anchorName, visited = /* @__PURE__ */ new WeakSet()) {
|
|
151
170
|
if (!isObject(node)) return void 0;
|
|
171
|
+
if (visited.has(node)) return void 0;
|
|
172
|
+
visited.add(node);
|
|
152
173
|
if (node.$anchor === anchorName) return node;
|
|
153
174
|
for (const value of Object.values(node)) {
|
|
154
175
|
if (isObject(value)) {
|
|
155
|
-
const found = findAnchor(value, anchorName);
|
|
176
|
+
const found = findAnchor(value, anchorName, visited);
|
|
156
177
|
if (found !== void 0) return found;
|
|
157
178
|
}
|
|
158
|
-
if (Array.isArray(value))
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
if (visited.has(value)) continue;
|
|
181
|
+
visited.add(value);
|
|
182
|
+
for (const item of value) {
|
|
183
|
+
const found = findAnchor(item, anchorName, visited);
|
|
184
|
+
if (found !== void 0) return found;
|
|
185
|
+
}
|
|
161
186
|
}
|
|
162
187
|
}
|
|
163
188
|
}
|