schema-components 1.22.0 → 1.24.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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +97 -3
  3. package/dist/core/adapter.mjs +260 -111
  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 +9 -2
  13. package/dist/core/formats.mjs +12 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/merge.d.mts +10 -1
  17. package/dist/core/merge.mjs +49 -10
  18. package/dist/core/normalise.d.mts +14 -3
  19. package/dist/core/normalise.mjs +2 -2
  20. package/dist/core/openapi30.d.mts +15 -1
  21. package/dist/core/openapi30.mjs +2 -2
  22. package/dist/core/openapiConstants.d.mts +67 -0
  23. package/dist/core/openapiConstants.mjs +90 -0
  24. package/dist/core/ref.d.mts +2 -2
  25. package/dist/core/ref.mjs +83 -6
  26. package/dist/core/refChain.d.mts +70 -0
  27. package/dist/core/refChain.mjs +44 -0
  28. package/dist/core/renderer.d.mts +1 -1
  29. package/dist/core/swagger2.d.mts +1 -1
  30. package/dist/core/swagger2.mjs +1 -1
  31. package/dist/core/typeInference.d.mts +982 -2
  32. package/dist/core/types.d.mts +1 -1
  33. package/dist/core/unionMatch.d.mts +36 -0
  34. package/dist/core/unionMatch.mjs +53 -0
  35. package/dist/core/version.d.mts +1 -1
  36. package/dist/core/version.mjs +29 -17
  37. package/dist/core/walkBuilders.d.mts +23 -4
  38. package/dist/core/walkBuilders.mjs +27 -7
  39. package/dist/core/walker.d.mts +1 -1
  40. package/dist/core/walker.mjs +44 -45
  41. package/dist/{diagnostics-D0QCYGv0.d.mts → diagnostics-Cbwak-ZX.d.mts} +1 -1
  42. package/dist/{errors-DpFwqs5C.d.mts → errors-g_MCTQel.d.mts} +9 -15
  43. package/dist/html/a11y.d.mts +9 -4
  44. package/dist/html/a11y.mjs +10 -19
  45. package/dist/html/renderToHtml.d.mts +2 -2
  46. package/dist/html/renderToHtmlStream.d.mts +2 -2
  47. package/dist/html/renderToHtmlStream.mjs +12 -1
  48. package/dist/html/renderers.d.mts +32 -8
  49. package/dist/html/renderers.mjs +125 -111
  50. package/dist/html/streamRenderers.d.mts +4 -5
  51. package/dist/html/streamRenderers.mjs +40 -61
  52. package/dist/{normalise-DVEJQmF7.mjs → normalise-DCYp06Sr.mjs} +352 -162
  53. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  54. package/dist/openapi/ApiLinks.d.mts +1 -1
  55. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  56. package/dist/openapi/ApiSecurity.d.mts +1 -1
  57. package/dist/openapi/components.d.mts +116 -37
  58. package/dist/openapi/components.mjs +54 -37
  59. package/dist/openapi/parser.d.mts +19 -9
  60. package/dist/openapi/parser.mjs +262 -86
  61. package/dist/openapi/resolve.d.mts +20 -11
  62. package/dist/openapi/resolve.mjs +135 -75
  63. package/dist/react/SchemaComponent.d.mts +32 -7
  64. package/dist/react/SchemaComponent.mjs +45 -21
  65. package/dist/react/SchemaView.d.mts +30 -10
  66. package/dist/react/a11y.d.mts +21 -0
  67. package/dist/react/a11y.mjs +24 -0
  68. package/dist/react/fieldPath.d.mts +1 -1
  69. package/dist/react/headless.d.mts +1 -1
  70. package/dist/react/headlessRenderers.d.mts +8 -9
  71. package/dist/react/headlessRenderers.mjs +41 -72
  72. package/dist/{ref-D-_JBZkF.d.mts → ref-DCDuswPe.d.mts} +38 -3
  73. package/dist/{renderer-BaRlQIuN.d.mts → renderer-CXJ8y0qw.d.mts} +1 -1
  74. package/dist/themes/mantine.d.mts +1 -1
  75. package/dist/themes/mui.d.mts +1 -1
  76. package/dist/themes/radix.d.mts +1 -1
  77. package/dist/themes/shadcn.d.mts +1 -1
  78. package/dist/themes/shadcn.mjs +2 -1
  79. package/dist/{types-BrRMV0en.d.mts → types-BTB73MB8.d.mts} +32 -4
  80. package/dist/{version-D2jfdX6E.d.mts → version-BFTVLsdb.d.mts} +7 -1
  81. package/package.json +1 -1
  82. package/dist/typeInference-DkcUHfaM.d.mts +0 -982
