@kubb/ast 5.0.0-alpha.31 → 5.0.0-alpha.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1556 -1067
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +42 -3
- package/dist/index.js +1519 -1067
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-z-5U8NoF.d.ts → visitor-DysNCWvh.d.ts} +794 -61
- package/package.json +1 -1
- package/src/constants.ts +7 -1
- package/src/factory.ts +296 -24
- package/src/guards.ts +17 -4
- package/src/index.ts +23 -5
- package/src/mocks.ts +6 -6
- package/src/nodes/base.ts +11 -2
- package/src/nodes/code.ts +237 -0
- package/src/nodes/file.ts +235 -0
- package/src/nodes/function.ts +35 -17
- package/src/nodes/index.ts +32 -7
- package/src/nodes/output.ts +26 -0
- package/src/nodes/root.ts +9 -8
- package/src/refs.ts +5 -5
- package/src/types.ts +14 -2
- package/src/utils.ts +177 -19
- package/src/visitor.ts +52 -25
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:
|
|
11
|
+
* const meta: InputMeta = { title: 'Pet API', version: '1.0.0' }
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
export type
|
|
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
|
-
*
|
|
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
|
|
40
|
-
* kind: '
|
|
40
|
+
* const input: InputNode = {
|
|
41
|
+
* kind: 'Input',
|
|
41
42
|
* schemas: [],
|
|
42
43
|
* operations: [],
|
|
43
44
|
* }
|
|
44
45
|
* ```
|
|
45
46
|
*/
|
|
46
|
-
export type
|
|
47
|
+
export type InputNode = BaseNode & {
|
|
47
48
|
/**
|
|
48
49
|
* Node kind.
|
|
49
50
|
*/
|
|
50
|
-
kind: '
|
|
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?:
|
|
63
|
+
meta?: InputMeta
|
|
63
64
|
}
|
package/src/refs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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 `
|
|
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(
|
|
30
|
+
* const refMap = buildRefMap(input)
|
|
31
31
|
* const pet = refMap.get('Pet')
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
-
export function buildRefMap(
|
|
34
|
+
export function buildRefMap(input: InputNode): RefMap {
|
|
35
35
|
const map: RefMap = new Map()
|
|
36
36
|
|
|
37
|
-
for (const schema of
|
|
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,58 @@ 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
|
+
CodeNode,
|
|
7
9
|
ComplexSchemaType,
|
|
10
|
+
ConstNode,
|
|
8
11
|
DateSchemaNode,
|
|
9
12
|
DatetimeSchemaNode,
|
|
10
13
|
EnumSchemaNode,
|
|
11
14
|
EnumValueNode,
|
|
15
|
+
ExportNode,
|
|
16
|
+
FileNode,
|
|
12
17
|
FormatStringSchemaNode,
|
|
13
18
|
FunctionNode,
|
|
14
19
|
FunctionNodeType,
|
|
15
20
|
FunctionParameterNode,
|
|
16
21
|
FunctionParametersNode,
|
|
22
|
+
FunctionParamNode,
|
|
17
23
|
HttpMethod,
|
|
18
24
|
HttpStatusCode,
|
|
25
|
+
ImportNode,
|
|
26
|
+
InputMeta,
|
|
27
|
+
InputNode,
|
|
19
28
|
IntersectionSchemaNode,
|
|
20
29
|
Ipv4SchemaNode,
|
|
21
30
|
Ipv6SchemaNode,
|
|
31
|
+
JSDocNode,
|
|
22
32
|
MediaType,
|
|
23
33
|
Node,
|
|
24
34
|
NodeKind,
|
|
25
35
|
NumberSchemaNode,
|
|
26
36
|
ObjectSchemaNode,
|
|
27
37
|
OperationNode,
|
|
38
|
+
OutputNode,
|
|
28
39
|
ParameterGroupNode,
|
|
29
40
|
ParameterLocation,
|
|
30
41
|
ParameterNode,
|
|
42
|
+
ParamsTypeNode,
|
|
31
43
|
PrimitiveSchemaType,
|
|
32
44
|
PropertyNode,
|
|
33
45
|
RefSchemaNode,
|
|
34
46
|
ResponseNode,
|
|
35
|
-
RootMeta,
|
|
36
|
-
RootNode,
|
|
37
47
|
ScalarSchemaNode,
|
|
38
48
|
ScalarSchemaType,
|
|
39
49
|
SchemaNode,
|
|
40
50
|
SchemaNodeByType,
|
|
41
51
|
SchemaType,
|
|
52
|
+
SourceNode,
|
|
42
53
|
SpecialSchemaType,
|
|
43
54
|
StatusCode,
|
|
44
55
|
StringSchemaNode,
|
|
45
56
|
TimeSchemaNode,
|
|
57
|
+
TypeDeclarationNode,
|
|
46
58
|
TypeNode,
|
|
47
59
|
UnionSchemaNode,
|
|
48
60
|
UrlSchemaNode,
|
package/src/utils.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { camelCase, isValidVarName } from '@internals/utils'
|
|
2
2
|
|
|
3
|
-
import { createFunctionParameter, createFunctionParameters, createParameterGroup, createProperty, createSchema
|
|
3
|
+
import { createFunctionParameter, createFunctionParameters, createParameterGroup, createParamsType, createProperty, createSchema } from './factory.ts'
|
|
4
4
|
import { narrowSchema } from './guards.ts'
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ExportNode,
|
|
7
|
+
FunctionParameterNode,
|
|
8
|
+
FunctionParametersNode,
|
|
9
|
+
ImportNode,
|
|
10
|
+
OperationNode,
|
|
11
|
+
ParameterGroupNode,
|
|
12
|
+
ParameterNode,
|
|
13
|
+
ParamsTypeNode,
|
|
14
|
+
SchemaNode,
|
|
15
|
+
SourceNode,
|
|
16
|
+
} from './nodes/index.ts'
|
|
6
17
|
import type { SchemaType } from './nodes/schema.ts'
|
|
7
18
|
|
|
8
19
|
const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
|
|
@@ -118,7 +129,7 @@ export type ParamGroupType = {
|
|
|
118
129
|
/**
|
|
119
130
|
* TypeNode for the group type.
|
|
120
131
|
*/
|
|
121
|
-
type:
|
|
132
|
+
type: ParamsTypeNode
|
|
122
133
|
/**
|
|
123
134
|
* Whether the parameter group is optional.
|
|
124
135
|
*/
|
|
@@ -251,9 +262,17 @@ export type CreateOperationParamsOptions = {
|
|
|
251
262
|
typeWrapper?: (type: string) => string
|
|
252
263
|
}
|
|
253
264
|
|
|
254
|
-
function
|
|
265
|
+
function resolveParamsType({
|
|
266
|
+
node,
|
|
267
|
+
param,
|
|
268
|
+
resolver,
|
|
269
|
+
}: {
|
|
270
|
+
node: OperationNode
|
|
271
|
+
param: ParameterNode
|
|
272
|
+
resolver: OperationParamsResolver | undefined
|
|
273
|
+
}): ParamsTypeNode {
|
|
255
274
|
if (!resolver) {
|
|
256
|
-
return
|
|
275
|
+
return createParamsType({ variant: 'reference', name: param.schema.primitive ?? 'unknown' })
|
|
257
276
|
}
|
|
258
277
|
|
|
259
278
|
const individualName = resolver.resolveParamName(node, param)
|
|
@@ -269,10 +288,10 @@ function resolveType({ node, param, resolver }: { node: OperationNode; param: Pa
|
|
|
269
288
|
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : undefined
|
|
270
289
|
|
|
271
290
|
if (groupName && groupName !== individualName) {
|
|
272
|
-
return
|
|
291
|
+
return createParamsType({ variant: 'member', base: groupName, key: param.name })
|
|
273
292
|
}
|
|
274
293
|
|
|
275
|
-
return
|
|
294
|
+
return createParamsType({ variant: 'reference', name: individualName })
|
|
276
295
|
}
|
|
277
296
|
|
|
278
297
|
/**
|
|
@@ -287,7 +306,7 @@ function resolveType({ node, param, resolver }: { node: OperationNode; param: Pa
|
|
|
287
306
|
* paramsType: 'inline',
|
|
288
307
|
* pathParamsType: 'inline',
|
|
289
308
|
* resolver: tsResolver,
|
|
290
|
-
* extraParams: [createFunctionParameter({ name: 'options', type:
|
|
309
|
+
* extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
|
|
291
310
|
* })
|
|
292
311
|
* ```
|
|
293
312
|
*/
|
|
@@ -299,10 +318,10 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
|
|
|
299
318
|
const headersName = paramNames?.headers ?? 'headers'
|
|
300
319
|
const pathName = paramNames?.path ?? 'pathParams'
|
|
301
320
|
|
|
302
|
-
const wrapType = (type: string):
|
|
303
|
-
// Only reference TypeNodes are wrapped
|
|
321
|
+
const wrapType = (type: string): ParamsTypeNode => createParamsType({ variant: 'reference', name: typeWrapper ? typeWrapper(type) : type })
|
|
322
|
+
// Only reference-variant TypeNodes are wrapped — they hold a plain type name string that needs casing applied.
|
|
304
323
|
// Member and struct TypeNodes are pre-resolved structured expressions and are passed through unchanged.
|
|
305
|
-
const wrapTypeNode = (type:
|
|
324
|
+
const wrapTypeNode = (type: ParamsTypeNode): ParamsTypeNode => (type.kind === 'ParamsType' && type.variant === 'reference' ? wrapType(type.name) : type)
|
|
306
325
|
|
|
307
326
|
const casedParams = caseParams(node.parameters, paramsCasing)
|
|
308
327
|
const pathParams = casedParams.filter((p) => p.in === 'path')
|
|
@@ -320,7 +339,7 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
|
|
|
320
339
|
if (paramsType === 'object') {
|
|
321
340
|
const children: Array<FunctionParameterNode> = [
|
|
322
341
|
...pathParams.map((p) => {
|
|
323
|
-
const type =
|
|
342
|
+
const type = resolveParamsType({ node, param: p, resolver })
|
|
324
343
|
return createFunctionParameter({ name: p.name, type: wrapTypeNode(type), optional: !p.required })
|
|
325
344
|
}),
|
|
326
345
|
...(bodyType ? [createFunctionParameter({ name: dataName, type: bodyType, optional: !bodyRequired })] : []),
|
|
@@ -338,7 +357,7 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
|
|
|
338
357
|
params.push(createFunctionParameter({ name: pathName, type: spreadType ? wrapType(spreadType) : undefined, rest: true }))
|
|
339
358
|
} else {
|
|
340
359
|
const pathChildren = pathParams.map((p) => {
|
|
341
|
-
const type =
|
|
360
|
+
const type = resolveParamsType({ node, param: p, resolver })
|
|
342
361
|
return createFunctionParameter({ name: p.name, type: wrapTypeNode(type), optional: !p.required })
|
|
343
362
|
})
|
|
344
363
|
params.push(
|
|
@@ -384,10 +403,10 @@ function buildGroupParam({
|
|
|
384
403
|
params: Array<ParameterNode>
|
|
385
404
|
groupType: ParamGroupType | undefined
|
|
386
405
|
resolver: OperationParamsResolver | undefined
|
|
387
|
-
wrapType: (type: string) =>
|
|
406
|
+
wrapType: (type: string) => ParamsTypeNode
|
|
388
407
|
}): Array<FunctionParameterNode> {
|
|
389
408
|
if (groupType) {
|
|
390
|
-
const type = groupType.type.variant === 'reference' ? wrapType(groupType.type.name) : groupType.type
|
|
409
|
+
const type = groupType.type.kind === 'ParamsType' && groupType.type.variant === 'reference' ? wrapType(groupType.type.name) : groupType.type
|
|
391
410
|
return [createFunctionParameter({ name, type, optional: groupType.optional })]
|
|
392
411
|
}
|
|
393
412
|
if (params.length) {
|
|
@@ -426,7 +445,7 @@ function resolveGroupType({
|
|
|
426
445
|
return undefined
|
|
427
446
|
}
|
|
428
447
|
const allOptional = params.every((p) => !p.required)
|
|
429
|
-
return { type:
|
|
448
|
+
return { type: createParamsType({ variant: 'reference', name: groupName }), optional: allOptional }
|
|
430
449
|
}
|
|
431
450
|
|
|
432
451
|
/**
|
|
@@ -443,9 +462,148 @@ function toStructType({
|
|
|
443
462
|
node: OperationNode
|
|
444
463
|
params: Array<ParameterNode>
|
|
445
464
|
resolver: OperationParamsResolver | undefined
|
|
446
|
-
}):
|
|
447
|
-
return
|
|
465
|
+
}): ParamsTypeNode {
|
|
466
|
+
return createParamsType({
|
|
448
467
|
variant: 'struct',
|
|
449
|
-
properties: params.map((p) => ({ name: p.name, optional: !p.required, type:
|
|
468
|
+
properties: params.map((p) => ({ name: p.name, optional: !p.required, type: resolveParamsType({ node, param: p, resolver }) })),
|
|
450
469
|
})
|
|
451
470
|
}
|
|
471
|
+
|
|
472
|
+
function sourceKey(source: SourceNode): string {
|
|
473
|
+
return `${source.name ?? source.value ?? ''}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function pathTypeKey(path: string, isTypeOnly: boolean | undefined): string {
|
|
477
|
+
return `${path}:${isTypeOnly ?? false}`
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function exportKey(path: string, name: string | undefined, isTypeOnly: boolean | undefined, asAlias: boolean | undefined): string {
|
|
481
|
+
return `${path}:${name ?? ''}:${isTypeOnly ?? false}:${asAlias ?? ''}`
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function importKey(path: string, name: string | undefined, isTypeOnly: boolean | undefined): string {
|
|
485
|
+
return `${path}:${name ?? ''}:${isTypeOnly ?? false}`
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Computes a multi-level sort key for exports and imports:
|
|
490
|
+
* non-array names first (wildcards/namespace aliases); type-only before value; alphabetical path; unnamed before named.
|
|
491
|
+
*/
|
|
492
|
+
function sortKey(node: { name?: string | Array<unknown>; isTypeOnly?: boolean; path: string }): string {
|
|
493
|
+
const isArray = Array.isArray(node.name) ? '1' : '0'
|
|
494
|
+
const typeOnly = node.isTypeOnly ? '0' : '1'
|
|
495
|
+
const hasName = node.name != null ? '1' : '0'
|
|
496
|
+
const name = Array.isArray(node.name) ? [...node.name].sort().join('\0') : (node.name ?? '')
|
|
497
|
+
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Deduplicates an array of `SourceNode` objects.
|
|
502
|
+
* Named sources are deduplicated by `name + isExportable + isTypeOnly`.
|
|
503
|
+
* Unnamed sources are deduplicated by `value`.
|
|
504
|
+
*/
|
|
505
|
+
export function combineSources(sources: Array<SourceNode>): Array<SourceNode> {
|
|
506
|
+
const seen = new Map<string, SourceNode>()
|
|
507
|
+
for (const source of sources) {
|
|
508
|
+
const key = sourceKey(source)
|
|
509
|
+
if (!seen.has(key)) seen.set(key, source)
|
|
510
|
+
}
|
|
511
|
+
return [...seen.values()]
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Deduplicates and merges an array of `ExportNode` objects.
|
|
516
|
+
* Exports with the same path and `isTypeOnly` flag have their names merged.
|
|
517
|
+
*/
|
|
518
|
+
export function combineExports(exports: Array<ExportNode>): Array<ExportNode> {
|
|
519
|
+
const result: Array<ExportNode> = []
|
|
520
|
+
// Accumulates array-named exports keyed by `path:isTypeOnly` for name-merging
|
|
521
|
+
const namedByPath = new Map<string, ExportNode>()
|
|
522
|
+
// Deduplicates non-array exports by their exact identity
|
|
523
|
+
const seen = new Set<string>()
|
|
524
|
+
|
|
525
|
+
for (const curr of [...exports].sort((a, b) => {
|
|
526
|
+
const ka = sortKey(a)
|
|
527
|
+
const kb = sortKey(b)
|
|
528
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0
|
|
529
|
+
})) {
|
|
530
|
+
const { name, path, isTypeOnly, asAlias } = curr
|
|
531
|
+
|
|
532
|
+
if (Array.isArray(name)) {
|
|
533
|
+
if (!name.length) continue
|
|
534
|
+
|
|
535
|
+
const key = pathTypeKey(path, isTypeOnly)
|
|
536
|
+
const existing = namedByPath.get(key)
|
|
537
|
+
|
|
538
|
+
if (existing && Array.isArray(existing.name)) {
|
|
539
|
+
existing.name = [...new Set([...existing.name, ...name])]
|
|
540
|
+
} else {
|
|
541
|
+
const newItem: ExportNode = { ...curr, name: [...new Set(name)] }
|
|
542
|
+
result.push(newItem)
|
|
543
|
+
namedByPath.set(key, newItem)
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
const key = exportKey(path, name, isTypeOnly, asAlias)
|
|
547
|
+
if (!seen.has(key)) {
|
|
548
|
+
result.push(curr)
|
|
549
|
+
seen.add(key)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return result
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Deduplicates and merges an array of `ImportNode` objects.
|
|
559
|
+
* Filters out unused imports (names not referenced in `source` or re-exported).
|
|
560
|
+
* Imports with the same path and `isTypeOnly` flag have their names merged.
|
|
561
|
+
*/
|
|
562
|
+
export function combineImports(imports: Array<ImportNode>, exports: Array<ExportNode>, source?: string): Array<ImportNode> {
|
|
563
|
+
// Build a lookup of all exported names to retain imports that are re-exported
|
|
564
|
+
const exportedNames = new Set(exports.flatMap((e) => (Array.isArray(e.name) ? e.name : e.name ? [e.name] : [])))
|
|
565
|
+
const isUsed = (importName: string): boolean => !source || source.includes(importName) || exportedNames.has(importName)
|
|
566
|
+
|
|
567
|
+
const result: Array<ImportNode> = []
|
|
568
|
+
// Accumulates array-named imports keyed by `path:isTypeOnly` for name-merging
|
|
569
|
+
const namedByPath = new Map<string, ImportNode>()
|
|
570
|
+
// Deduplicates non-array imports by their exact identity
|
|
571
|
+
const seen = new Set<string>()
|
|
572
|
+
|
|
573
|
+
for (const curr of [...imports].sort((a, b) => {
|
|
574
|
+
const ka = sortKey(a)
|
|
575
|
+
const kb = sortKey(b)
|
|
576
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0
|
|
577
|
+
})) {
|
|
578
|
+
if (curr.path === curr.root) continue
|
|
579
|
+
|
|
580
|
+
const { path, isTypeOnly } = curr
|
|
581
|
+
let { name } = curr
|
|
582
|
+
|
|
583
|
+
if (Array.isArray(name)) {
|
|
584
|
+
name = [...new Set(name)].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.propertyName)))
|
|
585
|
+
if (!name.length) continue
|
|
586
|
+
|
|
587
|
+
const key = pathTypeKey(path, isTypeOnly)
|
|
588
|
+
const existing = namedByPath.get(key)
|
|
589
|
+
|
|
590
|
+
if (existing && Array.isArray(existing.name)) {
|
|
591
|
+
existing.name = [...new Set([...existing.name, ...name])]
|
|
592
|
+
} else {
|
|
593
|
+
const newItem: ImportNode = { ...curr, name }
|
|
594
|
+
result.push(newItem)
|
|
595
|
+
namedByPath.set(key, newItem)
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
if (name && !isUsed(name)) continue
|
|
599
|
+
|
|
600
|
+
const key = importKey(path, name, isTypeOnly)
|
|
601
|
+
if (!seen.has(key)) {
|
|
602
|
+
result.push(curr)
|
|
603
|
+
seen.add(key)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return result
|
|
609
|
+
}
|
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,
|
|
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
|
-
[
|
|
55
|
-
[
|
|
56
|
-
[
|
|
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
|
|
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
|
-
* //
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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 '
|
|
326
|
-
await limit(() => visitor.
|
|
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:
|
|
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 '
|
|
389
|
-
let
|
|
390
|
-
const replaced = visitor.
|
|
391
|
-
if (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
|
-
...
|
|
395
|
-
schemas:
|
|
396
|
-
operations:
|
|
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
|
-
|
|
485
|
-
return visitors.reduce<
|
|
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 '
|
|
533
|
-
v = visitor.
|
|
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> })
|