@kubb/adapter-oas 5.0.0-beta.37 → 5.0.0-beta.39

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,13 +1,145 @@
1
1
  import "./chunk-C0LytTxp.js";
2
- import { access } from "node:fs/promises";
3
- import path from "node:path";
4
- import { DiagnosticError, Diagnostics, ast, createAdapter, diagnosticCode } from "@kubb/core";
2
+ import { Diagnostics, ast, createAdapter } from "@kubb/core";
5
3
  import BaseOas from "oas";
4
+ import path from "node:path";
5
+ import { access } from "node:fs/promises";
6
6
  import { bundle, loadConfig } from "@redocly/openapi-core";
7
7
  import OASNormalize from "oas-normalize";
8
8
  import swagger2openapi from "swagger2openapi";
9
9
  import { isRef } from "oas/types";
10
10
  import { matchesMimeType } from "oas/utils";
11
+ //#region src/constants.ts
12
+ /**
13
+ * Default parser options applied when no explicit options are provided.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
18
+ *
19
+ * const parser = createOasParser(oas)
20
+ * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
21
+ * ```
22
+ */
23
+ const DEFAULT_PARSER_OPTIONS = {
24
+ dateType: "string",
25
+ integerType: "bigint",
26
+ unknownType: "any",
27
+ emptySchemaType: "any",
28
+ enumSuffix: "enum"
29
+ };
30
+ /**
31
+ * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
32
+ *
33
+ * Used when building or parsing `$ref` strings.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
38
+ * ```
39
+ */
40
+ const SCHEMA_REF_PREFIX = "#/components/schemas/";
41
+ /**
42
+ * OpenAPI version string written into the stub document created during multi-spec merges.
43
+ */
44
+ const MERGE_OPENAPI_VERSION = "3.0.0";
45
+ /**
46
+ * Fallback `info.title` placed in the stub document when merging multiple API files.
47
+ */
48
+ const MERGE_DEFAULT_TITLE = "Merged API";
49
+ /**
50
+ * Fallback `info.version` placed in the stub document when merging multiple API files.
51
+ */
52
+ const MERGE_DEFAULT_VERSION = "1.0.0";
53
+ /**
54
+ * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
55
+ *
56
+ * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
57
+ * intersection member rather than being merged into the parent.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { structuralKeys } from '@kubb/adapter-oas'
62
+ *
63
+ * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
64
+ * // true when fragment has e.g. 'properties' or 'oneOf'
65
+ * ```
66
+ */
67
+ const structuralKeys = new Set([
68
+ "properties",
69
+ "items",
70
+ "additionalProperties",
71
+ "oneOf",
72
+ "anyOf",
73
+ "allOf",
74
+ "not"
75
+ ]);
76
+ /**
77
+ * Static map from OAS `format` strings to Kubb `SchemaType` values.
78
+ *
79
+ * Only formats whose AST type differs from the OAS `type` field appear here.
80
+ * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
81
+ * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
82
+ * `idn-hostname` map to `'url'` as the closest generic string-format type.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import { formatMap } from '@kubb/adapter-oas'
87
+ *
88
+ * formatMap['uuid'] // 'uuid'
89
+ * formatMap['binary'] // 'blob'
90
+ * formatMap['float'] // 'number'
91
+ * ```
92
+ */
93
+ /**
94
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
95
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
96
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
97
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
98
+ */
99
+ const specialCasedFormats = new Set([
100
+ "int64",
101
+ "date-time",
102
+ "date",
103
+ "time"
104
+ ]);
105
+ const formatMap = {
106
+ uuid: "uuid",
107
+ email: "email",
108
+ "idn-email": "email",
109
+ uri: "url",
110
+ "uri-reference": "url",
111
+ url: "url",
112
+ ipv4: "ipv4",
113
+ ipv6: "ipv6",
114
+ hostname: "url",
115
+ "idn-hostname": "url",
116
+ binary: "blob",
117
+ byte: "blob",
118
+ int32: "integer",
119
+ float: "number",
120
+ double: "number"
121
+ };
122
+ /**
123
+ * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * import { enumExtensionKeys } from '@kubb/adapter-oas'
128
+ *
129
+ * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
130
+ * ```
131
+ */
132
+ const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
133
+ /**
134
+ * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
135
+ * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
136
+ */
137
+ const typeOptionMap = new Map([
138
+ ["any", ast.schemaTypes.any],
139
+ ["unknown", ast.schemaTypes.unknown],
140
+ ["void", ast.schemaTypes.void]
141
+ ]);
142
+ //#endregion
11
143
  //#region ../../internals/utils/src/casing.ts
