@kubb/ast 5.0.0-alpha.13 → 5.0.0-alpha.14

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.
@@ -623,12 +623,19 @@ declare function createSchema<T extends DistributiveOmit<Exclude<SchemaNode, Obj
623
623
  kind: 'Schema';
624
624
  };
625
625
  declare function createSchema(props: DistributiveOmit<SchemaNode, 'kind'>): SchemaNode;
626
+ /**
627
+ * Derives `schema.optional` and `schema.nullish` from `required` and `schema.nullable`.
628
+ * This keeps `PropertyNode.required` as the single source of truth for optionality.
629
+ */
630
+ declare function syncPropertySchema(required: boolean, schema: SchemaNode): SchemaNode;
626
631
  /**
627
632
  * Creates a `PropertyNode`. `required` defaults to `false`.
633
+ * `schema.optional` and `schema.nullish` are auto-derived from `required` and `schema.nullable`.
628
634
  */
629
635
  declare function createProperty(props: Pick<PropertyNode, 'name' | 'schema'> & Partial<Omit<PropertyNode, 'kind' | 'name' | 'schema'>>): PropertyNode;
630
636
  /**
631
637
  * Creates a `ParameterNode`. `required` defaults to `false`.
638
+ * `schema.optional` is auto-derived from `required` and `schema.nullable`.
632
639
  */
633
640
  declare function createParameter(props: Pick<ParameterNode, 'name' | 'in' | 'schema'> & Partial<Omit<ParameterNode, 'kind' | 'name' | 'in' | 'schema'>>): ParameterNode;
634
641
  /**
@@ -852,69 +859,103 @@ declare function refMapToObject(refMap: RefMap): Record<string, SchemaNode>;
852
859
  //#endregion
853
860
  //#region src/visitor.d.ts
854
861
  /**
855
- * Shared options for `walk`, `transform`, and `collect`.
862
+ * Single source of truth: ordered list of `[NodeType, ParentType]` pairs
863
+ * describing which node types can be the parent of a given node in the AST.
864
+ *
865
+ * `ParentOf` walks this tuple and returns the parent type of the first matching entry.
856
866
  */
857
- type VisitorOptions = {
858
- depth?: VisitorDepth;
859
- /**
860
- * Maximum number of sibling nodes visited concurrently inside `walk`.
861
- * @default 30
862
- */
863
- concurrency?: number;
867
+ type ParentNodeMap = [[RootNode, undefined], [OperationNode, RootNode], [SchemaNode, RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode], [PropertyNode, SchemaNode], [ParameterNode, OperationNode], [ResponseNode, OperationNode]];
868
+ type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unknown]> = ParentNodeMap> = TEntries extends [infer TEntry extends [Node, unknown], ...infer TRest extends ReadonlyArray<[Node, unknown]>] ? T extends TEntry[0] ? TEntry[1] : ParentOf<T, TRest> : Node;
869
+ /**
870
+ * Traversal context passed as the second argument to every visitor callback.
871
+ * The `parent` field is narrowed based on the node type being visited.
872
+ */
873
+ type VisitorContext<T extends Node = Node> = {
874
+ parent?: ParentOf<T>;
864
875
  };
865
876
  /**
866
877
  * Synchronous visitor for `transform` and `walk`.
867
878
  */
868
879
  type Visitor = {
869
- root?(node: RootNode): void | RootNode;
870
- operation?(node: OperationNode): void | OperationNode;
871
- schema?(node: SchemaNode): void | SchemaNode;
872
- property?(node: PropertyNode): void | PropertyNode;
873
- parameter?(node: ParameterNode): void | ParameterNode;
874
- response?(node: ResponseNode): void | ResponseNode;
880
+ root?(node: RootNode, context: VisitorContext<RootNode>): void | RootNode;
881
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): void | OperationNode;
882
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): void | SchemaNode;
883
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): void | PropertyNode;
884
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): void | ParameterNode;
885
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): void | ResponseNode;
875
886
  };
876
887
  type MaybePromise<T> = T | Promise<T>;
877
888
  /**
878
889
  * Async visitor for `walk`. Synchronous `Visitor` objects are compatible.
879
890
  */
