@kubb/ast 5.0.0-beta.6 → 5.0.0-beta.60
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/LICENSE +17 -10
- package/README.md +50 -27
- package/dist/chunk-CNktS9qV.js +17 -0
- package/dist/extractStringsFromNodes-WMYJ8nQL.d.ts +82 -0
- package/dist/factory-Cl8Z7mcc.cjs +299 -0
- package/dist/factory-Cl8Z7mcc.cjs.map +1 -0
- package/dist/factory-Du7nEP4B.js +282 -0
- package/dist/factory-Du7nEP4B.js.map +1 -0
- package/dist/factory.cjs +29 -0
- package/dist/factory.d.ts +62 -0
- package/dist/factory.js +3 -0
- package/dist/index-BzjwdK2M.d.ts +2433 -0
- package/dist/index.cjs +442 -2180
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +93 -3408
- package/dist/index.js +392 -2101
- package/dist/index.js.map +1 -1
- package/dist/operationParams-BZ07xDm0.d.ts +204 -0
- package/dist/response-DKxTr522.js +683 -0
- package/dist/response-DKxTr522.js.map +1 -0
- package/dist/response-DS5S3IG4.cjs +1058 -0
- package/dist/response-DS5S3IG4.cjs.map +1 -0
- package/dist/types-olVl9v5p.d.ts +764 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/dist/utils-D83JA6Xx.cjs +1645 -0
- package/dist/utils-D83JA6Xx.cjs.map +1 -0
- package/dist/utils-Dj_KoXMv.js +1389 -0
- package/dist/utils-Dj_KoXMv.js.map +1 -0
- package/dist/utils.cjs +34 -0
- package/dist/utils.d.ts +332 -0
- package/dist/utils.js +3 -0
- package/package.json +17 -6
- package/src/constants.ts +19 -64
- package/src/dedupe.ts +239 -0
- package/src/dialect.ts +53 -0
- package/src/factory.ts +67 -678
- package/src/guards.ts +10 -92
- package/src/index.ts +16 -43
- package/src/infer.ts +16 -14
- package/src/mocks.ts +7 -127
- package/src/node.ts +128 -0
- package/src/nodes/base.ts +5 -12
- package/src/nodes/code.ts +165 -74
- package/src/nodes/content.ts +56 -0
- package/src/nodes/file.ts +97 -36
- package/src/nodes/function.ts +216 -156
- package/src/nodes/http.ts +1 -35
- package/src/nodes/index.ts +23 -15
- package/src/nodes/input.ts +140 -0
- package/src/nodes/operation.ts +122 -68
- package/src/nodes/output.ts +23 -0
- package/src/nodes/parameter.ts +33 -3
- package/src/nodes/property.ts +36 -3
- package/src/nodes/requestBody.ts +61 -0
- package/src/nodes/response.ts +58 -13
- package/src/nodes/schema.ts +93 -17
- package/src/printer.ts +48 -42
- package/src/registry.ts +75 -0
- package/src/signature.ts +207 -0
- package/src/transformers.ts +50 -18
- package/src/types.ts +7 -68
- package/src/utils/codegen.ts +104 -0
- package/src/utils/extractStringsFromNodes.ts +34 -0
- package/src/utils/fileMerge.ts +184 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/operationParams.ts +353 -0
- package/src/utils/refs.ts +112 -0
- package/src/utils/schemaGraph.ts +169 -0
- package/src/utils/schemaTraversal.ts +86 -0
- package/src/utils/strings.ts +139 -0
- package/src/visitor.ts +227 -289
- package/dist/chunk--u3MIqq1.js +0 -8
- package/src/nodes/root.ts +0 -64
- package/src/refs.ts +0 -67
- package/src/resolvers.ts +0 -45
- package/src/utils.ts +0 -915
package/src/guards.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FunctionParameterNode,
|
|
3
|
-
FunctionParametersNode,
|
|
4
|
-
InputNode,
|
|
5
|
-
Node,
|
|
6
|
-
NodeKind,
|
|
7
|
-
OperationNode,
|
|
8
|
-
OutputNode,
|
|
9
|
-
ParameterGroupNode,
|
|
10
|
-
ParameterNode,
|
|
11
|
-
PropertyNode,
|
|
12
|
-
ResponseNode,
|
|
13
|
-
SchemaNode,
|
|
14
|
-
SchemaNodeByType,
|
|
15
|
-
} from './nodes/index.ts'
|
|
1
|
+
import type { HttpOperationNode, OperationNode, SchemaNode, SchemaNodeByType } from './nodes/index.ts'
|
|
16
2
|
|
|
17
3
|
/**
|
|
18
4
|
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
@@ -20,91 +6,23 @@ import type {
|
|
|
20
6
|
* @example
|
|
21
7
|
* ```ts
|
|
22
8
|
* const schema = createSchema({ type: 'string' })
|
|
23
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode |
|
|
9
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
24
10
|
* ```
|
|
25
11
|
*/
|
|
26
|
-
export function narrowSchema<T extends SchemaNode['type']>(node: SchemaNode | undefined, type: T): SchemaNodeByType[T] |
|
|
27
|
-
return node?.type === type ? (node as SchemaNodeByType[T]) :
|
|
12
|
+
export function narrowSchema<T extends SchemaNode['type']>(node: SchemaNode | undefined, type: T): SchemaNodeByType[T] | null {
|
|
13
|
+
return node?.type === type ? (node as SchemaNodeByType[T]) : null
|
|
28
14
|
}
|
|
29
15
|
|
|
30
|
-
function isKind<T extends Node>(kind: NodeKind) {
|
|
31
|
-
return (node: unknown): node is T => (node as Node).kind === kind
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Returns `true` when the input is an `InputNode`.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* if (isInputNode(node)) {
|
|
40
|
-
* console.log(node.schemas.length)
|
|
41
|
-
* }
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export const isInputNode = isKind<InputNode>('Input')
|
|
45
|
-
|
|
46
16
|
/**
|
|
47
|
-
*
|
|
17
|
+
* Narrows an `OperationNode` to an `HttpOperationNode` so `method` and `path` are present.
|
|
48
18
|
*
|
|
49
19
|
* @example
|
|
50
20
|
* ```ts
|
|
51
|
-
* if (
|
|
52
|
-
* console.log(node.
|
|
21
|
+
* if (isHttpOperationNode(node)) {
|
|
22
|
+
* console.log(node.method, node.path)
|
|
53
23
|
* }
|
|
54
24
|
* ```
|
|
55
25
|
*/
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* Returns `true` when the input is an `OperationNode`.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```ts
|
|
63
|
-
* if (isOperationNode(node)) {
|
|
64
|
-
* console.log(node.operationId)
|
|
65
|
-
* }
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export const isOperationNode = isKind<OperationNode>('Operation')
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Returns `true` when the input is a `SchemaNode`.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* if (isSchemaNode(node)) {
|
|
76
|
-
* console.log(node.type)
|
|
77
|
-
* }
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
export const isSchemaNode = isKind<SchemaNode>('Schema')
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Returns `true` when the input is a `PropertyNode`.
|
|
84
|
-
*/
|
|
85
|
-
export const isPropertyNode = isKind<PropertyNode>('Property')
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Returns `true` when the input is a `ParameterNode`.
|
|
89
|
-
*/
|
|
90
|
-
export const isParameterNode = isKind<ParameterNode>('Parameter')
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Returns `true` when the input is a `ResponseNode`.
|
|
94
|
-
*/
|
|
95
|
-
export const isResponseNode = isKind<ResponseNode>('Response')
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Returns `true` when the input is a `FunctionParameterNode`.
|
|
99
|
-
*/
|
|
100
|
-
export const isFunctionParameterNode = isKind<FunctionParameterNode>('FunctionParameter')
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Returns `true` when the input is a `ParameterGroupNode`.
|
|
104
|
-
*/
|
|
105
|
-
export const isParameterGroupNode = isKind<ParameterGroupNode>('ParameterGroup')
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Returns `true` when the input is a `FunctionParametersNode`.
|
|
109
|
-
*/
|
|
110
|
-
export const isFunctionParametersNode = isKind<FunctionParametersNode>('FunctionParameters')
|
|
26
|
+
export function isHttpOperationNode(node: OperationNode): node is HttpOperationNode {
|
|
27
|
+
return node.protocol === 'http' || (node.method !== undefined && node.path !== undefined)
|
|
28
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,46 +1,19 @@
|
|
|
1
|
-
export { httpMethods,
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
createJsx,
|
|
14
|
-
createOperation,
|
|
15
|
-
createOutput,
|
|
16
|
-
createParameter,
|
|
17
|
-
createParameterGroup,
|
|
18
|
-
createParamsType,
|
|
19
|
-
createProperty,
|
|
20
|
-
createResponse,
|
|
21
|
-
createSchema,
|
|
22
|
-
createSource,
|
|
23
|
-
createText,
|
|
24
|
-
createType,
|
|
25
|
-
syncOptionality,
|
|
26
|
-
} from './factory.ts'
|
|
27
|
-
export { isInputNode, isOperationNode, isOutputNode, isSchemaNode, narrowSchema } from './guards.ts'
|
|
1
|
+
export { httpMethods, schemaTypes } from './constants.ts'
|
|
2
|
+
export { applyDedupe, buildDedupePlan } from './dedupe.ts'
|
|
3
|
+
export { defineSchemaDialect } from './dialect.ts'
|
|
4
|
+
// Node constructors, also published as the `@kubb/ast/factory` subpath, mirroring `ts.factory.createX`.
|
|
5
|
+
export * as factory from './factory.ts'
|
|
6
|
+
export { isHttpOperationNode, narrowSchema } from './guards.ts'
|
|
7
|
+
export { defineNode } from './node.ts'
|
|
8
|
+
export type { NodeDef } from './node.ts'
|
|
9
|
+
export { syncOptionality } from './node.ts'
|
|
10
|
+
// Every node def plus `nodeDefs` in one line, so adding a node never edits this barrel. The visitor
|
|
11
|
+
// tables derived from `nodeDefs` stay internal to `visitor.ts`.
|
|
12
|
+
export * from './registry.ts'
|
|
28
13
|
export { createPrinterFactory, definePrinter } from './printer.ts'
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export { mergeAdjacentObjects, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
|
|
14
|
+
export { signatureOf } from './signature.ts'
|
|
15
|
+
export { mergeAdjacentObjectsLazy, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
|
|
32
16
|
export type * from './types.ts'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
collectReferencedSchemaNames,
|
|
36
|
-
collectUsedSchemaNames,
|
|
37
|
-
containsCircularRef,
|
|
38
|
-
createDiscriminantNode,
|
|
39
|
-
createOperationParams,
|
|
40
|
-
extractStringsFromNodes,
|
|
41
|
-
findCircularSchemas,
|
|
42
|
-
isStringType,
|
|
43
|
-
resolveRefName,
|
|
44
|
-
syncSchemaRef,
|
|
45
|
-
} from './utils.ts'
|
|
17
|
+
// The node/AST helpers in ./utils/ live on the `@kubb/ast/utils` subpath, not the root barrel.
|
|
18
|
+
export { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
|
|
46
19
|
export { collect, transform, walk } from './visitor.ts'
|
package/src/infer.ts
CHANGED
|
@@ -20,15 +20,25 @@ import type {
|
|
|
20
20
|
*/
|
|
21
21
|
export type ParserOptions = {
|
|
22
22
|
/**
|
|
23
|
-
* How `format: 'date-time'` schemas are represented
|
|
23
|
+
* How `format: 'date-time'` schemas are represented downstream.
|
|
24
|
+
* - `false` falls through to a plain `string` (no validation).
|
|
25
|
+
* - `'string'` emits a datetime string node.
|
|
26
|
+
* - `'stringOffset'` emits a datetime node with timezone offset.
|
|
27
|
+
* - `'stringLocal'` emits a local datetime node.
|
|
28
|
+
* - `'date'` emits a `date` node (JavaScript `Date` object).
|
|
24
29
|
*/
|
|
25
30
|
dateType: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
32
|
+
* How `type: 'integer'` (and `format: 'int64'`) maps to TypeScript.
|
|
33
|
+
* - `'bigint'` is exact for 64-bit IDs, but does not round-trip through JSON.
|
|
34
|
+
* - `'number'` fits most JSON APIs. Loses precision above `Number.MAX_SAFE_INTEGER`.
|
|
35
|
+
*
|
|
36
|
+
* @default 'bigint'
|
|
28
37
|
*/
|
|
29
38
|
integerType?: 'number' | 'bigint'
|
|
30
39
|
/**
|
|
31
|
-
* AST type used when
|
|
40
|
+
* AST type used when a schema's type cannot be inferred from the spec
|
|
41
|
+
* (`additionalProperties: true`, missing `type`, ...).
|
|
32
42
|
*/
|
|
33
43
|
unknownType: 'any' | 'unknown' | 'void'
|
|
34
44
|
/**
|
|
@@ -36,7 +46,8 @@ export type ParserOptions = {
|
|
|
36
46
|
*/
|
|
37
47
|
emptySchemaType: 'any' | 'unknown' | 'void'
|
|
38
48
|
/**
|
|
39
|
-
* Suffix appended to derived enum names when
|
|
49
|
+
* Suffix appended to derived enum names when Kubb has to invent one
|
|
50
|
+
* (typically for inline enums on object properties).
|
|
40
51
|
*/
|
|
41
52
|
enumSuffix: 'enum' | (string & {})
|
|
42
53
|
}
|
|
@@ -66,7 +77,7 @@ type ResolveDateTimeNode<TDateType extends ParserOptions['dateType']> = DateTime
|
|
|
66
77
|
type SchemaNodeMap<TDateType extends ParserOptions['dateType'] = 'string'> = [
|
|
67
78
|
[{ $ref: string }, RefSchemaNode],
|
|
68
79
|
[{ allOf: ReadonlyArray<unknown>; properties: object }, IntersectionSchemaNode],
|
|
69
|
-
[{ allOf: readonly [unknown, unknown, ...unknown
|
|
80
|
+
[{ allOf: readonly [unknown, unknown, ...Array<unknown>] }, IntersectionSchemaNode],
|
|
70
81
|
[{ allOf: ReadonlyArray<unknown> }, SchemaNode],
|
|
71
82
|
[{ oneOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
72
83
|
[{ anyOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
@@ -119,12 +130,3 @@ export type InferSchemaNode<
|
|
|
119
130
|
? TEntry[1]
|
|
120
131
|
: InferSchemaNode<TSchema, TDateType, TRest>
|
|
121
132
|
: SchemaNode
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Backward-compatible alias for `InferSchemaNode`.
|
|
125
|
-
*/
|
|
126
|
-
export type InferSchema<
|
|
127
|
-
TSchema extends object,
|
|
128
|
-
TDateType extends ParserOptions['dateType'] = 'string',
|
|
129
|
-
TEntries extends ReadonlyArray<[object, SchemaNode]> = SchemaNodeMap<TDateType>,
|
|
130
|
-
> = InferSchemaNode<TSchema, TDateType, TEntries>
|
package/src/mocks.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { createInput
|
|
2
|
-
import type { InputNode } from './nodes/
|
|
1
|
+
import { createInput } from './nodes/input.ts'
|
|
2
|
+
import type { InputNode } from './nodes/input.ts'
|
|
3
|
+
import { createOperation } from './nodes/operation.ts'
|
|
4
|
+
import { createParameter } from './nodes/parameter.ts'
|
|
5
|
+
import { createProperty } from './nodes/property.ts'
|
|
6
|
+
import { createResponse } from './nodes/response.ts'
|
|
7
|
+
import { createSchema } from './nodes/schema.ts'
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Builds a minimal sample AST with one `Pet` schema and one `getPetById` operation.
|
|
@@ -49,128 +54,3 @@ export function buildSampleTree(): InputNode {
|
|
|
49
54
|
|
|
50
55
|
return createInput({ schemas: [petSchema], operations: [operation] })
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Builds a PetStore-like fixture AST with:
|
|
55
|
-
* - six named schemas (`Pet`, `NewPet`, `PetList`, `Error`, `PetOrError`, `FullPet`)
|
|
56
|
-
* - two operations (`listPets`, `createPet`)
|
|
57
|
-
*/
|
|
58
|
-
export function buildFixture(): InputNode {
|
|
59
|
-
const refPet = createSchema({ type: 'ref', ref: 'Pet' })
|
|
60
|
-
const refError = createSchema({ type: 'ref', ref: 'Error' })
|
|
61
|
-
|
|
62
|
-
return createInput({
|
|
63
|
-
schemas: [
|
|
64
|
-
createSchema({
|
|
65
|
-
name: 'Pet',
|
|
66
|
-
type: 'object',
|
|
67
|
-
properties: [
|
|
68
|
-
createProperty({
|
|
69
|
-
name: 'id',
|
|
70
|
-
schema: createSchema({ type: 'integer' }),
|
|
71
|
-
required: true,
|
|
72
|
-
}),
|
|
73
|
-
createProperty({
|
|
74
|
-
name: 'name',
|
|
75
|
-
schema: createSchema({ type: 'string' }),
|
|
76
|
-
required: true,
|
|
77
|
-
}),
|
|
78
|
-
],
|
|
79
|
-
}),
|
|
80
|
-
createSchema({
|
|
81
|
-
name: 'NewPet',
|
|
82
|
-
type: 'object',
|
|
83
|
-
properties: [
|
|
84
|
-
createProperty({
|
|
85
|
-
name: 'name',
|
|
86
|
-
schema: createSchema({ type: 'string' }),
|
|
87
|
-
required: true,
|
|
88
|
-
}),
|
|
89
|
-
],
|
|
90
|
-
}),
|
|
91
|
-
createSchema({
|
|
92
|
-
name: 'PetList',
|
|
93
|
-
type: 'array',
|
|
94
|
-
items: [refPet],
|
|
95
|
-
}),
|
|
96
|
-
createSchema({
|
|
97
|
-
name: 'Error',
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: [
|
|
100
|
-
createProperty({
|
|
101
|
-
name: 'code',
|
|
102
|
-
schema: createSchema({ type: 'integer' }),
|
|
103
|
-
required: true,
|
|
104
|
-
}),
|
|
105
|
-
createProperty({
|
|
106
|
-
name: 'message',
|
|
107
|
-
schema: createSchema({ type: 'string' }),
|
|
108
|
-
required: true,
|
|
109
|
-
}),
|
|
110
|
-
],
|
|
111
|
-
}),
|
|
112
|
-
createSchema({
|
|
113
|
-
name: 'PetOrError',
|
|
114
|
-
type: 'union',
|
|
115
|
-
members: [refPet, refError],
|
|
116
|
-
}),
|
|
117
|
-
createSchema({
|
|
118
|
-
name: 'FullPet',
|
|
119
|
-
type: 'intersection',
|
|
120
|
-
members: [
|
|
121
|
-
refPet,
|
|
122
|
-
createSchema({
|
|
123
|
-
type: 'object',
|
|
124
|
-
properties: [
|
|
125
|
-
createProperty({
|
|
126
|
-
name: 'createdAt',
|
|
127
|
-
schema: createSchema({ type: 'datetime' }),
|
|
128
|
-
}),
|
|
129
|
-
],
|
|
130
|
-
}),
|
|
131
|
-
],
|
|
132
|
-
}),
|
|
133
|
-
],
|
|
134
|
-
operations: [
|
|
135
|
-
createOperation({
|
|
136
|
-
operationId: 'listPets',
|
|
137
|
-
method: 'GET',
|
|
138
|
-
path: '/pets',
|
|
139
|
-
tags: ['pets'],
|
|
140
|
-
parameters: [
|
|
141
|
-
createParameter({
|
|
142
|
-
name: 'limit',
|
|
143
|
-
in: 'query',
|
|
144
|
-
schema: createSchema({ type: 'integer' }),
|
|
145
|
-
}),
|
|
146
|
-
],
|
|
147
|
-
responses: [
|
|
148
|
-
createResponse({
|
|
149
|
-
statusCode: '200',
|
|
150
|
-
schema: createSchema({ type: 'ref', ref: 'PetList' }),
|
|
151
|
-
mediaType: 'application/json',
|
|
152
|
-
}),
|
|
153
|
-
createResponse({
|
|
154
|
-
statusCode: '400',
|
|
155
|
-
schema: refError,
|
|
156
|
-
mediaType: 'application/json',
|
|
157
|
-
}),
|
|
158
|
-
],
|
|
159
|
-
}),
|
|
160
|
-
createOperation({
|
|
161
|
-
operationId: 'createPet',
|
|
162
|
-
method: 'POST',
|
|
163
|
-
path: '/pets',
|
|
164
|
-
tags: ['pets'],
|
|
165
|
-
requestBody: { content: [{ contentType: 'application/json', schema: createSchema({ type: 'ref', ref: 'NewPet' }) }] },
|
|
166
|
-
responses: [
|
|
167
|
-
createResponse({
|
|
168
|
-
statusCode: '201',
|
|
169
|
-
schema: refPet,
|
|
170
|
-
mediaType: 'application/json',
|
|
171
|
-
}),
|
|
172
|
-
],
|
|
173
|
-
}),
|
|
174
|
-
],
|
|
175
|
-
})
|
|
176
|
-
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { BaseNode, NodeKind } from './nodes/base.ts'
|
|
2
|
+
import type { SchemaNode } from './nodes/index.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visitor callback names, one per traversable node kind. Kept in sync with the
|
|
6
|
+
* keys of `Visitor` in `visitor.ts`.
|
|
7
|
+
*/
|
|
8
|
+
type VisitorKey = 'input' | 'output' | 'operation' | 'schema' | 'property' | 'parameter' | 'response'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Distributive `Omit` that preserves each member of a union.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* type A = { kind: 'a'; keep: string; drop: number }
|
|
16
|
+
* type B = { kind: 'b'; keep: boolean; drop: number }
|
|
17
|
+
* type Result = DistributiveOmit<A | B, 'drop'>
|
|
18
|
+
* // -> { kind: 'a'; keep: string } | { kind: 'b'; keep: boolean }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Builds a type guard that matches nodes of the given `kind`.
|
|
25
|
+
*/
|
|
26
|
+
function isKind<T extends BaseNode>(kind: NodeKind) {
|
|
27
|
+
return (node: unknown): node is T => (node as BaseNode).kind === kind
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
32
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
33
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
34
|
+
*
|
|
35
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
36
|
+
* - Non-required + nullable → `nullish: true`.
|
|
37
|
+
* - Required → both flags cleared.
|
|
38
|
+
*/
|
|
39
|
+
export function syncOptionality(schema: SchemaNode, required: boolean): SchemaNode {
|
|
40
|
+
const nullable = schema.nullable ?? false
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...schema,
|
|
44
|
+
optional: !required && !nullable ? true : undefined,
|
|
45
|
+
nullish: !required && nullable ? true : undefined,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The single definition derived from one {@link defineNode} call: the node's
|
|
51
|
+
* `create` builder, its `is` guard, and the traversal metadata the registry
|
|
52
|
+
* collects into the visitor tables.
|
|
53
|
+
*/
|
|
54
|
+
export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
|
|
55
|
+
/**
|
|
56
|
+
* Node discriminator this definition owns.
|
|
57
|
+
*/
|
|
58
|
+
kind: NodeKind
|
|
59
|
+
/**
|
|
60
|
+
* Builds a node from its input, applying `defaults` and the optional `build` hook.
|
|
61
|
+
*/
|
|
62
|
+
create: (input: TInput) => TNode
|
|
63
|
+
/**
|
|
64
|
+
* Type guard matching this node kind.
|
|
65
|
+
*/
|
|
66
|
+
is: (node: unknown) => node is TNode
|
|
67
|
+
/**
|
|
68
|
+
* Child node fields in traversal order. Feeds `VISITOR_KEYS`.
|
|
69
|
+
*/
|
|
70
|
+
children?: ReadonlyArray<string>
|
|
71
|
+
/**
|
|
72
|
+
* Visitor callback name. Feeds `VISITOR_KEY_BY_KIND`.
|
|
73
|
+
*/
|
|
74
|
+
visitorKey?: VisitorKey
|
|
75
|
+
/**
|
|
76
|
+
* When `true`, `create` is rerun after children are rebuilt so computed fields
|
|
77
|
+
* stay in sync. Feeds `nodeRebuilders`.
|
|
78
|
+
*/
|
|
79
|
+
rebuild?: boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
|
|
83
|
+
kind: TNode['kind']
|
|
84
|
+
defaults?: Partial<TNode>
|
|
85
|
+
build?: (input: TInput) => TBuilt
|
|
86
|
+
children?: ReadonlyArray<string>
|
|
87
|
+
visitorKey?: VisitorKey
|
|
88
|
+
rebuild?: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Defines a node once and derives its `create` builder, `is` guard, and traversal
|
|
93
|
+
* metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
|
|
94
|
+
* `kind`, so node construction lives in one place without scattered `as` casts.
|
|
95
|
+
*
|
|
96
|
+
* Set `rebuild: true` when the `build` hook derives fields from children. After a
|
|
97
|
+
* transform rewrites those children, the registry reruns `create` so the derived
|
|
98
|
+
* fields stay correct.
|
|
99
|
+
*
|
|
100
|
+
* @example Simple node
|
|
101
|
+
* ```ts
|
|
102
|
+
* const importDef = defineNode<ImportNode>({ kind: 'Import' })
|
|
103
|
+
* const createImport = importDef.create
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @example Node with a build hook that is rerun on transform
|
|
107
|
+
* ```ts
|
|
108
|
+
* const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
|
|
109
|
+
* kind: 'Property',
|
|
110
|
+
* build: (props) => ({ ...props, required: props.required ?? false }),
|
|
111
|
+
* children: ['schema'],
|
|
112
|
+
* visitorKey: 'property',
|
|
113
|
+
* rebuild: true,
|
|
114
|
+
* })
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function defineNode<TNode extends BaseNode, TInput = Omit<TNode, 'kind'>, TBuilt extends object = Omit<TNode, 'kind'>>(
|
|
118
|
+
config: DefineNodeConfig<TNode, TInput, TBuilt>,
|
|
119
|
+
): NodeDef<TNode, TInput> {
|
|
120
|
+
const { kind, defaults, build, children, visitorKey, rebuild } = config
|
|
121
|
+
|
|
122
|
+
function create(input: TInput): TNode {
|
|
123
|
+
const base = build ? build(input) : input
|
|
124
|
+
return { ...defaults, ...(base as object), kind } as TNode
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { kind, create, is: isKind<TNode>(kind), children, visitorKey, rebuild }
|
|
128
|
+
}
|
package/src/nodes/base.ts
CHANGED
|
@@ -14,11 +14,14 @@ export type NodeKind =
|
|
|
14
14
|
| 'Property'
|
|
15
15
|
| 'Parameter'
|
|
16
16
|
| 'Response'
|
|
17
|
+
| 'RequestBody'
|
|
18
|
+
| 'Content'
|
|
17
19
|
| 'FunctionParameter'
|
|
18
|
-
| 'ParameterGroup'
|
|
19
20
|
| 'FunctionParameters'
|
|
21
|
+
| 'TypeLiteral'
|
|
22
|
+
| 'IndexedAccessType'
|
|
23
|
+
| 'ObjectBindingPattern'
|
|
20
24
|
| 'Type'
|
|
21
|
-
| 'ParamsType'
|
|
22
25
|
| 'File'
|
|
23
26
|
| 'Import'
|
|
24
27
|
| 'Export'
|
|
@@ -44,13 +47,3 @@ export type BaseNode = {
|
|
|
44
47
|
*/
|
|
45
48
|
kind: NodeKind
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Minimal node type when only `kind` is needed.
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* const node: Node = { kind: 'Operation' }
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export type Node = BaseNode
|