@kubb/ast 5.0.0-beta.3 → 5.0.0-beta.30
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 +473 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1306 -999
- package/dist/index.js +465 -332
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- 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 +9 -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/transformers.ts +20 -15
- package/src/types.ts +7 -0
- package/src/utils.ts +109 -68
- package/src/visitor.ts +229 -275
package/dist/index.cjs
CHANGED
|
@@ -224,6 +224,60 @@ const mediaTypes = {
|
|
|
224
224
|
videoMp4: "video/mp4"
|
|
225
225
|
};
|
|
226
226
|
//#endregion
|
|
227
|
+
//#region src/dialect.ts
|
|
228
|
+
/**
|
|
229
|
+
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
230
|
+
* `defineParser`, it adds no runtime behavior — it pins the dialect's type for
|
|
231
|
+
* inference and gives adapter authors a discoverable anchor.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* export const oasDialect = defineSchemaDialect({
|
|
236
|
+
* name: 'oas',
|
|
237
|
+
* isNullable,
|
|
238
|
+
* isReference,
|
|
239
|
+
* isDiscriminator,
|
|
240
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
241
|
+
* resolveRef,
|
|
242
|
+
* })
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
function defineSchemaDialect(dialect) {
|
|
246
|
+
return dialect;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/dispatch.ts
|
|
250
|
+
/**
|
|
251
|
+
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
252
|
+
*
|
|
253
|
+
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
254
|
+
* The contract an adapter follows is intentionally minimal:
|
|
255
|
+
*
|
|
256
|
+
* context → [rule.match → rule.convert] → node
|
|
257
|
+
*
|
|
258
|
+
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
259
|
+
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
260
|
+
* context type and a new rules table — the traversal here is reused unchanged.
|
|
261
|
+
*
|
|
262
|
+
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
263
|
+
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
264
|
+
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
265
|
+
* returns `null`, leaving the caller to apply its own fallback.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
function dispatch(rules, context) {
|
|
273
|
+
for (const rule of rules) {
|
|
274
|
+
if (!rule.match(context)) continue;
|
|
275
|
+
const node = rule.convert(context);
|
|
276
|
+
if (node !== null && node !== void 0) return node;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
227
281
|
//#region ../../internals/utils/src/casing.ts
|
|
228
282
|
/**
|
|
229
283
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -288,6 +342,46 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
288
342
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
289
343
|
}
|
|
290
344
|
//#endregion
|
|
345
|
+
//#region ../../internals/utils/src/promise.ts
|
|
346
|
+
/**
|
|
347
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
348
|
+
*
|
|
349
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
350
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
351
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
352
|
+
* inner (created once per outer miss) keyed by the second.
|
|
353
|
+
*
|
|
354
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
355
|
+
* cleared independently of the memoized function.
|
|
356
|
+
*
|
|
357
|
+
* @example Single WeakMap key
|
|
358
|
+
* ```ts
|
|
359
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
360
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
361
|
+
* ```
|
|
362
|
+
*
|
|
363
|
+
* @example Single Map key (primitive)
|
|
364
|
+
* ```ts
|
|
365
|
+
* const cache = new Map<string, Resolver>()
|
|
366
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
367
|
+
* ```
|
|
368
|
+
*
|
|
369
|
+
* @example Two-level (object + primitive)
|
|
370
|
+
* ```ts
|
|
371
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
372
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
373
|
+
* fn(params)('camelcase')
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
function memoize(store, factory) {
|
|
377
|
+
return (key) => {
|
|
378
|
+
if (store.has(key)) return store.get(key);
|
|
379
|
+
const value = factory(key);
|
|
380
|
+
store.set(key, value);
|
|
381
|
+
return value;
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
291
385
|
//#region ../../internals/utils/src/reserved.ts
|
|
292
386
|
/**
|
|
293
387
|
* JavaScript and Java reserved words.
|
|
@@ -415,11 +509,11 @@ function trimExtName(text) {
|
|
|
415
509
|
* @example
|
|
416
510
|
* ```ts
|
|
417
511
|
* const schema = createSchema({ type: 'string' })
|
|
418
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode |
|
|
512
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
419
513
|
* ```
|
|
420
514
|
*/
|
|
421
515
|
function narrowSchema(node, type) {
|
|
422
|
-
return node?.type === type ? node :
|
|
516
|
+
return node?.type === type ? node : null;
|
|
423
517
|
}
|
|
424
518
|
function isKind(kind) {
|
|
425
519
|
return (node) => node.kind === kind;
|
|
@@ -458,6 +552,19 @@ const isOutputNode = isKind("Output");
|
|
|
458
552
|
*/
|
|
459
553
|
const isOperationNode = isKind("Operation");
|
|
460
554
|
/**
|
|
555
|
+
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* if (isHttpOperationNode(node)) {
|
|
560
|
+
* console.log(node.method, node.path)
|
|
561
|
+
* }
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
function isHttpOperationNode(node) {
|
|
565
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
461
568
|
* Returns `true` when the input is a `SchemaNode`.
|
|
462
569
|
*
|
|
463
570
|
* @example
|
|
@@ -468,12 +575,6 @@ const isOperationNode = isKind("Operation");
|
|
|
468
575
|
* ```
|
|
469
576
|
*/
|
|
470
577
|
const isSchemaNode = isKind("Schema");
|
|
471
|
-
isKind("Property");
|
|
472
|
-
isKind("Parameter");
|
|
473
|
-
isKind("Response");
|
|
474
|
-
isKind("FunctionParameter");
|
|
475
|
-
isKind("ParameterGroup");
|
|
476
|
-
isKind("FunctionParameters");
|
|
477
578
|
//#endregion
|
|
478
579
|
//#region src/refs.ts
|
|
479
580
|
/**
|
|
@@ -526,53 +627,92 @@ function createLimit(concurrency) {
|
|
|
526
627
|
});
|
|
527
628
|
};
|
|
528
629
|
}
|
|
630
|
+
const visitorKeysByKind = {
|
|
631
|
+
Input: ["schemas", "operations"],
|
|
632
|
+
Operation: [
|
|
633
|
+
"parameters",
|
|
634
|
+
"requestBody",
|
|
635
|
+
"responses"
|
|
636
|
+
],
|
|
637
|
+
RequestBody: ["content"],
|
|
638
|
+
Content: ["schema"],
|
|
639
|
+
Response: ["content"],
|
|
640
|
+
Schema: [
|
|
641
|
+
"properties",
|
|
642
|
+
"items",
|
|
643
|
+
"members",
|
|
644
|
+
"additionalProperties"
|
|
645
|
+
],
|
|
646
|
+
Property: ["schema"],
|
|
647
|
+
Parameter: ["schema"]
|
|
648
|
+
};
|
|
649
|
+
/**
|
|
650
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
651
|
+
*/
|
|
652
|
+
function isNode(value) {
|
|
653
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
654
|
+
}
|
|
529
655
|
/**
|
|
530
|
-
* Returns the immediate traversable children of `node
|
|
656
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
531
657
|
*
|
|
532
|
-
*
|
|
533
|
-
* `additionalProperties`) are only included
|
|
534
|
-
* when `recurse` is `true`; shallow mode skips them.
|
|
658
|
+
* `Schema` children are only included when `recurse` is `true`; shallow mode skips them.
|
|
535
659
|
*
|
|
536
660
|
* @example
|
|
537
661
|
* ```ts
|
|
538
662
|
* const children = getChildren(operationNode, true)
|
|
539
|
-
* // returns parameters,
|
|
540
|
-
* ```
|
|
541
|
-
*/
|
|
542
|
-
function getChildren(node, recurse) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const children = [];
|
|
553
|
-
if (!recurse) return [];
|
|
554
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
555
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
556
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
557
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
558
|
-
return children;
|
|
559
|
-
}
|
|
560
|
-
case "Property": return [node.schema];
|
|
561
|
-
case "Parameter": return [node.schema];
|
|
562
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
563
|
-
case "FunctionParameter":
|
|
564
|
-
case "ParameterGroup":
|
|
565
|
-
case "FunctionParameters":
|
|
566
|
-
case "Type": return [];
|
|
567
|
-
default: return [];
|
|
663
|
+
* // returns parameters, the request body, and responses
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
function* getChildren(node, recurse) {
|
|
667
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
668
|
+
const keys = visitorKeysByKind[node.kind];
|
|
669
|
+
if (!keys) return;
|
|
670
|
+
const record = node;
|
|
671
|
+
for (const key of keys) {
|
|
672
|
+
const value = record[key];
|
|
673
|
+
if (Array.isArray(value)) {
|
|
674
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
675
|
+
} else if (isNode(value)) yield value;
|
|
568
676
|
}
|
|
569
677
|
}
|
|
570
678
|
/**
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
679
|
+
* Maps a node `kind` to the matching visitor callback name. Only the seven
|
|
680
|
+
* traversable node kinds have an entry; every other kind resolves to
|
|
681
|
+
* `undefined` and is skipped.
|
|
682
|
+
*/
|
|
683
|
+
const VISITOR_KEY_BY_KIND = {
|
|
684
|
+
Input: "input",
|
|
685
|
+
Output: "output",
|
|
686
|
+
Operation: "operation",
|
|
687
|
+
Schema: "schema",
|
|
688
|
+
Property: "property",
|
|
689
|
+
Parameter: "parameter",
|
|
690
|
+
Response: "response"
|
|
691
|
+
};
|
|
692
|
+
/**
|
|
693
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
694
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
695
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
574
696
|
*
|
|
575
|
-
*
|
|
697
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
698
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
699
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
700
|
+
*/
|
|
701
|
+
function applyVisitor(node, visitor, parent) {
|
|
702
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
703
|
+
if (!key) return void 0;
|
|
704
|
+
const fn = visitor[key];
|
|
705
|
+
return fn?.(node, { parent });
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
709
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
710
|
+
*
|
|
711
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
712
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
713
|
+
* work; lower values reduce memory pressure.
|
|
714
|
+
*
|
|
715
|
+
* @example Log every operation
|
|
576
716
|
* ```ts
|
|
577
717
|
* await walk(root, {
|
|
578
718
|
* operation(node) {
|
|
@@ -581,213 +721,114 @@ function getChildren(node, recurse) {
|
|
|
581
721
|
* })
|
|
582
722
|
* ```
|
|
583
723
|
*
|
|
584
|
-
* @example
|
|
724
|
+
* @example Only visit the root node
|
|
585
725
|
* ```ts
|
|
586
|
-
*
|
|
587
|
-
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
726
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
588
727
|
* ```
|
|
589
728
|
*/
|
|
590
729
|
async function walk(node, options) {
|
|
591
730
|
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
592
731
|
}
|
|
593
732
|
async function _walk(node, visitor, recurse, limit, parent) {
|
|
594
|
-
|
|
595
|
-
case "Input":
|
|
596
|
-
await limit(() => visitor.input?.(node, { parent }));
|
|
597
|
-
break;
|
|
598
|
-
case "Output":
|
|
599
|
-
await limit(() => visitor.output?.(node, { parent }));
|
|
600
|
-
break;
|
|
601
|
-
case "Operation":
|
|
602
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
603
|
-
break;
|
|
604
|
-
case "Schema":
|
|
605
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
606
|
-
break;
|
|
607
|
-
case "Property":
|
|
608
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
609
|
-
break;
|
|
610
|
-
case "Parameter":
|
|
611
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
612
|
-
break;
|
|
613
|
-
case "Response":
|
|
614
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
615
|
-
break;
|
|
616
|
-
case "FunctionParameter":
|
|
617
|
-
case "ParameterGroup":
|
|
618
|
-
case "FunctionParameters": break;
|
|
619
|
-
}
|
|
733
|
+
await limit(() => applyVisitor(node, visitor, parent));
|
|
620
734
|
const children = getChildren(node, recurse);
|
|
621
735
|
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
622
736
|
}
|
|
623
737
|
function transform(node, options) {
|
|
624
738
|
const { depth, parent, ...visitor } = options;
|
|
625
739
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
parent: op
|
|
666
|
-
}) : void 0
|
|
667
|
-
}))
|
|
668
|
-
} : void 0,
|
|
669
|
-
responses: op.responses.map((r) => transform(r, {
|
|
670
|
-
...options,
|
|
671
|
-
parent: op
|
|
672
|
-
}))
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
case "Schema": {
|
|
676
|
-
let schema = node;
|
|
677
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
678
|
-
if (replaced) schema = replaced;
|
|
679
|
-
const childOptions = {
|
|
680
|
-
...options,
|
|
681
|
-
parent: schema
|
|
682
|
-
};
|
|
683
|
-
return {
|
|
684
|
-
...schema,
|
|
685
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
686
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
687
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
688
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
case "Property": {
|
|
692
|
-
let prop = node;
|
|
693
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
694
|
-
if (replaced) prop = replaced;
|
|
695
|
-
return createProperty({
|
|
696
|
-
...prop,
|
|
697
|
-
schema: transform(prop.schema, {
|
|
698
|
-
...options,
|
|
699
|
-
parent: prop
|
|
700
|
-
})
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
case "Parameter": {
|
|
704
|
-
let param = node;
|
|
705
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
706
|
-
if (replaced) param = replaced;
|
|
707
|
-
return createParameter({
|
|
708
|
-
...param,
|
|
709
|
-
schema: transform(param.schema, {
|
|
710
|
-
...options,
|
|
711
|
-
parent: param
|
|
712
|
-
})
|
|
740
|
+
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
741
|
+
if (rebuilt === node) return node;
|
|
742
|
+
const finalize = nodeFinalizers[rebuilt.kind];
|
|
743
|
+
return finalize ? finalize(rebuilt) : rebuilt;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
|
|
747
|
+
* resync schema optionality against their `required` flag once the schema may
|
|
748
|
+
* have changed.
|
|
749
|
+
*/
|
|
750
|
+
const nodeFinalizers = {
|
|
751
|
+
Property: (node) => createProperty(node),
|
|
752
|
+
Parameter: (node) => createParameter(node)
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
756
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
757
|
+
* `Schema` children are skipped in shallow mode.
|
|
758
|
+
*/
|
|
759
|
+
function transformChildren(node, options, recurse) {
|
|
760
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
761
|
+
const keys = visitorKeysByKind[node.kind];
|
|
762
|
+
if (!keys) return node;
|
|
763
|
+
const record = node;
|
|
764
|
+
const childOptions = {
|
|
765
|
+
...options,
|
|
766
|
+
parent: node
|
|
767
|
+
};
|
|
768
|
+
let updates;
|
|
769
|
+
for (const key of keys) {
|
|
770
|
+
if (!(key in record)) continue;
|
|
771
|
+
const value = record[key];
|
|
772
|
+
if (Array.isArray(value)) {
|
|
773
|
+
let changed = false;
|
|
774
|
+
const mapped = value.map((item) => {
|
|
775
|
+
if (!isNode(item)) return item;
|
|
776
|
+
const next = transform(item, childOptions);
|
|
777
|
+
if (next !== item) changed = true;
|
|
778
|
+
return next;
|
|
713
779
|
});
|
|
780
|
+
if (changed) (updates ??= {})[key] = mapped;
|
|
781
|
+
} else if (isNode(value)) {
|
|
782
|
+
const next = transform(value, childOptions);
|
|
783
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
714
784
|
}
|
|
715
|
-
case "Response": {
|
|
716
|
-
let response = node;
|
|
717
|
-
const replaced = visitor.response?.(response, { parent });
|
|
718
|
-
if (replaced) response = replaced;
|
|
719
|
-
return {
|
|
720
|
-
...response,
|
|
721
|
-
schema: transform(response.schema, {
|
|
722
|
-
...options,
|
|
723
|
-
parent: response
|
|
724
|
-
})
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
case "FunctionParameter":
|
|
728
|
-
case "ParameterGroup":
|
|
729
|
-
case "FunctionParameters":
|
|
730
|
-
case "Type": return node;
|
|
731
|
-
default: return node;
|
|
732
785
|
}
|
|
786
|
+
return updates ? {
|
|
787
|
+
...node,
|
|
788
|
+
...updates
|
|
789
|
+
} : node;
|
|
733
790
|
}
|
|
734
791
|
/**
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
792
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
793
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
738
794
|
*
|
|
739
|
-
* @example
|
|
795
|
+
* @example Collect every operationId
|
|
740
796
|
* ```ts
|
|
741
|
-
* const ids =
|
|
797
|
+
* const ids: string[] = []
|
|
798
|
+
* for (const id of collectLazy<string>(root, {
|
|
742
799
|
* operation(node) {
|
|
743
800
|
* return node.operationId
|
|
744
801
|
* },
|
|
745
|
-
* })
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
* @example
|
|
749
|
-
* ```ts
|
|
750
|
-
* // Collect from only the current node
|
|
751
|
-
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
802
|
+
* })) {
|
|
803
|
+
* ids.push(id)
|
|
804
|
+
* }
|
|
752
805
|
* ```
|
|
753
806
|
*/
|
|
754
|
-
function
|
|
807
|
+
function* collectLazy(node, options) {
|
|
755
808
|
const { depth, parent, ...visitor } = options;
|
|
756
809
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
case "Input":
|
|
761
|
-
v = visitor.input?.(node, { parent });
|
|
762
|
-
break;
|
|
763
|
-
case "Output":
|
|
764
|
-
v = visitor.output?.(node, { parent });
|
|
765
|
-
break;
|
|
766
|
-
case "Operation":
|
|
767
|
-
v = visitor.operation?.(node, { parent });
|
|
768
|
-
break;
|
|
769
|
-
case "Schema":
|
|
770
|
-
v = visitor.schema?.(node, { parent });
|
|
771
|
-
break;
|
|
772
|
-
case "Property":
|
|
773
|
-
v = visitor.property?.(node, { parent });
|
|
774
|
-
break;
|
|
775
|
-
case "Parameter":
|
|
776
|
-
v = visitor.parameter?.(node, { parent });
|
|
777
|
-
break;
|
|
778
|
-
case "Response":
|
|
779
|
-
v = visitor.response?.(node, { parent });
|
|
780
|
-
break;
|
|
781
|
-
case "FunctionParameter":
|
|
782
|
-
case "ParameterGroup":
|
|
783
|
-
case "FunctionParameters": break;
|
|
784
|
-
}
|
|
785
|
-
if (v !== void 0) results.push(v);
|
|
786
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
810
|
+
const v = applyVisitor(node, visitor, parent);
|
|
811
|
+
if (v != null) yield v;
|
|
812
|
+
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
787
813
|
...options,
|
|
788
814
|
parent: node
|
|
789
|
-
})
|
|
790
|
-
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
819
|
+
* the visitor callbacks return.
|
|
820
|
+
*
|
|
821
|
+
* @example Collect every operationId
|
|
822
|
+
* ```ts
|
|
823
|
+
* const ids = collect<string>(root, {
|
|
824
|
+
* operation(node) {
|
|
825
|
+
* return node.operationId
|
|
826
|
+
* },
|
|
827
|
+
* })
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
function collect(node, options) {
|
|
831
|
+
return Array.from(collectLazy(node, options));
|
|
791
832
|
}
|
|
792
833
|
//#endregion
|
|
793
834
|
//#region src/utils.ts
|
|
@@ -841,15 +882,16 @@ function isStringType(node) {
|
|
|
841
882
|
* the desired casing while preserving `OperationNode.parameters` for other consumers.
|
|
842
883
|
* The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
|
|
843
884
|
*/
|
|
885
|
+
const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
|
|
886
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
887
|
+
return {
|
|
888
|
+
...param,
|
|
889
|
+
name: transformed
|
|
890
|
+
};
|
|
891
|
+
})));
|
|
844
892
|
function caseParams(params, casing) {
|
|
845
893
|
if (!casing) return params;
|
|
846
|
-
return params
|
|
847
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
848
|
-
return {
|
|
849
|
-
...param,
|
|
850
|
-
name: transformed
|
|
851
|
-
};
|
|
852
|
-
});
|
|
894
|
+
return caseParamsMemo(params)(casing);
|
|
853
895
|
}
|
|
854
896
|
/**
|
|
855
897
|
* Creates a single-property object schema used as a discriminator literal.
|
|
@@ -978,7 +1020,7 @@ function createOperationParams(node, options) {
|
|
|
978
1020
|
}));
|
|
979
1021
|
} else {
|
|
980
1022
|
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
981
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0])
|
|
1023
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
982
1024
|
params.push(createFunctionParameter({
|
|
983
1025
|
name: pathName,
|
|
984
1026
|
type: spreadType ? wrapType(spreadType) : void 0,
|
|
@@ -1054,13 +1096,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
|
|
|
1054
1096
|
}
|
|
1055
1097
|
/**
|
|
1056
1098
|
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1057
|
-
* Returns `
|
|
1099
|
+
* Returns `null` when the group name equals the individual param name (no real group).
|
|
1058
1100
|
*/
|
|
1059
1101
|
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1060
|
-
if (!params.length) return;
|
|
1102
|
+
if (!params.length) return null;
|
|
1061
1103
|
const firstParam = params[0];
|
|
1062
1104
|
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1063
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1105
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
1064
1106
|
const allOptional = params.every((p) => !p.required);
|
|
1065
1107
|
return {
|
|
1066
1108
|
type: createParamsType({
|
|
@@ -1127,6 +1169,16 @@ function combineSources(sources) {
|
|
|
1127
1169
|
return [...seen.values()];
|
|
1128
1170
|
}
|
|
1129
1171
|
/**
|
|
1172
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
1173
|
+
*
|
|
1174
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
1175
|
+
*/
|
|
1176
|
+
function mergeNameArrays(existing, incoming) {
|
|
1177
|
+
const merged = new Set(existing);
|
|
1178
|
+
for (const name of incoming) merged.add(name);
|
|
1179
|
+
return [...merged];
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1130
1182
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1131
1183
|
*
|
|
1132
1184
|
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
@@ -1147,11 +1199,8 @@ function combineExports(exports) {
|
|
|
1147
1199
|
if (!name.length) continue;
|
|
1148
1200
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1149
1201
|
const existing = namedByPath.get(key);
|
|
1150
|
-
if (existing && Array.isArray(existing.name))
|
|
1151
|
-
|
|
1152
|
-
for (const n of name) merged.add(n);
|
|
1153
|
-
existing.name = [...merged];
|
|
1154
|
-
} else {
|
|
1202
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1203
|
+
else {
|
|
1155
1204
|
const newItem = {
|
|
1156
1205
|
...curr,
|
|
1157
1206
|
name: [...new Set(name)]
|
|
@@ -1187,6 +1236,11 @@ function combineImports(imports, exports, source) {
|
|
|
1187
1236
|
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
1188
1237
|
return importNameMemo.get(key);
|
|
1189
1238
|
};
|
|
1239
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
1240
|
+
for (const node of imports) {
|
|
1241
|
+
if (!Array.isArray(node.name)) continue;
|
|
1242
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
1243
|
+
}
|
|
1190
1244
|
const result = [];
|
|
1191
1245
|
const namedByPath = /* @__PURE__ */ new Map();
|
|
1192
1246
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1204,11 +1258,8 @@ function combineImports(imports, exports, source) {
|
|
|
1204
1258
|
if (!name.length) continue;
|
|
1205
1259
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1206
1260
|
const existing = namedByPath.get(key);
|
|
1207
|
-
if (existing && Array.isArray(existing.name))
|
|
1208
|
-
|
|
1209
|
-
for (const n of name) merged.add(n);
|
|
1210
|
-
existing.name = [...merged];
|
|
1211
|
-
} else {
|
|
1261
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1262
|
+
else {
|
|
1212
1263
|
const newItem = {
|
|
1213
1264
|
...curr,
|
|
1214
1265
|
name
|
|
@@ -1217,7 +1268,7 @@ function combineImports(imports, exports, source) {
|
|
|
1217
1268
|
namedByPath.set(key, newItem);
|
|
1218
1269
|
}
|
|
1219
1270
|
} else {
|
|
1220
|
-
if (name && !isUsed(name)) continue;
|
|
1271
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1221
1272
|
const key = importKey(path, name, isTypeOnly);
|
|
1222
1273
|
if (!seen.has(key)) {
|
|
1223
1274
|
result.push(curr);
|
|
@@ -1253,7 +1304,7 @@ function extractStringsFromNodes(nodes) {
|
|
|
1253
1304
|
/**
|
|
1254
1305
|
* Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
|
|
1255
1306
|
*
|
|
1256
|
-
* Returns `
|
|
1307
|
+
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1257
1308
|
* identifier for type definitions or error messages.
|
|
1258
1309
|
*
|
|
1259
1310
|
* @example
|
|
@@ -1263,9 +1314,9 @@ function extractStringsFromNodes(nodes) {
|
|
|
1263
1314
|
* ```
|
|
1264
1315
|
*/
|
|
1265
1316
|
function resolveRefName(node) {
|
|
1266
|
-
if (!node || node.type !== "ref") return
|
|
1267
|
-
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ??
|
|
1268
|
-
return node.name ?? node.schema?.name ??
|
|
1317
|
+
if (!node || node.type !== "ref") return null;
|
|
1318
|
+
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
1319
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1269
1320
|
}
|
|
1270
1321
|
/**
|
|
1271
1322
|
* Collects every named schema referenced (transitively) from a node via ref edges.
|
|
@@ -1287,14 +1338,19 @@ function resolveRefName(node) {
|
|
|
1287
1338
|
* }
|
|
1288
1339
|
* ```
|
|
1289
1340
|
*/
|
|
1290
|
-
|
|
1291
|
-
|
|
1341
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1342
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1292
1343
|
collect(node, { schema(child) {
|
|
1293
1344
|
if (child.type === "ref") {
|
|
1294
1345
|
const name = resolveRefName(child);
|
|
1295
|
-
if (name)
|
|
1346
|
+
if (name) refs.add(name);
|
|
1296
1347
|
}
|
|
1297
1348
|
} });
|
|
1349
|
+
return refs;
|
|
1350
|
+
});
|
|
1351
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1352
|
+
if (!node) return out;
|
|
1353
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1298
1354
|
return out;
|
|
1299
1355
|
}
|
|
1300
1356
|
/**
|
|
@@ -1310,10 +1366,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1310
1366
|
*
|
|
1311
1367
|
* @example Only generate schemas referenced by included operations
|
|
1312
1368
|
* ```ts
|
|
1313
|
-
* const includedOps =
|
|
1314
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1369
|
+
* const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
1370
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1315
1371
|
*
|
|
1316
|
-
* for (const schema of
|
|
1372
|
+
* for (const schema of schemas) {
|
|
1317
1373
|
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1318
1374
|
* // … generate schema
|
|
1319
1375
|
* }
|
|
@@ -1321,11 +1377,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1321
1377
|
*
|
|
1322
1378
|
* @example Check whether a specific schema is needed
|
|
1323
1379
|
* ```ts
|
|
1324
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1380
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1325
1381
|
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
1326
1382
|
* ```
|
|
1327
1383
|
*/
|
|
1328
|
-
|
|
1384
|
+
const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
|
|
1385
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1329
1386
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
1330
1387
|
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1331
1388
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -1337,22 +1394,17 @@ function collectUsedSchemaNames(operations, schemas) {
|
|
|
1337
1394
|
if (namedSchema) visitSchema(namedSchema);
|
|
1338
1395
|
}
|
|
1339
1396
|
}
|
|
1340
|
-
for (const op of operations) for (const schema of
|
|
1397
|
+
for (const op of operations) for (const schema of collectLazy(op, {
|
|
1341
1398
|
depth: "shallow",
|
|
1342
1399
|
schema: (node) => node
|
|
1343
1400
|
})) visitSchema(schema);
|
|
1344
1401
|
return result;
|
|
1345
1402
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1352
|
-
*
|
|
1353
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1354
|
-
*/
|
|
1355
|
-
function findCircularSchemas(schemas) {
|
|
1403
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
1404
|
+
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
1405
|
+
}
|
|
1406
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1407
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1356
1408
|
const graph = /* @__PURE__ */ new Map();
|
|
1357
1409
|
for (const schema of schemas) {
|
|
1358
1410
|
if (!schema.name) continue;
|
|
@@ -1375,6 +1427,19 @@ function findCircularSchemas(schemas) {
|
|
|
1375
1427
|
}
|
|
1376
1428
|
}
|
|
1377
1429
|
return circular;
|
|
1430
|
+
});
|
|
1431
|
+
/**
|
|
1432
|
+
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
1433
|
+
*
|
|
1434
|
+
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
1435
|
+
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
1436
|
+
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1437
|
+
*
|
|
1438
|
+
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1439
|
+
*/
|
|
1440
|
+
function findCircularSchemas(schemas) {
|
|
1441
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1442
|
+
return findCircularSchemasMemo(schemas);
|
|
1378
1443
|
}
|
|
1379
1444
|
/**
|
|
1380
1445
|
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
@@ -1386,19 +1451,23 @@ function findCircularSchemas(schemas) {
|
|
|
1386
1451
|
*/
|
|
1387
1452
|
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1388
1453
|
if (!node || circularSchemas.size === 0) return false;
|
|
1389
|
-
|
|
1390
|
-
if (child.type !== "ref") return
|
|
1454
|
+
for (const _ of collectLazy(node, { schema(child) {
|
|
1455
|
+
if (child.type !== "ref") return null;
|
|
1391
1456
|
const name = resolveRefName(child);
|
|
1392
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true :
|
|
1393
|
-
} })
|
|
1457
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
1458
|
+
} })) return true;
|
|
1459
|
+
return false;
|
|
1394
1460
|
}
|
|
1395
1461
|
//#endregion
|
|
1396
1462
|
//#region src/factory.ts
|
|
1397
1463
|
/**
|
|
1398
|
-
*
|
|
1464
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
1465
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
1466
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
1399
1467
|
*
|
|
1400
|
-
* -
|
|
1401
|
-
* -
|
|
1468
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
1469
|
+
* - Non-required + nullable → `nullish: true`.
|
|
1470
|
+
* - Required → both flags cleared.
|
|
1402
1471
|
*/
|
|
1403
1472
|
function syncOptionality(schema, required) {
|
|
1404
1473
|
const nullable = schema.nullable ?? false;
|
|
@@ -1409,6 +1478,29 @@ function syncOptionality(schema, required) {
|
|
|
1409
1478
|
};
|
|
1410
1479
|
}
|
|
1411
1480
|
/**
|
|
1481
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1482
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1483
|
+
* with the changes applied.
|
|
1484
|
+
*
|
|
1485
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract — pair it with the
|
|
1486
|
+
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1487
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1488
|
+
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
1489
|
+
*
|
|
1490
|
+
* @example
|
|
1491
|
+
* ```ts
|
|
1492
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1493
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
1494
|
+
* ```
|
|
1495
|
+
*/
|
|
1496
|
+
function update(node, changes) {
|
|
1497
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1498
|
+
...node,
|
|
1499
|
+
...changes
|
|
1500
|
+
};
|
|
1501
|
+
return node;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1412
1504
|
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
1413
1505
|
*
|
|
1414
1506
|
* @example
|
|
@@ -1427,11 +1519,31 @@ function createInput(overrides = {}) {
|
|
|
1427
1519
|
return {
|
|
1428
1520
|
schemas: [],
|
|
1429
1521
|
operations: [],
|
|
1522
|
+
meta: {
|
|
1523
|
+
circularNames: [],
|
|
1524
|
+
enumNames: []
|
|
1525
|
+
},
|
|
1430
1526
|
...overrides,
|
|
1431
1527
|
kind: "Input"
|
|
1432
1528
|
};
|
|
1433
1529
|
}
|
|
1434
1530
|
/**
|
|
1531
|
+
* Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```ts
|
|
1535
|
+
* const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
|
|
1536
|
+
* ```
|
|
1537
|
+
*/
|
|
1538
|
+
function createStreamInput(schemas, operations, meta) {
|
|
1539
|
+
return {
|
|
1540
|
+
kind: "Input",
|
|
1541
|
+
schemas,
|
|
1542
|
+
operations,
|
|
1543
|
+
meta
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1435
1547
|
* Creates an `OutputNode` with a stable default for `files`.
|
|
1436
1548
|
*
|
|
1437
1549
|
* @example
|
|
@@ -1453,35 +1565,35 @@ function createOutput(overrides = {}) {
|
|
|
1453
1565
|
};
|
|
1454
1566
|
}
|
|
1455
1567
|
/**
|
|
1456
|
-
* Creates
|
|
1457
|
-
*
|
|
1458
|
-
* @example
|
|
1459
|
-
* ```ts
|
|
1460
|
-
* const operation = createOperation({
|
|
1461
|
-
* operationId: 'getPetById',
|
|
1462
|
-
* method: 'GET',
|
|
1463
|
-
* path: '/pet/{petId}',
|
|
1464
|
-
* })
|
|
1465
|
-
* // tags, parameters, and responses are []
|
|
1466
|
-
* ```
|
|
1467
|
-
*
|
|
1468
|
-
* @example
|
|
1469
|
-
* ```ts
|
|
1470
|
-
* const operation = createOperation({
|
|
1471
|
-
* operationId: 'findPets',
|
|
1472
|
-
* method: 'GET',
|
|
1473
|
-
* path: '/pet/findByStatus',
|
|
1474
|
-
* tags: ['pet'],
|
|
1475
|
-
* })
|
|
1476
|
-
* ```
|
|
1568
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
1477
1569
|
*/
|
|
1570
|
+
function createContent(props) {
|
|
1571
|
+
return {
|
|
1572
|
+
...props,
|
|
1573
|
+
kind: "Content"
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
|
|
1578
|
+
*/
|
|
1579
|
+
function createRequestBody(props) {
|
|
1580
|
+
return {
|
|
1581
|
+
...props,
|
|
1582
|
+
kind: "RequestBody",
|
|
1583
|
+
content: props.content?.map(createContent)
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1478
1586
|
function createOperation(props) {
|
|
1587
|
+
const { requestBody, ...rest } = props;
|
|
1588
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
1479
1589
|
return {
|
|
1480
1590
|
tags: [],
|
|
1481
1591
|
parameters: [],
|
|
1482
1592
|
responses: [],
|
|
1483
|
-
...
|
|
1484
|
-
|
|
1593
|
+
...rest,
|
|
1594
|
+
...isHttp ? { protocol: "http" } : {},
|
|
1595
|
+
kind: "Operation",
|
|
1596
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
1485
1597
|
};
|
|
1486
1598
|
}
|
|
1487
1599
|
/**
|
|
@@ -1595,19 +1707,29 @@ function createParameter(props) {
|
|
|
1595
1707
|
/**
|
|
1596
1708
|
* Creates a `ResponseNode`.
|
|
1597
1709
|
*
|
|
1710
|
+
* Response body schemas live inside `content`. For convenience a single legacy `schema`
|
|
1711
|
+
* (with optional `mediaType`/`keysToOmit`) is normalized into one `content` entry, so the same
|
|
1712
|
+
* schema is never stored both at the node root and inside `content`.
|
|
1713
|
+
*
|
|
1598
1714
|
* @example
|
|
1599
1715
|
* ```ts
|
|
1600
1716
|
* const response = createResponse({
|
|
1601
1717
|
* statusCode: '200',
|
|
1602
|
-
*
|
|
1603
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
1718
|
+
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
1604
1719
|
* })
|
|
1605
1720
|
* ```
|
|
1606
1721
|
*/
|
|
1607
1722
|
function createResponse(props) {
|
|
1723
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
1724
|
+
const entries = content ?? (schema ? [{
|
|
1725
|
+
contentType: mediaType ?? "application/json",
|
|
1726
|
+
schema,
|
|
1727
|
+
keysToOmit: keysToOmit ?? null
|
|
1728
|
+
}] : void 0);
|
|
1608
1729
|
return {
|
|
1609
|
-
...
|
|
1610
|
-
kind: "Response"
|
|
1730
|
+
...rest,
|
|
1731
|
+
kind: "Response",
|
|
1732
|
+
content: entries?.map(createContent)
|
|
1611
1733
|
};
|
|
1612
1734
|
}
|
|
1613
1735
|
/**
|
|
@@ -2018,22 +2140,27 @@ function createJsx(value) {
|
|
|
2018
2140
|
//#endregion
|
|
2019
2141
|
//#region src/printer.ts
|
|
2020
2142
|
/**
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
2143
|
+
* Defines a schema printer: a function that takes a `SchemaNode` and emits
|
|
2144
|
+
* code in your target language. Each plugin that produces code from schemas
|
|
2145
|
+
* (TypeScript types, Zod schemas, Faker factories) ships a printer built
|
|
2146
|
+
* with this helper.
|
|
2024
2147
|
*
|
|
2025
2148
|
* The builder receives resolved options and returns:
|
|
2026
|
-
* - `name` — a unique identifier for the printer
|
|
2027
|
-
* - `options` — options stored on the returned printer instance
|
|
2028
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
2029
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
2030
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
2031
|
-
* - This keeps recursion safe and avoids self-calls
|
|
2032
2149
|
*
|
|
2033
|
-
*
|
|
2150
|
+
* - `name` — unique identifier for the printer.
|
|
2151
|
+
* - `options` — stored on the returned printer instance.
|
|
2152
|
+
* - `nodes` — map of `SchemaType` → handler. Handlers return the rendered
|
|
2153
|
+
* output (a string, a TypeScript AST node, ...) for that schema type.
|
|
2154
|
+
* - `print` (optional) — top-level override exposed as `printer.print`.
|
|
2155
|
+
* Use `this.transform(node)` inside it to dispatch to `nodes` recursively.
|
|
2034
2156
|
*
|
|
2035
|
-
*
|
|
2157
|
+
* Without a `print` override, `printer.print` falls back to `printer.transform`
|
|
2158
|
+
* (the node-level dispatcher).
|
|
2159
|
+
*
|
|
2160
|
+
* @example Tiny Zod printer
|
|
2036
2161
|
* ```ts
|
|
2162
|
+
* import { definePrinter, type PrinterFactoryOptions } from '@kubb/ast'
|
|
2163
|
+
*
|
|
2037
2164
|
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
2038
2165
|
*
|
|
2039
2166
|
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
@@ -2042,7 +2169,9 @@ function createJsx(value) {
|
|
|
2042
2169
|
* nodes: {
|
|
2043
2170
|
* string: () => 'z.string()',
|
|
2044
2171
|
* object(node) {
|
|
2045
|
-
* const props = node.properties
|
|
2172
|
+
* const props = node.properties
|
|
2173
|
+
* .map((p) => `${p.name}: ${this.transform(p.schema)}`)
|
|
2174
|
+
* .join(', ')
|
|
2046
2175
|
* return `z.object({ ${props} })`
|
|
2047
2176
|
* },
|
|
2048
2177
|
* },
|
|
@@ -2070,7 +2199,7 @@ function createPrinterFactory(getKey) {
|
|
|
2070
2199
|
options: resolvedOptions,
|
|
2071
2200
|
transform: (node) => {
|
|
2072
2201
|
const key = getKey(node);
|
|
2073
|
-
if (key ===
|
|
2202
|
+
if (key === null) return null;
|
|
2074
2203
|
const handler = nodes[key];
|
|
2075
2204
|
if (!handler) return null;
|
|
2076
2205
|
return handler.call(context, node);
|
|
@@ -2107,10 +2236,10 @@ function enumPropName(parentName, propName, enumSuffix) {
|
|
|
2107
2236
|
function collectImports({ node, nameMapping, resolve }) {
|
|
2108
2237
|
return collect(node, { schema(schemaNode) {
|
|
2109
2238
|
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2110
|
-
if (!schemaRef?.ref) return;
|
|
2239
|
+
if (!schemaRef?.ref) return null;
|
|
2111
2240
|
const rawName = extractRefName(schemaRef.ref);
|
|
2112
2241
|
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2113
|
-
if (!result) return;
|
|
2242
|
+
if (!result) return null;
|
|
2114
2243
|
return result;
|
|
2115
2244
|
} });
|
|
2116
2245
|
}
|
|
@@ -2164,23 +2293,27 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
|
2164
2293
|
* ])
|
|
2165
2294
|
* ```
|
|
2166
2295
|
*/
|
|
2167
|
-
function
|
|
2168
|
-
|
|
2296
|
+
function* mergeAdjacentObjectsLazy(members) {
|
|
2297
|
+
let acc;
|
|
2298
|
+
for (const member of members) {
|
|
2169
2299
|
const objectMember = narrowSchema(member, "object");
|
|
2170
|
-
if (objectMember && !objectMember.name) {
|
|
2171
|
-
const
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
...
|
|
2176
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2300
|
+
if (objectMember && !objectMember.name && acc !== void 0) {
|
|
2301
|
+
const accObject = narrowSchema(acc, "object");
|
|
2302
|
+
if (accObject && !accObject.name) {
|
|
2303
|
+
acc = createSchema({
|
|
2304
|
+
...accObject,
|
|
2305
|
+
properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2177
2306
|
});
|
|
2178
|
-
|
|
2307
|
+
continue;
|
|
2179
2308
|
}
|
|
2180
2309
|
}
|
|
2181
|
-
acc
|
|
2182
|
-
|
|
2183
|
-
}
|
|
2310
|
+
if (acc !== void 0) yield acc;
|
|
2311
|
+
acc = member;
|
|
2312
|
+
}
|
|
2313
|
+
if (acc !== void 0) yield acc;
|
|
2314
|
+
}
|
|
2315
|
+
function mergeAdjacentObjects(members) {
|
|
2316
|
+
return [...mergeAdjacentObjectsLazy(members)];
|
|
2184
2317
|
}
|
|
2185
2318
|
/**
|
|
2186
2319
|
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
@@ -2212,7 +2345,7 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2212
2345
|
const enumNode = narrowSchema(propNode, "enum");
|
|
2213
2346
|
if (enumNode?.primitive === "boolean") return {
|
|
2214
2347
|
...propNode,
|
|
2215
|
-
name:
|
|
2348
|
+
name: null
|
|
2216
2349
|
};
|
|
2217
2350
|
if (enumNode) return {
|
|
2218
2351
|
...propNode,
|
|
@@ -2225,12 +2358,14 @@ exports.caseParams = caseParams;
|
|
|
2225
2358
|
exports.childName = childName;
|
|
2226
2359
|
exports.collect = collect;
|
|
2227
2360
|
exports.collectImports = collectImports;
|
|
2361
|
+
exports.collectLazy = collectLazy;
|
|
2228
2362
|
exports.collectReferencedSchemaNames = collectReferencedSchemaNames;
|
|
2229
2363
|
exports.collectUsedSchemaNames = collectUsedSchemaNames;
|
|
2230
2364
|
exports.containsCircularRef = containsCircularRef;
|
|
2231
2365
|
exports.createArrowFunction = createArrowFunction;
|
|
2232
2366
|
exports.createBreak = createBreak;
|
|
2233
2367
|
exports.createConst = createConst;
|
|
2368
|
+
exports.createContent = createContent;
|
|
2234
2369
|
exports.createDiscriminantNode = createDiscriminantNode;
|
|
2235
2370
|
exports.createExport = createExport;
|
|
2236
2371
|
exports.createFile = createFile;
|
|
@@ -2248,18 +2383,23 @@ exports.createParameterGroup = createParameterGroup;
|
|
|
2248
2383
|
exports.createParamsType = createParamsType;
|
|
2249
2384
|
exports.createPrinterFactory = createPrinterFactory;
|
|
2250
2385
|
exports.createProperty = createProperty;
|
|
2386
|
+
exports.createRequestBody = createRequestBody;
|
|
2251
2387
|
exports.createResponse = createResponse;
|
|
2252
2388
|
exports.createSchema = createSchema;
|
|
2253
2389
|
exports.createSource = createSource;
|
|
2390
|
+
exports.createStreamInput = createStreamInput;
|
|
2254
2391
|
exports.createText = createText;
|
|
2255
2392
|
exports.createType = createType;
|
|
2256
2393
|
exports.definePrinter = definePrinter;
|
|
2394
|
+
exports.defineSchemaDialect = defineSchemaDialect;
|
|
2395
|
+
exports.dispatch = dispatch;
|
|
2257
2396
|
exports.enumPropName = enumPropName;
|
|
2258
2397
|
exports.extractRefName = extractRefName;
|
|
2259
2398
|
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
2260
2399
|
exports.findCircularSchemas = findCircularSchemas;
|
|
2261
2400
|
exports.findDiscriminator = findDiscriminator;
|
|
2262
2401
|
exports.httpMethods = httpMethods;
|
|
2402
|
+
exports.isHttpOperationNode = isHttpOperationNode;
|
|
2263
2403
|
exports.isInputNode = isInputNode;
|
|
2264
2404
|
exports.isOperationNode = isOperationNode;
|
|
2265
2405
|
exports.isOutputNode = isOutputNode;
|
|
@@ -2268,6 +2408,7 @@ exports.isSchemaNode = isSchemaNode;
|
|
|
2268
2408
|
exports.isStringType = isStringType;
|
|
2269
2409
|
exports.mediaTypes = mediaTypes;
|
|
2270
2410
|
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
2411
|
+
exports.mergeAdjacentObjectsLazy = mergeAdjacentObjectsLazy;
|
|
2271
2412
|
exports.narrowSchema = narrowSchema;
|
|
2272
2413
|
exports.nodeKinds = nodeKinds;
|
|
2273
2414
|
exports.resolveRefName = resolveRefName;
|
|
@@ -2278,6 +2419,7 @@ exports.simplifyUnion = simplifyUnion;
|
|
|
2278
2419
|
exports.syncOptionality = syncOptionality;
|
|
2279
2420
|
exports.syncSchemaRef = syncSchemaRef;
|
|
2280
2421
|
exports.transform = transform;
|
|
2422
|
+
exports.update = update;
|
|
2281
2423
|
exports.walk = walk;
|
|
2282
2424
|
|
|
2283
2425
|
//# sourceMappingURL=index.cjs.map
|