880
891
  type AsyncVisitor = {
881
- root?(node: RootNode): MaybePromise<void | RootNode>;
882
- operation?(node: OperationNode): MaybePromise<void | OperationNode>;
883
- schema?(node: SchemaNode): MaybePromise<void | SchemaNode>;
884
- property?(node: PropertyNode): MaybePromise<void | PropertyNode>;
885
- parameter?(node: ParameterNode): MaybePromise<void | ParameterNode>;
886
- response?(node: ResponseNode): MaybePromise<void | ResponseNode>;
892
+ root?(node: RootNode, context: VisitorContext<RootNode>): MaybePromise<void | RootNode>;
893
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<void | OperationNode>;
894
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<void | SchemaNode>;
895
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<void | PropertyNode>;
896
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<void | ParameterNode>;
897
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<void | ResponseNode>;
887
898
  };
888
899
  /**
889
900
  * Visitor for `collect`.
890
901
  */
891
902
  type CollectVisitor<T> = {
892
- root?(node: RootNode): T | undefined;
893
- operation?(node: OperationNode): T | undefined;
894
- schema?(node: SchemaNode): T | undefined;
895
- property?(node: PropertyNode): T | undefined;
896
- parameter?(node: ParameterNode): T | undefined;
897
- response?(node: ResponseNode): T | undefined;
903
+ root?(node: RootNode, context: VisitorContext<RootNode>): T | undefined;
904
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | undefined;
905
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | undefined;
906
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | undefined;
907
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | undefined;
908
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | undefined;
909
+ };
910
+ /**
911
+ * Options for `transform` and `collect`. Extends `Visitor` with traversal settings.
912
+ */
913
+ type TransformOptions = Visitor & {
914
+ depth?: VisitorDepth;
915
+ parent?: Node;
916
+ };
917
+ /**
918
+ * Options for `walk`. Extends `AsyncVisitor` with traversal settings.
919
+ */
920
+ type WalkOptions = AsyncVisitor & {
921
+ depth?: VisitorDepth;
922
+ /**
923
+ * Maximum number of sibling nodes visited concurrently.
924
+ * @default 30
925
+ */
926
+ concurrency?: number;
927
+ };
928
+ /**
929
+ * Options for `collect`. Extends `CollectVisitor` with traversal settings.
930
+ */
931
+ type CollectOptions<T> = CollectVisitor<T> & {
932
+ depth?: VisitorDepth;
933
+ parent?: Node;
898
934
  };
899
935
  /**
900
936
  * Depth-first traversal for side effects. Visitor return values are ignored.
901
937
  * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
902
938
  */
903
- declare function walk(node: Node, visitor: AsyncVisitor, options?: VisitorOptions): Promise<void>;
939
+ declare function walk(node: Node, options: WalkOptions): Promise<void>;
904
940
  /**
905
941
  * Depth-first immutable transformation. Visitor return values replace nodes; `undefined` keeps the original.
906
942
  */
907
- declare function transform(node: RootNode, visitor: Visitor, options?: VisitorOptions): RootNode;
908
- declare function transform(node: OperationNode, visitor: Visitor, options?: VisitorOptions): OperationNode;
909
- declare function transform(node: SchemaNode, visitor: Visitor, options?: VisitorOptions): SchemaNode;
910
- declare function transform(node: PropertyNode, visitor: Visitor, options?: VisitorOptions): PropertyNode;
911
- declare function transform(node: ParameterNode, visitor: Visitor, options?: VisitorOptions): ParameterNode;
912
- declare function transform(node: ResponseNode, visitor: Visitor, options?: VisitorOptions): ResponseNode;
913
- declare function transform(node: Node, visitor: Visitor, options?: VisitorOptions): Node;
943
+ declare function transform(node: RootNode, options: TransformOptions): RootNode;
944
+ declare function transform(node: OperationNode, options: TransformOptions): OperationNode;
945
+ declare function transform(node: SchemaNode, options: TransformOptions): SchemaNode;
946
+ declare function transform(node: PropertyNode, options: TransformOptions): PropertyNode;
947
+ declare function transform(node: ParameterNode, options: TransformOptions): ParameterNode;
948
+ declare function transform(node: ResponseNode, options: TransformOptions): ResponseNode;
949
+ declare function transform(node: Node, options: TransformOptions): Node;
950
+ /**
951
+ * Combines multiple visitors into a single visitor that applies them sequentially (left to right).
952
+ * For each node kind, the output of one visitor becomes the input of the next.
953
+ */
954
+ declare function composeTransformers(...visitors: Array<Visitor>): Visitor;
914
955
  /**
915
956
  * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
916
957
  */
