schema-components 1.19.0 → 1.20.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 (62) hide show
  1. package/dist/core/adapter.d.mts +2 -2
  2. package/dist/core/adapter.mjs +59 -9
  3. package/dist/core/constraints.d.mts +2 -2
  4. package/dist/core/diagnostics.d.mts +1 -1
  5. package/dist/core/errors.d.mts +1 -1
  6. package/dist/core/errors.mjs +9 -1
  7. package/dist/core/fieldOrder.d.mts +1 -1
  8. package/dist/core/formats.d.mts +21 -14
  9. package/dist/core/formats.mjs +88 -4
  10. package/dist/core/merge.d.mts +1 -1
  11. package/dist/core/normalise.d.mts +2 -2
  12. package/dist/core/normalise.mjs +1 -1
  13. package/dist/core/openapi30.mjs +1 -1
  14. package/dist/core/ref.d.mts +1 -1
  15. package/dist/core/renderer.d.mts +1 -1
  16. package/dist/core/swagger2.d.mts +1 -1
  17. package/dist/core/swagger2.mjs +1 -1
  18. package/dist/core/typeInference.d.mts +2 -2
  19. package/dist/core/types.d.mts +1 -1
  20. package/dist/core/uri.d.mts +41 -0
  21. package/dist/core/uri.mjs +76 -0
  22. package/dist/core/version.d.mts +2 -2
  23. package/dist/core/version.mjs +25 -1
  24. package/dist/core/walkBuilders.d.mts +3 -3
  25. package/dist/core/walker.d.mts +1 -1
  26. package/dist/core/walker.mjs +50 -3
  27. package/dist/{diagnostics-VgEKI_Ct.d.mts → diagnostics-CbBPsxSt.d.mts} +1 -1
  28. package/dist/{errors-CnGjT1cg.d.mts → errors-C2iABcn9.d.mts} +8 -1
  29. package/dist/html/a11y.d.mts +2 -2
  30. package/dist/html/renderToHtml.d.mts +2 -2
  31. package/dist/html/renderToHtmlStream.d.mts +2 -2
  32. package/dist/html/renderers.d.mts +2 -2
  33. package/dist/html/renderers.mjs +9 -2
  34. package/dist/html/streamRenderers.d.mts +2 -2
  35. package/dist/{normalise-C0ofw3W6.mjs → normalise-CMMEl4cd.mjs} +255 -18
  36. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  37. package/dist/openapi/ApiLinks.d.mts +1 -1
  38. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  39. package/dist/openapi/ApiSecurity.d.mts +1 -1
  40. package/dist/openapi/bundle.mjs +2 -0
  41. package/dist/openapi/components.d.mts +2 -2
  42. package/dist/openapi/components.mjs +16 -5
  43. package/dist/openapi/parser.d.mts +1 -1
  44. package/dist/openapi/parser.mjs +2 -0
  45. package/dist/openapi/resolve.d.mts +11 -1
  46. package/dist/openapi/resolve.mjs +39 -2
  47. package/dist/react/SchemaComponent.d.mts +21 -9
  48. package/dist/react/SchemaView.d.mts +3 -3
  49. package/dist/react/fieldPath.d.mts +1 -1
  50. package/dist/react/headless.d.mts +1 -1
  51. package/dist/react/headlessRenderers.d.mts +2 -2
  52. package/dist/react/headlessRenderers.mjs +18 -6
  53. package/dist/{ref-Bb43ZURY.d.mts → ref-C8JbwfiS.d.mts} +1 -1
  54. package/dist/{renderer-BQqiXUYP.d.mts → renderer-SOIbJBtk.d.mts} +1 -1
  55. package/dist/themes/mantine.d.mts +1 -1
  56. package/dist/themes/mui.d.mts +1 -1
  57. package/dist/themes/radix.d.mts +1 -1
  58. package/dist/themes/shadcn.d.mts +1 -1
  59. package/dist/{typeInference-5JiqIZ8t.d.mts → typeInference-CDoD_LZ_.d.mts} +187 -42
  60. package/dist/{types-D_5ST7SS.d.mts → types-C9zw9wbX.d.mts} +6 -0
  61. package/dist/{version-XNH7PRGP.d.mts → version-D-u7aMfy.d.mts} +36 -1
  62. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
3
3
  import { countDistinctRefs, resolveRef } from "./ref.mjs";
4
4
  import { extractArrayConstraints, extractObjectConstraints, stripInapplicableConstraints } from "./constraints.mjs";
5
5
  import { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf } from "./merge.mjs";
