@kubb/adapter-oas 5.12.7 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,11 +32,11 @@ yaml = __toESM(yaml, 1);
32
32
  //#region kubb-extension.json
33
33
  var compatibility = {
34
34
  "kubb": ">=5.8.3",
35
- "node": ">=22.12"
35
+ "node": ">=22.22"
36
36
  };
37
37
  //#endregion
38
38
  //#region package.json
39
- var version = "5.12.7";
39
+ var version = "5.13.0";
40
40
  //#endregion
41
41
  //#region src/constants.ts
42
42
  /**
@@ -630,8 +630,34 @@ async function validateDocument(document, { throwOnError = false } = {}) {
630
630
  message: compileErrors(result),
631
631
  help: "Fix each problem listed in the spec, then validate again."
632
632
  });
633
- for (const { message } of result.errors) reportInvalidSpec(message.replace(/\n.*/s, ""));
634
- if (result.additionalErrors > 0) reportInvalidSpec(`${result.additionalErrors} more validation errors are not listed.`);
633
+ reportValidationErrors(result);
634
+ }
635
+ function reportValidationErrors({ errors, additionalErrors }) {
636
+ for (const { message } of errors) reportInvalidSpec(message.replace(/\n.*/s, ""));
637
+ if (additionalErrors > 0) reportInvalidSpec(`${additionalErrors} more validation errors are not listed.`);
638
+ }
639
+ /**
640
+ * Validates like {@link validateDocument} without `throwOnError`, but in a worker thread, so the
641
+ * build keeps generating while `@readme/openapi-parser` dereferences and checks the spec. Await the
642
+ * returned promise inside the build's diagnostics scope to report its warnings there. Falls back to
643
+ * validating in place where worker threads are missing, as in edge runtimes.
644
+ */
645
+ async function validateDocumentInWorker(document) {
646
+ let message;
647
+ try {
648
+ const { Worker } = await import("node:worker_threads");
649
+ const workerFile = require("url").pathToFileURL(__filename).href.endsWith(".ts") ? "./validateWorker.ts" : "./validateWorker.js";
650
+ const worker = new Worker(new URL(workerFile, require("url").pathToFileURL(__filename).href), { workerData: document });
651
+ message = await new Promise((resolve, reject) => {
652
+ worker.once("message", resolve);
653
+ worker.once("error", reject);
654
+ worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`Validation worker exited with code ${code}`)));
655
+ }).finally(() => worker.terminate());
656
+ } catch {
657
+ return validateDocument(document);
658
+ }
659
+ if ("thrown" in message) return reportInvalidSpec(message.thrown);
660
+ if (!message.valid) reportValidationErrors(message);
635
661
  }
636
662
  //#endregion
637
663
  //#region src/oas.ts