917
- declare function collect<T>(node: Node, visitor: CollectVisitor<T>, options?: VisitorOptions): Array<T>;
958
+ declare function collect<T>(node: Node, options: CollectOptions<T>): Array<T>;
918
959
  //#endregion
919
- export { TimeSchemaNode as $, HttpStatusCode as A, EnumValueNode as B, createSchema as C, HttpMethod as D, RootNode as E, ArraySchemaNode as F, RefSchemaNode as G, NumberSchemaNode as H, ComplexSchemaType as I, SchemaNode as J, ScalarSchemaNode as K, DateSchemaNode as L, StatusCode as M, ParameterLocation as N, OperationNode as O, ParameterNode as P, StringSchemaNode as Q, DatetimeSchemaNode as R, createRoot as S, RootMeta as T, ObjectSchemaNode as U, IntersectionSchemaNode as V, PrimitiveSchemaType as W, SchemaType as X, SchemaNodeByType as Y, SpecialSchemaType as Z, createObjectBindingParameter as _, transform as a, FunctionParameterNode as at, createProperty as b, buildRefMap as c, BaseNode as ct, Printer as d, httpMethods as dt, UnionSchemaNode as et, PrinterFactoryOptions as f, mediaTypes as ft, createFunctionParameters as g, createFunctionParameter as h, collect as i, FunctionNodeType as it, MediaType as j, ResponseNode as k, refMapToObject as l, NodeKind as lt, DistributiveOmit as m, schemaTypes as mt, CollectVisitor as n, PropertyNode as nt, walk as o, FunctionParametersNode as ot, definePrinter as p, nodeKinds as pt, ScalarSchemaType as q, Visitor as r, FunctionNode as rt, RefMap as s, ObjectBindingParameterNode as st, AsyncVisitor as t, UrlSchemaNode as tt, resolveRef as u, VisitorDepth as ut, createOperation as v, Node as w, createResponse as x, createParameter as y, EnumSchemaNode as z };
920
- //# sourceMappingURL=visitor-D68E3x_d.d.ts.map
960
+ export { ScalarSchemaNode as $, syncPropertySchema as A, ParameterLocation as B, createObjectBindingParameter as C, createResponse as D, createProperty as E, OperationNode as F, DatetimeSchemaNode as G, ArraySchemaNode as H, ResponseNode as I, IntersectionSchemaNode as J, EnumSchemaNode as K, HttpStatusCode as L, RootMeta as M, RootNode as N, createRoot as O, HttpMethod as P, RefSchemaNode as Q, MediaType as R, createFunctionParameters as S, createParameter as T, ComplexSchemaType as U, ParameterNode as V, DateSchemaNode as W, ObjectSchemaNode as X, NumberSchemaNode as Y, PrimitiveSchemaType as Z, Printer as _, VisitorDepth as _t, TransformOptions as a, StringSchemaNode as at, DistributiveOmit as b, nodeKinds as bt, WalkOptions as c, UrlSchemaNode as ct, transform as d, FunctionNodeType as dt, ScalarSchemaType as et, walk as f, FunctionParameterNode as ft, resolveRef as g, NodeKind as gt, refMapToObject as h, BaseNode as ht, ParentOf as i, SpecialSchemaType as it, Node as j, createSchema as k, collect as l, PropertyNode as lt, buildRefMap as m, ObjectBindingParameterNode as mt, CollectOptions as n, SchemaNodeByType as nt, Visitor as o, TimeSchemaNode as ot, RefMap as p, FunctionParametersNode as pt, EnumValueNode as q, CollectVisitor as r, SchemaType as rt, VisitorContext as s, UnionSchemaNode as st, AsyncVisitor as t, SchemaNode as tt, composeTransformers as u, FunctionNode as ut, PrinterFactoryOptions as v, httpMethods as vt, createOperation as w, createFunctionParameter as x, schemaTypes as xt, definePrinter as y, mediaTypes as yt, StatusCode as z };
961
+ //# sourceMappingURL=visitor-UlWOe-In.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/ast",
3
- "version": "5.0.0-alpha.13",
3
+ "version": "5.0.0-alpha.14",
4
4
  "description": "Spec-agnostic AST layer for Kubb. Defines nodes, visitor pattern, and factory functions used across codegen plugins.",
