@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.js CHANGED
@@ -6,15 +6,21 @@ import { bundle } from "api-ref-bundler";
6
6
  import { CORE_SCHEMA, load } from "js-yaml";
7
7
  import * as yaml from "yaml";
8
8
  import { parse } from "yaml";
9
+ //#region \0rolldown/runtime.js
10
+ var __defProp = Object.defineProperty;
11
+ var __name = (target, value) => __defProp(target, "name", {
12
+ value,
13
+ configurable: true
14
+ });
9
15
  //#endregion
10
16
  //#region kubb-extension.json
11
17
  var compatibility = {
12
18
  "kubb": ">=5.8.3",
13
- "node": ">=22.12"
19
+ "node": ">=22.22"
14
20
  };
15
21
  //#endregion
16
22
  //#region package.json
17
- var version = "5.12.7";
23
+ var version = "5.13.0";
18
24
  //#endregion
19
25
  //#region src/constants.ts
20
26
  /**
@@ -608,8 +614,34 @@ async function validateDocument(document, { throwOnError = false } = {}) {
608
614
  message: compileErrors(result),
609
615
  help: "Fix each problem listed in the spec, then validate again."
610
616
  });
611
- for (const { message } of result.errors) reportInvalidSpec(message.replace(/\n.*/s, ""));
612
- if (result.additionalErrors > 0) reportInvalidSpec(`${result.additionalErrors} more validation errors are not listed.`);
617
+ reportValidationErrors(result);
618
+ }
619
+ function reportValidationErrors({ errors, additionalErrors }) {
620
+ for (const { message } of errors) reportInvalidSpec(message.replace(/\n.*/s, ""));
621
+ if (additionalErrors > 0) reportInvalidSpec(`${additionalErrors} more validation errors are not listed.`);
622
+ }
623
+ /**
624
+ * Validates like {@link validateDocument} without `throwOnError`, but in a worker thread, so the
625
+ * build keeps generating while `@readme/openapi-parser` dereferences and checks the spec. Await the
626
+ * returned promise inside the build's diagnostics scope to report its warnings there. Falls back to
627
+ * validating in place where worker threads are missing, as in edge runtimes.
628
+ */
629
+ async function validateDocumentInWorker(document) {
630
+ let message;
631
+ try {
632
+ const { Worker } = await import("node:worker_threads");
633
+ const workerFile = import.meta.url.endsWith(".ts") ? "./validateWorker.ts" : "./validateWorker.js";
634
+ const worker = new Worker(new URL(workerFile, import.meta.url), { workerData: document });
635
+ message = await new Promise((resolve, reject) => {
636
+ worker.once("message", resolve);
637
+ worker.once("error", reject);
638
+ worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`Validation worker exited with code ${code}`)));
639
+ }).finally(() => worker.terminate());
640
+ } catch {
641
+ return validateDocument(document);
642
+ }
643
+ if ("thrown" in message) return reportInvalidSpec(message.thrown);
644
+ if (!message.valid) reportValidationErrors(message);
613
645
  }
614
646
  //#endregion
615
647
  //#region src/oas.ts