12
144
  /**
13
145
  * Shared implementation for camelCase and PascalCase conversion.
@@ -123,30 +255,6 @@ function mergeDeep(target, source) {
123
255
  return result;
124
256
  }
125
257
  //#endregion
126
- //#region ../../internals/utils/src/promise.ts
127
- /**
128
- * Returns a wrapper that caches the result of the first invocation and replays
129
- * it for every subsequent call, ignoring later arguments.
130
- *
131
- * Works for sync and async factories — for async, the cached value is the
132
- * promise itself, so concurrent callers share one in-flight execution and
133
- * cannot race each other.
134
- *
135
- * @example
136
- * ```ts
137
- * const loadDocument = once(async (path: string) => parse(await readFile(path)))
138
- * const a = loadDocument('./a.yaml') // parses
139
- * const b = loadDocument('./b.yaml') // returns the cached promise from the first call
140
- * ```
141
- */
142
- function once(factory) {
143
- let cache;
144
- return (...args) => {
145
- if (!cache) cache = { value: factory(...args) };
146
- return cache.value;
147
- };
148
- }
149
- //#endregion
150
258
  //#region ../../internals/utils/src/reserved.ts
151
259
  /**
152
260
  * JavaScript and Java reserved words.
@@ -397,138 +505,6 @@ var URLPath = class {
397
505
  }
398
506
  };
399
507
  //#endregion
400
- //#region src/constants.ts
401
- /**
402
- * Default parser options applied when no explicit options are provided.
403
- *
404
- * @example
405
- * ```ts
406
- * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
407
- *
408
- * const parser = createOasParser(oas)
409
- * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
410
- * ```
411
- */
412
- const DEFAULT_PARSER_OPTIONS = {
413
- dateType: "string",
414
- integerType: "bigint",
415
- unknownType: "any",
416
- emptySchemaType: "any",
417
- enumSuffix: "enum"
418
- };
419
- /**
420
- * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
421
- *
422
- * Used when building or parsing `$ref` strings.
423
- *
424
- * @example
425
- * ```ts
426
- * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
427
- * ```
428
- */
429
- const SCHEMA_REF_PREFIX = "#/components/schemas/";
430
- /**
431
- * OpenAPI version string written into the stub document created during multi-spec merges.
432
- */
433
- const MERGE_OPENAPI_VERSION = "3.0.0";
434
- /**
435
- * Fallback `info.title` placed in the stub document when merging multiple API files.
436
- */
437
- const MERGE_DEFAULT_TITLE = "Merged API";
438
- /**
439
- * Fallback `info.version` placed in the stub document when merging multiple API files.
440
- */
441
- const MERGE_DEFAULT_VERSION = "1.0.0";
442
- /**
443
- * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
444
- *
445
- * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
446
- * intersection member rather than being merged into the parent.
447
- *
448
- * @example
449
- * ```ts
450
- * import { structuralKeys } from '@kubb/adapter-oas'
451
- *
452
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
453
- * // true when fragment has e.g. 'properties' or 'oneOf'
454
- * ```
455
- */
456
- const structuralKeys = new Set([
457
- "properties",
458
- "items",
459
- "additionalProperties",
460
- "oneOf",
461
- "anyOf",
462
- "allOf",
463
- "not"
464
- ]);
465
- /**
466
- * Static map from OAS `format` strings to Kubb `SchemaType` values.
467
- *
468
- * Only formats whose AST type differs from the OAS `type` field appear here.
469
- * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
470
- * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
471
- * `idn-hostname` map to `'url'` as the closest generic string-format type.
472
- *
473
- * @example
474
- * ```ts
475
- * import { formatMap } from '@kubb/adapter-oas'
476
- *
477
- * formatMap['uuid'] // 'uuid'
478
- * formatMap['binary'] // 'blob'
479
- * formatMap['float'] // 'number'
480
- * ```
481
- */
482
- /**
483
- * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
484
- * `int64` and the date/time family. Keep this in sync with the `convertFormat`
485
- * special-cases in `parser.ts`. `isHandledFormat` reads it so the
486
- * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
487
- */
488
- const specialCasedFormats = new Set([
489
- "int64",
490
- "date-time",
491
- "date",
492
- "time"
493
- ]);
494
- const formatMap = {
495
- uuid: "uuid",
496
- email: "email",
497
- "idn-email": "email",
498
- uri: "url",
499
- "uri-reference": "url",
500
- url: "url",
501
- ipv4: "ipv4",
502
- ipv6: "ipv6",
503
- hostname: "url",
504
- "idn-hostname": "url",
505
- binary: "blob",
506
- byte: "blob",
507
- int32: "integer",
508
- float: "number",
509
- double: "number"
510
- };
511
- /**
512
- * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
513
- *
514
- * @example
515
- * ```ts
516
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
517
- *
518
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
519
- * ```
520
- */
521
- const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
522
- /**
523
- * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
524
- * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
525
- */
526
- const typeOptionMap = new Map([
527
- ["any", ast.schemaTypes.any],
528
- ["unknown", ast.schemaTypes.unknown],
529
- ["void", ast.schemaTypes.void]
530
- ]);
531
- //#endregion
532
508
  //#region src/guards.ts
