schema-components 1.21.0 → 1.23.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.
Files changed (91) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +115 -4
  3. package/dist/core/adapter.mjs +405 -75
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -7
  6. package/dist/core/cssClasses.d.mts +52 -0
  7. package/dist/core/cssClasses.mjs +51 -0
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/errors.mjs +5 -13
  11. package/dist/core/fieldOrder.d.mts +1 -1
  12. package/dist/core/formats.d.mts +30 -2
  13. package/dist/core/formats.mjs +33 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/limits.d.mts +2 -0
  17. package/dist/core/limits.mjs +23 -0
  18. package/dist/core/merge.d.mts +10 -1
  19. package/dist/core/merge.mjs +49 -10
  20. package/dist/core/normalise.d.mts +40 -3
  21. package/dist/core/normalise.mjs +2 -2
  22. package/dist/core/openapi30.d.mts +15 -1
  23. package/dist/core/openapi30.mjs +2 -2
  24. package/dist/core/openapiConstants.d.mts +67 -0
  25. package/dist/core/openapiConstants.mjs +90 -0
  26. package/dist/core/ref.d.mts +2 -2
  27. package/dist/core/ref.mjs +85 -6
  28. package/dist/core/refChain.d.mts +70 -0
  29. package/dist/core/refChain.mjs +44 -0
  30. package/dist/core/renderer.d.mts +1 -1
  31. package/dist/core/renderer.mjs +0 -2
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +982 -2
  35. package/dist/core/types.d.mts +2 -2
  36. package/dist/core/types.mjs +1 -4
  37. package/dist/core/unionMatch.d.mts +36 -0
  38. package/dist/core/unionMatch.mjs +53 -0
  39. package/dist/core/version.d.mts +1 -1
  40. package/dist/core/version.mjs +29 -17
  41. package/dist/core/walkBuilders.d.mts +23 -4
  42. package/dist/core/walkBuilders.mjs +27 -7
  43. package/dist/core/walker.d.mts +1 -1
  44. package/dist/core/walker.mjs +123 -47
  45. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  46. package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
  47. package/dist/html/a11y.d.mts +9 -4
  48. package/dist/html/a11y.mjs +10 -12
  49. package/dist/html/renderToHtml.d.mts +10 -3
  50. package/dist/html/renderToHtml.mjs +13 -3
  51. package/dist/html/renderToHtmlStream.d.mts +2 -2
  52. package/dist/html/renderToHtmlStream.mjs +12 -1
  53. package/dist/html/renderers.d.mts +43 -8
  54. package/dist/html/renderers.mjs +136 -116
  55. package/dist/html/streamRenderers.d.mts +6 -6
  56. package/dist/html/streamRenderers.mjs +129 -89
  57. package/dist/limits-Cw5QZND8.d.mts +29 -0
  58. package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
  59. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  60. package/dist/openapi/ApiLinks.d.mts +1 -1
  61. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  62. package/dist/openapi/ApiSecurity.d.mts +1 -1
  63. package/dist/openapi/ApiSecurity.mjs +16 -2
  64. package/dist/openapi/components.d.mts +234 -23
  65. package/dist/openapi/components.mjs +183 -52
  66. package/dist/openapi/parser.d.mts +9 -8
  67. package/dist/openapi/parser.mjs +252 -70
  68. package/dist/openapi/resolve.d.mts +31 -15
  69. package/dist/openapi/resolve.mjs +260 -40
  70. package/dist/react/SchemaComponent.d.mts +126 -36
  71. package/dist/react/SchemaComponent.mjs +95 -57
  72. package/dist/react/SchemaView.d.mts +30 -10
  73. package/dist/react/SchemaView.mjs +2 -2
  74. package/dist/react/a11y.d.mts +21 -0
  75. package/dist/react/a11y.mjs +24 -0
  76. package/dist/react/fieldPath.d.mts +1 -1
  77. package/dist/react/headless.d.mts +1 -1
  78. package/dist/react/headless.mjs +1 -2
  79. package/dist/react/headlessRenderers.d.mts +9 -11
  80. package/dist/react/headlessRenderers.mjs +51 -102
  81. package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  82. package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
  83. package/dist/themes/mantine.d.mts +1 -1
  84. package/dist/themes/mui.d.mts +1 -1
  85. package/dist/themes/radix.d.mts +1 -1
  86. package/dist/themes/shadcn.d.mts +1 -1
  87. package/dist/themes/shadcn.mjs +2 -1
  88. package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
  89. package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
  90. package/package.json +1 -3
  91. package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
