@kubb/plugin-oas 4.18.5 → 4.19.1

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.
Files changed (43) hide show
  1. package/dist/{SchemaGenerator-DExEah4y.js → SchemaGenerator-CK_Mv0RW.js} +90 -39
  2. package/dist/SchemaGenerator-CK_Mv0RW.js.map +1 -0
  3. package/dist/{SchemaGenerator-B18-jWu7.cjs → SchemaGenerator-D2ZpjZgn.cjs} +91 -40
  4. package/dist/SchemaGenerator-D2ZpjZgn.cjs.map +1 -0
  5. package/dist/{SchemaMapper-oZdVAbXF.d.ts → SchemaMapper-DI2vHHE0.d.ts} +24 -7
  6. package/dist/{SchemaMapper-BWZ2ZjPQ.d.cts → SchemaMapper-DmB5NyNo.d.cts} +24 -7
  7. package/dist/{createGenerator-Bq76G0TY.d.ts → createGenerator-3zJdjpOn.d.ts} +38 -2
  8. package/dist/{createGenerator-RL6jHKJ-.d.cts → createGenerator-cYz-kExA.d.cts} +38 -2
  9. package/dist/generators.d.cts +2 -2
  10. package/dist/generators.d.ts +2 -2
  11. package/dist/getSchemaFactory-CBp1me72.cjs +29 -0
  12. package/dist/getSchemaFactory-CBp1me72.cjs.map +1 -0
  13. package/dist/getSchemaFactory-DsoVRgxV.js +24 -0
  14. package/dist/getSchemaFactory-DsoVRgxV.js.map +1 -0
  15. package/dist/hooks.cjs +1 -1
  16. package/dist/hooks.d.cts +2 -2
  17. package/dist/hooks.d.ts +2 -2
  18. package/dist/hooks.js +1 -1
  19. package/dist/index.cjs +4 -3
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +4 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/mocks.d.cts +1 -1
  26. package/dist/mocks.d.ts +1 -1
  27. package/dist/utils.cjs +26 -3
  28. package/dist/utils.cjs.map +1 -1
  29. package/dist/utils.d.cts +25 -5
  30. package/dist/utils.d.ts +25 -5
  31. package/dist/utils.js +24 -1
  32. package/dist/utils.js.map +1 -1
  33. package/package.json +3 -3
  34. package/src/SchemaGenerator.ts +90 -32
  35. package/src/plugin.ts +2 -0
  36. package/src/types.ts +30 -0
  37. package/src/utils/getSchemas.ts +27 -105
  38. package/dist/SchemaGenerator-B18-jWu7.cjs.map +0 -1
  39. package/dist/SchemaGenerator-DExEah4y.js.map +0 -1
  40. package/dist/getSchemas-BUXPwm-5.js +0 -101
  41. package/dist/getSchemas-BUXPwm-5.js.map +0 -1
  42. package/dist/getSchemas-D3YweIFO.cjs +0 -112
  43. package/dist/getSchemas-D3YweIFO.cjs.map +0 -1
@@ -3,7 +3,7 @@ import { BaseGenerator, type FileMetaBase } from '@kubb/core'
3
3
  import transformers, { pascalCase } from '@kubb/core/transformers'
4
4
  import { type AsyncEventEmitter, getUniqueName } from '@kubb/core/utils'
5
5
  import type { KubbFile } from '@kubb/fabric-core/types'
6
- import type { contentType, Oas, OpenAPIV3, SchemaObject } from '@kubb/oas'
6
+ import type { contentType, Oas, OasTypes, OpenAPIV3, SchemaObject } from '@kubb/oas'
7
7
  import { isDiscriminator, isNullable, isReference } from '@kubb/oas'
8
8
  import type { Fabric } from '@kubb/react-fabric'
9
9
  import pLimit from 'p-limit'
@@ -12,7 +12,6 @@ import type { Generator } from './generators/types.ts'
12
12
  import { isKeyword, type Schema, type SchemaKeywordMapper, schemaKeywords } from './SchemaMapper.ts'
13
13
  import type { OperationSchema, Override, Refs } from './types.ts'
14
14
  import { getSchemaFactory } from './utils/getSchemaFactory.ts'
