@kubb/adapter-oas 5.0.0-alpha.9 → 5.0.0-beta.2

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/oas/types.ts DELETED
@@ -1,71 +0,0 @@
1
- // external packages
2
- import type { Operation as OASOperation } from 'oas/operation'
3
- import type {
4
- DiscriminatorObject as OASDiscriminatorObject,
5
- OASDocument,
6
- HttpMethods as OASHttpMethods,
7
- MediaTypeObject as OASMediaTypeObject,
8
- ResponseObject as OASResponseObject,
9
- SchemaObject as OASSchemaObject,
10
- } from 'oas/types'
11
- import type { OpenAPIV3 } from 'openapi-types'
12
-
13
- export type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
14
-
15
- export type contentType = 'application/json' | (string & {})
16
-
17
- export type SchemaObject = OASSchemaObject & {
18
- /**
19
- * OAS 3.1 extension: allows marking a schema as nullable even when `type` does not include `'null'`.
20
- */
21
- 'x-nullable'?: boolean
22
- /**
23
- * OAS 3.1: constrains the schema to a single fixed value.
24
- * Semantically equivalent to a one-item `enum`.
25
- */
26
- const?: string | number | boolean | null
27
- /**
28
- * OAS 3.1: specifies the media type of the schema content.
29
- * When set to `'application/octet-stream'` on a `string` schema, the schema is treated as binary (`blob`).
30
- */
31
- contentMediaType?: string
32
- $ref?: string
33
- /**
34
- * OAS 3.1 / JSON Schema: positional items in a tuple schema.
35
- * Replaces the OAS 3.0 multi-item `items` array syntax.
36
- */
37
- prefixItems?: Array<SchemaObject | ReferenceObject>
38
- /**
39
- * JSON Schema: maps regex patterns to sub-schemas for additional property validation.
40
- */
41
- patternProperties?: Record<string, SchemaObject | boolean>
42
- /**
43
- * OAS 3.0 / JSON Schema: single-schema form.
44
- * The OAS base type already includes this, but we re-declare it here to ensure
45
- * the single-schema overload takes precedence over the multi-schema tuple form.
46
- */
47
- items?: SchemaObject | ReferenceObject
48
- /**
49
- * Enum values for this schema (narrowed from `unknown[]` in the base type).
50
- */
51
- enum?: Array<string | number | boolean | null>
52
- }
53
-
54
- /**
55
- * Re-exported from `constants.ts` for backwards compatibility.
56
- */
57
- export { httpMethods as HttpMethods } from '../constants.ts'
58
-
59
- export type HttpMethod = OASHttpMethods
60
-
61
- export type Document = OASDocument
62
-
63
- export type Operation = OASOperation
64
-
65
- export type DiscriminatorObject = OASDiscriminatorObject
66
-
67
- export type ReferenceObject = OpenAPIV3.ReferenceObject
68
-
69
- export type ResponseObject = OASResponseObject
70
-
71
- export type MediaTypeObject = OASMediaTypeObject
package/src/oas/utils.ts DELETED
@@ -1,402 +0,0 @@
1
- import path from 'node:path'
2
- import { pascalCase, URLPath } from '@internals/utils'
3
- import type { Config } from '@kubb/core'
4
- import { bundle, loadConfig } from '@redocly/openapi-core'
5
- import yaml from '@stoplight/yaml'
6
- import { isRef } from 'oas/types'
7
- import OASNormalize from 'oas-normalize'
8
- import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
9
- import { isPlainObject, mergeDeep } from 'remeda'
10
- import swagger2openapi from 'swagger2openapi'
11
- import { MERGE_DEFAULT_TITLE, MERGE_DEFAULT_VERSION, MERGE_OPENAPI_VERSION, structuralKeys } from '../constants.ts'
12
- import { Oas } from './Oas.ts'
13
- import type { contentType, Document, SchemaObject } from './types.ts'
14
-
15
- /**
16
- * Narrows `doc` to a Swagger 2.0 document.
17
- * Swagger 2.0 documents do not have an `openapi` version key.
18
- */
19
- export function isOpenApiV2Document(doc: unknown): doc is OpenAPIV2.Document {
20
- return !!doc && isPlainObject(doc) && !('openapi' in doc)
21
- }
22
-
23
- /**
24
- * Narrows `doc` to an OpenAPI 3.x document.
25
- * Any document that has an `openapi` key is considered 3.x (including 3.1).
26
- */
27
- export function isOpenApiV3Document(doc: unknown): doc is OpenAPIV3.Document {
28
- return !!doc && isPlainObject(doc) && 'openapi' in doc
29
- }
30
-
31
- /**
32
- * Narrows `doc` to an OpenAPI 3.1 document by checking the version prefix.
33
- */
34
- export function isOpenApiV3_1Document(doc: unknown): doc is OpenAPIV3_1.Document {
35
- return !!doc && isPlainObject(doc) && 'openapi' in (doc as object) && (doc as { openapi: string }).openapi.startsWith('3.1')
36
- }
37
-
38
- /**
39
- * Returns `true` when a schema should be treated as nullable.
40
- *
41
- * Covers three nullable signals across OAS versions:
42
- * - OAS 3.0: `nullable: true` or the vendor extension `x-nullable: true`.
43
- * - OAS 3.1 / JSON Schema: `type: 'null'` or `type: ['null', ...]` (multi-type array).
44
- */
45
- export function isNullable(schema?: SchemaObject & { 'x-nullable'?: boolean }): boolean {
46
- const explicitNullable = schema?.nullable ?? schema?.['x-nullable']
47
- if (explicitNullable === true) return true
48
-
49
- const schemaType = schema?.type
50
- if (schemaType === 'null') return true
51
- if (Array.isArray(schemaType)) return schemaType.includes('null')
52
-
53
- return false
54
- }
55
-
56
- /**
57
- * Narrows `obj` to an OpenAPI `$ref` pointer object.
58
- * Delegates to the `oas` package's own `isRef` helper.
59
- */
60
- export function isReference(obj?: unknown): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
61
- return !!obj && isRef(obj as object)
62
- }
63
-
64
- /**
65
- * Returns `true` when `obj` is a schema that carries a structured OAS 3.x `discriminator`
66
- * object — as opposed to a plain-string discriminator found in some Swagger 2 specs.
67
- */
68
- export function isDiscriminator(obj?: unknown): obj is SchemaObject & { discriminator: OpenAPIV3.DiscriminatorObject } {
69
- const record = obj as Record<string, unknown>
70
- return !!obj && !!record['discriminator'] && typeof record['discriminator'] !== 'string'
71
- }
72
-
73
- /**
74
- * Loads, dereferences, and wraps a raw OpenAPI document into an `Oas` instance.
75
- *
76
- * When given a file path string with `canBundle: true` (the default), Redocly's
77
- * bundler resolves all external `$ref`s first. Swagger 2.0 documents are
78
- * automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
79
- */
80
- export async function parse(
81
- pathOrApi: string | Document,
82
- { oasClass = Oas, canBundle = true, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
83
- ): Promise<Oas> {
84
- if (typeof pathOrApi === 'string' && canBundle) {
85
- const config = await loadConfig()
86
- const bundleResults = await bundle({ ref: pathOrApi, config, base: pathOrApi })
87
-
88
- return parse(bundleResults.bundle.parsed as string, { oasClass, canBundle, enablePaths })
89
- }
90
-
91
- const oasNormalize = new OASNormalize(pathOrApi, {
92
- enablePaths,
93
- colorizeErrors: true,
94
- })
95
- const document = (await oasNormalize.load()) as Document
96
-
97
- if (isOpenApiV2Document(document)) {
98
- const { openapi } = await swagger2openapi.convertObj(document, {
99
- anchors: true,
100
- })
101
-
102
- return new oasClass(openapi as Document)
103
- }
104
-
105
- return new oasClass(document)
106
- }
107
-
108
- /**
109
- * Deep-merges multiple OpenAPI documents into a single `Oas` instance.
110
- *
111
- * Each document is parsed independently (without path dereferencing) and then
112
- * recursively merged using `remeda`'s `mergeDeep`. The result is re-parsed so
113
- * the returned `Oas` instance is fully initialized.
114
- *
115
- * Throws when the input array is empty — at least one document is required.
116
- */
117
- export async function merge(pathOrApi: Array<string | Document>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
118
- const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false })))
119
-
120
- if (instances.length === 0) {
121
- throw new Error('No OAS instances provided for merging.')
122
- }
123
-
124
- const seed: Document = {
125
- openapi: MERGE_OPENAPI_VERSION,
126
- info: { title: MERGE_DEFAULT_TITLE, version: MERGE_DEFAULT_VERSION },
127
- paths: {},
128
- components: { schemas: {} },
129
- } as Document
130
-
131
- const merged = instances.reduce((acc, current) => mergeDeep(acc, current.document as Document), seed)
132
-
133
- return parse(merged, { oasClass })
134
- }
135
-
136
- /**
137
- * Constructs an `Oas` instance from a Kubb `Config` object.
138
- *
139
- * Handles all three input forms supported by `Config`:
140
- * - `data` — an inline YAML string, JSON string, or pre-parsed object.
141
- * - Array — multiple file paths that are deep-merged via {@link merge}.
142
- * - `path` — a local file path or a remote URL.
143
- */
144
- export function parseFromConfig(config: Config, oasClass: typeof Oas = Oas): Promise<Oas> {
145
- if ('data' in config.input) {
146
- if (typeof config.input.data === 'object') {
147
- return parse(structuredClone(config.input.data) as Document, { oasClass })
148
- }
149
-
150
- try {
151
- const api: string = yaml.parse(config.input.data as string)
152
- return parse(api, { oasClass })
153
- } catch {
154
- return parse(config.input.data as string, { oasClass })
155
- }
156
- }
157
-
158
- if (Array.isArray(config.input)) {
159
- return merge(
160
- config.input.map((input) => path.resolve(config.root, input.path)),
161
- { oasClass },
162
- )
163
- }
164
-
165
- if (new URLPath(config.input.path).isURL) {
166
- return parse(config.input.path, { oasClass })
167
- }
168
-
169
- return parse(path.resolve(config.root, config.input.path), { oasClass })
170
- }
171
-
172
- /**
173
- * Flattens a single-member or keyword-only `allOf` into its parent schema.
174
- *
175
- * Flattening is only performed when every `allOf` member is a "plain fragment" —
176
- * i.e. contains no structural composition keywords (see `structuralKeys`) and no
177
- * `$ref`. When any member carries structural meaning, the schema is returned unchanged.
178
- *
179
- * Keyword values from allOf members are merged into the parent on a first-write basis:
180
- * outer schema values always win over fragment values.
181
- */
182
- export function flattenSchema(schema: SchemaObject | null): SchemaObject | null {
183
- if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null
184
- if (schema.allOf.some((item) => isRef(item))) return schema
185
-
186
- const isPlainFragment = (item: SchemaObject) => !Object.keys(item).some((key) => structuralKeys.has(key as 'properties'))
187
- if (!schema.allOf.every((item) => isPlainFragment(item as SchemaObject))) return schema
188
-
189
- const merged: SchemaObject = { ...schema }
190
- delete merged.allOf
191
-
192
- for (const fragment of schema.allOf as SchemaObject[]) {
193
- for (const [key, value] of Object.entries(fragment)) {
194
- if (merged[key as keyof typeof merged] === undefined) {
195
- merged[key as keyof typeof merged] = value
196
- }
197
- }
198
- }
199
-
200
- return merged
201
- }
202
-
203
- /**
204
- * Validates an OpenAPI document using `oas-normalize`.
205
- * Enables path validation and colorized error output.
206
- */
207
- export async function validate(document: Document) {
208
- const oasNormalize = new OASNormalize(document, {
209
- enablePaths: true,
210
- colorizeErrors: true,
211
- })
212
-
213
- return oasNormalize.validate({
214
- parser: {
215
- validate: {
216
- errors: { colorize: true },
217
- },
218
- },
219
- })
220
- }
221
-
222
- /**
223
- * Discriminates the three component sources Kubb reads schemas from.
224
- */
225
- type SchemaSourceMode = 'schemas' | 'responses' | 'requestBodies'
226
-
227
- export type SchemaWithMetadata = {
228
- schema: SchemaObject
229
- source: SchemaSourceMode
230
- originalName: string
231
- }
232
-
233
- type GetSchemasResult = {
234
- schemas: Record<string, SchemaObject>
235
- nameMapping: Map<string, string>
236
- }
237
-
238
- /**
239
- * Walks a schema tree and collects the names of all `#/components/schemas/<name>` refs.
240
- * Used by `sortSchemas` to build the dependency graph.
241
- */
242
- function collectRefs(schema: unknown, refs = new Set<string>()): Set<string> {
243
- if (Array.isArray(schema)) {
244
- for (const item of schema) collectRefs(item, refs)
245
- return refs
246
- }
247
-
248
- if (schema && typeof schema === 'object') {
249
- for (const [key, value] of Object.entries(schema)) {
250
- if (key === '$ref' && typeof value === 'string') {
251
- const match = value.match(/^#\/components\/schemas\/(.+)$/)
252
- if (match) refs.add(match[1]!)
253
- } else {
254
- collectRefs(value, refs)
255
- }
256
- }
257
- }
258
-
259
- return refs
260
- }
261
-
262
- /**
263
- * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
264
- *
265
- * Schemas with no references come first; schemas that are referenced by others
266
- * come before those that reference them. This ensures code generators emit
267
- * referenced types before the types that depend on them.
268
- *
269
- * Cycles are silently skipped via the `stack` guard inside `visit`.
270
- */
271
- export function sortSchemas(schemas: Record<string, SchemaObject>): Record<string, SchemaObject> {
272
- const deps = new Map<string, string[]>()
273
-
274
- for (const [name, schema] of Object.entries(schemas)) {
275
- deps.set(name, Array.from(collectRefs(schema)))
276
- }
277
-
278
- const sorted: string[] = []
279
- const visited = new Set<string>()
280
-
281
- function visit(name: string, stack: Set<string>) {
282
- if (visited.has(name) || stack.has(name)) return
283
- stack.add(name)
284
- for (const child of deps.get(name) ?? []) {
285
- if (deps.has(child)) visit(child, stack)
286
- }
287
- stack.delete(name)
288
- visited.add(name)
289
- sorted.push(name)
290
- }
291
-
292
- for (const name of Object.keys(schemas)) {
293
- visit(name, new Set())
294
- }
295
-
296
- const result: Record<string, SchemaObject> = {}
297
- for (const name of sorted) result[name] = schemas[name]!
298
- return result
299
- }
300
-
301
- /**
302
- * Extracts the inline schema from a media-type `content` map.
303
- *
304
- * Prefers `preferredContentType` when provided; otherwise falls back to the
305
- * first key in the map. Returns `null` when:
306
- * - `content` is absent.
307
- * - The target content-type has no `schema` entry.
308
- * - The schema is a `$ref` (callers resolve refs separately).
309
- */
310
- export function extractSchemaFromContent(content: Record<string, unknown> | undefined, preferredContentType?: contentType): SchemaObject | null {
311
- if (!content) return null
312
-
313
- const firstContentType = Object.keys(content)[0] ?? 'application/json'
314
- const targetContentType = preferredContentType ?? firstContentType
315
- const contentSchema = content[targetContentType] as { schema?: SchemaObject } | undefined
316
- const schema = contentSchema?.schema
317
-
318
- if (schema && '$ref' in schema) return null
319
- return schema ?? null
320
- }
321
-
322
- /**
323
- * Returns the PascalCase suffix appended to a component name when resolving
324
- * cross-source name collisions (schemas vs. responses vs. requestBodies).
325
- */
326
- function getSemanticSuffix(source: SchemaSourceMode): string {
327
- switch (source) {
328
- case 'schemas':
329
- return 'Schema'
330
- case 'responses':
331
- return 'Response'
332
- case 'requestBodies':
333
- return 'Request'
334
- }
335
- }
336
-
337
- /**
338
- * Builds `GetSchemasResult` without any collision detection.
339
- * Each schema is registered under its original component name and its full
340
- * `#/components/<source>/<name>` ref path.
341
- */
342
- export function legacyResolve(schemasWithMeta: SchemaWithMetadata[]): GetSchemasResult {
343
- const schemas: Record<string, SchemaObject> = {}
344
- const nameMapping = new Map<string, string>()
345
-
346
- for (const item of schemasWithMeta) {
347
- schemas[item.originalName] = item.schema
348
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName)
349
- }
350
-
351
- return { schemas, nameMapping }
352
- }
353
-
354
- /**
355
- * Builds `GetSchemasResult` with automatic name-collision resolution.
356
- *
357
- * When two or more schemas normalize to the same PascalCase name:
358
- * - If they share the same source, a numeric suffix (`2`, `3`, …) is appended.
359
- * - If they come from different sources (schemas / responses / requestBodies),
360
- * a semantic suffix (`Schema`, `Response`, `Request`) is appended.
361
- *
362
- * Non-colliding schemas are left unchanged.
363
- */
364
- export function resolveCollisions(schemasWithMeta: SchemaWithMetadata[]): GetSchemasResult {
365
- const schemas: Record<string, SchemaObject> = {}
366
- const nameMapping = new Map<string, string>()
367
- const normalizedNames = new Map<string, SchemaWithMetadata[]>()
368
-
369
- for (const item of schemasWithMeta) {
370
- const normalized = pascalCase(item.originalName)
371
- const bucket = normalizedNames.get(normalized) ?? []
372
- bucket.push(item)
373
- normalizedNames.set(normalized, bucket)
374
- }
375
-
376
- for (const [, items] of normalizedNames) {
377
- if (items.length === 1) {
378
- const item = items[0]!
379
- schemas[item.originalName] = item.schema
380
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName)
381
- continue
382
- }
383
-
384
- const sources = new Set(items.map((item) => item.source))
385
-
386
- if (sources.size === 1) {
387
- items.forEach((item, index) => {
388
- const uniqueName = item.originalName + (index === 0 ? '' : (index + 1).toString())
389
- schemas[uniqueName] = item.schema
390
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName)
391
- })
392
- } else {
393
- items.forEach((item) => {
394
- const uniqueName = item.originalName + getSemanticSuffix(item.source)
395
- schemas[uniqueName] = item.schema
396
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName)
397
- })
398
- }
399
- }
400
-
401
- return { schemas, nameMapping }
402
- }
package/src/utils.ts DELETED
@@ -1,168 +0,0 @@
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
- }