@kubb/plugin-oas 0.0.0-canary-20240806053140 → 0.0.0-canary-20241106205013

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 (105) hide show
  1. package/README.md +14 -5
  2. package/dist/OperationGenerator-Bw6sj3Eb.d.cts +560 -0
  3. package/dist/OperationGenerator-Bw6sj3Eb.d.ts +560 -0
  4. package/dist/Schema-4FwWfdim.d.cts +22 -0
  5. package/dist/Schema-BlGtSgNB.d.ts +22 -0
  6. package/dist/chunk-2TGWPVZN.cjs +92 -0
  7. package/dist/chunk-2TGWPVZN.cjs.map +1 -0
  8. package/dist/chunk-A3ROGKLW.cjs +752 -0
  9. package/dist/chunk-A3ROGKLW.cjs.map +1 -0
  10. package/dist/chunk-ABOQ73FL.cjs +36 -0
  11. package/dist/chunk-ABOQ73FL.cjs.map +1 -0
  12. package/dist/chunk-BG77DP54.js +30 -0
  13. package/dist/chunk-BG77DP54.js.map +1 -0
  14. package/dist/chunk-GF26SDHQ.js +28 -0
  15. package/dist/chunk-GF26SDHQ.js.map +1 -0
  16. package/dist/chunk-JA75IPYU.js +744 -0
  17. package/dist/chunk-JA75IPYU.js.map +1 -0
  18. package/dist/chunk-PADR76WZ.cjs +4 -0
  19. package/dist/chunk-PADR76WZ.cjs.map +1 -0
  20. package/dist/chunk-QAFBZLJA.cjs +48 -0
  21. package/dist/chunk-QAFBZLJA.cjs.map +1 -0
  22. package/dist/chunk-R47XMJ32.js +3 -0
  23. package/dist/chunk-R47XMJ32.js.map +1 -0
  24. package/dist/chunk-TNWNNVQW.js +88 -0
  25. package/dist/chunk-TNWNNVQW.js.map +1 -0
  26. package/dist/chunk-XNCEFOE6.js +45 -0
  27. package/dist/chunk-XNCEFOE6.js.map +1 -0
  28. package/dist/chunk-ZWHQ54JM.cjs +32 -0
  29. package/dist/chunk-ZWHQ54JM.cjs.map +1 -0
  30. package/dist/components.cjs +20 -17
  31. package/dist/components.cjs.map +1 -1
  32. package/dist/components.d.cts +12 -9
  33. package/dist/components.d.ts +12 -9
  34. package/dist/components.js +3 -17
  35. package/dist/components.js.map +1 -1
  36. package/dist/generators.cjs +14 -0
  37. package/dist/generators.cjs.map +1 -0
  38. package/dist/generators.d.cts +9 -0
  39. package/dist/generators.d.ts +9 -0
  40. package/dist/generators.js +5 -0
  41. package/dist/generators.js.map +1 -0
  42. package/dist/hooks.cjs +90 -58
  43. package/dist/hooks.cjs.map +1 -1
  44. package/dist/hooks.d.cts +37 -10
  45. package/dist/hooks.d.ts +37 -10
  46. package/dist/hooks.js +79 -54
  47. package/dist/hooks.js.map +1 -1
  48. package/dist/index.cjs +277 -270
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +6 -78
  51. package/dist/index.d.ts +6 -78
  52. package/dist/index.js +235 -255
  53. package/dist/index.js.map +1 -1
  54. package/dist/utils.cjs +32 -87
  55. package/dist/utils.cjs.map +1 -1
  56. package/dist/utils.d.cts +8 -43
  57. package/dist/utils.d.ts +8 -43
  58. package/dist/utils.js +8 -86
  59. package/dist/utils.js.map +1 -1
  60. package/package.json +21 -15
  61. package/src/OperationGenerator.ts +91 -64
  62. package/src/SchemaGenerator.ts +102 -22
  63. package/src/SchemaMapper.ts +24 -5
  64. package/src/components/Oas.tsx +9 -3
  65. package/src/components/Operation.tsx +1 -1
  66. package/src/components/Schema.tsx +2 -102
  67. package/src/generator.tsx +129 -0
  68. package/src/generators/index.ts +1 -0
  69. package/src/generators/jsonGenerator.ts +32 -0
  70. package/src/hooks/index.ts +2 -0
  71. package/src/hooks/useOperationManager.ts +56 -30
  72. package/src/hooks/useSchemaManager.ts +77 -0
  73. package/src/index.ts +5 -13
  74. package/src/plugin.ts +73 -69
  75. package/src/types.ts +29 -27
  76. package/src/utils/getParams.ts +2 -2
  77. package/src/utils/getSchemaFactory.ts +1 -1
  78. package/src/utils/index.ts +2 -2
  79. package/src/utils/parseFromConfig.ts +7 -7
  80. package/dist/OperationGenerator-BV6QRtkW.d.cts +0 -60
  81. package/dist/OperationGenerator-DO5tNGuT.d.ts +0 -60
  82. package/dist/Schema-DefwBJMc.d.ts +0 -35
  83. package/dist/Schema-an5hOrjZ.d.cts +0 -35
  84. package/dist/SchemaMapper-CsBQ6eEx.d.cts +0 -248
  85. package/dist/SchemaMapper-CsBQ6eEx.d.ts +0 -248
  86. package/dist/chunk-6QFVU3BI.js +0 -3265
  87. package/dist/chunk-6QFVU3BI.js.map +0 -1
  88. package/dist/chunk-7BYOJGO3.js +0 -35
  89. package/dist/chunk-7BYOJGO3.js.map +0 -1
  90. package/dist/chunk-DHPXS6V7.cjs +0 -35
  91. package/dist/chunk-DHPXS6V7.cjs.map +0 -1
  92. package/dist/chunk-E4NYDEAU.js +0 -101
  93. package/dist/chunk-E4NYDEAU.js.map +0 -1
  94. package/dist/chunk-KGR7UP2W.js +0 -695
  95. package/dist/chunk-KGR7UP2W.js.map +0 -1
  96. package/dist/chunk-M62LERKB.cjs +0 -695
  97. package/dist/chunk-M62LERKB.cjs.map +0 -1
  98. package/dist/chunk-NLQIW5KR.cjs +0 -3265
  99. package/dist/chunk-NLQIW5KR.cjs.map +0 -1
  100. package/dist/chunk-PHNRHM3I.cjs +0 -101
  101. package/dist/chunk-PHNRHM3I.cjs.map +0 -1
  102. package/dist/types-Db0qp8u-.d.cts +0 -148
  103. package/dist/types-Db0qp8u-.d.ts +0 -148
  104. package/src/utils/getGroupedByTagFiles.ts +0 -82
  105. package/src/utils/refSorter.ts +0 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-oas",
