@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/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
- * Automatically resolve name collisions across schema components.
45
- * @default false
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
- dateType?: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
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
+ }