5
5
  "keywords": [
6
6
  "kubb",
package/src/factory.ts CHANGED
@@ -60,27 +60,48 @@ export function createSchema(props: Record<string, unknown>): Record<string, unk
60
60
  return { ...props, kind: 'Schema' }
61
61
  }
62
62
 
63
+ /**
64
+ * Derives `schema.optional` and `schema.nullish` from `required` and `schema.nullable`.
65
+ * This keeps `PropertyNode.required` as the single source of truth for optionality.
66
+ */
67
+ export function syncPropertySchema(required: boolean, schema: SchemaNode): SchemaNode {
68
+ const nullable = schema.nullable ?? false
69
+
70
+ return {
71
+ ...schema,
72
+ optional: !required && !nullable ? true : undefined,
73
+ nullish: !required && nullable ? true : undefined,
74
+ }
75
+ }
76
+
63
77
  /**
64
78
  * Creates a `PropertyNode`. `required` defaults to `false`.
79
+ * `schema.optional` and `schema.nullish` are auto-derived from `required` and `schema.nullable`.
65
80
  */
66
81
  export function createProperty(props: Pick<PropertyNode, 'name' | 'schema'> & Partial<Omit<PropertyNode, 'kind' | 'name' | 'schema'>>): PropertyNode {
82
+ const required = props.required ?? false
83
+
67
84
  return {
68
- required: false,
69
85
  ...props,
70
86
  kind: 'Property',
87
+ required,
88
+ schema: syncPropertySchema(required, props.schema),
71
89
  }
72
90
  }
73
91
 
74
92
  /**
75
93
  * Creates a `ParameterNode`. `required` defaults to `false`.
94
+ * `schema.optional` is auto-derived from `required` and `schema.nullable`.
76
95
  */
77
96
  export function createParameter(
78
97
  props: Pick<ParameterNode, 'name' | 'in' | 'schema'> & Partial<Omit<ParameterNode, 'kind' | 'name' | 'in' | 'schema'>>,
79
98
  ): ParameterNode {
99
+ const required = props.required ?? false
80
100
  return {
81
- required: false,
82
101
  ...props,
83
102
  kind: 'Parameter',
103
+ required,
104
+ schema: syncPropertySchema(required, props.schema),
84
105
  }
85
106
  }
86
107
 
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  createResponse,
10
10
  createRoot,
11
11
  createSchema,
12
+ syncPropertySchema,
12
13
  } from './factory.ts'
13
14
  export { functionPrinter } from './functionPrinter.ts'
