@kubb/oas 4.12.10 → 4.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/oas",
3
- "version": "4.12.10",
3
+ "version": "4.12.11",
4
4
  "description": "OpenAPI Specification (OAS) utilities and helpers for Kubb, providing parsing, normalization, and manipulation of OpenAPI/Swagger schemas.",
5
5
  "keywords": [
6
6
  "openapi",
@@ -26,21 +26,13 @@
26
26
  "require": "./dist/index.cjs",
27
27
  "import": "./dist/index.js"
28
28
  },
29
- "./infer": {
30
- "require": "./dist/infer.cjs",
31
- "import": "./dist/infer.js"
32
- },
33
29
  "./package.json": "./package.json"
34
30
  },
35
31
  "main": "./dist/index.cjs",
36
32
  "module": "./dist/index.js",
37
33
  "types": "./dist/index.d.cts",
38
34
  "typesVersions": {
39
- "*": {
40
- "infer": [
41
- "./dist/infer.d.ts"
42
- ]
43
- }
35
+ "*": {}
44
36
  },
45
37
  "files": [
46
38
  "src",
@@ -57,7 +49,6 @@
57
49
  ],
58
50
  "dependencies": {
59
51
  "@redocly/openapi-core": "^2.14.0",
60
- "hotscript": "^1.0.13",
61
52
  "json-schema-to-ts": "^3.1.1",
62
53
  "jsonpointer": "^5.0.1",
63
54
  "oas": "^28.7.0",
@@ -65,8 +56,7 @@
65
56
  "openapi-types": "^12.1.3",
66
57
  "remeda": "^2.32.0",
67
58
  "swagger2openapi": "^7.0.8",
68
- "ts-toolbelt": "^9.6.0",
69
- "@kubb/core": "4.12.10"
59
+ "@kubb/core": "4.12.11"
70
60
  },
