@kubb/adapter-oas 5.0.0-beta.19 → 5.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,236 +1,12 @@
1
1
  import "./chunk--u3MIqq1.js";
2
+ import path from "node:path";
2
3
  import { ast, createAdapter } from "@kubb/core";
3
4
  import BaseOas from "oas";
4
- import path from "node:path";
5
5
  import { bundle, loadConfig } from "@redocly/openapi-core";
6
6
  import OASNormalize from "oas-normalize";
7
7
  import swagger2openapi from "swagger2openapi";
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
- * Builds a map of child schema names → discriminator patch data by scanning the given
133
- * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
134
- *
135
- * Extracted from `applyDiscriminatorInheritance` so the streaming path can call it on a
136
- * small pre-parsed subset of schemas (only the discriminator parents) rather than on all
137
- * schemas at once.
138
- */
139
- function buildDiscriminatorChildMap(schemas) {
140
- const childMap = /* @__PURE__ */ new Map();
141
- for (const schema of schemas) {
142
- let unionNode = ast.narrowSchema(schema, "union");
143
- if (!unionNode) {
144
- const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
145
- if (intersectionMembers) for (const m of intersectionMembers) {
146
- const u = ast.narrowSchema(m, "union");
147
- if (u) {
148
- unionNode = u;
149
- break;
150
- }
151
- }
152
- }
153
- if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
154
- const { discriminatorPropertyName, members } = unionNode;
155
- for (const member of members) {
156
- const intersectionNode = ast.narrowSchema(member, "intersection");
157
- if (!intersectionNode?.members) continue;
158
- let refNode;
159
- let objNode;
160
- for (const m of intersectionNode.members) {
161
- refNode ??= ast.narrowSchema(m, "ref");
162
- objNode ??= ast.narrowSchema(m, "object");
163
- }
164
- if (!refNode?.name || !objNode) continue;
165
- const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
166
- const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : void 0;
167
- if (!enumNode?.enumValues?.length) continue;
168
- const enumValues = enumNode.enumValues.filter((v) => v !== null);
169
- if (!enumValues.length) continue;
170
- const existing = childMap.get(refNode.name);
171
- if (!existing) {
172
- childMap.set(refNode.name, {
173
- propertyName: discriminatorPropertyName,
174
- enumValues: [...enumValues]
175
- });
176
- continue;
177
- }
178
- existing.enumValues.push(...enumValues);
179
- }
180
- }
181
- return childMap;
182
- }
183
- /**
184
- * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
185
- * the discriminant property). Used by the streaming path to apply patches inline per yield
186
- * without buffering all schemas.
187
- */
188
- function patchDiscriminatorNode(node, entry) {
189
- const objectNode = ast.narrowSchema(node, "object");
190
- if (!objectNode) return node;
191
- const { propertyName, enumValues } = entry;
192
- const enumSchema = ast.createSchema({
193
- type: "enum",
194
- enumValues
195
- });
196
- const newProp = ast.createProperty({
197
- name: propertyName,
198
- required: true,
199
- schema: enumSchema
200
- });
201
- const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
202
- const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
203
- return {
204
- ...objectNode,
205
- properties: newProperties
206
- };
207
- }
208
- /**
209
- * Injects discriminator enum values into child schemas so they know which value identifies them.
210
- *
211
- * Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
212
- * enum value each union member is mapped to, then adds (or replaces) that property on the matching
213
- * child object schema.
214
- *
215
- * Returns a new `InputNode` — the original is never mutated.
216
- *
217
- * @example
218
- * ```ts
219
- * const { root } = parseOas(document, options)
220
- * const next = applyDiscriminatorInheritance(root)
221
- * ```
222
- */
223
- function applyDiscriminatorInheritance(root) {
224
- const childMap = buildDiscriminatorChildMap(root.schemas);
225
- if (childMap.size === 0) return root;
226
- return ast.transform(root, { schema(node, { parent }) {
227
- if (parent?.kind !== "Input" || !node.name) return;
228
- const entry = childMap.get(node.name);
229
- if (!entry) return;
230
- return patchDiscriminatorNode(node, entry);
231
- } });
232
- }
233
- //#endregion
234
10
  //#region ../../internals/utils/src/casing.ts