14
15
  export {
@@ -26,4 +27,4 @@ export {
26
27
  export { definePrinter } from './printer.ts'
27
28
  export { buildRefMap, refMapToObject, resolveRef } from './refs.ts'
28
29
  export { applyParamsCasing, isPlainStringType } from './utils.ts'
29
- export { collect, transform, walk } from './visitor.ts'
30
+ export { collect, composeTransformers, transform, walk } from './visitor.ts'
package/src/types.ts CHANGED
@@ -44,4 +44,4 @@ export type {
44
44
  } from './nodes/index.ts'
45
45
  export type { Printer, PrinterFactoryOptions } from './printer.ts'
46
46
  export type { RefMap } from './refs.ts'
47
- export type { AsyncVisitor, CollectVisitor, Visitor } from './visitor.ts'
47
+ export type { AsyncVisitor, CollectOptions, CollectVisitor, ParentOf, TransformOptions, Visitor, VisitorContext, WalkOptions } from './visitor.ts'
package/src/visitor.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
2
  import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
3
+ import { createParameter, createProperty } from './factory.ts'
3
4
  import type { Node, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode } from './nodes/index.ts'
4
5
 
5
6
  /**
@@ -35,27 +36,47 @@ function createLimit(concurrency: number) {
35
36
  type LimitFn = ReturnType<typeof createLimit>
36
37
 
37
38
  /**
38
- * Shared options for `walk`, `transform`, and `collect`.
39
+ * Single source of truth: ordered list of `[NodeType, ParentType]` pairs
40
+ * describing which node types can be the parent of a given node in the AST.
41
+ *
42
+ * `ParentOf` walks this tuple and returns the parent type of the first matching entry.
39
43
  */
40
- export type VisitorOptions = {
41
- depth?: VisitorDepth
42
- /**
43
- * Maximum number of sibling nodes visited concurrently inside `walk`.
44
- * @default 30
45
- */
46
- concurrency?: number
44
+ type ParentNodeMap = [
45
+ [RootNode, undefined],
46
+ [OperationNode, RootNode],
47
+ [SchemaNode, RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode],
48
+ [PropertyNode, SchemaNode],
49
+ [ParameterNode, OperationNode],
50
+ [ResponseNode, OperationNode],
51
+ ]
52
+
53
+ export type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unknown]> = ParentNodeMap> = TEntries extends [
54
+ infer TEntry extends [Node, unknown],
55
+ ...infer TRest extends ReadonlyArray<[Node, unknown]>,
56
+ ]
57
+ ? T extends TEntry[0]
58
+ ? TEntry[1]
59
+ : ParentOf<T, TRest>
60
+ : Node
61
+
62
+ /**
63
+ * Traversal context passed as the second argument to every visitor callback.
64
+ * The `parent` field is narrowed based on the node type being visited.
65
+ */
66
+ export type VisitorContext<T extends Node = Node> = {
67
+ parent?: ParentOf<T>
47
68
  }
48
69
 
49
70
  /**
50
71
  * Synchronous visitor for `transform` and `walk`.
51
72
  */
52
73
  export type Visitor = {
53
- root?(node: RootNode): void | RootNode
54
- operation?(node: OperationNode): void | OperationNode
55
- schema?(node: SchemaNode): void | SchemaNode
56
- property?(node: PropertyNode): void | PropertyNode
57
- parameter?(node: ParameterNode): void | ParameterNode
58
- response?(node: ResponseNode): void | ResponseNode
74
+ root?(node: RootNode, context: VisitorContext<RootNode>): void | RootNode
75
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): void | OperationNode
76
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): void | SchemaNode
77
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): void | PropertyNode
78
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): void | ParameterNode
79
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): void | ResponseNode
59
80
  }
60
81
 
61
82
  type MaybePromise<T> = T | Promise<T>
@@ -64,24 +85,52 @@ type MaybePromise<T> = T | Promise<T>
64
85
  * Async visitor for `walk`. Synchronous `Visitor` objects are compatible.
65
86
  */
66
87
  export type AsyncVisitor = {
67
- root?(node: RootNode): MaybePromise<void | RootNode>
68
- operation?(node: OperationNode): MaybePromise<void | OperationNode>
69
- schema?(node: SchemaNode): MaybePromise<void | SchemaNode>
70
- property?(node: PropertyNode): MaybePromise<void | PropertyNode>
71
- parameter?(node: ParameterNode): MaybePromise<void | ParameterNode>
72
- response?(node: ResponseNode): MaybePromise<void | ResponseNode>
88
+ root?(node: RootNode, context: VisitorContext<RootNode>): MaybePromise<void | RootNode>
89
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<void | OperationNode>
90
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<void | SchemaNode>
91
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<void | PropertyNode>
92
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<void | ParameterNode>
93
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<void | ResponseNode>
73
94
  }
74
95
 
75
96
  /**
76
97
  * Visitor for `collect`.
77
98
  */
