@kubb/ast 5.0.0-beta.6 → 5.0.0-beta.61
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 +51 -27
- package/dist/chunk-CNktS9qV.js +17 -0
- package/dist/defineMacro-BLIR6k-j.d.ts +475 -0
- package/dist/defineMacro-BTXvS8nI.js +106 -0
- package/dist/defineMacro-BTXvS8nI.js.map +1 -0
- package/dist/defineMacro-Bv9R_9a2.cjs +123 -0
- package/dist/defineMacro-Bv9R_9a2.cjs.map +1 -0
- package/dist/extractStringsFromNodes-Cja-xxx5.js +29 -0
- package/dist/extractStringsFromNodes-Cja-xxx5.js.map +1 -0
- package/dist/extractStringsFromNodes-DKgDjFO0.cjs +34 -0
- package/dist/extractStringsFromNodes-DKgDjFO0.cjs.map +1 -0
- package/dist/extractStringsFromNodes-p4mX1TQD.d.ts +14 -0
- package/dist/factory-CZNOGI-N.js +283 -0
- package/dist/factory-CZNOGI-N.js.map +1 -0
- package/dist/factory-DG1CVkEb.cjs +300 -0
- package/dist/factory-DG1CVkEb.cjs.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 +444 -2180
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +94 -3408
- package/dist/index.js +395 -2101
- package/dist/index.js.map +1 -1
- package/dist/macros.cjs +117 -0
- package/dist/macros.cjs.map +1 -0
- package/dist/macros.d.ts +59 -0
- package/dist/macros.js +115 -0
- package/dist/macros.js.map +1 -0
- package/dist/operationParams-BZ07xDm0.d.ts +204 -0
- package/dist/response-KUdWiDWw.js +658 -0
- package/dist/response-KUdWiDWw.js.map +1 -0
- package/dist/response-hnSw2NKE.cjs +1027 -0
- package/dist/response-hnSw2NKE.cjs.map +1 -0
- package/dist/types-DyDzizSf.d.ts +364 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/dist/utils-BLJwyza-.cjs +912 -0
- package/dist/utils-BLJwyza-.cjs.map +1 -0
- package/dist/utils-CF_-Pn_c.js +770 -0
- package/dist/utils-CF_-Pn_c.js.map +1 -0
- package/dist/utils.cjs +36 -0
- package/dist/utils.d.ts +358 -0
- package/dist/utils.js +4 -0
- package/dist/visitor-DJ6ZEJvq.js +548 -0
- package/dist/visitor-DJ6ZEJvq.js.map +1 -0
- package/dist/visitor-DpKZ9Tk0.cjs +654 -0
- package/dist/visitor-DpKZ9Tk0.cjs.map +1 -0
- package/package.json +21 -6
- package/src/constants.ts +19 -64
- package/src/dedupe.ts +239 -0
- package/src/defineMacro.ts +132 -0
- package/src/dialect.ts +53 -0
- package/src/factory.ts +67 -678
- package/src/guards.ts +10 -92
- package/src/index.ts +13 -44
- package/src/infer.ts +16 -14
- package/src/macros/index.ts +3 -0
- package/src/macros/macroDiscriminatorEnum.ts +44 -0
- package/src/macros/macroEnumName.ts +25 -0
- package/src/macros/macroSimplifyUnion.ts +50 -0
- 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/types.ts +8 -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 +138 -0
- package/src/utils/schemaGraph.ts +169 -0
- package/src/utils/schemaMerge.ts +34 -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/transformers.ts +0 -159
- 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,15 @@
|
|
|
1
|
-
export { httpMethods,
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
createFunctionParameters,
|
|
11
|
-
createImport,
|
|
12
|
-
createInput,
|
|
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
|
+
export * as factory from './factory.ts'
|
|
5
|
+
export { isHttpOperationNode, narrowSchema } from './guards.ts'
|
|
6
|
+
export { applyMacros, composeMacros, defineMacro } from './defineMacro.ts'
|
|
7
|
+
export { defineNode } from './node.ts'
|
|
8
|
+
export { syncOptionality } from './node.ts'
|
|
9
|
+
export * from './registry.ts'
|
|
28
10
|
export { createPrinterFactory, definePrinter } from './printer.ts'
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export { mergeAdjacentObjects, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
|
|
32
|
-
export type * from './types.ts'
|
|
33
|
-
export {
|
|
34
|
-
caseParams,
|
|
35
|
-
collectReferencedSchemaNames,
|
|
36
|
-
collectUsedSchemaNames,
|
|
37
|
-
containsCircularRef,
|
|
38
|
-
createDiscriminantNode,
|
|
39
|
-
createOperationParams,
|
|
40
|
-
extractStringsFromNodes,
|
|
41
|
-
findCircularSchemas,
|
|
42
|
-
isStringType,
|
|
43
|
-
resolveRefName,
|
|
44
|
-
syncSchemaRef,
|
|
45
|
-
} from './utils.ts'
|
|
11
|
+
export { signatureOf } from './signature.ts'
|
|
12
|
+
export { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
|
|
46
13
|
export { collect, transform, walk } from './visitor.ts'
|
|
14
|
+
|
|
15
|
+
export type * from './types.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>
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Builds a macro that replaces a discriminator property's schema with a string enum of the given
|
|
8
|
+
* values. Object schemas that lack the property are returned unchanged.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const macro = macroDiscriminatorEnum({ propertyName: 'type', values: ['dog', 'cat'] })
|
|
13
|
+
* const next = applyMacros(objectSchema, [macro], { depth: 'shallow' })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function macroDiscriminatorEnum({ propertyName, values, enumName }: { propertyName: string; values: Array<string>; enumName?: string }) {
|
|
17
|
+
return defineMacro({
|
|
18
|
+
name: 'discriminator-enum',
|
|
19
|
+
schema(node) {
|
|
20
|
+
const objectNode = narrowSchema(node, 'object')
|
|
21
|
+
if (!objectNode?.properties?.length) return undefined
|
|
22
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return undefined
|
|
23
|
+
|
|
24
|
+
return createSchema({
|
|
25
|
+
...objectNode,
|
|
26
|
+
properties: objectNode.properties.map((prop) => {
|
|
27
|
+
if (prop.name !== propertyName) return prop
|
|
28
|
+
|
|
29
|
+
return createProperty({
|
|
30
|
+
...prop,
|
|
31
|
+
schema: createSchema({
|
|
32
|
+
type: 'enum',
|
|
33
|
+
primitive: 'string',
|
|
34
|
+
enumValues: values,
|
|
35
|
+
name: enumName,
|
|
36
|
+
readOnly: prop.schema.readOnly,
|
|
37
|
+
writeOnly: prop.schema.writeOnly,
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineMacro } from '../defineMacro.ts'
|
|
2
|
+
import { narrowSchema } from '../guards.ts'
|
|
3
|
+
import { enumPropName } from '../utils/refs.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a macro that names an inline enum schema from its parent and property name. Boolean enums
|
|
7
|
+
* are left anonymous; non-enum nodes are returned unchanged.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const macro = macroEnumName({ parentName: 'Pet', propName: 'status', enumSuffix: 'enum' })
|
|
12
|
+
* const named = applyMacros(propSchema, [macro], { depth: 'shallow' })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function macroEnumName({ parentName, propName, enumSuffix }: { parentName: string | null | undefined; propName: string; enumSuffix: string }) {
|
|
16
|
+
return defineMacro({
|
|
17
|
+
name: 'enum-name',
|
|
18
|
+
schema(node) {
|
|
19
|
+
const enumNode = narrowSchema(node, 'enum')
|
|
20
|
+
if (enumNode?.primitive === 'boolean') return { ...node, name: null }
|
|
21
|
+
if (enumNode) return { ...node, name: enumPropName(parentName, propName, enumSuffix) }
|
|
22
|
+
return undefined
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isScalarPrimitive } from '../constants.ts'
|
|
2
|
+
import { defineMacro } from '../defineMacro.ts'
|
|
3
|
+
import { narrowSchema } from '../guards.ts'
|
|
4
|
+
import type { SchemaNode } from '../nodes/schema.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes union members a broader scalar primitive already covers, such as a multi-value string enum
|
|
8
|
+
* sitting next to a plain `string`. Single-value enums are kept.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const next = applyMacros(unionSchema, [macroSimplifyUnion], { depth: 'shallow' })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export const macroSimplifyUnion = defineMacro({
|
|
16
|
+
name: 'simplify-union',
|
|
17
|
+
schema(node) {
|
|
18
|
+
const unionNode = narrowSchema(node, 'union')
|
|
19
|
+
if (!unionNode?.members?.length) return undefined
|
|
20
|
+
|
|
21
|
+
const simplified = simplifyUnionMembers(unionNode.members)
|
|
22
|
+
if (simplified.length === unionNode.members.length) return undefined
|
|
23
|
+
|
|
24
|
+
return { ...unionNode, members: simplified }
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filters union members, dropping enum members that a broader scalar primitive already covers.
|
|
30
|
+
*/
|
|
31
|
+
function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
32
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type))
|
|
33
|
+
if (!scalarPrimitives.size) return members
|
|
34
|
+
|
|
35
|
+
return members.filter((member) => {
|
|
36
|
+
const enumNode = narrowSchema(member, 'enum')
|
|
37
|
+
if (!enumNode) return true
|
|
38
|
+
|
|
39
|
+
const primitive = enumNode.primitive
|
|
40
|
+
if (!primitive) return true
|
|
41
|
+
|
|
42
|
+
const enumValueCount = enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0
|
|
43
|
+
if (enumValueCount <= 1) return true
|
|
44
|
+
|
|
45
|
+
if (scalarPrimitives.has(primitive)) return false
|
|
46
|
+
if ((primitive === 'integer' || primitive === 'number') && (scalarPrimitives.has('integer') || scalarPrimitives.has('number'))) return false
|
|
47
|
+
|
|
48
|
+
return true
|
|
49
|
+
})
|
|
50
|
+
}
|
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
|