235
11
  /**
236
12
  * Shared implementation for camelCase and PascalCase conversion.
@@ -329,6 +105,30 @@ function mergeDeep(target, source) {
329
105
  return result;
330
106
  }
331
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
332
132
  //#region ../../internals/utils/src/reserved.ts
333
133
  /**
334
134
  * JavaScript and Java reserved words.
@@ -578,6 +378,126 @@ var URLPath = class {
578
378
  }
579
379
  };
580
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
581
501
  //#region src/guards.ts
582
502
  /**
583
503
  * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
@@ -1885,48 +1805,202 @@ function createSchemaParser(ctx) {
1885
1805
  parseParameter
1886
1806
  };
1887
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;
1838
+ let objNode;
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") : void 0;
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
1888
1889
  /**
1889
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
1890
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
1891
+ * interpolating any `serverVariables` into the URL template.
1892
+ *
1893
+ * Returns `undefined` when `serverIndex` is omitted or out of range.
1890
1894
  *
1891
- * This is the main entry point for `@kubb/adapter-oas`. It converts OpenAPI/Swagger specs into a spec-agnostic tree
1892
- * that downstream plugins (`plugin-ts`, `plugin-zod`, etc.) consume for code generation. No code is generated here —
1893
- * the tree is a pure data structure representing all schemas and operations.
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) : void 0;
1904
+ }
1905
+ /**
1906
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
1894
1907
  *
1895
- * Returns the AST root and a `nameMapping` for resolving schema references.
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.
1913
+ *
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.
1896
1919
  *
1897
1920
  * @example
1898
1921
  * ```ts
1899
- * import { parseOas } from '@kubb/adapter-oas'
1900
- *
1901
- * const document = await parseFromConfig(config)
1902
- * const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
1922
+ * const { refAliasMap, enumNames, circularNames } = preScan({
1923
+ * schemas,
1924
+ * parseSchema,
1925
+ * parserOptions,
1926
+ * discriminator: 'strict',
1927
+ * })
1903
1928
  * ```
1904
1929
  */
1905
- function parseOas(document, options = {}) {
1906
- const { contentType, ...parserOptions } = options;
1907
- const mergedOptions = {
1908
- ...DEFAULT_PARSER_OPTIONS,
1909
- ...parserOptions
1910
- };
1911
- const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1912
- const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1913
- document,
1914
- contentType
1915
- });
1916
- const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
1917
- schema,
1918
- name
1919
- }, mergedOptions));
1920
- const paths = new BaseOas(document).getPaths();
1921
- const operations = Object.entries(paths).flatMap(([, 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
+ }
1922
1945
  return {
1923
- root: ast.createInput({
1924
- schemas,
1925
- operations
1926
- }),
1927
- nameMapping
1946
+ refAliasMap,
1947
+ enumNames,
1948
+ circularNames: [...ast.findCircularSchemas(allNodes)],
1949
+ discriminatorChildMap: discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null
1928
1950
  };
1929
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
+ }
1930
2004
  //#endregion
1931
2005
  //#region src/adapter.ts
1932
2006
  /**
@@ -1964,38 +2038,54 @@ const adapterOas = createAdapter((options) => {
1964
2038
  };
1965
2039
  let nameMapping = /* @__PURE__ */ new Map();
1966
2040
  let parsedDocument = null;
1967
- let schemaObjects = null;
1968
- let baseOasInstance = null;
1969
- let schemaParserInstance = null;
1970
- function resolveBaseURL(document) {
1971
- const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1972
- return server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1973
- }
1974
- async function ensureDocument(source) {
1975
- if (parsedDocument) return parsedDocument;
2041
+ const ensureDocument = once(async (source) => {
1976
2042
  const fresh = await parseFromConfig(source);
1977
2043
  if (validate) await validateDocument(fresh);
1978
2044
  parsedDocument = fresh;
1979
2045
  return fresh;
1980
- }
1981
- async function ensureSchemas(document) {
1982
- if (!schemaObjects) {
1983
- const result = getSchemas(document, { contentType });
1984
- schemaObjects = result.schemas;
1985
- nameMapping = result.nameMapping;
1986
- }
1987
- return schemaObjects;
1988
- }
1989
- function ensureBaseOas(document) {
1990
- if (!baseOasInstance) baseOasInstance = new BaseOas(document);
1991
- return baseOasInstance;
1992
- }
1993
- function ensureSchemaParser(document) {
1994
- if (!schemaParserInstance) schemaParserInstance = createSchemaParser({
1995
- document,
1996
- contentType
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
+ }
1997
2088
  });
1998
- return schemaParserInstance;
1999
2089
  }
2000
2090
  return {
2001
2091
  name: "oas",
@@ -2035,81 +2125,20 @@ const adapterOas = createAdapter((options) => {
2035
2125
  });
2036
2126
  },
2037
2127
  async parse(source) {
2038
- const document = await ensureDocument(source);
2039
- const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
2040
- contentType,
2041
- dateType,
2042
- integerType,
2043
- unknownType,
2044
- emptySchemaType,
2045
- enumSuffix
2046
- });
2047
- const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
2048
- nameMapping = parsedNameMapping;
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)]);
2049
2135
  return ast.createInput({
2050
- ...node,
2051
- meta: {
2052
- title: document.info?.title,
2053
- description: document.info?.description,
2054
- version: document.info?.version,
2055
- baseURL: resolveBaseURL(document)
2056
- }
2136
+ schemas,
2137
+ operations,
2138
+ meta: streamNode.meta
2057
2139
  });