3
- "version": "0.0.0-canary-20240806053140",
3
+ "version": "0.0.0-canary-20241106205013",
4
4
  "description": "Generator swagger",
5
5
  "keywords": [
6
6
  "typescript",
@@ -25,6 +25,11 @@
25
25
  "require": "./dist/index.cjs",
26
26
  "default": "./dist/index.cjs"
27
27
  },
28
+ "./generators": {
29
+ "import": "./dist/generators.js",
30
+ "require": "./dist/generators.cjs",
31
+ "default": "./dist/generators.cjs"
32
+ },
28
33
  "./utils": {
29
34
  "import": "./dist/utils.js",
30
35
  "require": "./dist/utils.cjs",
@@ -56,6 +61,9 @@
56
61
  ],
57
62
  "components": [
58
63
  "./dist/components.d.ts"
64
+ ],
65
+ "generators": [
66
+ "./dist/generators.d.ts"
59
67
  ]
60
68
  }
61
69
  },
@@ -67,27 +75,25 @@
67
75
  ],
68
76
  "dependencies": {
69
77
  "@stoplight/yaml": "^4.3.0",
70
- "remeda": "^2.7.1",
71
- "@kubb/core": "0.0.0-canary-20240806053140",
72
- "@kubb/fs": "0.0.0-canary-20240806053140",
73
- "@kubb/oas": "0.0.0-canary-20240806053140",
74
- "@kubb/react": "0.0.0-canary-20240806053140"
78
+ "remeda": "^2.16.0",
79
+ "@kubb/core": "0.0.0-canary-20241106205013",
80
+ "@kubb/fs": "0.0.0-canary-20241106205013",
81
+ "@kubb/oas": "0.0.0-canary-20241106205013",
82
+ "@kubb/react": "0.0.0-canary-20241106205013"
75
83
  },
76
84
  "devDependencies": {
77
- "@types/react": "^18.3.3",
78
- "@types/react-dom": "^18.3.0",
85
+ "@types/react": "^18.3.12",
79
86
  "react": "^18.3.1",
80
- "tsup": "^8.2.4",
81
- "typescript": "^5.5.4",
82
- "@kubb/config-biome": "0.0.0-canary-20240806053140",
83
- "@kubb/config-ts": "0.0.0-canary-20240806053140",
84
- "@kubb/config-tsup": "0.0.0-canary-20240806053140"
87
+ "tsup": "^8.3.5",
88
+ "typescript": "^5.6.3",
89
+ "@kubb/config-ts": "0.0.0-canary-20241106205013",
90
+ "@kubb/config-tsup": "0.0.0-canary-20241106205013"
85
91
  },