@@ -698,7 +730,7 @@ function isJsonMimeType(mimeType) {
698
730
  //#endregion
699
731
  //#region src/model/components.ts
700
732
  /**
701
- * Extracts the inline schema from a media-type `content` map.
733
+ * Extracts the inline schema from a media-type `content` map, with the media type it sits under.
702
734
  *
703
735
  * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
704
736
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
@@ -706,15 +738,19 @@ function isJsonMimeType(mimeType) {
706
738
  * @example
707
739
  * ```ts
708
740
  * extractSchemaFromContent(operation.content, 'application/json')
709
- * // SchemaObject | null
741
+ * // { schema: SchemaObject, contentType: 'application/json' } | null
710
742
  * ```
711
743
  */
712
744
  function extractSchemaFromContent(content, preferredContentType) {
713
745
  if (!content) return null;
714
746
  const firstContentType = Object.keys(content)[0] ?? "application/json";
715
- const schema = content[preferredContentType ?? firstContentType]?.schema;
716
- if (isReference(schema)) return null;
717
- return schema ?? null;
747
+ const targetContentType = preferredContentType ?? firstContentType;
748
+ const schema = content[targetContentType]?.schema;
749
+ if (!schema || isReference(schema)) return null;
750
+ return {
751
+ schema,
752
+ contentType: targetContentType
753
+ };
718
754
  }
719
755
  /**
720
756
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
@@ -766,7 +802,7 @@ function* collectNestedDefinitions(schema, pointer, name) {
766
802
  const nestedName = `${name}_${key}`;
767
803
  yield {
768
804
  schema: definition,
769
- pointer: definitionPointer,
805
+ ref: definitionPointer,
770
806
  originalName: nestedName
771
807
  };
772
808
  yield* collectNestedDefinitions(definition, definitionPointer, nestedName);
@@ -844,25 +880,28 @@ function getSchemas(document, { contentType }, refs) {
844
880
  return resolved && !isReference(resolved) ? resolved : schema;
845
881
  }
846
882
  const candidates = [...Object.entries(components?.schemas ?? {}).flatMap(([name, schema]) => {
847
- const pointer = `#/components/schemas/${escapePointerToken(name)}`;
883
+ const ref = `#/components/schemas/${escapePointerToken(name)}`;
848
884
  return [{
849
885
  schema: resolveSchemaRef(schema),
850
886
  source: "schemas",
851
887
  originalName: name,
852
- pointer
853
- }, ...[...collectNestedDefinitions(schema, pointer, name)].map((nested) => ({
888
+ ref
889
+ }, ...[...collectNestedDefinitions(schema, ref, name)].map((nested) => ({
854
890
  ...nested,
855
891
  source: "schemas",
856
892
  nested: true
857
893
  }))];
858
894
  }), ...["responses", "requestBodies"].flatMap((source) => Object.entries(components?.[source] ?? {}).flatMap(([name, item]) => {
859
- const schema = extractSchemaFromContent(item.content, contentType);
860
- return schema ? [{
861
- schema: resolveSchemaRef(schema),
895
+ const extracted = extractSchemaFromContent(item.content, contentType);
896
+ if (!extracted) return [];
897
+ const ref = `#/components/${source}/${escapePointerToken(name)}`;
898
+ return [{
899
+ schema: resolveSchemaRef(extracted.schema),
862
900
  source,
863
901
  originalName: name,
864
- pointer: `#/components/${source}/${escapePointerToken(name)}`
865
- }] : [];
902
+ ref,
903
+ pointer: `${ref}/content/${escapePointerToken(extracted.contentType)}/schema`
904
+ }];
866
905
  }))];
867
906
  const normalizedNames = /* @__PURE__ */ new Map();
868
907
  for (const item of candidates) {
@@ -873,6 +912,7 @@ function getSchemas(document, { contentType }, refs) {
873
912
  }
874
913
  const schemas = {};
875
914
  const renames = /* @__PURE__ */ new Map();
915
+ const pointers = /* @__PURE__ */ new Map();
876
916
  for (const [, items] of normalizedNames) {
877
917
  const isSingle = items.length === 1;
878
918
  const hasMultipleSources = !isSingle && new Set(items.map((item) => item.source)).size > 1;
@@ -880,12 +920,14 @@ function getSchemas(document, { contentType }, refs) {
880
920
  const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index ? String(index + 1) : "";
881
921
  const uniqueName = item.originalName + suffix;
882
922
  schemas[uniqueName] = item.schema;
883
- if (suffix || item.nested) renames.set(item.pointer, uniqueName);
923
+ pointers.set(uniqueName, item.pointer ?? item.ref);
924
+ if (suffix || item.nested) renames.set(item.ref, uniqueName);
884
925
  });
885
926
  }
886
927
  return {
887
928
  schemas: sortSchemas(schemas, renames),
888
- renames
929
+ renames,
930
+ pointers
889
931
  };
890
932
  }