15
- import { getSchemas } from './utils/getSchemas.ts'
16
15
  import { buildSchema } from './utils.tsx'
17
16
 
18
17
  export type GetSchemaGeneratorOptions<T extends SchemaGenerator<any, any, any>> = T extends SchemaGenerator<infer Options, any, any> ? Options : never
@@ -41,6 +40,11 @@ export type SchemaGeneratorOptions = {
41
40
  emptySchemaType: 'any' | 'unknown' | 'void'
42
41
  enumType?: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
43
42
  enumSuffix?: string
43
+ /**
44
+ * @deprecated Will be removed in v5. Use `collisionDetection: true` instead to prevent enum name collisions.
45
+ * When `collisionDetection` is enabled, the rootName-based approach eliminates the need for numeric suffixes.
46
+ * @internal
47
+ */
44
48
  usedEnumNames?: Record<string, number>
45
49
  mapper?: Record<string, string>
46
50
  typed?: boolean
@@ -64,6 +68,7 @@ type SchemaProps = {
64
68
  schema: SchemaObject | null
65
69
  name: string | null
66
70
  parentName: string | null
71
+ rootName?: string | null
67
72
  }
68
73
 
69
74
  export class SchemaGenerator<
@@ -74,13 +79,31 @@ export class SchemaGenerator<
74
79
  // Collect the types of all referenced schemas, so we can export them later
75
80
  refs: Refs = {}
76
81
 
77
- // Keep track of already used type aliases
78
- #usedAliasNames: Record<string, number> = {}
82
+ // Map from original component paths to resolved schema names (after collision resolution)
83
+ // e.g., { '#/components/schemas/Order': 'OrderSchema', '#/components/responses/Product': 'ProductResponse' }
84
+ #schemaNameMapping: Map<string, string> = new Map()
85
+
86
+ // Flag to track if nameMapping has been initialized
87
+ #nameMappingInitialized = false
79
88
 
80
89
  // Cache for parsed schemas to avoid redundant parsing
81
90
  // Using WeakMap for automatic garbage collection when schemas are no longer referenced
82
91
  #parseCache: Map<string, Schema[]> = new Map()
83
92
 
93
+ /**
94
+ * Ensure the name mapping is initialized (lazy initialization)
95
+ */
96
+ #ensureNameMapping() {
97
+ if (this.#nameMappingInitialized) {
98
+ return
99
+ }
100
+
101
+ const { oas, contentType, include } = this.context
102
+ const { nameMapping } = oas.getSchemas({ contentType, includes: include })
103
+ this.#schemaNameMapping = nameMapping
104
+ this.#nameMappingInitialized = true
105
+ }
106
+
84
107
  /**
85
108
  * Creates a type node from a given schema.
86
109
  * Delegates to getBaseTypeFromSchema internally and
@@ -102,6 +125,7 @@ export class SchemaGenerator<
102
125
  schema: props.schema,
103
126
  name: props.name,
104
127
  parentName: props.parentName,
128
+ rootName: props.rootName,
105
129
  })
106
130
 
107
131
  const cached = this.#parseCache.get(cacheKey)
@@ -321,7 +345,7 @@ export class SchemaGenerator<
321
345
  /**
322
346
  * Recursively creates a type literal with the given props.
323
347
  */
324
- #parseProperties(name: string | null, schemaObject: SchemaObject): Schema[] {
348
+ #parseProperties(name: string | null, schemaObject: SchemaObject, rootName?: string | null): Schema[] {
325
349
  const properties = schemaObject?.properties || {}
326
350
  const additionalProperties = schemaObject?.additionalProperties
327
351
  const required = schemaObject?.required
@@ -335,7 +359,7 @@ export class SchemaGenerator<
335
359
  const isRequired = Array.isArray(required) ? required?.includes(propertyName) : !!required
336
360
  const nullable = isNullable(propertySchema)
337
361
 
338
- validationFunctions.push(...this.parse({ schema: propertySchema, name: propertyName, parentName: name }))
362
+ validationFunctions.push(...this.parse({ schema: propertySchema, name: propertyName, parentName: name, rootName: rootName || name }))
339
363
 