86
92
  "peerDependencies": {
87
- "@kubb/react": "0.0.0-canary-20240806053140"
93
+ "@kubb/react": "0.0.0-canary-20241106205013"
88
94
  },
89
95
  "engines": {
90
- "node": ">=18"
96
+ "node": ">=20"
91
97
  },
92
98
  "publishConfig": {
93
99
  "access": "public",
@@ -1,15 +1,14 @@
1
- import { type FileMetaBase, Generator } from '@kubb/core'
1
+ import { BaseGenerator, type FileMetaBase } from '@kubb/core'
2
2
  import transformers from '@kubb/core/transformers'
3
3
 
4
4
  import type { PluginFactoryOptions, PluginManager } from '@kubb/core'
5
5
  import type * as KubbFile from '@kubb/fs/types'
6
6
 
7
7
  import type { Plugin } from '@kubb/core'
8
- import type { HttpMethod, Oas, OasTypes, Operation, contentType } from '@kubb/oas'
8
+ import type { HttpMethod, Oas, OasTypes, Operation, SchemaObject, contentType } from '@kubb/oas'
9
+ import type { Generator } from './generator.tsx'
9
10
  import type { Exclude, Include, OperationSchemas, OperationsByMethod, Override } from './types.ts'
10
11
 
11
- export type GetOperationGeneratorOptions<T extends OperationGenerator<any, any, any>> = T extends OperationGenerator<infer Options, any, any> ? Options : never
12
-
13
12
  export type OperationMethodResult<TFileMeta extends FileMetaBase> = Promise<KubbFile.File<TFileMeta> | Array<KubbFile.File<TFileMeta>> | null>
14
13
 
15
14
  type Context<TOptions, TPluginOptions extends PluginFactoryOptions> = {
@@ -26,11 +25,10 @@ type Context<TOptions, TPluginOptions extends PluginFactoryOptions> = {
26
25
  mode: KubbFile.Mode
27
26
  }
28
27
 
29
- export abstract class OperationGenerator<
30
- TOptions = unknown,
28
+ export class OperationGenerator<
31
29
  TPluginOptions extends PluginFactoryOptions = PluginFactoryOptions,
32
30
  TFileMeta extends FileMetaBase = FileMetaBase,
33
- > extends Generator<TOptions, Context<TOptions, TPluginOptions>> {
31
+ > extends BaseGenerator<TPluginOptions['resolvedOptions'], Context<TPluginOptions['resolvedOptions'], TPluginOptions>> {
34
32
  #operationsByMethod: OperationsByMethod = {}
35
33
  get operationsByMethod(): OperationsByMethod {
36
34
  return this.#operationsByMethod
@@ -40,7 +38,7 @@ export abstract class OperationGenerator<
40
38
  this.#operationsByMethod = paths
41
39
  }
42
40
 
43
- #getOptions(operation: Operation, method: HttpMethod): Partial<TOptions> {
41
+ #getOptions(operation: Operation, method: HttpMethod): Partial<TPluginOptions['resolvedOptions']> {
44
42
  const { override = [] } = this.context
45
43
 
46
44
  return (
@@ -65,10 +63,7 @@ export abstract class OperationGenerator<
65
63
  })?.options || {}
66
64
  )
67
65
  }
68
- /**
69
- *
70
- * @deprecated
71
- */
66
+
72
67
  #isExcluded(operation: Operation, method: HttpMethod): boolean {
73
68
  const { exclude = [] } = this.context
74
69
  let matched = false
@@ -93,10 +88,7 @@ export abstract class OperationGenerator<
93
88
 
94
89
  return matched
95
90
  }
96
- /**
97
- *
98
- * @deprecated
99
- */
91
+
100
92
  #isIncluded(operation: Operation, method: HttpMethod): boolean {
101
93
  const { include = [] } = this.context
102
94
  let matched = false
@@ -124,15 +116,16 @@ export abstract class OperationGenerator<
124
116
 
