@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.js
CHANGED
|
@@ -1,213 +1,12 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import { ast, createAdapter } from "@kubb/core";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { ast, createAdapter } from "@kubb/core";
|
|
4
|
+
import BaseOas from "oas";
|
|
4
5
|
import { bundle, loadConfig } from "@redocly/openapi-core";
|
|
5
6
|
import OASNormalize from "oas-normalize";
|
|
6
7
|
import swagger2openapi from "swagger2openapi";
|
|
7
|
-
import BaseOas from "oas";
|
|
8
8
|
import { isRef } from "oas/types";
|
|
9
9
|
import { matchesMimeType } from "oas/utils";
|
|
10
|
-
//#region src/constants.ts
|
|
11
|
-
/**
|
|
12
|
-
* Default parser options applied when no explicit options are provided.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
17
|
-
*
|
|
18
|
-
* const parser = createOasParser(oas)
|
|
19
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
const DEFAULT_PARSER_OPTIONS = {
|
|
23
|
-
dateType: "string",
|
|
24
|
-
integerType: "bigint",
|
|
25
|
-
unknownType: "any",
|
|
26
|
-
emptySchemaType: "any",
|
|
27
|
-
enumSuffix: "enum"
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
31
|
-
*
|
|
32
|
-
* Used when building or parsing `$ref` strings.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
40
|
-
/**
|
|
41
|
-
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
42
|
-
*/
|
|
43
|
-
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
44
|
-
/**
|
|
45
|
-
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
46
|
-
*/
|
|
47
|
-
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
48
|
-
/**
|
|
49
|
-
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
50
|
-
*/
|
|
51
|
-
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
52
|
-
/**
|
|
53
|
-
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
54
|
-
*
|
|
55
|
-
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
56
|
-
* intersection member rather than being merged into the parent.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
61
|
-
*
|
|
62
|
-
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
63
|
-
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
const structuralKeys = new Set([
|
|
67
|
-
"properties",
|
|
68
|
-
"items",
|
|
69
|
-
"additionalProperties",
|
|
70
|
-
"oneOf",
|
|
71
|
-
"anyOf",
|
|
72
|
-
"allOf",
|
|
73
|
-
"not"
|
|
74
|
-
]);
|
|
75
|
-
/**
|
|
76
|
-
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
77
|
-
*
|
|
78
|
-
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
79
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
80
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
81
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* ```ts
|
|
85
|
-
* import { formatMap } from '@kubb/adapter-oas'
|
|
86
|
-
*
|
|
87
|
-
* formatMap['uuid'] // 'uuid'
|
|
88
|
-
* formatMap['binary'] // 'blob'
|
|
89
|
-
* formatMap['float'] // 'number'
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
const formatMap = {
|
|
93
|
-
uuid: "uuid",
|
|
94
|
-
email: "email",
|
|
95
|
-
"idn-email": "email",
|
|
96
|
-
uri: "url",
|
|
97
|
-
"uri-reference": "url",
|
|
98
|
-
url: "url",
|
|
99
|
-
ipv4: "ipv4",
|
|
100
|
-
ipv6: "ipv6",
|
|
101
|
-
hostname: "url",
|
|
102
|
-
"idn-hostname": "url",
|
|
103
|
-
binary: "blob",
|
|
104
|
-
byte: "blob",
|
|
105
|
-
int32: "integer",
|
|
106
|
-
float: "number",
|
|
107
|
-
double: "number"
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```ts
|
|
114
|
-
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
115
|
-
*
|
|
116
|
-
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
117
|
-
* ```
|
|
118
|
-
*/
|
|
119
|
-
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
120
|
-
/**
|
|
121
|
-
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
122
|
-
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
123
|
-
*/
|
|
124
|
-
const typeOptionMap = new Map([
|
|
125
|
-
["any", ast.schemaTypes.any],
|
|
126
|
-
["unknown", ast.schemaTypes.unknown],
|
|
127
|
-
["void", ast.schemaTypes.void]
|
|
128
|
-
]);
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region src/discriminator.ts
|
|
131
|
-
/**
|
|
132
|
-
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
133
|
-
*
|
|
134
|
-
* Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
|
|
135
|
-
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
136
|
-
* child object schema.
|
|
137
|
-
*
|
|
138
|
-
* Returns a new `InputNode` — the original is never mutated.
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```ts
|
|
142
|
-
* const { root } = parseOas(document, options)
|
|
143
|
-
* const next = applyDiscriminatorInheritance(root)
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
function applyDiscriminatorInheritance(root) {
|
|
147
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
148
|
-
for (const schema of root.schemas) {
|
|
149
|
-
let unionNode = ast.narrowSchema(schema, "union");
|
|
150
|
-
if (!unionNode) {
|
|
151
|
-
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
152
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
153
|
-
const u = ast.narrowSchema(m, "union");
|
|
154
|
-
if (u) {
|
|
155
|
-
unionNode = u;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
161
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
162
|
-
for (const member of members) {
|
|
163
|
-
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
164
|
-
if (!intersectionNode?.members) continue;
|
|
165
|
-
let refNode;
|
|
166
|
-
let objNode;
|
|
167
|
-
for (const m of intersectionNode.members) {
|
|
168
|
-
refNode ??= ast.narrowSchema(m, "ref");
|
|
169
|
-
objNode ??= ast.narrowSchema(m, "object");
|
|
170
|
-
}
|
|
171
|
-
if (!refNode?.name || !objNode) continue;
|
|
172
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
173
|
-
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : void 0;
|
|
174
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
175
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
176
|
-
if (!enumValues.length) continue;
|
|
177
|
-
const existing = childMap.get(refNode.name);
|
|
178
|
-
if (existing) existing.enumValues.push(...enumValues);
|
|
179
|
-
else childMap.set(refNode.name, {
|
|
180
|
-
propertyName: discriminatorPropertyName,
|
|
181
|
-
enumValues: [...enumValues]
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (childMap.size === 0) return root;
|
|
186
|
-
return ast.transform(root, { schema(node, { parent }) {
|
|
187
|
-
if (parent?.kind !== "Input" || !node.name) return;
|
|
188
|
-
const entry = childMap.get(node.name);
|
|
189
|
-
if (!entry) return;
|
|
190
|
-
const objectNode = ast.narrowSchema(node, "object");
|
|
191
|
-
if (!objectNode) return;
|
|
192
|
-
const { propertyName, enumValues } = entry;
|
|
193
|
-
const enumSchema = ast.createSchema({
|
|
194
|
-
type: "enum",
|
|
195
|
-
enumValues
|
|
196
|
-
});
|
|
197
|
-
const newProp = ast.createProperty({
|
|
198
|
-
name: propertyName,
|
|
199
|
-
required: true,
|
|
200
|
-
schema: enumSchema
|
|
201
|
-
});
|
|
202
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
203
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
204
|
-
return {
|
|
205
|
-
...objectNode,
|
|
206
|
-
properties: newProperties
|
|
207
|
-
};
|
|
208
|
-
} });
|
|
209
|
-
}
|
|
210
|
-
//#endregion
|
|
211
10
|
//#region ../../internals/utils/src/casing.ts
|
|
212
11
|
/**
|
|
213
12
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -306,6 +105,30 @@ function mergeDeep(target, source) {
|
|
|
306
105
|
return result;
|
|
307
106
|
}
|
|
308
107
|
//#endregion
|
|
108
|
+
//#region ../../internals/utils/src/promise.ts
|
|
109
|
+
/**
|
|
110
|
+
* Returns a wrapper that caches the result of the first invocation and replays
|
|
111
|
+
* it for every subsequent call, ignoring later arguments.
|
|
112
|
+
*
|
|
113
|
+
* Works for sync and async factories — for async, the cached value is the
|
|
114
|
+
* promise itself, so concurrent callers share one in-flight execution and
|
|
115
|
+
* cannot race each other.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const loadDocument = once(async (path: string) => parse(await readFile(path)))
|
|
120
|
+
* const a = loadDocument('./a.yaml') // parses
|
|
121
|
+
* const b = loadDocument('./b.yaml') // returns the cached promise from the first call
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
function once(factory) {
|
|
125
|
+
let cache;
|
|
126
|
+
return (...args) => {
|
|
127
|
+
if (!cache) cache = { value: factory(...args) };
|
|
128
|
+
return cache.value;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
309
132
|
//#region ../../internals/utils/src/reserved.ts
|
|
310
133
|
/**
|
|
311
134
|
* JavaScript and Java reserved words.
|
|
@@ -555,6 +378,126 @@ var URLPath = class {
|
|
|
555
378
|
}
|
|
556
379
|
};
|
|
557
380
|
//#endregion
|
|
381
|
+
//#region src/constants.ts
|
|
382
|
+
/**
|
|
383
|
+
* Default parser options applied when no explicit options are provided.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
388
|
+
*
|
|
389
|
+
* const parser = createOasParser(oas)
|
|
390
|
+
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
394
|
+
dateType: "string",
|
|
395
|
+
integerType: "bigint",
|
|
396
|
+
unknownType: "any",
|
|
397
|
+
emptySchemaType: "any",
|
|
398
|
+
enumSuffix: "enum"
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
402
|
+
*
|
|
403
|
+
* Used when building or parsing `$ref` strings.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
411
|
+
/**
|
|
412
|
+
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
413
|
+
*/
|
|
414
|
+
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
415
|
+
/**
|
|
416
|
+
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
417
|
+
*/
|
|
418
|
+
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
419
|
+
/**
|
|
420
|
+
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
421
|
+
*/
|
|
422
|
+
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
423
|
+
/**
|
|
424
|
+
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
425
|
+
*
|
|
426
|
+
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
427
|
+
* intersection member rather than being merged into the parent.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
432
|
+
*
|
|
433
|
+
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
434
|
+
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
const structuralKeys = new Set([
|
|
438
|
+
"properties",
|
|
439
|
+
"items",
|
|
440
|
+
"additionalProperties",
|
|
441
|
+
"oneOf",
|
|
442
|
+
"anyOf",
|
|
443
|
+
"allOf",
|
|
444
|
+
"not"
|
|
445
|
+
]);
|
|
446
|
+
/**
|
|
447
|
+
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
448
|
+
*
|
|
449
|
+
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
450
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
451
|
+
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
452
|
+
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* import { formatMap } from '@kubb/adapter-oas'
|
|
457
|
+
*
|
|
458
|
+
* formatMap['uuid'] // 'uuid'
|
|
459
|
+
* formatMap['binary'] // 'blob'
|
|
460
|
+
* formatMap['float'] // 'number'
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
const formatMap = {
|
|
464
|
+
uuid: "uuid",
|
|
465
|
+
email: "email",
|
|
466
|
+
"idn-email": "email",
|
|
467
|
+
uri: "url",
|
|
468
|
+
"uri-reference": "url",
|
|
469
|
+
url: "url",
|
|
470
|
+
ipv4: "ipv4",
|
|
471
|
+
ipv6: "ipv6",
|
|
472
|
+
hostname: "url",
|
|
473
|
+
"idn-hostname": "url",
|
|
474
|
+
binary: "blob",
|
|
475
|
+
byte: "blob",
|
|
476
|
+
int32: "integer",
|
|
477
|
+
float: "number",
|
|
478
|
+
double: "number"
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```ts
|
|
485
|
+
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
486
|
+
*
|
|
487
|
+
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
488
|
+
* ```
|
|
489
|
+
*/
|
|
490
|
+
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
491
|
+
/**
|
|
492
|
+
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
493
|
+
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
494
|
+
*/
|
|
495
|
+
const typeOptionMap = new Map([
|
|
496
|
+
["any", ast.schemaTypes.any],
|
|
497
|
+
["unknown", ast.schemaTypes.unknown],
|
|
498
|
+
["void", ast.schemaTypes.void]
|
|
499
|
+
]);
|
|
500
|
+
//#endregion
|
|
558
501
|
//#region src/guards.ts
|
|
559
502
|
/**
|
|
560
503
|
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
@@ -660,11 +603,10 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
660
603
|
* ```
|
|
661
604
|
*/
|
|
662
605
|
async function mergeDocuments(pathOrApi) {
|
|
663
|
-
const documents =
|
|
664
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
606
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
665
607
|
enablePaths: false,
|
|
666
608
|
canBundle: false
|
|
667
|
-
}));
|
|
609
|
+
})));
|
|
668
610
|
if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
|
|
669
611
|
const seed = {
|
|
670
612
|
openapi: MERGE_OPENAPI_VERSION,
|
|
@@ -720,6 +662,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
720
662
|
}
|
|
721
663
|
//#endregion
|
|
722
664
|
//#region src/refs.ts
|
|
665
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
723
666
|
/**
|
|
724
667
|
* Resolves a local JSON pointer reference from a document.
|
|
725
668
|
*
|
|
@@ -735,10 +678,17 @@ function resolveRef(document, $ref) {
|
|
|
735
678
|
const origRef = $ref;
|
|
736
679
|
$ref = $ref.trim();
|
|
737
680
|
if ($ref === "") return null;
|
|
738
|
-
if (
|
|
739
|
-
|
|
681
|
+
if (!$ref.startsWith("#")) return null;
|
|
682
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
683
|
+
let docCache = _refCache.get(document);
|
|
684
|
+
if (!docCache) {
|
|
685
|
+
docCache = /* @__PURE__ */ new Map();
|
|
686
|
+
_refCache.set(document, docCache);
|
|
687
|
+
}
|
|
688
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
740
689
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
741
690
|
if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
|
|
691
|
+
docCache.set($ref, current);
|
|
742
692
|
return current;
|
|
743
693
|
}
|
|
744
694
|
/**
|
|
@@ -953,21 +903,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
953
903
|
/**
|
|
954
904
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
955
905
|
*/
|
|
956
|
-
function collectRefs(schema
|
|
906
|
+
function* collectRefs(schema) {
|
|
957
907
|
if (Array.isArray(schema)) {
|
|
958
|
-
for (const item of schema) collectRefs(item
|
|
959
|
-
return
|
|
908
|
+
for (const item of schema) yield* collectRefs(item);
|
|
909
|
+
return;
|
|
960
910
|
}
|
|
961
911
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
962
912
|
const value = schema[key];
|
|
963
|
-
if (key === "$ref" && typeof value === "string") {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
913
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
914
|
+
yield* collectRefs(value);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
918
|
+
const name = value.slice(21);
|
|
919
|
+
if (name) yield name;
|
|
920
|
+
}
|
|
969
921
|
}
|
|
970
|
-
return refs;
|
|
971
922
|
}
|
|
972
923
|
/**
|
|
973
924
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -983,7 +934,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
983
934
|
*/
|
|
984
935
|
function sortSchemas(schemas) {
|
|
985
936
|
const deps = /* @__PURE__ */ new Map();
|
|
986
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
937
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
987
938
|
const sorted = [];
|
|
988
939
|
const visited = /* @__PURE__ */ new Set();
|
|
989
940
|
function visit(name, stack) {
|
|
@@ -1118,7 +1069,8 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1118
1069
|
readOnly: schema.readOnly,
|
|
1119
1070
|
writeOnly: schema.writeOnly,
|
|
1120
1071
|
default: defaultValue,
|
|
1121
|
-
example: schema.example
|
|
1072
|
+
example: schema.example,
|
|
1073
|
+
format: schema.format
|
|
1122
1074
|
};
|
|
1123
1075
|
}
|
|
1124
1076
|
/**
|
|
@@ -1168,7 +1120,7 @@ function normalizeArrayEnum(schema) {
|
|
|
1168
1120
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1169
1121
|
* made possible by hoisting of function declarations.
|
|
1170
1122
|
*
|
|
1171
|
-
* @
|
|
1123
|
+
* @internal
|
|
1172
1124
|
*/
|
|
1173
1125
|
function createSchemaParser(ctx) {
|
|
1174
1126
|
const document = ctx.document;
|
|
@@ -1178,6 +1130,19 @@ function createSchemaParser(ctx) {
|
|
|
1178
1130
|
*/
|
|
1179
1131
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1180
1132
|
/**
|
|
1133
|
+
* Cache of already-resolved `$ref` schemas within this parser instance.
|
|
1134
|
+
*
|
|
1135
|
+
* Without this, the same referenced schema (e.g. `customer`) is fully re-expanded
|
|
1136
|
+
* every time it appears as a `$ref` in a different parent schema. In heavily
|
|
1137
|
+
* cross-referenced specs like Stripe (~1 400 schemas), this causes exponential
|
|
1138
|
+
* blowup — `customer` alone may be referenced from dozens of top-level schemas,
|
|
1139
|
+
* each triggering a fresh recursive expansion of its entire sub-tree.
|
|
1140
|
+
*
|
|
1141
|
+
* Memoizing by `$ref` path reduces the overall work from O(2^depth) to O(N)
|
|
1142
|
+
* where N is the number of unique schema names.
|
|
1143
|
+
*/
|
|
1144
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1145
|
+
/**
|
|
1181
1146
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1182
1147
|
*
|
|
1183
1148
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1186,16 +1151,22 @@ function createSchemaParser(ctx) {
|
|
|
1186
1151
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1187
1152
|
*/
|
|
1188
1153
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1189
|
-
let resolvedSchema;
|
|
1154
|
+
let resolvedSchema = null;
|
|
1190
1155
|
const refPath = schema.$ref;
|
|
1191
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1156
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1157
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1158
|
+
try {
|
|
1159
|
+
const referenced = resolveRef(document, refPath);
|
|
1160
|
+
if (referenced) {
|
|
1161
|
+
resolvingRefs.add(refPath);
|
|
1162
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1163
|
+
resolvingRefs.delete(refPath);
|
|
1164
|
+
}
|
|
1165
|
+
} catch {}
|
|
1166
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1197
1167
|
}
|
|
1198
|
-
|
|
1168
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1169
|
+
}
|
|
1199
1170
|
return ast.createSchema({
|
|
1200
1171
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1201
1172
|
type: "ref",
|
|
@@ -1228,7 +1199,8 @@ function createSchemaParser(ctx) {
|
|
|
1228
1199
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1229
1200
|
default: mergedDefault,
|
|
1230
1201
|
example: schema.example ?? memberNode.example,
|
|
1231
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1202
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1203
|
+
format: schema.format ?? memberNode.format
|
|
1232
1204
|
});
|
|
1233
1205
|
}
|
|
1234
1206
|
const filteredDiscriminantValues = [];
|
|
@@ -1280,7 +1252,7 @@ function createSchemaParser(ctx) {
|
|
|
1280
1252
|
}));
|
|
1281
1253
|
return ast.createSchema({
|
|
1282
1254
|
type: "intersection",
|
|
1283
|
-
members: [...ast.
|
|
1255
|
+
members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1284
1256
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1285
1257
|
});
|
|
1286
1258
|
}
|
|
@@ -1360,7 +1332,8 @@ function createSchemaParser(ctx) {
|
|
|
1360
1332
|
name,
|
|
1361
1333
|
title: schema.title,
|
|
1362
1334
|
description: schema.description,
|
|
1363
|
-
deprecated: schema.deprecated
|
|
1335
|
+
deprecated: schema.deprecated,
|
|
1336
|
+
format: schema.format
|
|
1364
1337
|
});
|
|
1365
1338
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1366
1339
|
return ast.createSchema({
|
|
@@ -1464,7 +1437,8 @@ function createSchemaParser(ctx) {
|
|
|
1464
1437
|
readOnly: schema.readOnly,
|
|
1465
1438
|
writeOnly: schema.writeOnly,
|
|
1466
1439
|
default: enumDefault,
|
|
1467
|
-
example: schema.example
|
|
1440
|
+
example: schema.example,
|
|
1441
|
+
format: schema.format
|
|
1468
1442
|
};
|
|
1469
1443
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1470
1444
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1503,15 +1477,18 @@ function createSchemaParser(ctx) {
|
|
|
1503
1477
|
schema: resolvedPropSchema,
|
|
1504
1478
|
name: ast.childName(name, propName)
|
|
1505
1479
|
}, rawOptions);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1480
|
+
const schemaNode = (() => {
|
|
1481
|
+
const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1482
|
+
const tupleNode = ast.narrowSchema(node, "tuple");
|
|
1483
|
+
if (tupleNode?.items) {
|
|
1484
|
+
const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1485
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1486
|
+
...tupleNode,
|
|
1487
|
+
items: namedItems
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
return node;
|
|
1491
|
+
})();
|
|
1515
1492
|
return ast.createProperty({
|
|
1516
1493
|
name: propName,
|
|
1517
1494
|
schema: {
|
|
@@ -1522,11 +1499,12 @@ function createSchemaParser(ctx) {
|
|
|
1522
1499
|
});
|
|
1523
1500
|
}) : [];
|
|
1524
1501
|
const additionalProperties = schema.additionalProperties;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1502
|
+
const additionalPropertiesNode = (() => {
|
|
1503
|
+
if (additionalProperties === true) return true;
|
|
1504
|
+
if (additionalProperties === false) return false;
|
|
1505
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1506
|
+
if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1507
|
+
})();
|
|
1530
1508
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1531
1509
|
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1532
1510
|
const objectNode = ast.createSchema({
|
|
@@ -1573,7 +1551,7 @@ function createSchemaParser(ctx) {
|
|
|
1573
1551
|
*/
|
|
1574
1552
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1575
1553
|
const rawItems = schema.items;
|
|
1576
|
-
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(
|
|
1554
|
+
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(null, name, options.enumSuffix) : void 0;
|
|
1577
1555
|
const items = rawItems ? [parseSchema({
|
|
1578
1556
|
schema: rawItems,
|
|
1579
1557
|
name: itemName
|
|
@@ -1637,7 +1615,8 @@ function createSchemaParser(ctx) {
|
|
|
1637
1615
|
title: schema.title,
|
|
1638
1616
|
description: schema.description,
|
|
1639
1617
|
deprecated: schema.deprecated,
|
|
1640
|
-
nullable
|
|
1618
|
+
nullable,
|
|
1619
|
+
format: schema.format
|
|
1641
1620
|
});
|
|
1642
1621
|
}
|
|
1643
1622
|
/**
|
|
@@ -1715,7 +1694,8 @@ function createSchemaParser(ctx) {
|
|
|
1715
1694
|
type: emptyType,
|
|
1716
1695
|
name,
|
|
1717
1696
|
title: schema.title,
|
|
1718
|
-
description: schema.description
|
|
1697
|
+
description: schema.description,
|
|
1698
|
+
format: schema.format
|
|
1719
1699
|
});
|
|
1720
1700
|
}
|
|
1721
1701
|
/**
|
|
@@ -1762,13 +1742,13 @@ function createSchemaParser(ctx) {
|
|
|
1762
1742
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1763
1743
|
*/
|
|
1764
1744
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1765
|
-
if (!schema?.properties) return
|
|
1745
|
+
if (!schema?.properties) return null;
|
|
1766
1746
|
const keys = [];
|
|
1767
1747
|
for (const key in schema.properties) {
|
|
1768
1748
|
const prop = schema.properties[key];
|
|
1769
1749
|
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
1770
1750
|
}
|
|
1771
|
-
return keys.length ? keys :
|
|
1751
|
+
return keys.length ? keys : null;
|
|
1772
1752
|
}
|
|
1773
1753
|
/**
|
|
1774
1754
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
@@ -1825,48 +1805,202 @@ function createSchemaParser(ctx) {
|
|
|
1825
1805
|
parseParameter
|
|
1826
1806
|
};
|
|
1827
1807
|
}
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/discriminator.ts
|
|
1810
|
+
/**
|
|
1811
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
1812
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
1813
|
+
*
|
|
1814
|
+
* Extracted from `applyDiscriminatorInheritance` so the streaming path can call it on a
|
|
1815
|
+
* small pre-parsed subset of schemas (only the discriminator parents) rather than on all
|
|
1816
|
+
* schemas at once.
|
|
1817
|
+
*/
|
|
1818
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
1819
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
1820
|
+
for (const schema of schemas) {
|
|
1821
|
+
let unionNode = ast.narrowSchema(schema, "union");
|
|
1822
|
+
if (!unionNode) {
|
|
1823
|
+
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
1824
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
1825
|
+
const u = ast.narrowSchema(m, "union");
|
|
1826
|
+
if (u) {
|
|
1827
|
+
unionNode = u;
|
|
1828
|
+
break;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
1833
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
1834
|
+
for (const member of members) {
|
|
1835
|
+
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
1836
|
+
if (!intersectionNode?.members) continue;
|
|
1837
|
+
let refNode = null;
|
|
1838
|
+
let objNode = null;
|
|
1839
|
+
for (const m of intersectionNode.members) {
|
|
1840
|
+
refNode ??= ast.narrowSchema(m, "ref");
|
|
1841
|
+
objNode ??= ast.narrowSchema(m, "object");
|
|
1842
|
+
}
|
|
1843
|
+
if (!refNode?.name || !objNode) continue;
|
|
1844
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
1845
|
+
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
1846
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
1847
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
1848
|
+
if (!enumValues.length) continue;
|
|
1849
|
+
const existing = childMap.get(refNode.name);
|
|
1850
|
+
if (!existing) {
|
|
1851
|
+
childMap.set(refNode.name, {
|
|
1852
|
+
propertyName: discriminatorPropertyName,
|
|
1853
|
+
enumValues: [...enumValues]
|
|
1854
|
+
});
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
existing.enumValues.push(...enumValues);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return childMap;
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
1864
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
1865
|
+
* without buffering all schemas.
|
|
1866
|
+
*/
|
|
1867
|
+
function patchDiscriminatorNode(node, entry) {
|
|
1868
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
1869
|
+
if (!objectNode) return node;
|
|
1870
|
+
const { propertyName, enumValues } = entry;
|
|
1871
|
+
const enumSchema = ast.createSchema({
|
|
1872
|
+
type: "enum",
|
|
1873
|
+
enumValues
|
|
1874
|
+
});
|
|
1875
|
+
const newProp = ast.createProperty({
|
|
1876
|
+
name: propertyName,
|
|
1877
|
+
required: true,
|
|
1878
|
+
schema: enumSchema
|
|
1879
|
+
});
|
|
1880
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
1881
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
1882
|
+
return {
|
|
1883
|
+
...objectNode,
|
|
1884
|
+
properties: newProperties
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
//#endregion
|
|
1888
|
+
//#region src/stream.ts
|
|
1889
|
+
/**
|
|
1890
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
1891
|
+
* interpolating any `serverVariables` into the URL template.
|
|
1892
|
+
*
|
|
1893
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
1894
|
+
*
|
|
1895
|
+
* @example Resolve the first server
|
|
1896
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
1897
|
+
*
|
|
1898
|
+
* @example Override a path variable
|
|
1899
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
1900
|
+
*/
|
|
1901
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
1902
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
1903
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
1904
|
+
}
|
|
1828
1905
|
/**
|
|
1829
|
-
* Parses
|
|
1906
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
1830
1907
|
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* the
|
|
1908
|
+
* Three things happen in this single pass:
|
|
1909
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
1910
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
1911
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
1912
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
1834
1913
|
*
|
|
1835
|
-
*
|
|
1914
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
1915
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
1916
|
+
*
|
|
1917
|
+
* Each schema is parsed again during the streaming pass — this is intentional.
|
|
1918
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1836
1919
|
*
|
|
1837
1920
|
* @example
|
|
1838
1921
|
* ```ts
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
1922
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
1923
|
+
* schemas,
|
|
1924
|
+
* parseSchema,
|
|
1925
|
+
* parserOptions,
|
|
1926
|
+
* discriminator: 'strict',
|
|
1927
|
+
* })
|
|
1843
1928
|
* ```
|
|
1844
1929
|
*/
|
|
1845
|
-
function
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
}
|
|
1860
|
-
const paths = new BaseOas(document).getPaths();
|
|
1861
|
-
const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
|
|
1930
|
+
function preScan({ schemas, parseSchema, parserOptions, discriminator }) {
|
|
1931
|
+
const allNodes = [];
|
|
1932
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
1933
|
+
const enumNames = [];
|
|
1934
|
+
const discriminatorParentNodes = [];
|
|
1935
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1936
|
+
const node = parseSchema({
|
|
1937
|
+
schema,
|
|
1938
|
+
name
|
|
1939
|
+
}, parserOptions);
|
|
1940
|
+
allNodes.push(node);
|
|
1941
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
1942
|
+
if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
1943
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
1944
|
+
}
|
|
1862
1945
|
return {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
nameMapping
|
|
1946
|
+
refAliasMap,
|
|
1947
|
+
enumNames,
|
|
1948
|
+
circularNames: [...ast.findCircularSchemas(allNodes)],
|
|
1949
|
+
discriminatorChildMap: discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null
|
|
1868
1950
|
};
|
|
1869
1951
|
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
1954
|
+
*
|
|
1955
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
1956
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
1957
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
1958
|
+
*
|
|
1959
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
1960
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* ```ts
|
|
1964
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
1965
|
+
* for await (const schema of streamNode.schemas) {
|
|
1966
|
+
* // each call to for-await restarts from the first schema
|
|
1967
|
+
* }
|
|
1968
|
+
* ```
|
|
1969
|
+
*/
|
|
1970
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta }) {
|
|
1971
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
1972
|
+
return (async function* () {
|
|
1973
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1974
|
+
const alias = refAliasMap.get(name);
|
|
1975
|
+
if (alias?.name && schemas[alias.name]) {
|
|
1976
|
+
yield {
|
|
1977
|
+
...parseSchema({
|
|
1978
|
+
schema: schemas[alias.name],
|
|
1979
|
+
name: alias.name
|
|
1980
|
+
}, parserOptions),
|
|
1981
|
+
name
|
|
1982
|
+
};
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
const parsed = parseSchema({
|
|
1986
|
+
schema,
|
|
1987
|
+
name
|
|
1988
|
+
}, parserOptions);
|
|
1989
|
+
yield discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
1990
|
+
}
|
|
1991
|
+
})();
|
|
1992
|
+
} };
|
|
1993
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
1994
|
+
return (async function* () {
|
|
1995
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
1996
|
+
if (!operation) continue;
|
|
1997
|
+
const node = parseOperation(parserOptions, operation);
|
|
1998
|
+
if (node) yield node;
|
|
1999
|
+
}
|
|
2000
|
+
})();
|
|
2001
|
+
} };
|
|
2002
|
+
return ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
2003
|
+
}
|
|
1870
2004
|
//#endregion
|
|
1871
2005
|
//#region src/adapter.ts
|
|
1872
2006
|
/**
|
|
@@ -1894,9 +2028,65 @@ const adapterOasName = "oas";
|
|
|
1894
2028
|
*/
|
|
1895
2029
|
const adapterOas = createAdapter((options) => {
|
|
1896
2030
|
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;
|
|
2031
|
+
const parserOptions = {
|
|
2032
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2033
|
+
dateType,
|
|
2034
|
+
integerType,
|
|
2035
|
+
unknownType,
|
|
2036
|
+
emptySchemaType,
|
|
2037
|
+
enumSuffix
|
|
2038
|
+
};
|
|
1897
2039
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1898
|
-
let parsedDocument;
|
|
1899
|
-
|
|
2040
|
+
let parsedDocument = null;
|
|
2041
|
+
const ensureDocument = once(async (source) => {
|
|
2042
|
+
const fresh = await parseFromConfig(source);
|
|
2043
|
+
if (validate) await validateDocument(fresh);
|
|
2044
|
+
parsedDocument = fresh;
|
|
2045
|
+
return fresh;
|
|
2046
|
+
});
|
|
2047
|
+
const ensureSchemas = once(async (document) => {
|
|
2048
|
+
const result = getSchemas(document, { contentType });
|
|
2049
|
+
nameMapping = result.nameMapping;
|
|
2050
|
+
return result.schemas;
|
|
2051
|
+
});
|
|
2052
|
+
const ensureBaseOas = once((document) => new BaseOas(document));
|
|
2053
|
+
const ensureSchemaParser = once((document) => createSchemaParser({
|
|
2054
|
+
document,
|
|
2055
|
+
contentType
|
|
2056
|
+
}));
|
|
2057
|
+
const ensurePreScan = once((schemas, parseSchema) => preScan({
|
|
2058
|
+
schemas,
|
|
2059
|
+
parseSchema,
|
|
2060
|
+
parserOptions,
|
|
2061
|
+
discriminator
|
|
2062
|
+
}));
|
|
2063
|
+
async function createStream(source) {
|
|
2064
|
+
const document = await ensureDocument(source);
|
|
2065
|
+
const schemas = await ensureSchemas(document);
|
|
2066
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2067
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap } = ensurePreScan(schemas, parseSchema);
|
|
2068
|
+
return createInputStream({
|
|
2069
|
+
schemas,
|
|
2070
|
+
parseSchema,
|
|
2071
|
+
parseOperation,
|
|
2072
|
+
baseOas: ensureBaseOas(document),
|
|
2073
|
+
parserOptions,
|
|
2074
|
+
refAliasMap,
|
|
2075
|
+
discriminatorChildMap,
|
|
2076
|
+
meta: {
|
|
2077
|
+
title: document.info?.title,
|
|
2078
|
+
description: document.info?.description,
|
|
2079
|
+
version: document.info?.version,
|
|
2080
|
+
baseURL: resolveBaseUrl({
|
|
2081
|
+
document,
|
|
2082
|
+
serverIndex,
|
|
2083
|
+
serverVariables
|
|
2084
|
+
}),
|
|
2085
|
+
circularNames,
|
|
2086
|
+
enumNames
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
1900
2090
|
return {
|
|
1901
2091
|
name: "oas",
|
|
1902
2092
|
get options() {
|
|
@@ -1917,8 +2107,8 @@ const adapterOas = createAdapter((options) => {
|
|
|
1917
2107
|
get document() {
|
|
1918
2108
|
return parsedDocument;
|
|
1919
2109
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
2110
|
+
async validate(input, options) {
|
|
2111
|
+
await validateDocument(await parseDocument(input), options);
|
|
1922
2112
|
},
|
|
1923
2113
|
getImports(node, resolve) {
|
|
1924
2114
|
return ast.collectImports({
|
|
@@ -1926,7 +2116,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1926
2116
|
nameMapping,
|
|
1927
2117
|
resolve: (schemaName) => {
|
|
1928
2118
|
const result = resolve(schemaName);
|
|
1929
|
-
if (!result) return;
|
|
2119
|
+
if (!result) return null;
|
|
1930
2120
|
return ast.createImport({
|
|
1931
2121
|
name: [result.name],
|
|
1932
2122
|
path: result.path
|
|
@@ -1935,32 +2125,20 @@ const adapterOas = createAdapter((options) => {
|
|
|
1935
2125
|
});
|
|
1936
2126
|
},
|
|
1937
2127
|
async parse(source) {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
});
|
|
1950
|
-
const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
|
|
1951
|
-
nameMapping = parsedNameMapping;
|
|
1952
|
-
parsedDocument = document;
|
|
1953
|
-
inputNode = ast.createInput({
|
|
1954
|
-
...node,
|
|
1955
|
-
meta: {
|
|
1956
|
-
title: document.info?.title,
|
|
1957
|
-
description: document.info?.description,
|
|
1958
|
-
version: document.info?.version,
|
|
1959
|
-
baseURL
|
|
1960
|
-
}
|
|
2128
|
+
const streamNode = await createStream(source);
|
|
2129
|
+
const collect = async (iter) => {
|
|
2130
|
+
const out = [];
|
|
2131
|
+
for await (const item of iter) out.push(item);
|
|
2132
|
+
return out;
|
|
2133
|
+
};
|
|
2134
|
+
const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
|
|
2135
|
+
return ast.createInput({
|
|
2136
|
+
schemas,
|
|
2137
|
+
operations,
|
|
2138
|
+
meta: streamNode.meta
|
|
1961
2139
|
});
|
|
1962
|
-
|
|
1963
|
-
|
|
2140
|
+
},
|
|
2141
|
+
stream: createStream
|
|
1964
2142
|
};
|
|
1965
2143
|
});
|
|
1966
2144
|
//#endregion
|
|
@@ -1976,6 +2154,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
1976
2154
|
*/
|
|
1977
2155
|
const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
1978
2156
|
//#endregion
|
|
1979
|
-
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments
|
|
2157
|
+
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
|
|
1980
2158
|
|
|
1981
2159
|
//# sourceMappingURL=index.js.map
|