891
933
  //#endregion
@@ -942,6 +984,115 @@ function resolveServerUrl(server, overrides) {
942
984
  return url;
943
985
  }
944
986
  //#endregion
987
+ //#region src/emit/schemaShape.ts
988
+ /**
989
+ * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
990
+ */
991
+ function isHandledFormat(format) {
992
+ return formatMap[format] !== void 0 || specialCasedFormats.has(format);
993
+ }
994
+ /**
995
+ * Resolves the `dateType` option down to the value for one format. The scalar form of
996
+ * `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
997
+ * individually and defaults an omitted key to `'string'`.
998
+ */
999
+ function resolveDateTypeValue(dateType, format) {
1000
+ if (typeof dateType !== "object" || dateType === null) return dateType;
1001
+ return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
1002
+ }
1003
+ /**
1004
+ * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1005
+ * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1006
+ * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
1007
+ */
1008
+ function extractExamples(schema) {
1009
+ if (Array.isArray(schema.examples)) return schema.examples;
1010
+ return schema.example !== void 0 ? [schema.example] : void 0;
1011
+ }
1012
+ function hasStructuralKeywords(fragment) {
1013
+ return Object.keys(fragment).some((key) => structuralKeys.has(key));
1014
+ }
1015
+ /**
1016
+ * Flattens a keyword-only `allOf` into its parent schema.
1017
+ *
1018
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
1019
+ * (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
1020
+ * original schema unchanged when flattening is unsafe.
1021
+ *
1022
+ * @example
1023
+ * ```ts
1024
+ * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
1025
+ * // { type: 'object', properties: {}, description: 'A pet' }
1026
+ * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
1027
+ * // returned unchanged, contains a $ref
1028
+ * ```
1029
+ */
1030
+ function flattenSchema(schema) {
1031
+ if (!schema.allOf?.length) return schema;
1032
+ const allOfFragments = schema.allOf;
1033
+ if (allOfFragments.some((item) => isReference(item))) return schema;
1034
+ if (allOfFragments.some(hasStructuralKeywords)) return schema;
1035
+ const { allOf: _allOf, ...rest } = schema;
1036
+ const merged = rest;
1037
+ for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
1038
+ return merged;
1039
+ }
1040
+ //#endregion
1041
+ //#region src/schemaDiagnostics.ts
1042
+ /**
1043
+ * Links one freshly converted schema and its nested schemas to their RFC 6901 pointers
1044
+ * (`#/components/schemas/Pet/properties/owner/properties/name`), and reports the advisory
1045
+ * diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) against them.
1046
+ * Refs are not followed, so the resolved schema is handled under its own walk.
1047
+ * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
1048
+ */
1049
+ function scanSchema(node, pointer = node.pointer) {
1050
+ if (pointer) visit(node, pointer);
1051
+ }
1052
+ function visit(node, pointer) {
1053
+ node.pointer ??= pointer;
1054
+ if (node.deprecated) Diagnostics.report({
1055
+ code: Diagnostics.code.deprecated,
1056
+ severity: "info",
1057
+ message: "This schema is marked as deprecated.",
1058
+ location: {
1059
+ kind: "schema",
1060
+ pointer
1061
+ }
1062
+ });
1063
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
1064
+ code: Diagnostics.code.unsupportedFormat,
1065
+ severity: "warning",
1066
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
1067
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
1068
+ location: {
1069
+ kind: "schema",
1070
+ pointer
1071
+ }
1072
+ });
1073
+ if (node.type === "object") {
1074
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
1075
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
1076
+ return;
1077
+ }
1078
+ if (node.type === "array") {
1079
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
1080
+ return;
1081
+ }
1082
+ if (node.type === "tuple") {
1083
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
1084
+ return;
1085
+ }
1086
+ if (node.type === "not") {
1087
+ for (const member of node.members ?? []) visit(member, `${pointer}/not`);
1088
+ return;
1089
+ }
1090
+ if (node.type === "union" || node.type === "intersection") {
1091
+ const keyword = node.type === "intersection" ? "allOf" : node.strategy === "any" ? "anyOf" : "oneOf";
1092
+ for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/${keyword}/${index}`);
1093
+ }
1094
+ }
1095
+ //#endregion
945
1096
  //#region src/operation.ts
946
1097
  function slugify(value) {
947
1098
  return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
@@ -991,11 +1142,18 @@ function getRequestContentType({ operation, refs }) {
991
1142
  }
992
1143
  /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
993
1144
  function getParameters({ operation, refs }) {
994
- const resolveParams = (params) => params.map((p) => refs.derefKeepingRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
995
- const operationParams = resolveParams(operation.schema?.parameters || []);
996
- const pathLevelParams = resolveParams(operation.pathItem.parameters ?? []);
1145
+ const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
1146
+ const param = refs.derefKeepingRef(entry);
1147
+ return param && typeof param === "object" && "in" in param && "name" in param ? [{
1148
+ param,
1149
+ pointer: `${pointer}/parameters/${index}`
1150
+ }] : [];
1151
+ });
1152
+ const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
1153
+ const operationParams = resolveParams(operation.schema?.parameters || [], `${pathPointer}/${operation.method.toLowerCase()}`);
1154
+ const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
997
1155
  const paramMap = /* @__PURE__ */ new Map();
998
- for (const p of [...pathLevelParams, ...operationParams]) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
1156
+ for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
999
1157
  return Array.from(paramMap.values());
1000
1158
  }
1001
1159
  /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
@@ -1043,16 +1201,20 @@ function collectPropertyKeysByFlag(schema, flag) {
1043
1201
  }
1044
1202
  return keys.length ? keys : null;
1045
1203
  }
1046
- function parseParameter({ param, parentName, options, parseSchema }) {
1204
+ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1047
1205
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1048
- const schema = param.schema ? parseSchema({
1049
- schema: param.schema,
1206
+ const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1207
+ const rawSchema = param.schema ?? (contentType ? param.content?.[contentType]?.schema : void 0);
1208
+ const schema = rawSchema ? parseSchema({
1209
+ schema: rawSchema,
1050
1210
  name: schemaName
1051
1211
  }) : ast.factory.createSchema({ type: options.unknownType });
1212
+ if (rawSchema) scanSchema(schema, contentType ? `${pointer}/content/${escapePointerToken(contentType)}/schema` : `${pointer}/schema`);
1052
1213
  const style = param.style;
1053
1214
  const explode = param.explode;
1054
1215
  const allowReserved = param.allowReserved;
1055
1216
  return ast.factory.createParameter({
1217
+ pointer,
1056
1218
  name: param.name,
1057
1219
  in: param.in,
1058
1220
  schema: {
@@ -1062,7 +1224,8 @@ function parseParameter({ param, parentName, options, parseSchema }) {
1062
1224
  required: param.required ?? false,
1063
1225
  ...style !== void 0 ? { style } : {},
1064
1226
  ...explode !== void 0 ? { explode } : {},
1065
- ...allowReserved !== void 0 ? { allowReserved } : {}
1227
+ ...allowReserved !== void 0 ? { allowReserved } : {},
1228
+ ...contentType !== void 0 ? { contentType } : {}
1066
1229
  });
1067
1230
  }
1068
1231
  /**
@@ -1072,13 +1235,14 @@ function parseOperation({ operation, ...ctx }) {
1072
1235
  const { refs, contentType, options, parseSchema } = ctx;
1073
1236
  const operationId = getOperationId(operation);
1074
1237
  const operationName = operationId ? pascalCase(operationId) : void 0;
1238
+ const pointer = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
1075
1239
  const parameters = getParameters({
1076
1240
  operation,
1077
1241
  refs
1078
- }).map((param) => parseParameter({
1242
+ }).map((entry) => parseParameter({
1079
1243
  options,
1080
1244
  parseSchema,
1081
- param,
1245
+ ...entry,
1082
1246
  parentName: operationName
1083
1247
  }));
1084
1248
  const body = getRequestBody({
@@ -1094,16 +1258,19 @@ function parseOperation({ operation, ...ctx }) {
1094
1258
  refs
1095
1259
  });
1096
1260
  if (!schema) return [];
1261
+ const node = parseSchema({
1262
+ schema,
1263
+ name: requestBodyName
1264
+ });
1265
+ scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
1097
1266
  return [ast.factory.createContent({
1098
1267
  contentType: ct,
1099
- schema: ast.optionality(parseSchema({
1100
- schema,
1101
- name: requestBodyName
1102
- }), bodyRequired),
1268
+ schema: ast.optionality(node, bodyRequired),
1103
1269
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1104
1270
  })];
1105
1271
  });
1106
1272
  const requestBody = content.length > 0 || body?.description ? {
1273
+ pointer: `${pointer}/requestBody`,
1107
1274
  description: body?.description,
1108
1275
  required: bodyRequired || void 0,
1109
1276
  content: content.length > 0 ? content : void 0
@@ -1121,11 +1288,13 @@ function parseOperation({ operation, ...ctx }) {
1121
1288
  contentType: ct,
1122
1289
  refs
1123
1290
  });
1291
+ const node = raw && Object.keys(raw).length > 0 ? parseSchema({
1292
+ schema: raw,
1293
+ name: responseName
1294
+ }) : ast.factory.createSchema({ type: options.emptySchemaType });
1295
+ if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}/schema`);
1124
1296
  return {
1125
- schema: raw && Object.keys(raw).length > 0 ? parseSchema({
1126
- schema: raw,
1127
- name: responseName
1128
- }) : ast.factory.createSchema({ type: options.emptySchemaType }),
1297
+ schema: node,
1129
1298
  keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1130
1299
  };
