@kubb/adapter-oas 5.0.0-beta.84 → 5.0.0-beta.85
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.cjs +248 -355
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +248 -355
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1259,7 +1259,7 @@ type AdapterOasResolvedOptions = {
|
|
|
1259
1259
|
enumSuffix: AdapterOasOptions['enumSuffix'];
|
|
1260
1260
|
/**
|
|
1261
1261
|
* Map from original `$ref` paths to their collision-resolved schema names.
|
|
1262
|
-
* Populated once the adapter resolves a spec's schemas, on the first `
|
|
1262
|
+
* Populated once the adapter resolves a spec's schemas, on the first `parse()`.
|
|
1263
1263
|
*
|
|
1264
1264
|
* @example
|
|
1265
1265
|
* ```ts
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,119 @@ const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
|
142
142
|
*/
|
|
143
143
|
const enumDescriptionKeys = ["x-enumDescriptions", "x-enum-descriptions"];
|
|
144
144
|
//#endregion
|
|
145
|
+
//#region src/discriminator.ts
|
|
146
|
+
/**
|
|
147
|
+
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
148
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
149
|
+
*
|
|
150
|
+
* Called on a small pre-parsed subset of schemas (only the discriminator parents)
|
|
151
|
+
* rather than on all schemas at once.
|
|
152
|
+
*/
|
|
153
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
154
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const schema of schemas) {
|
|
156
|
+
let unionNode = ast.narrowSchema(schema, "union");
|
|
157
|
+
if (!unionNode) {
|
|
158
|
+
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
159
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
160
|
+
const u = ast.narrowSchema(m, "union");
|
|
161
|
+
if (u) {
|
|
162
|
+
unionNode = u;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
168
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
169
|
+
for (const member of members) {
|
|
170
|
+
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
171
|
+
if (!intersectionNode?.members) continue;
|
|
172
|
+
let refNode = null;
|
|
173
|
+
let objNode = null;
|
|
174
|
+
for (const m of intersectionNode.members) {
|
|
175
|
+
refNode ??= ast.narrowSchema(m, "ref");
|
|
176
|
+
objNode ??= ast.narrowSchema(m, "object");
|
|
177
|
+
}
|
|
178
|
+
if (!refNode?.name || !objNode) continue;
|
|
179
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
180
|
+
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
181
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
182
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
183
|
+
if (!enumValues.length) continue;
|
|
184
|
+
const existing = childMap.get(refNode.name);
|
|
185
|
+
if (!existing) {
|
|
186
|
+
childMap.set(refNode.name, {
|
|
187
|
+
propertyName: discriminatorPropertyName,
|
|
188
|
+
enumValues: [...enumValues]
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
existing.enumValues.push(...enumValues);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return childMap;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
199
|
+
* the discriminant property).
|
|
200
|
+
*/
|
|
201
|
+
function patchDiscriminatorNode(node, entry) {
|
|
202
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
203
|
+
if (!objectNode) return node;
|
|
204
|
+
const { propertyName, enumValues } = entry;
|
|
205
|
+
const enumSchema = ast.factory.createSchema({
|
|
206
|
+
type: "enum",
|
|
207
|
+
enumValues
|
|
208
|
+
});
|
|
209
|
+
const newProp = ast.factory.createProperty({
|
|
210
|
+
name: propertyName,
|
|
211
|
+
required: true,
|
|
212
|
+
schema: enumSchema
|
|
213
|
+
});
|
|
214
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
215
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
216
|
+
return {
|
|
217
|
+
...objectNode,
|
|
218
|
+
properties: newProperties
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
227
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
231
|
+
return ast.factory.createSchema({
|
|
232
|
+
type: "object",
|
|
233
|
+
primitive: "object",
|
|
234
|
+
properties: [ast.factory.createProperty({
|
|
235
|
+
name: propertyName,
|
|
236
|
+
schema: ast.factory.createSchema({
|
|
237
|
+
type: "enum",
|
|
238
|
+
primitive: "string",
|
|
239
|
+
enumValues: [value]
|
|
240
|
+
}),
|
|
241
|
+
required: true
|
|
242
|
+
})]
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
function findDiscriminator(mapping, ref) {
|
|
254
|
+
if (!mapping || !ref) return null;
|
|
255
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
145
258
|
//#region ../../internals/utils/src/casing.ts
|
|
146
259
|
/**
|
|
147
260
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -430,6 +543,34 @@ function isDiscriminator(obj) {
|
|
|
430
543
|
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
431
544
|
}
|
|
432
545
|
//#endregion
|
|
546
|
+
//#region src/mime.ts
|
|
547
|
+
/**
|
|
548
|
+
* MIME type fragments that mark a media type as JSON-like.
|
|
549
|
+
*
|
|
550
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
551
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
552
|
+
*/
|
|
553
|
+
const jsonMimeFragments = [
|
|
554
|
+
"application/json",
|
|
555
|
+
"application/x-json",
|
|
556
|
+
"text/json",
|
|
557
|
+
"text/x-json",
|
|
558
|
+
"+json"
|
|
559
|
+
];
|
|
560
|
+
/**
|
|
561
|
+
* Returns `true` when a media type string is JSON-like.
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```ts
|
|
565
|
+
* isJsonMimeType('application/json') // true
|
|
566
|
+
* isJsonMimeType('application/vnd.api+json') // true
|
|
567
|
+
* isJsonMimeType('multipart/form-data') // false
|
|
568
|
+
* ```
|
|
569
|
+
*/
|
|
570
|
+
function isJsonMimeType(mimeType) {
|
|
571
|
+
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
433
574
|
//#region src/refs.ts
|
|
434
575
|
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
435
576
|
/**
|
|
@@ -496,179 +637,6 @@ function dereferenceWithRef(document, schema) {
|
|
|
496
637
|
return schema;
|
|
497
638
|
}
|
|
498
639
|
//#endregion
|
|
499
|
-
//#region src/dialect.ts
|
|
500
|
-
/**
|
|
501
|
-
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
502
|
-
*
|
|
503
|
-
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
504
|
-
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
505
|
-
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
506
|
-
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
507
|
-
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
508
|
-
* the rest unchanged.
|
|
509
|
-
*
|
|
510
|
-
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
511
|
-
* JSON Schema vocabulary, so the converters keep that common case.
|
|
512
|
-
*
|
|
513
|
-
* @example
|
|
514
|
-
* ```ts
|
|
515
|
-
* const parser = createSchemaParser(context) // uses oasDialect
|
|
516
|
-
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
const oasDialect = ast.defineDialect({
|
|
520
|
-
name: "oas",
|
|
521
|
-
schema: {
|
|
522
|
-
isNullable,
|
|
523
|
-
isReference,
|
|
524
|
-
isDiscriminator,
|
|
525
|
-
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
526
|
-
resolveRef
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
//#endregion
|
|
530
|
-
//#region src/discriminator.ts
|
|
531
|
-
/**
|
|
532
|
-
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
533
|
-
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
534
|
-
*
|
|
535
|
-
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
536
|
-
* discriminator parents) rather than on all schemas at once.
|
|
537
|
-
*/
|
|
538
|
-
function buildDiscriminatorChildMap(schemas) {
|
|
539
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
540
|
-
for (const schema of schemas) {
|
|
541
|
-
let unionNode = ast.narrowSchema(schema, "union");
|
|
542
|
-
if (!unionNode) {
|
|
543
|
-
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
544
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
545
|
-
const u = ast.narrowSchema(m, "union");
|
|
546
|
-
if (u) {
|
|
547
|
-
unionNode = u;
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
553
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
554
|
-
for (const member of members) {
|
|
555
|
-
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
556
|
-
if (!intersectionNode?.members) continue;
|
|
557
|
-
let refNode = null;
|
|
558
|
-
let objNode = null;
|
|
559
|
-
for (const m of intersectionNode.members) {
|
|
560
|
-
refNode ??= ast.narrowSchema(m, "ref");
|
|
561
|
-
objNode ??= ast.narrowSchema(m, "object");
|
|
562
|
-
}
|
|
563
|
-
if (!refNode?.name || !objNode) continue;
|
|
564
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
565
|
-
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
566
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
567
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
568
|
-
if (!enumValues.length) continue;
|
|
569
|
-
const existing = childMap.get(refNode.name);
|
|
570
|
-
if (!existing) {
|
|
571
|
-
childMap.set(refNode.name, {
|
|
572
|
-
propertyName: discriminatorPropertyName,
|
|
573
|
-
enumValues: [...enumValues]
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
existing.enumValues.push(...enumValues);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return childMap;
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
584
|
-
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
585
|
-
* without buffering all schemas.
|
|
586
|
-
*/
|
|
587
|
-
function patchDiscriminatorNode(node, entry) {
|
|
588
|
-
const objectNode = ast.narrowSchema(node, "object");
|
|
589
|
-
if (!objectNode) return node;
|
|
590
|
-
const { propertyName, enumValues } = entry;
|
|
591
|
-
const enumSchema = ast.factory.createSchema({
|
|
592
|
-
type: "enum",
|
|
593
|
-
enumValues
|
|
594
|
-
});
|
|
595
|
-
const newProp = ast.factory.createProperty({
|
|
596
|
-
name: propertyName,
|
|
597
|
-
required: true,
|
|
598
|
-
schema: enumSchema
|
|
599
|
-
});
|
|
600
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
601
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
602
|
-
return {
|
|
603
|
-
...objectNode,
|
|
604
|
-
properties: newProperties
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Creates a single-property object schema used as a discriminator literal.
|
|
609
|
-
*
|
|
610
|
-
* @example
|
|
611
|
-
* ```ts
|
|
612
|
-
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
613
|
-
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
614
|
-
* ```
|
|
615
|
-
*/
|
|
616
|
-
function createDiscriminantNode({ propertyName, value }) {
|
|
617
|
-
return ast.factory.createSchema({
|
|
618
|
-
type: "object",
|
|
619
|
-
primitive: "object",
|
|
620
|
-
properties: [ast.factory.createProperty({
|
|
621
|
-
name: propertyName,
|
|
622
|
-
schema: ast.factory.createSchema({
|
|
623
|
-
type: "enum",
|
|
624
|
-
primitive: "string",
|
|
625
|
-
enumValues: [value]
|
|
626
|
-
}),
|
|
627
|
-
required: true
|
|
628
|
-
})]
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.
|
|
633
|
-
*
|
|
634
|
-
* @example
|
|
635
|
-
* ```ts
|
|
636
|
-
* findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'
|
|
637
|
-
* ```
|
|
638
|
-
*/
|
|
639
|
-
function findDiscriminator(mapping, ref) {
|
|
640
|
-
if (!mapping || !ref) return null;
|
|
641
|
-
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
642
|
-
}
|
|
643
|
-
//#endregion
|
|
644
|
-
//#region src/mime.ts
|
|
645
|
-
/**
|
|
646
|
-
* MIME type fragments that mark a media type as JSON-like.
|
|
647
|
-
*
|
|
648
|
-
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
649
|
-
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
650
|
-
*/
|
|
651
|
-
const jsonMimeFragments = [
|
|
652
|
-
"application/json",
|
|
653
|
-
"application/x-json",
|
|
654
|
-
"text/json",
|
|
655
|
-
"text/x-json",
|
|
656
|
-
"+json"
|
|
657
|
-
];
|
|
658
|
-
/**
|
|
659
|
-
* Returns `true` when a media type string is JSON-like.
|
|
660
|
-
*
|
|
661
|
-
* @example
|
|
662
|
-
* ```ts
|
|
663
|
-
* isJsonMimeType('application/json') // true
|
|
664
|
-
* isJsonMimeType('application/vnd.api+json') // true
|
|
665
|
-
* isJsonMimeType('multipart/form-data') // false
|
|
666
|
-
* ```
|
|
667
|
-
*/
|
|
668
|
-
function isJsonMimeType(mimeType) {
|
|
669
|
-
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
670
|
-
}
|
|
671
|
-
//#endregion
|
|
672
640
|
//#region src/operation.ts
|
|
673
641
|
/**
|
|
674
642
|
* Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
|
|
@@ -795,8 +763,56 @@ function getOperations(document) {
|
|
|
795
763
|
return operations;
|
|
796
764
|
}
|
|
797
765
|
//#endregion
|
|
766
|
+
//#region src/dialect.ts
|
|
767
|
+
/**
|
|
768
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
769
|
+
*
|
|
770
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
771
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
772
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
773
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
774
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
775
|
+
* the rest unchanged.
|
|
776
|
+
*
|
|
777
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
778
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
783
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
const oasDialect = ast.defineDialect({
|
|
787
|
+
name: "oas",
|
|
788
|
+
schema: {
|
|
789
|
+
isNullable,
|
|
790
|
+
isReference,
|
|
791
|
+
isDiscriminator,
|
|
792
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
793
|
+
resolveRef
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
//#endregion
|
|
798
797
|
//#region src/resolvers.ts
|
|
799
798
|
/**
|
|
799
|
+
* Reads the server URL from the document's `servers` array at `server.index`,
|
|
800
|
+
* interpolating any `server.variables` into the URL template.
|
|
801
|
+
*
|
|
802
|
+
* Returns `null` when `server.index` is omitted or out of range.
|
|
803
|
+
*
|
|
804
|
+
* @example Resolve the first server
|
|
805
|
+
* `resolveBaseUrl({ document, server: { index: 0 } })`
|
|
806
|
+
*
|
|
807
|
+
* @example Override a path variable
|
|
808
|
+
* `resolveBaseUrl({ document, server: { index: 0, variables: { version: 'v2' } } })`
|
|
809
|
+
*/
|
|
810
|
+
function resolveBaseUrl({ document, server }) {
|
|
811
|
+
const index = server?.index;
|
|
812
|
+
const entry = index !== void 0 ? document.servers?.at(index) : void 0;
|
|
813
|
+
return entry?.url ? resolveServerUrl(entry, server?.variables) : null;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
800
816
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
801
817
|
* Resolution order: `overrides[key]` → `variable.default` → left unreplaced.
|
|
802
818
|
* Throws if an override value is not in the variable's `enum` list.
|
|
@@ -991,7 +1007,7 @@ function flattenSchema(schema) {
|
|
|
991
1007
|
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
992
1008
|
const { allOf: _allOf, ...rest } = schema;
|
|
993
1009
|
const merged = rest;
|
|
994
|
-
for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment))
|
|
1010
|
+
for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
|
|
995
1011
|
return merged;
|
|
996
1012
|
}
|
|
997
1013
|
/**
|
|
@@ -2168,7 +2184,7 @@ function refPromotedEnums(node, promoted) {
|
|
|
2168
2184
|
//#region src/schemaDiagnostics.ts
|
|
2169
2185
|
/**
|
|
2170
2186
|
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2171
|
-
* top-level schema. Walks the node the parser produced
|
|
2187
|
+
* top-level schema. Walks the node the parser produced, threading the RFC 6901
|
|
2172
2188
|
* pointer as it descends so a nested field reports against its full path
|
|
2173
2189
|
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2174
2190
|
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
@@ -2220,148 +2236,6 @@ function visit(node, pointer) {
|
|
|
2220
2236
|
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2221
2237
|
}
|
|
2222
2238
|
//#endregion
|
|
2223
|
-
//#region src/stream.ts
|
|
2224
|
-
/**
|
|
2225
|
-
* Reads the server URL from the document's `servers` array at `server.index`,
|
|
2226
|
-
* interpolating any `server.variables` into the URL template.
|
|
2227
|
-
*
|
|
2228
|
-
* Returns `null` when `server.index` is omitted or out of range.
|
|
2229
|
-
*
|
|
2230
|
-
* @example Resolve the first server
|
|
2231
|
-
* `resolveBaseUrl({ document, server: { index: 0 } })`
|
|
2232
|
-
*
|
|
2233
|
-
* @example Override a path variable
|
|
2234
|
-
* `resolveBaseUrl({ document, server: { index: 0, variables: { version: 'v2' } } })`
|
|
2235
|
-
*/
|
|
2236
|
-
function resolveBaseUrl({ document, server }) {
|
|
2237
|
-
const index = server?.index;
|
|
2238
|
-
const entry = index !== void 0 ? document.servers?.at(index) : void 0;
|
|
2239
|
-
return entry?.url ? resolveServerUrl(entry, server?.variables) : null;
|
|
2240
|
-
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
2243
|
-
*
|
|
2244
|
-
* Three things happen in this single pass:
|
|
2245
|
-
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2246
|
-
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2247
|
-
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2248
|
-
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
2249
|
-
*
|
|
2250
|
-
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2251
|
-
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
2252
|
-
*
|
|
2253
|
-
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2254
|
-
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
2255
|
-
*
|
|
2256
|
-
* @example
|
|
2257
|
-
* ```ts
|
|
2258
|
-
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2259
|
-
* schemas,
|
|
2260
|
-
* parseSchema,
|
|
2261
|
-
* parserOptions,
|
|
2262
|
-
* discriminator: 'preserve',
|
|
2263
|
-
* })
|
|
2264
|
-
* ```
|
|
2265
|
-
*/
|
|
2266
|
-
function preScan({ schemas, parseSchema, parseOperation, document, parserOptions, discriminator, enums = "inline" }) {
|
|
2267
|
-
const allNodes = [];
|
|
2268
|
-
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2269
|
-
const enumNames = [];
|
|
2270
|
-
const discriminatorParentNodes = [];
|
|
2271
|
-
for (const [name, schema] of Object.entries(schemas)) {
|
|
2272
|
-
const node = parseSchema({
|
|
2273
|
-
schema,
|
|
2274
|
-
name
|
|
2275
|
-
}, parserOptions);
|
|
2276
|
-
allNodes.push(node);
|
|
2277
|
-
reportSchemaDiagnostics({
|
|
2278
|
-
node,
|
|
2279
|
-
name
|
|
2280
|
-
});
|
|
2281
|
-
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2282
|
-
if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2283
|
-
if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2284
|
-
}
|
|
2285
|
-
const circularNames = [...findCircularSchemas(allNodes)];
|
|
2286
|
-
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2287
|
-
let promotedEnums = null;
|
|
2288
|
-
if (enums === "root" && document && parseOperation) {
|
|
2289
|
-
const operationNodes = [];
|
|
2290
|
-
for (const operation of getOperations(document)) {
|
|
2291
|
-
const operationNode = parseOperation(parserOptions, operation);
|
|
2292
|
-
if (operationNode) operationNodes.push(operationNode);
|
|
2293
|
-
}
|
|
2294
|
-
promotedEnums = collectInlineEnums([...allNodes, ...operationNodes], new Set(Object.keys(schemas)));
|
|
2295
|
-
for (const name of promotedEnums.keys()) enumNames.push(name);
|
|
2296
|
-
}
|
|
2297
|
-
return {
|
|
2298
|
-
refAliasMap,
|
|
2299
|
-
enumNames,
|
|
2300
|
-
circularNames,
|
|
2301
|
-
discriminatorChildMap,
|
|
2302
|
-
promotedEnums
|
|
2303
|
-
};
|
|
2304
|
-
}
|
|
2305
|
-
/**
|
|
2306
|
-
* Creates a lazy `InputNode<true>` from already-resolved adapter state.
|
|
2307
|
-
*
|
|
2308
|
-
* The schema and operation iterables each start a fresh parse pass on every
|
|
2309
|
-
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2310
|
-
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2311
|
-
*
|
|
2312
|
-
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2313
|
-
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2314
|
-
*
|
|
2315
|
-
* @example
|
|
2316
|
-
* ```ts
|
|
2317
|
-
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2318
|
-
* for await (const schema of streamNode.schemas) {
|
|
2319
|
-
* // each call to for-await restarts from the first schema
|
|
2320
|
-
* }
|
|
2321
|
-
* ```
|
|
2322
|
-
*/
|
|
2323
|
-
function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, promotedEnums, meta }) {
|
|
2324
|
-
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2325
|
-
return (async function* () {
|
|
2326
|
-
if (promotedEnums) for (const definition of promotedEnums.values()) yield definition;
|
|
2327
|
-
for (const [name, schema] of Object.entries(schemas)) {
|
|
2328
|
-
const alias = refAliasMap.get(name);
|
|
2329
|
-
if (alias?.name && schemas[alias.name]) {
|
|
2330
|
-
const aliasNode = {
|
|
2331
|
-
...parseSchema({
|
|
2332
|
-
schema: schemas[alias.name],
|
|
2333
|
-
name: alias.name
|
|
2334
|
-
}, parserOptions),
|
|
2335
|
-
name
|
|
2336
|
-
};
|
|
2337
|
-
yield promotedEnums ? refPromotedEnums(aliasNode, promotedEnums) : aliasNode;
|
|
2338
|
-
continue;
|
|
2339
|
-
}
|
|
2340
|
-
const parsed = parseSchema({
|
|
2341
|
-
schema,
|
|
2342
|
-
name
|
|
2343
|
-
}, parserOptions);
|
|
2344
|
-
const node = discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2345
|
-
yield promotedEnums ? refPromotedEnums(node, promotedEnums) : node;
|
|
2346
|
-
}
|
|
2347
|
-
})();
|
|
2348
|
-
} };
|
|
2349
|
-
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2350
|
-
return (async function* () {
|
|
2351
|
-
for (const operation of getOperations(document)) {
|
|
2352
|
-
const node = parseOperation(parserOptions, operation);
|
|
2353
|
-
if (node) yield promotedEnums ? refPromotedEnums(node, promotedEnums) : node;
|
|
2354
|
-
}
|
|
2355
|
-
})();
|
|
2356
|
-
} };
|
|
2357
|
-
return ast.factory.createInput({
|
|
2358
|
-
stream: true,
|
|
2359
|
-
schemas: schemasIterable,
|
|
2360
|
-
operations: operationsIterable,
|
|
2361
|
-
meta
|
|
2362
|
-
});
|
|
2363
|
-
}
|
|
2364
|
-
//#endregion
|
|
2365
2239
|
//#region src/adapter.ts
|
|
2366
2240
|
/**
|
|
2367
2241
|
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
@@ -2409,7 +2283,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
2409
2283
|
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2410
2284
|
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2411
2285
|
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2412
|
-
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2413
2286
|
function ensureDocument(source) {
|
|
2414
2287
|
const cached = documentCache.get(source);
|
|
2415
2288
|
if (cached) return cached;
|
|
@@ -2443,35 +2316,57 @@ const adapterOas = createAdapter((options) => {
|
|
|
2443
2316
|
schemaParserCache.set(document, parser);
|
|
2444
2317
|
return parser;
|
|
2445
2318
|
}
|
|
2446
|
-
function
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2319
|
+
function parseInput({ document, schemas, parser }) {
|
|
2320
|
+
const { parseSchema, parseOperation } = parser;
|
|
2321
|
+
const parsedByName = /* @__PURE__ */ new Map();
|
|
2322
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2323
|
+
const enumNames = [];
|
|
2324
|
+
const discriminatorParentNodes = [];
|
|
2325
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2326
|
+
const node = parseSchema({
|
|
2327
|
+
schema,
|
|
2328
|
+
name
|
|
2329
|
+
}, parserOptions);
|
|
2330
|
+
parsedByName.set(name, node);
|
|
2331
|
+
reportSchemaDiagnostics({
|
|
2332
|
+
node,
|
|
2333
|
+
name
|
|
2334
|
+
});
|
|
2335
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2336
|
+
if (narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
|
|
2337
|
+
if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2338
|
+
}
|
|
2339
|
+
const circularNames = [...findCircularSchemas([...parsedByName.values()])];
|
|
2340
|
+
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2341
|
+
const operationNodes = [];
|
|
2342
|
+
for (const operation of getOperations(document)) {
|
|
2343
|
+
const operationNode = parseOperation(parserOptions, operation);
|
|
2344
|
+
if (operationNode) operationNodes.push(operationNode);
|
|
2345
|
+
}
|
|
2346
|
+
let promotedEnums = null;
|
|
2347
|
+
if (enums === "root") {
|
|
2348
|
+
promotedEnums = collectInlineEnums([...parsedByName.values(), ...operationNodes], new Set(Object.keys(schemas)));
|
|
2349
|
+
for (const name of promotedEnums.keys()) enumNames.push(name);
|
|
2350
|
+
}
|
|
2351
|
+
const schemaNodes = promotedEnums ? [...promotedEnums.values()] : [];
|
|
2352
|
+
for (const name of Object.keys(schemas)) {
|
|
2353
|
+
const alias = refAliasMap.get(name);
|
|
2354
|
+
let node;
|
|
2355
|
+
if (alias?.name && parsedByName.has(alias.name)) node = {
|
|
2356
|
+
...parsedByName.get(alias.name),
|
|
2357
|
+
name
|
|
2358
|
+
};
|
|
2359
|
+
else {
|
|
2360
|
+
const parsed = parsedByName.get(name);
|
|
2361
|
+
const child = discriminatorChildMap?.get(name);
|
|
2362
|
+
node = child ? patchDiscriminatorNode(parsed, child) : parsed;
|
|
2363
|
+
}
|
|
2364
|
+
schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
|
|
2365
|
+
}
|
|
2366
|
+
const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
|
|
2367
|
+
return ast.factory.createInput({
|
|
2368
|
+
schemas: schemaNodes,
|
|
2369
|
+
operations,
|
|
2475
2370
|
meta: {
|
|
2476
2371
|
title: document.info?.title,
|
|
2477
2372
|
description: document.info?.description,
|
|
@@ -2522,15 +2417,13 @@ const adapterOas = createAdapter((options) => {
|
|
|
2522
2417
|
} });
|
|
2523
2418
|
},
|
|
2524
2419
|
async parse(source) {
|
|
2525
|
-
const
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
schemas,
|
|
2529
|
-
|
|
2530
|
-
meta: streamNode.meta
|
|
2420
|
+
const document = await ensureDocument(source);
|
|
2421
|
+
return parseInput({
|
|
2422
|
+
document,
|
|
2423
|
+
schemas: await ensureSchemas(document),
|
|
2424
|
+
parser: ensureSchemaParser(document)
|
|
2531
2425
|
});
|
|
2532
|
-
}
|
|
2533
|
-
stream: createStream
|
|
2426
|
+
}
|
|
2534
2427
|
};
|
|
2535
2428
|
});
|
|
2536
2429
|
//#endregion
|