@kubb/ast 5.0.0-beta.60 → 5.0.0-beta.62
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/README.md +2 -1
- package/dist/defineMacro-BryDiuQl.cjs +115 -0
- package/dist/defineMacro-BryDiuQl.cjs.map +1 -0
- package/dist/defineMacro-CNkUpxia.js +99 -0
- package/dist/defineMacro-CNkUpxia.js.map +1 -0
- package/dist/defineMacro-Vw-xQvq7.d.ts +467 -0
- package/dist/factory.cjs +64 -4
- package/dist/factory.cjs.map +1 -0
- package/dist/factory.d.ts +2 -37
- package/dist/factory.js +58 -3
- package/dist/factory.js.map +1 -0
- package/dist/{index-BzjwdK2M.d.ts → index-CTaN_ee_.d.ts} +235 -198
- package/dist/index.cjs +33 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +36 -36
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- package/dist/macros.cjs +130 -0
- package/dist/macros.cjs.map +1 -0
- package/dist/macros.d.ts +61 -0
- package/dist/macros.js +128 -0
- package/dist/macros.js.map +1 -0
- package/dist/{operationParams-BZ07xDm0.d.ts → operationParams-B44p6EQy.d.ts} +2 -2
- package/dist/{response-DS5S3IG4.cjs → response-BeG6hq2D.cjs} +389 -173
- package/dist/response-BeG6hq2D.cjs.map +1 -0
- package/dist/{response-DKxTr522.js → response-Rd1uisM1.js} +377 -162
- package/dist/response-Rd1uisM1.js.map +1 -0
- package/dist/types-DQTgVSwE.d.ts +314 -0
- package/dist/types.d.ts +5 -5
- package/dist/{utils-D83JA6Xx.cjs → utils-BDI3kXlb.cjs} +20 -753
- package/dist/utils-BDI3kXlb.cjs.map +1 -0
- package/dist/{utils-Dj_KoXMv.js → utils-DW9rnfsD.js} +11 -630
- package/dist/utils-DW9rnfsD.js.map +1 -0
- package/dist/utils.cjs +11 -9
- package/dist/utils.d.ts +38 -4
- package/dist/utils.js +4 -3
- package/dist/visitor-B7Zlnp4c.cjs +604 -0
- package/dist/visitor-B7Zlnp4c.cjs.map +1 -0
- package/dist/visitor-DepQEKyp.js +510 -0
- package/dist/visitor-DepQEKyp.js.map +1 -0
- package/package.json +5 -1
- package/src/constants.ts +0 -30
- package/src/dedupe.ts +1 -9
- package/src/defineMacro.ts +139 -0
- package/src/{node.ts → defineNode.ts} +19 -42
- package/src/dialect.ts +42 -19
- package/src/factory.ts +3 -90
- package/src/index.ts +9 -15
- package/src/macros/index.ts +3 -0
- package/src/macros/macroDiscriminatorEnum.ts +50 -0
- package/src/macros/macroEnumName.ts +33 -0
- package/src/macros/macroSimplifyUnion.ts +60 -0
- package/src/nodes/code.ts +32 -75
- package/src/nodes/content.ts +2 -7
- package/src/nodes/file.ts +102 -14
- package/src/nodes/function.ts +46 -46
- package/src/nodes/index.ts +2 -3
- package/src/nodes/input.ts +13 -9
- package/src/nodes/operation.ts +5 -5
- package/src/nodes/output.ts +1 -1
- package/src/nodes/parameter.ts +6 -6
- package/src/nodes/property.ts +6 -6
- package/src/nodes/requestBody.ts +6 -13
- package/src/nodes/response.ts +93 -8
- package/src/nodes/schema.ts +1 -1
- package/src/registry.ts +1 -1
- package/src/types.ts +4 -4
- package/src/utils/extractStringsFromNodes.ts +1 -0
- package/src/utils/index.ts +2 -2
- package/src/utils/operationParams.ts +1 -1
- package/src/utils/refs.ts +27 -1
- package/src/utils/schemaMerge.ts +34 -0
- package/src/visitor.ts +2 -13
- package/dist/extractStringsFromNodes-WMYJ8nQL.d.ts +0 -82
- package/dist/factory-Cl8Z7mcc.cjs +0 -299
- package/dist/factory-Cl8Z7mcc.cjs.map +0 -1
- package/dist/factory-Du7nEP4B.js +0 -282
- package/dist/factory-Du7nEP4B.js.map +0 -1
- package/dist/response-DKxTr522.js.map +0 -1
- package/dist/response-DS5S3IG4.cjs.map +0 -1
- package/dist/types-olVl9v5p.d.ts +0 -764
- package/dist/utils-D83JA6Xx.cjs.map +0 -1
- package/dist/utils-Dj_KoXMv.js.map +0 -1
- package/src/mocks.ts +0 -56
- package/src/nodes/http.ts +0 -85
- package/src/transformers.ts +0 -191
- /package/src/{printer.ts → definePrinter.ts} +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { VisitorDepth } from './constants.ts'
|
|
2
|
+
import type { VisitorKey } from './defineNode.ts'
|
|
3
|
+
import { visitorKeys } from './defineNode.ts'
|
|
4
|
+
import type { Node } from './nodes/index.ts'
|
|
5
|
+
import type { Visitor, VisitorContext } from './visitor.ts'
|
|
6
|
+
import { transform } from './visitor.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ordering hint shared by macros and plugins. `pre` runs before unmarked items, `post` after,
|
|
10
|
+
* and `undefined` keeps declaration order.
|
|
11
|
+
*/
|
|
12
|
+
export type Enforce = 'pre' | 'post'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sort weight for an `enforce` hint. `pre` sorts before unmarked items and `post` after, so a plain
|
|
16
|
+
* list keeps its authored order.
|
|
17
|
+
*/
|
|
18
|
+
function enforceWeight(enforce?: Enforce): number {
|
|
19
|
+
if (enforce === 'pre') return 0
|
|
20
|
+
if (enforce === 'post') return 2
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A named, composable transform over the Kubb AST. It carries the same per-kind callbacks as a
|
|
26
|
+
* {@link Visitor} (`schema`, `operation`, …), plus a `name`, an optional `enforce` order, and an
|
|
27
|
+
* optional `when` gate. Macros run on the shared AST, so the same macro works across every adapter
|
|
28
|
+
* and output target. Exports follow the `macro<Name>` convention, mirroring plugins (`pluginTs`).
|
|
29
|
+
*/
|
|
30
|
+
export type Macro = Visitor & {
|
|
31
|
+
/**
|
|
32
|
+
* Macro identifier, surfaced in diagnostics. Follows the `macro<Name>` convention.
|
|
33
|
+
*/
|
|
34
|
+
name: string
|
|
35
|
+
/**
|
|
36
|
+
* Ordering hint. `pre` macros run before unmarked macros, `post` macros run after.
|
|
37
|
+
* Ordering within a bucket follows list order.
|
|
38
|
+
*/
|
|
39
|
+
enforce?: Enforce
|
|
40
|
+
/**
|
|
41
|
+
* Gate checked against the current node before any callback runs. When it returns `false`
|
|
42
|
+
* the macro is skipped for that node.
|
|
43
|
+
*/
|
|
44
|
+
when?: (node: Node) => boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Types a macro for inference and a single construction site, mirroring `definePlugin`.
|
|
49
|
+
* Adds no runtime behavior.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const macroUntagged = defineMacro({
|
|
54
|
+
* name: 'untagged',
|
|
55
|
+
* operation(node) {
|
|
56
|
+
* return node.tags?.length ? undefined : { ...node, tags: ['untagged'] }
|
|
57
|
+
* },
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function defineMacro(macro: Macro): Macro {
|
|
62
|
+
return macro
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type MacroCallback = (node: Node, context: VisitorContext) => Node | null | undefined
|
|
66
|
+
|
|
67
|
+
type ChainProps = {
|
|
68
|
+
macros: ReadonlyArray<Macro>
|
|
69
|
+
key: VisitorKey
|
|
70
|
+
node: Node
|
|
71
|
+
context: VisitorContext
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Runs every macro's callback for one node kind in order, chaining the result so each macro sees
|
|
76
|
+
* the previous macro's output. Returns `undefined` when nothing changed, so `transform` keeps the
|
|
77
|
+
* original reference (structural sharing).
|
|
78
|
+
*/
|
|
79
|
+
function chain({ macros, key, node, context }: ChainProps): Node | undefined {
|
|
80
|
+
let current = node
|
|
81
|
+
|
|
82
|
+
for (const macro of macros) {
|
|
83
|
+
const callback = macro[key] as MacroCallback | undefined
|
|
84
|
+
if (!callback) continue
|
|
85
|
+
if (macro.when && !macro.when(current)) continue
|
|
86
|
+
|
|
87
|
+
const next = callback(current, context)
|
|
88
|
+
if (next != null) current = next
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return current === node ? undefined : current
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Folds an ordered list of macros into a single {@link Visitor} that `transform` (and the per-plugin
|
|
96
|
+
* transform layer in `@kubb/core`) can run. Macros are stable-sorted by `enforce`, then applied
|
|
97
|
+
* sequentially per node so later macros see earlier output. This differs from a plain visitor, which
|
|
98
|
+
* has no names, ordering, or composition.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const visitor = composeMacros([macroSimplifyUnion, macroDiscriminatorEnum])
|
|
103
|
+
* const next = transform(root, visitor)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function composeMacros(macros: ReadonlyArray<Macro>): Visitor {
|
|
107
|
+
const ordered = [...macros].sort((a, b) => enforceWeight(a.enforce) - enforceWeight(b.enforce))
|
|
108
|
+
|
|
109
|
+
const visitor: Visitor = {}
|
|
110
|
+
for (const key of visitorKeys) {
|
|
111
|
+
if (!ordered.some((macro) => typeof macro[key] === 'function')) continue
|
|
112
|
+
|
|
113
|
+
const callback = (node: Node, context: VisitorContext) => chain({ macros: ordered, key, node, context })
|
|
114
|
+
;(visitor as Record<VisitorKey, MacroCallback>)[key] = callback
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return visitor
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Runs a list of macros over a node tree and returns the rewritten tree. Keeps `transform`'s
|
|
122
|
+
* structural sharing, so an empty or no-op macro list returns the same reference. Pass
|
|
123
|
+
* `depth: 'shallow'` to rewrite the root node only.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const next = applyMacros(root, [macroIntegerToString])
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example Apply to the root node only
|
|
131
|
+
* ```ts
|
|
132
|
+
* const named = applyMacros(node, [macroEnumName({ parentName, propName, enumSuffix })], { depth: 'shallow' })
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function applyMacros<TNode extends Node>(root: TNode, macros: ReadonlyArray<Macro>, options?: { depth?: VisitorDepth }): TNode {
|
|
136
|
+
if (macros.length === 0) return root
|
|
137
|
+
|
|
138
|
+
return transform(root, { ...composeMacros(macros), ...options }) as TNode
|
|
139
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import type { Dialect } from './dialect.ts'
|
|
1
2
|
import type { BaseNode, NodeKind } from './nodes/base.ts'
|
|
2
|
-
import type { SchemaNode } from './nodes/index.ts'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Visitor callback names, one per traversable node kind
|
|
6
|
-
* keys of `Visitor` in `visitor.ts`.
|
|
5
|
+
* Visitor callback names, one per traversable node kind, in traversal order.
|
|
6
|
+
* Kept in sync with the keys of `Visitor` in `visitor.ts`.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
export const visitorKeys = ['input', 'output', 'operation', 'schema', 'property', 'parameter', 'response'] as const
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One of the {@link visitorKeys} callback names.
|
|
12
|
+
*/
|
|
13
|
+
export type VisitorKey = (typeof visitorKeys)[number]
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Distributive `Omit` that preserves each member of a union.
|
|
@@ -27,25 +32,6 @@ function isKind<T extends BaseNode>(kind: NodeKind) {
|
|
|
27
32
|
return (node: unknown): node is T => (node as BaseNode).kind === kind
|
|
28
33
|
}
|
|
29
34
|
|
|
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
35
|
/**
|
|
50
36
|
* The single definition derived from one {@link defineNode} call: the node's
|
|
51
37
|
* `create` builder, its `is` guard, and the traversal metadata the registry
|
|
@@ -57,9 +43,11 @@ export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
|
|
|
57
43
|
*/
|
|
58
44
|
kind: NodeKind
|
|
59
45
|
/**
|
|
60
|
-
* Builds a node from its input, applying `defaults` and the optional `build` hook.
|
|
46
|
+
* Builds a node from its input, applying `defaults` and the optional `build` hook. An
|
|
47
|
+
* optional `dialect` is forwarded to `build` so nodes can derive spec-specific fields
|
|
48
|
+
* (e.g. a schema's `optional`/`nullish`) through `dialect.schema.optionality` when one is given.
|
|
61
49
|
*/
|
|
62
|
-
create: (input: TInput) => TNode
|
|
50
|
+
create: (input: TInput, dialect?: Dialect) => TNode
|
|
63
51
|
/**
|
|
64
52
|
* Type guard matching this node kind.
|
|
65
53
|
*/
|
|
@@ -72,20 +60,14 @@ export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
|
|
|
72
60
|
* Visitor callback name. Feeds `VISITOR_KEY_BY_KIND`.
|
|
73
61
|
*/
|
|
74
62
|
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
63
|
}
|
|
81
64
|
|
|
82
65
|
type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
|
|
83
66
|
kind: TNode['kind']
|
|
84
67
|
defaults?: Partial<TNode>
|
|
85
|
-
build?: (input: TInput) => TBuilt
|
|
68
|
+
build?: (input: TInput, dialect?: Dialect) => TBuilt
|
|
86
69
|
children?: ReadonlyArray<string>
|
|
87
70
|
visitorKey?: VisitorKey
|
|
88
|
-
rebuild?: boolean
|
|
89
71
|
}
|
|
90
72
|
|
|
91
73
|
/**
|
|
@@ -93,36 +75,31 @@ type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
|
|
|
93
75
|
* metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
|
|
94
76
|
* `kind`, so node construction lives in one place without scattered `as` casts.
|
|
95
77
|
*
|
|
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
78
|
* @example Simple node
|
|
101
79
|
* ```ts
|
|
102
80
|
* const importDef = defineNode<ImportNode>({ kind: 'Import' })
|
|
103
81
|
* const createImport = importDef.create
|
|
104
82
|
* ```
|
|
105
83
|
*
|
|
106
|
-
* @example Node with a build hook
|
|
84
|
+
* @example Node with a build hook
|
|
107
85
|
* ```ts
|
|
108
86
|
* const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
|
|
109
87
|
* kind: 'Property',
|
|
110
88
|
* build: (props) => ({ ...props, required: props.required ?? false }),
|
|
111
89
|
* children: ['schema'],
|
|
112
90
|
* visitorKey: 'property',
|
|
113
|
-
* rebuild: true,
|
|
114
91
|
* })
|
|
115
92
|
* ```
|
|
116
93
|
*/
|
|
117
94
|
export function defineNode<TNode extends BaseNode, TInput = Omit<TNode, 'kind'>, TBuilt extends object = Omit<TNode, 'kind'>>(
|
|
118
95
|
config: DefineNodeConfig<TNode, TInput, TBuilt>,
|
|
119
96
|
): NodeDef<TNode, TInput> {
|
|
120
|
-
const { kind, defaults, build, children, visitorKey
|
|
97
|
+
const { kind, defaults, build, children, visitorKey } = config
|
|
121
98
|
|
|
122
|
-
function create(input: TInput): TNode {
|
|
123
|
-
const base = build ? build(input) : input
|
|
99
|
+
function create(input: TInput, dialect?: Dialect): TNode {
|
|
100
|
+
const base = build ? build(input, dialect) : input
|
|
124
101
|
return { ...defaults, ...(base as object), kind } as TNode
|
|
125
102
|
}
|
|
126
103
|
|
|
127
|
-
return { kind, create, is: isKind<TNode>(kind), children, visitorKey
|
|
104
|
+
return { kind, create, is: isKind<TNode>(kind), children, visitorKey }
|
|
128
105
|
}
|
package/src/dialect.ts
CHANGED
|
@@ -1,53 +1,76 @@
|
|
|
1
|
+
import type { SchemaNode } from './nodes/index.ts'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The spec-specific questions a schema parser answers while turning a source document into Kubb
|
|
3
5
|
* AST nodes. The rest of the pipeline is generic JSON Schema, so this is the one seam where
|
|
4
6
|
* OpenAPI, AsyncAPI, and plain JSON Schema differ.
|
|
5
7
|
*/
|
|
6
8
|
export type SchemaDialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown> = {
|
|
7
|
-
/**
|
|
8
|
-
* Identifies the dialect in logs and diagnostics.
|
|
9
|
-
*/
|
|
10
|
-
name: string
|
|
11
9
|
/**
|
|
12
10
|
* Whether the schema is nullable.
|
|
13
11
|
*/
|
|
14
|
-
isNullable
|
|
12
|
+
isNullable(schema?: TSchema): boolean
|
|
15
13
|
/**
|
|
16
14
|
* Whether the value is a `$ref` pointer.
|
|
17
15
|
*/
|
|
18
|
-
isReference
|
|
16
|
+
isReference(value?: unknown): value is TRef
|
|
19
17
|
/**
|
|
20
18
|
* Whether the schema carries a discriminator for polymorphism.
|
|
21
19
|
*/
|
|
22
|
-
isDiscriminator
|
|
20
|
+
isDiscriminator(value?: unknown): value is TDiscriminated
|
|
23
21
|
/**
|
|
24
22
|
* Whether the schema is binary data, converted to a `blob` node.
|
|
25
23
|
*/
|
|
26
|
-
isBinary
|
|
24
|
+
isBinary(schema: TSchema): boolean
|
|
27
25
|
/**
|
|
28
26
|
* Resolves a local `$ref` against the document, or nullish when it cannot.
|
|
29
27
|
*/
|
|
30
|
-
resolveRef
|
|
28
|
+
resolveRef<TResolved>(document: TDocument, ref: string): TResolved | null | undefined
|
|
29
|
+
/**
|
|
30
|
+
* Derives a schema's `optional`/`nullish` flags from a parent's `required` value and the
|
|
31
|
+
* schema's own `nullable`. How "required" and "nullable" combine is spec-specific, so the
|
|
32
|
+
* dialect owns it. Method syntax keeps a concrete dialect assignable to the base
|
|
33
|
+
* `SchemaDialect` (bivariant parameters).
|
|
34
|
+
*/
|
|
35
|
+
optionality(schema: SchemaNode, required: boolean): SchemaNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A spec adapter's dialect. `name` identifies it in logs and diagnostics; `schema` holds the
|
|
40
|
+
* spec-specific schema questions the parser answers.
|
|
41
|
+
*/
|
|
42
|
+
export type Dialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown> = {
|
|
43
|
+
/**
|
|
44
|
+
* Identifies the dialect in logs and diagnostics.
|
|
45
|
+
*/
|
|
46
|
+
name: string
|
|
47
|
+
/**
|
|
48
|
+
* The spec-specific schema behavior. See {@link SchemaDialect}.
|
|
49
|
+
*/
|
|
50
|
+
schema: SchemaDialect<TSchema, TRef, TDiscriminated, TDocument>
|
|
31
51
|
}
|
|
32
52
|
|
|
33
53
|
/**
|
|
34
|
-
* Types a {@link
|
|
54
|
+
* Types a {@link Dialect} for an adapter. Adds no runtime behavior and only pins the
|
|
35
55
|
* dialect's type for inference.
|
|
36
56
|
*
|
|
37
57
|
* @example
|
|
38
58
|
* ```ts
|
|
39
|
-
* export const oasDialect =
|
|
59
|
+
* export const oasDialect = defineDialect({
|
|
40
60
|
* name: 'oas',
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
61
|
+
* schema: {
|
|
62
|
+
* isNullable,
|
|
63
|
+
* isReference,
|
|
64
|
+
* isDiscriminator,
|
|
65
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
66
|
+
* resolveRef,
|
|
67
|
+
* optionality,
|
|
68
|
+
* },
|
|
46
69
|
* })
|
|
47
70
|
* ```
|
|
48
71
|
*/
|
|
49
|
-
export function
|
|
50
|
-
dialect:
|
|
51
|
-
):
|
|
72
|
+
export function defineDialect<TSchema, TRef, TDiscriminated, TDocument>(
|
|
73
|
+
dialect: Dialect<TSchema, TRef, TDiscriminated, TDocument>,
|
|
74
|
+
): Dialect<TSchema, TRef, TDiscriminated, TDocument> {
|
|
52
75
|
return dialect
|
|
53
76
|
}
|
package/src/factory.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { trimExtName } from '@internals/utils'
|
|
4
|
-
import type { FileNode, Node } from './nodes/index.ts'
|
|
5
|
-
import { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
|
|
6
|
-
import { combineExports, combineImports, combineSources } from './utils/fileMerge.ts'
|
|
1
|
+
import type { Node } from './nodes/index.ts'
|
|
7
2
|
|
|
8
3
|
// Node constructors, grouped under the `factory` namespace the way the TypeScript compiler exposes
|
|
9
4
|
// `ts.factory.createX`. Aggregating them here lets `export * as factory from './factory.ts'` in the
|
|
10
5
|
// barrel surface every `createX` alongside the `createFile`/`update` helpers from a single module.
|
|
11
6
|
export { createArrowFunction, createBreak, createConst, createFunction, createJsx, createText, createType } from './nodes/code.ts'
|
|
12
7
|
export { createContent } from './nodes/content.ts'
|
|
13
|
-
export { createExport, createImport, createSource } from './nodes/file.ts'
|
|
8
|
+
export { createExport, createFile, createImport, createSource } from './nodes/file.ts'
|
|
9
|
+
export type { UserFileNode } from './nodes/file.ts'
|
|
14
10
|
export { createFunctionParameter, createFunctionParameters, createIndexedAccessType, createObjectBindingPattern, createTypeLiteral } from './nodes/function.ts'
|
|
15
11
|
export { createInput } from './nodes/input.ts'
|
|
16
12
|
export { createOperation } from './nodes/operation.ts'
|
|
@@ -46,86 +42,3 @@ export function update<T extends Node>(node: T, changes: Partial<T>): T {
|
|
|
46
42
|
|
|
47
43
|
return node
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Input descriptor for {@link createFile}, before `id`, `name`, and `extname` are computed
|
|
52
|
-
* and `imports`/`exports`/`sources` are deduplicated.
|
|
53
|
-
*/
|
|
54
|
-
export type UserFileNode<TMeta extends object = object> = Omit<FileNode<TMeta>, 'kind' | 'id' | 'name' | 'extname' | 'imports' | 'exports' | 'sources'> &
|
|
55
|
-
Pick<Partial<FileNode<TMeta>>, 'imports' | 'exports' | 'sources'>
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
59
|
-
*
|
|
60
|
-
* Computes:
|
|
61
|
-
* - `id` SHA256 hash of the file path
|
|
62
|
-
* - `name` `baseName` without extension
|
|
63
|
-
* - `extname` extension extracted from `baseName`
|
|
64
|
-
*
|
|
65
|
-
* Deduplicates:
|
|
66
|
-
* - `sources` via `combineSources`
|
|
67
|
-
* - `exports` via `combineExports`
|
|
68
|
-
* - `imports` via `combineImports` (also filters unused imports)
|
|
69
|
-
*
|
|
70
|
-
* @throws {Error} when `baseName` has no extension.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* const file = createFile({
|
|
75
|
-
* baseName: 'petStore.ts',
|
|
76
|
-
* path: 'src/models/petStore.ts',
|
|
77
|
-
* sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
|
|
78
|
-
* imports: [createImport({ name: ['z'], path: 'zod' })],
|
|
79
|
-
* exports: [createExport({ name: ['Pet'], path: './petStore' })],
|
|
80
|
-
* })
|
|
81
|
-
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
82
|
-
* // file.name = 'petStore'
|
|
83
|
-
* // file.extname = '.ts'
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
export function createFile<TMeta extends object = object>(input: UserFileNode<TMeta>): FileNode<TMeta> {
|
|
87
|
-
const rawExtname = path.extname(input.baseName)
|
|
88
|
-
// Handle dotfile basename like '.ts' where path.extname returns ''
|
|
89
|
-
const extname = (rawExtname || (input.baseName.startsWith('.') ? input.baseName : '')) as `.${string}`
|
|
90
|
-
if (!extname) {
|
|
91
|
-
throw new Error(`No extname found for ${input.baseName}`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const source = (input.sources ?? [])
|
|
95
|
-
.flatMap((item) => item.nodes ?? [])
|
|
96
|
-
.map((node) => extractStringsFromNodes([node]))
|
|
97
|
-
.filter(Boolean)
|
|
98
|
-
.join('\n\n')
|
|
99
|
-
const resolvedExports = input.exports?.length ? combineExports(input.exports) : []
|
|
100
|
-
const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || undefined) : []
|
|
101
|
-
const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name): name is string => Boolean(name)))
|
|
102
|
-
const nameOf = (item: string | { propertyName: string; name?: string }): string => (typeof item === 'string' ? item : (item.name ?? item.propertyName))
|
|
103
|
-
// Drop self-imports. Consolidating output (`mode: 'file'`) can place a symbol's
|
|
104
|
-
// definition and a cross-file import of it in the same file. The first pass catches imports that
|
|
105
|
-
// resolve to this file's own path. The second drops imports of names the file already defines,
|
|
106
|
-
// the case consolidation produces when the import path no longer matches `input.path`. Sources
|
|
107
|
-
// stay intact, so the local definition remains. Bare specifiers like `'zod'` never match a path.
|
|
108
|
-
const resolvedImports = combinedImports
|
|
109
|
-
.filter((imp) => imp.path !== input.path)
|
|
110
|
-
.flatMap((imp) => {
|
|
111
|
-
if (!Array.isArray(imp.name)) {
|
|
112
|
-
return typeof imp.name === 'string' && localNames.has(imp.name) ? [] : [imp]
|
|
113
|
-
}
|
|
114
|
-
const kept = imp.name.filter((item) => !localNames.has(nameOf(item)))
|
|
115
|
-
if (!kept.length) return []
|
|
116
|
-
return [kept.length === imp.name.length ? imp : { ...imp, name: kept }]
|
|
117
|
-
})
|
|
118
|
-
const resolvedSources = input.sources?.length ? combineSources(input.sources) : []
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
kind: 'File',
|
|
122
|
-
...input,
|
|
123
|
-
id: hash('sha256', input.path, 'hex'),
|
|
124
|
-
name: trimExtName(input.baseName),
|
|
125
|
-
extname,
|
|
126
|
-
imports: resolvedImports,
|
|
127
|
-
exports: resolvedExports,
|
|
128
|
-
sources: resolvedSources,
|
|
129
|
-
meta: input.meta ?? ({} as TMeta),
|
|
130
|
-
}
|
|
131
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { schemaTypes } from './constants.ts'
|
|
2
2
|
export { applyDedupe, buildDedupePlan } from './dedupe.ts'
|
|
3
|
-
export {
|
|
4
|
-
// Node constructors, also published as the `@kubb/ast/factory` subpath, mirroring `ts.factory.createX`.
|
|
5
|
-
export * as factory from './factory.ts'
|
|
3
|
+
export { defineDialect } from './dialect.ts'
|
|
6
4
|
export { isHttpOperationNode, narrowSchema } from './guards.ts'
|
|
7
|
-
export {
|
|
8
|
-
export
|
|
9
|
-
export {
|
|
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'
|
|
13
|
-
export { createPrinterFactory, definePrinter } from './printer.ts'
|
|
5
|
+
export { applyMacros, composeMacros, defineMacro } from './defineMacro.ts'
|
|
6
|
+
export { defineNode } from './defineNode.ts'
|
|
7
|
+
export { createPrinterFactory, definePrinter } from './definePrinter.ts'
|
|
14
8
|
export { signatureOf } from './signature.ts'
|
|
15
|
-
export { mergeAdjacentObjectsLazy, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
|
|
16
|
-
export type * from './types.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'
|
|
19
9
|
export { collect, transform, walk } from './visitor.ts'
|
|
10
|
+
|
|
11
|
+
export * as factory from './factory.ts'
|
|
12
|
+
export * from './registry.ts'
|
|
13
|
+
export type * from './types.ts'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineMacro } from '../defineMacro.ts'
|
|
2
|
+
import { narrowSchema } from '../guards.ts'
|
|
3
|
+
import { createProperty } from '../nodes/property.ts'
|
|
4
|
+
import { createSchema } from '../nodes/schema.ts'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
propertyName: string
|
|
8
|
+
values: Array<string>
|
|
9
|
+
enumName?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds a macro that replaces a discriminator property's schema with a string enum of the given
|
|
14
|
+
* values. Object schemas that lack the property are returned unchanged.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const macro = macroDiscriminatorEnum({ propertyName: 'type', values: ['dog', 'cat'] })
|
|
19
|
+
* const next = applyMacros(objectSchema, [macro], { depth: 'shallow' })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function macroDiscriminatorEnum({ propertyName, values, enumName }: Props) {
|
|
23
|
+
return defineMacro({
|
|
24
|
+
name: 'discriminator-enum',
|
|
25
|
+
schema(node) {
|
|
26
|
+
const objectNode = narrowSchema(node, 'object')
|
|
27
|
+
if (!objectNode?.properties?.length) return undefined
|
|
28
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return undefined
|
|
29
|
+
|
|
30
|
+
return createSchema({
|
|
31
|
+
...objectNode,
|
|
32
|
+
properties: objectNode.properties.map((prop) => {
|
|
33
|
+
if (prop.name !== propertyName) return prop
|
|
34
|
+
|
|
35
|
+
return createProperty({
|
|
36
|
+
...prop,
|
|
37
|
+
schema: createSchema({
|
|
38
|
+
type: 'enum',
|
|
39
|
+
primitive: 'string',
|
|
40
|
+
enumValues: values,
|
|
41
|
+
name: enumName,
|
|
42
|
+
readOnly: prop.schema.readOnly,
|
|
43
|
+
writeOnly: prop.schema.writeOnly,
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
}),
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineMacro } from '../defineMacro.ts'
|
|
2
|
+
import { narrowSchema } from '../guards.ts'
|
|
3
|
+
import { enumPropName } from '../utils/refs.ts'
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
parentName: string | null | undefined
|
|
7
|
+
propName: string
|
|
8
|
+
enumSuffix: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds a macro that names an inline enum schema from its parent and property name. Boolean enums
|
|
13
|
+
* are left anonymous; non-enum nodes are returned unchanged.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const macro = macroEnumName({ parentName: 'Pet', propName: 'status', enumSuffix: 'enum' })
|
|
18
|
+
* const named = applyMacros(propSchema, [macro], { depth: 'shallow' })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function macroEnumName({ parentName, propName, enumSuffix }: Props) {
|
|
22
|
+
return defineMacro({
|
|
23
|
+
name: 'enum-name',
|
|
24
|
+
schema(node) {
|
|
25
|
+
const enumNode = narrowSchema(node, 'enum')
|
|
26
|
+
|
|
27
|
+
if (enumNode?.primitive === 'boolean') return { ...node, name: null }
|
|
28
|
+
if (enumNode) return { ...node, name: enumPropName(parentName, propName, enumSuffix) }
|
|
29
|
+
|
|
30
|
+
return undefined
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineMacro } from '../defineMacro.ts'
|
|
2
|
+
import { narrowSchema } from '../guards.ts'
|
|
3
|
+
import type { SchemaNode } from '../nodes/schema.ts'
|
|
4
|
+
|
|
5
|
+
type ScalarPrimitive = 'string' | 'number' | 'integer' | 'bigint' | 'boolean'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Scalar primitive schema types used for union simplification and type narrowing.
|
|
9
|
+
*/
|
|
10
|
+
const SCALAR_PRIMITIVE_TYPES = new Set<ScalarPrimitive>(['string', 'number', 'integer', 'bigint', 'boolean'])
|
|
11
|
+
|
|
12
|
+
function isScalarPrimitive(type: string): type is ScalarPrimitive {
|
|
13
|
+
return SCALAR_PRIMITIVE_TYPES.has(type as ScalarPrimitive)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Filters union members, dropping enum members that a broader scalar primitive already covers.
|
|
18
|
+
*/
|
|
19
|
+
function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
20
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type))
|
|
21
|
+
if (!scalarPrimitives.size) return members
|
|
22
|
+
|
|
23
|
+
return members.filter((member) => {
|
|
24
|
+
const enumNode = narrowSchema(member, 'enum')
|
|
25
|
+
if (!enumNode) return true
|
|
26
|
+
|
|
27
|
+
const primitive = enumNode.primitive
|
|
28
|
+
if (!primitive) return true
|
|
29
|
+
|
|
30
|
+
const enumValueCount = enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0
|
|
31
|
+
if (enumValueCount <= 1) return true
|
|
32
|
+
|
|
33
|
+
if (scalarPrimitives.has(primitive)) return false
|
|
34
|
+
if ((primitive === 'integer' || primitive === 'number') && (scalarPrimitives.has('integer') || scalarPrimitives.has('number'))) return false
|
|
35
|
+
|
|
36
|
+
return true
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Removes union members a broader scalar primitive already covers, such as a multi-value string enum
|
|
42
|
+
* sitting next to a plain `string`. Single-value enums are kept.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const next = applyMacros(unionSchema, [macroSimplifyUnion], { depth: 'shallow' })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export const macroSimplifyUnion = defineMacro({
|
|
50
|
+
name: 'simplify-union',
|
|
51
|
+
schema(node) {
|
|
52
|
+
const unionNode = narrowSchema(node, 'union')
|
|
53
|
+
if (!unionNode?.members?.length) return undefined
|
|
54
|
+
|
|
55
|
+
const simplified = simplifyUnionMembers(unionNode.members)
|
|
56
|
+
if (simplified.length === unionNode.members.length) return undefined
|
|
57
|
+
|
|
58
|
+
return { ...unionNode, members: simplified }
|
|
59
|
+
},
|
|
60
|
+
})
|