1131
1300
  };
@@ -1141,6 +1310,7 @@ function parseOperation({ operation, ...ctx }) {
1141
1310
  ...parseEntrySchema(contentType)
1142
1311
  }));
1143
1312
  return ast.factory.createResponse({
1313
+ pointer: `${pointer}/responses/${statusCode}`,
1144
1314
  statusCode,
1145
1315
  description: response?.description,
1146
1316
  content: responseContent
@@ -1153,6 +1323,7 @@ function parseOperation({ operation, ...ctx }) {
1153
1323
  return typeof fallback === "string" ? fallback : void 0;
1154
1324
  };
1155
1325
  return ast.factory.createOperation({
1326
+ pointer,
1156
1327
  operationId,
1157
1328
  protocol: "http",
1158
1329
  method: operation.method.toUpperCase(),
@@ -1168,60 +1339,6 @@ function parseOperation({ operation, ...ctx }) {
1168
1339
  });
1169
1340
  }
1170
1341
  //#endregion
1171
- //#region src/emit/schemaShape.ts
1172
- /**
1173
- * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
1174
- */
1175
- function isHandledFormat(format) {
1176
- return formatMap[format] !== void 0 || specialCasedFormats.has(format);
1177
- }
1178
- /**
1179
- * Resolves the `dateType` option down to the value for one format. The scalar form of
1180
- * `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
1181
- * individually and defaults an omitted key to `'string'`.
1182
- */
1183
- function resolveDateTypeValue(dateType, format) {
1184
- if (typeof dateType !== "object" || dateType === null) return dateType;
1185
- return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
1186
- }
1187
- /**
1188
- * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1189
- * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1190
- * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
1191
- */
1192
- function extractExamples(schema) {
1193
- if (Array.isArray(schema.examples)) return schema.examples;
1194
- return schema.example !== void 0 ? [schema.example] : void 0;
1195
- }
1196
- function hasStructuralKeywords(fragment) {
1197
- return Object.keys(fragment).some((key) => structuralKeys.has(key));
1198
- }
1199
- /**
1200
- * Flattens a keyword-only `allOf` into its parent schema.
1201
- *
1202
- * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
1203
- * (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
1204
- * original schema unchanged when flattening is unsafe.
1205
- *
1206
- * @example
1207
- * ```ts
1208
- * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
1209
- * // { type: 'object', properties: {}, description: 'A pet' }
1210
- * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
1211
- * // returned unchanged, contains a $ref
1212
- * ```
1213
- */
1214
- function flattenSchema(schema) {
1215
- if (!schema.allOf?.length) return schema;
1216
- const allOfFragments = schema.allOf;
1217
- if (allOfFragments.some((item) => isReference(item))) return schema;
1218
- if (allOfFragments.some(hasStructuralKeywords)) return schema;
1219
- const { allOf: _allOf, ...rest } = schema;
1220
- const merged = rest;
1221
- for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
1222
- return merged;
1223
- }
1224
- //#endregion
1225
1342
  //#region src/emit/createNode.ts
1226
1343
  /**
1227
1344
  * Builds a schema node from a converter's base context plus its type-specific fields. Every
@@ -2331,55 +2448,6 @@ function createRefs(document) {
2331
2448
  };
2332
2449
  }
2333
2450
  //#endregion
2334
- //#region src/schemaDiagnostics.ts
2335
- /**
2336
- * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one freshly
2337
- * converted top-level schema.
2338
- *
2339
- * Walks the node the parser produced, threading the RFC 6901 pointer as it descends so a nested
2340
- * field reports against its full path (`#/components/schemas/Pet/properties/owner/properties/name`).
2341
- * Refs are not followed, so the resolved schema is reported under its own walk.
2342
- * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
2343
- */
2344
- function scanSchema({ node, name }) {
2345
- visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2346
- }
2347
- function visit(node, pointer) {
2348
- if (node.deprecated) Diagnostics.report({
2349
- code: Diagnostics.code.deprecated,
2350
- severity: "info",
2351
- message: "This schema is marked as deprecated.",
2352
- location: {
2353
- kind: "schema",
2354
- pointer
2355
- }
2356
- });
2357
- if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
2358
- code: Diagnostics.code.unsupportedFormat,
2359
- severity: "warning",
2360
- message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2361
- help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2362
- location: {
2363
- kind: "schema",
2364
- pointer
2365
- }
2366
- });
2367
- if (node.type === "object") {
2368
- for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2369
- if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2370
- return;
2371
- }
2372
- if (node.type === "array") {
2373
- for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2374
- return;
2375
- }
2376
- if (node.type === "tuple") {
2377
- for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2378
- return;
2379
- }
2380
- if (node.type === "union" || node.type === "intersection" || node.type === "not") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2381
- }
2382
- //#endregion
2383
2451
  //#region src/sourceLocator.ts
