@kubb/adapter-oas 5.0.0-beta.2 → 5.0.0-beta.21
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/README.md +98 -0
- package/dist/index.cjs +492 -317
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -49
- package/dist/index.js +492 -314
- package/dist/index.js.map +1 -1
- package/extension.yaml +344 -0
- package/package.json +6 -4
- package/src/adapter.bench.ts +64 -0
- package/src/adapter.ts +76 -42
- package/src/discriminator.ts +57 -40
- package/src/factory.ts +1 -4
- package/src/index.ts +2 -2
- package/src/parser.ts +69 -37
- package/src/refs.ts +16 -4
- package/src/resolvers.ts +12 -13
- package/src/stream.ts +175 -0
package/dist/index.cjs
CHANGED
|
@@ -21,219 +21,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
let _kubb_core = require("@kubb/core");
|
|
25
24
|
let node_path = require("node:path");
|
|
26
25
|
node_path = __toESM(node_path, 1);
|
|
26
|
+
let _kubb_core = require("@kubb/core");
|
|
27
|
+
let oas = require("oas");
|
|
28
|
+
oas = __toESM(oas, 1);
|
|
27
29
|
let _redocly_openapi_core = require("@redocly/openapi-core");
|
|
28
30
|
let oas_normalize = require("oas-normalize");
|
|
29
31
|
oas_normalize = __toESM(oas_normalize, 1);
|
|
30
32
|
let swagger2openapi = require("swagger2openapi");
|
|
31
33
|
swagger2openapi = __toESM(swagger2openapi, 1);
|
|
32
|
-
let oas = require("oas");
|
|
33
|
-
oas = __toESM(oas, 1);
|
|
34
34
|
let oas_types = require("oas/types");
|
|
35
35
|
let oas_utils = require("oas/utils");
|
|
36
|
-
//#region src/constants.ts
|
|
37
|
-
/**
|
|
38
|
-
* Default parser options applied when no explicit options are provided.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
43
|
-
*
|
|
44
|
-
* const parser = createOasParser(oas)
|
|
45
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
const DEFAULT_PARSER_OPTIONS = {
|
|
49
|
-
dateType: "string",
|
|
50
|
-
integerType: "bigint",
|
|
51
|
-
unknownType: "any",
|
|
52
|
-
emptySchemaType: "any",
|
|
53
|
-
enumSuffix: "enum"
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
57
|
-
*
|
|
58
|
-
* Used when building or parsing `$ref` strings.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```ts
|
|
62
|
-
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
66
|
-
/**
|
|
67
|
-
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
68
|
-
*/
|
|
69
|
-
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
70
|
-
/**
|
|
71
|
-
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
72
|
-
*/
|
|
73
|
-
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
74
|
-
/**
|
|
75
|
-
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
76
|
-
*/
|
|
77
|
-
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
78
|
-
/**
|
|
79
|
-
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
80
|
-
*
|
|
81
|
-
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
82
|
-
* intersection member rather than being merged into the parent.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
87
|
-
*
|
|
88
|
-
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
89
|
-
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
const structuralKeys = new Set([
|
|
93
|
-
"properties",
|
|
94
|
-
"items",
|
|
95
|
-
"additionalProperties",
|
|
96
|
-
"oneOf",
|
|
97
|
-
"anyOf",
|
|
98
|
-
"allOf",
|
|
99
|
-
"not"
|
|
100
|
-
]);
|
|
101
|
-
/**
|
|
102
|
-
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
103
|
-
*
|
|
104
|
-
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
105
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
106
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
107
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* import { formatMap } from '@kubb/adapter-oas'
|
|
112
|
-
*
|
|
113
|
-
* formatMap['uuid'] // 'uuid'
|
|
114
|
-
* formatMap['binary'] // 'blob'
|
|
115
|
-
* formatMap['float'] // 'number'
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
const formatMap = {
|
|
119
|
-
uuid: "uuid",
|
|
120
|
-
email: "email",
|
|
121
|
-
"idn-email": "email",
|
|
122
|
-
uri: "url",
|
|
123
|
-
"uri-reference": "url",
|
|
124
|
-
url: "url",
|
|
125
|
-
ipv4: "ipv4",
|
|
126
|
-
ipv6: "ipv6",
|
|
127
|
-
hostname: "url",
|
|
128
|
-
"idn-hostname": "url",
|
|
129
|
-
binary: "blob",
|
|
130
|
-
byte: "blob",
|
|
131
|
-
int32: "integer",
|
|
132
|
-
float: "number",
|
|
133
|
-
double: "number"
|
|
134
|
-
};
|
|
135
|
-
/**
|
|
136
|
-
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* ```ts
|
|
140
|
-
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
141
|
-
*
|
|
142
|
-
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
146
|
-
/**
|
|
147
|
-
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
148
|
-
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
149
|
-
*/
|
|
150
|
-
const typeOptionMap = new Map([
|
|
151
|
-
["any", _kubb_core.ast.schemaTypes.any],
|
|
152
|
-
["unknown", _kubb_core.ast.schemaTypes.unknown],
|
|
153
|
-
["void", _kubb_core.ast.schemaTypes.void]
|
|
154
|
-
]);
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region src/discriminator.ts
|
|
157
|
-
/**
|
|
158
|
-
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
159
|
-
*
|
|
160
|
-
* Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
|
|
161
|
-
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
162
|
-
* child object schema.
|
|
163
|
-
*
|
|
164
|
-
* Returns a new `InputNode` — the original is never mutated.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const { root } = parseOas(document, options)
|
|
169
|
-
* const next = applyDiscriminatorInheritance(root)
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
function applyDiscriminatorInheritance(root) {
|
|
173
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
174
|
-
for (const schema of root.schemas) {
|
|
175
|
-
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
176
|
-
if (!unionNode) {
|
|
177
|
-
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
178
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
179
|
-
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
180
|
-
if (u) {
|
|
181
|
-
unionNode = u;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
187
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
188
|
-
for (const member of members) {
|
|
189
|
-
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
190
|
-
if (!intersectionNode?.members) continue;
|
|
191
|
-
let refNode;
|
|
192
|
-
let objNode;
|
|
193
|
-
for (const m of intersectionNode.members) {
|
|
194
|
-
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
195
|
-
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
196
|
-
}
|
|
197
|
-
if (!refNode?.name || !objNode) continue;
|
|
198
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
199
|
-
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : void 0;
|
|
200
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
201
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
202
|
-
if (!enumValues.length) continue;
|
|
203
|
-
const existing = childMap.get(refNode.name);
|
|
204
|
-
if (existing) existing.enumValues.push(...enumValues);
|
|
205
|
-
else childMap.set(refNode.name, {
|
|
206
|
-
propertyName: discriminatorPropertyName,
|
|
207
|
-
enumValues: [...enumValues]
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (childMap.size === 0) return root;
|
|
212
|
-
return _kubb_core.ast.transform(root, { schema(node, { parent }) {
|
|
213
|
-
if (parent?.kind !== "Input" || !node.name) return;
|
|
214
|
-
const entry = childMap.get(node.name);
|
|
215
|
-
if (!entry) return;
|
|
216
|
-
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
217
|
-
if (!objectNode) return;
|
|
218
|
-
const { propertyName, enumValues } = entry;
|
|
219
|
-
const enumSchema = _kubb_core.ast.createSchema({
|
|
220
|
-
type: "enum",
|
|
221
|
-
enumValues
|
|
222
|
-
});
|
|
223
|
-
const newProp = _kubb_core.ast.createProperty({
|
|
224
|
-
name: propertyName,
|
|
225
|
-
required: true,
|
|
226
|
-
schema: enumSchema
|
|
227
|
-
});
|
|
228
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
229
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
230
|
-
return {
|
|
231
|
-
...objectNode,
|
|
232
|
-
properties: newProperties
|
|
233
|
-
};
|
|
234
|
-
} });
|
|
235
|
-
}
|
|
236
|
-
//#endregion
|
|
237
36
|
//#region ../../internals/utils/src/casing.ts
|
|
238
37
|
/**
|
|
239
38
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -332,6 +131,30 @@ function mergeDeep(target, source) {
|
|
|
332
131
|
return result;
|
|
333
132
|
}
|
|
334
133
|
//#endregion
|
|
134
|
+
//#region ../../internals/utils/src/promise.ts
|
|
135
|
+
/**
|
|
136
|
+
* Returns a wrapper that caches the result of the first invocation and replays
|
|
137
|
+
* it for every subsequent call, ignoring later arguments.
|
|
138
|
+
*
|
|
139
|
+
* Works for sync and async factories — for async, the cached value is the
|
|
140
|
+
* promise itself, so concurrent callers share one in-flight execution and
|
|
141
|
+
* cannot race each other.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const loadDocument = once(async (path: string) => parse(await readFile(path)))
|
|
146
|
+
* const a = loadDocument('./a.yaml') // parses
|
|
147
|
+
* const b = loadDocument('./b.yaml') // returns the cached promise from the first call
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function once(factory) {
|
|
151
|
+
let cache;
|
|
152
|
+
return (...args) => {
|
|
153
|
+
if (!cache) cache = { value: factory(...args) };
|
|
154
|
+
return cache.value;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
335
158
|
//#region ../../internals/utils/src/reserved.ts
|
|
336
159
|
/**
|
|
337
160
|
* JavaScript and Java reserved words.
|
|
@@ -581,6 +404,126 @@ var URLPath = class {
|
|
|
581
404
|
}
|
|
582
405
|
};
|
|
583
406
|
//#endregion
|
|
407
|
+
//#region src/constants.ts
|
|
408
|
+
/**
|
|
409
|
+
* Default parser options applied when no explicit options are provided.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```ts
|
|
413
|
+
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
414
|
+
*
|
|
415
|
+
* const parser = createOasParser(oas)
|
|
416
|
+
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
420
|
+
dateType: "string",
|
|
421
|
+
integerType: "bigint",
|
|
422
|
+
unknownType: "any",
|
|
423
|
+
emptySchemaType: "any",
|
|
424
|
+
enumSuffix: "enum"
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
428
|
+
*
|
|
429
|
+
* Used when building or parsing `$ref` strings.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
437
|
+
/**
|
|
438
|
+
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
439
|
+
*/
|
|
440
|
+
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
441
|
+
/**
|
|
442
|
+
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
443
|
+
*/
|
|
444
|
+
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
445
|
+
/**
|
|
446
|
+
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
447
|
+
*/
|
|
448
|
+
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
449
|
+
/**
|
|
450
|
+
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
451
|
+
*
|
|
452
|
+
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
453
|
+
* intersection member rather than being merged into the parent.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
458
|
+
*
|
|
459
|
+
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
460
|
+
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
const structuralKeys = new Set([
|
|
464
|
+
"properties",
|
|
465
|
+
"items",
|
|
466
|
+
"additionalProperties",
|
|
467
|
+
"oneOf",
|
|
468
|
+
"anyOf",
|
|
469
|
+
"allOf",
|
|
470
|
+
"not"
|
|
471
|
+
]);
|
|
472
|
+
/**
|
|
473
|
+
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
474
|
+
*
|
|
475
|
+
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
476
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
477
|
+
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
478
|
+
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* import { formatMap } from '@kubb/adapter-oas'
|
|
483
|
+
*
|
|
484
|
+
* formatMap['uuid'] // 'uuid'
|
|
485
|
+
* formatMap['binary'] // 'blob'
|
|
486
|
+
* formatMap['float'] // 'number'
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
const formatMap = {
|
|
490
|
+
uuid: "uuid",
|
|
491
|
+
email: "email",
|
|
492
|
+
"idn-email": "email",
|
|
493
|
+
uri: "url",
|
|
494
|
+
"uri-reference": "url",
|
|
495
|
+
url: "url",
|
|
496
|
+
ipv4: "ipv4",
|
|
497
|
+
ipv6: "ipv6",
|
|
498
|
+
hostname: "url",
|
|
499
|
+
"idn-hostname": "url",
|
|
500
|
+
binary: "blob",
|
|
501
|
+
byte: "blob",
|
|
502
|
+
int32: "integer",
|
|
503
|
+
float: "number",
|
|
504
|
+
double: "number"
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```ts
|
|
511
|
+
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
512
|
+
*
|
|
513
|
+
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
517
|
+
/**
|
|
518
|
+
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
519
|
+
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
520
|
+
*/
|
|
521
|
+
const typeOptionMap = new Map([
|
|
522
|
+
["any", _kubb_core.ast.schemaTypes.any],
|
|
523
|
+
["unknown", _kubb_core.ast.schemaTypes.unknown],
|
|
524
|
+
["void", _kubb_core.ast.schemaTypes.void]
|
|
525
|
+
]);
|
|
526
|
+
//#endregion
|
|
584
527
|
//#region src/guards.ts
|
|
585
528
|
/**
|
|
586
529
|
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
@@ -686,11 +629,10 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
686
629
|
* ```
|
|
687
630
|
*/
|
|
688
631
|
async function mergeDocuments(pathOrApi) {
|
|
689
|
-
const documents =
|
|
690
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
632
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
691
633
|
enablePaths: false,
|
|
692
634
|
canBundle: false
|
|
693
|
-
}));
|
|
635
|
+
})));
|
|
694
636
|
if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
|
|
695
637
|
const seed = {
|
|
696
638
|
openapi: MERGE_OPENAPI_VERSION,
|
|
@@ -746,6 +688,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
746
688
|
}
|
|
747
689
|
//#endregion
|
|
748
690
|
//#region src/refs.ts
|
|
691
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
749
692
|
/**
|
|
750
693
|
* Resolves a local JSON pointer reference from a document.
|
|
751
694
|
*
|
|
@@ -761,10 +704,17 @@ function resolveRef(document, $ref) {
|
|
|
761
704
|
const origRef = $ref;
|
|
762
705
|
$ref = $ref.trim();
|
|
763
706
|
if ($ref === "") return null;
|
|
764
|
-
if (
|
|
765
|
-
|
|
707
|
+
if (!$ref.startsWith("#")) return null;
|
|
708
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
709
|
+
let docCache = _refCache.get(document);
|
|
710
|
+
if (!docCache) {
|
|
711
|
+
docCache = /* @__PURE__ */ new Map();
|
|
712
|
+
_refCache.set(document, docCache);
|
|
713
|
+
}
|
|
714
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
766
715
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
767
716
|
if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
|
|
717
|
+
docCache.set($ref, current);
|
|
768
718
|
return current;
|
|
769
719
|
}
|
|
770
720
|
/**
|
|
@@ -979,21 +929,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
979
929
|
/**
|
|
980
930
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
981
931
|
*/
|
|
982
|
-
function collectRefs(schema
|
|
932
|
+
function* collectRefs(schema) {
|
|
983
933
|
if (Array.isArray(schema)) {
|
|
984
|
-
for (const item of schema) collectRefs(item
|
|
985
|
-
return
|
|
934
|
+
for (const item of schema) yield* collectRefs(item);
|
|
935
|
+
return;
|
|
986
936
|
}
|
|
987
937
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
988
938
|
const value = schema[key];
|
|
989
|
-
if (key === "$ref" && typeof value === "string") {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
939
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
940
|
+
yield* collectRefs(value);
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
944
|
+
const name = value.slice(21);
|
|
945
|
+
if (name) yield name;
|
|
946
|
+
}
|
|
995
947
|
}
|
|
996
|
-
return refs;
|
|
997
948
|
}
|
|
998
949
|
/**
|
|
999
950
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -1009,7 +960,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
1009
960
|
*/
|
|
1010
961
|
function sortSchemas(schemas) {
|
|
1011
962
|
const deps = /* @__PURE__ */ new Map();
|
|
1012
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
963
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
1013
964
|
const sorted = [];
|
|
1014
965
|
const visited = /* @__PURE__ */ new Set();
|
|
1015
966
|
function visit(name, stack) {
|
|
@@ -1144,7 +1095,8 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1144
1095
|
readOnly: schema.readOnly,
|
|
1145
1096
|
writeOnly: schema.writeOnly,
|
|
1146
1097
|
default: defaultValue,
|
|
1147
|
-
example: schema.example
|
|
1098
|
+
example: schema.example,
|
|
1099
|
+
format: schema.format
|
|
1148
1100
|
};
|
|
1149
1101
|
}
|
|
1150
1102
|
/**
|
|
@@ -1194,7 +1146,7 @@ function normalizeArrayEnum(schema) {
|
|
|
1194
1146
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1195
1147
|
* made possible by hoisting of function declarations.
|
|
1196
1148
|
*
|
|
1197
|
-
* @
|
|
1149
|
+
* @internal
|
|
1198
1150
|
*/
|
|
1199
1151
|
function createSchemaParser(ctx) {
|
|
1200
1152
|
const document = ctx.document;
|
|
@@ -1204,6 +1156,19 @@ function createSchemaParser(ctx) {
|
|
|
1204
1156
|
*/
|
|
1205
1157
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1206
1158
|
/**
|
|
1159
|
+
* Cache of already-resolved `$ref` schemas within this parser instance.
|
|
1160
|
+
*
|
|
1161
|
+
* Without this, the same referenced schema (e.g. `customer`) is fully re-expanded
|
|
1162
|
+
* every time it appears as a `$ref` in a different parent schema. In heavily
|
|
1163
|
+
* cross-referenced specs like Stripe (~1 400 schemas), this causes exponential
|
|
1164
|
+
* blowup — `customer` alone may be referenced from dozens of top-level schemas,
|
|
1165
|
+
* each triggering a fresh recursive expansion of its entire sub-tree.
|
|
1166
|
+
*
|
|
1167
|
+
* Memoizing by `$ref` path reduces the overall work from O(2^depth) to O(N)
|
|
1168
|
+
* where N is the number of unique schema names.
|
|
1169
|
+
*/
|
|
1170
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1171
|
+
/**
|
|
1207
1172
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1208
1173
|
*
|
|
1209
1174
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1212,16 +1177,22 @@ function createSchemaParser(ctx) {
|
|
|
1212
1177
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1213
1178
|
*/
|
|
1214
1179
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1215
|
-
let resolvedSchema;
|
|
1180
|
+
let resolvedSchema = null;
|
|
1216
1181
|
const refPath = schema.$ref;
|
|
1217
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1182
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1183
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1184
|
+
try {
|
|
1185
|
+
const referenced = resolveRef(document, refPath);
|
|
1186
|
+
if (referenced) {
|
|
1187
|
+
resolvingRefs.add(refPath);
|
|
1188
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1189
|
+
resolvingRefs.delete(refPath);
|
|
1190
|
+
}
|
|
1191
|
+
} catch {}
|
|
1192
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1223
1193
|
}
|
|
1224
|
-
|
|
1194
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1195
|
+
}
|
|
1225
1196
|
return _kubb_core.ast.createSchema({
|
|
1226
1197
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1227
1198
|
type: "ref",
|
|
@@ -1254,7 +1225,8 @@ function createSchemaParser(ctx) {
|
|
|
1254
1225
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1255
1226
|
default: mergedDefault,
|
|
1256
1227
|
example: schema.example ?? memberNode.example,
|
|
1257
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1228
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1229
|
+
format: schema.format ?? memberNode.format
|
|
1258
1230
|
});
|
|
1259
1231
|
}
|
|
1260
1232
|
const filteredDiscriminantValues = [];
|
|
@@ -1306,7 +1278,7 @@ function createSchemaParser(ctx) {
|
|
|
1306
1278
|
}));
|
|
1307
1279
|
return _kubb_core.ast.createSchema({
|
|
1308
1280
|
type: "intersection",
|
|
1309
|
-
members: [..._kubb_core.ast.
|
|
1281
|
+
members: [..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1310
1282
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1311
1283
|
});
|
|
1312
1284
|
}
|
|
@@ -1386,7 +1358,8 @@ function createSchemaParser(ctx) {
|
|
|
1386
1358
|
name,
|
|
1387
1359
|
title: schema.title,
|
|
1388
1360
|
description: schema.description,
|
|
1389
|
-
deprecated: schema.deprecated
|
|
1361
|
+
deprecated: schema.deprecated,
|
|
1362
|
+
format: schema.format
|
|
1390
1363
|
});
|
|
1391
1364
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1392
1365
|
return _kubb_core.ast.createSchema({
|
|
@@ -1490,7 +1463,8 @@ function createSchemaParser(ctx) {
|
|
|
1490
1463
|
readOnly: schema.readOnly,
|
|
1491
1464
|
writeOnly: schema.writeOnly,
|
|
1492
1465
|
default: enumDefault,
|
|
1493
|
-
example: schema.example
|
|
1466
|
+
example: schema.example,
|
|
1467
|
+
format: schema.format
|
|
1494
1468
|
};
|
|
1495
1469
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1496
1470
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1529,15 +1503,18 @@ function createSchemaParser(ctx) {
|
|
|
1529
1503
|
schema: resolvedPropSchema,
|
|
1530
1504
|
name: _kubb_core.ast.childName(name, propName)
|
|
1531
1505
|
}, rawOptions);
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1506
|
+
const schemaNode = (() => {
|
|
1507
|
+
const node = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1508
|
+
const tupleNode = _kubb_core.ast.narrowSchema(node, "tuple");
|
|
1509
|
+
if (tupleNode?.items) {
|
|
1510
|
+
const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1511
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1512
|
+
...tupleNode,
|
|
1513
|
+
items: namedItems
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return node;
|
|
1517
|
+
})();
|
|
1541
1518
|
return _kubb_core.ast.createProperty({
|
|
1542
1519
|
name: propName,
|
|
1543
1520
|
schema: {
|
|
@@ -1548,11 +1525,12 @@ function createSchemaParser(ctx) {
|
|
|
1548
1525
|
});
|
|
1549
1526
|
}) : [];
|
|
1550
1527
|
const additionalProperties = schema.additionalProperties;
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1528
|
+
const additionalPropertiesNode = (() => {
|
|
1529
|
+
if (additionalProperties === true) return true;
|
|
1530
|
+
if (additionalProperties === false) return false;
|
|
1531
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1532
|
+
if (additionalProperties) return _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1533
|
+
})();
|
|
1556
1534
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1557
1535
|
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1558
1536
|
const objectNode = _kubb_core.ast.createSchema({
|
|
@@ -1599,7 +1577,7 @@ function createSchemaParser(ctx) {
|
|
|
1599
1577
|
*/
|
|
1600
1578
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1601
1579
|
const rawItems = schema.items;
|
|
1602
|
-
const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(
|
|
1580
|
+
const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(null, name, options.enumSuffix) : void 0;
|
|
1603
1581
|
const items = rawItems ? [parseSchema({
|
|
1604
1582
|
schema: rawItems,
|
|
1605
1583
|
name: itemName
|
|
@@ -1663,7 +1641,8 @@ function createSchemaParser(ctx) {
|
|
|
1663
1641
|
title: schema.title,
|
|
1664
1642
|
description: schema.description,
|
|
1665
1643
|
deprecated: schema.deprecated,
|
|
1666
|
-
nullable
|
|
1644
|
+
nullable,
|
|
1645
|
+
format: schema.format
|
|
1667
1646
|
});
|
|
1668
1647
|
}
|
|
1669
1648
|
/**
|
|
@@ -1741,7 +1720,8 @@ function createSchemaParser(ctx) {
|
|
|
1741
1720
|
type: emptyType,
|
|
1742
1721
|
name,
|
|
1743
1722
|
title: schema.title,
|
|
1744
|
-
description: schema.description
|
|
1723
|
+
description: schema.description,
|
|
1724
|
+
format: schema.format
|
|
1745
1725
|
});
|
|
1746
1726
|
}
|
|
1747
1727
|
/**
|
|
@@ -1788,13 +1768,13 @@ function createSchemaParser(ctx) {
|
|
|
1788
1768
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1789
1769
|
*/
|
|
1790
1770
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1791
|
-
if (!schema?.properties) return
|
|
1771
|
+
if (!schema?.properties) return null;
|
|
1792
1772
|
const keys = [];
|
|
1793
1773
|
for (const key in schema.properties) {
|
|
1794
1774
|
const prop = schema.properties[key];
|
|
1795
1775
|
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
1796
1776
|
}
|
|
1797
|
-
return keys.length ? keys :
|
|
1777
|
+
return keys.length ? keys : null;
|
|
1798
1778
|
}
|
|
1799
1779
|
/**
|
|
1800
1780
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
@@ -1851,48 +1831,202 @@ function createSchemaParser(ctx) {
|
|
|
1851
1831
|
parseParameter
|
|
1852
1832
|
};
|
|
1853
1833
|
}
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/discriminator.ts
|
|
1836
|
+
/**
|
|
1837
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
1838
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
1839
|
+
*
|
|
1840
|
+
* Extracted from `applyDiscriminatorInheritance` so the streaming path can call it on a
|
|
1841
|
+
* small pre-parsed subset of schemas (only the discriminator parents) rather than on all
|
|
1842
|
+
* schemas at once.
|
|
1843
|
+
*/
|
|
1844
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
1845
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
1846
|
+
for (const schema of schemas) {
|
|
1847
|
+
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
1848
|
+
if (!unionNode) {
|
|
1849
|
+
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
1850
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
1851
|
+
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
1852
|
+
if (u) {
|
|
1853
|
+
unionNode = u;
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
1859
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
1860
|
+
for (const member of members) {
|
|
1861
|
+
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
1862
|
+
if (!intersectionNode?.members) continue;
|
|
1863
|
+
let refNode = null;
|
|
1864
|
+
let objNode = null;
|
|
1865
|
+
for (const m of intersectionNode.members) {
|
|
1866
|
+
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
1867
|
+
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
1868
|
+
}
|
|
1869
|
+
if (!refNode?.name || !objNode) continue;
|
|
1870
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
1871
|
+
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
|
|
1872
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
1873
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
1874
|
+
if (!enumValues.length) continue;
|
|
1875
|
+
const existing = childMap.get(refNode.name);
|
|
1876
|
+
if (!existing) {
|
|
1877
|
+
childMap.set(refNode.name, {
|
|
1878
|
+
propertyName: discriminatorPropertyName,
|
|
1879
|
+
enumValues: [...enumValues]
|
|
1880
|
+
});
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
existing.enumValues.push(...enumValues);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
return childMap;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
1890
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
1891
|
+
* without buffering all schemas.
|
|
1892
|
+
*/
|
|
1893
|
+
function patchDiscriminatorNode(node, entry) {
|
|
1894
|
+
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
1895
|
+
if (!objectNode) return node;
|
|
1896
|
+
const { propertyName, enumValues } = entry;
|
|
1897
|
+
const enumSchema = _kubb_core.ast.createSchema({
|
|
1898
|
+
type: "enum",
|
|
1899
|
+
enumValues
|
|
1900
|
+
});
|
|
1901
|
+
const newProp = _kubb_core.ast.createProperty({
|
|
1902
|
+
name: propertyName,
|
|
1903
|
+
required: true,
|
|
1904
|
+
schema: enumSchema
|
|
1905
|
+
});
|
|
1906
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
1907
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
1908
|
+
return {
|
|
1909
|
+
...objectNode,
|
|
1910
|
+
properties: newProperties
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
//#endregion
|
|
1914
|
+
//#region src/stream.ts
|
|
1915
|
+
/**
|
|
1916
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
1917
|
+
* interpolating any `serverVariables` into the URL template.
|
|
1918
|
+
*
|
|
1919
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
1920
|
+
*
|
|
1921
|
+
* @example Resolve the first server
|
|
1922
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
1923
|
+
*
|
|
1924
|
+
* @example Override a path variable
|
|
1925
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
1926
|
+
*/
|
|
1927
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
1928
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
1929
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
1930
|
+
}
|
|
1854
1931
|
/**
|
|
1855
|
-
* Parses
|
|
1932
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
1856
1933
|
*
|
|
1857
|
-
*
|
|
1858
|
-
*
|
|
1859
|
-
* the
|
|
1934
|
+
* Three things happen in this single pass:
|
|
1935
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
1936
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
1937
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
1938
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
1860
1939
|
*
|
|
1861
|
-
*
|
|
1940
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
1941
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
1942
|
+
*
|
|
1943
|
+
* Each schema is parsed again during the streaming pass — this is intentional.
|
|
1944
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1862
1945
|
*
|
|
1863
1946
|
* @example
|
|
1864
1947
|
* ```ts
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
*
|
|
1948
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
1949
|
+
* schemas,
|
|
1950
|
+
* parseSchema,
|
|
1951
|
+
* parserOptions,
|
|
1952
|
+
* discriminator: 'strict',
|
|
1953
|
+
* })
|
|
1869
1954
|
* ```
|
|
1870
1955
|
*/
|
|
1871
|
-
function
|
|
1872
|
-
const
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
}
|
|
1886
|
-
const paths = new oas.default(document).getPaths();
|
|
1887
|
-
const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
|
|
1956
|
+
function preScan({ schemas, parseSchema, parserOptions, discriminator }) {
|
|
1957
|
+
const allNodes = [];
|
|
1958
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
1959
|
+
const enumNames = [];
|
|
1960
|
+
const discriminatorParentNodes = [];
|
|
1961
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1962
|
+
const node = parseSchema({
|
|
1963
|
+
schema,
|
|
1964
|
+
name
|
|
1965
|
+
}, parserOptions);
|
|
1966
|
+
allNodes.push(node);
|
|
1967
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
1968
|
+
if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
1969
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
1970
|
+
}
|
|
1888
1971
|
return {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
nameMapping
|
|
1972
|
+
refAliasMap,
|
|
1973
|
+
enumNames,
|
|
1974
|
+
circularNames: [..._kubb_core.ast.findCircularSchemas(allNodes)],
|
|
1975
|
+
discriminatorChildMap: discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null
|
|
1894
1976
|
};
|
|
1895
1977
|
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
1980
|
+
*
|
|
1981
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
1982
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
1983
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
1984
|
+
*
|
|
1985
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
1986
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
1987
|
+
*
|
|
1988
|
+
* @example
|
|
1989
|
+
* ```ts
|
|
1990
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
1991
|
+
* for await (const schema of streamNode.schemas) {
|
|
1992
|
+
* // each call to for-await restarts from the first schema
|
|
1993
|
+
* }
|
|
1994
|
+
* ```
|
|
1995
|
+
*/
|
|
1996
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta }) {
|
|
1997
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
1998
|
+
return (async function* () {
|
|
1999
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2000
|
+
const alias = refAliasMap.get(name);
|
|
2001
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2002
|
+
yield {
|
|
2003
|
+
...parseSchema({
|
|
2004
|
+
schema: schemas[alias.name],
|
|
2005
|
+
name: alias.name
|
|
2006
|
+
}, parserOptions),
|
|
2007
|
+
name
|
|
2008
|
+
};
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
const parsed = parseSchema({
|
|
2012
|
+
schema,
|
|
2013
|
+
name
|
|
2014
|
+
}, parserOptions);
|
|
2015
|
+
yield discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2016
|
+
}
|
|
2017
|
+
})();
|
|
2018
|
+
} };
|
|
2019
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2020
|
+
return (async function* () {
|
|
2021
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2022
|
+
if (!operation) continue;
|
|
2023
|
+
const node = parseOperation(parserOptions, operation);
|
|
2024
|
+
if (node) yield node;
|
|
2025
|
+
}
|
|
2026
|
+
})();
|
|
2027
|
+
} };
|
|
2028
|
+
return _kubb_core.ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
2029
|
+
}
|
|
1896
2030
|
//#endregion
|
|
1897
2031
|
//#region src/adapter.ts
|
|
1898
2032
|
/**
|
|
@@ -1920,9 +2054,65 @@ const adapterOasName = "oas";
|
|
|
1920
2054
|
*/
|
|
1921
2055
|
const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
1922
2056
|
const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
|
|
2057
|
+
const parserOptions = {
|
|
2058
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2059
|
+
dateType,
|
|
2060
|
+
integerType,
|
|
2061
|
+
unknownType,
|
|
2062
|
+
emptySchemaType,
|
|
2063
|
+
enumSuffix
|
|
2064
|
+
};
|
|
1923
2065
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1924
|
-
let parsedDocument;
|
|
1925
|
-
|
|
2066
|
+
let parsedDocument = null;
|
|
2067
|
+
const ensureDocument = once(async (source) => {
|
|
2068
|
+
const fresh = await parseFromConfig(source);
|
|
2069
|
+
if (validate) await validateDocument(fresh);
|
|
2070
|
+
parsedDocument = fresh;
|
|
2071
|
+
return fresh;
|
|
2072
|
+
});
|
|
2073
|
+
const ensureSchemas = once(async (document) => {
|
|
2074
|
+
const result = getSchemas(document, { contentType });
|
|
2075
|
+
nameMapping = result.nameMapping;
|
|
2076
|
+
return result.schemas;
|
|
2077
|
+
});
|
|
2078
|
+
const ensureBaseOas = once((document) => new oas.default(document));
|
|
2079
|
+
const ensureSchemaParser = once((document) => createSchemaParser({
|
|
2080
|
+
document,
|
|
2081
|
+
contentType
|
|
2082
|
+
}));
|
|
2083
|
+
const ensurePreScan = once((schemas, parseSchema) => preScan({
|
|
2084
|
+
schemas,
|
|
2085
|
+
parseSchema,
|
|
2086
|
+
parserOptions,
|
|
2087
|
+
discriminator
|
|
2088
|
+
}));
|
|
2089
|
+
async function createStream(source) {
|
|
2090
|
+
const document = await ensureDocument(source);
|
|
2091
|
+
const schemas = await ensureSchemas(document);
|
|
2092
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2093
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap } = ensurePreScan(schemas, parseSchema);
|
|
2094
|
+
return createInputStream({
|
|
2095
|
+
schemas,
|
|
2096
|
+
parseSchema,
|
|
2097
|
+
parseOperation,
|
|
2098
|
+
baseOas: ensureBaseOas(document),
|
|
2099
|
+
parserOptions,
|
|
2100
|
+
refAliasMap,
|
|
2101
|
+
discriminatorChildMap,
|
|
2102
|
+
meta: {
|
|
2103
|
+
title: document.info?.title,
|
|
2104
|
+
description: document.info?.description,
|
|
2105
|
+
version: document.info?.version,
|
|
2106
|
+
baseURL: resolveBaseUrl({
|
|
2107
|
+
document,
|
|
2108
|
+
serverIndex,
|
|
2109
|
+
serverVariables
|
|
2110
|
+
}),
|
|
2111
|
+
circularNames,
|
|
2112
|
+
enumNames
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
1926
2116
|
return {
|
|
1927
2117
|
name: "oas",
|
|
1928
2118
|
get options() {
|
|
@@ -1943,8 +2133,8 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1943
2133
|
get document() {
|
|
1944
2134
|
return parsedDocument;
|
|
1945
2135
|
},
|
|
1946
|
-
|
|
1947
|
-
|
|
2136
|
+
async validate(input, options) {
|
|
2137
|
+
await validateDocument(await parseDocument(input), options);
|
|
1948
2138
|
},
|
|
1949
2139
|
getImports(node, resolve) {
|
|
1950
2140
|
return _kubb_core.ast.collectImports({
|
|
@@ -1952,7 +2142,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1952
2142
|
nameMapping,
|
|
1953
2143
|
resolve: (schemaName) => {
|
|
1954
2144
|
const result = resolve(schemaName);
|
|
1955
|
-
if (!result) return;
|
|
2145
|
+
if (!result) return null;
|
|
1956
2146
|
return _kubb_core.ast.createImport({
|
|
1957
2147
|
name: [result.name],
|
|
1958
2148
|
path: result.path
|
|
@@ -1961,32 +2151,20 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1961
2151
|
});
|
|
1962
2152
|
},
|
|
1963
2153
|
async parse(source) {
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
});
|
|
1976
|
-
const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
|
|
1977
|
-
nameMapping = parsedNameMapping;
|
|
1978
|
-
parsedDocument = document;
|
|
1979
|
-
inputNode = _kubb_core.ast.createInput({
|
|
1980
|
-
...node,
|
|
1981
|
-
meta: {
|
|
1982
|
-
title: document.info?.title,
|
|
1983
|
-
description: document.info?.description,
|
|
1984
|
-
version: document.info?.version,
|
|
1985
|
-
baseURL
|
|
1986
|
-
}
|
|
2154
|
+
const streamNode = await createStream(source);
|
|
2155
|
+
const collect = async (iter) => {
|
|
2156
|
+
const out = [];
|
|
2157
|
+
for await (const item of iter) out.push(item);
|
|
2158
|
+
return out;
|
|
2159
|
+
};
|
|
2160
|
+
const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
|
|
2161
|
+
return _kubb_core.ast.createInput({
|
|
2162
|
+
schemas,
|
|
2163
|
+
operations,
|
|
2164
|
+
meta: streamNode.meta
|
|
1987
2165
|
});
|
|
1988
|
-
|
|
1989
|
-
|
|
2166
|
+
},
|
|
2167
|
+
stream: createStream
|
|
1990
2168
|
};
|
|
1991
2169
|
});
|
|
1992
2170
|
//#endregion
|
|
@@ -2006,8 +2184,5 @@ exports.HttpMethods = HttpMethods;
|
|
|
2006
2184
|
exports.adapterOas = adapterOas;
|
|
2007
2185
|
exports.adapterOasName = adapterOasName;
|
|
2008
2186
|
exports.mergeDocuments = mergeDocuments;
|
|
2009
|
-
exports.parseDocument = parseDocument;
|
|
2010
|
-
exports.parseFromConfig = parseFromConfig;
|
|
2011
|
-
exports.validateDocument = validateDocument;
|
|
2012
2187
|
|
|
2013
2188
|
//# sourceMappingURL=index.cjs.map
|