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