2384
2452
  let cached;
2385
2453
  const toRange = ([start, end]) => ({
@@ -2420,17 +2488,123 @@ function findRefUsage(document, ref) {
2420
2488
  return found[0];
2421
2489
  }
2422
2490
  /**
2423
- * Maps OpenAPI JSON pointers and unresolved references to ranges in YAML or JSON source.
2491
+ * One pass over JSON text, so a located diagnostic in a large JSON spec skips `yaml.parseDocument`.
2492
+ * Holds the range of every key and array item by pointer, and the first usage of every `$ref`, as
2493
+ * `findPointer` and `findRefUsage` return them. Throws where the text breaks JSON syntax, such as a
2494
+ * comment, an unquoted key, or an unquoted `$ref` value. Other bare values, such as `object`, pass.
2424
2495
  */
2425
- const locate = (source, location) => {
2426
- if (!("pointer" in location)) return void 0;
2427
- if (cached?.source === source) cached.source = source;
2428
- else cached = {
2496
+ function indexJson(source) {
2497
+ const pointers = /* @__PURE__ */ new Map();
2498
+ const refs = /* @__PURE__ */ new Map();
2499
+ let at = source.indexOf("{");
2500
+ const fail = () => {
2501
+ throw new SyntaxError(`Unexpected character at ${at}`);
2502
+ };
2503
+ const skipSpace = () => {
2504
+ for (let code = source.charCodeAt(at); code === 32 || code === 9 || code === 10 || code === 13; code = source.charCodeAt(++at));
2505
+ };
2506
+ const readString = () => {
2507
+ if (source[at] !== "\"") fail();
2508
+ const start = at;
2509
+ for (at++; source.charCodeAt(at) !== 34; at++) {
2510
+ if (at >= source.length) fail();
2511
+ if (source.charCodeAt(at) === 92) at++;
2512
+ }
2513
+ const raw = source.slice(start + 1, at++);
2514
+ return {
2515
+ text: raw.includes("\\") ? JSON.parse(source.slice(start, at)) : raw,
2516
+ start,
2517
+ end: at
2518
+ };
2519
+ };
2520
+ const expect = (char) => {
2521
+ skipSpace();
2522
+ if (source[at++] !== char) fail();
2523
+ skipSpace();
2524
+ };
2525
+ const walk = (pointer) => {
2526
+ const open = source[at];
2527
+ if (open === "\"") {
2528
+ readString();
2529
+ return;
2530
+ }
2531
+ if (open !== "{" && open !== "[") {
2532
+ const start = at;
2533
+ while (at < source.length && !/[\s,\]}]/.test(source[at])) at++;
2534
+ if (at === start) fail();
2535
+ return;
2536
+ }
2537
+ const close = open === "{" ? "}" : "]";
2538
+ at++;
2539
+ for (let index = 0;; index++) {
2540
+ skipSpace();
2541
+ if (source[at] === close) break;
2542
+ if (index > 0) expect(",");
2543
+ if (open === "[") {
2544
+ const start = at;
2545
+ const next = `${pointer}/${index}`;
2546
+ walk(next);
2547
+ if (!pointers.has(next)) pointers.set(next, {
2548
+ start,
2549
+ end: at
2550
+ });
2551
+ continue;
2552
+ }
2553
+ const key = readString();
2554
+ const next = `${pointer}/${escapePointerToken(key.text)}`;
2555
+ if (!pointers.has(next)) pointers.set(next, {
2556
+ start: key.start,
2557
+ end: key.end
2558
+ });
2559
+ expect(":");
2560
+ if (key.text !== "$ref" || source[at] === "{" || source[at] === "[") {
2561
+ walk(next);
2562
+ continue;
2563
+ }
2564
+ const value = readString();
2565
+ if (!refs.has(value.text)) refs.set(value.text, {
2566
+ start: value.start,
2567
+ end: value.end
2568
+ });
2569
+ }
2570
+ at++;
2571
+ };
2572
+ walk("#");
2573
+ return {
2574
+ pointers,
2575
+ refs
2576
+ };
2577
+ }
2578
+ /** Indexes plain JSON text; anything else goes to the YAML parser. */
2579
+ function parse$1(source) {
2580
+ if (/^[ \t\n\r]*\{/.test(source)) try {
2581
+ return {
2582
+ source,
2583
+ json: indexJson(source)
2584
+ };
2585
+ } catch {}
2586
+ return {
2429
2587
  source,
2430
2588
  document: yaml.parseDocument(source)
2431
2589
  };
2590
+ }
2591
+ __name(parse$1, "parse");
2592
+ /**
2593
+ * Maps OpenAPI JSON pointers and unresolved references to ranges in YAML or JSON source.
2594
+ */
2595
+ const locate = (source, location) => {
2596
+ if (!("pointer" in location)) return void 0;
2597
+ cached = cached?.source === source ? {
2598
+ ...cached,
2599
+ source
2600
+ } : parse$1(source);
2601
+ const ref = "ref" in location ? location.ref : void 0;
2602
+ if ("json" in cached) {
2603
+ const { pointers, refs } = cached.json;
2604
+ return pointers.get(location.pointer) ?? (ref ? refs.get(ref) : void 0);
2605
+ }
2432
2606
  const { document } = cached;
2433
- return findPointer(document, location.pointer) ?? ("ref" in location && location.ref ? findRefUsage(document, location.ref) : void 0);
2607
+ return findPointer(document, location.pointer) ?? (ref ? findRefUsage(document, ref) : void 0);
2434
2608
  };
2435
2609
  //#endregion
2436
2610
  //#region src/adapter.ts
@@ -2487,25 +2661,26 @@ const adapterOas = defineExtension({
2487
2661
  }),
2488
2662
  build({ validate, contentType, server, discriminator, enums, ...parserOptions }) {
2489
2663
  let parsedDocument = null;
2664
+ const pendingValidations = [];
2490
2665
  const inputCache = /* @__PURE__ */ new WeakMap();
2491
2666
  /**
2492
2667
  * Parses every schema and operation once, resolving ref aliases and discriminator children from that same pass.
2493
2668
  */
2494
- function parseInput({ document, refs, schemas, parseSchema }) {
2669
+ function parseInput({ document, refs, schemas, pointers, parseSchema }) {
2495
2670
  const parsedByName = /* @__PURE__ */ new Map();
2496
2671
  const refAliasMap = /* @__PURE__ */ new Map();
2497
2672
  const enumNames = [];
2498
2673
  const discriminatorParentNodes = [];
2499
2674
  for (const [name, schema] of Object.entries(schemas)) {
2500
- const node = parseSchema({
2501
- schema,
2502
- name
2503
- });
2675
+ const node = {
2676
+ ...parseSchema({
2677
+ schema,
2678
+ name
2679
+ }),
2680
+ pointer: pointers.get(name)
2681
+ };
2504
2682
  parsedByName.set(name, node);
2505
- scanSchema({
2506
- node,
2507
- name
2508
- });
2683
+ scanSchema(node);
2509
2684
  if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2510
2685
  if (ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
2511
2686
  if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
@@ -2530,7 +2705,8 @@ const adapterOas = defineExtension({
2530
2705
  const base = child ? patchDiscriminatorNode(parsed, child) : parsed;
2531
2706
  const node = aliased ? {
2532
2707
  ...aliased,
2533
- name
2708
+ name,
2709
+ pointer: parsed.pointer
2534
2710
  } : base;
2535
2711
  schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
2536
2712
  }
@@ -2596,6 +2772,9 @@ const adapterOas = defineExtension({
2596
2772
  assertDocument(document);
2597
2773
  await validateDocument(document, options);
2598
2774
  },
2775
+ async settle() {
2776
+ await Promise.all(pendingValidations.splice(0));
2777
+ },
2599
2778
  async parse(source, { signal } = {}) {
2600
2779
  signal?.throwIfAborted();
2601
2780
  const cached = inputCache.get(source);
@@ -2604,14 +2783,15 @@ const adapterOas = defineExtension({
2604
2783
  const document = await parseFromConfig(source);
2605
2784
  signal?.throwIfAborted();
2606
2785
  assertDocument(document);
2607
- if (validate) await validateDocument(document);
2786
+ if (validate) pendingValidations.push(validateDocumentInWorker(document));
2608
2787
  parsedDocument = document;
2609
2788
  const refs = createRefs(document);
2610
- const { schemas, renames } = getSchemas(document, { contentType }, refs);
2789
+ const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
2611
2790
  return parseInput({
2612
2791
  document,
2613
2792
  refs,
2614
2793
  schemas,
2794
+ pointers,
2615
2795
  parseSchema: createSchemaParser({
2616
2796
  document,
2617
2797
  refs,