6
+ import { isPrototypePollutingKey } from "./uri.mjs";
6
7
  import { buildBase, buildBooleanField, buildFileField, buildNullField, buildNumberField, buildStringField, buildUnknownField, extractChildOverride, extractSchemaMetaFields, getArray, getObject, getString, isPrimitive, walkDependentRequiredMap, walkSubSchemaMap, withoutKeys } from "./walkBuilders.mjs";
7
8
  //#region src/core/walker.ts
8
9
  /**
@@ -236,11 +237,28 @@ function walkBoolean(schema, ctx) {
236
237
  return buildBooleanField(schema, ctx);
237
238
  }
238
239
  function walkEnum(schema, enumValues, ctx) {
240
+ const accepted = [];
241
+ for (let i = 0; i < enumValues.length; i++) {
242
+ const v = enumValues[i];
243
+ if (isPrimitive(v)) {
244
+ accepted.push(v);
245
+ continue;
246
+ }
247
+ emitDiagnostic(ctx.diagnostics, {
248
+ code: "enum-value-filtered",
249
+ message: `enum value at index ${String(i)} is not a primitive (${v === void 0 ? "undefined" : typeof v}); dropping the entry`,
250
+ pointer: appendPointer(ctx.pointer, `enum/${String(i)}`),
251
+ detail: {
252
+ index: i,
253
+ value: v
254
+ }
255
+ });
256
+ }
239
257
  return {
240
258
  ...buildBase(schema, ctx),
241
259
  type: "enum",
242
260
  constraints: {},
243
- enumValues: enumValues.filter((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null)
261
+ enumValues: accepted
244
262
  };
245
263
  }
246
264
  function walkLiteral(schema, ctx) {
@@ -254,9 +272,35 @@ function walkLiteral(schema, ctx) {
254
272
  };
255
273
  }
256
274
  function walkObject(schema, properties, ctx) {
257
- const requiredFields = getArray(schema, "required")?.filter((r) => typeof r === "string") ?? [];
275
+ const required = getArray(schema, "required");
276
+ const requiredFields = [];
277
+ if (required !== void 0) for (let i = 0; i < required.length; i++) {
278
+ const r = required[i];
279
+ if (typeof r === "string") {
280
+ requiredFields.push(r);
281
+ continue;
282
+ }
283
+ emitDiagnostic(ctx.diagnostics, {
284
+ code: "required-non-string",
285
+ message: `required[${String(i)}] is not a string (${r === null ? "null" : typeof r}); dropping the entry`,
286
+ pointer: appendPointer(ctx.pointer, `required/${String(i)}`),
287
+ detail: {
288
+ index: i,
289
+ value: r
290
+ }
291
+ });
292
+ }
258
293
  const fields = {};
259
294
  for (const [key, propSchema] of Object.entries(properties)) {
295
+ if (isPrototypePollutingKey(key)) {
296
+ emitDiagnostic(ctx.diagnostics, {
297
+ code: "prototype-polluting-property",
298
+ message: `Refusing to register prototype-polluting property name: ${key}`,
299
+ pointer: appendPointer(ctx.pointer, key),
300
+ detail: { propertyName: key }
301
+ });
302
+ continue;
303
+ }
260
304
  const childOverride = extractChildOverride(ctx.fieldOverrides, key);
261
305
  const isRequired = requiredFields.includes(key);
262
306
  const childCtx = {
@@ -343,11 +387,14 @@ function walkArray(schema, ctx) {
343
387
  const prefixItems = getArray(schema, "prefixItems");
344
388
  if (prefixItems !== void 0) {
345
389
  const walkedItems = prefixItems.filter(isObject).map((item) => walkNode(item, ctx));
390
+ const restSchema = getObject(schema, "items");
391
+ const restItems = restSchema !== void 0 ? walkNode(restSchema, ctx) : void 0;
346
392
  return {
347
393
  ...buildBase(schema, ctx),
348
394
  type: "tuple",
349
395
  constraints: extractArrayConstraints(schema),
350
- prefixItems: walkedItems
396
+ prefixItems: walkedItems,
397
+ ...restItems !== void 0 ? { restItems } : {}
351
398
  };
352
399
  }
353
400
  const unevaluatedItemsSchema = getObject(schema, "unevaluatedItems");
@@ -16,7 +16,7 @@
16
16
  * Machine-readable codes identifying each class of diagnostic.
17
17
  * Stable across releases — consumers can pattern-match on these.
18
18
  */
