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