@kubb/adapter-oas 5.0.0-alpha.2 → 5.0.0-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/adapter-oas",
3
- "version": "5.0.0-alpha.2",
3
+ "version": "5.0.0-alpha.4",
4
4
  "description": "OpenAPI / Swagger adapter for Kubb — converts OAS input into a @kubb/ast RootNode.",
5
5
  "keywords": [
6
6
  "openapi",
@@ -46,8 +46,8 @@
46
46
  "openapi-types": "^12.1.3",
47
47
  "remeda": "^2.33.6",
48
48
  "swagger2openapi": "^7.0.8",
49
- "@kubb/ast": "5.0.0-alpha.2",
50
- "@kubb/core": "5.0.0-alpha.2"
49
+ "@kubb/ast": "5.0.0-alpha.4",
50
+ "@kubb/core": "5.0.0-alpha.4"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/swagger2openapi": "^7.0.4",
package/src/adapter.ts CHANGED
@@ -6,6 +6,7 @@ import { resolveServerUrl } from './oas/resolveServerUrl.ts'
6
6
  import { parseFromConfig } from './oas/utils.ts'
7
7
  import { createOasParser } from './parser.ts'
8
8
  import type { OasAdapter } from './types.ts'
9
+ import { getImports } from './utils.ts'
9
10
 
10
11
  export const adapterOasName = 'oas' satisfies OasAdapter['name']
11
12
 
@@ -42,6 +43,10 @@ export const adapterOas = defineAdapter<OasAdapter>((options) => {
42
43
  emptySchemaType = unknownType,
43
44
  } = options
44
45
 
46
+ // Mutable Map shared between `options` and each `parse()` call.
47
+ // Populated (and replaced) on every parse so consumers always see the latest state.
48
+ const nameMapping = new Map<string, string>()
49
+
45
50
  return {
46
51
  name: adapterOasName,
47
52
  options: {
@@ -56,6 +61,10 @@ export const adapterOas = defineAdapter<OasAdapter>((options) => {
56
61
  integerType,
57
62
  unknownType,
58
63
  emptySchemaType,
64
+ nameMapping,
65
+ },
66
+ getImports(node, resolve) {
67
+ return getImports({ node, nameMapping, resolve })
59
68
  },
60
69
  async parse(source) {
61
70
  const fakeConfig = sourceToFakeConfig(source)
@@ -75,6 +84,13 @@ export const adapterOas = defineAdapter<OasAdapter>((options) => {
75
84
  const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : undefined
76
85
 
77
86
  const parser = createOasParser(oas, { contentType, collisionDetection })
87
+
88
+ // Sync the adapter's shared nameMapping with the one computed by the parser.
89
+ nameMapping.clear()
90
+ for (const [key, value] of parser.nameMapping) {
91
+ nameMapping.set(key, value)
92
+ }
93
+
78
94
  const root = parser.parse({ dateType, integerType, unknownType, emptySchemaType })
79
95
 
80
96
  return createRoot({
package/src/oas/types.ts CHANGED
@@ -30,6 +30,25 @@ export type SchemaObject = OASSchemaObject & {
30
30
  */
31
31
  contentMediaType?: string
32
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>
33
52
  }
34
53
 
35
54
  /**
package/src/parser.ts CHANGED
@@ -1,16 +1,5 @@
1
- import { pascalCase } from '@internals/utils'
2
- import {
3
- collect,
4
- createOperation,
5
- createParameter,
6
- createProperty,
7
- createResponse,
8
- createRoot,
9
- createSchema,
10
- narrowSchema,
11
- schemaTypes,
12
- transform,
13
- } from '@kubb/ast'
1
+ import { pascalCase, URLPath } from '@internals/utils'
2
+ import { createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, narrowSchema, schemaTypes, transform } from '@kubb/ast'
14
3
  import type {
15
4
  ArraySchemaNode,
16
5
  DateSchemaNode,
@@ -38,11 +27,11 @@ import type {
38
27
  TimeSchemaNode,
39
28
  UnionSchemaNode,
40
29
  } from '@kubb/ast/types'
41
- import type { KubbFile } from '@kubb/fabric-core/types'
42
30
  import { enumExtensionKeys, formatMap, knownMediaTypes } from './constants.ts'
43
31
  import type { Oas } from './oas/Oas.ts'
44
- import type { contentType, Operation, SchemaObject } from './oas/types.ts'
32
+ import type { contentType, Operation, ReferenceObject, SchemaObject } from './oas/types.ts'
45
33
  import { flattenSchema, isDiscriminator, isNullable, isReference } from './oas/utils.ts'
34
+ import { applyDiscriminatorEnum, extractRefName, mergeAdjacentAnonymousObjects, simplifyUnionMembers } from './utils.ts'
46
35
 
47
36
  /**
48
37
  * Distributive `Omit` — correctly distributes over union types so that
@@ -178,15 +167,6 @@ function formatToSchemaType(format: string): SchemaType | undefined {
178
167
  return formatMap[format as keyof typeof formatMap]
179
168
  }
180
169
 
181
- /**
182
- * Extracts the final path segment of a JSON Pointer `$ref` string.
183
- * For `#/components/schemas/Order` this returns `'Order'`.
184
- * Falls back to the full ref string when no slash is present.
185
- */
186
- function extractRefName($ref: string): string {
187
- return $ref.split('/').at(-1) ?? $ref
188
- }
189
-
190
170
  /**
191
171
  * Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
192
172
  * Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
@@ -245,45 +225,15 @@ export type OasParser = {
245
225
  * the transformed name to use (e.g. with a plugin `transformers.name` applied).
246
226
  */
247
227
  resolveRefs: (node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined) => SchemaNode
228
+
248
229
  /**
249
- * Extracts `KubbFile.Import` entries from a `SchemaNode` tree by collecting
250
- * all importable `ref` nodes. A `$ref` is considered importable when it resolves
251
- * to a known component in the OAS spec (`oas.get($ref)` is truthy).
252
- *
253
- * The `resolve` callback is called with the schema name (last segment of the
254
- * `$ref`, collision-corrected via the OAS name mapping) and must return the
255
- * `{ name, path }` pair for the generated import, or `undefined` to skip it.
230
+ * Map from original `$ref` paths to their collision-resolved schema names.
231
+ * e.g. `'#/components/schemas/Order'` `'OrderSchema'`
256
232
  *
257
- * @example
258
- * ```ts
259
- * const imports = parser.getImports(schemaNode, (schemaName) => ({
260
- * name: schemaManager.getName(schemaName, { type: 'type' }),
261
- * path: schemaManager.getFile(schemaName).path,
262
- * }))
263
- * ```
233
+ * Pass this to the standalone `getImports()` to resolve imports without holding
234
+ * a reference to the full parser or OAS instance.
264
235
  */
265
- getImports: (node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined) => Array<KubbFile.Import>
266
- }
267
-
268
- /**
269
- * When a discriminator is present, replaces the discriminator property's schema with
270
- * an enum of the mapping keys so downstream code emits a precise literal-union type.
271
- * Returns the original schema unchanged when there is no discriminator or no mapping.
272
- */
273
- function applyDiscriminatorEnum(schema: SchemaObject): SchemaObject {
274
- if (!isDiscriminator(schema)) return schema
275
- const propName = schema.discriminator.propertyName
276
- if (!schema.properties?.[propName]) return schema
277
- return {
278
- ...schema,
279
- properties: {
280
- ...schema.properties,
281
- [propName]: {
282
- ...(schema.properties[propName] as SchemaObject),
283
- enum: schema.discriminator.mapping ? Object.keys(schema.discriminator.mapping) : undefined,
284
- },
285
- },
286
- } as SchemaObject
236
+ nameMapping: Map<string, string>
287
237
  }
288
238
 
289
239
  /**
@@ -383,18 +333,17 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
383
333
  * reflected in generated JSDoc and type modifiers.
384
334
  */
385
335
  function convertRef({ schema, nullable, defaultValue }: SchemaContext): SchemaNode {
386
- const schemaObject = schema as unknown as SchemaObject & { $ref: string }
387
336
  return createSchema({
388
337
  type: 'ref',
389
- name: extractRefName(schemaObject.$ref),
390
- ref: schemaObject.$ref,
338
+ name: extractRefName(schema.$ref!),
339
+ ref: schema.$ref,
391
340
  nullable,
392
- description: schemaObject.description,
393
- deprecated: schemaObject.deprecated,
394
- readOnly: schemaObject.readOnly,
395
- writeOnly: schemaObject.writeOnly,
396
- pattern: schemaObject.type === 'string' ? schemaObject.pattern : undefined,
397
- example: schemaObject.example,
341
+ description: schema.description,
342
+ deprecated: schema.deprecated,
343
+ readOnly: schema.readOnly,
344
+ writeOnly: schema.writeOnly,
345
+ pattern: schema.type === 'string' ? schema.pattern : undefined,
346
+ example: schema.example,
398
347
  default: defaultValue,
399
348
  })
400
349
  }
@@ -421,8 +370,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
421
370
  !(Array.isArray(schema.required) && schema.required.length) &&
422
371
  schema.additionalProperties === undefined
423
372
  ) {
424
- const [memberSchema] = schema.allOf as SchemaObject[]
425
- const memberNode = convertSchema({ schema: memberSchema! }, options)
373
+ const [memberSchema] = schema.allOf as Array<SchemaObject | ReferenceObject>
374
+ const memberNode = convertSchema({ schema: memberSchema! as SchemaObject }, options)
426
375
  const { kind: _kind, ...memberNodeProps } = memberNode
427
376
  const mergedNullable = nullable || memberNode.nullable || undefined
428
377
  const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
@@ -444,22 +393,24 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
444
393
 
445
394
  // When a child schema extends a discriminator parent via allOf and the parent's oneOf/anyOf
446
395
  // references that child back, skip that allOf item to prevent a circular type reference.
447
- const allOfMembers: SchemaNode[] = (schema.allOf as SchemaObject[])
396
+ const allOfMembers: Array<SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
448
397
  .filter((item) => {
449
398
  if (!isReference(item) || !name) return true
450
- const deref = oas.get<SchemaObject>((item as { $ref: string }).$ref)
399
+ const deref = oas.get<SchemaObject>(item.$ref)
451
400
  if (!deref || !isDiscriminator(deref)) return true
452
- const parentUnion = (deref as SchemaObject).oneOf ?? (deref as SchemaObject).anyOf
401
+ const parentUnion = deref.oneOf ?? deref.anyOf
453
402
  if (!parentUnion) return true
454
403
  const childRef = `#/components/schemas/${name}`
455
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && (oneOfItem as { $ref: string }).$ref === childRef)
456
- const inMapping = Object.values((deref as SchemaObject & { discriminator: { mapping?: Record<string, string> } }).discriminator.mapping ?? {}).some(
457
- (v) => v === childRef,
458
- )
404
+ const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
405
+ const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
459
406
  return !inOneOf && !inMapping
460
407
  })
461
408
  .map((s) => convertSchema({ schema: s as SchemaObject }, options))
462
409
 
410
+ // Track where allOf-derived members end so only the synthetic members added below
411
+ // (injected required-key objects + outer-properties object) are candidates for merging.
412
+ const syntheticStart = allOfMembers.length
413
+
463
414
  // When `required` lists keys not present in the outer `properties`, resolve them from
464
415
  // the allOf member schemas and inject them as extra intersection members.
465
416
  if (Array.isArray(schema.required) && schema.required.length) {
@@ -467,10 +418,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
467
418
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key))
468
419
 
469
420
  if (missingRequired.length) {
470
- const resolvedMembers = (schema.allOf as SchemaObject[]).flatMap((item) => {
471
- if (!isReference(item)) return [item]
421
+ const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
422
+ if (!isReference(item)) return [item as SchemaObject]
472
423
  const deref = oas.get<SchemaObject>(item.$ref)
473
- return deref && !isReference(deref) ? [deref as SchemaObject] : []
424
+ return deref && !isReference(deref) ? [deref] : []
474
425
  })
475
426
 
476
427
  for (const key of missingRequired) {
@@ -485,13 +436,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
485
436
  }
486
437
 
487
438
  if (schema.properties) {
488
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema as SchemaObject & { allOf?: unknown[] }
489
- allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf as SchemaObject }, options))
439
+ const { allOf: _allOf, ...schemaWithoutAllOf } = schema
440
+ allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, options))
490
441
  }
491
442
 
443
+ // Merge consecutive anonymous object members within the synthetic portion — see `mergeAdjacentAnonymousObjects`.
492
444
  return createSchema({
493
445
  type: 'intersection',
494
- members: allOfMembers,
446
+ members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
495
447
  ...buildSchemaBase(schema, name, nullable, defaultValue),
496
448
  })
497
449
  }
@@ -512,25 +464,39 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
512
464
  }
513
465
 
514
466
  if (schema.properties) {
515
- const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema as SchemaObject & { oneOf?: unknown[]; anyOf?: unknown[] }
516
- const propertiesNode = convertSchema({ schema: schemaWithoutUnion as SchemaObject }, options)
467
+ const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
468
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
469
+
470
+ // Strip discriminator so convertObject won't re-apply the full mapping enum.
471
+ const memberBaseSchema: SchemaObject = discriminator
472
+ ? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
473
+ : schemaWithoutUnion
517
474
 
518
475
  return createSchema({
519
476
  type: 'union',
520
477
  ...unionBase,
521
- members: unionMembers.map((s) =>
522
- createSchema({
478
+ members: unionMembers.map((s) => {
479
+ const ref = isReference(s) ? s.$ref : undefined
480
+ const discriminatorValue = discriminator?.mapping && ref ? Object.entries(discriminator.mapping).find(([, v]) => v === ref)?.[0] : undefined
481
+
482
+ let propertiesNode = convertSchema({ schema: memberBaseSchema, name }, options)
483
+
484
+ if (discriminatorValue && discriminator) {
485
+ propertiesNode = applyDiscriminatorEnum({ node: propertiesNode, propertyName: discriminator.propertyName, values: [discriminatorValue] })
486
+ }
487
+
488
+ return createSchema({
523
489
  type: 'intersection',
524
490
  members: [convertSchema({ schema: s as SchemaObject }, options), propertiesNode],
525
- }),
526
- ),
491
+ })
492
+ }),
527
493
  })
528
494
  }
529
495
 
530
496
  return createSchema({
531
497
  type: 'union',
532
498
  ...unionBase,
533
- members: unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options)),
499
+ members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options))),
534
500
  })