340
364
  validationFunctions.push({
341
365
  keyword: schemaKeywords.name,
@@ -359,7 +383,7 @@ export class SchemaGenerator<
359
383
  additionalPropertiesSchemas =
360
384
  additionalProperties === true || !Object.keys(additionalProperties).length
361
385
  ? [{ keyword: this.#getUnknownType(name) }]
362
- : this.parse({ schema: additionalProperties as SchemaObject, name: null, parentName: name })
386
+ : this.parse({ schema: additionalProperties as SchemaObject, name: null, parentName: name, rootName: rootName || name })
363
387
  }
364
388
 
365
389
  let patternPropertiesSchemas: Record<string, Schema[]> = {}
@@ -369,7 +393,7 @@ export class SchemaGenerator<
369
393
  const schemas =
370
394
  patternSchema === true || !Object.keys(patternSchema as object).length
371
395
  ? [{ keyword: this.#getUnknownType(name) }]
372
- : this.parse({ schema: patternSchema, name: null, parentName: name })
396
+ : this.parse({ schema: patternSchema, name: null, parentName: name, rootName: rootName || name })
373
397
 
374
398
  return {
375
399
  ...acc,
@@ -453,15 +477,21 @@ export class SchemaGenerator<
453
477
  ]
454
478
  }
455
479
 
456
- const originalName = getUniqueName($ref.replace(/.+\//, ''), this.#usedAliasNames)
480
+ // Ensure name mapping is initialized before resolving names
481
+ this.#ensureNameMapping()
482
+
483
+ const originalName = $ref.replace(/.+\//, '')
484
+ // Use the full $ref path to look up the collision-resolved name
485
+ const resolvedName = this.#schemaNameMapping.get($ref) || originalName
486
+
457
487
  const propertyName = this.context.pluginManager.resolveName({
458
- name: originalName,
488
+ name: resolvedName,
459
489
  pluginKey: this.context.plugin.key,
460
490
  type: 'function',
461
491
  })
462
492
 
463
493
  const fileName = this.context.pluginManager.resolveName({
464
- name: originalName,
494
+ name: resolvedName,
465
495
  pluginKey: this.context.plugin.key,
466
496
  type: 'file',
467
497
  })
@@ -473,7 +503,7 @@ export class SchemaGenerator<
473
503
 
474
504
  this.refs[$ref] = {
475
505
  propertyName,
476
- originalName,
506
+ originalName: resolvedName,
477
507
  path: file.path,
478
508
  }
479
509
 
@@ -504,7 +534,7 @@ export class SchemaGenerator<
504
534
  return schema
505
535
  }
506
536
 
507
- const objectPropertySchema = SchemaGenerator.find(this.parse({ schema: schemaObject, name: null, parentName: null }), schemaKeywords.object)
537
+ const objectPropertySchema = SchemaGenerator.find(this.parse({ schema: schemaObject, name: null, parentName: null, rootName: null }), schemaKeywords.object)
508
538
 
509
539
  return {
510
540
  ...schema,
@@ -605,7 +635,7 @@ export class SchemaGenerator<
605
635
  * This is the very core of the OpenAPI to TS conversion - it takes a
606
636
  * schema and returns the appropriate type.
607
637
  */
608
- #parseSchemaObject({ schema: _schemaObject, name, parentName }: SchemaProps): Schema[] {
638
+ #parseSchemaObject({ schema: _schemaObject, name, parentName, rootName }: SchemaProps): Schema[] {
609
639
  const normalizedSchema = this.context.oas.flattenSchema(_schemaObject)
610
640
 
611
641
  const { schemaObject, version } = this.#getParsedSchemaObject(normalizedSchema)
@@ -710,6 +740,7 @@ export class SchemaGenerator<
710
740
  schema: { ...schemaObject, type: item },
711
741
  name,
712
742
  parentName,
743
+ rootName,
713
744
  })[0],
714
745
  )
715
746
  .filter(Boolean)
@@ -764,7 +795,7 @@ export class SchemaGenerator<
764
795
  args: (schemaObject.oneOf || schemaObject.anyOf)!
765
796
  .map((item) => {
766
797
  // first item, this is ref
767
- return item && this.parse({ schema: item as SchemaObject, name, parentName })[0]
798
+ return item && this.parse({ schema: item as SchemaObject, name, parentName, rootName })[0]
768
799
  })
769
800
  .filter(Boolean),
770
801
  }
@@ -778,7 +809,7 @@ export class SchemaGenerator<
778
809
  }
779
810
 
780
811
  if (schemaWithoutOneOf.properties) {
781
- const propertySchemas = this.parse({ schema: schemaWithoutOneOf, name, parentName })
812
+ const propertySchemas = this.parse({ schema: schemaWithoutOneOf, name, parentName, rootName })
782
813
 
783
814
  union.args = [
784
815
  ...union.args.map((arg) => {
@@ -808,7 +839,7 @@ export class SchemaGenerator<
808
839
  return []
809
840
  }
810
841
 
811
- return item ? this.parse({ schema: item, name, parentName }) : []
842
+ return item ? this.parse({ schema: item, name, parentName, rootName }) : []
812
843
  })
813
844
  .filter(Boolean),
814
845
  }
@@ -848,7 +879,7 @@ export class SchemaGenerator<
848
879
  }
849
880
 
850
881
  for (const item of parsedItems) {
851
- const parsed = this.parse({ schema: item, name, parentName })
882
+ const parsed = this.parse({ schema: item, name, parentName, rootName })
852
883
 
853
884
  if (Array.isArray(parsed)) {
854
885
  and.args = and.args ? and.args.concat(parsed) : parsed
@@ -857,7 +888,7 @@ export class SchemaGenerator<
857
888
  }
858
889
 
859
890
  if (schemaWithoutAllOf.properties) {
860
- and.args = [...(and.args || []), ...this.parse({ schema: schemaWithoutAllOf, name, parentName })]
891
+ and.args = [...(and.args || []), ...this.parse({ schema: schemaWithoutAllOf, name, parentName, rootName })]
861
892
  }
862
893
 
863
894
  return SchemaGenerator.combineObjects([and, ...baseItems])
@@ -880,12 +911,22 @@ export class SchemaGenerator<
880
911
  items: normalizedItems,
881
912
  } as SchemaObject
882
913
 
883
- return this.parse({ schema: normalizedSchema, name, parentName })
914
+ return this.parse({ schema: normalizedSchema, name, parentName, rootName })
884
915
  }