78
99
  export type CollectVisitor<T> = {
79
- root?(node: RootNode): T | undefined
80
- operation?(node: OperationNode): T | undefined
81
- schema?(node: SchemaNode): T | undefined
82
- property?(node: PropertyNode): T | undefined
83
- parameter?(node: ParameterNode): T | undefined
84
- response?(node: ResponseNode): T | undefined
100
+ root?(node: RootNode, context: VisitorContext<RootNode>): T | undefined
101
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | undefined
102
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | undefined
103
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | undefined
104
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | undefined
105
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | undefined
106
+ }
107
+
108
+ /**
109
+ * Options for `transform` and `collect`. Extends `Visitor` with traversal settings.
110
+ */
111
+ export type TransformOptions = Visitor & {
112
+ depth?: VisitorDepth
113
+ parent?: Node
114
+ }
115
+
116
+ /**
117
+ * Options for `walk`. Extends `AsyncVisitor` with traversal settings.
118
+ */
119
+ export type WalkOptions = AsyncVisitor & {
120
+ depth?: VisitorDepth
121
+ /**
122
+ * Maximum number of sibling nodes visited concurrently.
123
+ * @default 30
124
+ */
125
+ concurrency?: number
126
+ }
127
+
128
+ /**
129
+ * Options for `collect`. Extends `CollectVisitor` with traversal settings.
130
+ */
131
+ export type CollectOptions<T> = CollectVisitor<T> & {
132
+ depth?: VisitorDepth
133
+ parent?: Node
85
134
  }
86
135
 
87
136
  /**
@@ -125,34 +174,31 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
125
174
  * Depth-first traversal for side effects. Visitor return values are ignored.
126
175
  * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
127
176
  */
128
- export async function walk(node: Node, visitor: AsyncVisitor, options: VisitorOptions = {}): Promise<void> {
177
+ export async function walk(node: Node, options: WalkOptions): Promise<void> {
129
178
  const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep
130
179
  const limit = createLimit(options.concurrency ?? WALK_CONCURRENCY)
131
- return _walk(node, visitor, recurse, limit)
180
+ return _walk(node, options, recurse, limit, undefined)
132
181
  }
133
182
 
134
- /**
135
- * Internal recursive walk implementation — calls visitor then recurses into children.
136
- */
137
- async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn): Promise<void> {
183
+ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
138
184
  switch (node.kind) {
139
185
  case 'Root':
140
- await limit(() => visitor.root?.(node))
186
+ await limit(() => visitor.root?.(node, { parent: parent as ParentOf<RootNode> }))
141
187
  break
142
188
  case 'Operation':
143
- await limit(() => visitor.operation?.(node))
189
+ await limit(() => visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> }))
144
190
  break
145
191
  case 'Schema':
146
- await limit(() => visitor.schema?.(node))
192
+ await limit(() => visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> }))
147
193
  break
148
194
  case 'Property':
149
- await limit(() => visitor.property?.(node))
195
+ await limit(() => visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> }))
150
196
  break
151
197
  case 'Parameter':
152
- await limit(() => visitor.parameter?.(node))
198
+ await limit(() => visitor.parameter?.(node, { parent: parent as ParentOf<ParameterNode> }))
153
199
  break
154
200
  case 'Response':
155
- await limit(() => visitor.response?.(node))
201
+ await limit(() => visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> }))
156
202
  break
157
203
  case 'FunctionParameter':
158
204
  case 'ObjectBindingParameter':
@@ -161,91 +207,94 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
161
207
  }
162
208
 
163
209
  const children = getChildren(node, recurse)
164
- await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)))
210
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)))
165
211
  }
166
212
 
167
213
  /**
168
214
  * Depth-first immutable transformation. Visitor return values replace nodes; `undefined` keeps the original.
169
215
  */
