@kubb/adapter-oas 5.0.0-alpha.1 → 5.0.0-alpha.11
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 +432 -220
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +71 -27
- package/dist/index.js +433 -221
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +28 -8
- package/src/constants.ts +19 -9
- package/src/oas/Oas.ts +16 -9
- package/src/oas/types.ts +19 -0
- package/src/oas/utils.ts +1 -1
- package/src/parser.ts +209 -228
- package/src/types.ts +41 -19
- package/src/utils.ts +168 -0
package/src/types.ts
CHANGED
|
@@ -2,6 +2,32 @@ import type { AdapterFactoryOptions } from '@kubb/core'
|
|
|
2
2
|
import type { Oas as OasClass } from './oas/Oas.ts'
|
|
3
3
|
import type { contentType } from './oas/types.ts'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Controls how various OAS constructs are mapped to Kubb AST nodes.
|
|
7
|
+
*/
|
|
8
|
+
export type ParserOptions = {
|
|
9
|
+
/**
|
|
10
|
+
* How `format: 'date-time'` schemas are represented. `false` falls through to a plain string.
|
|
11
|
+
*/
|
|
12
|
+
dateType: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
|
|
13
|
+
/**
|
|
14
|
+
* Whether `type: 'integer'` and `format: 'int64'` produce `number` or `bigint` nodes.
|
|
15
|
+
*/
|
|
16
|
+
integerType?: 'number' | 'bigint'
|
|
17
|
+
/**
|
|
18
|
+
* AST type used when no schema type can be inferred.
|
|
19
|
+
*/
|
|
20
|
+
unknownType: 'any' | 'unknown' | 'void'
|
|
21
|
+
/**
|
|
22
|
+
* AST type used for completely empty schemas (`{}`).
|
|
23
|
+
*/
|
|
24
|
+
emptySchemaType: 'any' | 'unknown' | 'void'
|
|
25
|
+
/**
|
|
26
|
+
* Suffix appended to derived enum names when building property schema names.
|
|
27
|
+
*/
|
|
28
|
+
enumSuffix: 'enum' | (string & {})
|
|
29
|
+
}
|
|
30
|
+
|
|
5
31
|
export type OasAdapterOptions = {
|
|
6
32
|
/**
|
|
7
33
|
* Validate the OpenAPI spec before parsing.
|
|
@@ -41,8 +67,13 @@ export type OasAdapterOptions = {
|
|
|
41
67
|
*/
|
|
42
68
|
discriminator?: 'strict' | 'inherit'
|
|
43
69
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
70
|
+
* Enable collision detection for inline enum and schema type names.
|
|
71
|
+
* When `true` (default), full-path names are used and name collisions
|
|
72
|
+
* across schema components are automatically resolved (e.g. `OrderParamsStatusEnum`).
|
|
73
|
+
* When `false`, enum names use only the immediate property context
|
|
74
|
+
* (e.g. `ParamsStatusEnum`) matching the v4 naming conventions, with numeric
|
|
75
|
+
* suffixes for deduplication (e.g. `ParamsStatusEnum2`).
|
|
76
|
+
* @default true
|
|
46
77
|
*/
|
|
47
78
|
collisionDetection?: boolean
|
|
48
79
|
/**
|
|
@@ -52,23 +83,7 @@ export type OasAdapterOptions = {
|
|
|
52
83
|
* - `false` falls through to a plain `string` node.
|
|
53
84
|
* @default 'string'
|
|
54
85
|
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Whether `type: 'integer'` / `format: 'int64'` produces `number` or `bigint` nodes.
|
|
58
|
-
* @default 'number'
|
|
59
|
-
*/
|
|
60
|
-
integerType?: 'number' | 'bigint'
|
|
61
|
-
/**
|
|
62
|
-
* AST type used when no schema type can be inferred.
|
|
63
|
-
* @default 'any'
|
|
64
|
-
*/
|
|
65
|
-
unknownType?: 'any' | 'unknown' | 'void'
|
|
66
|
-
/**
|
|
67
|
-
* AST type used for completely empty schemas (`{}`).
|
|
68
|
-
* @default `unknownType`
|
|
69
|
-
*/
|
|
70
|
-
emptySchemaType?: 'any' | 'unknown' | 'void'
|
|
71
|
-
}
|
|
86
|
+
} & Partial<ParserOptions>
|
|
72
87
|
|
|
73
88
|
export type OasAdapterResolvedOptions = {
|
|
74
89
|
validate: boolean
|
|
@@ -82,6 +97,13 @@ export type OasAdapterResolvedOptions = {
|
|
|
82
97
|
integerType: NonNullable<OasAdapterOptions['integerType']>
|
|
83
98
|
unknownType: NonNullable<OasAdapterOptions['unknownType']>
|
|
84
99
|
emptySchemaType: NonNullable<OasAdapterOptions['emptySchemaType']>
|
|
100
|
+
enumSuffix: OasAdapterOptions['enumSuffix']
|
|
101
|
+
/**
|
|
102
|
+
* Map from original `$ref` paths to their collision-resolved schema names.
|
|
103
|
+
* Populated by the adapter after each `parse()` call.
|
|
104
|
+
* e.g. `'#/components/schemas/Order'` → `'OrderSchema'`
|
|
105
|
+
*/
|
|
106
|
+
nameMapping: Map<string, string>
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
export type OasAdapter = AdapterFactoryOptions<'oas', OasAdapterOptions, OasAdapterResolvedOptions>
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { collect, createProperty, createSchema, narrowSchema } from '@kubb/ast'
|
|
2
|
+
import type { SchemaNode } from '@kubb/ast/types'
|
|
3
|
+
import type { KubbFile } from '@kubb/fabric-core/types'
|
|
4
|
+
import { SCALAR_PRIMITIVE_TYPES } from './constants.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts the schema name from a `$ref` string.
|
|
8
|
+
* For `#/components/schemas/Order` this returns `'Order'`.
|
|
9
|
+
* Falls back to the full ref string when no slash is present.
|
|
10
|
+
*/
|
|
11
|
+
export function extractRefName($ref: string): string {
|
|
12
|
+
return $ref.split('/').at(-1) ?? $ref
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Replaces the discriminator property's schema inside an `ObjectSchemaNode`
|
|
17
|
+
* with an enum of the given `values`.
|
|
18
|
+
*
|
|
19
|
+
* - When `enumName` is provided the enum is named, which lets printers emit a
|
|
20
|
+
* standalone enum declaration + type reference (e.g. `PetTypeEnum`).
|
|
21
|
+
* - When `enumName` is omitted the enum stays anonymous, so printers inline it
|
|
22
|
+
* as a literal union (e.g. `'dog'`).
|
|
23
|
+
*
|
|
24
|
+
* Returns the node unchanged when it is not an object or lacks the target property.
|
|
25
|
+
*/
|
|
26
|
+
export function applyDiscriminatorEnum({
|
|
27
|
+
node,
|
|
28
|
+
propertyName,
|
|
29
|
+
values,
|
|
30
|
+
enumName,
|
|
31
|
+
}: {
|
|
32
|
+
node: SchemaNode
|
|
33
|
+
propertyName: string
|
|
34
|
+
values: Array<string>
|
|
35
|
+
enumName?: string
|
|
36
|
+
}): SchemaNode {
|
|
37
|
+
if (node.type !== 'object' || !node.properties?.length) return node
|
|
38
|
+
|
|
39
|
+
const hasProperty = node.properties.some((prop) => prop.name === propertyName)
|
|
40
|
+
if (!hasProperty) return node
|
|
41
|
+
|
|
42
|
+
return createSchema({
|
|
43
|
+
...node,
|
|
44
|
+
properties: node.properties.map((prop) => {
|
|
45
|
+
if (prop.name !== propertyName) return prop
|
|
46
|
+
|
|
47
|
+
const enumSchema: SchemaNode = createSchema({
|
|
48
|
+
type: 'enum' as const,
|
|
49
|
+
primitive: 'string' as const,
|
|
50
|
+
enumValues: values,
|
|
51
|
+
name: enumName,
|
|
52
|
+
readOnly: prop.schema.readOnly,
|
|
53
|
+
writeOnly: prop.schema.writeOnly,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return createProperty({
|
|
57
|
+
...prop,
|
|
58
|
+
schema: enumSchema,
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merges consecutive anonymous (`name`-less) `ObjectSchemaNode`s in `members` into a
|
|
66
|
+
* single object by combining their `properties` arrays.
|
|
67
|
+
*
|
|
68
|
+
* Only adjacent pairs are merged — non-object or named nodes act as boundaries.
|
|
69
|
+
* This collapses patterns like `Address & { streetNumber } & { streetName }` into
|
|
70
|
+
* `Address & { streetNumber; streetName }`.
|
|
71
|
+
*/
|
|
72
|
+
export function mergeAdjacentAnonymousObjects(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
73
|
+
return members.reduce<Array<SchemaNode>>((acc, member) => {
|
|
74
|
+
const obj = narrowSchema(member, 'object')
|
|
75
|
+
if (obj && !obj.name) {
|
|
76
|
+
const prev = acc[acc.length - 1]
|
|
77
|
+
const prevObj = prev ? narrowSchema(prev, 'object') : null
|
|
78
|
+
if (prevObj && !prevObj.name) {
|
|
79
|
+
acc[acc.length - 1] = createSchema({
|
|
80
|
+
...prevObj,
|
|
81
|
+
properties: [...(prevObj.properties ?? []), ...(obj.properties ?? [])],
|
|
82
|
+
}) as SchemaNode
|
|
83
|
+
return acc
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
acc.push(member)
|
|
87
|
+
return acc
|
|
88
|
+
}, [])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Simplifies a union member list by removing `enum` nodes whose `primitive` type is
|
|
93
|
+
* already represented by a broader scalar node in the same union.
|
|
94
|
+
*
|
|
95
|
+
* For example `['placed', 'approved'] | string` collapses to `string` because
|
|
96
|
+
* `string` subsumes all string literals. `'' | string` similarly becomes `string`.
|
|
97
|
+
*
|
|
98
|
+
* Only scalar primitives (`string`, `number`, `integer`, `bigint`, `boolean`) are
|
|
99
|
+
* considered — object, array, and ref members are left untouched.
|
|
100
|
+
*
|
|
101
|
+
* Const-derived enums (those without an `enumType`, produced from an OpenAPI `const`
|
|
102
|
+
* keyword) are **never** removed — `'accepted' | string` must stay as-is because the
|
|
103
|
+
* literal is intentional.
|
|
104
|
+
*/
|
|
105
|
+
export function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
106
|
+
const scalarPrimitives = new Set(members.filter((m) => SCALAR_PRIMITIVE_TYPES.has(m.type as 'string')).map((m) => m.type as string))
|
|
107
|
+
if (!scalarPrimitives.size) return members
|
|
108
|
+
|
|
109
|
+
return members.filter((m) => {
|
|
110
|
+
if (m.type !== 'enum') return true
|
|
111
|
+
const prim = m.primitive
|
|
112
|
+
// Keep the enum if its primitive isn't fully subsumed.
|
|
113
|
+
if (!prim) return true
|
|
114
|
+
// Const-derived enums have no `enumType`; keep them as intentional literals.
|
|
115
|
+
if (!m.enumType) return true
|
|
116
|
+
// `number` subsumes `integer` literals and vice-versa for our purposes.
|
|
117
|
+
if (scalarPrimitives.has(prim)) return false
|
|
118
|
+
if ((prim === 'integer' || prim === 'number') && (scalarPrimitives.has('integer') || scalarPrimitives.has('number'))) return false
|
|
119
|
+
return true
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `nameMapping`, and calls `resolve` to obtain the `{ name, path }` pair for
|
|
125
|
+
* each import. When `oas` is supplied, only `$ref`s that are resolvable in the
|
|
126
|
+
* spec are included; omit it to skip the existence check.
|
|
127
|
+
*
|
|
128
|
+
* This function is the pure, state-free alternative to `OasParser.getImports`.
|
|
129
|
+
* Because it receives `nameMapping` explicitly it can be called without holding
|
|
130
|
+
* a reference to the parser or the OAS instance.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* // Use adapter state directly — no parser reference needed
|
|
135
|
+
* const imports = getImports({
|
|
136
|
+
* node: schemaNode,
|
|
137
|
+
* nameMapping: adapter.options.nameMapping,
|
|
138
|
+
* resolve: (schemaName) => ({
|
|
139
|
+
* name: schemaManager.getName(schemaName, { type: 'type' }),
|
|
140
|
+
* path: schemaManager.getFile(schemaName).path,
|
|
141
|
+
* }),
|
|
142
|
+
* })
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function getImports({
|
|
146
|
+
node,
|
|
147
|
+
nameMapping,
|
|
148
|
+
resolve,
|
|
149
|
+
}: {
|
|
150
|
+
node: SchemaNode
|
|
151
|
+
nameMapping: Map<string, string>
|
|
152
|
+
resolve: (schemaName: string) => { name: string; path: string } | undefined
|
|
153
|
+
}): Array<KubbFile.Import> {
|
|
154
|
+
return collect<KubbFile.Import>(node, {
|
|
155
|
+
schema(schemaNode): KubbFile.Import | undefined {
|
|
156
|
+
if (schemaNode.type !== 'ref' || !schemaNode.ref) return
|
|
157
|
+
|
|
158
|
+
const rawName = extractRefName(schemaNode.ref)
|
|
159
|
+
|
|
160
|
+
// Apply collision-resolved name if available.
|
|
161
|
+
const schemaName = nameMapping.get(rawName) ?? rawName
|
|
162
|
+
const result = resolve(schemaName)
|
|
163
|
+
if (!result) return
|
|
164
|
+
|
|
165
|
+
return { name: [result.name], path: result.path }
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
}
|