@kubb/ast 5.0.0-alpha.31 → 5.0.0-alpha.33

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.
@@ -0,0 +1,26 @@
1
+ import type { BaseNode } from './base.ts'
2
+ import type { FileNode } from './file.ts'
3
+
4
+ /**
5
+ * Output AST node that groups all generated file output for one API document.
6
+ *
7
+ * Produced by generators and consumed by the build pipeline to write files.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const output: OutputNode = {
12
+ * kind: 'Output',
13
+ * files: [],
14
+ * }
15
+ * ```
16
+ */
17
+ export type OutputNode = BaseNode & {
18
+ /**
19
+ * Node kind.
20
+ */
21
+ kind: 'Output'
22
+ /**
23
+ * Generated file nodes.
24
+ */
25
+ files: Array<FileNode>
26
+ }
package/src/nodes/root.ts CHANGED
@@ -8,10 +8,10 @@ import type { SchemaNode } from './schema.ts'
8
8
  *
9
9
  * @example
10
10
  * ```ts
11
- * const meta: RootMeta = { title: 'Pet API', version: '1.0.0' }
11
+ * const meta: InputMeta = { title: 'Pet API', version: '1.0.0' }
12
12
  * ```
13
13
  */
14
- export type RootMeta = {
14
+ export type InputMeta = {
15
15
  /**
16
16
  * API title (from `info.title` in OAS/AsyncAPI).
17
17
  */
@@ -32,22 +32,23 @@ export type RootMeta = {
32
32
  }
33
33
 
34
34
  /**
35
- * Root AST node that contains all schemas and operations for one API document.
35
+ * Input AST node that contains all schemas and operations for one API document.
36
+ * Produced by the adapter and consumed by all Kubb plugins.
36
37
  *
37
38
  * @example
38
39
  * ```ts
39
- * const root: RootNode = {
40
- * kind: 'Root',
40
+ * const input: InputNode = {
41
+ * kind: 'Input',
41
42
  * schemas: [],
42
43
  * operations: [],
43
44
  * }
44
45
  * ```
45
46
  */
46
- export type RootNode = BaseNode & {
47
+ export type InputNode = BaseNode & {
47
48
  /**
48
49
  * Node kind.
49
50
  */
50
- kind: 'Root'
51
+ kind: 'Input'
51
52
  /**
52
53
  * All schema nodes in the document.
53
54
  */
@@ -59,5 +60,5 @@ export type RootNode = BaseNode & {
59
60
  /**
60
61
  * Optional document metadata populated by the adapter.
61
62
  */
62
- meta?: RootMeta
63
+ meta?: InputMeta
63
64
  }
package/src/refs.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { RootNode } from './nodes/root.ts'
1
+ import type { InputNode } from './nodes/root.ts'
2
2
  import type { SchemaNode } from './nodes/schema.ts'
3
3
 
4
4
  /**
@@ -21,20 +21,20 @@ export function extractRefName(ref: string): string {
21
21
  }
22
22
 
23
23
  /**
24
- * Builds a `RefMap` from `root.schemas` using each schema's `name`.
24
+ * Builds a `RefMap` from `input.schemas` using each schema's `name`.
25
25
  *
26
26
  * Unnamed schemas are skipped.
27
27
  *
28
28
  * @example
29
29
  * ```ts
30
- * const refMap = buildRefMap(root)
30
+ * const refMap = buildRefMap(input)
31
31
  * const pet = refMap.get('Pet')
32
32
  * ```
33
33
  */
34
- export function buildRefMap(root: RootNode): RefMap {
34
+ export function buildRefMap(input: InputNode): RefMap {
35
35
  const map: RefMap = new Map()
36
36
 
37
- for (const schema of root.schemas) {
37
+ for (const schema of input.schemas) {
38
38
  if (schema.name) {
39
39
  map.set(schema.name, schema)
40
40
  }
package/src/types.ts CHANGED
@@ -3,46 +3,61 @@ export type { DistributiveOmit } from './factory.ts'
3
3
  export type { InferSchema, InferSchemaNode, ParserOptions } from './infer.ts'
4
4
  export type {
5
5
  ArraySchemaNode,
6
+ ArrowFunctionNode,
6
7
  BaseNode,
8
+ BreakNode,
9
+ CodeNode,
7
10
  ComplexSchemaType,
11
+ ConstNode,
8
12
  DateSchemaNode,
9
13
  DatetimeSchemaNode,
10
14
  EnumSchemaNode,
11
15
  EnumValueNode,
16
+ ExportNode,
17
+ FileNode,
12
18
  FormatStringSchemaNode,
13
19
  FunctionNode,
14
20
  FunctionNodeType,
15
21
  FunctionParameterNode,
16
22
  FunctionParametersNode,
23
+ FunctionParamNode,
17
24
  HttpMethod,
18
25
  HttpStatusCode,
26
+ ImportNode,
27
+ InputMeta,
28
+ InputNode,
19
29
  IntersectionSchemaNode,
20
30
  Ipv4SchemaNode,
21
31
  Ipv6SchemaNode,
32
+ JSDocNode,
33
+ JsxNode,
22
34
  MediaType,
23
35
  Node,
24
36
  NodeKind,
25
37
  NumberSchemaNode,
26
38
  ObjectSchemaNode,
27
39
  OperationNode,
40
+ OutputNode,
28
41
  ParameterGroupNode,
29
42
  ParameterLocation,
30
43
  ParameterNode,
44
+ ParamsTypeNode,
31
45
  PrimitiveSchemaType,
32
46
  PropertyNode,
33
47
  RefSchemaNode,
34
48
  ResponseNode,
35
- RootMeta,
36
- RootNode,
37
49
  ScalarSchemaNode,
38
50
  ScalarSchemaType,
39
51
  SchemaNode,
40
52
  SchemaNodeByType,
41
53
  SchemaType,
54
+ SourceNode,
42
55
  SpecialSchemaType,
43
56
  StatusCode,
44
57
  StringSchemaNode,
58
+ TextNode,
45
59
  TimeSchemaNode,
60
+ TypeDeclarationNode,
46
61
  TypeNode,
47
62
  UnionSchemaNode,
48
63
  UrlSchemaNode,
package/src/utils.ts CHANGED
@@ -1,8 +1,20 @@
1
1
  import { camelCase, isValidVarName } from '@internals/utils'
2
2
 
3
- import { createFunctionParameter, createFunctionParameters, createParameterGroup, createProperty, createSchema, createTypeNode } from './factory.ts'
3
+ import { createFunctionParameter, createFunctionParameters, createParameterGroup, createParamsType, createProperty, createSchema } from './factory.ts'
4
4
  import { narrowSchema } from './guards.ts'
5
- import type { FunctionParameterNode, FunctionParametersNode, OperationNode, ParameterGroupNode, ParameterNode, SchemaNode, TypeNode } from './nodes/index.ts'
5
+ import type {
6
+ CodeNode,
7
+ ExportNode,
8
+ FunctionParameterNode,
9
+ FunctionParametersNode,
10
+ ImportNode,
11
+ OperationNode,
12
+ ParameterGroupNode,
13
+ ParameterNode,
14
+ ParamsTypeNode,
15
+ SchemaNode,
16
+ SourceNode,
17
+ } from './nodes/index.ts'
6
18
  import type { SchemaType } from './nodes/schema.ts'
7
19
 
8
20
  const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
@@ -118,7 +130,7 @@ export type ParamGroupType = {
118
130
  /**
119
131
  * TypeNode for the group type.
120
132
  */
121
- type: TypeNode
133
+ type: ParamsTypeNode
122
134
  /**
123
135
  * Whether the parameter group is optional.
124
136
  */
@@ -251,9 +263,17 @@ export type CreateOperationParamsOptions = {
251
263
  typeWrapper?: (type: string) => string
252
264
  }
253
265
 
254
- function resolveType({ node, param, resolver }: { node: OperationNode; param: ParameterNode; resolver: OperationParamsResolver | undefined }): TypeNode {
266
+ function resolveParamsType({
267
+ node,
268
+ param,
269
+ resolver,
270
+ }: {
271
+ node: OperationNode
272
+ param: ParameterNode
273
+ resolver: OperationParamsResolver | undefined
274
+ }): ParamsTypeNode {
255
275
  if (!resolver) {
256
- return createTypeNode({ variant: 'reference', name: param.schema.primitive ?? 'unknown' })
276
+ return createParamsType({ variant: 'reference', name: param.schema.primitive ?? 'unknown' })
257
277
  }
258
278
 
259
279
  const individualName = resolver.resolveParamName(node, param)
@@ -269,10 +289,10 @@ function resolveType({ node, param, resolver }: { node: OperationNode; param: Pa
269
289
  const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : undefined
270
290
 
271
291
  if (groupName && groupName !== individualName) {
272
- return createTypeNode({ variant: 'member', base: groupName, key: param.name })
292
+ return createParamsType({ variant: 'member', base: groupName, key: param.name })
273
293
  }
274
294
 
275
- return createTypeNode({ variant: 'reference', name: individualName })
295
+ return createParamsType({ variant: 'reference', name: individualName })
276
296
  }
277
297
 
278
298
  /**
@@ -287,7 +307,7 @@ function resolveType({ node, param, resolver }: { node: OperationNode; param: Pa
287
307
  * paramsType: 'inline',
288
308
  * pathParamsType: 'inline',
289
309
  * resolver: tsResolver,
290
- * extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
310
+ * extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
291
311
  * })
292
312
  * ```
293
313
  */
@@ -299,10 +319,10 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
299
319
  const headersName = paramNames?.headers ?? 'headers'
300
320
  const pathName = paramNames?.path ?? 'pathParams'
301
321
 
302
- const wrapType = (type: string): TypeNode => createTypeNode({ variant: 'reference', name: typeWrapper ? typeWrapper(type) : type })
303
- // Only reference TypeNodes are wrapped (they hold a plain type name string).
322
+ const wrapType = (type: string): ParamsTypeNode => createParamsType({ variant: 'reference', name: typeWrapper ? typeWrapper(type) : type })
323
+ // Only reference-variant TypeNodes are wrapped they hold a plain type name string that needs casing applied.
304
324
  // Member and struct TypeNodes are pre-resolved structured expressions and are passed through unchanged.
305
- const wrapTypeNode = (type: TypeNode): TypeNode => (type.variant === 'reference' ? wrapType(type.name) : type)
325
+ const wrapTypeNode = (type: ParamsTypeNode): ParamsTypeNode => (type.kind === 'ParamsType' && type.variant === 'reference' ? wrapType(type.name) : type)
306
326
 
307
327
  const casedParams = caseParams(node.parameters, paramsCasing)
308
328
  const pathParams = casedParams.filter((p) => p.in === 'path')
@@ -320,7 +340,7 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
320
340
  if (paramsType === 'object') {
321
341
  const children: Array<FunctionParameterNode> = [
322
342
  ...pathParams.map((p) => {
323
- const type = resolveType({ node, param: p, resolver })
343
+ const type = resolveParamsType({ node, param: p, resolver })
324
344
  return createFunctionParameter({ name: p.name, type: wrapTypeNode(type), optional: !p.required })
325
345
  }),
326
346
  ...(bodyType ? [createFunctionParameter({ name: dataName, type: bodyType, optional: !bodyRequired })] : []),
@@ -338,7 +358,7 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
338
358
  params.push(createFunctionParameter({ name: pathName, type: spreadType ? wrapType(spreadType) : undefined, rest: true }))
339
359
  } else {
340
360
  const pathChildren = pathParams.map((p) => {
341
- const type = resolveType({ node, param: p, resolver })
361
+ const type = resolveParamsType({ node, param: p, resolver })
342
362
  return createFunctionParameter({ name: p.name, type: wrapTypeNode(type), optional: !p.required })
343
363
  })
344
364
  params.push(
@@ -384,10 +404,10 @@ function buildGroupParam({
384
404
  params: Array<ParameterNode>
385
405
  groupType: ParamGroupType | undefined
386
406
  resolver: OperationParamsResolver | undefined
387
- wrapType: (type: string) => TypeNode
407
+ wrapType: (type: string) => ParamsTypeNode
388
408
  }): Array<FunctionParameterNode> {
389
409
  if (groupType) {
390
- const type = groupType.type.variant === 'reference' ? wrapType(groupType.type.name) : groupType.type
410
+ const type = groupType.type.kind === 'ParamsType' && groupType.type.variant === 'reference' ? wrapType(groupType.type.name) : groupType.type
391
411
  return [createFunctionParameter({ name, type, optional: groupType.optional })]
392
412
  }
393
413
  if (params.length) {
@@ -426,7 +446,7 @@ function resolveGroupType({
426
446
  return undefined
427
447
  }
428
448
  const allOptional = params.every((p) => !p.required)
429
- return { type: createTypeNode({ variant: 'reference', name: groupName }), optional: allOptional }
449
+ return { type: createParamsType({ variant: 'reference', name: groupName }), optional: allOptional }
430
450
  }
431
451
 
432
452
  /**
@@ -443,9 +463,178 @@ function toStructType({
443
463
  node: OperationNode
444
464
  params: Array<ParameterNode>
445
465
  resolver: OperationParamsResolver | undefined
446
- }): TypeNode {
447
- return createTypeNode({
466
+ }): ParamsTypeNode {
467
+ return createParamsType({
448
468
  variant: 'struct',
449
- properties: params.map((p) => ({ name: p.name, optional: !p.required, type: resolveType({ node, param: p, resolver }) })),
469
+ properties: params.map((p) => ({ name: p.name, optional: !p.required, type: resolveParamsType({ node, param: p, resolver }) })),
450
470
  })
451
471
  }
472
+
473
+ function sourceKey(source: SourceNode): string {
474
+ const nameKey = source.name ?? extractStringsFromNodes(source.nodes)
475
+ return `${nameKey}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`
476
+ }
477
+
478
+ function pathTypeKey(path: string, isTypeOnly: boolean | undefined): string {
479
+ return `${path}:${isTypeOnly ?? false}`
480
+ }
481
+
482
+ function exportKey(path: string, name: string | undefined, isTypeOnly: boolean | undefined, asAlias: boolean | undefined): string {
483
+ return `${path}:${name ?? ''}:${isTypeOnly ?? false}:${asAlias ?? ''}`
484
+ }
485
+
486
+ function importKey(path: string, name: string | undefined, isTypeOnly: boolean | undefined): string {
487
+ return `${path}:${name ?? ''}:${isTypeOnly ?? false}`
488
+ }
489
+
490
+ /**
491
+ * Computes a multi-level sort key for exports and imports:
492
+ * non-array names first (wildcards/namespace aliases); type-only before value; alphabetical path; unnamed before named.
493
+ */
494
+ function sortKey(node: { name?: string | Array<unknown>; isTypeOnly?: boolean; path: string }): string {
495
+ const isArray = Array.isArray(node.name) ? '1' : '0'
496
+ const typeOnly = node.isTypeOnly ? '0' : '1'
497
+ const hasName = node.name != null ? '1' : '0'
498
+ const name = Array.isArray(node.name) ? [...node.name].sort().join('\0') : (node.name ?? '')
499
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`
500
+ }
501
+
502
+ /**
503
+ * Deduplicates an array of `SourceNode` objects.
504
+ * Named sources are deduplicated by `name + isExportable + isTypeOnly`.
505
+ * Unnamed sources are deduplicated by object reference.
506
+ */
507
+ export function combineSources(sources: Array<SourceNode>): Array<SourceNode> {
508
+ const seen = new Map<string, SourceNode>()
509
+ for (const source of sources) {
510
+ const key = sourceKey(source)
511
+ if (!seen.has(key)) seen.set(key, source)
512
+ }
513
+ return [...seen.values()]
514
+ }
515
+
516
+ /**
517
+ * Deduplicates and merges an array of `ExportNode` objects.
518
+ * Exports with the same path and `isTypeOnly` flag have their names merged.
519
+ */
520
+ export function combineExports(exports: Array<ExportNode>): Array<ExportNode> {
521
+ const result: Array<ExportNode> = []
522
+ // Accumulates array-named exports keyed by `path:isTypeOnly` for name-merging
523
+ const namedByPath = new Map<string, ExportNode>()
524
+ // Deduplicates non-array exports by their exact identity
525
+ const seen = new Set<string>()
526
+
527
+ for (const curr of [...exports].sort((a, b) => {
528
+ const ka = sortKey(a)
529
+ const kb = sortKey(b)
530
+ return ka < kb ? -1 : ka > kb ? 1 : 0
531
+ })) {
532
+ const { name, path, isTypeOnly, asAlias } = curr
533
+
534
+ if (Array.isArray(name)) {
535
+ if (!name.length) continue
536
+
537
+ const key = pathTypeKey(path, isTypeOnly)
538
+ const existing = namedByPath.get(key)
539
+
540
+ if (existing && Array.isArray(existing.name)) {
541
+ existing.name = [...new Set([...existing.name, ...name])]
542
+ } else {
543
+ const newItem: ExportNode = { ...curr, name: [...new Set(name)] }
544
+ result.push(newItem)
545
+ namedByPath.set(key, newItem)
546
+ }
547
+ } else {
548
+ const key = exportKey(path, name, isTypeOnly, asAlias)
549
+ if (!seen.has(key)) {
550
+ result.push(curr)
551
+ seen.add(key)
552
+ }
553
+ }
554
+ }
555
+
556
+ return result
557
+ }
558
+
559
+ /**
560
+ * Deduplicates and merges an array of `ImportNode` objects.
561
+ * Filters out unused imports (names not referenced in `source` or re-exported).
562
+ * Imports with the same path and `isTypeOnly` flag have their names merged.
563
+ */
564
+ export function combineImports(imports: Array<ImportNode>, exports: Array<ExportNode>, source?: string): Array<ImportNode> {
565
+ // Build a lookup of all exported names to retain imports that are re-exported
566
+ const exportedNames = new Set(exports.flatMap((e) => (Array.isArray(e.name) ? e.name : e.name ? [e.name] : [])))
567
+ const isUsed = (importName: string): boolean => !source || source.includes(importName) || exportedNames.has(importName)
568
+
569
+ const result: Array<ImportNode> = []
570
+ // Accumulates array-named imports keyed by `path:isTypeOnly` for name-merging
571
+ const namedByPath = new Map<string, ImportNode>()
572
+ // Deduplicates non-array imports by their exact identity
573
+ const seen = new Set<string>()
574
+
575
+ for (const curr of [...imports].sort((a, b) => {
576
+ const ka = sortKey(a)
577
+ const kb = sortKey(b)
578
+ return ka < kb ? -1 : ka > kb ? 1 : 0
579
+ })) {
580
+ if (curr.path === curr.root) continue
581
+
582
+ const { path, isTypeOnly } = curr
583
+ let { name } = curr
584
+
585
+ if (Array.isArray(name)) {
586
+ name = [...new Set(name)].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.propertyName)))
587
+ if (!name.length) continue
588
+
589
+ const key = pathTypeKey(path, isTypeOnly)
590
+ const existing = namedByPath.get(key)
591
+
592
+ if (existing && Array.isArray(existing.name)) {
593
+ existing.name = [...new Set([...existing.name, ...name])]
594
+ } else {
595
+ const newItem: ImportNode = { ...curr, name }
596
+ result.push(newItem)
597
+ namedByPath.set(key, newItem)
598
+ }
599
+ } else {
600
+ if (name && !isUsed(name)) continue
601
+
602
+ const key = importKey(path, name, isTypeOnly)
603
+ if (!seen.has(key)) {
604
+ result.push(curr)
605
+ seen.add(key)
606
+ }
607
+ }
608
+ }
609
+
610
+ return result
611
+ }
612
+
613
+ /**
614
+ * Recursively extracts all string content embedded in a {@link CodeNode} tree.
615
+ *
616
+ * Includes text node values, and string attribute fields (`params`, `generics`,
617
+ * `returnType`, `type`) that may reference identifiers needing imports.
618
+ * Used by `createFile` to build the full source string for import filtering.
619
+ */
620
+ export function extractStringsFromNodes(nodes: Array<CodeNode> | undefined): string {
621
+ if (!nodes?.length) return ''
622
+ return nodes
623
+ .map((node) => {
624
+ // Backward-compat: compiled plugins may still pass bare strings at runtime
625
+ if (typeof node === 'string') return node as string
626
+ if (node.kind === 'Text') return node.value
627
+ if (node.kind === 'Break') return ''
628
+ if (node.kind === 'Jsx') return node.value
629
+ const parts: string[] = []
630
+ if ('params' in node && node.params) parts.push(node.params)
631
+ if ('generics' in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(', ') : node.generics)
632
+ if ('returnType' in node && node.returnType) parts.push(node.returnType)
633
+ if ('type' in node && typeof node.type === 'string') parts.push(node.type)
634
+ const nested = extractStringsFromNodes(node.nodes)
635
+ if (nested) parts.push(nested)
636
+ return parts.join('\n')
637
+ })
638
+ .filter(Boolean)
639
+ .join('\n')
640
+ }
package/src/visitor.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
2
  import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
3
3
  import { createParameter, createProperty } from './factory.ts'
4
- import type { Node, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode } from './nodes/index.ts'
4
+ import type { InputNode, Node, OperationNode, OutputNode, ParameterNode, PropertyNode, ResponseNode, SchemaNode } from './nodes/index.ts'
5
5
 
6
6
  /**
7
7
  * Creates a small async concurrency limiter.
@@ -51,9 +51,10 @@ type LimitFn = ReturnType<typeof createLimit>
51
51
  * `ParentOf` uses this map to find parent types.
52
52
  */
53
53
  type ParentNodeMap = [
54
- [RootNode, undefined],
55
- [OperationNode, RootNode],
56
- [SchemaNode, RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode],
54
+ [InputNode, undefined],
55
+ [OutputNode, undefined],
56
+ [OperationNode, InputNode],
57
+ [SchemaNode, InputNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode],
57
58
  [PropertyNode, SchemaNode],
58
59
  [ParameterNode, OperationNode],
59
60
  [ResponseNode, OperationNode],
@@ -67,7 +68,7 @@ type ParentNodeMap = [
67
68
  *
68
69
  * @example
69
70
  * ```ts
70
- * type RootParent = ParentOf<RootNode>
71
+ * type InputParent = ParentOf<InputNode>
71
72
  * // undefined
72
73
  * ```
73
74
  *
@@ -80,7 +81,7 @@ type ParentNodeMap = [
80
81
  * @example
81
82
  * ```ts
82
83
  * type SchemaParent = ParentOf<SchemaNode>
83
- * // RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode
84
+ * // InputNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode
84
85
  * ```
85
86
  */
86
87
  export type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unknown]> = ParentNodeMap> = TEntries extends [
@@ -108,7 +109,7 @@ export type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unkno
108
109
  export type VisitorContext<T extends Node = Node> = {
109
110
  /**
110
111
  * Parent node of the currently visited node.
111
- * For `RootNode`, this is `undefined`.
112
+ * For `InputNode`, this is `undefined`.
112
113
  */
113
114
  parent?: ParentOf<T>
114
115
  }
@@ -126,7 +127,8 @@ export type VisitorContext<T extends Node = Node> = {
126
127
  * ```
127
128
  */
128
129
  export type Visitor = {
129
- root?(node: RootNode, context: VisitorContext<RootNode>): void | RootNode
130
+ input?(node: InputNode, context: VisitorContext<InputNode>): void | InputNode
131
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): void | OutputNode
130
132
  operation?(node: OperationNode, context: VisitorContext<OperationNode>): void | OperationNode
131
133
  schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): void | SchemaNode
132
134
  property?(node: PropertyNode, context: VisitorContext<PropertyNode>): void | PropertyNode
@@ -152,7 +154,8 @@ type MaybePromise<T> = T | Promise<T>
152
154
  * ```
153
155
  */
154
156
  export type AsyncVisitor = {
155
- root?(node: RootNode, context: VisitorContext<RootNode>): MaybePromise<void | RootNode>
157
+ input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<void | InputNode>
158
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<void | OutputNode>
156
159
  operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<void | OperationNode>
157
160
  schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<void | SchemaNode>
158
161
  property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<void | PropertyNode>
@@ -173,7 +176,8 @@ export type AsyncVisitor = {
173
176
  * ```
174
177
  */
175
178
  export type CollectVisitor<T> = {
176
- root?(node: RootNode, context: VisitorContext<RootNode>): T | undefined
179
+ input?(node: InputNode, context: VisitorContext<InputNode>): T | undefined
180
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): T | undefined
177
181
  operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | undefined
178
182
  schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | undefined
179
183
  property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | undefined
@@ -263,8 +267,10 @@ export type CollectOptions<T> = CollectVisitor<T> & {
263
267
  */
264
268
  function getChildren(node: Node, recurse: boolean): Array<Node> {
265
269
  switch (node.kind) {
266
- case 'Root':
270
+ case 'Input':
267
271
  return [...node.schemas, ...node.operations]
272
+ case 'Output':
273
+ return []
268
274
  case 'Operation':
269
275
  return [...node.parameters, ...(node.requestBody?.schema ? [node.requestBody.schema] : []), ...node.responses]
270
276
  case 'Schema': {
@@ -290,6 +296,8 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
290
296
  case 'FunctionParameters':
291
297
  case 'Type':
292
298
  return []
299
+ default:
300
+ return []
293
301
  }
294
302
  }
295
303
 
@@ -322,8 +330,11 @@ export async function walk(node: Node, options: WalkOptions): Promise<void> {
322
330
 
323
331
  async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
324
332
  switch (node.kind) {
325
- case 'Root':
326
- await limit(() => visitor.root?.(node, { parent: parent as ParentOf<RootNode> }))
333
+ case 'Input':
334
+ await limit(() => visitor.input?.(node, { parent: parent as ParentOf<InputNode> }))
335
+ break
336
+ case 'Output':
337
+ await limit(() => visitor.output?.(node, { parent: parent as ParentOf<OutputNode> }))
327
338
  break
328
339
  case 'Operation':
329
340
  await limit(() => visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> }))
@@ -373,7 +384,8 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
373
384
  * const next = transform(root, { depth: 'shallow', root: (node) => node })
374
385
  * ```
375
386
  */
376
- export function transform(node: RootNode, options: TransformOptions): RootNode
387
+ export function transform(node: InputNode, options: TransformOptions): InputNode
388
+ export function transform(node: OutputNode, options: TransformOptions): OutputNode
377
389
  export function transform(node: OperationNode, options: TransformOptions): OperationNode
378
390
  export function transform(node: SchemaNode, options: TransformOptions): SchemaNode
379
391
  export function transform(node: PropertyNode, options: TransformOptions): PropertyNode
@@ -385,17 +397,24 @@ export function transform(node: Node, options: TransformOptions): Node {
385
397
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
386
398
 
387
399
  switch (node.kind) {
388
- case 'Root': {
389
- let root = node
390
- const replaced = visitor.root?.(root, { parent: parent as ParentOf<RootNode> })
391
- if (replaced) root = replaced
400
+ case 'Input': {
401
+ let input = node
402
+ const replaced = visitor.input?.(input, { parent: parent as ParentOf<InputNode> })
403
+ if (replaced) input = replaced
392
404
 
393
405
  return {
394
- ...root,
395
- schemas: root.schemas.map((s) => transform(s, { ...options, parent: root })),
396
- operations: root.operations.map((op) => transform(op, { ...options, parent: root })),
406
+ ...input,
407
+ schemas: input.schemas.map((s) => transform(s, { ...options, parent: input })),
408
+ operations: input.operations.map((op) => transform(op, { ...options, parent: input })),
397
409
  }
398
410
  }
411
+ case 'Output': {
412
+ let output = node
413
+ const replaced = visitor.output?.(output, { parent: parent as ParentOf<OutputNode> })
414
+ if (replaced) output = replaced
415
+
416
+ return output
417
+ }
399
418
  case 'Operation': {
400
419
  let op = node
401
420
  const replaced = visitor.operation?.(op, { parent: parent as ParentOf<OperationNode> })
@@ -462,6 +481,8 @@ export function transform(node: Node, options: TransformOptions): Node {
462
481
  case 'FunctionParameters':
463
482
  case 'Type':
464
483
  return node
484
+ default:
485
+ return node
465
486
  }
466
487
  }
467
488
 
@@ -481,8 +502,11 @@ export function transform(node: Node, options: TransformOptions): Node {
481
502
  */
482
503
  export function composeTransformers(...visitors: Array<Visitor>): Visitor {
483
504
  return {
484
- root(node, context) {
485
- return visitors.reduce<RootNode>((acc, v) => v.root?.(acc, context) ?? acc, node)
505
+ input(node, context) {
506
+ return visitors.reduce<InputNode>((acc, v) => v.input?.(acc, context) ?? acc, node)
507
+ },
508
+ output(node, context) {
509
+ return visitors.reduce<OutputNode>((acc, v) => v.output?.(acc, context) ?? acc, node)
486
510
  },
487
511
  operation(node, context) {
488
512
  return visitors.reduce<OperationNode>((acc, v) => v.operation?.(acc, context) ?? acc, node)
@@ -529,8 +553,11 @@ export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
529
553
 
530
554
  let v: T | undefined
531
555
  switch (node.kind) {
532
- case 'Root':
533
- v = visitor.root?.(node, { parent: parent as ParentOf<RootNode> })
556
+ case 'Input':
557
+ v = visitor.input?.(node, { parent: parent as ParentOf<InputNode> })
558
+ break
559
+ case 'Output':
560
+ v = visitor.output?.(node, { parent: parent as ParentOf<OutputNode> })
534
561
  break
535
562
  case 'Operation':
536
563
  v = visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> })