@@ -1,13 +1,37 @@
1
1
  import { getProperty, isObject } from "../core/guards.mjs";
2
+ import "../core/limits.mjs";
3
+ import { emitDiagnostic } from "../core/diagnostics.mjs";
2
4
  import { isPrototypePollutingKey } from "../core/uri.mjs";
5
+ import { detectOpenApiVersion } from "../core/version.mjs";
6
+ import { HTTP_METHODS } from "../core/openapiConstants.mjs";
7
+ import { resolveRefChain } from "../core/refChain.mjs";
3
8
  //#region src/openapi/parser.ts
4
9
  function getString(value, key) {
5
10
  const result = isObject(value) ? value[key] : void 0;
6
11
  return typeof result === "string" ? result : void 0;
7
12
  }
8
- function toParameterLocation(value) {
13
+ /**
14
+ * Narrow an OpenAPI parameter `in` value to the canonical four-value
15
+ * `ParameterLocation` union. Returns `undefined` for any other value
16
+ * (including the Swagger 2.0 `body`/`formData` locations, which the
17
+ * Swagger 2.0 → OpenAPI 3.x normaliser is expected to have already
18
+ * lifted out of the parameter array). When `diagnostics` is supplied,
19
+ * emits an `unknown-parameter-location` diagnostic so the caller can
20
+ * audit silent drops; without a sink the function still returns
21
+ * `undefined` rather than coercing the value, so the parameter is
22
+ * excluded from the operation's parameter list either way.
23
+ */
24
+ function toParameterLocation(value, parameterName, pointer, diagnostics) {
9
25
  if (value === "query" || value === "path" || value === "header" || value === "cookie") return value;
10
- return "query";
26
+ emitDiagnostic(diagnostics, {
27
+ code: "unknown-parameter-location",
28
+ message: parameterName !== void 0 ? `Parameter "${parameterName}" declares unknown \`in\` value ${JSON.stringify(value)}; expected one of query, path, header, cookie` : `Parameter declares unknown \`in\` value ${JSON.stringify(value)}; expected one of query, path, header, cookie`,
29
+ pointer,
30
+ detail: {
31
+ name: parameterName,
32
+ in: value
33
+ }
34
+ });
11
35
  }
12
36
  function parseOpenApiDocument(doc) {
13
37
  const schemas = /* @__PURE__ */ new Map();
@@ -29,46 +53,79 @@ function getSchema(parsed, ref) {
29
53
  return resolved;
30
54
  }
31
55
  }
32
- const METHODS = [
33
- "get",
34
- "post",
35
- "put",
36
- "patch",
37
- "delete",
38
- "head",
39
- "options",
40
- "trace"
41
- ];
42
56
  /**
43
- * Resolve a path item, following a `$ref` to `components/pathItems/<Name>`
44
- * (OpenAPI 3.1) if present. Returns `undefined` when the value is not a
45
- * path item, the ref is malformed, or the target does not resolve.
57
+ * Follow Path Item Object `$ref` chains (up to MAX_PATH_ITEM_REF_HOPS).
58
+ * Returns the resolved Path Item, or `undefined` when the chain cycles,
59
+ * exceeds the cap, or any intermediate ref fails to resolve.
60
+ *
61
+ * When `diagnostics` is supplied, cycle and depth-cap conditions emit
62
+ * `cyclic-path-item-ref` and `path-item-ref-too-deep` respectively. When
63
+ * `diagnostics` is omitted, the resolver still rejects cycles and
64
+ * over-deep chains silently — callers that wire their own resolver
65
+ * (notably `resolve.ts:resolvePathItemNode`) supply diagnostics to
66
+ * mirror this behaviour with full pointer information.
46
67
  */
47
- function resolvePathItem(parsed, pathItem) {
68
+ function resolvePathItem(parsed, pathItem, diagnostics) {
48
69
  if (!isObject(pathItem)) return void 0;
49
- const ref = getString(pathItem, "$ref");
50
- if (ref === void 0) return pathItem;
51
- return resolveRefInDoc(parsed.doc, ref) ?? void 0;
70
+ return resolveRefChain(pathItem, {
71
+ lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(parsed.doc, ref) : void 0,
72
+ maxHops: 8,
73
+ onCycle: (ref) => {
74
+ emitDiagnostic(diagnostics, {
75
+ code: "cyclic-path-item-ref",
76
+ message: `Cyclic Path Item Object $ref "${ref}"`,
77
+ pointer: ref,
78
+ detail: { ref }
79
+ });
80
+ },
81
+ onDepthExceeded: (ref) => {
82
+ emitDiagnostic(diagnostics, {
83
+ code: "path-item-ref-too-deep",
84
+ message: `Path Item Object $ref chain exceeded ${String(8)} hops`,
85
+ pointer: ref,
86
+ detail: {
87
+ maxHops: 8,
88
+ ref
89
+ }
90
+ });
91
+ }
92
+ });
52
93
  }
53
94
  function lookupPathItem(parsed, path) {
54
95
  const resolved = resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "paths"), path));