2058
2140
  },
2059
- async count(source) {
2060
- const document = await ensureDocument(source);
2061
- const schemas = await ensureSchemas(document);
2062
- const baseOas = ensureBaseOas(document);
2063
- const operationCount = Object.values(baseOas.getPaths()).flatMap(Object.values).filter(Boolean).length;
2064
- return {
2065
- schemas: Object.keys(schemas).length,
2066
- operations: operationCount
2067
- };
2068
- },
2069
- async stream(source) {
2070
- const document = await ensureDocument(source);
2071
- const schemas = await ensureSchemas(document);
2072
- const discriminatorChildMap = (() => {
2073
- if (discriminator !== "inherit") return null;
2074
- const { parseSchema: _preParser } = ensureSchemaParser(document);
2075
- const parentNodes = [];
2076
- for (const [name, schema] of Object.entries(schemas)) if ((schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) parentNodes.push(_preParser({
2077
- schema,
2078
- name
2079
- }, parserOptions));
2080
- return parentNodes.length > 0 ? buildDiscriminatorChildMap(parentNodes) : null;
2081
- })();
2082
- const schemasIterable = { [Symbol.asyncIterator]() {
2083
- return (async function* () {
2084
- const { parseSchema: _parseSchema } = ensureSchemaParser(document);
2085
- for (const [name, schema] of Object.entries(schemas)) {
2086
- const parsedNode = _parseSchema({
2087
- schema,
2088
- name
2089
- }, parserOptions);
2090
- const entry = discriminatorChildMap?.get(name);
2091
- yield entry ? patchDiscriminatorNode(parsedNode, entry) : parsedNode;
2092
- }
2093
- })();
2094
- } };
2095
- const operationsIterable = { [Symbol.asyncIterator]() {
2096
- return (async function* () {
2097
- const { parseOperation: _parseOperation } = ensureSchemaParser(document);
2098
- const paths = ensureBaseOas(document).getPaths();
2099
- for (const methods of Object.values(paths)) for (const operation of Object.values(methods)) {
2100
- if (!operation) continue;
2101
- const node = _parseOperation(parserOptions, operation);
2102
- if (node) yield node;
2103
- }
2104
- })();
2105
- } };
2106
- return ast.createStreamInput(schemasIterable, operationsIterable, {
2107
- title: document.info?.title,
2108
- description: document.info?.description,
2109
- version: document.info?.version,
2110
- baseURL: resolveBaseURL(document)
2111
- });
2112
- }
2141
+ stream: createStream
2113
2142
  };
2114
2143
  });
2115
2144
  //#endregion