@@ -720,7 +746,7 @@ function isJsonMimeType(mimeType) {
720
746
  //#endregion
721
747
  //#region src/model/components.ts
722
748
  /**
723
- * Extracts the inline schema from a media-type `content` map.
749
+ * Extracts the inline schema from a media-type `content` map, with the media type it sits under.
724
750
  *
725
751
  * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
726
752
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
@@ -728,15 +754,19 @@ function isJsonMimeType(mimeType) {
728
754
  * @example
729
755
  * ```ts
730
756
  * extractSchemaFromContent(operation.content, 'application/json')
731
- * // SchemaObject | null
757
+ * // { schema: SchemaObject, contentType: 'application/json' } | null
732
758
  * ```
733
759
  */
734
760
  function extractSchemaFromContent(content, preferredContentType) {
735
761
  if (!content) return null;
736
762
  const firstContentType = Object.keys(content)[0] ?? "application/json";
737
- const schema = content[preferredContentType ?? firstContentType]?.schema;
738
- if (isReference(schema)) return null;
739
- return schema ?? null;
763
+ const targetContentType = preferredContentType ?? firstContentType;
764
+ const schema = content[targetContentType]?.schema;
765
+ if (!schema || isReference(schema)) return null;
766
+ return {
767
+ schema,
768
+ contentType: targetContentType
769
+ };
740
770
  }
741
771
  /**
742
772
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
@@ -788,7 +818,7 @@ function* collectNestedDefinitions(schema, pointer, name) {
788
818
  const nestedName = `${name}_${key}`;
789
819
  yield {
790
820
  schema: definition,
791
- pointer: definitionPointer,
821
+ ref: definitionPointer,
792
822
  originalName: nestedName
793
823
  };
794
824
  yield* collectNestedDefinitions(definition, definitionPointer, nestedName);
@@ -866,25 +896,28 @@ function getSchemas(document, { contentType }, refs) {
866
896
  return resolved && !isReference(resolved) ? resolved : schema;
867
897
  }
868
898
  const candidates = [...Object.entries(components?.schemas ?? {}).flatMap(([name, schema]) => {
869
- const pointer = `#/components/schemas/${escapePointerToken(name)}`;
899
+ const ref = `#/components/schemas/${escapePointerToken(name)}`;
870
900
  return [{
871
901
  schema: resolveSchemaRef(schema),
872
902
  source: "schemas",
873
903
  originalName: name,
874
- pointer
875
- }, ...[...collectNestedDefinitions(schema, pointer, name)].map((nested) => ({
904
+ ref
905
+ }, ...[...collectNestedDefinitions(schema, ref, name)].map((nested) => ({
876
906
  ...nested,
877
907
  source: "schemas",
878
908
  nested: true
879
909
  }))];
880
910
  }), ...["responses", "requestBodies"].flatMap((source) => Object.entries(components?.[source] ?? {}).flatMap(([name, item]) => {
881
- const schema = extractSchemaFromContent(item.content, contentType);
882
- return schema ? [{
883
- schema: resolveSchemaRef(schema),
911
+ const extracted = extractSchemaFromContent(item.content, contentType);
912
+ if (!extracted) return [];
913
+ const ref = `#/components/${source}/${escapePointerToken(name)}`;
914
+ return [{
915
+ schema: resolveSchemaRef(extracted.schema),
884
916
  source,
885
917
  originalName: name,
886
- pointer: `#/components/${source}/${escapePointerToken(name)}`
887
- }] : [];
918
+ ref,
919
+ pointer: `${ref}/content/${escapePointerToken(extracted.contentType)}/schema`
920
+ }];
888
921
  }))];
889
922
  const normalizedNames = /* @__PURE__ */ new Map();
890
923
  for (const item of candidates) {
@@ -895,6 +928,7 @@ function getSchemas(document, { contentType }, refs) {
895
928
  }
896
929
  const schemas = {};
897
930
  const renames = /* @__PURE__ */ new Map();
931
+ const pointers = /* @__PURE__ */ new Map();
898
932
  for (const [, items] of normalizedNames) {
899
933
  const isSingle = items.length === 1;
900
934
  const hasMultipleSources = !isSingle && new Set(items.map((item) => item.source)).size > 1;
@@ -902,12 +936,14 @@ function getSchemas(document, { contentType }, refs) {
902
936
  const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index ? String(index + 1) : "";
903
937
  const uniqueName = item.originalName + suffix;
904
938
  schemas[uniqueName] = item.schema;
905
- if (suffix || item.nested) renames.set(item.pointer, uniqueName);
939
+ pointers.set(uniqueName, item.pointer ?? item.ref);
940
+ if (suffix || item.nested) renames.set(item.ref, uniqueName);
906
941
  });
907
942
  }
908
943
  return {
909
944
  schemas: sortSchemas(schemas, renames),
910
- renames
945
+ renames,
946
+ pointers
911
947
  };
912
948
  }
913
949
  //#endregion
@@ -964,6 +1000,115 @@ function resolveServerUrl(server, overrides) {
964
1000
  return url;
965
1001
  }
