schema-components 1.18.0 → 1.19.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 (61) hide show
  1. package/dist/core/adapter.d.mts +1 -1
  2. package/dist/core/adapter.mjs +77 -14
  3. package/dist/core/constraints.d.mts +1 -1
  4. package/dist/core/diagnostics.d.mts +1 -1
  5. package/dist/core/errors.d.mts +1 -1
  6. package/dist/core/errors.mjs +7 -1
  7. package/dist/core/fieldOrder.d.mts +10 -0
  8. package/dist/core/fieldOrder.mjs +12 -0
  9. package/dist/core/formats.mjs +9 -1
  10. package/dist/core/merge.d.mts +1 -1
  11. package/dist/core/merge.mjs +30 -2
  12. package/dist/core/normalise.d.mts +38 -5
  13. package/dist/core/normalise.mjs +2 -2
  14. package/dist/core/openapi30.d.mts +33 -4
  15. package/dist/core/openapi30.mjs +2 -2
  16. package/dist/core/ref.d.mts +1 -1
  17. package/dist/core/renderer.d.mts +1 -1
  18. package/dist/core/renderer.mjs +7 -21
  19. package/dist/core/swagger2.d.mts +1 -1
  20. package/dist/core/swagger2.mjs +1 -1
  21. package/dist/core/version.d.mts +2 -2
  22. package/dist/core/version.mjs +19 -9
  23. package/dist/core/walkBuilders.d.mts +2 -2
  24. package/dist/{diagnostics-BYk63jsC.d.mts → diagnostics-VgEKI_Ct.d.mts} +1 -1
  25. package/dist/{errors-C5zRC2PU.d.mts → errors-CnGjT1cg.d.mts} +7 -2
  26. package/dist/html/a11y.d.mts +1 -1
  27. package/dist/html/renderToHtml.d.mts +1 -1
  28. package/dist/html/renderToHtml.mjs +13 -30
  29. package/dist/html/renderToHtmlStream.d.mts +1 -1
  30. package/dist/html/renderers.d.mts +1 -1
  31. package/dist/html/renderers.mjs +56 -23
  32. package/dist/html/streamRenderers.d.mts +1 -1
  33. package/dist/html/streamRenderers.mjs +10 -21
  34. package/dist/{normalise-tL9FckAk.mjs → normalise-C0ofw3W6.mjs} +418 -97
  35. package/dist/openapi/ApiSecurity.mjs +1 -1
  36. package/dist/openapi/bundle.mjs +1 -0
  37. package/dist/openapi/components.mjs +6 -2
  38. package/dist/openapi/parser.d.mts +2 -2
  39. package/dist/openapi/parser.mjs +8 -5
  40. package/dist/openapi/resolve.d.mts +6 -5
  41. package/dist/openapi/resolve.mjs +7 -6
  42. package/dist/react/SchemaComponent.d.mts +4 -4
  43. package/dist/react/SchemaComponent.mjs +4 -14
  44. package/dist/react/SchemaView.d.mts +2 -2
  45. package/dist/react/SchemaView.mjs +2 -1
  46. package/dist/react/headless.d.mts +7 -1
  47. package/dist/react/headless.mjs +13 -1
  48. package/dist/react/headlessRenderers.d.mts +53 -2
  49. package/dist/react/headlessRenderers.mjs +175 -33
  50. package/dist/{ref-Ckt5liZs.d.mts → ref-Bb43ZURY.d.mts} +1 -1
  51. package/dist/{renderer-DXo-rXHJ.d.mts → renderer-BQqiXUYP.d.mts} +15 -32
  52. package/dist/themes/mantine.d.mts +1 -1
  53. package/dist/themes/mantine.mjs +2 -1
  54. package/dist/themes/mui.d.mts +1 -1
  55. package/dist/themes/mui.mjs +3 -2
  56. package/dist/themes/radix.d.mts +1 -1
  57. package/dist/themes/radix.mjs +2 -1
  58. package/dist/themes/shadcn.d.mts +1 -1
  59. package/dist/themes/shadcn.mjs +2 -1
  60. package/dist/{version-B5NV-35j.d.mts → version-XNH7PRGP.d.mts} +8 -1
  61. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { isObject } from "./core/guards.mjs";
2
- import { emitDiagnostic } from "./core/diagnostics.mjs";
2
+ import { appendPointer, emitDiagnostic } from "./core/diagnostics.mjs";
3
3
  import { isOpenApi30, isSwagger2 } from "./core/version.mjs";
4
4
  //#region src/core/openapi30.ts
5
5
  /**
@@ -7,7 +7,7 @@ import { isOpenApi30, isSwagger2 } from "./core/version.mjs";
7
7
  *
8
8
  * Transforms `nullable`, `discriminator`, `example` keywords, and walks
9
9
  * all schema locations (components, paths, parameters, request bodies,
10
- * responses) to apply normalisation.
10
+ * responses, headers, callbacks, links, examples) to apply normalisation.
11
11
  */
12
12
  /**
13
13
  * Normalise OpenAPI 3.0.x `nullable` keyword to `anyOf [T, null]`.
@@ -127,86 +127,145 @@ function normaliseOpenApi30Combined(node) {
127
127
  return normaliseOpenApi30Discriminator(normaliseOpenApi30Node(normaliseDraft04Node(node)));
128
128
  }
129
129
  /**
130
- * Deep-normalise all schemas in an OpenAPI 3.0.x document.
131
- * Walks components/schemas, path operations, parameters, request bodies,
132
- * and responses applying `nullable` normalisation to each schema.
130
+ * Deep-clone the parent first, then patch back any keys whose values were
131
+ * rewritten by the visitor. This preserves immutability of the original
132
+ * document while keeping the visitor straightforward to write.
133
133
  */