71
61
  "devDependencies": {
72
62
  "@stoplight/yaml": "^4.3.0",
package/src/Oas.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import jsonpointer from 'jsonpointer'
2
2
  import BaseOas from 'oas'
3
- import type { Operation } from 'oas/operation'
4
- import type { MediaTypeObject, OASDocument, ResponseObject, SchemaObject, User } from 'oas/types'
3
+
5
4
  import { matchesMimeType } from 'oas/utils'
6
- import OASNormalize from 'oas-normalize'
7
- import type { OpenAPIV3 } from 'openapi-types'
8
- import type { OasTypes } from './index.ts'
9
- import type { contentType } from './types.ts'
5
+ import type { contentType, DiscriminatorObject, Document, MediaTypeObject, Operation, ReferenceObject, ResponseObject, SchemaObject } from './types.ts'
10
6
  import { isDiscriminator, isReference, STRUCTURAL_KEYS } from './utils.ts'
11
7
 
12
8
  type Options = {
@@ -14,20 +10,16 @@ type Options = {
14
10
  discriminator?: 'strict' | 'inherit'
15
11
  }
16
12
 
17
- export class Oas<const TOAS = unknown> extends BaseOas {
13
+ export class Oas extends BaseOas {
18
14
  #options: Options = {
19
15
  discriminator: 'strict',
20
16
  }
21
- document: TOAS = undefined as unknown as TOAS
22
-
23
- constructor({ oas, user }: { oas: TOAS | OASDocument | string; user?: User }) {
24
- if (typeof oas === 'string') {
25
- oas = JSON.parse(oas)
26
- }
17
+ document: Document
27
18
 
28
- super(oas as OASDocument, user)
19
+ constructor(document: Document) {
20
+ super(document, undefined)
29
21
 
30
- this.document = oas as TOAS
22
+ this.document = document
31
23
  }
32
24
 
33
25
  setOptions(options: Options) {
@@ -45,11 +37,11 @@ export class Oas<const TOAS = unknown> extends BaseOas {
45
37
  return this.#options
46
38
  }
47
39
 
48
- get($ref: string) {
40
+ get<T = unknown>($ref: string): T | null {
49
41
  const origRef = $ref
50
42
  $ref = $ref.trim()
51
43
  if ($ref === '') {
52
- return false
44
+ return null
53
45
  }
54
46
  if ($ref.startsWith('#')) {
55
47
  $ref = globalThis.decodeURIComponent($ref.substring(1))
@@ -80,13 +72,17 @@ export class Oas<const TOAS = unknown> extends BaseOas {
80
72
  }
81
73
  }
82
74
 
83
- #setDiscriminator(schema: OasTypes.SchemaObject & { discriminator: OpenAPIV3.DiscriminatorObject }): void {
75
+ #setDiscriminator(schema: SchemaObject & { discriminator: DiscriminatorObject }): void {
84
76
  const { mapping = {}, propertyName } = schema.discriminator
85
77
 
86
78
  if (this.#options.discriminator === 'inherit') {
87
79
  Object.entries(mapping).forEach(([mappingKey, mappingValue]) => {
88
80
  if (mappingValue) {
89
- const childSchema = this.get(mappingValue)
81
+ const childSchema = this.get<any>(mappingValue)
82
+ if (!childSchema) {
83
+ return
84
+ }
85
+
90
86
  if (!childSchema.properties) {
91
87
  childSchema.properties = {}
92
88
  }
@@ -95,11 +91,12 @@ export class Oas<const TOAS = unknown> extends BaseOas {
95
91
 
96
92
  if (childSchema.properties) {
97
93
  childSchema.properties[propertyName] = {
98
- ...(childSchema.properties ? childSchema.properties[propertyName] : {}),
94
+ ...((childSchema.properties ? childSchema.properties[propertyName] : {}) as SchemaObject),
99
95
  enum: [...(property?.enum?.filter((value) => value !== mappingKey) ?? []), mappingKey],
100
96
  }
101
97
 
102
- childSchema.required = [...new Set([...(childSchema.required ?? []), propertyName])]
98
+ childSchema.required =
99
+ typeof childSchema.required === 'boolean' ? childSchema.required : [...new Set([...(childSchema.required ?? []), propertyName])]
103
100
 
104
101
  this.set(mappingValue, childSchema)
105
102
  }
@@ -108,50 +105,100 @@ export class Oas<const TOAS = unknown> extends BaseOas {
108
105
  }
109
106
  }
110
107
 
111
- getDiscriminator(schema: OasTypes.SchemaObject): OpenAPIV3.DiscriminatorObject | undefined {
112
- if (!isDiscriminator(schema)) {
113
- return undefined
108
+ getDiscriminator(schema: SchemaObject | null): DiscriminatorObject | null {
109
+ if (!isDiscriminator(schema) || !schema) {
110
+ return null
114
111
  }
115
112
 
116
113
  const { mapping = {}, propertyName } = schema.discriminator
117
114
 
118
- // loop over oneOf and add default mapping when none is defined
119
- if (schema.oneOf) {
120
- schema.oneOf.forEach((schema) => {
121
- if (isReference(schema)) {
122
- const key = this.getKey(schema.$ref)
123
- const refSchema: OpenAPIV3.SchemaObject = this.get(schema.$ref)
124
- // special case where enum in the schema is set without mapping being defined, see https://github.com/kubb-labs/kubb/issues/1669
125
- const propertySchema = refSchema.properties?.[propertyName] as OpenAPIV3.SchemaObject
126
- const canAdd = key && !Object.values(mapping).includes(schema.$ref)
127
-
128
- if (canAdd && propertySchema?.enum?.length === 1) {
129
- mapping[propertySchema.enum[0]] = schema.$ref
130
- } else if (canAdd) {
131
- mapping[key] = schema.$ref
132
- }
115
+ /**
116
+ * Helper to extract discriminator value from a schema.
117
+ * Checks in order:
118
+ * 1. Extension property matching propertyName (e.g., x-linode-ref-name)
119
+ * 2. Property with const value
120
+ * 3. Property with single enum value
121
+ * 4. Title as fallback
122
+ */
123
+ const getDiscriminatorValue = (schema: SchemaObject | null): string | null => {
124
+ if (!schema) {
125
+ return null
126
+ }
127
+
128
+ // Check extension properties first (e.g., x-linode-ref-name)
129
+ // Only check if propertyName starts with 'x-' to avoid conflicts with standard properties
130
+ if (propertyName.startsWith('x-')) {
131
+ const extensionValue = (schema as Record<string, unknown>)[propertyName]
132
+ if (extensionValue && typeof extensionValue === 'string') {
133
+ return extensionValue
133
134
  }
134
- })
135
+ }
136
+
137
+ // Check if property has const value
138
+ const propertySchema = schema.properties?.[propertyName] as SchemaObject
139
+ if (propertySchema && 'const' in propertySchema && propertySchema.const !== undefined) {
140
+ return String(propertySchema.const)
141
+ }
142
+
143
+ // Check if property has single enum value
144
+ if (propertySchema && propertySchema.enum?.length === 1) {
145
+ return String(propertySchema.enum[0])
146
+ }
147
+
148
+ // Fallback to title if available
149
+ return schema.title || null
135
150
  }
136
151
 
137
- if (schema.anyOf) {
138
- schema.anyOf.forEach((schema) => {
139
- if (isReference(schema)) {
140
- const key = this.getKey(schema.$ref)
141
- const refSchema: OpenAPIV3.SchemaObject = this.get(schema.$ref)
142
- // special case where enum in the schema is set without mapping being defined, see https://github.com/kubb-labs/kubb/issues/1669
143
- const propertySchema = refSchema.properties?.[propertyName] as OpenAPIV3.SchemaObject
144
- const canAdd = key && !Object.values(mapping).includes(schema.$ref)
145
-
146
- if (canAdd && propertySchema?.enum?.length === 1) {
147
- mapping[propertySchema.enum[0]] = schema.$ref
148
- } else if (canAdd) {
149
- mapping[key] = schema.$ref
152
+ /**
153
+ * Process oneOf/anyOf items to build mapping.
154
+ * Handles both $ref and inline schemas.
155
+ */
156
+ const processSchemas = (schemas: Array<SchemaObject>, existingMapping: Record<string, string>) => {
157
+ schemas.forEach((schemaItem, index) => {
158
+ if (isReference(schemaItem)) {
159
+ // Handle $ref case
160
+ const key = this.getKey(schemaItem.$ref)
161
+
162
+ try {
163
+ const refSchema = this.get<SchemaObject>(schemaItem.$ref)
164
+ const discriminatorValue = getDiscriminatorValue(refSchema)
165
+ const canAdd = key && !Object.values(existingMapping).includes(schemaItem.$ref)
166
+
167
+ if (canAdd && discriminatorValue) {
168
+ existingMapping[discriminatorValue] = schemaItem.$ref
169
+ } else if (canAdd) {
170
+ existingMapping[key] = schemaItem.$ref
171
+ }
172
+ } catch (_error) {
173
+ // If we can't resolve the reference, skip it and use the key as fallback
174
+ if (key && !Object.values(existingMapping).includes(schemaItem.$ref)) {
175
+ existingMapping[key] = schemaItem.$ref
176
+ }
177
+ }
178
+ } else {
179
+ // Handle inline schema case
180
+ const inlineSchema = schemaItem as SchemaObject
181
+ const discriminatorValue = getDiscriminatorValue(inlineSchema)
182
+
183
+ if (discriminatorValue) {
184
+ // Create a synthetic ref for inline schemas using index
185
+ // The value points to the inline schema itself via a special marker
186
+ existingMapping[discriminatorValue] = `#kubb-inline-${index}`
150
187
  }
151
188
  }
152
189
  })
153
190
  }
154
191
 
192
+ // Process oneOf schemas
193
+ if (schema.oneOf) {
194
+ processSchemas(schema.oneOf as Array<SchemaObject>, mapping)
195
+ }
196
+
197
+ // Process anyOf schemas
198
+ if (schema.anyOf) {
199
+ processSchemas(schema.anyOf as Array<SchemaObject>, mapping)
200
+ }
201
+
155
202
  return {
156
203
  ...schema.discriminator,
157
204
  mapping,
@@ -159,7 +206,7 @@ export class Oas<const TOAS = unknown> extends BaseOas {
159
206
  }
160
207
 
161
208
  // TODO add better typing
162
- dereferenceWithRef(schema?: unknown) {
209
+ dereferenceWithRef<T = unknown>(schema?: T): T {
163
210
  if (isReference(schema)) {
164
211
  return {
165
212
  ...schema,
@@ -168,7 +215,7 @@ export class Oas<const TOAS = unknown> extends BaseOas {
168
215
  }
169
216
  }
170
217
 
171
- return schema
218
+ return schema as T
172
219
  }
173
220
 
174
221
  #applyDiscriminatorInheritance() {
@@ -195,17 +242,17 @@ export class Oas<const TOAS = unknown> extends BaseOas {
195
242
  }
196
243
  }
197
244
 
198
- const visit = (schema?: SchemaObject | OpenAPIV3.ReferenceObject | null) => {
245
+ const visit = (schema?: SchemaObject | ReferenceObject | null) => {
199
246
  if (!schema || typeof schema !== 'object') {
200
247
  return
201
248
  }
202
249
 
203
250
  if (isReference(schema)) {
204
- visit(this.get(schema.$ref) as OpenAPIV3.SchemaObject)
251
+ visit(this.get(schema.$ref) as SchemaObject)
205
252
  return
206
253
  }
207
254
 
208
- const schemaObject = schema as OpenAPIV3.SchemaObject
255
+ const schemaObject = schema as SchemaObject
209
256
 
210
257
  if (visited.has(schemaObject as object)) {
211
258
  return
@@ -314,7 +361,7 @@ export class Oas<const TOAS = unknown> extends BaseOas {
314
361
  const $ref = isReference(schema) ? schema.$ref : undefined
315
362
 
316
363
  if (schema && $ref) {
317
- operation.schema.responses![key] = this.get($ref)
364
+ operation.schema.responses![key] = this.get<any>($ref)
318
365
  }
319
366
  })
320
367
  }
@@ -377,8 +424,11 @@ export class Oas<const TOAS = unknown> extends BaseOas {
377
424
 
378
425
  return params.reduce(
379
426
  (schema, pathParameters) => {
380
- const property = pathParameters.content?.[contentType]?.schema ?? (pathParameters.schema as SchemaObject)
381
- const required = [...(schema.required || ([] as any)), pathParameters.required ? pathParameters.name : undefined].filter(Boolean)
427
+ const property = (pathParameters.content?.[contentType]?.schema ?? (pathParameters.schema as SchemaObject)) as SchemaObject | null
428
+ const required =
429
+ typeof schema.required === 'boolean'
430
+ ? schema.required
431
+ : [...(schema.required || []), pathParameters.required ? pathParameters.name : undefined].filter(Boolean)
382
432
 
383
433
  // Handle explode=true with style=form for object with additionalProperties
384
434
  // According to OpenAPI spec, when explode is true, object properties are flattened
@@ -407,7 +457,7 @@ export class Oas<const TOAS = unknown> extends BaseOas {
407
457
  deprecated: schema.deprecated,
408
458
  example: property.example || schema.example,
409
459
  additionalProperties: property.additionalProperties,
410
- }
460
+ } as SchemaObject
411
461
  }
412
462
 
413
463
  return {
@@ -423,13 +473,14 @@ export class Oas<const TOAS = unknown> extends BaseOas {
423
473
  ...property,
424
474
  },
425
475
  },
426
- }
476
+ } as SchemaObject
427
477
  },
428
478
  { type: 'object', required: [], properties: {} } as SchemaObject,
429
479
  )
430
480
  }
431
481
 
432
482
  async valdiate() {
483
+ const OASNormalize = await import('oas-normalize').then((m) => m.default)
433
484
  const oasNormalize = new OASNormalize(this.api, {
434
485
  enablePaths: true,
435
486
  colorizeErrors: true,
@@ -446,9 +497,9 @@ export class Oas<const TOAS = unknown> extends BaseOas {
446
497
  })
447
498
  }
448
499
 
449
- flattenSchema(schema?: SchemaObject): SchemaObject | undefined {
500
+ flattenSchema(schema: SchemaObject | null): SchemaObject | null {
450
501
  if (!schema?.allOf || schema.allOf.length === 0) {
451
- return schema
502
+ return schema || null
452
503
  }
453
504
 
454
505
  // Never touch ref-based or structural composition
@@ -468,13 +519,12 @@ export class Oas<const TOAS = unknown> extends BaseOas {
468
519
 
469
520
  for (const fragment of schema.allOf as SchemaObject[]) {
470
521
  for (const [key, value] of Object.entries(fragment)) {
471
- if ((merged as any)[key] === undefined) {
472
- ;(merged as any)[key] = value
522
+ if (merged[key as keyof typeof merged] === undefined) {
523
+ merged[key as keyof typeof merged] = value
473
524
  }
474
525
  }
475
526
  }
476
527
 
477
- // biome-ignore lint/suspicious/noAssignInExpressions: should not trigger this
478
528
  return merged
479
529
  }
480
530
  }
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export { findSchemaDefinition, matchesMimeType } from 'oas/utils'
2
- export type { Infer, Model, RequestParams, Response } from './infer/index.ts'
3
2
  export { Oas } from './Oas.ts'
4
3
  export * from './types.ts'
5
4
  export {
package/src/types.ts CHANGED
@@ -1,14 +1,23 @@
1
- import type * as OasTypes from 'oas/types'
2
-
3
1
  // external packages
4
- export type { Operation } from 'oas/operation'
2
+ import type { Operation as OASOperation } from 'oas/operation'
3
+ import type { OpenAPIV3 } from 'openapi-types'
4
+
5
5
  export type * as OasTypes from 'oas/types'
6
- export type { HttpMethods as HttpMethod } from 'oas/types'
6
+
7
+ import type {
8
+ DiscriminatorObject as OASDiscriminatorObject,
9
+ OASDocument,
10
+ HttpMethods as OASHttpMethods,
11
+ MediaTypeObject as OASMediaTypeObject,
12
+ ResponseObject as OASResponseObject,
13
+ SchemaObject as OASSchemaObject,
14
+ } from 'oas/types'
15
+
7
16
  export type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
8
17
 
9
18
  export type contentType = 'application/json' | (string & {})
10
19
 
11
- export type SchemaObject = OasTypes.SchemaObject & {
20
+ export type SchemaObject = OASSchemaObject & {
12
21
  'x-nullable'?: boolean
13
22
  $ref?: string
14
23
  }
@@ -22,4 +31,18 @@ export const HttpMethods = {
22
31
  HEAD: 'head',
23
32
  OPTIONS: 'options',
24
33
  TRACE: 'trace',
25
- } satisfies Record<Uppercase<OasTypes.HttpMethods>, OasTypes.HttpMethods>
34
+ } satisfies Record<Uppercase<OASHttpMethods>, OASHttpMethods>
35
+
36
+ export type HttpMethod = OASHttpMethods
37
+
38
+ export type Document = OASDocument
39
+
40
+ export type Operation = OASOperation
41
+
42
+ export type DiscriminatorObject = OASDiscriminatorObject
43
+
44
+ export type ReferenceObject = OpenAPIV3.ReferenceObject
45
+
46
+ export type ResponseObject = OASResponseObject
47
+
48
+ export type MediaTypeObject = OASMediaTypeObject
package/src/utils.ts CHANGED
@@ -2,14 +2,14 @@ import path from 'node:path'
2
2
  import type { Config } from '@kubb/core'
3
3
  import { URLPath } from '@kubb/core/utils'
4
4
  import yaml from '@stoplight/yaml'
5
- import type * as OasTypes from 'oas/types'
6
- import type { OASDocument, ParameterObject, SchemaObject } from 'oas/types'
5
+ import type { ParameterObject, SchemaObject } from 'oas/types'
7
6
  import { isRef, isSchema } from 'oas/types'
8
7
  import OASNormalize from 'oas-normalize'
9
- import type { OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
8
+ import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
10
9
  import { isPlainObject, mergeDeep } from 'remeda'
11
10
  import swagger2openapi from 'swagger2openapi'
12
11
  import { Oas } from './Oas.ts'
12
+ import type { Document } from './types.ts'
13
13
 
14
14
  export const STRUCTURAL_KEYS = new Set(['properties', 'items', 'additionalProperties', 'oneOf', 'anyOf', 'allOf', 'not'])
15
15
 
@@ -81,7 +81,7 @@ export function isOptional(schema?: SchemaObject): boolean {
81
81
  }
82
82
 
83
83
  export async function parse(
84
- pathOrApi: string | OASDocument,
84
+ pathOrApi: string | Document,
85
85
  { oasClass = Oas, canBundle = true, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
86
86
  ): Promise<Oas> {
87
87
  const { loadConfig, bundle } = await import('@redocly/openapi-core')
@@ -98,20 +98,20 @@ export async function parse(
98
98
  enablePaths,
99
99
  colorizeErrors: true,
100
100
  })
101
- const document = (await oasNormalize.load()) as OpenAPI.Document
101
+ const document = (await oasNormalize.load()) as Document
102
102
 
103
103
  if (isOpenApiV2Document(document)) {
104
104
  const { openapi } = await swagger2openapi.convertObj(document, {
105
105
  anchors: true,
106
106
  })
107
107
 
108
- return new oasClass({ oas: openapi as OASDocument })
108
+ return new oasClass(openapi as Document)
109
109
  }
110
110
 
111
- return new oasClass({ oas: document })
111
+ return new oasClass(document)
112
112
  }
113
113
 
114
- export async function merge(pathOrApi: Array<string | OASDocument>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
114
+ export async function merge(pathOrApi: Array<string | Document>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
115
115
  const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false })))
116
116
 
117
117
  if (instances.length === 0) {
@@ -120,7 +120,7 @@ export async function merge(pathOrApi: Array<string | OASDocument>, { oasClass =
120
120
 
121
121
  const merged = instances.reduce(
122
122
  (acc, current) => {
123
- return mergeDeep(acc, current.document as OASDocument)
123
+ return mergeDeep(acc, current.document as Document)
124
124
  },
125
125
  {
126
126
  openapi: '3.0.0',
@@ -141,7 +141,7 @@ export async function merge(pathOrApi: Array<string | OASDocument>, { oasClass =
141
141
  export function parseFromConfig(config: Config, oasClass: typeof Oas = Oas): Promise<Oas> {
142
142
  if ('data' in config.input) {
143
143
  if (typeof config.input.data === 'object') {
144
- const api: OasTypes.OASDocument = structuredClone(config.input.data) as OasTypes.OASDocument
144
+ const api: Document = structuredClone(config.input.data) as Document
145
145
  return parse(api, { oasClass })
146
146
  }
147
147