@kubb/ast 5.0.0-alpha.67 → 5.0.0-alpha.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -381,212 +381,527 @@ isKind("FunctionParameter");
381
381
  isKind("ParameterGroup");
382
382
  isKind("FunctionParameters");
383
383
  //#endregion
384
- //#region src/utils.ts
385
- const plainStringTypes = new Set([
386
- "string",
387
- "uuid",
388
- "email",
389
- "url",
390
- "datetime"
391
- ]);
384
+ //#region src/refs.ts
392
385
  /**
393
- * Returns a merged schema view for a ref node, combining the resolved `node.schema`
394
- * (base from the referenced definition) with any usage-site sibling fields set directly
395
- * on the ref node (description, readOnly, nullable, deprecated, etc.).
386
+ * Returns the last path segment of a reference string.
396
387
  *
397
- * Usage-site fields take precedence over the resolved schema's own fields when both are defined.
388
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
398
389
  *
399
- * For non-ref nodes the node itself is returned unchanged.
390
+ * @example
391
+ * ```ts
392
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
393
+ * ```
400
394
  */
401
- function syncSchemaRef(node) {
402
- const ref = narrowSchema(node, "ref");
403
- if (!ref) return node;
404
- if (!ref.schema) return node;
405
- const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
406
- const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
407
- return createSchema({
408
- ...ref.schema,
409
- ...definedOverrides
410
- });
395
+ function extractRefName(ref) {
396
+ return ref.split("/").at(-1) ?? ref;
411
397
  }
398
+ //#endregion
399
+ //#region src/visitor.ts
412
400
  /**
413
- * Returns `true` when a schema is emitted as a plain `string` type.
401
+ * Creates a small async concurrency limiter.
414
402
  *
415
- * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
416
- * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
403
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
417
404
  *
418
405
  * @example
419
406
  * ```ts
420
- * isStringType(createSchema({ type: 'uuid' })) // true
421
- * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
407
+ * const limit = createLimit(2)
408
+ * for (const task of [taskA, taskB, taskC]) {
409
+ * await limit(() => task())
410
+ * }
411
+ * // only 2 tasks run at the same time
422
412
  * ```
423
413
  */
424
- function isStringType(node) {
425
- if (plainStringTypes.has(node.type)) return true;
426
- const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
427
- if (temporal) return temporal.representation !== "date";
428
- return false;
414
+ function createLimit(concurrency) {
415
+ let active = 0;
416
+ const queue = [];
417
+ function next() {
418
+ if (active < concurrency && queue.length > 0) {
419
+ active++;
420
+ queue.shift()();
421
+ }
422
+ }
423
+ return function limit(fn) {
424
+ return new Promise((resolve, reject) => {
425
+ queue.push(() => {
426
+ Promise.resolve(fn()).then(resolve, reject).finally(() => {
427
+ active--;
428
+ next();
429
+ });
430
+ });
431
+ next();
432
+ });
433
+ };
429
434
  }
430
435
  /**
431
- * Applies casing rules to parameter names and returns a new parameter array.
432
- *
433
- * The input array is not mutated.
434
- * If `casing` is not set, the original array is returned unchanged.
436
+ * Returns the immediate traversable children of `node`.
435
437
  *
436
- * Use this before passing parameters to schema builders so that property keys
437
- * in generated output match the desired casing while preserving
438
- * `OperationNode.parameters` for other consumers.
438
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
439
+ * `additionalProperties`) are only included
440
+ * when `recurse` is `true`; shallow mode skips them.
439
441
  *
440
442
  * @example
441
443
  * ```ts
442
- * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
443
- * const cased = caseParams(params, 'camelcase')
444
- * // cased[0].name === 'petId'
444
+ * const children = getChildren(operationNode, true)
445
+ * // returns parameters, requestBody schema (if present), and responses
445
446
  * ```
446
447
  */
447
- function caseParams(params, casing) {
448
- if (!casing) return params;
449
- return params.map((param) => {
450
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
451
- return {
452
- ...param,
453
- name: transformed
454
- };
455
- });
448
+ function getChildren(node, recurse) {
449
+ switch (node.kind) {
450
+ case "Input": return [...node.schemas, ...node.operations];
451
+ case "Output": return [];
452
+ case "Operation": return [
453
+ ...node.parameters,
454
+ ...node.requestBody?.content?.flatMap((c) => c.schema ? [c.schema] : []) ?? [],
455
+ ...node.responses
456
+ ];
457
+ case "Schema": {
458
+ const children = [];
459
+ if (!recurse) return [];
460
+ if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
461
+ if ("items" in node && node.items) children.push(...node.items);
462
+ if ("members" in node && node.members) children.push(...node.members);
463
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
464
+ return children;
465
+ }
466
+ case "Property": return [node.schema];
467
+ case "Parameter": return [node.schema];
468
+ case "Response": return node.schema ? [node.schema] : [];
469
+ case "FunctionParameter":
470
+ case "ParameterGroup":
471
+ case "FunctionParameters":
472
+ case "Type": return [];
473
+ default: return [];
474
+ }
456
475
  }
457
476
  /**
458
- * Creates a single-property object schema used as a discriminator literal.
477
+ * Depth-first traversal for side effects. Visitor return values are ignored.
478
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
479
+ * (default: `WALK_CONCURRENCY`).
459
480
  *
460
481
  * @example
461
482
  * ```ts
462
- * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
463
- * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
483
+ * await walk(root, {
484
+ * operation(node) {
485
+ * console.log(node.operationId)
486
+ * },
487
+ * })
464
488
  * ```
465
- */
466
- function createDiscriminantNode({ propertyName, value }) {
467
- return createSchema({
468
- type: "object",
469
- primitive: "object",
470
- properties: [createProperty({
471
- name: propertyName,
472
- schema: createSchema({
473
- type: "enum",
474
- primitive: "string",
475
- enumValues: [value]
476
- }),
477
- required: true
478
- })]
479
- });
480
- }
481
- function resolveParamsType({ node, param, resolver }) {
482
- if (!resolver) return createParamsType({
483
- variant: "reference",
484
- name: param.schema.primitive ?? "unknown"
485
- });
486
- const individualName = resolver.resolveParamName(node, param);
487
- const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
488
- const groupResolvers = {
489
- path: resolver.resolvePathParamsName,
490
- query: resolver.resolveQueryParamsName,
491
- header: resolver.resolveHeaderParamsName
492
- };
493
- const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
494
- if (groupName && groupName !== individualName) return createParamsType({
495
- variant: "member",
496
- base: groupName,
497
- key: param.name
498
- });
499
- return createParamsType({
500
- variant: "reference",
501
- name: individualName
502
- });
503
- }
504
- /**
505
- * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
506
- *
507
- * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
508
- * type resolution and `extraParams` for plugin-specific trailing parameters.
509
489
  *
510
490
  * @example
511
491
  * ```ts
512
- * const params = createOperationParams(node, {
513
- * paramsType: 'inline',
514
- * pathParamsType: 'inline',
515
- * resolver: tsResolver,
516
- * extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
517
- * })
492
+ * // Visit only the current node
493
+ * await walk(root, { depth: 'shallow', root: () => {} })
518
494
  * ```
519
495
  */
520
- function createOperationParams(node, options) {
521
- const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
522
- const dataName = paramNames?.data ?? "data";
523
- const paramsName = paramNames?.params ?? "params";
524
- const headersName = paramNames?.headers ?? "headers";
525
- const pathName = paramNames?.path ?? "pathParams";
526
- const wrapType = (type) => createParamsType({
527
- variant: "reference",
528
- name: typeWrapper ? typeWrapper(type) : type
529
- });
530
- const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
531
- const casedParams = caseParams(node.parameters, paramsCasing);
532
- const pathParams = casedParams.filter((p) => p.in === "path");
533
- const queryParams = casedParams.filter((p) => p.in === "query");
534
- const headerParams = casedParams.filter((p) => p.in === "header");
535
- const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
536
- const bodyRequired = node.requestBody?.required ?? false;
537
- const queryGroupType = resolver ? resolveGroupType({
538
- node,
539
- params: queryParams,
540
- groupMethod: resolver.resolveQueryParamsName,
541
- resolver
542
- }) : void 0;
543
- const headerGroupType = resolver ? resolveGroupType({
544
- node,
545
- params: headerParams,
546
- groupMethod: resolver.resolveHeaderParamsName,
547
- resolver
548
- }) : void 0;
549
- const params = [];
550
- if (paramsType === "object") {
551
- const children = [
552
- ...pathParams.map((p) => {
553
- const type = resolveParamsType({
554
- node,
555
- param: p,
556
- resolver
557
- });
558
- return createFunctionParameter({
559
- name: p.name,
560
- type: wrapTypeNode(type),
561
- optional: !p.required
562
- });
563
- }),
564
- ...bodyType ? [createFunctionParameter({
565
- name: dataName,
566
- type: bodyType,
567
- optional: !bodyRequired
568
- })] : [],
569
- ...buildGroupParam({
570
- name: paramsName,
571
- node,
572
- params: queryParams,
573
- groupType: queryGroupType,
574
- resolver,
575
- wrapType
576
- }),
577
- ...buildGroupParam({
578
- name: headersName,
579
- node,
580
- params: headerParams,
581
- groupType: headerGroupType,
582
- resolver,
583
- wrapType
584
- })
585
- ];
586
- if (children.length) params.push(createParameterGroup({
587
- properties: children,
588
- default: children.every((c) => c.optional) ? "{}" : void 0
589
- }));
496
+ async function walk(node, options) {
497
+ return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
498
+ }
499
+ async function _walk(node, visitor, recurse, limit, parent) {
500
+ switch (node.kind) {
501
+ case "Input":
502
+ await limit(() => visitor.input?.(node, { parent }));
503
+ break;
504
+ case "Output":
505
+ await limit(() => visitor.output?.(node, { parent }));
506
+ break;
507
+ case "Operation":
508
+ await limit(() => visitor.operation?.(node, { parent }));
509
+ break;
510
+ case "Schema":
511
+ await limit(() => visitor.schema?.(node, { parent }));
512
+ break;
513
+ case "Property":
514
+ await limit(() => visitor.property?.(node, { parent }));
515
+ break;
516
+ case "Parameter":
517
+ await limit(() => visitor.parameter?.(node, { parent }));
518
+ break;
519
+ case "Response":
520
+ await limit(() => visitor.response?.(node, { parent }));
521
+ break;
522
+ case "FunctionParameter":
523
+ case "ParameterGroup":
524
+ case "FunctionParameters": break;
525
+ }
526
+ const children = getChildren(node, recurse);
527
+ for (const child of children) await _walk(child, visitor, recurse, limit, node);
528
+ }
529
+ function transform(node, options) {
530
+ const { depth, parent, ...visitor } = options;
531
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
532
+ switch (node.kind) {
533
+ case "Input": {
534
+ let input = node;
535
+ const replaced = visitor.input?.(input, { parent });
536
+ if (replaced) input = replaced;
537
+ return {
538
+ ...input,
539
+ schemas: input.schemas.map((s) => transform(s, {
540
+ ...options,
541
+ parent: input
542
+ })),
543
+ operations: input.operations.map((op) => transform(op, {
544
+ ...options,
545
+ parent: input
546
+ }))
547
+ };
548
+ }
549
+ case "Output": {
550
+ let output = node;
551
+ const replaced = visitor.output?.(output, { parent });
552
+ if (replaced) output = replaced;
553
+ return output;
554
+ }
555
+ case "Operation": {
556
+ let op = node;
557
+ const replaced = visitor.operation?.(op, { parent });
558
+ if (replaced) op = replaced;
559
+ return {
560
+ ...op,
561
+ parameters: op.parameters.map((p) => transform(p, {
562
+ ...options,
563
+ parent: op
564
+ })),
565
+ requestBody: op.requestBody ? {
566
+ ...op.requestBody,
567
+ content: op.requestBody.content?.map((c) => ({
568
+ ...c,
569
+ schema: c.schema ? transform(c.schema, {
570
+ ...options,
571
+ parent: op
572
+ }) : void 0
573
+ }))
574
+ } : void 0,
575
+ responses: op.responses.map((r) => transform(r, {
576
+ ...options,
577
+ parent: op
578
+ }))
579
+ };
580
+ }
581
+ case "Schema": {
582
+ let schema = node;
583
+ const replaced = visitor.schema?.(schema, { parent });
584
+ if (replaced) schema = replaced;
585
+ const childOptions = {
586
+ ...options,
587
+ parent: schema
588
+ };
589
+ return {
590
+ ...schema,
591
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
592
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
593
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
594
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
595
+ };
596
+ }
597
+ case "Property": {
598
+ let prop = node;
599
+ const replaced = visitor.property?.(prop, { parent });
600
+ if (replaced) prop = replaced;
601
+ return createProperty({
602
+ ...prop,
603
+ schema: transform(prop.schema, {
604
+ ...options,
605
+ parent: prop
606
+ })
607
+ });
608
+ }
609
+ case "Parameter": {
610
+ let param = node;
611
+ const replaced = visitor.parameter?.(param, { parent });
612
+ if (replaced) param = replaced;
613
+ return createParameter({
614
+ ...param,
615
+ schema: transform(param.schema, {
616
+ ...options,
617
+ parent: param
618
+ })
619
+ });
620
+ }
621
+ case "Response": {
622
+ let response = node;
623
+ const replaced = visitor.response?.(response, { parent });
624
+ if (replaced) response = replaced;
625
+ return {
626
+ ...response,
627
+ schema: transform(response.schema, {
628
+ ...options,
629
+ parent: response
630
+ })
631
+ };
632
+ }
633
+ case "FunctionParameter":
634
+ case "ParameterGroup":
635
+ case "FunctionParameters":
636
+ case "Type": return node;
637
+ default: return node;
638
+ }
639
+ }
640
+ /**
641
+ * Runs a depth-first synchronous collection pass.
642
+ *
643
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * const ids = collect(root, {
648
+ * operation(node) {
649
+ * return node.operationId
650
+ * },
651
+ * })
652
+ * ```
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * // Collect from only the current node
657
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
658
+ * ```
659
+ */
660
+ function collect(node, options) {
661
+ const { depth, parent, ...visitor } = options;
662
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
663
+ const results = [];
664
+ let v;
665
+ switch (node.kind) {
666
+ case "Input":
667
+ v = visitor.input?.(node, { parent });
668
+ break;
669
+ case "Output":
670
+ v = visitor.output?.(node, { parent });
671
+ break;
672
+ case "Operation":
673
+ v = visitor.operation?.(node, { parent });
674
+ break;
675
+ case "Schema":
676
+ v = visitor.schema?.(node, { parent });
677
+ break;
678
+ case "Property":
679
+ v = visitor.property?.(node, { parent });
680
+ break;
681
+ case "Parameter":
682
+ v = visitor.parameter?.(node, { parent });
683
+ break;
684
+ case "Response":
685
+ v = visitor.response?.(node, { parent });
686
+ break;
687
+ case "FunctionParameter":
688
+ case "ParameterGroup":
689
+ case "FunctionParameters": break;
690
+ }
691
+ if (v !== void 0) results.push(v);
692
+ for (const child of getChildren(node, recurse)) for (const item of collect(child, {
693
+ ...options,
694
+ parent: node
695
+ })) results.push(item);
696
+ return results;
697
+ }
698
+ //#endregion
699
+ //#region src/utils.ts
700
+ const plainStringTypes = new Set([
701
+ "string",
702
+ "uuid",
703
+ "email",
704
+ "url",
705
+ "datetime"
706
+ ]);
707
+ /**
708
+ * Returns a merged schema view for a ref node, combining the resolved `node.schema`
709
+ * (base from the referenced definition) with any usage-site sibling fields set directly
710
+ * on the ref node (description, readOnly, nullable, deprecated, etc.).
711
+ *
712
+ * Usage-site fields take precedence over the resolved schema's own fields when both are defined.
713
+ *
714
+ * For non-ref nodes the node itself is returned unchanged.
715
+ */
716
+ function syncSchemaRef(node) {
717
+ const ref = narrowSchema(node, "ref");
718
+ if (!ref) return node;
719
+ if (!ref.schema) return node;
720
+ const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
721
+ const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
722
+ return createSchema({
723
+ ...ref.schema,
724
+ ...definedOverrides
725
+ });
726
+ }
727
+ /**
728
+ * Returns `true` when a schema is emitted as a plain `string` type.
729
+ *
730
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
731
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
732
+ *
733
+ * @example
734
+ * ```ts
735
+ * isStringType(createSchema({ type: 'uuid' })) // true
736
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
737
+ * ```
738
+ */
739
+ function isStringType(node) {
740
+ if (plainStringTypes.has(node.type)) return true;
741
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
742
+ if (temporal) return temporal.representation !== "date";
743
+ return false;
744
+ }
745
+ /**
746
+ * Applies casing rules to parameter names and returns a new parameter array.
747
+ *
748
+ * The input array is not mutated.
749
+ * If `casing` is not set, the original array is returned unchanged.
750
+ *
751
+ * Use this before passing parameters to schema builders so that property keys
752
+ * in generated output match the desired casing while preserving
753
+ * `OperationNode.parameters` for other consumers.
754
+ *
755
+ * @example
756
+ * ```ts
757
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
758
+ * const cased = caseParams(params, 'camelcase')
759
+ * // cased[0].name === 'petId'
760
+ * ```
761
+ */
762
+ function caseParams(params, casing) {
763
+ if (!casing) return params;
764
+ return params.map((param) => {
765
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
766
+ return {
767
+ ...param,
768
+ name: transformed
769
+ };
770
+ });
771
+ }
772
+ /**
773
+ * Creates a single-property object schema used as a discriminator literal.
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
778
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
779
+ * ```
780
+ */
781
+ function createDiscriminantNode({ propertyName, value }) {
782
+ return createSchema({
783
+ type: "object",
784
+ primitive: "object",
785
+ properties: [createProperty({
786
+ name: propertyName,
787
+ schema: createSchema({
788
+ type: "enum",
789
+ primitive: "string",
790
+ enumValues: [value]
791
+ }),
792
+ required: true
793
+ })]
794
+ });
795
+ }
796
+ function resolveParamsType({ node, param, resolver }) {
797
+ if (!resolver) return createParamsType({
798
+ variant: "reference",
799
+ name: param.schema.primitive ?? "unknown"
800
+ });
801
+ const individualName = resolver.resolveParamName(node, param);
802
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
803
+ const groupResolvers = {
804
+ path: resolver.resolvePathParamsName,
805
+ query: resolver.resolveQueryParamsName,
806
+ header: resolver.resolveHeaderParamsName
807
+ };
808
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
809
+ if (groupName && groupName !== individualName) return createParamsType({
810
+ variant: "member",
811
+ base: groupName,
812
+ key: param.name
813
+ });
814
+ return createParamsType({
815
+ variant: "reference",
816
+ name: individualName
817
+ });
818
+ }
819
+ /**
820
+ * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
821
+ *
822
+ * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
823
+ * type resolution and `extraParams` for plugin-specific trailing parameters.
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * const params = createOperationParams(node, {
828
+ * paramsType: 'inline',
829
+ * pathParamsType: 'inline',
830
+ * resolver: tsResolver,
831
+ * extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
832
+ * })
833
+ * ```
834
+ */
835
+ function createOperationParams(node, options) {
836
+ const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
837
+ const dataName = paramNames?.data ?? "data";
838
+ const paramsName = paramNames?.params ?? "params";
839
+ const headersName = paramNames?.headers ?? "headers";
840
+ const pathName = paramNames?.path ?? "pathParams";
841
+ const wrapType = (type) => createParamsType({
842
+ variant: "reference",
843
+ name: typeWrapper ? typeWrapper(type) : type
844
+ });
845
+ const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
846
+ const casedParams = caseParams(node.parameters, paramsCasing);
847
+ const pathParams = casedParams.filter((p) => p.in === "path");
848
+ const queryParams = casedParams.filter((p) => p.in === "query");
849
+ const headerParams = casedParams.filter((p) => p.in === "header");
850
+ const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
851
+ const bodyRequired = node.requestBody?.required ?? false;
852
+ const queryGroupType = resolver ? resolveGroupType({
853
+ node,
854
+ params: queryParams,
855
+ groupMethod: resolver.resolveQueryParamsName,
856
+ resolver
857
+ }) : void 0;
858
+ const headerGroupType = resolver ? resolveGroupType({
859
+ node,
860
+ params: headerParams,
861
+ groupMethod: resolver.resolveHeaderParamsName,
862
+ resolver
863
+ }) : void 0;
864
+ const params = [];
865
+ if (paramsType === "object") {
866
+ const children = [
867
+ ...pathParams.map((p) => {
868
+ const type = resolveParamsType({
869
+ node,
870
+ param: p,
871
+ resolver
872
+ });
873
+ return createFunctionParameter({
874
+ name: p.name,
875
+ type: wrapTypeNode(type),
876
+ optional: !p.required
877
+ });
878
+ }),
879
+ ...bodyType ? [createFunctionParameter({
880
+ name: dataName,
881
+ type: bodyType,
882
+ optional: !bodyRequired
883
+ })] : [],
884
+ ...buildGroupParam({
885
+ name: paramsName,
886
+ node,
887
+ params: queryParams,
888
+ groupType: queryGroupType,
889
+ resolver,
890
+ wrapType
891
+ }),
892
+ ...buildGroupParam({
893
+ name: headersName,
894
+ node,
895
+ params: headerParams,
896
+ groupType: headerGroupType,
897
+ resolver,
898
+ wrapType
899
+ })
900
+ ];
901
+ if (children.length) params.push(createParameterGroup({
902
+ properties: children,
903
+ default: children.every((c) => c.optional) ? "{}" : void 0
904
+ }));
590
905
  } else {
591
906
  if (pathParams.length) if (pathParamsType === "inlineSpread") {
592
907
  const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
@@ -850,6 +1165,114 @@ function extractStringsFromNodes(nodes) {
850
1165
  return parts.join("\n");
851
1166
  }).filter(Boolean).join("\n");
852
1167
  }
1168
+ /**
1169
+ * Resolves the referenced schema name of a `ref` node, falling back through
1170
+ * `ref` → `name` → nested `schema.name`. Returns `undefined` for non-ref
1171
+ * nodes or when no name can be resolved.
1172
+ *
1173
+ * @example
1174
+ * ```ts
1175
+ * resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' })
1176
+ * // => 'Pet'
1177
+ * ```
1178
+ */
1179
+ function resolveRefName(node) {
1180
+ if (!node || node.type !== "ref") return void 0;
1181
+ if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? void 0;
1182
+ return node.name ?? node.schema?.name ?? void 0;
1183
+ }
1184
+ /**
1185
+ * Recursively collects every named schema referenced (transitively) from
1186
+ * `node` via `ref` edges. Refs are followed by name only — the resolved
1187
+ * `node.schema` of a ref is not traversed inline.
1188
+ *
1189
+ * @example
1190
+ * ```ts
1191
+ * const refs = collectReferencedSchemaNames(petSchema)
1192
+ * // => Set { 'Cat', 'Dog' }
1193
+ * ```
1194
+ */
1195
+ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1196
+ if (!node) return out;
1197
+ collect(node, { schema(child) {
1198
+ if (child.type === "ref") {
1199
+ const name = resolveRefName(child);
1200
+ if (name) out.add(name);
1201
+ }
1202
+ } });
1203
+ return out;
1204
+ }
1205
+ /**
1206
+ * Identifies every named schema that participates in a circular dependency
1207
+ * chain — including direct self-loops (e.g. `TreeNode → TreeNode`) and indirect
1208
+ * cycles spanning multiple schemas (e.g. `Pet → Cat → Pet`).
1209
+ *
1210
+ * The returned set contains schema names. Plugins that translate schemas into
1211
+ * a host language can use this to wrap recursive positions in a deferred
1212
+ * construct (lazy getter, `z.lazy(() => …)`, etc.) and avoid runtime stack
1213
+ * overflows when the generated code is executed.
1214
+ *
1215
+ * Refs are followed by name only — `node.schema` (the resolved referent) is
1216
+ * not traversed inline, which keeps the algorithm linear in the size of the
1217
+ * schema graph.
1218
+ *
1219
+ * @example
1220
+ * ```ts
1221
+ * const circular = findCircularSchemas(inputNode.schemas)
1222
+ * if (circular.has('Pet')) {
1223
+ * // emit lazy wrapper for any property whose schema references Pet
1224
+ * }
1225
+ * ```
1226
+ */
1227
+ function findCircularSchemas(schemas) {
1228
+ const graph = /* @__PURE__ */ new Map();
1229
+ for (const schema of schemas) {
1230
+ if (!schema.name) continue;
1231
+ graph.set(schema.name, collectReferencedSchemaNames(schema));
1232
+ }
1233
+ const circular = /* @__PURE__ */ new Set();
1234
+ for (const start of graph.keys()) {
1235
+ const visited = /* @__PURE__ */ new Set();
1236
+ const stack = [...graph.get(start) ?? []];
1237
+ while (stack.length > 0) {
1238
+ const node = stack.pop();
1239
+ if (node === start) {
1240
+ circular.add(start);
1241
+ break;
1242
+ }
1243
+ if (visited.has(node)) continue;
1244
+ visited.add(node);
1245
+ const next = graph.get(node);
1246
+ if (next) for (const r of next) stack.push(r);
1247
+ }
1248
+ }
1249
+ return circular;
1250
+ }
1251
+ /**
1252
+ * Returns true when `node` (or anything nested within it) carries a `ref`
1253
+ * whose resolved name belongs to `circularSchemas`.
1254
+ *
1255
+ * When `excludeName` is provided, refs to that name are ignored — useful
1256
+ * when self-references are already handled separately from cross-schema
1257
+ * cycles (e.g. the faker plugin emits `undefined as any` for direct
1258
+ * self-recursion but a lazy getter for indirect cycles).
1259
+ *
1260
+ * @example
1261
+ * ```ts
1262
+ * const circular = findCircularSchemas(schemas)
1263
+ * if (containsCircularRef(property.schema, { circularSchemas: circular, excludeName: 'Pet' })) {
1264
+ * // emit `get foo() { return fakeCat() }` instead of eager call
1265
+ * }
1266
+ * ```
1267
+ */
1268
+ function containsCircularRef(node, { circularSchemas, excludeName }) {
1269
+ if (!node || circularSchemas.size === 0) return false;
1270
+ return collect(node, { schema(child) {
1271
+ if (child.type !== "ref") return void 0;
1272
+ const name = resolveRefName(child);
1273
+ return name && name !== excludeName && circularSchemas.has(name) ? true : void 0;
1274
+ } }).length > 0;
1275
+ }
853
1276
  //#endregion
854
1277
  //#region src/factory.ts
855
1278
  /**
@@ -1318,545 +1741,230 @@ function createFile(input) {
1318
1741
  */
1319
1742
  function createConst(props) {
1320
1743
  return {
1321
- ...props,
1322
- kind: "Const"
1323
- };
1324
- }
1325
- /**
1326
- * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
1327
- *
1328
- * Mirrors the `Type` component from `@kubb/renderer-jsx`.
1329
- * The component's `children` are represented as `nodes`.
1330
- *
1331
- * @example Simple type alias
1332
- * ```ts
1333
- * createType({ name: 'Pet' })
1334
- * // type Pet = ...
1335
- * ```
1336
- *
1337
- * @example Exported type with JSDoc
1338
- * ```ts
1339
- * createType({
1340
- * name: 'PetStatus',
1341
- * export: true,
1342
- * JSDoc: { comments: ['@description Status of a pet'] },
1343
- * })
1344
- * // export type PetStatus = ...
1345
- * ```
1346
- */
1347
- function createType(props) {
1348
- return {
1349
- ...props,
1350
- kind: "Type"
1351
- };
1352
- }
1353
- /**
1354
- * Creates a `FunctionNode` representing a TypeScript `function` declaration.
1355
- *
1356
- * Mirrors the `Function` component from `@kubb/renderer-jsx`.
1357
- * The component's `children` are represented as `nodes`.
1358
- *
1359
- * @example Simple function
1360
- * ```ts
1361
- * createFunction({ name: 'getPet' })
1362
- * // function getPet() { ... }
1363
- * ```
1364
- *
1365
- * @example Exported async function with return type
1366
- * ```ts
1367
- * createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
1368
- * // export async function fetchPet(): Promise<Pet> { ... }
1369
- * ```
1370
- *
1371
- * @example Function with generics and params
1372
- * ```ts
1373
- * createFunction({
1374
- * name: 'identity',
1375
- * export: true,
1376
- * generics: ['T'],
1377
- * params: 'value: T',
1378
- * returnType: 'T',
1379
- * })
1380
- * // export function identity<T>(value: T): T { ... }
1381
- * ```
1382
- */
1383
- function createFunction(props) {
1384
- return {
1385
- ...props,
1386
- kind: "Function"
1387
- };
1388
- }
1389
- /**
1390
- * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
1391
- *
1392
- * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
1393
- * The component's `children` are represented as `nodes`.
1394
- *
1395
- * @example Simple arrow function
1396
- * ```ts
1397
- * createArrowFunction({ name: 'getPet' })
1398
- * // const getPet = () => { ... }
1399
- * ```
1400
- *
1401
- * @example Single-line exported arrow function
1402
- * ```ts
1403
- * createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
1404
- * // export const double = (n: number) => ...
1405
- * ```
1406
- *
1407
- * @example Async arrow function with generics
1408
- * ```ts
1409
- * createArrowFunction({
1410
- * name: 'fetchPet',
1411
- * export: true,
1412
- * async: true,
1413
- * generics: ['T'],
1414
- * params: 'id: string',
1415
- * returnType: 'T',
1416
- * })
1417
- * // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
1418
- * ```
1419
- */
1420
- function createArrowFunction(props) {
1421
- return {
1422
- ...props,
1423
- kind: "ArrowFunction"
1424
- };
1425
- }
1426
- /**
1427
- * Creates a {@link TextNode} representing a raw string fragment in the source output.
1428
- *
1429
- * Use this instead of bare strings when building `nodes` arrays so that every
1430
- * entry in the array is a typed {@link CodeNode}.
1431
- *
1432
- * @example
1433
- * ```ts
1434
- * createText('return fetch(id)')
1435
- * // { kind: 'Text', value: 'return fetch(id)' }
1436
- * ```
1437
- */
1438
- function createText(value) {
1439
- return {
1440
- value,
1441
- kind: "Text"
1442
- };
1443
- }
1444
- /**
1445
- * Creates a {@link BreakNode} representing a line break in the source output.
1446
- *
1447
- * Corresponds to `<br/>` in JSX components. Prints as an empty string which,
1448
- * when joined with `\n` by `printNodes`, produces a blank line.
1449
- *
1450
- * @example
1451
- * ```ts
1452
- * createBreak()
1453
- * // { kind: 'Break' }
1454
- * ```
1455
- */
1456
- function createBreak() {
1457
- return { kind: "Break" };
1458
- }
1459
- /**
1460
- * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
1461
- *
1462
- * Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
1463
- *
1464
- * @example
1465
- * ```ts
1466
- * createJsx('<>\n <a href={href}>Open</a>\n</>')
1467
- * // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
1468
- * ```
1469
- */
1470
- function createJsx(value) {
1471
- return {
1472
- value,
1473
- kind: "Jsx"
1744
+ ...props,
1745
+ kind: "Const"
1474
1746
  };
1475
1747
  }
1476
- //#endregion
1477
- //#region src/printer.ts
1478
1748
  /**
1479
- * Creates a schema printer factory.
1480
- *
1481
- * This function wraps a builder and makes options optional at call sites.
1482
- *
1483
- * The builder receives resolved options and returns:
1484
- * - `name` — a unique identifier for the printer
1485
- * - `options` — options stored on the returned printer instance
1486
- * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
1487
- * - `print` _(optional)_ — top-level override exposed as `printer.print`
1488
- * - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
1489
- * - This keeps recursion safe and avoids self-calls
1749
+ * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
1490
1750
  *
1491
- * When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
1751
+ * Mirrors the `Type` component from `@kubb/renderer-jsx`.
1752
+ * The component's `children` are represented as `nodes`.
1492
1753
  *
1493
- * @example Basic usage — Zod schema printer
1754
+ * @example Simple type alias
1494
1755
  * ```ts
1495
- * type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
1496
- *
1497
- * export const zodPrinter = definePrinter<PrinterZod>((options) => ({
1498
- * name: 'zod',
1499
- * options: { strict: options.strict ?? true },
1500
- * nodes: {
1501
- * string: () => 'z.string()',
1502
- * object(node) {
1503
- * const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
1504
- * return `z.object({ ${props} })`
1505
- * },
1506
- * },
1507
- * }))
1756
+ * createType({ name: 'Pet' })
1757
+ * // type Pet = ...
1508
1758
  * ```
1509
- */
1510
- function definePrinter(build) {
1511
- return createPrinterFactory((node) => node.type)(build);
1512
- }
1513
- /**
1514
- * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
1515
- **
1516
- * @example
1759
+ *
1760
+ * @example Exported type with JSDoc
1517
1761
  * ```ts
1518
- * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
1519
- * (node) => kindToHandlerKey[node.kind],
1520
- * )
1762
+ * createType({
1763
+ * name: 'PetStatus',
1764
+ * export: true,
1765
+ * JSDoc: { comments: ['@description Status of a pet'] },
1766
+ * })
1767
+ * // export type PetStatus = ...
1521
1768
  * ```
1522
1769
  */
1523
- function createPrinterFactory(getKey) {
1524
- return function(build) {
1525
- return (options) => {
1526
- const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
1527
- const context = {
1528
- options: resolvedOptions,
1529
- transform: (node) => {
1530
- const key = getKey(node);
1531
- if (key === void 0) return null;
1532
- const handler = nodes[key];
1533
- if (!handler) return null;
1534
- return handler.call(context, node);
1535
- }
1536
- };
1537
- return {
1538
- name,
1539
- options: resolvedOptions,
1540
- transform: context.transform,
1541
- print: printOverride ? printOverride.bind(context) : context.transform
1542
- };
1543
- };
1770
+ function createType(props) {
1771
+ return {
1772
+ ...props,
1773
+ kind: "Type"
1544
1774
  };
1545
1775
  }
1546
- //#endregion
1547
- //#region src/refs.ts
1548
1776
  /**
1549
- * Returns the last path segment of a reference string.
1777
+ * Creates a `FunctionNode` representing a TypeScript `function` declaration.
1550
1778
  *
1551
- * Example: `#/components/schemas/Pet` becomes `Pet`.
1779
+ * Mirrors the `Function` component from `@kubb/renderer-jsx`.
1780
+ * The component's `children` are represented as `nodes`.
1552
1781
  *
1553
- * @example
1782
+ * @example Simple function
1554
1783
  * ```ts
1555
- * extractRefName('#/components/schemas/Pet') // 'Pet'
1784
+ * createFunction({ name: 'getPet' })
1785
+ * // function getPet() { ... }
1556
1786
  * ```
1557
- */
1558
- function extractRefName(ref) {
1559
- return ref.split("/").at(-1) ?? ref;
1560
- }
1561
- //#endregion
1562
- //#region src/visitor.ts
1563
- /**
1564
- * Creates a small async concurrency limiter.
1565
1787
  *
1566
- * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
1788
+ * @example Exported async function with return type
1789
+ * ```ts
1790
+ * createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
1791
+ * // export async function fetchPet(): Promise<Pet> { ... }
1792
+ * ```
1567
1793
  *
1568
- * @example
1794
+ * @example Function with generics and params
1569
1795
  * ```ts
1570
- * const limit = createLimit(2)
1571
- * for (const task of [taskA, taskB, taskC]) {
1572
- * await limit(() => task())
1573
- * }
1574
- * // only 2 tasks run at the same time
1796
+ * createFunction({
1797
+ * name: 'identity',
1798
+ * export: true,
1799
+ * generics: ['T'],
1800
+ * params: 'value: T',
1801
+ * returnType: 'T',
1802
+ * })
1803
+ * // export function identity<T>(value: T): T { ... }
1575
1804
  * ```
1576
1805
  */
1577
- function createLimit(concurrency) {
1578
- let active = 0;
1579
- const queue = [];
1580
- function next() {
1581
- if (active < concurrency && queue.length > 0) {
1582
- active++;
1583
- queue.shift()();
1584
- }
1585
- }
1586
- return function limit(fn) {
1587
- return new Promise((resolve, reject) => {
1588
- queue.push(() => {
1589
- Promise.resolve(fn()).then(resolve, reject).finally(() => {
1590
- active--;
1591
- next();
1592
- });
1593
- });
1594
- next();
1595
- });
1806
+ function createFunction(props) {
1807
+ return {
1808
+ ...props,
1809
+ kind: "Function"
1596
1810
  };
1597
1811
  }
1598
1812
  /**
1599
- * Returns the immediate traversable children of `node`.
1813
+ * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
1600
1814
  *
1601
- * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
1602
- * `additionalProperties`) are only included
1603
- * when `recurse` is `true`; shallow mode skips them.
1815
+ * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
1816
+ * The component's `children` are represented as `nodes`.
1604
1817
  *
1605
- * @example
1818
+ * @example Simple arrow function
1606
1819
  * ```ts
1607
- * const children = getChildren(operationNode, true)
1608
- * // returns parameters, requestBody schema (if present), and responses
1820
+ * createArrowFunction({ name: 'getPet' })
1821
+ * // const getPet = () => { ... }
1609
1822
  * ```
1610
- */
1611
- function getChildren(node, recurse) {
1612
- switch (node.kind) {
1613
- case "Input": return [...node.schemas, ...node.operations];
1614
- case "Output": return [];
1615
- case "Operation": return [
1616
- ...node.parameters,
1617
- ...node.requestBody?.content?.flatMap((c) => c.schema ? [c.schema] : []) ?? [],
1618
- ...node.responses
1619
- ];
1620
- case "Schema": {
1621
- const children = [];
1622
- if (!recurse) return [];
1623
- if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
1624
- if ("items" in node && node.items) children.push(...node.items);
1625
- if ("members" in node && node.members) children.push(...node.members);
1626
- if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1627
- return children;
1628
- }
1629
- case "Property": return [node.schema];
1630
- case "Parameter": return [node.schema];
1631
- case "Response": return node.schema ? [node.schema] : [];
1632
- case "FunctionParameter":
1633
- case "ParameterGroup":
1634
- case "FunctionParameters":
1635
- case "Type": return [];
1636
- default: return [];
1637
- }
1638
- }
1639
- /**
1640
- * Depth-first traversal for side effects. Visitor return values are ignored.
1641
- * Sibling nodes at each level are visited concurrently up to `options.concurrency`
1642
- * (default: `WALK_CONCURRENCY`).
1643
1823
  *
1644
- * @example
1824
+ * @example Single-line exported arrow function
1645
1825
  * ```ts
1646
- * await walk(root, {
1647
- * operation(node) {
1648
- * console.log(node.operationId)
1649
- * },
1650
- * })
1826
+ * createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
1827
+ * // export const double = (n: number) => ...
1651
1828
  * ```
1652
1829
  *
1653
- * @example
1830
+ * @example Async arrow function with generics
1654
1831
  * ```ts
1655
- * // Visit only the current node
1656
- * await walk(root, { depth: 'shallow', root: () => {} })
1832
+ * createArrowFunction({
1833
+ * name: 'fetchPet',
1834
+ * export: true,
1835
+ * async: true,
1836
+ * generics: ['T'],
1837
+ * params: 'id: string',
1838
+ * returnType: 'T',
1839
+ * })
1840
+ * // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
1657
1841
  * ```
1658
1842
  */
1659
- async function walk(node, options) {
1660
- return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
1843
+ function createArrowFunction(props) {
1844
+ return {
1845
+ ...props,
1846
+ kind: "ArrowFunction"
1847
+ };
1661
1848
  }
1662
- async function _walk(node, visitor, recurse, limit, parent) {
1663
- switch (node.kind) {
1664
- case "Input":
1665
- await limit(() => visitor.input?.(node, { parent }));
1666
- break;
1667
- case "Output":
1668
- await limit(() => visitor.output?.(node, { parent }));
1669
- break;
1670
- case "Operation":
1671
- await limit(() => visitor.operation?.(node, { parent }));
1672
- break;
1673
- case "Schema":
1674
- await limit(() => visitor.schema?.(node, { parent }));
1675
- break;
1676
- case "Property":
1677
- await limit(() => visitor.property?.(node, { parent }));
1678
- break;
1679
- case "Parameter":
1680
- await limit(() => visitor.parameter?.(node, { parent }));
1681
- break;
1682
- case "Response":
1683
- await limit(() => visitor.response?.(node, { parent }));
1684
- break;
1685
- case "FunctionParameter":
1686
- case "ParameterGroup":
1687
- case "FunctionParameters": break;
1688
- }
1689
- const children = getChildren(node, recurse);
1690
- for (const child of children) await _walk(child, visitor, recurse, limit, node);
1849
+ /**
1850
+ * Creates a {@link TextNode} representing a raw string fragment in the source output.
1851
+ *
1852
+ * Use this instead of bare strings when building `nodes` arrays so that every
1853
+ * entry in the array is a typed {@link CodeNode}.
1854
+ *
1855
+ * @example
1856
+ * ```ts
1857
+ * createText('return fetch(id)')
1858
+ * // { kind: 'Text', value: 'return fetch(id)' }
1859
+ * ```
1860
+ */
1861
+ function createText(value) {
1862
+ return {
1863
+ value,
1864
+ kind: "Text"
1865
+ };
1691
1866
  }
1692
- function transform(node, options) {
1693
- const { depth, parent, ...visitor } = options;
1694
- const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
1695
- switch (node.kind) {
1696
- case "Input": {
1697
- let input = node;
1698
- const replaced = visitor.input?.(input, { parent });
1699
- if (replaced) input = replaced;
1700
- return {
1701
- ...input,
1702
- schemas: input.schemas.map((s) => transform(s, {
1703
- ...options,
1704
- parent: input
1705
- })),
1706
- operations: input.operations.map((op) => transform(op, {
1707
- ...options,
1708
- parent: input
1709
- }))
1710
- };
1711
- }
1712
- case "Output": {
1713
- let output = node;
1714
- const replaced = visitor.output?.(output, { parent });
1715
- if (replaced) output = replaced;
1716
- return output;
1717
- }
1718
- case "Operation": {
1719
- let op = node;
1720
- const replaced = visitor.operation?.(op, { parent });
1721
- if (replaced) op = replaced;
1722
- return {
1723
- ...op,
1724
- parameters: op.parameters.map((p) => transform(p, {
1725
- ...options,
1726
- parent: op
1727
- })),
1728
- requestBody: op.requestBody ? {
1729
- ...op.requestBody,
1730
- content: op.requestBody.content?.map((c) => ({
1731
- ...c,
1732
- schema: c.schema ? transform(c.schema, {
1733
- ...options,
1734
- parent: op
1735
- }) : void 0
1736
- }))
1737
- } : void 0,
1738
- responses: op.responses.map((r) => transform(r, {
1739
- ...options,
1740
- parent: op
1741
- }))
1742
- };
1743
- }
1744
- case "Schema": {
1745
- let schema = node;
1746
- const replaced = visitor.schema?.(schema, { parent });
1747
- if (replaced) schema = replaced;
1748
- const childOptions = {
1749
- ...options,
1750
- parent: schema
1751
- };
1752
- return {
1753
- ...schema,
1754
- ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
1755
- ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
1756
- ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
1757
- ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
1758
- };
1759
- }
1760
- case "Property": {
1761
- let prop = node;
1762
- const replaced = visitor.property?.(prop, { parent });
1763
- if (replaced) prop = replaced;
1764
- return createProperty({
1765
- ...prop,
1766
- schema: transform(prop.schema, {
1767
- ...options,
1768
- parent: prop
1769
- })
1770
- });
1771
- }
1772
- case "Parameter": {
1773
- let param = node;
1774
- const replaced = visitor.parameter?.(param, { parent });
1775
- if (replaced) param = replaced;
1776
- return createParameter({
1777
- ...param,
1778
- schema: transform(param.schema, {
1779
- ...options,
1780
- parent: param
1781
- })
1782
- });
1783
- }
1784
- case "Response": {
1785
- let response = node;
1786
- const replaced = visitor.response?.(response, { parent });
1787
- if (replaced) response = replaced;
1788
- return {
1789
- ...response,
1790
- schema: transform(response.schema, {
1791
- ...options,
1792
- parent: response
1793
- })
1794
- };
1795
- }
1796
- case "FunctionParameter":
1797
- case "ParameterGroup":
1798
- case "FunctionParameters":
1799
- case "Type": return node;
1800
- default: return node;
1801
- }
1867
+ /**
1868
+ * Creates a {@link BreakNode} representing a line break in the source output.
1869
+ *
1870
+ * Corresponds to `<br/>` in JSX components. Prints as an empty string which,
1871
+ * when joined with `\n` by `printNodes`, produces a blank line.
1872
+ *
1873
+ * @example
1874
+ * ```ts
1875
+ * createBreak()
1876
+ * // { kind: 'Break' }
1877
+ * ```
1878
+ */
1879
+ function createBreak() {
1880
+ return { kind: "Break" };
1802
1881
  }
1803
1882
  /**
1804
- * Runs a depth-first synchronous collection pass.
1883
+ * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
1805
1884
  *
1806
- * Non-`undefined` values returned by visitor callbacks are appended to the result.
1885
+ * Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
1807
1886
  *
1808
1887
  * @example
1809
1888
  * ```ts
1810
- * const ids = collect(root, {
1811
- * operation(node) {
1812
- * return node.operationId
1813
- * },
1814
- * })
1889
+ * createJsx('<>\n <a href={href}>Open</a>\n</>')
1890
+ * // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
1815
1891
  * ```
1892
+ */
1893
+ function createJsx(value) {
1894
+ return {
1895
+ value,
1896
+ kind: "Jsx"
1897
+ };
1898
+ }
1899
+ //#endregion
1900
+ //#region src/printer.ts
1901
+ /**
1902
+ * Creates a schema printer factory.
1903
+ *
1904
+ * This function wraps a builder and makes options optional at call sites.
1905
+ *
1906
+ * The builder receives resolved options and returns:
1907
+ * - `name` — a unique identifier for the printer
1908
+ * - `options` — options stored on the returned printer instance
1909
+ * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
1910
+ * - `print` _(optional)_ — top-level override exposed as `printer.print`
1911
+ * - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
1912
+ * - This keeps recursion safe and avoids self-calls
1913
+ *
1914
+ * When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
1915
+ *
1916
+ * @example Basic usage — Zod schema printer
1917
+ * ```ts
1918
+ * type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
1816
1919
  *
1920
+ * export const zodPrinter = definePrinter<PrinterZod>((options) => ({
1921
+ * name: 'zod',
1922
+ * options: { strict: options.strict ?? true },
1923
+ * nodes: {
1924
+ * string: () => 'z.string()',
1925
+ * object(node) {
1926
+ * const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
1927
+ * return `z.object({ ${props} })`
1928
+ * },
1929
+ * },
1930
+ * }))
1931
+ * ```
1932
+ */
1933
+ function definePrinter(build) {
1934
+ return createPrinterFactory((node) => node.type)(build);
1935
+ }
1936
+ /**
1937
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
1938
+ **
1817
1939
  * @example
1818
1940
  * ```ts
1819
- * // Collect from only the current node
1820
- * const values = collect(root, { depth: 'shallow', root: () => 'root' })
1941
+ * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
1942
+ * (node) => kindToHandlerKey[node.kind],
1943
+ * )
1821
1944
  * ```
1822
1945
  */
1823
- function collect(node, options) {
1824
- const { depth, parent, ...visitor } = options;
1825
- const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
1826
- const results = [];
1827
- let v;
1828
- switch (node.kind) {
1829
- case "Input":
1830
- v = visitor.input?.(node, { parent });
1831
- break;
1832
- case "Output":
1833
- v = visitor.output?.(node, { parent });
1834
- break;
1835
- case "Operation":
1836
- v = visitor.operation?.(node, { parent });
1837
- break;
1838
- case "Schema":
1839
- v = visitor.schema?.(node, { parent });
1840
- break;
1841
- case "Property":
1842
- v = visitor.property?.(node, { parent });
1843
- break;
1844
- case "Parameter":
1845
- v = visitor.parameter?.(node, { parent });
1846
- break;
1847
- case "Response":
1848
- v = visitor.response?.(node, { parent });
1849
- break;
1850
- case "FunctionParameter":
1851
- case "ParameterGroup":
1852
- case "FunctionParameters": break;
1853
- }
1854
- if (v !== void 0) results.push(v);
1855
- for (const child of getChildren(node, recurse)) for (const item of collect(child, {
1856
- ...options,
1857
- parent: node
1858
- })) results.push(item);
1859
- return results;
1946
+ function createPrinterFactory(getKey) {
1947
+ return function(build) {
1948
+ return (options) => {
1949
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
1950
+ const context = {
1951
+ options: resolvedOptions,
1952
+ transform: (node) => {
1953
+ const key = getKey(node);
1954
+ if (key === void 0) return null;
1955
+ const handler = nodes[key];
1956
+ if (!handler) return null;
1957
+ return handler.call(context, node);
1958
+ }
1959
+ };
1960
+ return {
1961
+ name,
1962
+ options: resolvedOptions,
1963
+ transform: context.transform,
1964
+ print: printOverride ? printOverride.bind(context) : context.transform
1965
+ };
1966
+ };
1967
+ };
1860
1968
  }
1861
1969
  //#endregion
1862
1970
  //#region src/resolvers.ts
@@ -1998,6 +2106,8 @@ exports.caseParams = caseParams;
1998
2106
  exports.childName = childName;
1999
2107
  exports.collect = collect;
2000
2108
  exports.collectImports = collectImports;
2109
+ exports.collectReferencedSchemaNames = collectReferencedSchemaNames;
2110
+ exports.containsCircularRef = containsCircularRef;
2001
2111
  exports.createArrowFunction = createArrowFunction;
2002
2112
  exports.createBreak = createBreak;
2003
2113
  exports.createConst = createConst;
@@ -2027,6 +2137,7 @@ exports.definePrinter = definePrinter;
2027
2137
  exports.enumPropName = enumPropName;
2028
2138
  exports.extractRefName = extractRefName;
2029
2139
  exports.extractStringsFromNodes = extractStringsFromNodes;
2140
+ exports.findCircularSchemas = findCircularSchemas;
2030
2141
  exports.findDiscriminator = findDiscriminator;
2031
2142
  exports.httpMethods = httpMethods;
2032
2143
  exports.isInputNode = isInputNode;
@@ -2039,6 +2150,7 @@ exports.mediaTypes = mediaTypes;
2039
2150
  exports.mergeAdjacentObjects = mergeAdjacentObjects;
2040
2151
  exports.narrowSchema = narrowSchema;
2041
2152
  exports.nodeKinds = nodeKinds;
2153
+ exports.resolveRefName = resolveRefName;
2042
2154
  exports.schemaTypes = schemaTypes;
2043
2155
  exports.setDiscriminatorEnum = setDiscriminatorEnum;
2044
2156
  exports.setEnumName = setEnumName;