134
- function deepNormaliseOpenApi30Doc(doc, deepNormalise) {
134
+ /**
135
+ * Deep-normalise every Schema Object in an OpenAPI document.
136
+ *
137
+ * Walks: `paths.*` (operations + path-level parameters), `webhooks.*`
138
+ * (3.1), `components.schemas`, `components.parameters`,
139
+ * `components.responses`, `components.requestBodies`,
140
+ * `components.headers`, `components.callbacks`, `components.pathItems`
141
+ * (3.1). For each Schema-bearing location, applies the supplied
142
+ * `normaliseSchema` function.
143
+ *
144
+ * The walker is structural (it understands OAS document shapes) and
145
+ * delegates the per-schema transformation. For OAS 3.0 the caller
146
+ * passes a full Draft 04 + nullable + discriminator + example
147
+ * normaliser; for OAS 3.1 the caller passes a discriminator-only
148
+ * normaliser so the walker's discriminated-union detection sees the
149
+ * injected `const`s regardless of OAS minor version.
150
+ */
151
+ function deepNormaliseOpenApiDoc(doc, normaliseSchema) {
135
152
  const result = { ...doc };
136
153
  const components = doc.components;
137
- if (isObject(components)) {
138
- const schemas = components.schemas;
139
- if (isObject(schemas)) {
140
- const normalisedSchemas = {};
141
- for (const [name, schema] of Object.entries(schemas)) normalisedSchemas[name] = isObject(schema) ? deepNormalise(schema, normaliseOpenApi30Combined) : schema;
142
- result.components = {
143
- ...components,
144
- schemas: normalisedSchemas
145
- };
146
- }
147
- }
154
+ if (isObject(components)) result.components = normaliseComponents(components, normaliseSchema);
148
155
  const paths = doc.paths;
149
- if (isObject(paths)) {
150
- const normalisedPaths = {};
151
- for (const [path, pathItem] of Object.entries(paths)) normalisedPaths[path] = isObject(pathItem) ? normalisePathItem(pathItem, deepNormalise) : pathItem;
152
- result.paths = normalisedPaths;
153
- }
156
+ if (isObject(paths)) result.paths = normalisePathMap(paths, normaliseSchema);
157
+ const webhooks = doc.webhooks;
158
+ if (isObject(webhooks)) result.webhooks = normalisePathMap(webhooks, normaliseSchema);
154
159
  return result;
155
160
  }
156
- function normalisePathItem(pathItem, deepNormalise) {
161
+ /**
162
+ * Backwards-compatible wrapper retaining the historic `deepNormalise`
163
+ * signature used by callers in `normalise.ts`. Always applies the full
164
+ * 3.0 combined transform via `deepNormalise(schema, normaliseOpenApi30Combined)`.
165
+ */
166
+ function deepNormaliseOpenApi30Doc(doc, deepNormalise) {
167
+ return deepNormaliseOpenApiDoc(doc, (schema) => deepNormalise(schema, normaliseOpenApi30Combined));
168
+ }
169
+ function normaliseComponents(components, normaliseSchema) {
170
+ const result = { ...components };
171
+ const schemas = components.schemas;
172
+ if (isObject(schemas)) result.schemas = mapObjectValues(schemas, (schema) => isObject(schema) ? normaliseSchema(schema) : schema);
173
+ const parameters = components.parameters;
174
+ if (isObject(parameters)) result.parameters = mapObjectValues(parameters, (param) => isObject(param) ? normaliseParameter(param, normaliseSchema) : param);
175
+ const responses = components.responses;
176
+ if (isObject(responses)) result.responses = mapObjectValues(responses, (response) => isObject(response) ? normaliseResponse(response, normaliseSchema) : response);
177
+ const requestBodies = components.requestBodies;
178
+ if (isObject(requestBodies)) result.requestBodies = mapObjectValues(requestBodies, (body) => isObject(body) ? normaliseRequestBody(body, normaliseSchema) : body);
179
+ const headers = components.headers;
180
+ if (isObject(headers)) result.headers = mapObjectValues(headers, (header) => isObject(header) ? normaliseHeader(header, normaliseSchema) : header);
181
+ const callbacks = components.callbacks;
182
+ if (isObject(callbacks)) result.callbacks = mapObjectValues(callbacks, (callback) => isObject(callback) ? normaliseCallback(callback, normaliseSchema) : callback);
183
+ const pathItems = components.pathItems;
184
+ if (isObject(pathItems)) result.pathItems = mapObjectValues(pathItems, (pathItem) => isObject(pathItem) ? normalisePathItem(pathItem, normaliseSchema) : pathItem);
185
+ return result;
186
+ }
187
+ function normalisePathMap(paths, normaliseSchema) {
188
+ return mapObjectValues(paths, (pathItem) => isObject(pathItem) ? normalisePathItem(pathItem, normaliseSchema) : pathItem);
189
+ }
190
+ const HTTP_METHODS = [
191
+ "get",
192
+ "put",
193
+ "post",
194
+ "delete",
195
+ "options",
196
+ "head",
197
+ "patch",
198
+ "trace"
199
+ ];
200
+ function normalisePathItem(pathItem, normaliseSchema) {
157
201
  const result = { ...pathItem };
158
- for (const method of [
159
- "get",
160
- "post",
161
- "put",
162
- "patch",
163
- "delete"
164
- ]) {
202
+ for (const method of HTTP_METHODS) {
165
203
  const operation = pathItem[method];
166
- if (!isObject(operation)) continue;
167
- result[method] = normaliseOperation(operation, deepNormalise);
204
+ if (isObject(operation)) result[method] = normaliseOperation(operation, normaliseSchema);
168
205
  }
169
206
  const parameters = pathItem.parameters;
170
- if (Array.isArray(parameters)) result.parameters = parameters.map((param) => isObject(param) ? normaliseParameter(param, deepNormalise) : param);
207
+ if (Array.isArray(parameters)) result.parameters = parameters.map((param) => isObject(param) ? normaliseParameter(param, normaliseSchema) : param);
171
208
  return result;
172
209
  }
173
- function normaliseOperation(operation, deepNormalise) {
210
+ function normaliseOperation(operation, normaliseSchema) {
174
211
  const result = { ...operation };
175
212
  const parameters = operation.parameters;
176
- if (Array.isArray(parameters)) result.parameters = parameters.map((param) => isObject(param) ? normaliseParameter(param, deepNormalise) : param);
213
+ if (Array.isArray(parameters)) result.parameters = parameters.map((param) => isObject(param) ? normaliseParameter(param, normaliseSchema) : param);
177
214
  const requestBody = operation.requestBody;
178
- if (isObject(requestBody)) result.requestBody = normaliseRequestBody(requestBody, deepNormalise);
215
+ if (isObject(requestBody)) result.requestBody = normaliseRequestBody(requestBody, normaliseSchema);
179
216
  const responses = operation.responses;
180
- if (isObject(responses)) {
181
- const normalisedResponses = {};
182
- for (const [code, response] of Object.entries(responses)) normalisedResponses[code] = isObject(response) ? normaliseResponse(response, deepNormalise) : response;
183
- result.responses = normalisedResponses;
184
- }
217
+ if (isObject(responses)) result.responses = mapObjectValues(responses, (response) => isObject(response) ? normaliseResponse(response, normaliseSchema) : response);
218
+ const callbacks = operation.callbacks;
219
+ if (isObject(callbacks)) result.callbacks = mapObjectValues(callbacks, (callback) => isObject(callback) ? normaliseCallback(callback, normaliseSchema) : callback);
185
220
  return result;
186
221
  }
187
- function normaliseParameter(param, deepNormalise) {
222
+ function normaliseParameter(param, normaliseSchema) {
188
223
  const result = { ...param };
189
224
  const schema = param.schema;
190
- if (isObject(schema)) result.schema = deepNormalise(schema, normaliseOpenApi30Combined);
225
+ if (isObject(schema)) result.schema = normaliseSchema(schema);
226
+ const content = param.content;
227
+ if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
191
228
  if ("example" in result && !("examples" in result)) {
192
229
  result.examples = [result.example];
193
230
  delete result.example;
194
231
  } else if ("example" in result) delete result.example;
195
232
  return result;
196
233
  }
197
- function normaliseRequestBody(requestBody, deepNormalise) {
234
+ function normaliseRequestBody(requestBody, normaliseSchema) {
198
235
  const result = { ...requestBody };
199
236
  const content = requestBody.content;
200
- if (isObject(content)) result.content = normaliseContentMap(content, deepNormalise);
237
+ if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
201
238
  return result;
202
239
  }
203
- function normaliseResponse(response, deepNormalise) {
240
+ function normaliseResponse(response, normaliseSchema) {
204
241
  const result = { ...response };
205
242
  const content = response.content;
206
- if (isObject(content)) result.content = normaliseContentMap(content, deepNormalise);
243
+ if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
244
+ const headers = response.headers;
245
+ if (isObject(headers)) result.headers = mapObjectValues(headers, (header) => isObject(header) ? normaliseHeader(header, normaliseSchema) : header);
246
+ return result;
247
+ }
248
+ function normaliseHeader(header, normaliseSchema) {
249
+ const result = { ...header };
250
+ const schema = header.schema;
251
+ if (isObject(schema)) result.schema = normaliseSchema(schema);
252
+ const content = header.content;
253
+ if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
254
+ if ("example" in result && !("examples" in result)) {
255
+ result.examples = [result.example];
256
+ delete result.example;
257
+ } else if ("example" in result) delete result.example;
207
258
  return result;
208
259
  }
209
- function normaliseContentMap(content, deepNormalise) {
260
+ /**
261
+ * A Callback Object is a map of runtime-expression keys → Path Item
262
+ * Objects. Each Path Item carries operations whose responses, request
263
+ * bodies, parameters, and headers may all contain Schema Objects.
264
+ */
265
+ function normaliseCallback(callback, normaliseSchema) {
266
+ return mapObjectValues(callback, (pathItem) => isObject(pathItem) ? normalisePathItem(pathItem, normaliseSchema) : pathItem);
267
+ }
268
+ function normaliseContentMap(content, normaliseSchema) {
210
269
  const result = {};
211
270
  for (const [mediaType, mediaObj] of Object.entries(content)) {
212
271
  if (!isObject(mediaObj)) {
@@ -215,7 +274,9 @@ function normaliseContentMap(content, deepNormalise) {
215
274
  }
216
275
  const normalised = { ...mediaObj };
217
276
  const schema = mediaObj.schema;
218
- if (isObject(schema)) normalised.schema = deepNormalise(schema, normaliseOpenApi30Combined);
277
+ if (isObject(schema)) normalised.schema = normaliseSchema(schema);
278
+ const encoding = mediaObj.encoding;
279
+ if (isObject(encoding)) normalised.encoding = mapObjectValues(encoding, (enc) => isObject(enc) ? normaliseEncoding(enc, normaliseSchema) : enc);
219
280
  if ("example" in normalised && !("examples" in normalised)) {
220
281
  normalised.examples = { value: normalised.example };
221
282
  delete normalised.example;
@@ -224,6 +285,22 @@ function normaliseContentMap(content, deepNormalise) {
224
285
  }
225
286
  return result;
226
287
  }
288
+ function normaliseEncoding(encoding, normaliseSchema) {
289
+ const result = { ...encoding };
290
+ const headers = encoding.headers;
291
+ if (isObject(headers)) result.headers = mapObjectValues(headers, (header) => isObject(header) ? normaliseHeader(header, normaliseSchema) : header);
292
+ return result;
293
+ }
294
+ /**
295
+ * Apply `transform` to each value of a `Record<string, unknown>` and
296
+ * return a new record. Non-object values pass through transform unchanged
297
+ * — callers add their own `isObject` guard inside `transform`.
298
+ */
299
+ function mapObjectValues(source, transform) {
300
+ const result = {};
301
+ for (const [key, value] of Object.entries(source)) result[key] = transform(value);
302
+ return result;
303
+ }
227
304
  //#endregion
228
305
  //#region src/core/swagger2.ts
229
306
  /**
@@ -264,16 +341,39 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
264
341
  components.schemas = schemas;
265
342
  }
266
343
  const parameters = doc.parameters;
267
- if (isObject(parameters)) components.parameters = { ...parameters };
344
+ const requestBodies = {};
345
+ if (isObject(parameters)) {
346
+ const globalConsumes = Array.isArray(doc.consumes) ? doc.consumes : ["application/json"];
347
+ const convertedParameters = {};
348
+ for (const [name, param] of Object.entries(parameters)) {
349
+ if (!isObject(param)) {
350
+ convertedParameters[name] = param;
351
+ continue;
352
+ }
353
+ const resolved = resolveSwaggerParameter(param, doc);
354
+ const location = resolved.in;
355
+ if (location === "body") requestBodies[name] = buildRequestBody(resolved, globalConsumes);
356
+ else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]), ["multipart/form-data"]);
357
+ else convertedParameters[name] = normaliseSwaggerParameter(resolved, doc);
358
+ }
359
+ if (Object.keys(convertedParameters).length > 0) components.parameters = convertedParameters;
360
+ }
268
361
  const responses = doc.responses;
269
- if (isObject(responses)) components.responses = { ...responses };
362
+ if (isObject(responses)) {
363
+ const globalProduces = Array.isArray(doc.produces) ? doc.produces : ["application/json"];
364
+ const convertedResponses = {};
365
+ for (const [name, response] of Object.entries(responses)) convertedResponses[name] = isObject(response) ? normaliseSwaggerSingleResponse(response, doc, globalProduces) : response;
366
+ components.responses = convertedResponses;
367
+ }
368
+ if (Object.keys(requestBodies).length > 0) components.requestBodies = requestBodies;
270
369
  const securityDefinitions = doc.securityDefinitions;
271
370
  if (isObject(securityDefinitions)) components.securitySchemes = { ...securityDefinitions };
272
371
  if (Object.keys(components).length > 0) result.components = components;
273
372
  if (Array.isArray(doc.tags)) result.tags = doc.tags;
274
373
  if (isObject(doc.externalDocs)) result.externalDocs = doc.externalDocs;
374
+ if (Array.isArray(doc.security)) result.security = doc.security;
275
375
  rewriteSwaggerRefs(result);
276
- if (isObject(doc.xml) || isObject(doc.definitions) && hasXmlInSchemas(doc.definitions)) emitDiagnostic(diagnostics, {
376
+ if (hasXmlAnywhere(doc.definitions) || hasXmlAnywhere(doc.paths) || hasXmlAnywhere(doc.parameters) || hasXmlAnywhere(doc.responses)) emitDiagnostic(diagnostics, {
277
377
  code: "dropped-swagger-feature",
278
378
  message: "Swagger 2.0 xml markup is not supported and will be dropped",
279
379
  pointer: "",
@@ -480,21 +580,33 @@ function normaliseSwaggerResponses(responses, doc, produces) {
480
580
  result[code] = response;
481
581
  continue;
482
582
  }
483
- const resolved = resolveSwaggerResponse(response, doc);
484
- const normalised = {};
485
- for (const [key, value] of Object.entries(resolved)) if (key !== "schema") normalised[key] = value;
486
- const schema = resolved.schema;
487
- if (isObject(schema)) {
488
- const content = {};
489
- const contentTypes = produces.length > 0 ? produces : ["application/json"];
490
- for (const ct of contentTypes) if (typeof ct === "string") content[ct] = { schema };
491
- normalised.content = content;
492
- }
493
- result[code] = normalised;
583
+ result[code] = normaliseSwaggerSingleResponse(response, doc, produces);
494
584
  }
495
585
  return result;
496
586
  }
497
587
  /**
588
+ * Normalise a single Swagger 2.0 response object — resolves any `$ref` to
589
+ * `#/responses/<Name>` and wraps a top-level `schema` in an OpenAPI 3.x
590
+ * `content` map keyed by the supplied media types.
591
+ *
592
+ * Extracted so the same logic applies whether the response sits inside an
593
+ * operation’s `responses` map or under document-level `responses`
594
+ * (now `components.responses`).
595
+ */
596
+ function normaliseSwaggerSingleResponse(response, doc, produces) {
597
+ const resolved = resolveSwaggerResponse(response, doc);
598
+ const normalised = {};
599
+ for (const [key, value] of Object.entries(resolved)) if (key !== "schema") normalised[key] = value;
600
+ const schema = resolved.schema;
601
+ if (isObject(schema)) {
602
+ const content = {};
603
+ const contentTypes = produces.length > 0 ? produces : ["application/json"];
604
+ for (const ct of contentTypes) if (typeof ct === "string") content[ct] = { schema };
605
+ normalised.content = content;
606
+ }
607
+ return normalised;
608
+ }
609
+ /**
498
610
  * Mapping of Swagger 2.0 $ref prefixes to OpenAPI 3.x equivalents.
499
611
  * Applied after document restructuring so all $ref strings point
500
612
  * to the correct locations in the normalised document.
@@ -522,10 +634,21 @@ function rewriteSwaggerRefs(node) {
522
634
  else if (Array.isArray(value)) for (const item of value) rewriteSwaggerRefs(item);
523
635
  }
524
636
  /**
525
- * Check if any schema in a definitions block contains an `xml` property.
637
+ * Recursively check whether any node in the supplied subtree carries an
638
+ * `xml` annotation. Walks both objects and arrays so the check works for
639
+ * schemas (definitions, parameter schemas, response schemas, request body
640
+ * schemas) as well as operations and parameters that may carry `xml`
641
+ * metadata at any depth.
526
642
  */
527
- function hasXmlInSchemas(definitions) {
528
- for (const schema of Object.values(definitions)) if (isObject(schema) && "xml" in schema) return true;
643
+ function hasXmlAnywhere(node) {
644
+ if (!isObject(node)) {
645
+ if (Array.isArray(node)) {
646
+ for (const item of node) if (hasXmlAnywhere(item)) return true;
647
+ }
648
+ return false;
649
+ }
650
+ if ("xml" in node) return true;
651
+ for (const value of Object.values(node)) if (hasXmlAnywhere(value)) return true;
529
652
  return false;
530
653
  }
531
654
  //#endregion
@@ -602,6 +725,107 @@ function deepNormalise(schema, transform) {
602
725
  } else result[key] = value;
603
726
  return result;
604
727
  }
728
+ function normaliseArrayWithContext(items, transform, ctx) {
729
+ const result = [];
730
+ for (let i = 0; i < items.length; i++) {
731
+ const item = items[i];
732
+ if (isObject(item)) result.push(deepNormaliseWithContext(item, transform, {
733
+ diagnostics: ctx.diagnostics,
734
+ pointer: appendPointer(ctx.pointer, String(i))
735
+ }));
736
+ else result.push(item);
737
+ }
738
+ return result;
739
+ }
740
+ function normaliseSubSchemaMapWithContext(map, transform, ctx) {
741
+ const result = {};
742
+ for (const [k, v] of Object.entries(map)) if (isObject(v)) result[k] = deepNormaliseWithContext(v, transform, {
743
+ diagnostics: ctx.diagnostics,
744
+ pointer: appendPointer(ctx.pointer, k)
745
+ });
746
+ else result[k] = v;
747
+ return result;
748
+ }
749
+ /**
750
+ * Deep-normalise a JSON Schema object, threading a context (diagnostics
751
+ * sink + JSON Pointer) through each recursive call. Used by the JSON
752
+ * Schema normalisation path so per-node transforms can emit diagnostics
753
+ * with accurate pointers.
754
+ *
755
+ * Mirrors `deepNormalise` structurally — keep the two in sync when
756
+ * adding new sub-schema locations.
757
+ */
758
+ function deepNormaliseWithContext(schema, transform, ctx) {
759
+ const node = transform({ ...schema }, ctx);
760
+ const result = {};
761
+ for (const [key, value] of Object.entries(node)) if (isObject(value) && OBJECT_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseSubSchemaMapWithContext(value, transform, {
762
+ diagnostics: ctx.diagnostics,
763
+ pointer: appendPointer(ctx.pointer, key)
764
+ });
765
+ else if (Array.isArray(value) && ARRAY_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseArrayWithContext(value, transform, {
766
+ diagnostics: ctx.diagnostics,
767
+ pointer: appendPointer(ctx.pointer, key)
768
+ });
769
+ else if (isObject(value) && SINGLE_SUBSCHEMA_KEYS.has(key)) result[key] = deepNormaliseWithContext(value, transform, {
770
+ diagnostics: ctx.diagnostics,
771
+ pointer: appendPointer(ctx.pointer, key)
772
+ });
773
+ else if (key === "items") if (Array.isArray(value)) result[key] = normaliseArrayWithContext(value, transform, {
774
+ diagnostics: ctx.diagnostics,
775
+ pointer: appendPointer(ctx.pointer, key)
776
+ });
777
+ else if (isObject(value)) result[key] = deepNormaliseWithContext(value, transform, {
778
+ diagnostics: ctx.diagnostics,
779
+ pointer: appendPointer(ctx.pointer, key)
780
+ });
781
+ else result[key] = value;
782
+ else if (key === "dependencies" && isObject(value)) {
783
+ const normalised = {};
784
+ const depsPointer = appendPointer(ctx.pointer, key);
785
+ for (const [dk, dv] of Object.entries(value)) if (isObject(dv)) normalised[dk] = deepNormaliseWithContext(dv, transform, {
786
+ diagnostics: ctx.diagnostics,
787
+ pointer: appendPointer(depsPointer, dk)
788
+ });
789
+ else normalised[dk] = dv;
790
+ result[key] = normalised;
791
+ } else result[key] = value;
792
+ return result;
793
+ }
794
+ /**
795
+ * Walk an array of supposed required-property names. Each non-string
796
+ * element triggers a `dependent-required-invalid` diagnostic against
797
+ * the supplied context. Returns the collected string entries when
798
+ * every element validates, or `undefined` when at least one entry
799
+ * was invalid (signalling the caller should drop the property
800
+ * entirely rather than emit a partial rewrite).
801
+ *
802
+ * `keyword` distinguishes diagnostics that originate from the legacy
803
+ * `dependencies` keyword versus the modern `dependentRequired`.
804
+ */
805
+ function collectDependencyStrings(items, property, keyword, ctx) {
806
+ const strings = [];
807
+ let sawInvalid = false;
808
+ for (let i = 0; i < items.length; i++) {
809
+ const item = items[i];
810
+ if (typeof item === "string") {
811
+ strings.push(item);
812
+ continue;
813
+ }
814
+ sawInvalid = true;
815
+ if (ctx === void 0) continue;
816
+ emitDiagnostic(ctx.diagnostics, {
817
+ code: "dependent-required-invalid",
818
+ message: `\`${keyword}.${property}[${String(i)}]\` is not a string; only string property names are valid in a required-dependency array`,
819
+ pointer: appendPointer(appendPointer(appendPointer(ctx.pointer, keyword), property), String(i)),
820
+ detail: {
821
+ property,
822
+ index: i,
823
+ value: item
824
+ }
825
+ });
826
+ }
827
+ return sawInvalid ? void 0 : strings;
828
+ }
605
829
  /**
606
830
  * Split the legacy `dependencies` keyword into `dependentRequired` and
607
831
  * `dependentSchemas` per the Draft 2019-09+ replacement.
@@ -612,15 +836,28 @@ function deepNormalise(schema, transform) {
612
836
  *
613
837
  * Both forms can coexist within the same `dependencies` object.
614
838
  * After splitting, `dependencies` is removed from the node.
839
+ *
840
+ * When `ctx` is supplied, diagnostics are emitted for:
841
+ * - `legacy-dependencies-split` once per node that contained the
842
+ * deprecated keyword (callers pass this only on draft paths where
843
+ * the keyword is unexpected, e.g. 2020-12).
844
+ * - `dependent-required-invalid` for each array entry whose element is
845
+ * not a string.
615
846
  */
616
- function splitDependencies(node) {
847
+ function splitDependencies(node, ctx, emitLegacyDiagnostic) {
617
848
  const deps = node.dependencies;
618
849
  if (!isObject(deps)) return;
850
+ if (emitLegacyDiagnostic && ctx !== void 0) emitDiagnostic(ctx.diagnostics, {
851
+ code: "legacy-dependencies-split",
852
+ message: "Legacy `dependencies` keyword was split into `dependentRequired`/`dependentSchemas`; `dependencies` was deprecated in Draft 2019-09",
853
+ pointer: appendPointer(ctx.pointer, "dependencies"),
854
+ detail: { keys: Object.keys(deps) }
855
+ });
619
856
  const requiredEntries = {};
620
857
  const schemaEntries = {};
621
858
  for (const [key, value] of Object.entries(deps)) if (Array.isArray(value)) {
622
- const strings = value.filter((v) => typeof v === "string");
623
- if (strings.length === value.length) requiredEntries[key] = strings;
859
+ const accepted = collectDependencyStrings(value, key, "dependencies", ctx);
860
+ if (accepted !== void 0) requiredEntries[key] = accepted;
624
861
  } else if (isObject(value)) schemaEntries[key] = value;
625
862
  if (Object.keys(requiredEntries).length > 0) {
626
863
  const existing = node.dependentRequired;
@@ -635,21 +872,36 @@ function splitDependencies(node) {
635
872
  delete node.dependencies;
636
873
  }
637
874
  /**
638
- * Normalise Draft 04 `exclusiveMinimum`/`exclusiveMaximum` from boolean
639
- * to number form.
875
+ * Emit diagnostics for any non-string entries inside a pre-existing
876
+ * `dependentRequired` keyword. Used on draft paths where the author may
877
+ * have already migrated to the Draft 2019-09 form but still produced
878
+ * invalid array entries. The keyword value is not rewritten — the
879
+ * walker is responsible for honouring (or rejecting) the constraint.
880
+ */
881
+ function validateDependentRequired(node, ctx) {
882
+ if (ctx === void 0) return;
883
+ const dr = node.dependentRequired;
884
+ if (!isObject(dr)) return;
885
+ for (const [key, value] of Object.entries(dr)) {
886
+ if (!Array.isArray(value)) continue;
887
+ collectDependencyStrings(value, key, "dependentRequired", ctx);
888
+ }
889
+ }
890
+ /**
891
+ * Apply the version-agnostic Draft 04 keyword translations to a single
892
+ * node: boolean exclusive-min/max → number form, bare `id` → `$id`, and
893
+ * tuple-form `items` → `prefixItems`.
640
894
  *
641
- * In Draft 04:
642
- * - `exclusiveMinimum: true` + `minimum: 5` value must be > 5
643
- * - `exclusiveMinimum: false` (or absent) + `minimum: 5` value must be >= 5
895
+ * `divisibleBy` is also translated to `multipleOf` (a Draft 03 carryover
896
+ * that legitimately appears in legacy Draft 04 schemas). When `ctx` is
897
+ * supplied and both keywords are present with conflicting values, a
898
+ * `divisible-by-conflict` diagnostic is emitted.
644
899
  *
645
- * In Draft 06+:
646
- * - `exclusiveMinimum: 5` value must be > 5 (no separate `minimum`)
647
- * - `minimum: 5` value must be >= 5
648
- *
649
- * The transform converts boolean form to number form so the walker can
650
- * treat `exclusiveMinimum`/`exclusiveMaximum` uniformly as numbers.
900
+ * `dependencies` is split into `dependentRequired`/`dependentSchemas`
901
+ * via {@link splitDependencies}; passing `ctx` enables per-entry
902
+ * diagnostics for non-string array members.
651
903
  */
652
- function normaliseDraft04Node(node) {
904
+ function applyDraft04Translations(node, ctx) {
653
905
  if (node.exclusiveMinimum === true && typeof node.minimum === "number") {
654
906
  node.exclusiveMinimum = node.minimum;
655
907
  delete node.minimum;
@@ -658,6 +910,22 @@ function normaliseDraft04Node(node) {
658
910
  node.exclusiveMaximum = node.maximum;
659
911
  delete node.maximum;
660
912
  } else if (node.exclusiveMaximum === false) delete node.exclusiveMaximum;
913
+ const divisibleBy = node.divisibleBy;
914
+ if (typeof divisibleBy === "number") {
915
+ const multipleOf = node.multipleOf;
916
+ if (typeof multipleOf === "number") {
917
+ if (ctx !== void 0 && divisibleBy !== multipleOf) emitDiagnostic(ctx.diagnostics, {
918
+ code: "divisible-by-conflict",
919
+ message: `Legacy \`divisibleBy\` (${String(divisibleBy)}) conflicts with \`multipleOf\` (${String(multipleOf)}); keeping \`multipleOf\``,
920
+ pointer: ctx.pointer,
921
+ detail: {
922
+ divisibleBy,
923
+ multipleOf
924
+ }
925
+ });
926
+ } else node.multipleOf = divisibleBy;
927
+ delete node.divisibleBy;
928
+ }
661
929
  if (typeof node.id === "string" && !("$id" in node)) {
662
930
  node.$id = node.id;
663
931
  delete node.id;
@@ -670,7 +938,42 @@ function normaliseDraft04Node(node) {
670
938
  delete node.additionalItems;
671
939
  }
672
940
  }
673
- splitDependencies(node);
941
+ splitDependencies(node, ctx, false);
942
+ validateDependentRequired(node, ctx);
943
+ }
944
+ /**
945
+ * Normalise Draft 04 `exclusiveMinimum`/`exclusiveMaximum` from boolean
946
+ * to number form, plus the other Draft 04 translations applied to a
947
+ * single node.
948
+ *
949
+ * In Draft 04:
950
+ * - `exclusiveMinimum: true` + `minimum: 5` → value must be > 5
951
+ * - `exclusiveMinimum: false` (or absent) + `minimum: 5` → value must be >= 5
952
+ *
953
+ * In Draft 06+:
954
+ * - `exclusiveMinimum: 5` → value must be > 5 (no separate `minimum`)
955
+ * - `minimum: 5` → value must be >= 5
956
+ *
957
+ * The transform converts boolean form to number form so the walker can
958
+ * treat `exclusiveMinimum`/`exclusiveMaximum` uniformly as numbers.
959
+ *
960
+ * This function preserves the no-context signature for the OpenAPI 3.0
961
+ * and Swagger 2.0 normalisers that compose it directly. The JSON Schema
962
+ * normalisation path uses {@link normaliseDraft04NodeWithContext} via
963
+ * {@link deepNormaliseWithContext} to thread diagnostics.
964
+ */
965
+ function normaliseDraft04Node(node) {
966
+ applyDraft04Translations(node, void 0);
967
+ return node;
968
+ }
969
+ /**
970
+ * Context-aware Draft 04 per-node transform. Identical to
971
+ * {@link normaliseDraft04Node} but threads a {@link NodeContext} so
972
+ * `divisibleBy`/`multipleOf` conflicts and invalid dependency entries
973
+ * can be surfaced as diagnostics with accurate pointers.
974
+ */
975
+ function normaliseDraft04NodeWithContext(node, ctx) {
976
+ applyDraft04Translations(node, ctx);
674
977
  return node;
675
978
  }
676
979
  /**
@@ -681,8 +984,9 @@ function normaliseDraft04Node(node) {
681
984
  * legacy `dependencies` keyword. Split it into `dependentRequired` /
682
985
  * `dependentSchemas` so the walker can process them uniformly.
683
986
  */
684
- function normaliseDraft06Or07Node(node) {
685
- splitDependencies(node);
987
+ function normaliseDraft06Or07NodeWithContext(node, ctx) {
988
+ splitDependencies(node, ctx, false);
989
+ validateDependentRequired(node, ctx);
686
990
  return node;
687
991
  }
688
992
  /**
@@ -693,22 +997,28 @@ function normaliseDraft06Or07Node(node) {
693
997
  * `$recursiveAnchor: true` — the normaliser converts
694
998
  * `$recursiveRef: "#"` to `$ref: "#"` pointing to the root.
695
999
  *
696
- * If a `$recursiveAnchor` name is given (non-empty string), the ref
697
- * is converted to `$ref: "#<anchor>"` so the existing $anchor
698
- * resolution in ref.ts can find it.
1000
+ * The original `$recursiveRef` value is preserved (rather than
1001
+ * collapsed to `"#"`) so that anchored variants such as
1002
+ * `$recursiveRef: "#meta"` resolve correctly against the
1003
+ * corresponding `$recursiveAnchor` name. String-valued
1004
+ * `$recursiveAnchor` names are likewise preserved as `$anchor`.
699
1005
  */
700
- function normaliseDraft201909Node(node) {
1006
+ function normaliseDraft201909NodeWithContext(node, ctx) {
701
1007
  if (typeof node.$recursiveRef === "string") {
702
- node.$ref = "#";
1008
+ node.$ref = node.$recursiveRef;
703
1009
  delete node.$recursiveRef;
704
1010
  }
705
1011
  if (node.$recursiveAnchor === true) {
706
1012
  if (typeof node.$anchor !== "string") node.$anchor = "__recursive__";
707
1013
  delete node.$recursiveAnchor;
1014
+ } else if (typeof node.$recursiveAnchor === "string") {
1015
+ if (typeof node.$anchor !== "string") node.$anchor = node.$recursiveAnchor;
1016
+ delete node.$recursiveAnchor;
708
1017
  }
1018
+ validateDependentRequired(node, ctx);
709
1019
  return node;
710
1020
  }
711
- function normaliseDynamicRefNode(node) {
1021
+ function normaliseDynamicRefNodeWithContext(node, ctx) {
712
1022
  if (typeof node.$dynamicRef === "string") {
713
1023
  node.$ref = node.$dynamicRef;
714
1024
  delete node.$dynamicRef;
@@ -717,19 +1027,30 @@ function normaliseDynamicRefNode(node) {
717
1027
  if (typeof node.$anchor !== "string") node.$anchor = node.$dynamicAnchor;
718
1028
  delete node.$dynamicAnchor;
719
1029
  }
1030
+ splitDependencies(node, ctx, true);
1031
+ validateDependentRequired(node, ctx);
720
1032
  return node;
721
1033
  }
722
1034
  /**
723
1035
  * Normalise a JSON Schema to canonical Draft 2020-12 form.
724
1036
  * Deep-clones the input — the original is never mutated.
1037
+ *
1038
+ * When `diagnostics` is supplied, per-node transforms emit diagnostics
1039
+ * for legacy-keyword rewrites and invalid constructs (e.g. `divisibleBy`
1040
+ * conflicts, non-string entries in a `dependentRequired` array, legacy
1041
+ * `dependencies` reaching the 2020-12 path).
725
1042
  */
726
- function normaliseJsonSchema(schema, draft) {
1043
+ function normaliseJsonSchema(schema, draft, diagnostics) {
1044
+ const ctx = {
1045
+ diagnostics,
1046
+ pointer: ""
1047
+ };
727
1048
  switch (draft) {
728
- case "draft-04": return deepNormalise(schema, normaliseDraft04Node);
729
- case "draft-2019-09": return deepNormalise(schema, normaliseDraft201909Node);
730
- case "draft-2020-12": return deepNormalise(schema, normaliseDynamicRefNode);
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);
731
1052
  case "draft-06":
732
- case "draft-07": return deepNormalise(schema, normaliseDraft06Or07Node);
1053
+ case "draft-07": return deepNormaliseWithContext(schema, normaliseDraft06Or07NodeWithContext, ctx);
733
1054
  }
734
1055
  }
735
1056
  /**
@@ -742,7 +1063,7 @@ function normaliseJsonSchema(schema, draft) {
742
1063
  function normaliseOpenApiSchemas(doc, version, diagnostics) {
743
1064
  if (isSwagger2(version)) return normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, diagnostics);
744
1065
  if (isOpenApi30(version)) return deepNormaliseOpenApi30Doc(doc, deepNormalise);
745
- return doc;
1066
+ return deepNormaliseOpenApiDoc(doc, (schema) => deepNormalise(schema, normaliseOpenApi30Discriminator));
746
1067
  }
747
1068
  //#endregion
748
- export { normaliseSwagger2Document as a, normaliseOpenApi30Discriminator as c, normaliseOpenApiSchemas as i, normaliseOpenApi30Node as l, normaliseDraft04Node as n, deepNormaliseOpenApi30Doc as o, normaliseJsonSchema as r, normaliseOpenApi30Combined as s, deepNormalise as t };
1069
+ export { normaliseOpenApiSchemas as a, deepNormaliseOpenApiDoc as c, normaliseOpenApi30Node as d, normaliseJsonSchema as i, normaliseOpenApi30Combined as l, deepNormaliseWithContext as n, normaliseSwagger2Document as o, normaliseDraft04Node as r, deepNormaliseOpenApi30Doc as s, deepNormalise as t, normaliseOpenApi30Discriminator as u };