966
1002
  //#endregion
1003
+ //#region src/emit/schemaShape.ts
1004
+ /**
1005
+ * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
1006
+ */
1007
+ function isHandledFormat(format) {
1008
+ return formatMap[format] !== void 0 || specialCasedFormats.has(format);
1009
+ }
1010
+ /**
1011
+ * Resolves the `dateType` option down to the value for one format. The scalar form of
1012
+ * `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
1013
+ * individually and defaults an omitted key to `'string'`.
1014
+ */
1015
+ function resolveDateTypeValue(dateType, format) {
1016
+ if (typeof dateType !== "object" || dateType === null) return dateType;
1017
+ return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
1018
+ }
1019
+ /**
1020
+ * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1021
+ * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1022
+ * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
1023
+ */
1024
+ function extractExamples(schema) {
1025
+ if (Array.isArray(schema.examples)) return schema.examples;
1026
+ return schema.example !== void 0 ? [schema.example] : void 0;
1027
+ }
1028
+ function hasStructuralKeywords(fragment) {
1029
+ return Object.keys(fragment).some((key) => structuralKeys.has(key));
1030
+ }
1031
+ /**
1032
+ * Flattens a keyword-only `allOf` into its parent schema.
1033
+ *
1034
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
1035
+ * (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
1036
+ * original schema unchanged when flattening is unsafe.
1037
+ *
1038
+ * @example
1039
+ * ```ts
1040
+ * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
1041
+ * // { type: 'object', properties: {}, description: 'A pet' }
1042
+ * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
1043
+ * // returned unchanged, contains a $ref
1044
+ * ```
1045
+ */
1046
+ function flattenSchema(schema) {
1047
+ if (!schema.allOf?.length) return schema;
1048
+ const allOfFragments = schema.allOf;
1049
+ if (allOfFragments.some((item) => isReference(item))) return schema;
1050
+ if (allOfFragments.some(hasStructuralKeywords)) return schema;
1051
+ const { allOf: _allOf, ...rest } = schema;
1052
+ const merged = rest;
1053
+ for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
1054
+ return merged;
1055
+ }
1056
+ //#endregion
1057
+ //#region src/schemaDiagnostics.ts
1058
+ /**
1059
+ * Links one freshly converted schema and its nested schemas to their RFC 6901 pointers
1060
+ * (`#/components/schemas/Pet/properties/owner/properties/name`), and reports the advisory
1061
+ * diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) against them.
1062
+ * Refs are not followed, so the resolved schema is handled under its own walk.
1063
+ * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
1064
+ */
1065
+ function scanSchema(node, pointer = node.pointer) {
1066
+ if (pointer) visit(node, pointer);
1067
+ }
1068
+ function visit(node, pointer) {
1069
+ node.pointer ??= pointer;
1070
+ if (node.deprecated) _kubb_kit.Diagnostics.report({
1071
+ code: _kubb_kit.Diagnostics.code.deprecated,
1072
+ severity: "info",
1073
+ message: "This schema is marked as deprecated.",
1074
+ location: {
1075
+ kind: "schema",
1076
+ pointer
1077
+ }
1078
+ });
1079
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_kit.Diagnostics.report({
1080
+ code: _kubb_kit.Diagnostics.code.unsupportedFormat,
1081
+ severity: "warning",
1082
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
1083
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
1084
+ location: {
1085
+ kind: "schema",
1086
+ pointer
1087
+ }
1088
+ });
1089
+ if (node.type === "object") {
1090
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
1091
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
1092
+ return;
1093
+ }
1094
+ if (node.type === "array") {
1095
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
1096
+ return;
1097
+ }
1098
+ if (node.type === "tuple") {
1099
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
1100
+ return;
1101
+ }
1102
+ if (node.type === "not") {
1103
+ for (const member of node.members ?? []) visit(member, `${pointer}/not`);
1104
+ return;
1105
+ }
1106
+ if (node.type === "union" || node.type === "intersection") {
1107
+ const keyword = node.type === "intersection" ? "allOf" : node.strategy === "any" ? "anyOf" : "oneOf";
1108
+ for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/${keyword}/${index}`);
1109
+ }
1110
+ }
1111
+ //#endregion
967
1112
  //#region src/operation.ts
968
1113
  function slugify(value) {
969
1114
  return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
@@ -1013,11 +1158,18 @@ function getRequestContentType({ operation, refs }) {
1013
1158
  }
1014
1159
  /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
1015
1160
  function getParameters({ operation, refs }) {
1016
- const resolveParams = (params) => params.map((p) => refs.derefKeepingRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
1017
- const operationParams = resolveParams(operation.schema?.parameters || []);
1018
- const pathLevelParams = resolveParams(operation.pathItem.parameters ?? []);
1161
+ const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
1162
+ const param = refs.derefKeepingRef(entry);
1163
+ return param && typeof param === "object" && "in" in param && "name" in param ? [{
1164
+ param,
1165
+ pointer: `${pointer}/parameters/${index}`
1166
+ }] : [];
1167
+ });
1168
+ const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
1169
+ const operationParams = resolveParams(operation.schema?.parameters || [], `${pathPointer}/${operation.method.toLowerCase()}`);
1170
+ const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
1019
1171
  const paramMap = /* @__PURE__ */ new Map();
1020
- for (const p of [...pathLevelParams, ...operationParams]) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
1172
+ for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
1021
1173
  return Array.from(paramMap.values());
1022
1174
  }
1023
1175
  /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
@@ -1065,16 +1217,20 @@ function collectPropertyKeysByFlag(schema, flag) {
1065
1217
  }
1066
1218
  return keys.length ? keys : null;
1067
1219
  }
1068
- function parseParameter({ param, parentName, options, parseSchema }) {
1220
+ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1069
1221
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1070
- const schema = param.schema ? parseSchema({
1071
- schema: param.schema,
1222
+ const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1223
+ const rawSchema = param.schema ?? (contentType ? param.content?.[contentType]?.schema : void 0);
1224
+ const schema = rawSchema ? parseSchema({
1225
+ schema: rawSchema,
1072
1226
  name: schemaName
1073
1227
  }) : _kubb_kit.ast.factory.createSchema({ type: options.unknownType });
1228
+ if (rawSchema) scanSchema(schema, contentType ? `${pointer}/content/${escapePointerToken(contentType)}/schema` : `${pointer}/schema`);
1074
1229
  const style = param.style;
1075
1230
  const explode = param.explode;
1076
1231
  const allowReserved = param.allowReserved;
1077
1232
  return _kubb_kit.ast.factory.createParameter({
1233
+ pointer,
1078
1234
  name: param.name,
1079
1235
  in: param.in,
1080
1236
  schema: {
@@ -1084,7 +1240,8 @@ function parseParameter({ param, parentName, options, parseSchema }) {
1084
1240
  required: param.required ?? false,
1085
1241
  ...style !== void 0 ? { style } : {},
1086
1242
  ...explode !== void 0 ? { explode } : {},
1087
- ...allowReserved !== void 0 ? { allowReserved } : {}
1243
+ ...allowReserved !== void 0 ? { allowReserved } : {},
1244
+ ...contentType !== void 0 ? { contentType } : {}
1088
1245
  });
1089
1246
  }
1090
1247
  /**
@@ -1094,13 +1251,14 @@ function parseOperation({ operation, ...ctx }) {
1094
1251
  const { refs, contentType, options, parseSchema } = ctx;
1095
1252
  const operationId = getOperationId(operation);
1096
1253
  const operationName = operationId ? pascalCase(operationId) : void 0;
1254
+ const pointer = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
1097
1255
  const parameters = getParameters({
1098
1256
  operation,
1099
1257
  refs
1100
- }).map((param) => parseParameter({
1258
+ }).map((entry) => parseParameter({
1101
1259
  options,
1102
1260
  parseSchema,
1103
- param,
1261
+ ...entry,
1104
1262
  parentName: operationName
1105
1263
  }));
1106
1264
  const body = getRequestBody({
@@ -1116,16 +1274,19 @@ function parseOperation({ operation, ...ctx }) {
1116
1274
  refs
1117
1275
  });
1118
1276
  if (!schema) return [];
1277
+ const node = parseSchema({
1278
+ schema,
1279
+ name: requestBodyName
1280
+ });
1281
+ scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
1119
1282
  return [_kubb_kit.ast.factory.createContent({
1120
1283
  contentType: ct,
1121
- schema: _kubb_kit.ast.optionality(parseSchema({
1122
- schema,
1123
- name: requestBodyName
1124
- }), bodyRequired),
1284
+ schema: _kubb_kit.ast.optionality(node, bodyRequired),
1125
1285
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1126
1286
  })];
1127
1287
  });
1128
1288
  const requestBody = content.length > 0 || body?.description ? {
1289
+ pointer: `${pointer}/requestBody`,
1129
1290
  description: body?.description,
1130
1291
  required: bodyRequired || void 0,
1131
1292
  content: content.length > 0 ? content : void 0
@@ -1143,11 +1304,13 @@ function parseOperation({ operation, ...ctx }) {
1143
1304
  contentType: ct,
1144
1305
  refs
1145
1306
  });
1307
+ const node = raw && Object.keys(raw).length > 0 ? parseSchema({
1308
+ schema: raw,
1309
+ name: responseName
1310
+ }) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType });
1311
+ if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}/schema`);
1146
1312
  return {
1147
- schema: raw && Object.keys(raw).length > 0 ? parseSchema({
1148
- schema: raw,
1149
- name: responseName
1150
- }) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType }),
1313
+ schema: node,
1151
1314
  keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1152