19
- type DiagnosticCode = "unresolved-ref" | "unknown-keyword" | "unknown-format" | "invalid-const" | "unsupported-type" | "dropped-swagger-feature" | "external-ref" | "type-negation-fallback" | "conditional-fallback" | "assumed-draft" | "depth-exceeded" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "legacy-dependencies-split" | "dependent-required-invalid";
19
+ type DiagnosticCode = "unresolved-ref" | "unknown-keyword" | "unknown-format" | "invalid-const" | "unsupported-type" | "dropped-swagger-feature" | "external-ref" | "type-negation-fallback" | "conditional-fallback" | "assumed-draft" | "depth-exceeded" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "legacy-dependencies-split" | "dependent-required-invalid" | "unknown-json-schema-dialect" | "relative-ref-resolved" | "bare-exclusive-bound" | "enum-value-filtered" | "required-non-string" | "pattern-invalid" | "duplicate-body-parameter" | "prototype-polluting-property";
20
20
  /**
21
21
  * A single diagnostic emitted during schema processing.
22
22
  */
@@ -33,7 +33,14 @@ declare class SchemaNormalisationError extends SchemaError {
33
33
  * (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
34
34
  */
35
35
  readonly zodType: string | undefined;
36
- constructor(message: string, schema: unknown, kind: SchemaNormalisationError["kind"], zodType?: string);
36
+ /**
37
+ * The original underlying error, when this normalisation error wraps
38
+ * another exception (typically the error thrown by `z.toJSONSchema()`).
39
+ * Preserves the source stack trace so the root cause is not lost when
40
+ * the classifier translates the message.
41
+ */
42
+ readonly cause: unknown;
43
+ constructor(message: string, schema: unknown, kind: SchemaNormalisationError["kind"], zodType?: string, cause?: unknown);
37
44
  }
38
45
  /**
39
46
  * A theme adapter's render function threw during rendering.
@@ -1,5 +1,5 @@
1
- import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { t as AllConstraints } from "../renderer-BQqiXUYP.mjs";
1
+ import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
+ import { t as AllConstraints } from "../renderer-SOIbJBtk.mjs";
3
3
  import { HtmlAttributes, HtmlNode } from "./html.mjs";
4
4
 
5
5
  //#region src/html/a11y.d.ts
@@ -1,5 +1,5 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-BQqiXUYP.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
+ import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
3
3
 
4
4
  //#region src/html/renderToHtml.d.ts
5
5
  interface RenderToHtmlOptions {
@@ -1,5 +1,5 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-BQqiXUYP.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
+ import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
3
3
 
4
4
  //#region src/html/renderToHtmlStream.d.ts
5
5
  interface StreamRenderOptions {
@@ -1,5 +1,5 @@
1
- import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-BQqiXUYP.mjs";
1
+ import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
+ import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
3
3
 
4
4
  //#region src/html/renderers.d.ts
5
5
  declare function dateInputType(format: string | undefined): string | undefined;
@@ -1,4 +1,5 @@
1
1
  import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
2
+ import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
2
3
  import { h, raw, serialize } from "./html.mjs";
3
4
  import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
4
5
  //#region src/html/renderers.ts
@@ -26,12 +27,12 @@ function renderStringReadOnly(props) {
26
27
  ...ariaReadonlyAttrs()
27
28
  }, "—");
28
29
  const format = props.constraints.format;
29
- if (format === "email") return h("a", {
30
+ if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
30
31
  class: "sc-value",
31
32
  href: `mailto:${strValue}`,
32
33
  ...ariaReadonlyAttrs()
33
34
  }, strValue);
34
- if (format === "uri" || format === "url") return h("a", {
35
+ if ((format === "uri" || format === "url") && isSafeHyperlink(strValue)) return h("a", {
35
36
  class: "sc-value",
36
37
  href: strValue,
37
38
  ...ariaReadonlyAttrs()
@@ -356,6 +357,7 @@ function renderTupleHtml(props) {
356
357
  if (props.tree.type !== "tuple") return renderUnknownHtml(props);
357
358
  const arr = Array.isArray(props.value) ? props.value : [];
358
359
  const prefixItems = props.tree.prefixItems;
360
+ const restItems = props.tree.restItems;
359
361
  const children = [];
360
362
  for (let i = 0; i < prefixItems.length; i++) {
361
363
  const itemValue = arr[i];
@@ -364,6 +366,11 @@ function renderTupleHtml(props) {
364
366
  const childHtml = props.renderChild(element, itemValue, `[${String(i)}]`);
365
367
  children.push(h("div", { class: "sc-tuple-item" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
366
368
  }
369
+ if (restItems !== void 0) for (let i = prefixItems.length; i < arr.length; i++) {
370
+ const itemValue = arr[i];
371
+ const childHtml = props.renderChild(restItems, itemValue, `[${String(i)}]`);
372
+ children.push(h("div", { class: "sc-tuple-item sc-tuple-rest" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
373
+ }
367
374
  return serialize(h("div", { class: "sc-tuple" }, ...children));
368
375
  }
369
376
  function renderConditionalHtml(props) {
@@ -1,5 +1,5 @@
1
- import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-BQqiXUYP.mjs";
1
+ import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
+ import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
3
3
  import { HtmlElement } from "./html.mjs";
4
4
 
5
5
  //#region src/html/streamRenderers.d.ts
@@ -1,6 +1,6 @@
1
1
  import { isObject } from "./core/guards.mjs";
2
2
  import { appendPointer, emitDiagnostic } from "./core/diagnostics.mjs";
3
- import { isOpenApi30, isSwagger2 } from "./core/version.mjs";
3
+ import { isOpenApi30, isOpenApi31, isSwagger2, readJsonSchemaDialect } from "./core/version.mjs";
4
4
  //#region src/core/openapi30.ts
5
5
  /**
6
6
  * OpenAPI 3.0.x schema normalisation.
@@ -278,7 +278,7 @@ function normaliseContentMap(content, normaliseSchema) {
278
278
  const encoding = mediaObj.encoding;
279
279
  if (isObject(encoding)) normalised.encoding = mapObjectValues(encoding, (enc) => isObject(enc) ? normaliseEncoding(enc, normaliseSchema) : enc);
280
280
  if ("example" in normalised && !("examples" in normalised)) {
281
- normalised.examples = { value: normalised.example };
281
+ normalised.examples = { default: { value: normalised.example } };
282
282
  delete normalised.example;
283
283
  } else if ("example" in normalised) delete normalised.example;
284
284
  result[mediaType] = normalised;
@@ -332,7 +332,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
332
332
  result.servers = [{ url: `${typeof schemes[0] === "string" ? schemes[0] : "https"}://${host}${basePath}` }];
333
333
  }
334
334
  const paths = doc.paths;
335
- if (isObject(paths)) result.paths = normaliseSwaggerPaths(paths, doc);
335
+ if (isObject(paths)) result.paths = normaliseSwaggerPaths(paths, doc, diagnostics);
336
336
  const components = {};
337
337
  const definitions = doc.definitions;
338
338
  if (isObject(definitions)) {
@@ -353,7 +353,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
353
353
  const resolved = resolveSwaggerParameter(param, doc);
354
354
  const location = resolved.in;
355
355
  if (location === "body") requestBodies[name] = buildRequestBody(resolved, globalConsumes);
356
- else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]), ["multipart/form-data"]);
356
+ else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]), formDataContentTypes(globalConsumes));
357
357
  else convertedParameters[name] = normaliseSwaggerParameter(resolved, doc);
358
358
  }
359
359
  if (Object.keys(convertedParameters).length > 0) components.parameters = convertedParameters;
@@ -381,7 +381,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
381
381
  });
382
382
  return result;
383
383
  }
384
- function normaliseSwaggerPaths(paths, doc) {
384
+ function normaliseSwaggerPaths(paths, doc, diagnostics) {
385
385
  const result = {};
386
386
  const METHODS = [
387
387
  "get",
@@ -401,7 +401,7 @@ function normaliseSwaggerPaths(paths, doc) {
401
401
  for (const method of METHODS) {
402
402
  const operation = pathItem[method];
403
403
  if (!isObject(operation)) continue;
404
- normalisedPath[method] = normaliseSwaggerOperation(operation, doc);
404
+ normalisedPath[method] = normaliseSwaggerOperation(operation, doc, path, method, diagnostics);
405
405
  }
406
406
  const pathParams = pathItem.parameters;
407
407
  if (Array.isArray(pathParams)) normalisedPath.parameters = pathParams.map((p) => isObject(p) ? normaliseSwaggerParameter(p, doc) : p);
@@ -409,7 +409,7 @@ function normaliseSwaggerPaths(paths, doc) {
409
409
  }
410
410
  return result;
411
411
  }
412
- function normaliseSwaggerOperation(operation, doc) {
412
+ function normaliseSwaggerOperation(operation, doc, path, method, diagnostics) {
413
413
  const result = {};
414
414
  const globalProduces = Array.isArray(doc.produces) ? doc.produces : ["application/json"];
415
415
  const globalConsumes = Array.isArray(doc.consumes) ? doc.consumes : ["application/json"];
@@ -420,28 +420,61 @@ function normaliseSwaggerOperation(operation, doc) {
420
420
  if (Array.isArray(params)) {
421
421
  const nonBodyParams = [];
422
422
  let bodyParam;
423
+ let firstBodyName;
423
424
  let usesFormData = false;
424
- for (const param of params) {
425
+ for (const [index, param] of params.entries()) {
425
426
  if (!isObject(param)) {
426
427
  nonBodyParams.push(param);
427
428
  continue;
428
429
  }
429
430
  const resolvedParam = resolveSwaggerParameter(param, doc);
430
431
  const location = resolvedParam.in;
431
- if (location === "body") bodyParam = resolvedParam;
432
- else if (location === "formData") {
433
- bodyParam = buildFormDataBody(resolvedParam, params);
434
- usesFormData = true;
432
+ if (location === "body") {
433
+ if (bodyParam !== void 0) {
434
+ const duplicateName = typeof resolvedParam.name === "string" ? resolvedParam.name : `parameters[${String(index)}]`;
435
+ emitDiagnostic(diagnostics, {
436
+ code: "duplicate-body-parameter",
437
+ message: `Operation defines more than one "in: body" parameter; keeping the first ("${firstBodyName ?? "(unnamed)"}") and discarding "${duplicateName}"`,
438
+ pointer: appendPointer(appendPointer(appendPointer(appendPointer("", "paths"), path), method), "parameters"),
439
+ detail: {
440
+ kept: firstBodyName,
441
+ discarded: duplicateName,
442
+ location: "operation"
443
+ }
444
+ });
445
+ continue;
446
+ }
447
+ bodyParam = resolvedParam;
448
+ firstBodyName = typeof resolvedParam.name === "string" ? resolvedParam.name : void 0;
449
+ } else if (location === "formData") {
450
+ if (!usesFormData) {
451
+ bodyParam = buildFormDataBody(resolvedParam, params);
452
+ usesFormData = true;
453
+ }
435
454
  } else nonBodyParams.push(normaliseSwaggerParameter(resolvedParam, doc));
436
455
  }
437
456
  if (nonBodyParams.length > 0) result.parameters = nonBodyParams;
438
- if (bodyParam !== void 0) result.requestBody = buildRequestBody(bodyParam, usesFormData ? ["multipart/form-data"] : consumes);
457
+ if (bodyParam !== void 0) result.requestBody = buildRequestBody(bodyParam, usesFormData ? formDataContentTypes(consumes) : consumes);
439
458
  }
440
459
  const responses = operation.responses;
441
460
  if (isObject(responses)) result.responses = normaliseSwaggerResponses(responses, doc, produces);
442
461
  return result;
443
462
  }
444
463
  /**
464
+ * Determine the request body media type for a Swagger 2.0 formData operation.
465
+ *
466
+ * Per the OAS 3 conversion rules, `application/x-www-form-urlencoded` is
467
+ * preferred when the operation- or document-level `consumes` includes it;
468
+ * otherwise `multipart/form-data` is the default. File uploads (Swagger 2.0
469
+ * `type: file`) still require `multipart/form-data`, but the formData body
470
+ * schema-builder normalises them to `string` + `format: binary` either way
471
+ * and the choice of media type is left to the source document.
472
+ */
473
+ function formDataContentTypes(consumes) {
474
+ if (consumes.includes("application/x-www-form-urlencoded")) return ["application/x-www-form-urlencoded"];
475
+ return ["multipart/form-data"];
476
+ }
477
+ /**
445
478
  * Resolve a Swagger parameter that may be a `$ref`.
446
479
  */
447
480
  function resolveSwaggerParameter(param, doc, visited = /* @__PURE__ */ new Set()) {
@@ -596,7 +629,7 @@ function normaliseSwaggerResponses(responses, doc, produces) {
596
629
  function normaliseSwaggerSingleResponse(response, doc, produces) {
597
630
  const resolved = resolveSwaggerResponse(response, doc);
598
631
  const normalised = {};
599
- for (const [key, value] of Object.entries(resolved)) if (key !== "schema") normalised[key] = value;
632
+ for (const [key, value] of Object.entries(resolved)) if (key !== "schema" && key !== "headers") normalised[key] = value;
600
633
  const schema = resolved.schema;
601
634
  if (isObject(schema)) {
602
635
  const content = {};
@@ -604,9 +637,64 @@ function normaliseSwaggerSingleResponse(response, doc, produces) {
604
637
  for (const ct of contentTypes) if (typeof ct === "string") content[ct] = { schema };
605
638
  normalised.content = content;
606
639
  }
640
+ const headers = resolved.headers;
641
+ if (isObject(headers)) {
642
+ const convertedHeaders = {};
643
+ for (const [name, header] of Object.entries(headers)) convertedHeaders[name] = isObject(header) ? normaliseSwaggerHeader(header) : header;
644
+ normalised.headers = convertedHeaders;
645
+ }
607
646
  return normalised;
608
647
  }
609
648
  /**
649
+ * Normalise a single Swagger 2.0 response header to OpenAPI 3.x form.
650
+ *
651
+ * Swagger 2.0 headers mirror parameter shape: `type`/`format`/
652
+ * `collectionFormat` live at the root. OpenAPI 3.x requires the type
653
+ * descriptor under `schema`, with collection serialisation expressed via
654
+ * `style`/`explode`. Headers do not carry `name` or `in` — those are not
655
+ * part of either spec at this level — so this is a thin sibling to
656
+ * `normaliseSwaggerParameter` rather than a full reuse. The OpenAPI 3.x
657
+ * default header style is `simple`, so CSV-encoded headers map to
658
+ * `simple`/`explode: false` rather than the `form` style used for query
659
+ * parameters.
660
+ */
661
+ function normaliseSwaggerHeader(header) {
662
+ const result = {};
663
+ for (const [key, value] of Object.entries(header)) {
664
+ if (key === "type" || key === "format" || key === "collectionFormat") continue;
665
+ result[key] = value;
666
+ }
667
+ if (typeof header.type === "string") {
668
+ const schema = { type: header.type };
669
+ if (typeof header.format === "string") schema.format = header.format;
670
+ if (header.enum !== void 0) schema.enum = header.enum;
671
+ if (header.default !== void 0) schema.default = header.default;
672
+ if (header.minimum !== void 0) schema.minimum = header.minimum;
673
+ if (header.maximum !== void 0) schema.maximum = header.maximum;
674
+ result.schema = schema;
675
+ }
676
+ const cf = header.collectionFormat;
677
+ if (typeof cf === "string") switch (cf) {
678
+ case "csv":
679
+ result.style = "simple";
680
+ result.explode = false;
681
+ break;
682
+ case "ssv":
683
+ result.style = "spaceDelimited";
684
+ result.explode = false;
685
+ break;
686
+ case "tsv":
687
+ result.style = "tabDelimited";
688
+ result.explode = false;
689
+ break;
690
+ case "pipes":
691
+ result.style = "pipeDelimited";
692
+ result.explode = false;
693
+ break;
694
+ }
695
+ return result;
696
+ }
697
+ /**
610
698
  * Mapping of Swagger 2.0 $ref prefixes to OpenAPI 3.x equivalents.
611
699
  * Applied after document restructuring so all $ref strings point
612
700
  * to the correct locations in the normalised document.
@@ -906,10 +994,28 @@ function applyDraft04Translations(node, ctx) {
906
994
  node.exclusiveMinimum = node.minimum;
907
995
  delete node.minimum;
908
996
  } else if (node.exclusiveMinimum === false) delete node.exclusiveMinimum;
997
+ else if (node.exclusiveMinimum === true) {
998
+ if (ctx !== void 0) emitDiagnostic(ctx.diagnostics, {
999
+ code: "bare-exclusive-bound",
1000
+ message: "`exclusiveMinimum: true` requires a sibling numeric `minimum` in Draft 04; dropping the keyword",
1001
+ pointer: appendPointer(ctx.pointer, "exclusiveMinimum"),
1002
+ detail: { keyword: "exclusiveMinimum" }
1003
+ });
1004
+ delete node.exclusiveMinimum;
1005
+ }
909
1006
  if (node.exclusiveMaximum === true && typeof node.maximum === "number") {
910
1007
  node.exclusiveMaximum = node.maximum;
911
1008
  delete node.maximum;
912
1009
  } else if (node.exclusiveMaximum === false) delete node.exclusiveMaximum;
1010
+ else if (node.exclusiveMaximum === true) {
1011
+ if (ctx !== void 0) emitDiagnostic(ctx.diagnostics, {
1012
+ code: "bare-exclusive-bound",
1013
+ message: "`exclusiveMaximum: true` requires a sibling numeric `maximum` in Draft 04; dropping the keyword",
1014
+ pointer: appendPointer(ctx.pointer, "exclusiveMaximum"),
1015
+ detail: { keyword: "exclusiveMaximum" }
1016
+ });
1017
+ delete node.exclusiveMaximum;
1018
+ }
913
1019
  const divisibleBy = node.divisibleBy;
914
1020
  if (typeof divisibleBy === "number") {
915
1021
  const multipleOf = node.multipleOf;
@@ -1045,13 +1151,135 @@ function normaliseJsonSchema(schema, draft, diagnostics) {
1045
1151
  diagnostics,
1046
1152
  pointer: ""
1047
1153
  };
1154
+ let normalised;
1048
1155
  switch (draft) {
1049
- case "draft-04": return deepNormaliseWithContext(schema, normaliseDraft04NodeWithContext, ctx);
1050
- case "draft-2019-09": return deepNormaliseWithContext(schema, normaliseDraft201909NodeWithContext, ctx);
1051
- case "draft-2020-12": return deepNormaliseWithContext(schema, normaliseDynamicRefNodeWithContext, ctx);
1156
+ case "draft-04":
1157
+ normalised = deepNormaliseWithContext(schema, normaliseDraft04NodeWithContext, ctx);
1158
+ break;
1159
+ case "draft-2019-09":
1160
+ normalised = deepNormaliseWithContext(schema, normaliseDraft201909NodeWithContext, ctx);
1161
+ break;
1162
+ case "draft-2020-12":
1163
+ normalised = deepNormaliseWithContext(schema, normaliseDynamicRefNodeWithContext, ctx);
1164
+ break;
1052
1165
  case "draft-06":
1053
- case "draft-07": return deepNormaliseWithContext(schema, normaliseDraft06Or07NodeWithContext, ctx);
1166
+ case "draft-07":
1167
+ normalised = deepNormaliseWithContext(schema, normaliseDraft06Or07NodeWithContext, ctx);
1168
+ break;
1169
+ }
1170
+ return resolveRelativeRefs(normalised, diagnostics);
1171
+ }
1172
+ /**
1173
+ * Parse a string as an absolute URI, returning `undefined` when it has
1174
+ * no scheme. Used to detect whether an `$id` value defines a base URI.
1175
+ */
1176
+ function parseAbsoluteUri(value) {
1177
+ if (typeof value !== "string" || value.length === 0) return void 0;
1178
+ try {
1179
+ const url = new URL(value);
1180
+ if (url.protocol.length === 0) return void 0;
1181
+ return url;
1182
+ } catch {
1183
+ return;
1184
+ }
1185
+ }
1186
+ /**
1187
+ * Resolve a relative reference against a base URI. Returns `undefined`
1188
+ * when the reference cannot be resolved (e.g. malformed input).
1189
+ */
1190
+ function resolveAgainst(ref, base) {
1191
+ try {
1192
+ return new URL(ref, base);
1193
+ } catch {
1194
+ return;
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Strip the fragment portion from a URL, returning the canonical
1199
+ * `scheme://authority/path?query` form. Used to compare a resolved
1200
+ * `$ref` URI against the document's `$id` base.
1201
+ */
1202
+ function stripFragment(url) {
1203
+ const clone = new URL(url.toString());
1204
+ clone.hash = "";
1205
+ return clone.toString();
1206
+ }
1207
+ /**
1208
+ * Recursively rewrite relative `$ref`s in a schema so they resolve
1209
+ * correctly under the JSON Schema base-URI rules (RFC 3986 + JSON
1210
+ * Schema §8.2). Refs that resolve to the document's own `$id` are
1211
+ * rewritten to fragment-only form so the existing dereferencer can
1212
+ * handle them; refs that resolve outside the document are left as
1213
+ * absolute URIs (handled by the external resolver path).
1214
+ *
1215
+ * Returns the input unchanged when the document has no base URI or
1216
+ * no relative refs.
1217
+ */
1218
+ function resolveRelativeRefs(schema, diagnostics) {
1219
+ const docBaseUrl = parseAbsoluteUri(schema.$id);
1220
+ if (docBaseUrl === void 0) return schema;
1221
+ const docBase = stripFragment(docBaseUrl);
1222
+ return rewriteRelativeRefsNode(schema, docBase, docBase, "", diagnostics);
1223
+ }
1224
+ function rewriteRelativeRefsNode(node, currentBase, docBase, pointer, diagnostics) {
1225
+ let nextBase = currentBase;
1226
+ const nodeId = node.$id;
1227
+ if (typeof nodeId === "string" && nodeId.length > 0) {
1228
+ const resolved = resolveAgainst(nodeId, currentBase);
1229
+ if (resolved !== void 0) nextBase = stripFragment(resolved);
1230
+ }
1231
+ const result = {};
1232
+ for (const [key, value] of Object.entries(node)) {
1233
+ if (key === "$ref" && typeof value === "string") {
1234
+ result[key] = rewriteRef(value, nextBase, docBase, appendPointer(pointer, key), diagnostics);
1235
+ continue;
1236
+ }
1237
+ result[key] = rewriteRelativeRefsValue(value, key, nextBase, docBase, appendPointer(pointer, key), diagnostics);
1054
1238
  }
1239
+ return result;
1240
+ }
1241
+ function rewriteRelativeRefsValue(value, parentKey, currentBase, docBase, pointer, diagnostics) {
1242
+ if (Array.isArray(value)) return value.map((item, i) => rewriteRelativeRefsValue(item, parentKey, currentBase, docBase, appendPointer(pointer, String(i)), diagnostics));
1243
+ if (isObject(value)) return rewriteRelativeRefsNode(value, currentBase, docBase, pointer, diagnostics);
1244
+ return value;
1245
+ }
1246
+ /**
1247
+ * Rewrite a single `$ref` string. Fragment-only refs and refs that
1248
+ * already include a scheme are returned unchanged. Relative refs are
1249
+ * resolved against `currentBase`; if the result lives in the same
1250
+ * document as `docBase`, the ref is rewritten to fragment form.
1251
+ */
1252
+ function rewriteRef(ref, currentBase, docBase, pointer, diagnostics) {
1253
+ if (ref.startsWith("#")) return ref;
1254
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(ref)) return ref;
1255
+ const resolved = resolveAgainst(ref, currentBase);
1256
+ if (resolved === void 0) return ref;
1257
+ if (stripFragment(resolved) === docBase) {
1258
+ const fragment = resolved.hash === "" ? "#" : resolved.hash;
1259
+ emitDiagnostic(diagnostics, {
1260
+ code: "relative-ref-resolved",
1261
+ message: `Relative $ref "${ref}" resolved to "${fragment}" against base "${currentBase}"`,
1262
+ pointer,
1263
+ detail: {
1264
+ ref,
1265
+ base: currentBase,
1266
+ resolved: fragment
1267
+ }
1268
+ });
1269
+ return fragment;
1270
+ }
1271
+ const absolute = resolved.toString();
1272
+ emitDiagnostic(diagnostics, {
1273
+ code: "relative-ref-resolved",
1274
+ message: `Relative $ref "${ref}" resolved to "${absolute}" against base "${currentBase}"`,
1275
+ pointer,
1276
+ detail: {
1277
+ ref,
1278
+ base: currentBase,
1279
+ resolved: absolute
1280
+ }
1281
+ });
1282
+ return absolute;
1055
1283
  }
1056
1284
  /**
1057
1285
  * Normalise an OpenAPI document's schemas for walker consumption.
@@ -1063,6 +1291,15 @@ function normaliseJsonSchema(schema, draft, diagnostics) {
1063
1291
  function normaliseOpenApiSchemas(doc, version, diagnostics) {
1064
1292
  if (isSwagger2(version)) return normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, diagnostics);
1065
1293
  if (isOpenApi30(version)) return deepNormaliseOpenApi30Doc(doc, deepNormalise);
1294
+ if (isOpenApi31(version)) {
1295
+ const dialect = readJsonSchemaDialect(doc);
1296
+ if (dialect.kind === "unknown") emitDiagnostic(diagnostics, {
1297
+ code: "unknown-json-schema-dialect",
1298
+ message: `OpenAPI 3.1 \`jsonSchemaDialect\` URI "${dialect.uri}" does not match a supported JSON Schema draft; falling back to Draft 2020-12`,
1299
+ pointer: "/jsonSchemaDialect",
1300
+ detail: { uri: dialect.uri }
1301
+ });
1302
+ }
1066
1303
  return deepNormaliseOpenApiDoc(doc, (schema) => deepNormalise(schema, normaliseOpenApi30Discriminator));
1067
1304
  }
1068
1305
  //#endregion
@@ -1,4 +1,4 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
2
  import { CallbackInfo } from "./parser.mjs";
3
3
  import { ReactNode } from "react";
4
4
 
@@ -1,4 +1,4 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
2
  import { LinkInfo } from "./parser.mjs";
3
3
  import { ReactNode } from "react";
4
4
 
@@ -1,4 +1,4 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
2
  import { HeaderInfo } from "./parser.mjs";
3
3
  import { ReactNode } from "react";
4
4
 
@@ -1,4 +1,4 @@
1
- import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
1
+ import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
2
  import { SecurityRequirement, SecurityScheme } from "./parser.mjs";
3
3
  import { ReactNode } from "react";
4
4
 
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "../core/guards.mjs";
2
+ import { isPrototypePollutingKey } from "../core/uri.mjs";
2
3
  //#region src/openapi/bundle.ts
3
4
  /**
4
5
  * OpenAPI document bundler — inlines external $ref files.
@@ -104,6 +105,7 @@ function resolveFragment(doc, fragment) {
104
105
  for (const part of parts) {
105
106
  if (!isObject(current)) return void 0;
106
107
  const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
108
+ if (isPrototypePollutingKey(decoded)) return void 0;
107
109
  current = current[decoded];
108
110
  }
109
111
  return isObject(current) ? current : void 0;
@@ -1,5 +1,5 @@
1
- import { T as SchemaMeta, u as FieldOverride } from "../types-D_5ST7SS.mjs";
2
- import { a as InferResponseFields, d as UnsafeFields, i as InferRequestBodyFields, r as InferParameterOverrides } from "../typeInference-5JiqIZ8t.mjs";
1
+ import { T as SchemaMeta, u as FieldOverride } from "../types-C9zw9wbX.mjs";
2
+ import { a as InferResponseFields, i as InferRequestBodyFields, m as UnsafeFields, r as InferParameterOverrides } from "../typeInference-CDoD_LZ_.mjs";
3
3
  import { WidgetMap } from "../react/SchemaComponent.mjs";
4
4
  import { ReactNode } from "react";
5
5