@kubb/adapter-oas 5.9.2 → 5.9.4

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
@@ -2,9 +2,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#endregion
3
3
  let _kubb_kit = require("@kubb/kit");
4
4
  let node_fs_promises = require("node:fs/promises");
5
- let yaml = require("yaml");
6
5
  let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
7
6
  let api_ref_bundler = require("api-ref-bundler");
7
+ let yaml = require("yaml");
8
8
  //#region kubb-extension.json
9
9
  var compatibility = {
10
10
  "kubb": ">=5.8.3",
@@ -12,7 +12,7 @@ var compatibility = {
12
12
  };
13
13
  //#endregion
14
14
  //#region package.json
15
- var version = "5.9.2";
15
+ var version = "5.9.4";
16
16
  //#endregion
17
17
  //#region src/constants.ts
18
18
  /**
@@ -70,7 +70,7 @@ const structuralKeys = /* @__PURE__ */ new Set([
70
70
  /**
71
71
  * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
72
72
  * `int64`, `uint64` and the date/time family. Keep this in sync with the `convertFormat`
73
- * special-cases in `parser.ts`. `isHandledFormat` reads it so the
73
+ * special-cases in `emit/converters/scalar.ts`. `isHandledFormat` reads it so the
74
74
  * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
75
75
  */
76
76
  const specialCasedFormats = /* @__PURE__ */ new Set([
@@ -139,17 +139,7 @@ const enumDescriptionKeys = ["x-enumDescriptions", "x-enum-descriptions"];
139
139
  function buildDiscriminatorChildMap(schemas) {
140
140
  const childMap = /* @__PURE__ */ new Map();
141
141
  for (const schema of schemas) {
142
- let unionNode = _kubb_kit.ast.narrowSchema(schema, "union");
143
- if (!unionNode) {
144
- const intersectionMembers = _kubb_kit.ast.narrowSchema(schema, "intersection")?.members;
145
- if (intersectionMembers) for (const m of intersectionMembers) {
146
- const u = _kubb_kit.ast.narrowSchema(m, "union");
147
- if (u) {
148
- unionNode = u;
149
- break;
150
- }
151
- }
152
- }
142
+ const unionNode = _kubb_kit.ast.narrowSchema(schema, "union") ?? _kubb_kit.ast.narrowSchema(schema, "intersection")?.members?.map((member) => _kubb_kit.ast.narrowSchema(member, "union")).find((union) => union);
153
143
  if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
154
144
  const { discriminatorPropertyName, members } = unionNode;
155
145
  for (const member of members) {
@@ -456,29 +446,15 @@ async function bundleDocument(pathOrUrl) {
456
446
  return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
457
447
  }
458
448
  /**
459
- * Loads and bundles an OpenAPI document, returning the raw `Document`.
460
- *
461
- * A string is a file path or URL: it is bundled via `api-ref-bundler`, hoisting external file
462
- * schemas into named `components.schemas` entries so generators can emit named types and imports.
463
- * An object is treated as an already-parsed document. Swagger 2.0 and OpenAPI 3.0 documents are
464
- * up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
465
- *
466
- * @example
467
- * ```ts
468
- * const document = await parseDocument('./openapi.yaml')
469
- * const document = await parseDocument(rawDocumentObject)
470
- * ```
471
- */
472
- async function parseDocument(pathOrApi) {
473
- if (typeof pathOrApi === "string") return parseDocument(await bundleDocument(pathOrApi));
474
- return (0, _scalar_openapi_upgrader.upgrade)(pathOrApi, "3.1");
475
- }
476
- /**
477
449
  * Creates a `Document` from an `AdapterSource`.
478
450
  *
479
- * - `{ type: 'path' }` bundles an absolute file path (core resolves `input`) or remote URL.
451
+ * - `{ type: 'path' }` bundles an absolute file path (core resolves `input`) or remote URL via
452
+ * `api-ref-bundler`, hoisting external file schemas into named `components.schemas` entries so
453
+ * generators can emit named types and imports.
480
454
  * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
481
455
  *
456
+ * Swagger 2.0 and OpenAPI 3.0 documents are up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
457
+ *
482
458
  * @example
483
459
  * ```ts
484
460
  * const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
@@ -486,9 +462,12 @@ async function parseDocument(pathOrApi) {
486
462
  * ```
487
463
  */
488
464
  async function parseFromConfig(source) {
489
- if (source.type === "data") return parseDocument(typeof source.data === "string" ? (0, yaml.parse)(source.data) : structuredClone(source.data));
490
- await assertInputExists(source.path);
491
- return parseDocument(source.path);
465
+ const raw = await (async () => {
466
+ if (source.type === "data") return typeof source.data === "string" ? (0, yaml.parse)(source.data) : structuredClone(source.data);
467
+ await assertInputExists(source.path);
468
+ return bundleDocument(source.path);
469
+ })();
470
+ return (0, _scalar_openapi_upgrader.upgrade)(raw, "3.1");
492
471
  }
493
472
  /**
494
473
  * Asserts the parsed input is an OpenAPI or Swagger document.
@@ -694,17 +673,6 @@ const semanticSuffixes = {
694
673
  requestBodies: "Request"
695
674
  };
696
675
  /**
697
- * Picks the collision suffix for one name-colliding schema: none when the name is unique,
698
- * a semantic suffix (`Schema`, `Response`, `Request`) when the collision spans sources, otherwise
699
- * a numeric suffix (`2`, `3`, …) for same-source collisions.
700
- */
701
- function collisionSuffix({ isSingle, hasMultipleSources, source, index }) {
702
- if (isSingle) return "";
703
- if (hasMultipleSources) return semanticSuffixes[source];
704
- if (index === 0) return "";
705
- return String(index + 1);
706
- }
707
- /**
708
676
  * Collects component schemas from one or more sources and resolves name collisions.
709
677
  *
710
678
  * Sources default to `['schemas', 'requestBodies', 'responses']`. Returned schemas are
@@ -751,12 +719,7 @@ function getSchemas(document, { contentType }, refs) {
751
719
  const isSingle = items.length === 1;
752
720
  const hasMultipleSources = !isSingle && new Set(items.map((item) => item.source)).size > 1;
753
721
  items.forEach((item, index) => {
754
- const suffix = collisionSuffix({
755
- isSingle,
756
- hasMultipleSources,
757
- source: item.source,
758
- index
759
- });
722
+ const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index ? String(index + 1) : "";
760
723
  const uniqueName = item.originalName + suffix;
761
724
  schemas[uniqueName] = item.schema;
762
725
  if (suffix) renames.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
@@ -1358,11 +1321,11 @@ function resolveUnionMembers(members, siblingProperties) {
1358
1321
  const obj = member;
1359
1322
  if (!Array.isArray(obj.required) || obj.required.length === 0) return member;
1360
1323
  const existingProps = obj.properties ?? {};
1361
- const missingKeys = obj.required.filter((key) => !Object.prototype.hasOwnProperty.call(existingProps, key));
1324
+ const missingKeys = obj.required.filter((key) => !Object.hasOwn(existingProps, key));
1362
1325
  if (missingKeys.length === 0) return member;
1363
1326
  const newProperties = { ...existingProps };
1364
1327
  for (const key of missingKeys) {
1365
- const value = Object.prototype.hasOwnProperty.call(siblingProperties, key) ? siblingProperties[key] : {};
1328
+ const value = Object.hasOwn(siblingProperties, key) ? siblingProperties[key] : {};
1366
1329
  Object.defineProperty(newProperties, key, {
1367
1330
  value,
1368
1331
  enumerable: true,
@@ -1404,7 +1367,7 @@ function convertRef({ schema, name, nullable, defaultValue, document, parse, ref
1404
1367
  nullable,
1405
1368
  defaultValue
1406
1369
  };
1407
- if (refPath && document.components && !refs.exists(refPath)) return createNode(ctx, { type: "unknown" });
1370
+ if (refPath && document.components && !refs.resolve(refPath, { report: false })) return createNode(ctx, { type: "unknown" });
1408
1371
  const targetName = renames?.get(schema.$ref);
1409
1372
  return createNode(ctx, {
1410
1373
  type: "ref",
@@ -1614,29 +1577,6 @@ function getNumericConstraints(schema) {
1614
1577
  multipleOf: schema.multipleOf
1615
1578
  };
1616
1579
  }
1617
- function getDateType(options, format) {
1618
- const value = resolveDateTypeValue(options.dateType, format);
1619
- if (format !== "date-time") return {
1620
- type: format,
1621
- representation: value === "date" ? "date" : "string"
1622
- };
1623
- if (value === "date") return {
1624
- type: "date",
1625
- representation: "date"
1626
- };
1627
- if (value === "stringOffset") return {
1628
- type: "datetime",
1629
- offset: true
1630
- };
1631
- if (value === "stringLocal") return {
1632
- type: "datetime",
1633
- local: true
1634
- };
1635
- return {
1636
- type: "datetime",
1637
- offset: false
1638
- };
1639
- }
1640
1580
  /**
1641
1581
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1642
1582
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
@@ -1680,17 +1620,17 @@ function convertFormat(context) {
1680
1620
  ...getNumericConstraints(schema)
1681
1621
  });
1682
1622
  if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1683
- const dateType = getDateType(options, schema.format);
1684
- if (dateType.type === "datetime") return createNode(context, {
1623
+ const value = resolveDateTypeValue(options.dateType, schema.format);
1624
+ if (schema.format === "date-time" && value !== "date") return createNode(context, {
1685
1625
  primitive: "string",
1686
1626
  type: "datetime",
1687
- offset: dateType.offset,
1688
- local: dateType.local
1627
+ offset: value === "stringOffset",
1628
+ local: value === "stringLocal"
1689
1629
  });
1690
1630
  return createNode(context, {
1691
1631
  primitive: "string",
1692
- type: dateType.type,
1693
- representation: dateType.representation
1632
+ type: schema.format === "date-time" ? "date" : schema.format,
1633
+ representation: value === "date" ? "date" : "string"
1694
1634
  });
1695
1635
  }
1696
1636
  const specialType = formatMap[schema.format];
@@ -1718,47 +1658,37 @@ function convertEnum({ schema, name, nullable, type, parse }) {
1718
1658
  if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
1719
1659
  const enumNullable = nullable || nullInEnum || void 0;
1720
1660
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1721
- const enumPrimitive = getPrimitiveType(type);
1661
+ const primitive = type === "boolean" ? "boolean" : type === "number" || type === "integer" ? "number" : "string";
1662
+ const uniqueValues = [...new Set(filteredValues)];
1722
1663
  const ctx = {
1723
1664
  schema,
1724
1665
  name,
1725
1666
  nullable: enumNullable,
1726
1667
  defaultValue: enumDefault
1727
1668
  };
1728
- const enumExtras = {
1729
- type: "enum",
1730
- primitive: enumPrimitive
1731
- };
1732
1669
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1733
1670
  const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1734
- if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1735
- const enumPrimitiveType = (() => {
1736
- if (enumPrimitive === "boolean") return "boolean";
1737
- if (enumPrimitive === "number" || enumPrimitive === "integer") return "number";
1738
- return "string";
1739
- })();
1740
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1741
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1742
- const uniqueValues = [...new Set(filteredValues)];
1743
- const seenNames = /* @__PURE__ */ new Set();
1744
- return createNode(ctx, {
1745
- ...enumExtras,
1746
- primitive: enumPrimitiveType,
1747
- namedEnumValues: uniqueValues.map((value, index) => ({
1748
- name: String(rawEnumNames?.[index] ?? value),
1749
- value,
1750
- primitive: enumPrimitiveType,
1751
- description: rawEnumDescriptions?.[index]
1752
- })).filter((entry) => {
1753
- if (seenNames.has(entry.name)) return false;
1754
- seenNames.add(entry.name);
1755
- return true;
1756
- })
1757
- });
1758
- }
1671
+ if (!extensionKey && !descriptionKey && primitive === "string") return createNode(ctx, {
1672
+ type: "enum",
1673
+ primitive,
1674
+ enumValues: uniqueValues
1675
+ });
1676
+ const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1677
+ const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1678
+ const seenNames = /* @__PURE__ */ new Set();
1759
1679
  return createNode(ctx, {
1760
- ...enumExtras,
1761
- enumValues: [...new Set(filteredValues)]
1680
+ type: "enum",
1681
+ primitive,
1682
+ namedEnumValues: uniqueValues.map((value, index) => ({
1683
+ name: String(rawEnumNames?.[index] ?? value),
1684
+ value,
1685
+ primitive,
1686
+ description: rawEnumDescriptions?.[index]
1687
+ })).filter((entry) => {
1688
+ if (seenNames.has(entry.name)) return false;
1689
+ seenNames.add(entry.name);
1690
+ return true;
1691
+ })
1762
1692
  });
1763
1693
  }
1764
1694
  /**
@@ -2112,7 +2042,6 @@ function refPromotedEnums(node, promoted) {
2112
2042
  * const refs = createRefs(document)
2113
2043
  * refs.resolve<SchemaObject>('#/components/schemas/Pet')
2114
2044
  * refs.resolve<SchemaObject>('#/components/schemas/Pet', { report: false })
2115
- * refs.exists('#/components/schemas/Pet')
2116
2045
  * refs.resolveNode('#/components/schemas/Pet', parseSchema)
2117
2046
  * refs.deref<ResponseObject>(operation.schema.responses?.['200'])
2118
2047
  * refs.derefKeepingRef(parameter)
@@ -2164,14 +2093,6 @@ function createRefs(document) {
2164
2093
  return null;
2165
2094
  }
2166
2095
  /**
2167
- * Returns `true` when a `$ref` path resolves to a component the document actually defines.
2168
- * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
2169
- * goes `false` for a `$ref` that points at a component the spec never declares.
2170
- */
2171
- function exists(refPath) {
2172
- return !!resolve(refPath, { report: false });
2173
- }
2174
- /**
2175
2096
  * Resolves a `$ref` to its parsed node via `parse`, guarding against cycles and memoizing per
2176
2097
  * instance. Returns `null` when the ref is currently being resolved (a cycle) or cannot be
2177
2098
  * resolved (e.g. a minimal document in a unit test).
@@ -2214,7 +2135,6 @@ function createRefs(document) {
2214
2135
  }
2215
2136
  return {
2216
2137
  resolve,
2217
- exists,
2218
2138
  resolveNode,
2219
2139
  deref,
2220
2140
  derefKeepingRef
@@ -2223,20 +2143,16 @@ function createRefs(document) {
2223
2143
  //#endregion
2224
2144
  //#region src/schemaDiagnostics.ts
2225
2145
  /**
2226
- * Scans one freshly converted top-level schema in a single walk, so the post-convert pass never
2227
- * sweeps the same nodes twice. It reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`,
2228
- * `KUBB_DEPRECATED`) and returns the names of every schema the node references, ready to feed the
2229
- * circular-dependency graph.
2146
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one freshly
2147
+ * converted top-level schema.
2230
2148
  *
2231
2149
  * Walks the node the parser produced, threading the RFC 6901 pointer as it descends so a nested
2232
2150
  * field reports against its full path (`#/components/schemas/Pet/properties/owner/properties/name`).
2233
- * Refs are recorded by name and not followed, so the resolved schema is reported under its own walk.
2151
+ * Refs are not followed, so the resolved schema is reported under its own walk.
2234
2152
  * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
2235
2153
  */
2236
2154
  function scanSchema({ node, name }) {
2237
- const refs = /* @__PURE__ */ new Set();
2238
- visit(node, `#/components/schemas/${escapePointerToken(name)}`, refs);
2239
- return refs;
2155
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2240
2156
  }
2241
2157
  /**
2242
2158
  * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
@@ -2245,11 +2161,7 @@ function scanSchema({ node, name }) {
2245
2161
  function escapePointerToken(token) {
2246
2162
  return token.replace(/~/g, "~0").replace(/\//g, "~1");
2247
2163
  }
2248
- function visit(node, pointer, refs) {
2249
- if (node.type === "ref") {
2250
- const refName = _kubb_kit.ast.resolveRefName(node);
2251
- if (refName) refs.add(refName);
2252
- }
2164
+ function visit(node, pointer) {
2253
2165
  if (node.deprecated) _kubb_kit.Diagnostics.report({
2254
2166
  code: _kubb_kit.Diagnostics.code.deprecated,
2255
2167
  severity: "info",
@@ -2270,19 +2182,19 @@ function visit(node, pointer, refs) {
2270
2182
  }
2271
2183
  });
2272
2184
  if (node.type === "object") {
2273
- for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`, refs);
2274
- if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`, refs);
2185
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2186
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2275
2187
  return;
2276
2188
  }
2277
2189
  if (node.type === "array") {
2278
- for (const item of node.items ?? []) visit(item, `${pointer}/items`, refs);
2190
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2279
2191
  return;
2280
2192
  }
2281
2193
  if (node.type === "tuple") {
2282
- for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`, refs);
2194
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2283
2195
  return;
2284
2196
  }
2285
- if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`, refs);
2197
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2286
2198
  }
2287
2199
  //#endregion
2288
2200
  //#region src/adapter.ts
@@ -2345,23 +2257,21 @@ const adapterOas = (0, _kubb_kit.defineExtension)({
2345
2257
  const refAliasMap = /* @__PURE__ */ new Map();
2346
2258
  const enumNames = [];
2347
2259
  const discriminatorParentNodes = [];
2348
- const refGraph = /* @__PURE__ */ new Map();
2349
2260
  for (const [name, schema] of Object.entries(schemas)) {
2350
2261
  const node = parseSchema({
2351
2262
  schema,
2352
2263
  name
2353
2264
  });
2354
2265
  parsedByName.set(name, node);
2355
- const refs = scanSchema({
2266
+ scanSchema({
2356
2267
  node,
2357
2268
  name
2358
2269
  });
2359
- if (node.name) refGraph.set(node.name, refs);
2360
2270
  if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2361
2271
  if (_kubb_kit.ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
2362
2272
  if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2363
2273
  }
2364
- const circularNames = [..._kubb_kit.ast.findCircularSchemasFromGraph(refGraph)];
2274
+ const circularNames = [..._kubb_kit.ast.findCircularSchemas([...parsedByName.values()])];
2365
2275
  const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2366
2276
  const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
2367
2277
  operation,
@@ -2407,8 +2317,10 @@ const adapterOas = (0, _kubb_kit.defineExtension)({
2407
2317
  return parsedDocument;
2408
2318
  },
2409
2319
  async validate(input, options) {
2410
- await assertInputExists(input);
2411
- const document = await parseDocument(input);
2320
+ const document = await parseFromConfig({
2321
+ type: "path",
2322
+ path: input
2323
+ });
2412
2324
  assertDocument(document);
2413
2325
  await validateDocument(document, options);
2414
2326
  },