170
- export function transform(node: RootNode, visitor: Visitor, options?: VisitorOptions): RootNode
171
- export function transform(node: OperationNode, visitor: Visitor, options?: VisitorOptions): OperationNode
172
- export function transform(node: SchemaNode, visitor: Visitor, options?: VisitorOptions): SchemaNode
173
- export function transform(node: PropertyNode, visitor: Visitor, options?: VisitorOptions): PropertyNode
174
- export function transform(node: ParameterNode, visitor: Visitor, options?: VisitorOptions): ParameterNode
175
- export function transform(node: ResponseNode, visitor: Visitor, options?: VisitorOptions): ResponseNode
176
- export function transform(node: Node, visitor: Visitor, options?: VisitorOptions): Node
177
- export function transform(node: Node, visitor: Visitor, options: VisitorOptions = {}): Node {
178
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep
216
+ export function transform(node: RootNode, options: TransformOptions): RootNode
217
+ export function transform(node: OperationNode, options: TransformOptions): OperationNode
218
+ export function transform(node: SchemaNode, options: TransformOptions): SchemaNode
219
+ export function transform(node: PropertyNode, options: TransformOptions): PropertyNode
220
+ export function transform(node: ParameterNode, options: TransformOptions): ParameterNode
221
+ export function transform(node: ResponseNode, options: TransformOptions): ResponseNode
222
+ export function transform(node: Node, options: TransformOptions): Node
223
+ export function transform(node: Node, options: TransformOptions): Node {
224
+ const { depth, parent, ...visitor } = options
225
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
179
226
 
180
227
  switch (node.kind) {
181
228
  case 'Root': {
182
229
  let root = node
183
- const replaced = visitor.root?.(root)
230
+ const replaced = visitor.root?.(root, { parent: parent as ParentOf<RootNode> })
184
231
  if (replaced) root = replaced
185
232
 
186
233
  return {
187
234
  ...root,
188
- schemas: root.schemas.map((s) => transform(s, visitor, options)),
189
- operations: root.operations.map((op) => transform(op, visitor, options)),
235
+ schemas: root.schemas.map((s) => transform(s, { ...options, parent: root })),
236
+ operations: root.operations.map((op) => transform(op, { ...options, parent: root })),
190
237
  }
191
238
  }
192
239
  case 'Operation': {
193
240
  let op = node
194
- const replaced = visitor.operation?.(op)
241
+ const replaced = visitor.operation?.(op, { parent: parent as ParentOf<OperationNode> })
195
242
  if (replaced) op = replaced
196
243
 
197
244
  return {
198
245
  ...op,
199
- parameters: op.parameters.map((p) => transform(p, visitor, options)),
246
+ parameters: op.parameters.map((p) => transform(p, { ...options, parent: op })),
200
247
  requestBody: op.requestBody
201
- ? { ...op.requestBody, schema: op.requestBody.schema ? transform(op.requestBody.schema, visitor, options) : undefined }
248
+ ? { ...op.requestBody, schema: op.requestBody.schema ? transform(op.requestBody.schema, { ...options, parent: op }) : undefined }
202
249
  : undefined,
203
- responses: op.responses.map((r) => transform(r, visitor, options)),
250
+ responses: op.responses.map((r) => transform(r, { ...options, parent: op })),
204
251
  }
205
252
  }
206
253
  case 'Schema': {
207
254
  let schema = node
208
- const replaced = visitor.schema?.(schema)
255
+ const replaced = visitor.schema?.(schema, { parent: parent as ParentOf<SchemaNode> })
209
256
  if (replaced) schema = replaced
210
257
 
258
+ const childOptions = { ...options, parent: schema }
259
+
211
260
  return {
212
261
  ...schema,
213
- ...('properties' in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {}),
214
- ...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {}),
215
- ...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}),
262
+ ...('properties' in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {}),
263
+ ...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {}),
264
+ ...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {}),
216
265
  ...('additionalProperties' in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true
217
- ? { additionalProperties: transform(schema.additionalProperties, visitor, options) }
266
+ ? { additionalProperties: transform(schema.additionalProperties, childOptions) }
218
267
  : {}),
219
- }
268
+ } as SchemaNode
220
269
  }
221
270
  case 'Property': {
222
271
  let prop = node
223
- const replaced = visitor.property?.(prop)
272
+ const replaced = visitor.property?.(prop, { parent: parent as ParentOf<PropertyNode> })
224
273
  if (replaced) prop = replaced
225
274
 
226
- return {
275
+ return createProperty({
227
276
  ...prop,
228
- schema: transform(prop.schema, visitor, options),
229
- }
277
+ schema: transform(prop.schema, { ...options, parent: prop }),
278
+ })
230
279
  }