535
501
  }
536
502
 
@@ -619,10 +585,9 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
619
585
  function convertEnum({ schema, name, nullable, type, options }: SchemaContext): SchemaNode {
620
586
  // Malformed schema: `{ type: 'array', enum: [...] }` — normalize by moving the enum into items.
621
587
  if (type === 'array') {
622
- const rawSchema = schema as unknown as { items?: SchemaObject; enum?: unknown[] }
623
- const isItemsObject = typeof rawSchema.items === 'object' && !Array.isArray(rawSchema.items)
624
- const normalizedItems = { ...(isItemsObject ? rawSchema.items : {}), enum: schema.enum } as SchemaObject
625
- const { enum: _enum, ...schemaWithoutEnum } = schema as SchemaObject & { enum?: unknown[] }
588
+ const isItemsObject = typeof schema.items === 'object' && !Array.isArray(schema.items)
589
+ const normalizedItems: SchemaObject = { ...(isItemsObject ? (schema.items as SchemaObject) : {}), enum: schema.enum }
590
+ const { enum: _enum, ...schemaWithoutEnum } = schema
626
591
  return convertSchema({ schema: { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject, name }, options)
627
592
  }
628
593
 
@@ -716,21 +681,21 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
716
681
  * - not required + nullable → `nullish: true`
717
682
  */
718
683
  function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
719
- // When a discriminator is present, override the discriminator property's schema to use
720
- // an enum of the mapping keys for a precise literal-union type.
721
- const resolvedSchema = applyDiscriminatorEnum(schema)
722
-
723
- const properties: Array<PropertyNode> = resolvedSchema.properties
724
- ? Object.entries(resolvedSchema.properties).map(([propName, propSchema]) => {
725
- const required = Array.isArray(resolvedSchema.required) ? resolvedSchema.required.includes(propName) : !!resolvedSchema.required
684
+ const properties: Array<PropertyNode> = schema.properties
685
+ ? Object.entries(schema.properties).map(([propName, propSchema]) => {
686
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
726
687
  const resolvedPropSchema = propSchema as SchemaObject
727
688
  const propNullable = isNullable(resolvedPropSchema)
728
- const derivedPropName = name ? pascalCase([name, propName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : undefined
689
+ const basePropName = name ? pascalCase([name, propName].join(' ')) : undefined
690
+ const propNode = convertSchema({ schema: resolvedPropSchema, name: basePropName }, options)
691
+ const isEnumNode = !!narrowSchema(propNode, 'enum')
692
+ const derivedPropName = isEnumNode && name ? pascalCase([name, propName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : basePropName
693
+ const schemaNode = isEnumNode && derivedPropName !== basePropName ? { ...propNode, name: derivedPropName } : propNode
729
694
 
730
695
  return createProperty({
731
696
  name: propName,
732
697
  schema: {
733
- ...convertSchema({ schema: resolvedPropSchema, name: derivedPropName }, options),
698
+ ...schemaNode,
734
699
  nullable: propNullable || undefined,
735
700
  optional: !required && !propNullable ? true : undefined,
736
701
  nullish: !required && propNullable ? true : undefined,
@@ -740,7 +705,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
740
705
  })
741
706
  : []
742
707
 
743
- const additionalProperties = resolvedSchema.additionalProperties
708
+ const additionalProperties = schema.additionalProperties
744
709
  let additionalPropertiesNode: SchemaNode | true | undefined
745
710
  if (additionalProperties === true) {
746
711
  additionalPropertiesNode = true
@@ -752,21 +717,20 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
752
717
  additionalPropertiesNode = createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
753
718
  }
754
719
 
755
- const rawPatternProperties =
756
- 'patternProperties' in resolvedSchema ? (resolvedSchema as unknown as { patternProperties?: Record<string, SchemaObject> }).patternProperties : undefined
720
+ const rawPatternProperties = 'patternProperties' in schema ? schema.patternProperties : undefined
757
721
 
758
722
  const patternProperties = rawPatternProperties
759
723
  ? Object.fromEntries(
760
724
  Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
761
725
  pattern,
762
- (patternSchema as unknown) === true || Object.keys(patternSchema as object).length === 0
726
+ patternSchema === true || (typeof patternSchema === 'object' && Object.keys(patternSchema).length === 0)
763
727
  ? createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
764
728
  : convertSchema({ schema: patternSchema as SchemaObject }, options),
765
729
  ]),
766
730
  )
767
731
  : undefined
768
732
 
769
- return createSchema({
733
+ const objectNode: SchemaNode = createSchema({
770
734
  type: 'object',
771
735
  primitive: 'object',
772
736
  properties,
@@ -774,6 +738,17 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
774
738
  patternProperties,
775
739
  ...buildSchemaBase(schema, name, nullable, defaultValue),
776
740
  })
741
+
742
+ // When a discriminator is present, replace the discriminator property's schema
743
+ // with an enum of the mapping keys for a precise literal-union type.
744
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
745
+ const discPropName = schema.discriminator.propertyName
746
+ const values = Object.keys(schema.discriminator.mapping)
747
+ const enumName = name ? pascalCase([name, discPropName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : undefined
748
+ return applyDiscriminatorEnum({ node: objectNode, propertyName: discPropName, values, enumName })
749
+ }
750
+
751
+ return objectNode
777
752
  }
778
753
 
779
754
  /**
@@ -783,9 +758,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
783
758
  * after the prefix items is mapped to the rest parameter of the tuple.
784
759
  */
785
760
  function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
786
- const rawSchema = schema as unknown as { prefixItems: SchemaObject[]; items?: SchemaObject }
787
- const tupleItems = rawSchema.prefixItems.map((item) => convertSchema({ schema: item }, options))
788
- const rest = rawSchema.items ? convertSchema({ schema: rawSchema.items }, options) : undefined
761
+ const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, options))
762
+ const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) : undefined
789
763
 
790
764
  return createSchema({
791
765
  type: 'tuple',
@@ -805,12 +779,11 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
805
779
  * `enumSuffix` is forwarded so generators can emit a named enum declaration.
806
780
  */
807
781
  function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
808
- const rawSchema = schema as unknown as { items?: SchemaObject }
782
+ const rawItems = schema.items as SchemaObject | undefined
809
783
  // When the array items schema contains an inline enum, derive a name from the parent
810
784
  // array's name + enumSuffix so generators can emit a named enum declaration.
811
- const rawItems = rawSchema.items as SchemaObject | undefined
812
785
  const itemName = rawItems?.enum?.length && name ? pascalCase([name, mergedOptions.enumSuffix].join(' ')) : undefined
813
- const items = rawSchema.items ? [convertSchema({ schema: rawSchema.items, name: itemName }, options)] : []
786
+ const items = rawItems ? [convertSchema({ schema: rawItems, name: itemName }, options)] : []
814
787
 
815
788
  return createSchema({
816
789
  type: 'array',
@@ -898,8 +871,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
898
871
  const mergedOptions: Options = { ...DEFAULT_OPTIONS, ...options }
899
872
  // Flatten keyword-only allOf fragments (no $ref, no structural keys) into the parent
900
873
  // schema before parsing, so simple annotation patterns don't produce needless intersections.
901
- const flattenedSchema = flattenSchema(schema as unknown as Parameters<typeof flattenSchema>[0]) as SchemaObject | null
902
- if (flattenedSchema && flattenedSchema !== (schema as unknown)) {
874
+ const flattenedSchema = flattenSchema(schema)
875
+ if (flattenedSchema && flattenedSchema !== schema) {
903
876
  return convertSchema({ schema: flattenedSchema, name }, options)
904
877
  }
905
878
 
@@ -933,7 +906,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
933
906
  }
934
907
 
935
908
  // OAS 3.1: `contentMediaType: 'application/octet-stream'` on a string schema signals binary data.
936
- if (schema.type === 'string' && (schema as SchemaObject & { contentMediaType?: string }).contentMediaType === 'application/octet-stream') {
909
+ if (schema.type === 'string' && schema.contentMediaType === 'application/octet-stream') {
937
910
  return createSchema({ type: 'blob', primitive: 'string', ...buildSchemaBase(schema, name, nullable, defaultValue) })
938
911
  }
939
912
 
@@ -947,7 +920,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
947
920
  if (nonNullTypes.length > 1) {
948
921
  return createSchema({
949
922
  type: 'union',
950
- members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject }, options)),
923
+ members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject, name }, options)),
951
924
  ...buildSchemaBase(schema, name, arrayNullable, defaultValue),
952
925
  })
953
926
  }
@@ -984,16 +957,21 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
984
957
  * When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
985
958
  */
986
959
  function parseParameter(options: Options, param: Record<string, unknown>): ParameterNode {
987
- const schema =
988
- param['schema'] && !isReference(param['schema'] as object)
960
+ const required = (param['required'] as boolean | undefined) ?? false
961
+
962
+ const schema: SchemaNode =
963
+ param['schema'] && !isReference(param['schema'])
989
964
  ? convertSchema({ schema: param['schema'] as SchemaObject }, options)
990
965
  : createSchema({ type: resolveTypeOption(options.unknownType) })
991
966
 
992
967
  return createParameter({
993
968
  name: param['name'] as string,
994
969
  in: param['in'] as ParameterLocation,
995
- schema,
996
- required: (param['required'] as boolean | undefined) ?? false,
970
+ schema: {
971
+ ...schema,
972
+ optional: !required || !!schema.optional ? true : undefined,
973
+ },
974
+ required,
997
975
  })
998
976
  }
999
977
 
@@ -1037,7 +1015,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1037
1015
  return createOperation({
1038
1016
  operationId: operation.getOperationId(),
1039
1017
  method: operation.method.toUpperCase() as HttpMethod,
1040
- path: operation.path,
1018
+ path: new URLPath(operation.path).URL,
1041
1019
  tags: operation.getTags().map((tag) => tag.name),
1042
1020
  summary: operation.getSummary() || undefined,
1043
1021
  description: operation.getDescription() || undefined,
@@ -1101,36 +1079,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1101
1079
  }) as SchemaNode
1102
1080
  }
1103
1081
 
1104
- /**
1105
- * Collects all `KubbFile.Import` descriptors needed by a `SchemaNode` tree.
1106
- *
1107
- * Walks the tree looking for `ref` nodes, verifies each `$ref` is resolvable in the spec,
1108
- * applies collision-resolved names from `nameMapping`, and calls `resolve` to obtain the
1109
- * import path and name. Returns an empty array for refs that cannot be resolved.
1110
- */
1111
- function getImports(node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined): Array<KubbFile.Import> {
1112
- return collect<KubbFile.Import>(node, {
1113
- schema(schemaNode): KubbFile.Import | undefined {
1114
- if (schemaNode.type !== 'ref' || !schemaNode.ref) return
1115
- // Use the OAS instance to verify this $ref is importable (exists in the spec).
1116
- if (!oas.get(schemaNode.ref)) return
1117
-
1118
- const rawName = extractRefName(schemaNode.ref)
1119
-
1120
- // Apply collision-resolved name if available.
1121
- const schemaName = nameMapping.get(rawName) ?? rawName
1122
- const result = resolve(schemaName)
1123
- if (!result) return
1124
-
1125
- return { name: [result.name], path: result.path }
1126
- },
1127
- })
1128
- }
1129
-
1130
1082
  return {
1131
- parse: parse,
1083
+ parse,
1132
1084
  convertSchema,
1133
1085
  resolveRefs,
1134
- getImports,
1086
+ nameMapping,
1135
1087
  } as OasParser
1136
1088
  }
package/src/types.ts CHANGED
@@ -82,6 +82,12 @@ export type OasAdapterResolvedOptions = {
82
82
  integerType: NonNullable<OasAdapterOptions['integerType']>
83
83
  unknownType: NonNullable<OasAdapterOptions['unknownType']>
84
84
  emptySchemaType: NonNullable<OasAdapterOptions['emptySchemaType']>
85
+ /**
86
+ * Map from original `$ref` paths to their collision-resolved schema names.
87
+ * Populated by the adapter after each `parse()` call.
88
+ * e.g. `'#/components/schemas/Order'` → `'OrderSchema'`
89
+ */
90
+ nameMapping: Map<string, string>
85
91
  }
86
92
 
87
93
  export type OasAdapter = AdapterFactoryOptions<'oas', OasAdapterOptions, OasAdapterResolvedOptions>