@@ -1,14 +1,37 @@
1
1
  import { getProperty, isObject } from "../core/guards.mjs";
2
2
  import "../core/limits.mjs";
3
+ import { emitDiagnostic } from "../core/diagnostics.mjs";
3
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";
4
8
  //#region src/openapi/parser.ts
5
9
  function getString(value, key) {
6
10
  const result = isObject(value) ? value[key] : void 0;
7
11
  return typeof result === "string" ? result : void 0;
8
12
  }
9
- 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) {
10
25
  if (value === "query" || value === "path" || value === "header" || value === "cookie") return value;
11
- 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
+ });
12
35
  }
13
36
  function parseOpenApiDocument(doc) {
14
37
  const schemas = /* @__PURE__ */ new Map();
@@ -30,62 +53,94 @@ function getSchema(parsed, ref) {
30
53
  return resolved;
31
54
  }
32
55
  }
33
- const METHODS = [
34
- "get",
35
- "post",
36
- "put",
37
- "patch",
38
- "delete",
39
- "head",
40
- "options",
41
- "trace"
42
- ];
43
- /**
44
- * Resolve a path item, following a `$ref` to `components/pathItems/<Name>`
45
- * (OpenAPI 3.1) if present. Returns `undefined` when the value is not a
46
- * path item, the ref is malformed, or the target does not resolve.
47
- */
48
56
  /**
49
57
  * Follow Path Item Object `$ref` chains (up to MAX_PATH_ITEM_REF_HOPS).
50
58
  * Returns the resolved Path Item, or `undefined` when the chain cycles,
51
- * exceeds the cap, or any intermediate ref fails to resolve. The
52
- * detailed diagnostics for cycle and depth-cap are emitted by the
53
- * mirroring resolver in `resolve.ts` this parser-side resolver simply
54
- * stops walking.
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.
55
67
  */
56
- function resolvePathItem(parsed, pathItem) {
68
+ function resolvePathItem(parsed, pathItem, diagnostics) {
57
69
  if (!isObject(pathItem)) return void 0;
58
- const visited = /* @__PURE__ */ new Set();
59
- let current = pathItem;
60
- for (let hop = 0; hop < 8; hop++) {
61
- const ref = getString(current, "$ref");
62
- if (ref === void 0) return current;
63
- if (visited.has(ref)) return void 0;
64
- visited.add(ref);
65
- const target = resolveRefInDoc(parsed.doc, ref);
66
- if (target === void 0) return void 0;
67
- current = target;
68
- }
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
+ });
69
93
  }
70
94
  function lookupPathItem(parsed, path) {
71
95
  const resolved = resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "paths"), path));
72
96
  if (resolved !== void 0) return resolved;
73
97
  return resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "webhooks"), path));
74
98
  }