125
117
  getSchemas(
126
118
  operation: Operation,
127
- { forStatusCode, resolveName = (name) => name }: { forStatusCode?: string | number; resolveName?: (name: string) => string } = {},
119
+ {
120
+ resolveName = (name) => name,
121
+ }: {
122
+ resolveName?: (name: string) => string
123
+ } = {},
128
124
  ): OperationSchemas {
129
125
  const pathParamsSchema = this.context.oas.getParametersSchema(operation, 'path')
130
126
  const queryParamsSchema = this.context.oas.getParametersSchema(operation, 'query')
131
127
  const headerParamsSchema = this.context.oas.getParametersSchema(operation, 'header')
132
128
  const requestSchema = this.context.oas.getRequestSchema(operation)
133
- const responseStatusCode =
134
- forStatusCode || (operation.schema.responses && Object.keys(operation.schema.responses).find((key) => key.startsWith('2'))) || 200
135
- const responseSchema = this.context.oas.getResponseSchema(operation, responseStatusCode)
136
129
  const statusCodes = operation.getResponseStatusCodes().map((statusCode) => {
137
130
  let name = statusCode
138
131
  if (name === 'default') {
@@ -149,8 +142,15 @@ export abstract class OperationGenerator<
149
142
  operationName: transformers.pascalCase(`${operation.getOperationId()}`),
150
143
  statusCode: name === 'error' ? undefined : Number(statusCode),
151
144
  keys: schema?.properties ? Object.keys(schema.properties) : undefined,
145
+ keysToOmit: schema?.properties
146
+ ? Object.keys(schema.properties).filter((key) => {
147
+ const item = schema.properties?.[key] as OasTypes.SchemaObject
148
+ return item?.writeOnly
149
+ })
150
+ : undefined,
152
151
  }
153
152
  })
153
+ const hasResponses = statusCodes.some((item) => item.statusCode?.toString().startsWith('2'))
154
154
 
155
155
  return {
156
156
  pathParams: pathParamsSchema
@@ -199,38 +199,30 @@ export abstract class OperationGenerator<
199
199
  : undefined,
200
200
  response: {
201
201
  name: resolveName(transformers.pascalCase(`${operation.getOperationId()} ${operation.method === 'get' ? 'queryResponse' : 'mutationResponse'}`)),
202
- description: operation.getResponseAsJSONSchema(responseStatusCode)?.at(0)?.description,
203
202
  operation,
204
203
  operationName: transformers.pascalCase(`${operation.getOperationId()}`),
205
- schema: responseSchema,
206
- statusCode: Number(responseStatusCode),
207
- keys: responseSchema?.properties ? Object.keys(responseSchema.properties) : undefined,
208
- keysToOmit: responseSchema?.properties
209
- ? Object.keys(responseSchema.properties).filter((key) => {
210
- const item = responseSchema.properties?.[key] as OasTypes.SchemaObject
211
- return item?.writeOnly
212
- })
213
- : undefined,
204
+ schema: {
205
+ oneOf: hasResponses
206
+ ? statusCodes
207
+ .filter((item) => item.statusCode?.toString().startsWith('2'))
208
+ .map((item) => {
209
+ return {
210
+ ...item.schema,
211
+ $ref: resolveName(transformers.pascalCase(`${operation.getOperationId()} ${item.statusCode}`)),
212
+ }
213
+ })
214
+ : undefined,
215
+ } as SchemaObject,
214
216
  },
217
+ responses: statusCodes.filter((item) => item.statusCode?.toString().startsWith('2')),
215
218
  errors: statusCodes.filter((item) => item.statusCode?.toString().startsWith('4') || item.statusCode?.toString().startsWith('5')),
216
219
  statusCodes,
217
220
  }
218
221
  }
219
222
 
220
- get #methods() {
221
- return {
222
- get: this.get,
223
- post: this.post,
224
- patch: this.patch,
225
- put: this.put,
226
- delete: this.delete,
227
- head: undefined,
228
- options: undefined,
229
- trace: undefined,
230
- } as const
231
- }
223
+ #methods = ['get', 'post', 'patch', 'put', 'delete']
232
224
 