55
96
  if (resolved !== void 0) return resolved;
56
97
  return resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "webhooks"), path));
57
98
  }
58
- function listOperations(parsed) {
99
+ function listOperations(parsed, diagnostics) {
59
100
  const operations = [];
60
101
  const paths = getProperty(parsed.doc, "paths");
61
102
  if (!isObject(paths)) return operations;
103
+ const seenIds = /* @__PURE__ */ new Map();
62
104
  for (const [path, rawPathItem] of Object.entries(paths)) {
63
- const pathItem = resolvePathItem(parsed, rawPathItem);
105
+ const pathItem = resolvePathItem(parsed, rawPathItem, diagnostics);
64
106
  if (pathItem === void 0) continue;
65
- for (const method of METHODS) {
107
+ for (const method of HTTP_METHODS) {
66
108
  const operation = getProperty(pathItem, method);
67
109
  if (!isObject(operation)) continue;
110
+ const operationId = getString(operation, "operationId");
111
+ if (operationId !== void 0) {
112
+ const firstSeenAt = seenIds.get(operationId);
113
+ if (firstSeenAt !== void 0) emitDiagnostic(diagnostics, {
114
+ code: "duplicate-operation-id",
115
+ message: `operationId "${operationId}" is declared more than once (first at ${firstSeenAt}, again at ${method.toUpperCase()} ${path})`,
116
+ pointer: `/paths/${jsonPointerEscape(path)}/${method}/operationId`,
117
+ detail: {
118
+ operationId,
119
+ firstSeenAt,
120
+ duplicateAt: `${method.toUpperCase()} ${path}`
121
+ }
122
+ });
123
+ else seenIds.set(operationId, `${method.toUpperCase()} ${path}`);
124
+ }
68
125
  operations.push({
69
126
  path,
70
127
  method,
71
- operationId: getString(operation, "operationId"),
128
+ operationId,
72
129
  summary: getString(operation, "summary"),
73
130
  description: getString(operation, "description"),
74
131
  deprecated: getProperty(operation, "deprecated") === true,
@@ -78,31 +135,35 @@ function listOperations(parsed) {
78
135
  }
79
136
  return operations;
80
137
  }
81
- function getParameters(parsed, path, method) {
138
+ function getParameters(parsed, path, method, diagnostics) {
82
139
  const pathItem = lookupPathItem(parsed, path);
83
140
  if (pathItem === void 0) return [];
84
141
  const operation = getProperty(pathItem, method);
85
142
  if (!isObject(operation)) return [];
86
- const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"));
87
- const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"));
143
+ const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"), `/paths/${jsonPointerEscape(path)}/parameters`, diagnostics);
144
+ const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"), `/paths/${jsonPointerEscape(path)}/${method}/parameters`, diagnostics);
88
145
  const map = /* @__PURE__ */ new Map();
89
146
  for (const param of pathParams) map.set(`${param.name}:${param.location}`, param);
90
147
  for (const param of opParams) map.set(`${param.name}:${param.location}`, param);
91
148
  return [...map.values()];
92
149
  }
93
- function extractParameterList(doc, parameters) {
150
+ function extractParameterList(doc, parameters, pointerBase, diagnostics) {
94
151
  if (!Array.isArray(parameters)) return [];
95
152
  const result = [];
96
- for (const param of parameters) {
153
+ for (const [index, param] of parameters.entries()) {
97
154
  if (!isObject(param)) continue;
98
- const resolved = resolveParam(doc, param);
155
+ const entryPointer = `${pointerBase}/${String(index)}`;
156
+ const resolved = resolveParam(doc, param, diagnostics);
157
+ if (resolved === void 0) continue;
99
158
  const name = getProperty(resolved, "name");
100
- const location = getProperty(resolved, "in");
101
- if (typeof name !== "string" || typeof location !== "string") continue;
159
+ const rawLocation = getProperty(resolved, "in");
160
+ if (typeof name !== "string" || typeof rawLocation !== "string") continue;
161
+ const location = toParameterLocation(rawLocation, name, `${entryPointer}/in`, diagnostics);
162
+ if (location === void 0) continue;
102
163
  const schema = getProperty(resolved, "schema");
103
164
  result.push({
104
165
  name,
105
- location: toParameterLocation(location),
166
+ location,
106
167
  required: getProperty(resolved, "required") === true,
107
168
  deprecated: getProperty(resolved, "deprecated") === true,
108
169
  description: getString(resolved, "description"),
@@ -111,13 +172,69 @@ function extractParameterList(doc, parameters) {
111
172
  }
112
173
  return result;
113
174
  }
114
- function resolveParam(doc, param) {
115
- const ref = getProperty(param, "$ref");
116
- if (typeof ref === "string" && ref.startsWith("#/")) {
117
- const resolved = resolveRefInDoc(doc, ref);
118
- if (resolved !== void 0) return resolved;
119
- }
120
- return param;
175
+ /**
176
+ * Resolve a Reference Object chain on a non-Path-Item OpenAPI node
177
+ * (Parameter, Header, Link, etc.). Single-hop resolution is insufficient
178
+ * because OAS 3.x permits chains of Reference Objects of arbitrary
179
+ * length a Reference Object whose target is itself a Reference Object
180
+ * is legal. `resolveRefChain` centralises cycle and depth-cap protection.
181
+ *
182
+ * Cycles and over-deep chains reuse the existing `cyclic-path-item-ref`
183
+ * and `path-item-ref-too-deep` diagnostic codes with a
184
+ * `detail.kind: "<node-kind>"` discriminator (e.g. `"parameter"`,
185
+ * `"header"`, `"link"`). There is no dedicated code per node kind in the
186
+ * current `DiagnosticCode` union; consumers filter by `detail.kind` when
187
+ * they care to distinguish the source.
188
+ *
189
+ * TODO(round7-integration): consider adding dedicated `cyclic-*-ref` /
190
+ * `*-ref-too-deep` codes per node kind to `core/diagnostics.ts` so the
191
+ * kind discriminator on `detail` is not needed. The existing Swagger
192
+ * 2.0 path already uses a dedicated `swagger-cyclic-parameter-ref` for
193
+ * the equivalent failure mode.
194
+ */
195
+ function resolveReferenceObjectChain(doc, node, kind, diagnostics) {
196
+ const kindLabel = kind === "parameter" ? "Parameter Object" : kind === "header" ? "Header Object" : "Link Object";
197
+ return resolveRefChain(node, {
198
+ lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(doc, ref) : void 0,
199
+ onCycle: (ref) => {
200
+ emitDiagnostic(diagnostics, {
201
+ code: "cyclic-path-item-ref",
202
+ message: `Cyclic ${kindLabel} $ref "${ref}"`,
203
+ pointer: ref,
204
+ detail: {
205
+ ref,
206
+ kind
207
+ }
208
+ });
209
+ },
210
+ onDepthExceeded: (ref) => {
211
+ emitDiagnostic(diagnostics, {
212
+ code: "path-item-ref-too-deep",
213
+ message: `${kindLabel} $ref chain exceeded the hop cap starting from "${ref}"`,
214
+ pointer: ref,
215
+ detail: {
216
+ ref,
217
+ kind
218
+ }
219
+ });
220
+ }
221
+ });
222
+ }
223
+ /**
224
+ * Resolve a Parameter Object `$ref` chain. See
225
+ * `resolveReferenceObjectChain` for the resolution contract.
226
+ */
227
+ function resolveParam(doc, param, diagnostics) {
228
+ return resolveReferenceObjectChain(doc, param, "parameter", diagnostics);
229
+ }
230
+ /**
231
+ * Encode a path segment for embedding in a JSON Pointer (RFC 6901).
232
+ * `~` → `~0`, `/` → `~1`. Used to build pointer fragments for diagnostics
233
+ * — the diagnostics layer does not currently re-encode segments inserted
234
+ * via template strings.
235
+ */
236
+ function jsonPointerEscape(segment) {
237
+ return segment.replace(/~/g, "~0").replace(/\//g, "~1");
121
238
  }
122
239
  function getRequestBody(parsed, path, method) {
123
240
  const requestBodyRaw = getProperty(getProperty(lookupPathItem(parsed, path), method), "requestBody");
@@ -140,7 +257,7 @@ function getRequestBody(parsed, path, method) {
140
257
  schema
141
258
  };
142
259
  }
143
- function getResponses(parsed, path, method) {
260
+ function getResponses(parsed, path, method, diagnostics) {
144
261
  const responses = getProperty(getProperty(lookupPathItem(parsed, path), method), "responses");
145
262
  if (!isObject(responses)) return [];
146
263
  const result = [];
@@ -151,7 +268,7 @@ function getResponses(parsed, path, method) {
151
268
  const content = getProperty(response, "content");
152
269
  const contentTypes = isObject(content) ? Object.keys(content) : [];
153
270
  const schema = isObject(content) ? extractSchemaFromContent(content) : void 0;
154
- const headers = getResponseHeaders(response, parsed.doc);
271
+ const headers = getResponseHeaders(response, parsed.doc, diagnostics);
155
272
  result.push({
156
273
  statusCode,
157
274
  description: getString(response, "description"),
@@ -169,15 +286,60 @@ function getResponses(parsed, path, method) {
169
286
  * it has no `$ref`, or `undefined` when the `$ref` is malformed or
170
287
  * cannot be resolved (so the caller skips the entry rather than reading
171
288
  * stale fields from the bare `{ $ref }` envelope).
289
+ *
290
+ * OpenAPI 3.1 Reference Object — sibling merge. OAS 3.1 explicitly
291
+ * permits `summary` and `description` siblings of `$ref`; the wrapper's
292
+ * siblings override the corresponding fields on the referenced node
293
+ * (spec: "If the property is present on both the Reference Object and
294
+ * the referenced node, the value on the Reference Object overrides the
295
+ * value of the referenced node"). OAS 3.0 forbids siblings, so the merge
296
+ * is gated on the document version. The gating is best-effort — if no
297
+ * recognisable `openapi`/`swagger` field is present we err on the side
298
+ * of NOT merging siblings to avoid changing behaviour for ambiguous or
299
+ * partially-built documents.
172
300
  */
173
301
  function resolveWrapperRef(doc, wrapper) {
174
302
  const ref = getString(wrapper, "$ref");
175
303
  if (ref === void 0) return wrapper;
176
- return resolveRefInDoc(doc, ref);
304
+ const target = resolveRefInDoc(doc, ref);
305
+ if (target === void 0) return void 0;
306
+ if (!documentAllowsReferenceSiblings(doc)) return target;
307
+ return mergeReferenceSiblings(wrapper, target);
308
+ }
309
+ /**
310
+ * OAS 3.1 admits `summary` and `description` siblings on a Reference
311
+ * Object; OAS 3.0 does not. Detect the document version once per call
312
+ * — `detectOpenApiVersion` reads the top-level `openapi`/`swagger` field
313
+ * and is cheap enough to call on every resolution without caching.
314
+ */
315
+ function documentAllowsReferenceSiblings(doc) {
316
+ const version = detectOpenApiVersion(doc);
317
+ if (version === void 0) return false;
318
+ return version.major === 3 && version.minor >= 1;
319
+ }
320
+ /**
321
+ * Per OAS 3.1, only `summary` and `description` siblings on a Reference
322
+ * Object are permitted and they override the referenced node. Any other
323
+ * sibling is ignored (spec: "Any properties of a Reference Object other
324
+ * than those described above SHALL be ignored"). The returned object is
325
+ * a fresh shallow merge — the input wrapper and target are not mutated.
326
+ */
327
+ const REFERENCE_OBJECT_SIBLING_KEYS = ["summary", "description"];
328
+ function mergeReferenceSiblings(wrapper, target) {
329
+ const merged = { ...target };
330
+ for (const key of REFERENCE_OBJECT_SIBLING_KEYS) {
331
+ const siblingValue = wrapper[key];
332
+ if (typeof siblingValue === "string") merged[key] = siblingValue;
333
+ }
334
+ return merged;
177
335
  }
178
336
  function extractSchemaFromContent(content) {
179
- const jsonSchema = getProperty(getProperty(content, "application/json"), "schema");
180
- if (isObject(jsonSchema)) return jsonSchema;
337
+ for (const [mediaType, mediaObj] of Object.entries(content)) {
338
+ if (mediaTypeBase(mediaType) !== "application/json") continue;
339
+ if (!isObject(mediaObj)) continue;
340
+ const schema = getProperty(mediaObj, "schema");
341
+ if (isObject(schema)) return schema;
342
+ }
181
343
  for (const [mediaType, mediaObj] of Object.entries(content)) {
182
344
  if (!isJsonSuffixMediaType(mediaType)) continue;
183
345
  if (!isObject(mediaObj)) continue;
@@ -191,17 +353,40 @@ function extractSchemaFromContent(content) {
191
353
  }
192
354
  }
193
355
  /**
356
+ * Return the lowercased media-type base — the type/subtype with any
357
+ * RFC 7231 parameters (`; charset=...`, `; profile=...`, etc.) stripped
358
+ * and surrounding whitespace trimmed. Returns the empty string when the
359
+ * input has no recognisable base (defensive against malformed entries).
360
+ */
361
+ function mediaTypeBase(mediaType) {
362
+ return mediaType.toLowerCase().split(";", 1)[0]?.trim() ?? "";
363
+ }
364
+ /**
194
365
  * Detect RFC 6839 structured-syntax-suffix media types that encode JSON.
195
366
  * Matches `application/<anything>+json`, optionally with parameters
196
367
  * (`; charset=utf-8`). Excludes the literal `application/json`, which
197
368
  * the caller checks separately to preserve preference order.
198
369
  */
199
370
  function isJsonSuffixMediaType(mediaType) {
200
- const lower = mediaType.toLowerCase();
201
- if (lower === "application/json") return false;
202
- const baseType = lower.split(";", 1)[0]?.trim() ?? "";
203
- return baseType.startsWith("application/") && baseType.endsWith("+json");
371
+ const base = mediaTypeBase(mediaType);
372
+ if (base === "application/json") return false;
373
+ return base.startsWith("application/") && base.endsWith("+json");
204
374
  }
375
+ /**
376
+ * Resolve an in-document `$ref` against the supplied doc root.
377
+ *
378
+ * Limitation — cross-Schema-Object relative refs: refs that do NOT
379
+ * start with `#/` are not resolved here. The `normaliseOpenApiSchemas`
380
+ * pipeline (see `resolveRelativeRefs` in `core/normalise.ts`) rewrites
381
+ * relative refs WITHIN a Schema Object using that schema's `$id` base
382
+ * URI, but it does not currently model `$id` scopes that span Schema
383
+ * Object boundaries (e.g. a sibling component schema with its own
384
+ * `$id` that another schema's relative ref targets). Such refs survive
385
+ * normalisation unchanged and fall through this function returning
386
+ * `undefined`. `resolve.ts:detectUnsupportedCrossSchemaRefs` walks the
387
+ * normalised doc and emits `cross-schema-relative-ref-unsupported` per
388
+ * offending ref so consumers notice the silent failure.
389
+ */
205
390
  function resolveRefInDoc(doc, ref) {
206
391
  if (!ref.startsWith("#/")) return void 0;
207
392
  const parts = ref.slice(2).split("/");
@@ -248,14 +433,13 @@ function getSecuritySchemes(parsed) {
248
433
  }
249
434
  return result;
250
435
  }
251
- function getResponseHeaders(response, doc) {
436
+ function getResponseHeaders(response, doc, diagnostics) {
252
437
  const result = /* @__PURE__ */ new Map();
253
438
  const headers = getProperty(response, "headers");
254
439
  if (!isObject(headers)) return result;
255
440
  for (const [name, headerObj] of Object.entries(headers)) {
256
441
  if (!isObject(headerObj)) continue;
257
- const ref = getString(headerObj, "$ref");
258
- const header = (ref !== void 0 && doc !== void 0 ? resolveRefInDoc(doc, ref) : void 0) ?? headerObj;
442
+ const header = doc !== void 0 ? resolveReferenceObjectChain(doc, headerObj, "header", diagnostics) ?? headerObj : headerObj;
259
443
  const schemaProp = getProperty(header, "schema");
260
444
  result.set(name, {
261
445
  name,
@@ -267,14 +451,15 @@ function getResponseHeaders(response, doc) {
267
451
  }
268
452
  return result;
269
453
  }
270
- function listWebhooks(parsed) {
454
+ function listWebhooks(parsed, diagnostics) {
271
455
  const result = [];
272
456
  const webhooks = getProperty(parsed.doc, "webhooks");
273
457
  if (!isObject(webhooks)) return result;
274
- for (const [name, hookItem] of Object.entries(webhooks)) {
275
- if (!isObject(hookItem)) continue;
458
+ for (const [name, rawHookItem] of Object.entries(webhooks)) {
459
+ const hookItem = resolvePathItem(parsed, rawHookItem, diagnostics);
460
+ if (hookItem === void 0) continue;
276
461
  const operations = [];
277
- for (const method of METHODS) {
462
+ for (const method of HTTP_METHODS) {
278
463
  const operation = getProperty(hookItem, method);
279
464
  if (!isObject(operation)) continue;
280
465
  operations.push({
@@ -315,7 +500,7 @@ function getXmlInfo(schema) {
315
500
  wrapped: getProperty(xml, "wrapped") === true
316
501
  };
317
502
  }
318
- function listCallbacks(parsed, path, method) {
503
+ function listCallbacks(parsed, path, method, diagnostics) {
319
504
  const operation = getProperty(lookupPathItem(parsed, path), method);
320
505
  if (!isObject(operation)) return [];
321
506
  const callbacks = getProperty(operation, "callbacks");
@@ -324,21 +509,20 @@ function listCallbacks(parsed, path, method) {
324
509
  for (const [name, callbackItem] of Object.entries(callbacks)) {
325
510
  if (!isObject(callbackItem)) continue;
326
511
  const operations = [];
327
- for (const [cbPath, cbPathItem] of Object.entries(callbackItem)) {
328
- if (!isObject(cbPathItem)) continue;
329
- for (const cbMethod of METHODS) {
512
+ for (const [cbPath, rawCbPathItem] of Object.entries(callbackItem)) {
513
+ const cbPathItem = resolvePathItem(parsed, rawCbPathItem, diagnostics);
514
+ if (cbPathItem === void 0) continue;
515
+ for (const cbMethod of HTTP_METHODS) {
330
516
  const cbOp = getProperty(cbPathItem, cbMethod);
331
517
  if (!isObject(cbOp)) continue;
332
- const ref = getString(cbOp, "$ref");
333
- const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? cbOp : cbOp;
334
518
  operations.push({
335
519
  path: `${name}/${cbPath}`,
336
520
  method: cbMethod,
337
- operationId: getString(resolved, "operationId"),
338
- summary: getString(resolved, "summary"),
339
- description: getString(resolved, "description"),
340
- deprecated: getProperty(resolved, "deprecated") === true,
341
- operation: isObject(resolved) ? resolved : cbOp
521
+ operationId: getString(cbOp, "operationId"),
522
+ summary: getString(cbOp, "summary"),
523
+ description: getString(cbOp, "description"),
524
+ deprecated: getProperty(cbOp, "deprecated") === true,
525
+ operation: cbOp
342
526
  });
343
527
  }
344
528
  }
@@ -349,7 +533,7 @@ function listCallbacks(parsed, path, method) {
349
533
  }
350
534
  return result;
351
535
  }
352
- function getLinks(parsed, path, method, statusCode) {
536
+ function getLinks(parsed, path, method, statusCode, diagnostics) {
353
537
  const response = getProperty(getProperty(getProperty(lookupPathItem(parsed, path), method), "responses"), statusCode);
354
538
  if (!isObject(response)) return [];
355
539
  const links = getProperty(response, "links");
@@ -357,9 +541,7 @@ function getLinks(parsed, path, method, statusCode) {
357
541
  const result = [];
358
542
  for (const [name, linkObj] of Object.entries(links)) {
359
543
  if (!isObject(linkObj)) continue;
360
- const ref = getString(linkObj, "$ref");
361
- const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? linkObj : linkObj;
362
- const link = isObject(resolved) ? resolved : linkObj;
544
+ const link = resolveReferenceObjectChain(parsed.doc, linkObj, "link", diagnostics) ?? linkObj;
363
545
  const params = getProperty(link, "parameters");
364
546
  const paramMap = /* @__PURE__ */ new Map();
365
547
  if (isObject(params)) {
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
2
2
  import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequestBody } from "./parser.mjs";
3
3
 
4
4
  //#region src/openapi/resolve.d.ts
@@ -15,24 +15,40 @@ import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequest
15
15
  * same form `<SchemaComponent>` does, keeping the OpenAPI components on
16
16
  * the same pipeline as the top-level adapter.
17
17
  *
18
- * When `diagnostics` is supplied, normalisation events
19
- * (`duplicate-body-parameter`, `dropped-swagger-feature`,
20
- * `unknown-json-schema-dialect`, `divisible-by-conflict`,
21
- * `relative-ref-resolved`, etc.) are forwarded to the sink. Passing
22
- * diagnostics also bypasses the cache so each call observes the
23
- * normalisation pipeline running against the supplied sink — caching
24
- * would silently swallow every emission after the first.
18
+ * ### Caching and diagnostics
25
19
  *
26
- * The cache is keyed by the caller-supplied document so subsequent
27
- * cache-eligible calls with the same input bypass both normalisation
28
- * and parsing.
20
+ * Normalisation runs at most once per document identity. The full set
21
+ * of doc-level diagnostics emitted during that single run is captured
22
+ * into the cache alongside the parsed result. Each caller-supplied
23
+ * sink receives the captured diagnostics exactly once per cached
24
+ * entry, no matter how many times `getParsed` is called with that
25
+ * `(doc, sink)` pair.
26
+ *
27
+ * The previous implementation bypassed the cache whenever
28
+ * `diagnostics` was supplied and re-ran the entire normalisation
29
+ * pipeline against the new sink. That fired every doc-level
30
+ * diagnostic once per call, so a parent like `ApiWebhooks` that
31
+ * renders `ApiWebhook` per webhook entry caused N-fold emission of a
32
+ * single real cause. With the new strategy, cardinality stays at one
33
+ * per real cause regardless of how many child renders share the
34
+ * sink.
35
+ *
36
+ * Strict mode is treated as a per-call invariant — see
37
+ * {@link replayCapturedDiagnostics} for the rationale.
29
38
  */
30
39
  declare function getParsed(doc: Record<string, unknown>, diagnostics?: DiagnosticsOptions): OpenApiDocument;
31
40
  /**
32
- * Coerce an unknown value to a record, returning an empty record
33
- * for non-objects.
41
+ * Coerce an unknown value to a record, returning `undefined` when the
42
+ * value is not a plain object. Callers MUST handle the `undefined` case
43
+ * explicitly — typically by rendering a "doc not an object" diagnostic
44
+ * and short-circuiting, never by silently substituting `{}`.
45
+ *
46
+ * A previous implementation fell back to `{}` for non-objects, which
47
+ * masked configuration mistakes (passing a string, `null`, an array, or
48
+ * `undefined` as the OpenAPI document) as an empty document with no
49
+ * operations.
34
50
  */
35
- declare function toDoc(value: unknown): Record<string, unknown>;
51
+ declare function toDoc(value: unknown): Record<string, unknown> | undefined;
36
52
  /**
37
53
  * Path-Item-level metadata. OpenAPI 3.1 added `summary` and `description`
38
54
  * to Path Item Objects alongside the existing operation-level fields.
@@ -59,7 +75,7 @@ interface ResolvedOperation {
59
75
  * normalisation pipeline (every re-run would emit each diagnostic
60
76
  * again into the sink).
61
77
  */
62
- declare function resolveOperationFromParsed(parsed: OpenApiDocument, path: string, method: string): ResolvedOperation;
78
+ declare function resolveOperationFromParsed(parsed: OpenApiDocument, path: string, method: string, diagnostics?: DiagnosticsOptions): ResolvedOperation;
63
79
  /**
64
80
  * Resolve an operation from an OpenAPI document by path and method.
65
81
  * Throws if the operation is not found.