75
- function listOperations(parsed) {
99
+ /**
100
+ * Record an `operationId` against a shared `seenIds` map and emit a
101
+ * `duplicate-operation-id` diagnostic when a subsequent location reuses
102
+ * the same identifier. Returns the original `operationId` so the caller
103
+ * can pass the value straight onto its `OperationInfo`.
104
+ *
105
+ * When the same map is threaded through `listOperations` and
106
+ * `listWebhooks` (see `listAllOperations`), cross-list collisions
107
+ * between a path operation and a webhook operation surface as the same
108
+ * diagnostic class as same-list collisions.
109
+ */
110
+ function recordOperationId(operationId, location, pointer, seenIds, diagnostics) {
111
+ if (operationId === void 0) return;
112
+ const firstSeenAt = seenIds.get(operationId);
113
+ if (firstSeenAt !== void 0) {
114
+ emitDiagnostic(diagnostics, {
115
+ code: "duplicate-operation-id",
116
+ message: `operationId "${operationId}" is declared more than once (first at ${firstSeenAt}, again at ${location})`,
117
+ pointer,
118
+ detail: {
119
+ operationId,
120
+ firstSeenAt,
121
+ duplicateAt: location
122
+ }
123
+ });
124
+ return;
125
+ }
126
+ seenIds.set(operationId, location);
127
+ }
128
+ function listOperations(parsed, diagnostics, seenIds = /* @__PURE__ */ new Map()) {
76
129
  const operations = [];
77
130
  const paths = getProperty(parsed.doc, "paths");
78
131
  if (!isObject(paths)) return operations;
79
132
  for (const [path, rawPathItem] of Object.entries(paths)) {
80
- const pathItem = resolvePathItem(parsed, rawPathItem);
133
+ const pathItem = resolvePathItem(parsed, rawPathItem, diagnostics);
81
134
  if (pathItem === void 0) continue;
82
- for (const method of METHODS) {
135
+ for (const method of HTTP_METHODS) {
83
136
  const operation = getProperty(pathItem, method);
84
137
  if (!isObject(operation)) continue;
138
+ const operationId = getString(operation, "operationId");
139
+ recordOperationId(operationId, `${method.toUpperCase()} ${path}`, `/paths/${jsonPointerEscape(path)}/${method}/operationId`, seenIds, diagnostics);
85
140
  operations.push({
86
141
  path,
87
142
  method,
88
- operationId: getString(operation, "operationId"),
143
+ operationId,
89
144
  summary: getString(operation, "summary"),
90
145
  description: getString(operation, "description"),
91
146
  deprecated: getProperty(operation, "deprecated") === true,
@@ -95,31 +150,35 @@ function listOperations(parsed) {
95
150
  }
96
151
  return operations;
97
152
  }
98
- function getParameters(parsed, path, method) {
153
+ function getParameters(parsed, path, method, diagnostics) {
99
154
  const pathItem = lookupPathItem(parsed, path);
100
155
  if (pathItem === void 0) return [];
101
156
  const operation = getProperty(pathItem, method);
102
157
  if (!isObject(operation)) return [];
103
- const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"));
104
- const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"));
158
+ const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"), `/paths/${jsonPointerEscape(path)}/parameters`, diagnostics);
159
+ const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"), `/paths/${jsonPointerEscape(path)}/${method}/parameters`, diagnostics);
105
160
  const map = /* @__PURE__ */ new Map();
106
161
  for (const param of pathParams) map.set(`${param.name}:${param.location}`, param);
107
162
  for (const param of opParams) map.set(`${param.name}:${param.location}`, param);
108
163
  return [...map.values()];
109
164
  }
110
- function extractParameterList(doc, parameters) {
165
+ function extractParameterList(doc, parameters, pointerBase, diagnostics) {
111
166
  if (!Array.isArray(parameters)) return [];
112
167
  const result = [];
113
- for (const param of parameters) {
168
+ for (const [index, param] of parameters.entries()) {
114
169
  if (!isObject(param)) continue;
115
- const resolved = resolveParam(doc, param);
170
+ const entryPointer = `${pointerBase}/${String(index)}`;
171
+ const resolved = resolveParam(doc, param, diagnostics);
172
+ if (resolved === void 0) continue;
116
173
  const name = getProperty(resolved, "name");
117
- const location = getProperty(resolved, "in");
118
- if (typeof name !== "string" || typeof location !== "string") continue;
174
+ const rawLocation = getProperty(resolved, "in");
175
+ if (typeof name !== "string" || typeof rawLocation !== "string") continue;
176
+ const location = toParameterLocation(rawLocation, name, `${entryPointer}/in`, diagnostics);
177
+ if (location === void 0) continue;
119
178
  const schema = getProperty(resolved, "schema");
120
179
  result.push({
121
180
  name,
122
- location: toParameterLocation(location),
181
+ location,
123
182
  required: getProperty(resolved, "required") === true,
124
183
  deprecated: getProperty(resolved, "deprecated") === true,
125
184
  description: getString(resolved, "description"),
@@ -128,13 +187,64 @@ function extractParameterList(doc, parameters) {
128
187
  }
129
188
  return result;
130
189
  }
131
- function resolveParam(doc, param) {
132
- const ref = getProperty(param, "$ref");
133
- if (typeof ref === "string" && ref.startsWith("#/")) {
134
- const resolved = resolveRefInDoc(doc, ref);
135
- if (resolved !== void 0) return resolved;
136
- }
137
- return param;
190
+ /**
191
+ * Resolve a Reference Object chain on a non-Path-Item OpenAPI node
192
+ * (Parameter, Header, Link, etc.). Single-hop resolution is insufficient
193
+ * because OAS 3.x permits chains of Reference Objects of arbitrary
194
+ * length a Reference Object whose target is itself a Reference Object
195
+ * is legal. `resolveRefChain` centralises cycle and depth-cap protection.
196
+ *
197
+ * Cycles and over-deep chains emit a dedicated diagnostic code per node
198
+ * kind (`cyclic-parameter-ref`, `parameter-ref-too-deep`, and the
199
+ * `header` / `link` equivalents), mirroring the existing
200
+ * `swagger-cyclic-parameter-ref` precedent so consumers can pattern-match
201
+ * directly on the code instead of filtering by `detail.kind`.
202
+ */
203
+ function resolveReferenceObjectChain(doc, node, kind, diagnostics) {
204
+ const kindLabel = kind === "parameter" ? "Parameter Object" : kind === "header" ? "Header Object" : "Link Object";
205
+ const cyclicCode = kind === "parameter" ? "cyclic-parameter-ref" : kind === "header" ? "cyclic-header-ref" : "cyclic-link-ref";
206
+ const tooDeepCode = kind === "parameter" ? "parameter-ref-too-deep" : kind === "header" ? "header-ref-too-deep" : "link-ref-too-deep";
207
+ return resolveRefChain(node, {
208
+ lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(doc, ref) : void 0,
209
+ onCycle: (ref) => {
210
+ emitDiagnostic(diagnostics, {
211
+ code: cyclicCode,
212
+ message: `Cyclic ${kindLabel} $ref "${ref}"`,
213
+ pointer: ref,
214
+ detail: {
215
+ ref,
216
+ kind
217
+ }
218
+ });
219
+ },
220
+ onDepthExceeded: (ref) => {
221
+ emitDiagnostic(diagnostics, {
222
+ code: tooDeepCode,
223
+ message: `${kindLabel} $ref chain exceeded the hop cap starting from "${ref}"`,
224
+ pointer: ref,
225
+ detail: {
226
+ ref,
227
+ kind
228
+ }
229
+ });
230
+ }
231
+ });
232
+ }
233
+ /**
234
+ * Resolve a Parameter Object `$ref` chain. See
235
+ * `resolveReferenceObjectChain` for the resolution contract.
236
+ */
237
+ function resolveParam(doc, param, diagnostics) {
238
+ return resolveReferenceObjectChain(doc, param, "parameter", diagnostics);
239
+ }
240
+ /**
241
+ * Encode a path segment for embedding in a JSON Pointer (RFC 6901).
242
+ * `~` → `~0`, `/` → `~1`. Used to build pointer fragments for diagnostics
243
+ * — the diagnostics layer does not currently re-encode segments inserted
244
+ * via template strings.
245
+ */
246
+ function jsonPointerEscape(segment) {
247
+ return segment.replace(/~/g, "~0").replace(/\//g, "~1");
138
248
  }
139
249
  function getRequestBody(parsed, path, method) {
140
250
  const requestBodyRaw = getProperty(getProperty(lookupPathItem(parsed, path), method), "requestBody");
@@ -157,7 +267,7 @@ function getRequestBody(parsed, path, method) {
157
267
  schema
158
268
  };
159
269
  }
160
- function getResponses(parsed, path, method) {
270
+ function getResponses(parsed, path, method, diagnostics) {
161
271
  const responses = getProperty(getProperty(lookupPathItem(parsed, path), method), "responses");
162
272
  if (!isObject(responses)) return [];
163
273
  const result = [];
@@ -168,7 +278,7 @@ function getResponses(parsed, path, method) {
168
278
  const content = getProperty(response, "content");
169
279
  const contentTypes = isObject(content) ? Object.keys(content) : [];
170
280
  const schema = isObject(content) ? extractSchemaFromContent(content) : void 0;
171
- const headers = getResponseHeaders(response, parsed.doc);
281
+ const headers = getResponseHeaders(response, parsed.doc, diagnostics);
172
282
  result.push({
173
283
  statusCode,
174
284
  description: getString(response, "description"),
@@ -186,15 +296,60 @@ function getResponses(parsed, path, method) {
186
296
  * it has no `$ref`, or `undefined` when the `$ref` is malformed or
187
297
  * cannot be resolved (so the caller skips the entry rather than reading
188
298
  * stale fields from the bare `{ $ref }` envelope).
299
+ *
300
+ * OpenAPI 3.1 Reference Object — sibling merge. OAS 3.1 explicitly
301
+ * permits `summary` and `description` siblings of `$ref`; the wrapper's
302
+ * siblings override the corresponding fields on the referenced node
303
+ * (spec: "If the property is present on both the Reference Object and
304
+ * the referenced node, the value on the Reference Object overrides the
305
+ * value of the referenced node"). OAS 3.0 forbids siblings, so the merge
306
+ * is gated on the document version. The gating is best-effort — if no
307
+ * recognisable `openapi`/`swagger` field is present we err on the side
308
+ * of NOT merging siblings to avoid changing behaviour for ambiguous or
309
+ * partially-built documents.
189
310
  */
190
311
  function resolveWrapperRef(doc, wrapper) {
191
312
  const ref = getString(wrapper, "$ref");
192
313
  if (ref === void 0) return wrapper;
193
- return resolveRefInDoc(doc, ref);
314
+ const target = resolveRefInDoc(doc, ref);
315
+ if (target === void 0) return void 0;
316
+ if (!documentAllowsReferenceSiblings(doc)) return target;
317
+ return mergeReferenceSiblings(wrapper, target);
318
+ }
319
+ /**
320
+ * OAS 3.1 admits `summary` and `description` siblings on a Reference
321
+ * Object; OAS 3.0 does not. Detect the document version once per call
322
+ * — `detectOpenApiVersion` reads the top-level `openapi`/`swagger` field
323
+ * and is cheap enough to call on every resolution without caching.
324
+ */
325
+ function documentAllowsReferenceSiblings(doc) {
326
+ const version = detectOpenApiVersion(doc);
327
+ if (version === void 0) return false;
328
+ return version.major === 3 && version.minor >= 1;
329
+ }
330
+ /**
331
+ * Per OAS 3.1, only `summary` and `description` siblings on a Reference
332
+ * Object are permitted and they override the referenced node. Any other
333
+ * sibling is ignored (spec: "Any properties of a Reference Object other
334
+ * than those described above SHALL be ignored"). The returned object is
335
+ * a fresh shallow merge — the input wrapper and target are not mutated.
336
+ */
337
+ const REFERENCE_OBJECT_SIBLING_KEYS = ["summary", "description"];
338
+ function mergeReferenceSiblings(wrapper, target) {
339
+ const merged = { ...target };
340
+ for (const key of REFERENCE_OBJECT_SIBLING_KEYS) {
341
+ const siblingValue = wrapper[key];
342
+ if (typeof siblingValue === "string") merged[key] = siblingValue;
343
+ }
344
+ return merged;
194
345
  }
195
346
  function extractSchemaFromContent(content) {
196
- const jsonSchema = getProperty(getProperty(content, "application/json"), "schema");
197
- if (isObject(jsonSchema)) return jsonSchema;
347
+ for (const [mediaType, mediaObj] of Object.entries(content)) {
348
+ if (mediaTypeBase(mediaType) !== "application/json") continue;
349
+ if (!isObject(mediaObj)) continue;
350
+ const schema = getProperty(mediaObj, "schema");
351
+ if (isObject(schema)) return schema;
352
+ }
198
353
  for (const [mediaType, mediaObj] of Object.entries(content)) {
199
354
  if (!isJsonSuffixMediaType(mediaType)) continue;
200
355
  if (!isObject(mediaObj)) continue;
@@ -208,16 +363,24 @@ function extractSchemaFromContent(content) {
208
363
  }
209
364
  }
210
365
  /**
366
+ * Return the lowercased media-type base — the type/subtype with any
367
+ * RFC 7231 parameters (`; charset=...`, `; profile=...`, etc.) stripped
368
+ * and surrounding whitespace trimmed. Returns the empty string when the
369
+ * input has no recognisable base (defensive against malformed entries).
370
+ */
371
+ function mediaTypeBase(mediaType) {
372
+ return mediaType.toLowerCase().split(";", 1)[0]?.trim() ?? "";
373
+ }
374
+ /**
211
375
  * Detect RFC 6839 structured-syntax-suffix media types that encode JSON.
212
376
  * Matches `application/<anything>+json`, optionally with parameters
213
377
  * (`; charset=utf-8`). Excludes the literal `application/json`, which
214
378
  * the caller checks separately to preserve preference order.
215
379
  */
216
380
  function isJsonSuffixMediaType(mediaType) {
217
- const lower = mediaType.toLowerCase();
218
- if (lower === "application/json") return false;
219
- const baseType = lower.split(";", 1)[0]?.trim() ?? "";
220
- return baseType.startsWith("application/") && baseType.endsWith("+json");
381
+ const base = mediaTypeBase(mediaType);
382
+ if (base === "application/json") return false;
383
+ return base.startsWith("application/") && base.endsWith("+json");
221
384
  }
222
385
  /**
223
386
  * Resolve an in-document `$ref` against the supplied doc root.
@@ -280,14 +443,13 @@ function getSecuritySchemes(parsed) {
280
443
  }
281
444
  return result;
282
445
  }
283
- function getResponseHeaders(response, doc) {
446
+ function getResponseHeaders(response, doc, diagnostics) {
284
447
  const result = /* @__PURE__ */ new Map();
285
448
  const headers = getProperty(response, "headers");
286
449
  if (!isObject(headers)) return result;
287
450
  for (const [name, headerObj] of Object.entries(headers)) {
288
451
  if (!isObject(headerObj)) continue;
289
- const ref = getString(headerObj, "$ref");
290
- const header = (ref !== void 0 && doc !== void 0 ? resolveRefInDoc(doc, ref) : void 0) ?? headerObj;
452
+ const header = doc !== void 0 ? resolveReferenceObjectChain(doc, headerObj, "header", diagnostics) ?? headerObj : headerObj;
291
453
  const schemaProp = getProperty(header, "schema");
292
454
  result.set(name, {
293
455
  name,
@@ -299,20 +461,23 @@ function getResponseHeaders(response, doc) {
299
461
  }
300
462
  return result;
301
463
  }
302
- function listWebhooks(parsed) {
464
+ function listWebhooks(parsed, diagnostics, seenIds = /* @__PURE__ */ new Map()) {
303
465
  const result = [];
304
466
  const webhooks = getProperty(parsed.doc, "webhooks");
305
467
  if (!isObject(webhooks)) return result;
306
- for (const [name, hookItem] of Object.entries(webhooks)) {
307
- if (!isObject(hookItem)) continue;
468
+ for (const [name, rawHookItem] of Object.entries(webhooks)) {
469
+ const hookItem = resolvePathItem(parsed, rawHookItem, diagnostics);
470
+ if (hookItem === void 0) continue;
308
471
  const operations = [];
309
- for (const method of METHODS) {
472
+ for (const method of HTTP_METHODS) {
310
473
  const operation = getProperty(hookItem, method);
311
474
  if (!isObject(operation)) continue;
475
+ const operationId = getString(operation, "operationId");
476
+ recordOperationId(operationId, `${method.toUpperCase()} webhook:${name}`, `/webhooks/${jsonPointerEscape(name)}/${method}/operationId`, seenIds, diagnostics);
312
477
  operations.push({
313
478
  path: name,
314
479
  method,
315
- operationId: getString(operation, "operationId"),
480
+ operationId,
316
481
  summary: getString(operation, "summary"),
317
482
  description: getString(operation, "description"),
318
483
  deprecated: getProperty(operation, "deprecated") === true,
@@ -326,6 +491,20 @@ function listWebhooks(parsed) {
326
491
  }
327
492
  return result;
328
493
  }
494
+ /**
495
+ * Enumerate every operation in the document — both the `paths` map and
496
+ * the OpenAPI 3.1 `webhooks` map — sharing a single `seenIds` cache so
497
+ * cross-list `operationId` collisions surface the same way as same-list
498
+ * collisions. Returns the path-operation list followed by webhook
499
+ * operations (flattened); callers that need the structured webhook
500
+ * grouping should call `listWebhooks` directly.
501
+ */
502
+ function listAllOperations(parsed, diagnostics) {
503
+ const seenIds = /* @__PURE__ */ new Map();
504
+ const pathOps = listOperations(parsed, diagnostics, seenIds);
505
+ const webhookOps = listWebhooks(parsed, diagnostics, seenIds).flatMap((w) => w.operations);
506
+ return [...pathOps, ...webhookOps];
507
+ }
329
508
  function getExternalDocs(obj) {
330
509
  const docs = getProperty(obj, "externalDocs");
331
510
  if (!isObject(docs)) return void 0;
@@ -347,7 +526,7 @@ function getXmlInfo(schema) {
347
526
  wrapped: getProperty(xml, "wrapped") === true
348
527
  };
349
528
  }
350
- function listCallbacks(parsed, path, method) {
529
+ function listCallbacks(parsed, path, method, diagnostics) {
351
530
  const operation = getProperty(lookupPathItem(parsed, path), method);
352
531
  if (!isObject(operation)) return [];
353
532
  const callbacks = getProperty(operation, "callbacks");
@@ -356,21 +535,20 @@ function listCallbacks(parsed, path, method) {
356
535
  for (const [name, callbackItem] of Object.entries(callbacks)) {
357
536
  if (!isObject(callbackItem)) continue;
358
537
  const operations = [];
359
- for (const [cbPath, cbPathItem] of Object.entries(callbackItem)) {
360
- if (!isObject(cbPathItem)) continue;
361
- for (const cbMethod of METHODS) {
538
+ for (const [cbPath, rawCbPathItem] of Object.entries(callbackItem)) {
539
+ const cbPathItem = resolvePathItem(parsed, rawCbPathItem, diagnostics);
540
+ if (cbPathItem === void 0) continue;
541
+ for (const cbMethod of HTTP_METHODS) {
362
542
  const cbOp = getProperty(cbPathItem, cbMethod);
363
543
  if (!isObject(cbOp)) continue;
364
- const ref = getString(cbOp, "$ref");
365
- const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? cbOp : cbOp;
366
544
  operations.push({
367
545
  path: `${name}/${cbPath}`,
368
546
  method: cbMethod,
369
- operationId: getString(resolved, "operationId"),
370
- summary: getString(resolved, "summary"),
371
- description: getString(resolved, "description"),
372
- deprecated: getProperty(resolved, "deprecated") === true,
373
- operation: isObject(resolved) ? resolved : cbOp
547
+ operationId: getString(cbOp, "operationId"),
548
+ summary: getString(cbOp, "summary"),
549
+ description: getString(cbOp, "description"),
550
+ deprecated: getProperty(cbOp, "deprecated") === true,
551
+ operation: cbOp
374
552
  });
375
553
  }
376
554
  }
@@ -381,7 +559,7 @@ function listCallbacks(parsed, path, method) {
381
559
  }
382
560
  return result;
383
561
  }
384
- function getLinks(parsed, path, method, statusCode) {
562
+ function getLinks(parsed, path, method, statusCode, diagnostics) {
385
563
  const response = getProperty(getProperty(getProperty(lookupPathItem(parsed, path), method), "responses"), statusCode);
386
564
  if (!isObject(response)) return [];
387
565
  const links = getProperty(response, "links");
@@ -389,9 +567,7 @@ function getLinks(parsed, path, method, statusCode) {
389
567
  const result = [];
390
568
  for (const [name, linkObj] of Object.entries(links)) {
391
569
  if (!isObject(linkObj)) continue;
392
- const ref = getString(linkObj, "$ref");
393
- const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? linkObj : linkObj;
394
- const link = isObject(resolved) ? resolved : linkObj;
570
+ const link = resolveReferenceObjectChain(parsed.doc, linkObj, "link", diagnostics) ?? linkObj;
395
571
  const params = getProperty(link, "parameters");
396
572
  const paramMap = /* @__PURE__ */ new Map();
397
573
  if (isObject(params)) {
@@ -409,4 +585,4 @@ function getLinks(parsed, path, method, statusCode) {
409
585
  return result;
410
586
  }
411
587
  //#endregion
412
- export { getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
588
+ export { getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listAllOperations, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
2
2
  import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequestBody } from "./parser.mjs";
3
3
 
4
4
  //#region src/openapi/resolve.d.ts
@@ -15,17 +15,26 @@ 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
  /**