233
- async build(): Promise<Array<KubbFile.File<TFileMeta>>> {
225
+ async build(...generators: Array<Generator<TPluginOptions>>): Promise<Array<KubbFile.File<TFileMeta>>> {
234
226
  const { oas } = this.context
235
227
 
236
228
  const paths = oas.getPaths()
@@ -239,7 +231,7 @@ export abstract class OperationGenerator<
239
231
 
240
232
  methods.forEach((method) => {
241
233
  const operation = oas.operation(path, method)
242
- if (operation && this.#methods[method]) {
234
+ if (operation && [this.#methods].some((methods) => method === operation.method)) {
243
235
  const isExcluded = this.#isExcluded(operation, method)
244
236
  const isIncluded = this.context.include ? this.#isIncluded(operation, method) : true
245
237
 
@@ -267,21 +259,43 @@ export abstract class OperationGenerator<
267
259
  methods.forEach((method) => {
268
260
  const { operation } = this.operationsByMethod[path]?.[method]!
269
261
  const options = this.#getOptions(operation, method)
270
- const promiseMethod = this.#methods[method]?.call(this, operation, {
271
- ...this.options,
272
- ...options,
273
- })
262
+
263
+ const methodToCall = this[method as keyof typeof this] as any
264
+
265
+ if (typeof methodToCall === 'function') {
266
+ const promiseMethod = methodToCall?.call(this, operation, {
267
+ ...this.options,
268
+ ...options,
269
+ })
270
+
271
+ if (promiseMethod) {
272
+ acc.push(promiseMethod)
273
+ }
274
+ }
275
+
274
276
  const promiseOperation = this.operation.call(this, operation, {
275
277
  ...this.options,
276
278
  ...options,
277
279
  })
278
280
 
279
- if (promiseMethod) {
280
- acc.push(promiseMethod)
281
- }
282
281
  if (promiseOperation) {
283
282
  acc.push(promiseOperation)
284
283
  }
284
+
285
+ generators?.forEach((generator) => {
286
+ const promise = generator.operation?.({
287
+ instance: this,
288
+ operation,
289
+ options: {
290
+ ...this.options,
291
+ ...options,
292
+ },
293
+ } as any) as Promise<Array<KubbFile.File<TFileMeta>>>
294
+
295
+ if (promise) {
296
+ acc.push(promise)
297
+ }
298
+ })
285
299
  })
286
300
 
287
301
  return acc
@@ -291,6 +305,19 @@ export abstract class OperationGenerator<
291
305
 
292
306
  promises.push(this.all(operations.flat().filter(Boolean), this.operationsByMethod))
293
307
 
308
+ generators?.forEach((generator) => {
309
+ const promise = generator.operations?.({
310
+ instance: this,
311
+ operations: operations.flat().filter(Boolean),
312
+ operationsByMethod: this.operationsByMethod,
313
+ options: this.options,
314
+ } as any) as Promise<Array<KubbFile.File<TFileMeta>>>
315
+
316
+ if (promise) {
317
+ promises.push(promise)
318
+ }
319
+ })
320
+
294
321
  const files = await Promise.all(promises)
295
322
 
296
323
  // using .flat because operationGenerator[method] can return a array of files or just one file
@@ -300,48 +327,48 @@ export abstract class OperationGenerator<
300
327
  /**
301
328
  * Operation
302
329
  */
303
- async operation(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
304
- return null
330
+ async operation(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
331
+ return []
305
332
  }
306
333
 
307
334
  /**
308
335
  * GET
309
336
  */
310
- async get(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
311
- return null
337
+ async get(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
338
+ return []
312
339
  }
313
340
 
314
341
  /**
315
342
  * POST
316
343
  */
317
- async post(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
318
- return null
344
+ async post(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
345
+ return []
319
346
  }
320
347
  /**
321
348
  * PATCH
322
349
  */
323
- async patch(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
324
- return null
350
+ async patch(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
351
+ return []
325
352
  }
326
353
 
327
354
  /**
328
355
  * PUT
329
356
  */
330
- async put(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
331
- return null
357
+ async put(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
358
+ return []
332
359
  }
333
360
 
334
361
  /**
335
362
  * DELETE
336
363
  */
337
- async delete(operation: Operation, options: TOptions): OperationMethodResult<TFileMeta> {
338
- return null
364
+ async delete(operation: Operation, options: TPluginOptions['resolvedOptions']): OperationMethodResult<TFileMeta> {
365
+ return []
339
366
  }
340
367
 
341
368
  /**
342
369
  * Combination of GET, POST, PATCH, PUT, DELETE
343
370
  */
344
371
  async all(operations: Operation[], paths: OperationsByMethod): OperationMethodResult<TFileMeta> {
345
- return null
372
+ return []
346
373
  }
347
374
  }
@@ -1,4 +1,4 @@
1
- import { type FileMetaBase, Generator } from '@kubb/core'
1
+ import { BaseGenerator, type FileMetaBase } from '@kubb/core'
2
2
  import transformers, { pascalCase } from '@kubb/core/transformers'
3
3
  import { getUniqueName } from '@kubb/core/utils'
4
4
 
@@ -13,6 +13,7 @@ import type * as KubbFile from '@kubb/fs/types'
13
13
 
14
14
  import type { Oas, OpenAPIV3, SchemaObject, contentType } from '@kubb/oas'
15
15
  import type { Schema, SchemaKeywordMapper } from './SchemaMapper.ts'
16
+ import type { Generator } from './generator.tsx'
16
17
  import type { OperationSchema, Override, Refs } from './types.ts'
17
18
 
18
19
  export type GetSchemaGeneratorOptions<T extends SchemaGenerator<any, any, any>> = T extends SchemaGenerator<infer Options, any, any> ? Options : never
@@ -63,11 +64,11 @@ type SchemaProps = {
63
64
  parentName?: string
64
65
  }
65
66
 
66
- export abstract class SchemaGenerator<
67
+ export class SchemaGenerator<
67
68
  TOptions extends SchemaGeneratorOptions = SchemaGeneratorOptions,
68
69
  TPluginOptions extends PluginFactoryOptions = PluginFactoryOptions,
69
70
  TFileMeta extends FileMetaBase = FileMetaBase,
70
- > extends Generator<TOptions, Context<TOptions, TPluginOptions>> {
71
+ > extends BaseGenerator<TOptions, Context<TOptions, TPluginOptions>> {
71
72
  // Collect the types of all referenced schemas, so we can export them later
72
73
  refs: Refs = {}
73
74
 
@@ -85,7 +86,7 @@ export abstract class SchemaGenerator<
85
86
  const defaultSchemas = this.#parseSchemaObject(props)
86
87
  const schemas = options.transformers?.schema?.(props, defaultSchemas) || defaultSchemas || []
87
88
 
88
- return uniqueWith<Schema>(schemas, isDeepEqual)
89
+ return uniqueWith(schemas, isDeepEqual)
89
90
  }
90
91
 
91
92
  deepSearch<T extends keyof SchemaKeywordMapper>(tree: Schema[] | undefined, keyword: T): SchemaKeywordMapper[T][] {
@@ -301,7 +302,7 @@ export abstract class SchemaGenerator<
301
302
 
302
303
  if (additionalProperties) {
303
304
  additionalPropertiesSchemas =
304
- additionalProperties === true
305
+ additionalProperties === true || !Object.keys(additionalProperties).length
305
306
  ? [{ keyword: this.#getUnknownReturn({ schema, name }) }]
306
307
  : this.parse({ schema: additionalProperties as SchemaObject, parentName: name })
307
308
  }
@@ -335,7 +336,7 @@ export abstract class SchemaGenerator<
335
336
  return [
336
337
  {
337
338
  keyword: schemaKeywords.ref,
338
- args: { name: ref.propertyName, path: ref.path },
339
+ args: { name: ref.propertyName, path: ref.path, isImportable: !!this.context.oas.get($ref) },
339
340
  },
340
341
  ]
341
342
  }
@@ -348,7 +349,7 @@ export abstract class SchemaGenerator<
348
349
  const file = this.context.pluginManager.getFile({
349
350
  name: fileName,
350
351
  pluginKey: this.context.plugin.key,
351
- extName: '.ts',
352
+ extname: '.ts',
352
353
  })
353
354
 
354
355
  ref = this.refs[$ref] = {
@@ -360,7 +361,7 @@ export abstract class SchemaGenerator<
360
361
  return [
361
362
  {
362
363
  keyword: schemaKeywords.ref,
363
- args: { name: ref.propertyName, path: ref?.path, isTypeOnly: false },
364
+ args: { name: ref.propertyName, path: ref?.path, isImportable: !!this.context.oas.get($ref) },
364
365
  },
365
366
  ]
366
367
  }
@@ -378,12 +379,6 @@ export abstract class SchemaGenerator<
378
379
  const options = this.#getOptions({ schema: _schema, name })
379
380
  const unknownReturn = this.#getUnknownReturn({ schema: _schema, name })
380
381
  const { schema, version } = this.#getParsedSchemaObject(_schema)
381
- const resolvedName = this.context.pluginManager.resolveName({
382
- name: `${parentName || ''} ${name}`,
383
- pluginKey: this.context.plugin.key,
384
- type: 'type',
385
- })
386
-
387
382
  if (!schema) {
388
383
  return [{ keyword: unknownReturn }]
389
384
  }
@@ -407,12 +402,16 @@ export abstract class SchemaGenerator<
407
402
  keyword: schemaKeywords.default,
408
403
  args: transformers.stringify(schema.default),
409
404
  })
410
- }
411
- if (typeof schema.default === 'boolean') {
405
+ } else if (typeof schema.default === 'boolean') {
412
406
  baseItems.push({
413
407
  keyword: schemaKeywords.default,
414
408
  args: schema.default ?? false,
415
409
  })
410
+ } else {
411
+ baseItems.push({
412
+ keyword: schemaKeywords.default,
413
+ args: schema.default,
414
+ })
416
415
  }
417
416
  }
418
417
 
@@ -454,10 +453,16 @@ export abstract class SchemaGenerator<
454
453
  baseItems.push({ keyword: schemaKeywords.readOnly })
455
454
  }
456
455
 
456
+ if (schema.writeOnly) {
457
+ baseItems.push({ keyword: schemaKeywords.writeOnly })
458
+ }
459
+
457
460
  if (isReference(schema)) {
458
461
  return [
459
462
  ...this.#getRefAlias(schema),
460
463
  nullable && { keyword: schemaKeywords.nullable },
464
+ schema.readOnly && { keyword: schemaKeywords.readOnly },
465
+ schema.writeOnly && { keyword: schemaKeywords.writeOnly },
461
466
  {
462
467
  keyword: schemaKeywords.schema,
463
468
  args: {
@@ -484,7 +489,20 @@ export abstract class SchemaGenerator<
484
489
  }),
485
490
  }
486
491
  if (schemaWithoutOneOf.properties) {
487
- return [...this.parse({ schema: schemaWithoutOneOf, name, parentName }), union, ...baseItems]
492
+ const propertySchemas = this.parse({ schema: schemaWithoutOneOf, name, parentName })
493
+
494
+ return [
495
+ {
496
+ ...union,
497
+ args: union.args.map((arg) => {
498
+ return {
499
+ keyword: schemaKeywords.and,
500
+ args: [arg, ...propertySchemas],
501
+ }
502
+ }),
503
+ },
504
+ ...baseItems,
505
+ ]
488
506
  }
489
507
 
490
508
  return [union, ...baseItems]
@@ -619,6 +637,35 @@ export abstract class SchemaGenerator<
619
637
  ]
620
638
  }
621
639
 
640
+ if (schema.type === 'boolean') {
641
+ // we cannot use z.enum when enum type is boolean
642
+ const enumNames = extensionEnums[0]?.find((item) => isKeyword(item, schemaKeywords.enum)) as unknown as SchemaKeywordMapper['enum']
643
+ return [
644
+ {
645
+ keyword: schemaKeywords.enum,
646
+ args: {
647
+ name: enumName,
648
+ typeName,
649
+ asConst: true,
650
+ items: enumNames?.args?.items
651
+ ? [...new Set(enumNames.args.items)].map(({ name, value }) => ({
652
+ name,
653
+ value,
654
+ format: 'boolean',
655
+ }))
656
+ : [...new Set(filteredValues)].map((value: string) => {
657
+ return {
658
+ name: value,
659
+ value,
660
+ format: 'boolean',
661
+ }
662
+ }),
663
+ },
664
+ },
665
+ ...baseItems.filter((item) => item.keyword !== schemaKeywords.matches),
666
+ ]
667
+ }
668
+
622
669
  if (extensionEnums.length > 0 && extensionEnums[0]) {
623
670
  return extensionEnums[0]
624
671
  }
@@ -809,6 +856,10 @@ export abstract class SchemaGenerator<
809
856
  ].filter(Boolean)
810
857
  }
811
858
 
859
+ if (!['boolean', 'object', 'number', 'string', 'integer', 'null'].includes(schema.type)) {
860
+ this.context.pluginManager.logger.emit('warning', `Schema type '${schema.type}' is not valid for schema ${parentName}.${name}`)
861
+ }
862
+
812
863
  // 'string' | 'number' | 'integer' | 'boolean'
813
864
  return [{ keyword: schema.type }, ...baseItems]
814
865
  }
@@ -816,14 +867,20 @@ export abstract class SchemaGenerator<
816
867
  return [{ keyword: unknownReturn }]
817
868
  }
818
869
 
819
- async build(): Promise<Array<KubbFile.File<TFileMeta>>> {
870
+ async build(...generators: Array<Generator<TPluginOptions>>): Promise<Array<KubbFile.File<TFileMeta>>> {
820
871
  const { oas, contentType, include } = this.context
821
872
 
873
+ oas.resolveDiscriminators()
874
+
822
875
  const schemas = getSchemas({ oas, contentType, includes: include })
823
876
 
824
- const promises = Object.entries(schemas).reduce((acc, [name, schema]) => {
877
+ const promises = Object.entries(schemas).reduce((acc, [name, value]) => {
878
+ if (!value) {
879
+ return acc
880
+ }
881
+
825
882
  const options = this.#getOptions({ name })
826
- const promiseOperation = this.schema.call(this, name, schema, {
883
+ const promiseOperation = this.schema.call(this, name, value, {
827
884
  ...this.options,
828
885
  ...options,
829
886
  })
@@ -832,17 +889,40 @@ export abstract class SchemaGenerator<
832
889
  acc.push(promiseOperation)
833
890
  }
834
891
 
892
+ generators?.forEach((generator) => {
893
+ const tree = this.parse({ schema: value, name: name })
894
+
895
+ const promise = generator.schema?.({
896
+ instance: this,
897
+ schema: {
898
+ name,
899
+ value,
900
+ tree,
901
+ },
902
+ options: {
903
+ ...this.options,
904
+ ...options,
905
+ },
906
+ } as any) as Promise<Array<KubbFile.File<TFileMeta>>>
907
+
908
+ if (promise) {
909
+ acc.push(promise)
910
+ }
911
+ })
912
+
835
913
  return acc
836
914
  }, [] as SchemaMethodResult<TFileMeta>[])
837
915
 
838
916
  const files = await Promise.all(promises)
839
917
 
840
- // using .flat because schemaGenerator[method] can return a array of files or just one file
918
+ // using .flat because schemaGenerator[method] can return an array of files or just one file
841
919
  return files.flat().filter(Boolean)
842
920
  }
843
921
 
844
922
  /**
845
923
  * Schema
846
924
  */
847
- abstract schema(name: string, object: SchemaObject, options: TOptions): SchemaMethodResult<TFileMeta>
925
+ async schema(name: string, object: SchemaObject, options: TOptions): SchemaMethodResult<TFileMeta> {
926
+ return []
927
+ }
848
928
  }
@@ -12,6 +12,7 @@ export type SchemaKeywordMapper = {
12
12
  strict: { keyword: 'strict' }
13
13
  url: { keyword: 'url' }
14
14
  readOnly: { keyword: 'readOnly' }
15
+ writeOnly: { keyword: 'writeOnly' }
15
16
  uuid: { keyword: 'uuid' }
16
17
  email: { keyword: 'email' }
17
18
  firstName: { keyword: 'firstName' }
@@ -34,8 +35,8 @@ export type SchemaKeywordMapper = {
34
35
  asConst: boolean
35
36
  items: Array<{
36
37
  name: string | number
37
- format: 'string' | 'number'
38
- value?: string | number
38
+ format: 'string' | 'number' | 'boolean'
39
+ value?: string | number | boolean
39
40
  }>
40
41
  }
41
42
  }
@@ -44,14 +45,25 @@ export type SchemaKeywordMapper = {
44
45
  keyword: 'const'
45
46
  args: {
46
47
  name: string | number
47
- format: 'string' | 'number'
48
- value?: string | number
48
+ format: 'string' | 'number' | 'boolean'
49
+ value?: string | number | boolean
49
50
  }
50
51
  }
51
52
  union: { keyword: 'union'; args: Schema[] }
52
53
  ref: {
53
54
  keyword: 'ref'
54
- args: { name: string; path: KubbFile.OptionalPath; isTypeOnly?: boolean }
55
+ args: {
56
+ name: string
57
+ /**
58
+ * Full qualified path.
59
+ */
60
+ path: KubbFile.OptionalPath
61
+ /**
62
+ * When true `File.Import` will be used.
63
+ * When false a reference will be used inside the current file.
64
+ */
65
+ isImportable: boolean
66
+ }
55
67
  }
56
68
  matches: { keyword: 'matches'; args?: string }
57
69
  boolean: { keyword: 'boolean' }
@@ -107,6 +119,7 @@ export const schemaKeywords = {
107
119
  max: 'max',
108
120
  optional: 'optional',
109
121
  readOnly: 'readOnly',
122
+ writeOnly: 'writeOnly',
110
123
 
111
124
  // custom ones
112
125
  object: 'object',
@@ -140,6 +153,12 @@ export type SchemaKeywordBase<T> = {
140
153
 
141
154
  export type Schema = { keyword: string } | SchemaKeywordMapper[keyof SchemaKeywordMapper]
142
155
 
156
+ export type SchemaTree = {
157
+ parent: Schema | undefined
158
+ current: Schema
159
+ siblings: Schema[]
160
+ }
161
+
143
162
  export function isKeyword<T extends Schema, K extends keyof SchemaKeywordMapper>(meta: T, keyword: K): meta is Extract<T, SchemaKeywordMapper[K]> {
144
163
  return meta.keyword === keyword
145
164
  }