@kubb/ast 5.0.0-beta.3 → 5.0.0-beta.31
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 +1 -1
- package/dist/index.cjs +694 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1433 -1007
- package/dist/index.js +682 -332
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/dedupe.ts +202 -0
- package/src/dialect.ts +64 -0
- package/src/dispatch.ts +53 -0
- package/src/factory.ts +127 -11
- package/src/guards.ts +18 -3
- package/src/index.ts +11 -3
- package/src/infer.ts +16 -5
- package/src/nodes/base.ts +2 -0
- package/src/nodes/code.ts +21 -21
- package/src/nodes/content.ts +37 -0
- package/src/nodes/file.ts +16 -14
- package/src/nodes/index.ts +7 -3
- package/src/nodes/operation.ts +98 -62
- package/src/nodes/response.ts +21 -14
- package/src/nodes/root.ts +72 -10
- package/src/nodes/schema.ts +9 -3
- package/src/printer.ts +34 -28
- package/src/refs.ts +4 -2
- package/src/resolvers.ts +4 -4
- package/src/signature.ts +135 -0
- package/src/transformers.ts +20 -15
- package/src/types.ts +8 -0
- package/src/utils.ts +109 -68
- package/src/visitor.ts +229 -275
package/dist/index.js
CHANGED
|
@@ -265,6 +265,46 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
265
265
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
266
266
|
}
|
|
267
267
|
//#endregion
|
|
268
|
+
//#region ../../internals/utils/src/promise.ts
|
|
269
|
+
/**
|
|
270
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
271
|
+
*
|
|
272
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
273
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
274
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
275
|
+
* inner (created once per outer miss) keyed by the second.
|
|
276
|
+
*
|
|
277
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
278
|
+
* cleared independently of the memoized function.
|
|
279
|
+
*
|
|
280
|
+
* @example Single WeakMap key
|
|
281
|
+
* ```ts
|
|
282
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
283
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* @example Single Map key (primitive)
|
|
287
|
+
* ```ts
|
|
288
|
+
* const cache = new Map<string, Resolver>()
|
|
289
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
290
|
+
* ```
|
|
291
|
+
*
|
|
292
|
+
* @example Two-level (object + primitive)
|
|
293
|
+
* ```ts
|
|
294
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
295
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
296
|
+
* fn(params)('camelcase')
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
function memoize(store, factory) {
|
|
300
|
+
return (key) => {
|
|
301
|
+
if (store.has(key)) return store.get(key);
|
|
302
|
+
const value = factory(key);
|
|
303
|
+
store.set(key, value);
|
|
304
|
+
return value;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
268
308
|
//#region ../../internals/utils/src/reserved.ts
|
|
269
309
|
/**
|
|
270
310
|
* JavaScript and Java reserved words.
|
|
@@ -392,11 +432,11 @@ function trimExtName(text) {
|
|
|
392
432
|
* @example
|
|
393
433
|
* ```ts
|
|
394
434
|
* const schema = createSchema({ type: 'string' })
|
|
395
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode |
|
|
435
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
396
436
|
* ```
|
|
397
437
|
*/
|
|
398
438
|
function narrowSchema(node, type) {
|
|
399
|
-
return node?.type === type ? node :
|
|
439
|
+
return node?.type === type ? node : null;
|
|
400
440
|
}
|
|
401
441
|
function isKind(kind) {
|
|
402
442
|
return (node) => node.kind === kind;
|
|
@@ -435,6 +475,19 @@ const isOutputNode = isKind("Output");
|
|
|
435
475
|
*/
|
|
436
476
|
const isOperationNode = isKind("Operation");
|
|
437
477
|
/**
|
|
478
|
+
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* if (isHttpOperationNode(node)) {
|
|
483
|
+
* console.log(node.method, node.path)
|
|
484
|
+
* }
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
function isHttpOperationNode(node) {
|
|
488
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
438
491
|
* Returns `true` when the input is a `SchemaNode`.
|
|
439
492
|
*
|
|
440
493
|
* @example
|
|
@@ -445,12 +498,6 @@ const isOperationNode = isKind("Operation");
|
|
|
445
498
|
* ```
|
|
446
499
|
*/
|
|
447
500
|
const isSchemaNode = isKind("Schema");
|
|
448
|
-
isKind("Property");
|
|
449
|
-
isKind("Parameter");
|
|
450
|
-
isKind("Response");
|
|
451
|
-
isKind("FunctionParameter");
|
|
452
|
-
isKind("ParameterGroup");
|
|
453
|
-
isKind("FunctionParameters");
|
|
454
501
|
//#endregion
|
|
455
502
|
//#region src/refs.ts
|
|
456
503
|
/**
|
|
@@ -503,53 +550,92 @@ function createLimit(concurrency) {
|
|
|
503
550
|
});
|
|
504
551
|
};
|
|
505
552
|
}
|
|
553
|
+
const visitorKeysByKind = {
|
|
554
|
+
Input: ["schemas", "operations"],
|
|
555
|
+
Operation: [
|
|
556
|
+
"parameters",
|
|
557
|
+
"requestBody",
|
|
558
|
+
"responses"
|
|
559
|
+
],
|
|
560
|
+
RequestBody: ["content"],
|
|
561
|
+
Content: ["schema"],
|
|
562
|
+
Response: ["content"],
|
|
563
|
+
Schema: [
|
|
564
|
+
"properties",
|
|
565
|
+
"items",
|
|
566
|
+
"members",
|
|
567
|
+
"additionalProperties"
|
|
568
|
+
],
|
|
569
|
+
Property: ["schema"],
|
|
570
|
+
Parameter: ["schema"]
|
|
571
|
+
};
|
|
572
|
+
/**
|
|
573
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
574
|
+
*/
|
|
575
|
+
function isNode(value) {
|
|
576
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
577
|
+
}
|
|
506
578
|
/**
|
|
507
|
-
* Returns the immediate traversable children of `node
|
|
579
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
508
580
|
*
|
|
509
|
-
*
|
|
510
|
-
* `additionalProperties`) are only included
|
|
511
|
-
* when `recurse` is `true`; shallow mode skips them.
|
|
581
|
+
* `Schema` children are only included when `recurse` is `true`; shallow mode skips them.
|
|
512
582
|
*
|
|
513
583
|
* @example
|
|
514
584
|
* ```ts
|
|
515
585
|
* const children = getChildren(operationNode, true)
|
|
516
|
-
* // returns parameters,
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
function getChildren(node, recurse) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const children = [];
|
|
530
|
-
if (!recurse) return [];
|
|
531
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
532
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
533
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
534
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
535
|
-
return children;
|
|
536
|
-
}
|
|
537
|
-
case "Property": return [node.schema];
|
|
538
|
-
case "Parameter": return [node.schema];
|
|
539
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
540
|
-
case "FunctionParameter":
|
|
541
|
-
case "ParameterGroup":
|
|
542
|
-
case "FunctionParameters":
|
|
543
|
-
case "Type": return [];
|
|
544
|
-
default: return [];
|
|
586
|
+
* // returns parameters, the request body, and responses
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
589
|
+
function* getChildren(node, recurse) {
|
|
590
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
591
|
+
const keys = visitorKeysByKind[node.kind];
|
|
592
|
+
if (!keys) return;
|
|
593
|
+
const record = node;
|
|
594
|
+
for (const key of keys) {
|
|
595
|
+
const value = record[key];
|
|
596
|
+
if (Array.isArray(value)) {
|
|
597
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
598
|
+
} else if (isNode(value)) yield value;
|
|
545
599
|
}
|
|
546
600
|
}
|
|
547
601
|
/**
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
602
|
+
* Maps a node `kind` to the matching visitor callback name. Only the seven
|
|
603
|
+
* traversable node kinds have an entry; every other kind resolves to
|
|
604
|
+
* `undefined` and is skipped.
|
|
605
|
+
*/
|
|
606
|
+
const VISITOR_KEY_BY_KIND = {
|
|
607
|
+
Input: "input",
|
|
608
|
+
Output: "output",
|
|
609
|
+
Operation: "operation",
|
|
610
|
+
Schema: "schema",
|
|
611
|
+
Property: "property",
|
|
612
|
+
Parameter: "parameter",
|
|
613
|
+
Response: "response"
|
|
614
|
+
};
|
|
615
|
+
/**
|
|
616
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
617
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
618
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
551
619
|
*
|
|
552
|
-
*
|
|
620
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
621
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
622
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
623
|
+
*/
|
|
624
|
+
function applyVisitor(node, visitor, parent) {
|
|
625
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
626
|
+
if (!key) return void 0;
|
|
627
|
+
const fn = visitor[key];
|
|
628
|
+
return fn?.(node, { parent });
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
632
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
633
|
+
*
|
|
634
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
635
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
636
|
+
* work; lower values reduce memory pressure.
|
|
637
|
+
*
|
|
638
|
+
* @example Log every operation
|
|
553
639
|
* ```ts
|
|
554
640
|
* await walk(root, {
|
|
555
641
|
* operation(node) {
|
|
@@ -558,213 +644,114 @@ function getChildren(node, recurse) {
|
|
|
558
644
|
* })
|
|
559
645
|
* ```
|
|
560
646
|
*
|
|
561
|
-
* @example
|
|
647
|
+
* @example Only visit the root node
|
|
562
648
|
* ```ts
|
|
563
|
-
*
|
|
564
|
-
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
649
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
565
650
|
* ```
|
|
566
651
|
*/
|
|
567
652
|
async function walk(node, options) {
|
|
568
653
|
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
569
654
|
}
|
|
570
655
|
async function _walk(node, visitor, recurse, limit, parent) {
|
|
571
|
-
|
|
572
|
-
case "Input":
|
|
573
|
-
await limit(() => visitor.input?.(node, { parent }));
|
|
574
|
-
break;
|
|
575
|
-
case "Output":
|
|
576
|
-
await limit(() => visitor.output?.(node, { parent }));
|
|
577
|
-
break;
|
|
578
|
-
case "Operation":
|
|
579
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
580
|
-
break;
|
|
581
|
-
case "Schema":
|
|
582
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
583
|
-
break;
|
|
584
|
-
case "Property":
|
|
585
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
586
|
-
break;
|
|
587
|
-
case "Parameter":
|
|
588
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
589
|
-
break;
|
|
590
|
-
case "Response":
|
|
591
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
592
|
-
break;
|
|
593
|
-
case "FunctionParameter":
|
|
594
|
-
case "ParameterGroup":
|
|
595
|
-
case "FunctionParameters": break;
|
|
596
|
-
}
|
|
656
|
+
await limit(() => applyVisitor(node, visitor, parent));
|
|
597
657
|
const children = getChildren(node, recurse);
|
|
598
658
|
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
599
659
|
}
|
|
600
660
|
function transform(node, options) {
|
|
601
661
|
const { depth, parent, ...visitor } = options;
|
|
602
662
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
parent: op
|
|
643
|
-
}) : void 0
|
|
644
|
-
}))
|
|
645
|
-
} : void 0,
|
|
646
|
-
responses: op.responses.map((r) => transform(r, {
|
|
647
|
-
...options,
|
|
648
|
-
parent: op
|
|
649
|
-
}))
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
case "Schema": {
|
|
653
|
-
let schema = node;
|
|
654
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
655
|
-
if (replaced) schema = replaced;
|
|
656
|
-
const childOptions = {
|
|
657
|
-
...options,
|
|
658
|
-
parent: schema
|
|
659
|
-
};
|
|
660
|
-
return {
|
|
661
|
-
...schema,
|
|
662
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
663
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
664
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
665
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
case "Property": {
|
|
669
|
-
let prop = node;
|
|
670
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
671
|
-
if (replaced) prop = replaced;
|
|
672
|
-
return createProperty({
|
|
673
|
-
...prop,
|
|
674
|
-
schema: transform(prop.schema, {
|
|
675
|
-
...options,
|
|
676
|
-
parent: prop
|
|
677
|
-
})
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
case "Parameter": {
|
|
681
|
-
let param = node;
|
|
682
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
683
|
-
if (replaced) param = replaced;
|
|
684
|
-
return createParameter({
|
|
685
|
-
...param,
|
|
686
|
-
schema: transform(param.schema, {
|
|
687
|
-
...options,
|
|
688
|
-
parent: param
|
|
689
|
-
})
|
|
663
|
+
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
664
|
+
if (rebuilt === node) return node;
|
|
665
|
+
const finalize = nodeFinalizers[rebuilt.kind];
|
|
666
|
+
return finalize ? finalize(rebuilt) : rebuilt;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
|
|
670
|
+
* resync schema optionality against their `required` flag once the schema may
|
|
671
|
+
* have changed.
|
|
672
|
+
*/
|
|
673
|
+
const nodeFinalizers = {
|
|
674
|
+
Property: (node) => createProperty(node),
|
|
675
|
+
Parameter: (node) => createParameter(node)
|
|
676
|
+
};
|
|
677
|
+
/**
|
|
678
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
679
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
680
|
+
* `Schema` children are skipped in shallow mode.
|
|
681
|
+
*/
|
|
682
|
+
function transformChildren(node, options, recurse) {
|
|
683
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
684
|
+
const keys = visitorKeysByKind[node.kind];
|
|
685
|
+
if (!keys) return node;
|
|
686
|
+
const record = node;
|
|
687
|
+
const childOptions = {
|
|
688
|
+
...options,
|
|
689
|
+
parent: node
|
|
690
|
+
};
|
|
691
|
+
let updates;
|
|
692
|
+
for (const key of keys) {
|
|
693
|
+
if (!(key in record)) continue;
|
|
694
|
+
const value = record[key];
|
|
695
|
+
if (Array.isArray(value)) {
|
|
696
|
+
let changed = false;
|
|
697
|
+
const mapped = value.map((item) => {
|
|
698
|
+
if (!isNode(item)) return item;
|
|
699
|
+
const next = transform(item, childOptions);
|
|
700
|
+
if (next !== item) changed = true;
|
|
701
|
+
return next;
|
|
690
702
|
});
|
|
703
|
+
if (changed) (updates ??= {})[key] = mapped;
|
|
704
|
+
} else if (isNode(value)) {
|
|
705
|
+
const next = transform(value, childOptions);
|
|
706
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
691
707
|
}
|
|
692
|
-
case "Response": {
|
|
693
|
-
let response = node;
|
|
694
|
-
const replaced = visitor.response?.(response, { parent });
|
|
695
|
-
if (replaced) response = replaced;
|
|
696
|
-
return {
|
|
697
|
-
...response,
|
|
698
|
-
schema: transform(response.schema, {
|
|
699
|
-
...options,
|
|
700
|
-
parent: response
|
|
701
|
-
})
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
case "FunctionParameter":
|
|
705
|
-
case "ParameterGroup":
|
|
706
|
-
case "FunctionParameters":
|
|
707
|
-
case "Type": return node;
|
|
708
|
-
default: return node;
|
|
709
708
|
}
|
|
709
|
+
return updates ? {
|
|
710
|
+
...node,
|
|
711
|
+
...updates
|
|
712
|
+
} : node;
|
|
710
713
|
}
|
|
711
714
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
715
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
716
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
715
717
|
*
|
|
716
|
-
* @example
|
|
718
|
+
* @example Collect every operationId
|
|
717
719
|
* ```ts
|
|
718
|
-
* const ids =
|
|
720
|
+
* const ids: string[] = []
|
|
721
|
+
* for (const id of collectLazy<string>(root, {
|
|
719
722
|
* operation(node) {
|
|
720
723
|
* return node.operationId
|
|
721
724
|
* },
|
|
722
|
-
* })
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* @example
|
|
726
|
-
* ```ts
|
|
727
|
-
* // Collect from only the current node
|
|
728
|
-
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
725
|
+
* })) {
|
|
726
|
+
* ids.push(id)
|
|
727
|
+
* }
|
|
729
728
|
* ```
|
|
730
729
|
*/
|
|
731
|
-
function
|
|
730
|
+
function* collectLazy(node, options) {
|
|
732
731
|
const { depth, parent, ...visitor } = options;
|
|
733
732
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
case "Input":
|
|
738
|
-
v = visitor.input?.(node, { parent });
|
|
739
|
-
break;
|
|
740
|
-
case "Output":
|
|
741
|
-
v = visitor.output?.(node, { parent });
|
|
742
|
-
break;
|
|
743
|
-
case "Operation":
|
|
744
|
-
v = visitor.operation?.(node, { parent });
|
|
745
|
-
break;
|
|
746
|
-
case "Schema":
|
|
747
|
-
v = visitor.schema?.(node, { parent });
|
|
748
|
-
break;
|
|
749
|
-
case "Property":
|
|
750
|
-
v = visitor.property?.(node, { parent });
|
|
751
|
-
break;
|
|
752
|
-
case "Parameter":
|
|
753
|
-
v = visitor.parameter?.(node, { parent });
|
|
754
|
-
break;
|
|
755
|
-
case "Response":
|
|
756
|
-
v = visitor.response?.(node, { parent });
|
|
757
|
-
break;
|
|
758
|
-
case "FunctionParameter":
|
|
759
|
-
case "ParameterGroup":
|
|
760
|
-
case "FunctionParameters": break;
|
|
761
|
-
}
|
|
762
|
-
if (v !== void 0) results.push(v);
|
|
763
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
733
|
+
const v = applyVisitor(node, visitor, parent);
|
|
734
|
+
if (v != null) yield v;
|
|
735
|
+
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
764
736
|
...options,
|
|
765
737
|
parent: node
|
|
766
|
-
})
|
|
767
|
-
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
742
|
+
* the visitor callbacks return.
|
|
743
|
+
*
|
|
744
|
+
* @example Collect every operationId
|
|
745
|
+
* ```ts
|
|
746
|
+
* const ids = collect<string>(root, {
|
|
747
|
+
* operation(node) {
|
|
748
|
+
* return node.operationId
|
|
749
|
+
* },
|
|
750
|
+
* })
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
function collect(node, options) {
|
|
754
|
+
return Array.from(collectLazy(node, options));
|
|
768
755
|
}
|
|
769
756
|
//#endregion
|
|
770
757
|
//#region src/utils.ts
|
|
@@ -818,15 +805,16 @@ function isStringType(node) {
|
|
|
818
805
|
* the desired casing while preserving `OperationNode.parameters` for other consumers.
|
|
819
806
|
* The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
|
|
820
807
|
*/
|
|
808
|
+
const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
|
|
809
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
810
|
+
return {
|
|
811
|
+
...param,
|
|
812
|
+
name: transformed
|
|
813
|
+
};
|
|
814
|
+
})));
|
|
821
815
|
function caseParams(params, casing) {
|
|
822
816
|
if (!casing) return params;
|
|
823
|
-
return params
|
|
824
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
825
|
-
return {
|
|
826
|
-
...param,
|
|
827
|
-
name: transformed
|
|
828
|
-
};
|
|
829
|
-
});
|
|
817
|
+
return caseParamsMemo(params)(casing);
|
|
830
818
|
}
|
|
831
819
|
/**
|
|
832
820
|
* Creates a single-property object schema used as a discriminator literal.
|
|
@@ -955,7 +943,7 @@ function createOperationParams(node, options) {
|
|
|
955
943
|
}));
|
|
956
944
|
} else {
|
|
957
945
|
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
958
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0])
|
|
946
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
959
947
|
params.push(createFunctionParameter({
|
|
960
948
|
name: pathName,
|
|
961
949
|
type: spreadType ? wrapType(spreadType) : void 0,
|
|
@@ -1031,13 +1019,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
|
|
|
1031
1019
|
}
|
|
1032
1020
|
/**
|
|
1033
1021
|
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1034
|
-
* Returns `
|
|
1022
|
+
* Returns `null` when the group name equals the individual param name (no real group).
|
|
1035
1023
|
*/
|
|
1036
1024
|
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1037
|
-
if (!params.length) return;
|
|
1025
|
+
if (!params.length) return null;
|
|
1038
1026
|
const firstParam = params[0];
|
|
1039
1027
|
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1040
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1028
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
1041
1029
|
const allOptional = params.every((p) => !p.required);
|
|
1042
1030
|
return {
|
|
1043
1031
|
type: createParamsType({
|
|
@@ -1104,6 +1092,16 @@ function combineSources(sources) {
|
|
|
1104
1092
|
return [...seen.values()];
|
|
1105
1093
|
}
|
|
1106
1094
|
/**
|
|
1095
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
1096
|
+
*
|
|
1097
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
1098
|
+
*/
|
|
1099
|
+
function mergeNameArrays(existing, incoming) {
|
|
1100
|
+
const merged = new Set(existing);
|
|
1101
|
+
for (const name of incoming) merged.add(name);
|
|
1102
|
+
return [...merged];
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1107
1105
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1108
1106
|
*
|
|
1109
1107
|
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
@@ -1124,11 +1122,8 @@ function combineExports(exports) {
|
|
|
1124
1122
|
if (!name.length) continue;
|
|
1125
1123
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1126
1124
|
const existing = namedByPath.get(key);
|
|
1127
|
-
if (existing && Array.isArray(existing.name))
|
|
1128
|
-
|
|
1129
|
-
for (const n of name) merged.add(n);
|
|
1130
|
-
existing.name = [...merged];
|
|
1131
|
-
} else {
|
|
1125
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1126
|
+
else {
|
|
1132
1127
|
const newItem = {
|
|
1133
1128
|
...curr,
|
|
1134
1129
|
name: [...new Set(name)]
|
|
@@ -1164,6 +1159,11 @@ function combineImports(imports, exports, source) {
|
|
|
1164
1159
|
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
1165
1160
|
return importNameMemo.get(key);
|
|
1166
1161
|
};
|
|
1162
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
1163
|
+
for (const node of imports) {
|
|
1164
|
+
if (!Array.isArray(node.name)) continue;
|
|
1165
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
1166
|
+
}
|
|
1167
1167
|
const result = [];
|
|
1168
1168
|
const namedByPath = /* @__PURE__ */ new Map();
|
|
1169
1169
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1181,11 +1181,8 @@ function combineImports(imports, exports, source) {
|
|
|
1181
1181
|
if (!name.length) continue;
|
|
1182
1182
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1183
1183
|
const existing = namedByPath.get(key);
|
|
1184
|
-
if (existing && Array.isArray(existing.name))
|
|
1185
|
-
|
|
1186
|
-
for (const n of name) merged.add(n);
|
|
1187
|
-
existing.name = [...merged];
|
|
1188
|
-
} else {
|
|
1184
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1185
|
+
else {
|
|
1189
1186
|
const newItem = {
|
|
1190
1187
|
...curr,
|
|
1191
1188
|
name
|
|
@@ -1194,7 +1191,7 @@ function combineImports(imports, exports, source) {
|
|
|
1194
1191
|
namedByPath.set(key, newItem);
|
|
1195
1192
|
}
|
|
1196
1193
|
} else {
|
|
1197
|
-
if (name && !isUsed(name)) continue;
|
|
1194
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1198
1195
|
const key = importKey(path, name, isTypeOnly);
|
|
1199
1196
|
if (!seen.has(key)) {
|
|
1200
1197
|
result.push(curr);
|
|
@@ -1230,7 +1227,7 @@ function extractStringsFromNodes(nodes) {
|
|
|
1230
1227
|
/**
|
|
1231
1228
|
* Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
|
|
1232
1229
|
*
|
|
1233
|
-
* Returns `
|
|
1230
|
+
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1234
1231
|
* identifier for type definitions or error messages.
|
|
1235
1232
|
*
|
|
1236
1233
|
* @example
|
|
@@ -1240,9 +1237,9 @@ function extractStringsFromNodes(nodes) {
|
|
|
1240
1237
|
* ```
|
|
1241
1238
|
*/
|
|
1242
1239
|
function resolveRefName(node) {
|
|
1243
|
-
if (!node || node.type !== "ref") return
|
|
1244
|
-
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ??
|
|
1245
|
-
return node.name ?? node.schema?.name ??
|
|
1240
|
+
if (!node || node.type !== "ref") return null;
|
|
1241
|
+
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
1242
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1246
1243
|
}
|
|
1247
1244
|
/**
|
|
1248
1245
|
* Collects every named schema referenced (transitively) from a node via ref edges.
|
|
@@ -1264,14 +1261,19 @@ function resolveRefName(node) {
|
|
|
1264
1261
|
* }
|
|
1265
1262
|
* ```
|
|
1266
1263
|
*/
|
|
1267
|
-
|
|
1268
|
-
|
|
1264
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1265
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1269
1266
|
collect(node, { schema(child) {
|
|
1270
1267
|
if (child.type === "ref") {
|
|
1271
1268
|
const name = resolveRefName(child);
|
|
1272
|
-
if (name)
|
|
1269
|
+
if (name) refs.add(name);
|
|
1273
1270
|
}
|
|
1274
1271
|
} });
|
|
1272
|
+
return refs;
|
|
1273
|
+
});
|
|
1274
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1275
|
+
if (!node) return out;
|
|
1276
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1275
1277
|
return out;
|
|
1276
1278
|
}
|
|
1277
1279
|
/**
|
|
@@ -1287,10 +1289,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1287
1289
|
*
|
|
1288
1290
|
* @example Only generate schemas referenced by included operations
|
|
1289
1291
|
* ```ts
|
|
1290
|
-
* const includedOps =
|
|
1291
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1292
|
+
* const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
1293
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1292
1294
|
*
|
|
1293
|
-
* for (const schema of
|
|
1295
|
+
* for (const schema of schemas) {
|
|
1294
1296
|
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1295
1297
|
* // … generate schema
|
|
1296
1298
|
* }
|
|
@@ -1298,11 +1300,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1298
1300
|
*
|
|
1299
1301
|
* @example Check whether a specific schema is needed
|
|
1300
1302
|
* ```ts
|
|
1301
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1303
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1302
1304
|
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
1303
1305
|
* ```
|
|
1304
1306
|
*/
|
|
1305
|
-
|
|
1307
|
+
const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
|
|
1308
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1306
1309
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
1307
1310
|
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1308
1311
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -1314,22 +1317,17 @@ function collectUsedSchemaNames(operations, schemas) {
|
|
|
1314
1317
|
if (namedSchema) visitSchema(namedSchema);
|
|
1315
1318
|
}
|
|
1316
1319
|
}
|
|
1317
|
-
for (const op of operations) for (const schema of
|
|
1320
|
+
for (const op of operations) for (const schema of collectLazy(op, {
|
|
1318
1321
|
depth: "shallow",
|
|
1319
1322
|
schema: (node) => node
|
|
1320
1323
|
})) visitSchema(schema);
|
|
1321
1324
|
return result;
|
|
1322
1325
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1329
|
-
*
|
|
1330
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1331
|
-
*/
|
|
1332
|
-
function findCircularSchemas(schemas) {
|
|
1326
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
1327
|
+
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
1328
|
+
}
|
|
1329
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1330
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1333
1331
|
const graph = /* @__PURE__ */ new Map();
|
|
1334
1332
|
for (const schema of schemas) {
|
|
1335
1333
|
if (!schema.name) continue;
|
|
@@ -1352,6 +1350,19 @@ function findCircularSchemas(schemas) {
|
|
|
1352
1350
|
}
|
|
1353
1351
|
}
|
|
1354
1352
|
return circular;
|
|
1353
|
+
});
|
|
1354
|
+
/**
|
|
1355
|
+
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
1356
|
+
*
|
|
1357
|
+
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
1358
|
+
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
1359
|
+
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1360
|
+
*
|
|
1361
|
+
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1362
|
+
*/
|
|
1363
|
+
function findCircularSchemas(schemas) {
|
|
1364
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1365
|
+
return findCircularSchemasMemo(schemas);
|
|
1355
1366
|
}
|
|
1356
1367
|
/**
|
|
1357
1368
|
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
@@ -1363,19 +1374,23 @@ function findCircularSchemas(schemas) {
|
|
|
1363
1374
|
*/
|
|
1364
1375
|
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1365
1376
|
if (!node || circularSchemas.size === 0) return false;
|
|
1366
|
-
|
|
1367
|
-
if (child.type !== "ref") return
|
|
1377
|
+
for (const _ of collectLazy(node, { schema(child) {
|
|
1378
|
+
if (child.type !== "ref") return null;
|
|
1368
1379
|
const name = resolveRefName(child);
|
|
1369
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true :
|
|
1370
|
-
} })
|
|
1380
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
1381
|
+
} })) return true;
|
|
1382
|
+
return false;
|
|
1371
1383
|
}
|
|
1372
1384
|
//#endregion
|
|
1373
1385
|
//#region src/factory.ts
|
|
1374
1386
|
/**
|
|
1375
|
-
*
|
|
1387
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
1388
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
1389
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
1376
1390
|
*
|
|
1377
|
-
* -
|
|
1378
|
-
* -
|
|
1391
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
1392
|
+
* - Non-required + nullable → `nullish: true`.
|
|
1393
|
+
* - Required → both flags cleared.
|
|
1379
1394
|
*/
|
|
1380
1395
|
function syncOptionality(schema, required) {
|
|
1381
1396
|
const nullable = schema.nullable ?? false;
|
|
@@ -1386,6 +1401,29 @@ function syncOptionality(schema, required) {
|
|
|
1386
1401
|
};
|
|
1387
1402
|
}
|
|
1388
1403
|
/**
|
|
1404
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1405
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1406
|
+
* with the changes applied.
|
|
1407
|
+
*
|
|
1408
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract — pair it with the
|
|
1409
|
+
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1410
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1411
|
+
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
1412
|
+
*
|
|
1413
|
+
* @example
|
|
1414
|
+
* ```ts
|
|
1415
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1416
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
1417
|
+
* ```
|
|
1418
|
+
*/
|
|
1419
|
+
function update(node, changes) {
|
|
1420
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1421
|
+
...node,
|
|
1422
|
+
...changes
|
|
1423
|
+
};
|
|
1424
|
+
return node;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1389
1427
|
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
1390
1428
|
*
|
|
1391
1429
|
* @example
|
|
@@ -1404,11 +1442,31 @@ function createInput(overrides = {}) {
|
|
|
1404
1442
|
return {
|
|
1405
1443
|
schemas: [],
|
|
1406
1444
|
operations: [],
|
|
1445
|
+
meta: {
|
|
1446
|
+
circularNames: [],
|
|
1447
|
+
enumNames: []
|
|
1448
|
+
},
|
|
1407
1449
|
...overrides,
|
|
1408
1450
|
kind: "Input"
|
|
1409
1451
|
};
|
|
1410
1452
|
}
|
|
1411
1453
|
/**
|
|
1454
|
+
* Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
|
|
1455
|
+
*
|
|
1456
|
+
* @example
|
|
1457
|
+
* ```ts
|
|
1458
|
+
* const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
|
|
1459
|
+
* ```
|
|
1460
|
+
*/
|
|
1461
|
+
function createStreamInput(schemas, operations, meta) {
|
|
1462
|
+
return {
|
|
1463
|
+
kind: "Input",
|
|
1464
|
+
schemas,
|
|
1465
|
+
operations,
|
|
1466
|
+
meta
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1412
1470
|
* Creates an `OutputNode` with a stable default for `files`.
|
|
1413
1471
|
*
|
|
1414
1472
|
* @example
|
|
@@ -1430,35 +1488,35 @@ function createOutput(overrides = {}) {
|
|
|
1430
1488
|
};
|
|
1431
1489
|
}
|
|
1432
1490
|
/**
|
|
1433
|
-
* Creates
|
|
1434
|
-
*
|
|
1435
|
-
* @example
|
|
1436
|
-
* ```ts
|
|
1437
|
-
* const operation = createOperation({
|
|
1438
|
-
* operationId: 'getPetById',
|
|
1439
|
-
* method: 'GET',
|
|
1440
|
-
* path: '/pet/{petId}',
|
|
1441
|
-
* })
|
|
1442
|
-
* // tags, parameters, and responses are []
|
|
1443
|
-
* ```
|
|
1444
|
-
*
|
|
1445
|
-
* @example
|
|
1446
|
-
* ```ts
|
|
1447
|
-
* const operation = createOperation({
|
|
1448
|
-
* operationId: 'findPets',
|
|
1449
|
-
* method: 'GET',
|
|
1450
|
-
* path: '/pet/findByStatus',
|
|
1451
|
-
* tags: ['pet'],
|
|
1452
|
-
* })
|
|
1453
|
-
* ```
|
|
1491
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
1454
1492
|
*/
|
|
1493
|
+
function createContent(props) {
|
|
1494
|
+
return {
|
|
1495
|
+
...props,
|
|
1496
|
+
kind: "Content"
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
|
|
1501
|
+
*/
|
|
1502
|
+
function createRequestBody(props) {
|
|
1503
|
+
return {
|
|
1504
|
+
...props,
|
|
1505
|
+
kind: "RequestBody",
|
|
1506
|
+
content: props.content?.map(createContent)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1455
1509
|
function createOperation(props) {
|
|
1510
|
+
const { requestBody, ...rest } = props;
|
|
1511
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
1456
1512
|
return {
|
|
1457
1513
|
tags: [],
|
|
1458
1514
|
parameters: [],
|
|
1459
1515
|
responses: [],
|
|
1460
|
-
...
|
|
1461
|
-
|
|
1516
|
+
...rest,
|
|
1517
|
+
...isHttp ? { protocol: "http" } : {},
|
|
1518
|
+
kind: "Operation",
|
|
1519
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
1462
1520
|
};
|
|
1463
1521
|
}
|
|
1464
1522
|
/**
|
|
@@ -1572,19 +1630,29 @@ function createParameter(props) {
|
|
|
1572
1630
|
/**
|
|
1573
1631
|
* Creates a `ResponseNode`.
|
|
1574
1632
|
*
|
|
1633
|
+
* Response body schemas live inside `content`. For convenience a single legacy `schema`
|
|
1634
|
+
* (with optional `mediaType`/`keysToOmit`) is normalized into one `content` entry, so the same
|
|
1635
|
+
* schema is never stored both at the node root and inside `content`.
|
|
1636
|
+
*
|
|
1575
1637
|
* @example
|
|
1576
1638
|
* ```ts
|
|
1577
1639
|
* const response = createResponse({
|
|
1578
1640
|
* statusCode: '200',
|
|
1579
|
-
*
|
|
1580
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
1641
|
+
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
1581
1642
|
* })
|
|
1582
1643
|
* ```
|
|
1583
1644
|
*/
|
|
1584
1645
|
function createResponse(props) {
|
|
1646
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
1647
|
+
const entries = content ?? (schema ? [{
|
|
1648
|
+
contentType: mediaType ?? "application/json",
|
|
1649
|
+
schema,
|
|
1650
|
+
keysToOmit: keysToOmit ?? null
|
|
1651
|
+
}] : void 0);
|
|
1585
1652
|
return {
|
|
1586
|
-
...
|
|
1587
|
-
kind: "Response"
|
|
1653
|
+
...rest,
|
|
1654
|
+
kind: "Response",
|
|
1655
|
+
content: entries?.map(createContent)
|
|
1588
1656
|
};
|
|
1589
1657
|
}
|
|
1590
1658
|
/**
|
|
@@ -1993,24 +2061,300 @@ function createJsx(value) {
|
|
|
1993
2061
|
};
|
|
1994
2062
|
}
|
|
1995
2063
|
//#endregion
|
|
1996
|
-
//#region src/
|
|
2064
|
+
//#region src/signature.ts
|
|
2065
|
+
/**
|
|
2066
|
+
* The shape-affecting flags shared by every node kind: base primitive, format, and `nullable`.
|
|
2067
|
+
* Documentation and usage-slot flags (`optional`/`nullish`/`readOnly`/`writeOnly`) are
|
|
2068
|
+
* intentionally excluded — they describe the property slot, not the type.
|
|
2069
|
+
*/
|
|
2070
|
+
function flagsDescriptor(node) {
|
|
2071
|
+
return `${node.primitive ?? ""};${node.format ?? ""};${node.nullable ? 1 : 0}`;
|
|
2072
|
+
}
|
|
2073
|
+
function refTargetName(node) {
|
|
2074
|
+
if (node.ref) return extractRefName(node.ref);
|
|
2075
|
+
return node.name ?? "";
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Builds the local, shape-only descriptor for a node: its kind, flags, constraints, and its
|
|
2079
|
+
* children's signatures. {@link signatureOf} hashes this string; children contribute their
|
|
2080
|
+
* fixed-length signature rather than their own full descriptor, which keeps the result bounded.
|
|
2081
|
+
*/
|
|
2082
|
+
function describeShape(node, signatures) {
|
|
2083
|
+
const flags = flagsDescriptor(node);
|
|
2084
|
+
switch (node.type) {
|
|
2085
|
+
case "object": {
|
|
2086
|
+
const props = (node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema, signatures)}`).join(",");
|
|
2087
|
+
let additional = "";
|
|
2088
|
+
if (typeof node.additionalProperties === "boolean") additional = `ab:${node.additionalProperties}`;
|
|
2089
|
+
else if (node.additionalProperties) additional = `as:${signatureOf(node.additionalProperties, signatures)}`;
|
|
2090
|
+
const pattern = node.patternProperties ? Object.keys(node.patternProperties).sort().map((key) => `${key}=${signatureOf(node.patternProperties[key], signatures)}`).join(",") : "";
|
|
2091
|
+
return `object|${flags}|p[${props}]|${additional}|pp[${pattern}]|mn:${node.minProperties ?? ""}|mx:${node.maxProperties ?? ""}`;
|
|
2092
|
+
}
|
|
2093
|
+
case "array":
|
|
2094
|
+
case "tuple": {
|
|
2095
|
+
const items = (node.items ?? []).map((item) => signatureOf(item, signatures)).join(",");
|
|
2096
|
+
const rest = node.rest ? signatureOf(node.rest, signatures) : "";
|
|
2097
|
+
return `${node.type}|${flags}|i[${items}]|r:${rest}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|u:${node.unique ? 1 : 0}`;
|
|
2098
|
+
}
|
|
2099
|
+
case "union": {
|
|
2100
|
+
const members = (node.members ?? []).map((member) => signatureOf(member, signatures)).join(",");
|
|
2101
|
+
return `union|${flags}|s:${node.strategy ?? ""}|d:${node.discriminatorPropertyName ?? ""}|m[${members}]`;
|
|
2102
|
+
}
|
|
2103
|
+
case "intersection": return `intersection|${flags}|m[${(node.members ?? []).map((member) => signatureOf(member, signatures)).join(",")}]`;
|
|
2104
|
+
case "enum": {
|
|
2105
|
+
let values = "";
|
|
2106
|
+
if (node.namedEnumValues?.length) values = node.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
|
|
2107
|
+
else if (node.enumValues?.length) values = node.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
|
|
2108
|
+
return `enum|${flags}|v[${values}]`;
|
|
2109
|
+
}
|
|
2110
|
+
case "ref": return `ref|${flags}|->${refTargetName(node)}`;
|
|
2111
|
+
case "string": return `string|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|pt:${node.pattern ?? ""}`;
|
|
2112
|
+
case "number":
|
|
2113
|
+
case "integer":
|
|
2114
|
+
case "bigint": return `${node.type}|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|emn:${node.exclusiveMinimum ?? ""}|emx:${node.exclusiveMaximum ?? ""}|mo:${node.multipleOf ?? ""}`;
|
|
2115
|
+
case "url": return `url|${flags}|path:${node.path ?? ""}|mn:${node.min ?? ""}|mx:${node.max ?? ""}`;
|
|
2116
|
+
case "uuid":
|
|
2117
|
+
case "email": return `${node.type}|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}`;
|
|
2118
|
+
case "datetime": return `datetime|${flags}|o:${node.offset ? 1 : 0}|l:${node.local ? 1 : 0}`;
|
|
2119
|
+
case "date":
|
|
2120
|
+
case "time": return `${node.type}|${flags}|rep:${node.representation}`;
|
|
2121
|
+
default: return `${node.type}|${flags}`;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Hash-consing: each node's signature is a fixed-length digest of its local shape plus its
|
|
2126
|
+
* children's digests (a Merkle hash). Children contribute their 64-char hash instead of their
|
|
2127
|
+
* full nested descriptor, so a signature stays bounded regardless of subtree depth, and the
|
|
2128
|
+
* digest is identical across calls because it depends only on content — never on traversal
|
|
2129
|
+
* order. This keeps the keys built during planning consistent with the ones recomputed later
|
|
2130
|
+
* during streaming. `signatures` memoizes node → digest within a single computation.
|
|
2131
|
+
*/
|
|
2132
|
+
function signatureOf(node, signatures) {
|
|
2133
|
+
const cached = signatures.get(node);
|
|
2134
|
+
if (cached !== void 0) return cached;
|
|
2135
|
+
const signature = createHash("sha256").update(describeShape(node, signatures)).digest("hex");
|
|
2136
|
+
signatures.set(node, signature);
|
|
2137
|
+
return signature;
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Computes a deterministic, shape-only signature (a fixed-length content hash) for a schema node.
|
|
2141
|
+
*
|
|
2142
|
+
* Two schemas share a signature when they are structurally identical, ignoring
|
|
2143
|
+
* documentation (`name`, `title`, `description`, `example`, `default`, `deprecated`)
|
|
2144
|
+
* and usage-slot flags (`optional`, `nullish`, `readOnly`, `writeOnly`). `nullable`
|
|
2145
|
+
* is kept because it changes the produced type. `ref` nodes compare by target name,
|
|
2146
|
+
* which also keeps the algorithm terminating on circular shapes.
|
|
2147
|
+
*
|
|
2148
|
+
* @example Two enums with different descriptions share a signature
|
|
2149
|
+
* ```ts
|
|
2150
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'], description: 'x' })) ===
|
|
2151
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'] }))
|
|
2152
|
+
* ```
|
|
2153
|
+
*/
|
|
2154
|
+
function schemaSignature(node) {
|
|
2155
|
+
return signatureOf(node, /* @__PURE__ */ new Map());
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Returns `true` when two schema nodes are structurally identical under shape-only equality.
|
|
2159
|
+
*
|
|
2160
|
+
* @example
|
|
2161
|
+
* ```ts
|
|
2162
|
+
* isSchemaEqual(a, b) // a and b produce the same TypeScript type
|
|
2163
|
+
* ```
|
|
2164
|
+
*/
|
|
2165
|
+
function isSchemaEqual(a, b) {
|
|
2166
|
+
return schemaSignature(a) === schemaSignature(b);
|
|
2167
|
+
}
|
|
2168
|
+
//#endregion
|
|
2169
|
+
//#region src/dedupe.ts
|
|
2170
|
+
/**
|
|
2171
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
2172
|
+
* usage-slot and documentation fields that are not part of the canonical type.
|
|
2173
|
+
*/
|
|
2174
|
+
function createRefNode(node, canonical) {
|
|
2175
|
+
return createSchema({
|
|
2176
|
+
type: "ref",
|
|
2177
|
+
name: canonical.name,
|
|
2178
|
+
ref: canonical.ref,
|
|
2179
|
+
optional: node.optional,
|
|
2180
|
+
nullish: node.nullish,
|
|
2181
|
+
readOnly: node.readOnly,
|
|
2182
|
+
writeOnly: node.writeOnly,
|
|
2183
|
+
deprecated: node.deprecated,
|
|
2184
|
+
description: node.description,
|
|
2185
|
+
default: node.default,
|
|
2186
|
+
example: node.example
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
function applyDedupe(node, canonicalBySignature, skipRootMatch = false) {
|
|
2190
|
+
if (canonicalBySignature.size === 0) return node;
|
|
2191
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
2192
|
+
const root = node;
|
|
2193
|
+
return transform(node, { schema(schemaNode) {
|
|
2194
|
+
const signature = signatureOf(schemaNode, signatures);
|
|
2195
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
2196
|
+
const canonical = canonicalBySignature.get(signature);
|
|
2197
|
+
if (!canonical) return void 0;
|
|
2198
|
+
return createRefNode(schemaNode, canonical);
|
|
2199
|
+
} });
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Strips usage-slot flags from a hoisted definition and applies its canonical name.
|
|
2203
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
2204
|
+
*/
|
|
2205
|
+
function cleanDefinition(node, name) {
|
|
2206
|
+
return {
|
|
2207
|
+
...node,
|
|
2208
|
+
name,
|
|
2209
|
+
optional: void 0,
|
|
2210
|
+
nullish: void 0
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
1997
2213
|
/**
|
|
1998
|
-
*
|
|
2214
|
+
* Scans a forest of schema and operation nodes and produces a {@link DedupePlan}.
|
|
1999
2215
|
*
|
|
2000
|
-
*
|
|
2216
|
+
* A shape that occurs at least `minOccurrences` times is deduplicated: if any occurrence
|
|
2217
|
+
* is a named top-level schema, that name becomes the canonical (so other top-level duplicates
|
|
2218
|
+
* and inline copies turn into references to it); otherwise a new definition is hoisted using
|
|
2219
|
+
* `nameFor`. The plan is then applied per node with {@link applyDedupe}.
|
|
2220
|
+
*
|
|
2221
|
+
* @example
|
|
2222
|
+
* ```ts
|
|
2223
|
+
* const plan = buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2224
|
+
* isCandidate: (node) => node.type === 'enum' || node.type === 'object',
|
|
2225
|
+
* nameFor: (node) => node.name ?? null,
|
|
2226
|
+
* refFor: (name) => `#/components/schemas/${name}`,
|
|
2227
|
+
* })
|
|
2228
|
+
* ```
|
|
2229
|
+
*/
|
|
2230
|
+
function buildDedupePlan(roots, options) {
|
|
2231
|
+
const { isCandidate, nameFor, refFor, minOccurrences = 2 } = options;
|
|
2232
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
2233
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
2234
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2235
|
+
function record(schemaNode) {
|
|
2236
|
+
const signature = signatureOf(schemaNode, signatures);
|
|
2237
|
+
if (!isCandidate(schemaNode)) return;
|
|
2238
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
2239
|
+
const group = groups.get(signature);
|
|
2240
|
+
if (group) {
|
|
2241
|
+
group.count++;
|
|
2242
|
+
if (isTopLevel && !group.topLevelName) group.topLevelName = schemaNode.name;
|
|
2243
|
+
} else groups.set(signature, {
|
|
2244
|
+
count: 1,
|
|
2245
|
+
representative: schemaNode,
|
|
2246
|
+
topLevelName: isTopLevel ? schemaNode.name : void 0
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
for (const root of roots) {
|
|
2250
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
2251
|
+
for (const schemaNode of collectLazy(root, { schema: (node) => node })) record(schemaNode);
|
|
2252
|
+
}
|
|
2253
|
+
const canonicalBySignature = /* @__PURE__ */ new Map();
|
|
2254
|
+
const pendingHoists = [];
|
|
2255
|
+
for (const [signature, group] of groups) {
|
|
2256
|
+
if (group.count < minOccurrences) continue;
|
|
2257
|
+
if (group.topLevelName) {
|
|
2258
|
+
canonicalBySignature.set(signature, {
|
|
2259
|
+
name: group.topLevelName,
|
|
2260
|
+
ref: refFor(group.topLevelName)
|
|
2261
|
+
});
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const name = nameFor(group.representative, signature);
|
|
2265
|
+
if (!name) continue;
|
|
2266
|
+
canonicalBySignature.set(signature, {
|
|
2267
|
+
name,
|
|
2268
|
+
ref: refFor(name)
|
|
2269
|
+
});
|
|
2270
|
+
pendingHoists.push({
|
|
2271
|
+
name,
|
|
2272
|
+
representative: group.representative
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
return {
|
|
2276
|
+
canonicalBySignature,
|
|
2277
|
+
hoisted: pendingHoists.map(({ name, representative }) => cleanDefinition(applyDedupe(representative, canonicalBySignature, true), name))
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
//#endregion
|
|
2281
|
+
//#region src/dialect.ts
|
|
2282
|
+
/**
|
|
2283
|
+
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
2284
|
+
* `defineParser`, it adds no runtime behavior — it pins the dialect's type for
|
|
2285
|
+
* inference and gives adapter authors a discoverable anchor.
|
|
2286
|
+
*
|
|
2287
|
+
* @example
|
|
2288
|
+
* ```ts
|
|
2289
|
+
* export const oasDialect = defineSchemaDialect({
|
|
2290
|
+
* name: 'oas',
|
|
2291
|
+
* isNullable,
|
|
2292
|
+
* isReference,
|
|
2293
|
+
* isDiscriminator,
|
|
2294
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
2295
|
+
* resolveRef,
|
|
2296
|
+
* })
|
|
2297
|
+
* ```
|
|
2298
|
+
*/
|
|
2299
|
+
function defineSchemaDialect(dialect) {
|
|
2300
|
+
return dialect;
|
|
2301
|
+
}
|
|
2302
|
+
//#endregion
|
|
2303
|
+
//#region src/dispatch.ts
|
|
2304
|
+
/**
|
|
2305
|
+
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
2306
|
+
*
|
|
2307
|
+
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
2308
|
+
* The contract an adapter follows is intentionally minimal:
|
|
2309
|
+
*
|
|
2310
|
+
* context → [rule.match → rule.convert] → node
|
|
2311
|
+
*
|
|
2312
|
+
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
2313
|
+
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
2314
|
+
* context type and a new rules table — the traversal here is reused unchanged.
|
|
2315
|
+
*
|
|
2316
|
+
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
2317
|
+
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
2318
|
+
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
2319
|
+
* returns `null`, leaving the caller to apply its own fallback.
|
|
2320
|
+
*
|
|
2321
|
+
* @example
|
|
2322
|
+
* ```ts
|
|
2323
|
+
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
2324
|
+
* ```
|
|
2325
|
+
*/
|
|
2326
|
+
function dispatch(rules, context) {
|
|
2327
|
+
for (const rule of rules) {
|
|
2328
|
+
if (!rule.match(context)) continue;
|
|
2329
|
+
const node = rule.convert(context);
|
|
2330
|
+
if (node !== null && node !== void 0) return node;
|
|
2331
|
+
}
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
//#endregion
|
|
2335
|
+
//#region src/printer.ts
|
|
2336
|
+
/**
|
|
2337
|
+
* Defines a schema printer: a function that takes a `SchemaNode` and emits
|
|
2338
|
+
* code in your target language. Each plugin that produces code from schemas
|
|
2339
|
+
* (TypeScript types, Zod schemas, Faker factories) ships a printer built
|
|
2340
|
+
* with this helper.
|
|
2001
2341
|
*
|
|
2002
2342
|
* The builder receives resolved options and returns:
|
|
2003
|
-
* - `name` — a unique identifier for the printer
|
|
2004
|
-
* - `options` — options stored on the returned printer instance
|
|
2005
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
2006
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
2007
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
2008
|
-
* - This keeps recursion safe and avoids self-calls
|
|
2009
2343
|
*
|
|
2010
|
-
*
|
|
2344
|
+
* - `name` — unique identifier for the printer.
|
|
2345
|
+
* - `options` — stored on the returned printer instance.
|
|
2346
|
+
* - `nodes` — map of `SchemaType` → handler. Handlers return the rendered
|
|
2347
|
+
* output (a string, a TypeScript AST node, ...) for that schema type.
|
|
2348
|
+
* - `print` (optional) — top-level override exposed as `printer.print`.
|
|
2349
|
+
* Use `this.transform(node)` inside it to dispatch to `nodes` recursively.
|
|
2011
2350
|
*
|
|
2012
|
-
*
|
|
2351
|
+
* Without a `print` override, `printer.print` falls back to `printer.transform`
|
|
2352
|
+
* (the node-level dispatcher).
|
|
2353
|
+
*
|
|
2354
|
+
* @example Tiny Zod printer
|
|
2013
2355
|
* ```ts
|
|
2356
|
+
* import { definePrinter, type PrinterFactoryOptions } from '@kubb/ast'
|
|
2357
|
+
*
|
|
2014
2358
|
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
2015
2359
|
*
|
|
2016
2360
|
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
@@ -2019,7 +2363,9 @@ function createJsx(value) {
|
|
|
2019
2363
|
* nodes: {
|
|
2020
2364
|
* string: () => 'z.string()',
|
|
2021
2365
|
* object(node) {
|
|
2022
|
-
* const props = node.properties
|
|
2366
|
+
* const props = node.properties
|
|
2367
|
+
* .map((p) => `${p.name}: ${this.transform(p.schema)}`)
|
|
2368
|
+
* .join(', ')
|
|
2023
2369
|
* return `z.object({ ${props} })`
|
|
2024
2370
|
* },
|
|
2025
2371
|
* },
|
|
@@ -2047,7 +2393,7 @@ function createPrinterFactory(getKey) {
|
|
|
2047
2393
|
options: resolvedOptions,
|
|
2048
2394
|
transform: (node) => {
|
|
2049
2395
|
const key = getKey(node);
|
|
2050
|
-
if (key ===
|
|
2396
|
+
if (key === null) return null;
|
|
2051
2397
|
const handler = nodes[key];
|
|
2052
2398
|
if (!handler) return null;
|
|
2053
2399
|
return handler.call(context, node);
|
|
@@ -2084,10 +2430,10 @@ function enumPropName(parentName, propName, enumSuffix) {
|
|
|
2084
2430
|
function collectImports({ node, nameMapping, resolve }) {
|
|
2085
2431
|
return collect(node, { schema(schemaNode) {
|
|
2086
2432
|
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2087
|
-
if (!schemaRef?.ref) return;
|
|
2433
|
+
if (!schemaRef?.ref) return null;
|
|
2088
2434
|
const rawName = extractRefName(schemaRef.ref);
|
|
2089
2435
|
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2090
|
-
if (!result) return;
|
|
2436
|
+
if (!result) return null;
|
|
2091
2437
|
return result;
|
|
2092
2438
|
} });
|
|
2093
2439
|
}
|
|
@@ -2141,23 +2487,27 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
|
2141
2487
|
* ])
|
|
2142
2488
|
* ```
|
|
2143
2489
|
*/
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2490
|
+
function* mergeAdjacentObjectsLazy(members) {
|
|
2491
|
+
let acc;
|
|
2492
|
+
for (const member of members) {
|
|
2146
2493
|
const objectMember = narrowSchema(member, "object");
|
|
2147
|
-
if (objectMember && !objectMember.name) {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
...
|
|
2153
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2494
|
+
if (objectMember && !objectMember.name && acc !== void 0) {
|
|
2495
|
+
const accObject = narrowSchema(acc, "object");
|
|
2496
|
+
if (accObject && !accObject.name) {
|
|
2497
|
+
acc = createSchema({
|
|
2498
|
+
...accObject,
|
|
2499
|
+
properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2154
2500
|
});
|
|
2155
|
-
|
|
2501
|
+
continue;
|
|
2156
2502
|
}
|
|
2157
2503
|
}
|
|
2158
|
-
acc
|
|
2159
|
-
|
|
2160
|
-
}
|
|
2504
|
+
if (acc !== void 0) yield acc;
|
|
2505
|
+
acc = member;
|
|
2506
|
+
}
|
|
2507
|
+
if (acc !== void 0) yield acc;
|
|
2508
|
+
}
|
|
2509
|
+
function mergeAdjacentObjects(members) {
|
|
2510
|
+
return [...mergeAdjacentObjectsLazy(members)];
|
|
2161
2511
|
}
|
|
2162
2512
|
/**
|
|
2163
2513
|
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
@@ -2189,7 +2539,7 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2189
2539
|
const enumNode = narrowSchema(propNode, "enum");
|
|
2190
2540
|
if (enumNode?.primitive === "boolean") return {
|
|
2191
2541
|
...propNode,
|
|
2192
|
-
name:
|
|
2542
|
+
name: null
|
|
2193
2543
|
};
|
|
2194
2544
|
if (enumNode) return {
|
|
2195
2545
|
...propNode,
|
|
@@ -2198,6 +2548,6 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2198
2548
|
return propNode;
|
|
2199
2549
|
}
|
|
2200
2550
|
//#endregion
|
|
2201
|
-
export { caseParams, childName, collect, collectImports, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createResponse, createSchema, createSource, createText, createType, definePrinter, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, walk };
|
|
2551
|
+
export { applyDedupe, buildDedupePlan, caseParams, childName, collect, collectImports, collectLazy, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createContent, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createRequestBody, createResponse, createSchema, createSource, createStreamInput, createText, createType, definePrinter, defineSchemaDialect, dispatch, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaEqual, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, mergeAdjacentObjectsLazy, narrowSchema, nodeKinds, resolveRefName, schemaSignature, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, update, walk };
|
|
2202
2552
|
|
|
2203
2553
|
//# sourceMappingURL=index.js.map
|