533
509
  /**
534
510
  * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
@@ -638,8 +614,8 @@ async function mergeDocuments(pathOrApi) {
638
614
  enablePaths: false,
639
615
  canBundle: false
640
616
  })));
641
- if (documents.length === 0) throw new DiagnosticError({
642
- code: diagnosticCode.inputRequired,
617
+ if (documents.length === 0) throw new Diagnostics.Error({
618
+ code: Diagnostics.code.inputRequired,
643
619
  severity: "error",
644
620
  message: "No OAS documents were provided for merging.",
645
621
  help: "Pass at least one path or document to `input.path`.",
@@ -688,8 +664,8 @@ async function parseFromConfig(source) {
688
664
  */
689
665
  async function assertInputExists(input) {
690
666
  if (new URLPath(input).isURL) return;
691
- if (!await exists(input)) throw new DiagnosticError({
692
- code: diagnosticCode.inputNotFound,
667
+ if (!await exists(input)) throw new Diagnostics.Error({
668
+ code: Diagnostics.code.inputNotFound,
693
669
  severity: "error",
694
670
  message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
695
671
  help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
@@ -743,7 +719,7 @@ function resolveRef(document, $ref) {
743
719
  const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
744
720
  if (!current) {
745
721
  const diagnostic = {
746
- code: diagnosticCode.refNotFound,
722
+ code: Diagnostics.code.refNotFound,
747
723
  severity: "error",
748
724
  message: `Could not find a definition for ${origRef}.`,
749
725
  help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
@@ -753,7 +729,7 @@ function resolveRef(document, $ref) {
753
729
  ref: origRef
754
730
  }
755
731
  };
756
- Diagnostics.report(diagnostic);
732
+ if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
757
733
  return null;
758
734
  }
759
735
  docCache.set($ref, current);
@@ -830,8 +806,8 @@ function resolveServerUrl(server, overrides) {
830
806
  for (const [key, variable] of Object.entries(server.variables)) {
831
807
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
832
808
  if (value === void 0) continue;
833
- if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new DiagnosticError({
834
- code: diagnosticCode.invalidServerVariable,
809
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Diagnostics.Error({
810
+ code: Diagnostics.code.invalidServerVariable,
835
811
  severity: "error",
836
812
  message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
837
813
  help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
@@ -2162,11 +2138,18 @@ function patchDiscriminatorNode(node, entry) {
2162
2138
  * no-op outside one, and repeats are deduped by the build.
2163
2139
  */
2164
2140
  function reportSchemaDiagnostics({ node, name }) {
2165
- visit(node, `#/components/schemas/${name}`);
2141
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2142
+ }
2143
+ /**
2144
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2145
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2146
+ */
2147
+ function escapePointerToken(token) {
2148
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2166
2149
  }
2167
2150
  function visit(node, pointer) {
2168
2151
  if (node.deprecated) Diagnostics.report({
2169
- code: diagnosticCode.deprecated,
2152
+ code: Diagnostics.code.deprecated,
2170
2153
  severity: "info",
2171
2154
  message: "This schema is marked as deprecated.",
2172
2155
  location: {
@@ -2175,7 +2158,7 @@ function visit(node, pointer) {
2175
2158
  }
2176
2159
  });
2177
2160
  if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
2178
- code: diagnosticCode.unsupportedFormat,
2161
+ code: Diagnostics.code.unsupportedFormat,
2179
2162
  severity: "warning",
2180
2163
  message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2181
2164
  help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
@@ -2185,15 +2168,19 @@ function visit(node, pointer) {
2185
2168
  }
2186
2169
  });
2187
2170
  if (node.type === "object") {
2188
- for (const property of node.properties) visit(property.schema, `${pointer}/properties/${property.name}`);
2171
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2189
2172
  if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2190
2173
  return;
2191
2174
  }
2192
- if (node.type === "array" || node.type === "tuple") {
2175
+ if (node.type === "array") {
2193
2176
  for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2194
2177
  return;
2195
2178
  }
2196
- if (node.type === "union" || node.type === "intersection") for (const member of node.members ?? []) visit(member, pointer);
2179
+ if (node.type === "tuple") {
2180
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2181
+ return;
2182
+ }
2183
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2197
2184
  }
2198
2185
  //#endregion
2199
2186
  //#region src/stream.ts
@@ -2423,37 +2410,72 @@ const adapterOas = createAdapter((options) => {
2423
2410
  };
2424
2411
  let nameMapping = /* @__PURE__ */ new Map();
2425
2412
  let parsedDocument = null;
2426
- const ensureDocument = once(async (source) => {
2427
- const fresh = await parseFromConfig(source);
2428
- if (validate) await validateDocument(fresh);
2429
- parsedDocument = fresh;
2430
- return fresh;
2431
- });
2432
- const ensureSchemas = once(async (document) => {
2433
- const result = getSchemas(document, { contentType });
2434
- nameMapping = result.nameMapping;
2435
- return result.schemas;
2436
- });
2437
- const ensureBaseOas = once((document) => new BaseOas(document));
2438
- const ensureSchemaParser = once((document) => createSchemaParser({
2439
- document,
2440
- contentType
2441
- }));
2442
- const ensurePreScan = once((schemas, parseSchema, parseOperation, baseOas) => preScan({
2443
- schemas,
2444
- parseSchema,
2445
- parseOperation,
2446
- baseOas,
2447
- parserOptions,
2448
- discriminator,
2449
- dedupe
2450
- }));
2413
+ const documentCache = /* @__PURE__ */ new WeakMap();
2414
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2415
+ const baseOasCache = /* @__PURE__ */ new WeakMap();
2416
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2417
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2418
+ function ensureDocument(source) {
2419
+ const cached = documentCache.get(source);
2420
+ if (cached) return cached;
2421
+ const promise = (async () => {
2422
+ const fresh = await parseFromConfig(source);
2423
+ if (validate) await validateDocument(fresh);
2424
+ parsedDocument = fresh;
2425
+ return fresh;
2426
+ })();
2427
+ documentCache.set(source, promise);
2428
+ return promise;
2429
+ }
2430
+ function ensureSchemas(document) {
2431
+ const cached = schemasCache.get(document);
2432
+ if (cached) return cached;
2433
+ const promise = Promise.resolve().then(() => {
2434
+ const result = getSchemas(document, { contentType });
2435
+ nameMapping = result.nameMapping;
2436
+ return result.schemas;
2437
+ });
2438
+ schemasCache.set(document, promise);
2439
+ return promise;
2440
+ }
2441
+ function ensureBaseOas(document) {
2442
+ const cached = baseOasCache.get(document);
2443
+ if (cached) return cached;
2444
+ const baseOas = new BaseOas(document);
2445
+ baseOasCache.set(document, baseOas);
2446
+ return baseOas;
2447
+ }
2448
+ function ensureSchemaParser(document) {
2449
+ const cached = schemaParserCache.get(document);
2450
+ if (cached) return cached;
2451
+ const parser = createSchemaParser({
2452
+ document,
2453
+ contentType
2454
+ });
2455
+ schemaParserCache.set(document, parser);
2456
+ return parser;
2457
+ }
2458
+ function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
2459
+ const cached = preScanCache.get(document);
2460
+ if (cached) return cached;
2461
+ const result = preScan({
2462
+ schemas,
2463
+ parseSchema,
2464
+ parseOperation,
2465
+ baseOas,
2466
+ parserOptions,
2467
+ discriminator,
2468
+ dedupe
2469
+ });
2470
+ preScanCache.set(document, result);
2471
+ return result;
2472
+ }
2451
2473
  async function createStream(source) {
2452
2474
  const document = await ensureDocument(source);
2453
2475
  const schemas = await ensureSchemas(document);
2454
2476
  const { parseSchema, parseOperation } = ensureSchemaParser(document);
2455
2477
  const baseOas = ensureBaseOas(document);
2456
- const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(schemas, parseSchema, parseOperation, baseOas);
2478
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
2457
2479
  return createInputStream({
2458
2480
  schemas,
2459
2481
  parseSchema,