231
280
  case 'Parameter': {
232
281
  let param = node
233
- const replaced = visitor.parameter?.(param)
282
+ const replaced = visitor.parameter?.(param, { parent: parent as ParentOf<ParameterNode> })
234
283
  if (replaced) param = replaced
235
284
 
236
- return {
285
+ return createParameter({
237
286
  ...param,
238
- schema: transform(param.schema, visitor, options),
239
- }
287
+ schema: transform(param.schema, { ...options, parent: param }),
288
+ })
240
289
  }
241
290
  case 'Response': {
242
291
  let response = node
243
- const replaced = visitor.response?.(response)
292
+ const replaced = visitor.response?.(response, { parent: parent as ParentOf<ResponseNode> })
244
293
  if (replaced) response = replaced
245
294
 
246
295
  return {
247
296
  ...response,
248
- schema: transform(response.schema, visitor, options),
297
+ schema: transform(response.schema, { ...options, parent: response }),
249
298
  }
250
299
  }
251
300
  case 'FunctionParameter':
@@ -255,32 +304,60 @@ export function transform(node: Node, visitor: Visitor, options: VisitorOptions
255
304
  }
256
305
  }
257
306
 
307
+ /**
308
+ * Combines multiple visitors into a single visitor that applies them sequentially (left to right).
309
+ * For each node kind, the output of one visitor becomes the input of the next.
310
+ */
311
+ export function composeTransformers(...visitors: Array<Visitor>): Visitor {
312
+ return {
313
+ root(node, context) {
314
+ return visitors.reduce<RootNode>((acc, v) => v.root?.(acc, context) ?? acc, node)
315
+ },
316
+ operation(node, context) {
317
+ return visitors.reduce<OperationNode>((acc, v) => v.operation?.(acc, context) ?? acc, node)
318
+ },
319
+ schema(node, context) {
320
+ return visitors.reduce<SchemaNode>((acc, v) => v.schema?.(acc, context) ?? acc, node)
321
+ },
322
+ property(node, context) {
323
+ return visitors.reduce<PropertyNode>((acc, v) => v.property?.(acc, context) ?? acc, node)
324
+ },
325
+ parameter(node, context) {
326
+ return visitors.reduce<ParameterNode>((acc, v) => v.parameter?.(acc, context) ?? acc, node)
327
+ },
328
+ response(node, context) {
329
+ return visitors.reduce<ResponseNode>((acc, v) => v.response?.(acc, context) ?? acc, node)
330
+ },
331
+ }
332
+ }
333
+
258
334
  /**
259
335
  * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
260
336
  */
261
- export function collect<T>(node: Node, visitor: CollectVisitor<T>, options: VisitorOptions = {}): Array<T> {
262
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep
337
+ export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
338
+ const { depth, parent, ...visitor } = options
339
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
263
340
  const results: Array<T> = []
264
341
 
265
342
  let v: T | undefined
266
343
  switch (node.kind) {
267
344
  case 'Root':
268
- v = visitor.root?.(node)
345
+ v = visitor.root?.(node, { parent: parent as ParentOf<RootNode> })
269
346
  break
270
347
  case 'Operation':
271
- v = visitor.operation?.(node)
348
+ v = visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> })
272
349
  break
273
350
  case 'Schema':
274
- v = visitor.schema?.(node)
351
+ v = visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> })
275
352
  break
276
353
  case 'Property':
277
- v = visitor.property?.(node)
354
+ v = visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> })
278
355
  break
279
356
  case 'Parameter':
280
- v = visitor.parameter?.(node)
357
+ v = visitor.parameter?.(node, { parent: parent as ParentOf<ParameterNode> })
281
358
  break
282
359
  case 'Response':
283
- v = visitor.response?.(node)
360
+ v = visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> })
284
361
  break
285
362
  case 'FunctionParameter':
286
363
  case 'ObjectBindingParameter':
@@ -290,7 +367,7 @@ export function collect<T>(node: Node, visitor: CollectVisitor<T>, options: Visi
290
367
  if (v !== undefined) results.push(v)
291
368
 
292
369
  for (const child of getChildren(node, recurse)) {
293
- for (const item of collect(child, visitor, options)) {
370
+ for (const item of collect(child, { ...options, parent: node })) {
294
371
  results.push(item)
295
372
  }
296
373
  }