885
916
 
886
917
  // Removed verbose enum parsing debug log - too noisy for hundreds of enums
887
918
 
888
- const enumName = getUniqueName(pascalCase([parentName, name, options.enumSuffix].join(' ')), this.options.usedEnumNames || {})
919
+ // Include rootName in enum naming to avoid collisions for nested enums with same path
920
+ // Only add rootName if it differs from parentName to avoid duplication
921
+ // This is controlled by the collisionDetection flag to maintain backward compatibility
922
+ const useCollisionDetection = this.context.oas.options.collisionDetection ?? false
923
+ const enumNameParts =
924
+ useCollisionDetection && rootName && rootName !== parentName ? [rootName, parentName, name, options.enumSuffix] : [parentName, name, options.enumSuffix]
925
+
926
+ // @deprecated usedEnumNames will be removed in v5 - collisionDetection with rootName-based naming eliminates the need for numeric suffixes
927
+ const enumName = useCollisionDetection
928
+ ? pascalCase(enumNameParts.join(' '))
929
+ : getUniqueName(pascalCase(enumNameParts.join(' ')), this.options.usedEnumNames || {})
889
930
  const typeName = this.context.pluginManager.resolveName({
890
931
  name: enumName,
891
932
  pluginKey: this.context.plugin.key,
@@ -1016,13 +1057,14 @@ export class SchemaGenerator<
1016
1057
  max,
1017
1058
  items: prefixItems
1018
1059
  .map((item) => {
1019
- return this.parse({ schema: item, name, parentName })[0]
1060
+ return this.parse({ schema: item, name, parentName, rootName })[0]
1020
1061
  })
1021
1062
  .filter(Boolean),
1022
1063
  rest: this.parse({
1023
1064
  schema: items,
1024
1065
  name,
1025
1066
  parentName,
1067
+ rootName,
1026
1068
  })[0],
1027
1069
  },
1028
1070
  },