1315
  };
1153
1316
  };
@@ -1163,6 +1326,7 @@ function parseOperation({ operation, ...ctx }) {
1163
1326
  ...parseEntrySchema(contentType)
1164
1327
  }));
1165
1328
  return _kubb_kit.ast.factory.createResponse({
1329
+ pointer: `${pointer}/responses/${statusCode}`,
1166
1330
  statusCode,
1167
1331
  description: response?.description,
1168
1332
  content: responseContent
@@ -1175,6 +1339,7 @@ function parseOperation({ operation, ...ctx }) {
1175
1339
  return typeof fallback === "string" ? fallback : void 0;
1176
1340
  };
1177
1341
  return _kubb_kit.ast.factory.createOperation({
1342
+ pointer,
1178
1343
  operationId,
1179
1344
  protocol: "http",
1180
1345
  method: operation.method.toUpperCase(),
@@ -1190,60 +1355,6 @@ function parseOperation({ operation, ...ctx }) {
1190
1355
  });
1191
1356
  }
1192
1357
  //#endregion
1193
- //#region src/emit/schemaShape.ts
1194
- /**
1195
- * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
1196
- */
1197
- function isHandledFormat(format) {
1198
- return formatMap[format] !== void 0 || specialCasedFormats.has(format);
1199
- }
1200
- /**
1201
- * Resolves the `dateType` option down to the value for one format. The scalar form of
1202
- * `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
1203
- * individually and defaults an omitted key to `'string'`.
1204
- */
1205
- function resolveDateTypeValue(dateType, format) {
1206
- if (typeof dateType !== "object" || dateType === null) return dateType;
1207
- return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
1208
- }
1209
- /**
1210
- * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1211
- * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1212
- * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
1213
- */
1214
- function extractExamples(schema) {
1215
- if (Array.isArray(schema.examples)) return schema.examples;
1216
- return schema.example !== void 0 ? [schema.example] : void 0;
1217
- }
1218
- function hasStructuralKeywords(fragment) {
1219
- return Object.keys(fragment).some((key) => structuralKeys.has(key));
1220
- }
1221
- /**
1222
- * Flattens a keyword-only `allOf` into its parent schema.
1223
- *
1224
- * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
1225
- * (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
1226
- * original schema unchanged when flattening is unsafe.
1227
- *
1228
- * @example
1229
- * ```ts
1230
- * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
1231
- * // { type: 'object', properties: {}, description: 'A pet' }
1232
- * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
1233
- * // returned unchanged, contains a $ref
1234
- * ```
1235
- */
1236
- function flattenSchema(schema) {
1237
- if (!schema.allOf?.length) return schema;
1238
- const allOfFragments = schema.allOf;
1239
- if (allOfFragments.some((item) => isReference(item))) return schema;
1240
- if (allOfFragments.some(hasStructuralKeywords)) return schema;
1241
- const { allOf: _allOf, ...rest } = schema;
1242
- const merged = rest;
1243
- for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
1244
- return merged;
1245
- }
1246
- //#endregion
1247
1358
  //#region src/emit/createNode.ts
1248
1359
  /**
1249
1360
  * Builds a schema node from a converter's base context plus its type-specific fields. Every
@@ -2353,55 +2464,6 @@ function createRefs(document) {
2353
2464
  };
2354
2465
  }
2355
2466
  //#endregion
2356
- //#region src/schemaDiagnostics.ts
2357
- /**
2358
- * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one freshly
2359
- * converted top-level schema.
2360
- *
2361
- * Walks the node the parser produced, threading the RFC 6901 pointer as it descends so a nested
2362
- * field reports against its full path (`#/components/schemas/Pet/properties/owner/properties/name`).
2363
- * Refs are not followed, so the resolved schema is reported under its own walk.
2364
- * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
2365
- */
2366
- function scanSchema({ node, name }) {
2367
- visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2368
- }
2369
- function visit(node, pointer) {
2370
- if (node.deprecated) _kubb_kit.Diagnostics.report({
2371
- code: _kubb_kit.Diagnostics.code.deprecated,
2372
- severity: "info",
2373
- message: "This schema is marked as deprecated.",
2374
- location: {
2375
- kind: "schema",
2376
- pointer
2377
- }
2378
- });
2379
- if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_kit.Diagnostics.report({
2380
- code: _kubb_kit.Diagnostics.code.unsupportedFormat,
2381
- severity: "warning",
2382
- message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2383
- help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2384
- location: {
2385
- kind: "schema",
2386
- pointer
2387
- }
2388
- });
2389
- if (node.type === "object") {
2390
- for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2391
- if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2392
- return;
2393
- }
2394
- if (node.type === "array") {
2395
- for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2396
- return;
2397
- }
2398
- if (node.type === "tuple") {
2399
- for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2400
- return;
2401
- }
2402
- if (node.type === "union" || node.type === "intersection" || node.type === "not") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2403
- }
2404
- //#endregion
2405
2467
  //#region src/sourceLocator.ts
2406
2468
  let cached;
2407
2469
  const toRange = ([start, end]) => ({
@@ -2442,17 +2504,122 @@ function findRefUsage(document, ref) {
2442
2504
  return found[0];
2443
2505
  }
2444
2506
  /**
2445
- * Maps OpenAPI JSON pointers and unresolved references to ranges in YAML or JSON source.
2507
+ * One pass over JSON text, so a located diagnostic in a large JSON spec skips `yaml.parseDocument`.
2508
+ * Holds the range of every key and array item by pointer, and the first usage of every `$ref`, as
2509
+ * `findPointer` and `findRefUsage` return them. Throws where the text breaks JSON syntax, such as a
2510
+ * comment, an unquoted key, or an unquoted `$ref` value. Other bare values, such as `object`, pass.
2446
2511
  */
2447
- const locate = (source, location) => {
2448
- if (!("pointer" in location)) return void 0;
2449
- if (cached?.source === source) cached.source = source;
2450
- else cached = {
2512
+ function indexJson(source) {
2513
+ const pointers = /* @__PURE__ */ new Map();
2514
+ const refs = /* @__PURE__ */ new Map();
2515
+ let at = source.indexOf("{");
2516
+ const fail = () => {
2517
+ throw new SyntaxError(`Unexpected character at ${at}`);
2518
+ };
2519
+ const skipSpace = () => {
2520
+ for (let code = source.charCodeAt(at); code === 32 || code === 9 || code === 10 || code === 13; code = source.charCodeAt(++at));
2521
+ };
2522
+ const readString = () => {
2523
+ if (source[at] !== "\"") fail();
2524
+ const start = at;
2525
+ for (at++; source.charCodeAt(at) !== 34; at++) {
2526
+ if (at >= source.length) fail();
2527
+ if (source.charCodeAt(at) === 92) at++;
2528
+ }
2529
+ const raw = source.slice(start + 1, at++);
2530
+ return {
2531
+ text: raw.includes("\\") ? JSON.parse(source.slice(start, at)) : raw,
2532
+ start,
2533
+ end: at
2534
+ };
2535
+ };
2536
+ const expect = (char) => {
2537
+ skipSpace();
2538
+ if (source[at++] !== char) fail();
2539
+ skipSpace();
2540
+ };
2541
+ const walk = (pointer) => {
2542
+ const open = source[at];
2543
+ if (open === "\"") {
2544
+ readString();
2545
+ return;
2546
+ }
2547
+ if (open !== "{" && open !== "[") {
2548
+ const start = at;
2549
+ while (at < source.length && !/[\s,\]}]/.test(source[at])) at++;
2550
+ if (at === start) fail();
2551
+ return;
2552
+ }
2553
+ const close = open === "{" ? "}" : "]";
2554
+ at++;
2555
+ for (let index = 0;; index++) {
2556
+ skipSpace();
2557
+ if (source[at] === close) break;
2558
+ if (index > 0) expect(",");
2559
+ if (open === "[") {
2560
+ const start = at;
2561
+ const next = `${pointer}/${index}`;
2562
+ walk(next);
2563
+ if (!pointers.has(next)) pointers.set(next, {
2564
+ start,
2565
+ end: at
2566
+ });
2567
+ continue;
2568
+ }
2569
+ const key = readString();
2570
+ const next = `${pointer}/${escapePointerToken(key.text)}`;
2571
+ if (!pointers.has(next)) pointers.set(next, {
2572
+ start: key.start,
2573
+ end: key.end
2574
+ });
2575
+ expect(":");
2576
+ if (key.text !== "$ref" || source[at] === "{" || source[at] === "[") {
2577
+ walk(next);
2578
+ continue;
2579
+ }
2580
+ const value = readString();
2581
+ if (!refs.has(value.text)) refs.set(value.text, {
2582
+ start: value.start,
2583
+ end: value.end
2584
+ });
2585
+ }
2586
+ at++;
2587
+ };
2588
+ walk("#");
2589
+ return {
2590
+ pointers,
2591
+ refs
2592
+ };
2593
+ }
2594
+ /** Indexes plain JSON text; anything else goes to the YAML parser. */
2595
+ function parse(source) {
2596
+ if (/^[ \t\n\r]*\{/.test(source)) try {
2597
+ return {
2598
+ source,
2599
+ json: indexJson(source)
2600
+ };
2601
+ } catch {}
2602
+ return {
2451
2603
  source,
2452
2604
  document: yaml.parseDocument(source)
2453
2605
  };
2606
+ }
2607
+ /**
2608
+ * Maps OpenAPI JSON pointers and unresolved references to ranges in YAML or JSON source.
2609
+ */
2610
+ const locate = (source, location) => {
2611
+ if (!("pointer" in location)) return void 0;
2612
+ cached = cached?.source === source ? {
2613
+ ...cached,
2614
+ source
2615
+ } : parse(source);
2616
+ const ref = "ref" in location ? location.ref : void 0;
2617
+ if ("json" in cached) {
2618
+ const { pointers, refs } = cached.json;
2619
+ return pointers.get(location.pointer) ?? (ref ? refs.get(ref) : void 0);
2620
+ }
2454
2621
  const { document } = cached;
2455
- return findPointer(document, location.pointer) ?? ("ref" in location && location.ref ? findRefUsage(document, location.ref) : void 0);
2622
+ return findPointer(document, location.pointer) ?? (ref ? findRefUsage(document, ref) : void 0);
2456
2623
  };
2457
2624
  //#endregion
2458
2625
  //#region src/adapter.ts
@@ -2509,25 +2676,26 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2509
2676
  }),
2510
2677
  build({ validate, contentType, server, discriminator, enums, ...parserOptions }) {
2511
2678
  let parsedDocument = null;
2679
+ const pendingValidations = [];
2512
2680
  const inputCache = /* @__PURE__ */ new WeakMap();
2513
2681
  /**
2514
2682
  * Parses every schema and operation once, resolving ref aliases and discriminator children from that same pass.
2515
2683
  */
2516
- function parseInput({ document, refs, schemas, parseSchema }) {
2684
+ function parseInput({ document, refs, schemas, pointers, parseSchema }) {
2517
2685
  const parsedByName = /* @__PURE__ */ new Map();
2518
2686
  const refAliasMap = /* @__PURE__ */ new Map();
2519
2687
  const enumNames = [];
2520
2688
  const discriminatorParentNodes = [];
2521
2689
  for (const [name, schema] of Object.entries(schemas)) {
2522
- const node = parseSchema({
2523
- schema,
2524
- name
2525
- });
2690
+ const node = {
2691
+ ...parseSchema({
2692
+ schema,
2693
+ name
2694
+ }),
2695
+ pointer: pointers.get(name)
2696
+ };
2526
2697
  parsedByName.set(name, node);
2527
- scanSchema({
2528
- node,
2529
- name
2530
- });
2698
+ scanSchema(node);
2531
2699
  if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2532
2700
  if (_kubb_kit.ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
2533
2701
  if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
@@ -2552,7 +2720,8 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2552
2720
  const base = child ? patchDiscriminatorNode(parsed, child) : parsed;
2553
2721
  const node = aliased ? {
2554
2722
  ...aliased,
2555
- name
2723
+ name,
2724
+ pointer: parsed.pointer
2556
2725
  } : base;
2557
2726
  schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
2558
2727
  }
@@ -2618,6 +2787,9 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2618
2787
  assertDocument(document);
2619
2788
  await validateDocument(document, options);
2620
2789
  },
2790
+ async settle() {
2791
+ await Promise.all(pendingValidations.splice(0));
2792
+ },
2621
2793
  async parse(source, { signal } = {}) {
2622
2794
  signal?.throwIfAborted();
2623
2795
  const cached = inputCache.get(source);
@@ -2626,14 +2798,15 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2626
2798
  const document = await parseFromConfig(source);
2627
2799
  signal?.throwIfAborted();
2628
2800
  assertDocument(document);
2629
- if (validate) await validateDocument(document);
2801
+ if (validate) pendingValidations.push(validateDocumentInWorker(document));
2630
2802
  parsedDocument = document;
2631
2803
  const refs = createRefs(document);
2632
- const { schemas, renames } = getSchemas(document, { contentType }, refs);
2804
+ const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
2633
2805
  return parseInput({
2634
2806
  document,
2635
2807
  refs,
2636
2808
  schemas,
2809
+ pointers,
2637
2810
  parseSchema: createSchemaParser({
2638
2811
  document,
2639
2812
  refs,