@@ -1168,7 +1210,7 @@ export class SchemaGenerator<
1168
1210
  if ('items' in schemaObject || schemaObject.type === ('array' as 'string')) {
1169
1211
  const min = schemaObject.minimum ?? schemaObject.minLength ?? schemaObject.minItems ?? undefined
1170
1212
  const max = schemaObject.maximum ?? schemaObject.maxLength ?? schemaObject.maxItems ?? undefined
1171
- const items = this.parse({ schema: 'items' in schemaObject ? (schemaObject.items as SchemaObject) : [], name, parentName })
1213
+ const items = this.parse({ schema: 'items' in schemaObject ? (schemaObject.items as SchemaObject) : [], name, parentName, rootName })
1172
1214
  const unique = !!schemaObject.uniqueItems
1173
1215
 
1174
1216
  return [
@@ -1205,10 +1247,10 @@ export class SchemaGenerator<
1205
1247
  return acc
1206
1248
  }, schemaObject || {}) as SchemaObject
1207
1249
 
1208
- return [...this.#parseProperties(name, schemaObjectOverridden), ...baseItems]
1250
+ return [...this.#parseProperties(name, schemaObjectOverridden, rootName), ...baseItems]
1209
1251
  }
1210
1252
 
1211
- return [...this.#parseProperties(name, schemaObject), ...baseItems]
1253
+ return [...this.#parseProperties(name, schemaObject, rootName), ...baseItems]
1212
1254
  }
1213
1255
 
1214
1256
  if (schemaObject.type) {
@@ -1244,13 +1286,29 @@ export class SchemaGenerator<
1244
1286
 
1245
1287
  async build(...generators: Array<Generator<TPluginOptions>>): Promise<Array<KubbFile.File<TFileMeta>>> {
1246
1288
  const { oas, contentType, include } = this.context
1247
- const schemas = getSchemas({ oas, contentType, includes: include })
1248
- const schemaEntries = Object.entries(schemas)
1249
1289
 
1250
- this.context.events?.emit('debug', {
1251
- date: new Date(),
1252
- logs: [`Building ${schemaEntries.length} schemas`, ` • Content Type: ${contentType || 'application/json'}`, ` • Generators: ${generators.length}`],
1253
- })
1290
+ // Initialize the name mapping if not already done
1291
+ if (!this.#nameMappingInitialized) {
1292
+ const { schemas, nameMapping } = oas.getSchemas({ contentType, includes: include })
1293
+ this.#schemaNameMapping = nameMapping
1294
+ this.#nameMappingInitialized = true
1295
+ const schemaEntries = Object.entries(schemas)
1296
+
1297
+ this.context.events?.emit('debug', {
1298
+ date: new Date(),
1299
+ logs: [`Building ${schemaEntries.length} schemas`, ` • Content Type: ${contentType || 'application/json'}`, ` • Generators: ${generators.length}`],
1300
+ })
1301
+
1302
+ // Continue with build using the schemas
1303
+ return this.#doBuild(schemas, generators)
1304
+ }
1305
+ // If already initialized, just get the schemas (without mapping)
1306
+ const { schemas } = oas.getSchemas({ contentType, includes: include })
1307
+ return this.#doBuild(schemas, generators)
1308
+ }
1309
+
1310
+ async #doBuild(schemas: Record<string, OasTypes.SchemaObject>, generators: Array<Generator<TPluginOptions>>): Promise<Array<KubbFile.File<TFileMeta>>> {
1311
+ const schemaEntries = Object.entries(schemas)
1254
1312
 
1255
1313
  // Increased parallelism for better performance
1256
1314
  // - generatorLimit increased from 1 to 3 to allow parallel generator processing
@@ -1263,7 +1321,7 @@ export class SchemaGenerator<
1263
1321
  const schemaTasks = schemaEntries.map(([name, schemaObject]) =>
1264
1322
  schemaLimit(async () => {
1265
1323
  const options = this.#getOptions(name)
1266
- const tree = this.parse({ schema: schemaObject, name, parentName: null })
1324
+ const tree = this.parse({ schema: schemaObject, name, parentName: null, rootName: name })
1267
1325
 
1268
1326
  if (generator.type === 'react') {
1269
1327
  await buildSchema(
package/src/plugin.ts CHANGED
@@ -23,6 +23,7 @@ export const pluginOas = definePlugin<PluginOas>((options) => {
23
23
  contentType,
24
24
  oasClass,
25
25
  discriminator = 'strict',
26
+ collisionDetection = false,
26
27
  } = options
27
28
 
28
29
  const getOas = async ({ validate, config, events }: { validate: boolean; config: Config; events: AsyncEventEmitter<KubbEvents> }): Promise<Oas> => {
@@ -32,6 +33,7 @@ export const pluginOas = definePlugin<PluginOas>((options) => {
32
33
  oas.setOptions({
33
34
  contentType,
34
35
  discriminator,
36
+ collisionDetection,
35
37
  })
36
38
 
37
39
  try {
package/src/types.ts CHANGED
@@ -71,6 +71,36 @@ export type Options = {
71
71
  * Define some generators next to the JSON generation
72
72
  */
73
73
  generators?: Array<Generator<PluginOas>>
74
+ /**
75
+ * Resolve name collisions when schemas from different components share the same name (case-insensitive).
76
+ *
77
+ * When enabled, Kubb automatically detects and resolves collisions using intelligent suffixes:
78
+ * - Cross-component collisions: Adds semantic suffixes based on the component type (Schema/Response/Request)
79
+ * - Same-component collisions: Adds numeric suffixes (2, 3, ...) for case-insensitive duplicates
80
+ * - Nested enum collisions: Includes root schema name in enum names to prevent duplicates across schemas
81
+ *
82
+ * When disabled (legacy behavior), collisions may result in duplicate files or overwrite issues.
83
+ *
84
+ * **Cross-component collision example:**
85
+ * If you have "Order" in both schemas and requestBodies:
86
+ * - With `collisionDetection: true`: Generates `OrderSchema.ts`, `OrderRequest.ts`
87
+ * - With `collisionDetection: false`: May generate duplicate `Order.ts` files
88
+ *
89
+ * **Same-component collision example:**
90
+ * If you have "Variant" and "variant" in schemas:
91
+ * - With `collisionDetection: true`: Generates `Variant.ts`, `Variant2.ts`
92
+ * - With `collisionDetection: false`: May overwrite or create duplicates
93
+ *
94
+ * **Nested enum collision example:**
95
+ * If you have "params.channel" enum in both "NotificationTypeA" and "NotificationTypeB":
96
+ * - With `collisionDetection: true`: Generates `notificationTypeAParamsChannelEnum`, `notificationTypeBParamsChannelEnum`
97
+ * - With `collisionDetection: false`: Generates duplicate `paramsChannelEnum` in both files
98
+ *
99
+ * @default false (will be `true` in v5)
100
+ * @see https://github.com/kubb-labs/kubb/issues/1999
101
+ * @note In Kubb v5, this will be enabled by default and the deprecated `usedEnumNames` mechanism will be removed
102
+ */
103
+ collisionDetection?: boolean
74
104
  }
75
105
 
76
106
  /**
@@ -1,119 +1,41 @@
1
1
  import type { contentType, Oas, OasTypes } from '@kubb/oas'
2
2
 
3
+ export type GetSchemasResult = {
4
+ schemas: Record<string, OasTypes.SchemaObject>
5
+ /**
6
+ * Mapping from original component name to resolved name after collision handling
7
+ * e.g., { 'Order': 'OrderSchema', 'variant': 'variant2' }
8
+ */
9
+ nameMapping: Map<string, string>
10
+ }
11
+
3
12
  type Mode = 'schemas' | 'responses' | 'requestBodies'
4
13
 
5
14
  type GetSchemasProps = {
6
15
  oas: Oas
7
16
  contentType?: contentType
8
17
  includes?: Mode[]
9
- }
10
-
11
- /**
12
- * Collect all schema $ref dependencies recursively.
13
- */
14
- function collectRefs(schema: unknown, refs = new Set<string>()): Set<string> {
15
- if (Array.isArray(schema)) {
16
- for (const item of schema) {
17
- collectRefs(item, refs)
18
- }
19
- return refs
20
- }
21
-
22
- if (schema && typeof schema === 'object') {
23
- for (const [key, value] of Object.entries(schema)) {
24
- if (key === '$ref' && typeof value === 'string') {
25
- const match = value.match(/^#\/components\/schemas\/(.+)$/)
26
- if (match) {
27
- refs.add(match[1]!)
28
- }
29
- } else {
30
- collectRefs(value, refs)
31
- }
32
- }
33
- }
34
-
35
- return refs
36
- }
37
-
38
- /**
39
- * Sort schemas topologically so referenced schemas appear first.
40
- */
41
- function sortSchemas(schemas: Record<string, OasTypes.SchemaObject>): Record<string, OasTypes.SchemaObject> {
42
- const deps = new Map<string, string[]>()
43
-
44
- for (const [name, schema] of Object.entries(schemas)) {
45
- deps.set(name, Array.from(collectRefs(schema)))
46
- }
47
-
48
- const sorted: string[] = []
49
- const visited = new Set<string>()
50
-
51
- function visit(name: string, stack = new Set<string>()) {
52
- if (visited.has(name)) {
53
- return
54
- }
55
- if (stack.has(name)) {
56
- return
57
- } // circular refs, ignore
58
- stack.add(name)
59
- const children = deps.get(name) || []
60
- for (const child of children) {
61
- if (deps.has(child)) {
62
- visit(child, stack)
63
- }
64
- }
65
- stack.delete(name)
66
- visited.add(name)
67
- sorted.push(name)
68
- }
69
-
70
- for (const name of Object.keys(schemas)) {
71
- visit(name)
72
- }
73
-
74
- const sortedSchemas: Record<string, OasTypes.SchemaObject> = {}
75
- for (const name of sorted) {
76
- sortedSchemas[name] = schemas[name]!
77
- }
78
- return sortedSchemas
18
+ /**
19
+ * Whether to resolve name collisions with suffixes.
20
+ * If not provided, uses oas.options.collisionDetection
21
+ * @default false (from oas.options or fallback)
22
+ */
23
+ collisionDetection?: boolean
79
24
  }
80
25
 
81
26
  /**
82
27
  * Collect schemas from OpenAPI components (schemas, responses, requestBodies)
83
- * and return them in dependency order.
28
+ * and return them in dependency order along with name mapping for collision resolution.
29
+ *
30
+ * This function is a wrapper around the oas.getSchemas() method for backward compatibility.
31
+ * New code should use oas.getSchemas() directly.
32
+ *
33
+ * @deprecated Use oas.getSchemas() instead
84
34
  */
85
- export function getSchemas({ oas, contentType, includes = ['schemas', 'requestBodies', 'responses'] }: GetSchemasProps): Record<string, OasTypes.SchemaObject> {
86
- const components = oas.getDefinition().components
87
- let schemas: Record<string, OasTypes.SchemaObject> = {}
88
-
89
- if (includes.includes('schemas')) {
90
- schemas = {
91
- ...schemas,
92
- ...((components?.schemas as Record<string, OasTypes.SchemaObject>) || {}),
93
- }
94
- }
95
-
96
- if (includes.includes('responses')) {
97
- const responses = components?.responses || {}
98
- for (const [name, response] of Object.entries(responses)) {
99
- const responseObject = response as OasTypes.ResponseObject
100
- if (responseObject.content && !schemas[name]) {
101
- const firstContentType = Object.keys(responseObject.content)[0] || 'application/json'
102
- schemas[name] = responseObject.content?.[contentType || firstContentType]?.schema as OasTypes.SchemaObject
103
- }
104
- }
105
- }
106
-
107
- if (includes.includes('requestBodies')) {
108
- const requestBodies = components?.requestBodies || {}
109
- for (const [name, request] of Object.entries(requestBodies)) {
110
- const requestObject = request as OasTypes.RequestBodyObject
111
- if (requestObject.content && !schemas[name]) {
112
- const firstContentType = Object.keys(requestObject.content)[0] || 'application/json'
113
- schemas[name] = requestObject.content?.[contentType || firstContentType]?.schema as OasTypes.SchemaObject
114
- }
115
- }
116
- }
117
-
118
- return sortSchemas(schemas)
35
+ export function getSchemas({ oas, contentType, includes = ['schemas', 'requestBodies', 'responses'], collisionDetection }: GetSchemasProps): GetSchemasResult {
36
+ return oas.getSchemas({
37
+ contentType,
38
+ includes,
39
+ collisionDetection,
40
+ })
119
41
  }