fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2127 from hey-api/fix/input-patch-components

fix(parser): add parameters, requestBodies, and responses to supported input.patch fields

authored by

Lubos and committed by
GitHub
d2a67679 7a2905b0

+1230 -1099
+5
.changeset/shaggy-beers-call.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(parser): add parameters, requestBodies, and responses to supported input.patch fields
+20 -76
docs/openapi-ts/configuration.md
··· 474 474 }; 475 475 ``` 476 476 477 - ## Patch Schemas 478 - 479 - If you need to modify schemas in your OpenAPI specification before code generation, you can use `input.patch.schemas` to apply custom transformations to specific schemas. 477 + ## Patch 480 478 481 - You can provide patch functions for individual schemas by their names. Each function receives the schema object and can modify it directly. 479 + There are times when you need to modify your input before it's processed further. A common use case is fixing an invalid specification or adding a missing field. You can apply custom patches with `input.patch`. 482 480 483 - ::: code-group 481 + You can patch individual schemas by their name. All patches work with raw input data and are applied before we generate any code. 484 482 485 - ```js [date-time to timestamp] 483 + ```js 486 484 export default { 487 485 input: { 488 486 patch: { 489 487 schemas: { 490 - UserResponseDto: (schema) => { 491 - // Convert date-time format to timestamp 492 - if (schema.properties?.updatedAt) { 493 - delete schema.properties.updatedAt.format; 494 - schema.properties.updatedAt.type = 'number'; 495 - } 488 + Foo: (schema) => { 489 + // convert date-time format to timestamp 490 + delete schema.properties.updatedAt.format; 491 + schema.properties.updatedAt.type = 'number'; 496 492 }, 497 - }, 498 - }, 499 - path: 'https://get.heyapi.dev/hey-api/backend', 500 - }, 501 - output: 'src/client', 502 - plugins: ['@hey-api/client-fetch'], 503 - }; 504 - ``` 505 - 506 - ```js [add properties] 507 - export default { 508 - input: { 509 - patch: { 510 - schemas: { 511 - ProductModel: (schema) => { 512 - // Add metadata property 513 - schema.properties.metadata = { 514 - type: 'object', 493 + Bar: (schema) => { 494 + // add missing property 495 + schema.properties.meta = { 515 496 additionalProperties: true, 497 + type: 'object', 516 498 }; 517 - schema.required = ['id']; 499 + schema.required = ['meta']; 518 500 }, 519 - }, 520 - }, 521 - path: 'https://get.heyapi.dev/hey-api/backend', 522 - }, 523 - output: 'src/client', 524 - plugins: ['@hey-api/client-fetch'], 525 - }; 526 - ``` 527 - 528 - ```js [remove properties] 529 - export default { 530 - input: { 531 - patch: { 532 - schemas: { 533 - ApiResponseDto: (schema) => { 534 - // Remove internal fields 501 + Baz: (schema) => { 502 + // remove property 535 503 delete schema.properties.internalField; 536 504 }, 537 505 }, ··· 543 511 }; 544 512 ``` 545 513 546 - ```ts [typescript] 547 - import { defineConfig, type OpenApiSchemaObject } from '@hey-api/openapi-ts'; 548 - 549 - export default defineConfig({ 550 - input: { 551 - patch: { 552 - schemas: { 553 - ApiResponseDto: (schema: OpenApiSchemaObject.V3_1_X) => { 554 - if (typeof schema.properties?.updatedAt === 'object') { 555 - delete schema.properties.updatedAt.format; 556 - schema.properties.updatedAt.type = 'number'; 557 - } 558 - }, 559 - }, 560 - }, 561 - path: 'https://get.heyapi.dev/hey-api/backend', 562 - }, 563 - output: 'src/client', 564 - plugins: ['@hey-api/client-fetch'], 565 - }); 566 - ``` 567 - 568 - ::: 569 - 570 - Patch functions work with both OpenAPI v3.x schemas (in `components.schemas`) and Swagger v2.0 schemas (in `definitions`). 571 - 572 514 ## Watch Mode 573 515 574 516 ::: warning 575 517 Watch mode currently supports only remote files via URL. 576 518 ::: 577 519 578 - If your schema changes frequently, you may want to automatically regenerate the output during development. To watch your input file for changes, enable `watch` mode in your configuration or pass the `--watch` flag to the CLI. 520 + If your schema changes frequently, you may want to automatically regenerate the output during development. To watch your input file for changes, enable `input.watch` mode in your configuration or pass the `--watch` flag to the CLI. 579 521 580 522 ::: code-group 581 523 582 524 ```js [config] 583 525 export default { 584 - input: 'https://get.heyapi.dev/hey-api/backend', 526 + input: { 527 + path: 'https://get.heyapi.dev/hey-api/backend', 528 + watch: true, // [!code ++] 529 + }, 585 530 output: 'src/client', 586 531 plugins: ['@hey-api/client-fetch'], 587 - watch: true, // [!code ++] 588 532 }; 589 533 ``` 590 534
-620
packages/openapi-ts/src/__tests__/patchSchemas.test.ts
··· 1 - import { describe, expect, it, vi } from 'vitest'; 2 - 3 - import type { OpenApi } from '../openApi/types'; 4 - import { patchSchemas } from '../patchSchemas'; 5 - 6 - describe('patchSchemas', () => { 7 - describe('edge cases', () => { 8 - it('should return early when data is null', () => { 9 - const fixFn = vi.fn(); 10 - 11 - patchSchemas({ 12 - data: null, 13 - patch: { 14 - schemas: { 15 - TestSchema: fixFn, 16 - }, 17 - }, 18 - }); 19 - 20 - expect(fixFn).not.toHaveBeenCalled(); 21 - }); 22 - 23 - it('should return early when data is undefined', () => { 24 - const fixFn = vi.fn(); 25 - 26 - patchSchemas({ 27 - data: undefined, 28 - patch: { 29 - schemas: { 30 - TestSchema: fixFn, 31 - }, 32 - }, 33 - }); 34 - 35 - expect(fixFn).not.toHaveBeenCalled(); 36 - }); 37 - 38 - it('should return early when fix is undefined', () => { 39 - const mockSpec: OpenApi.V3_1_X = { 40 - components: { 41 - schemas: { 42 - TestSchema: { 43 - properties: { 44 - id: { type: 'string' }, 45 - }, 46 - type: 'object', 47 - }, 48 - }, 49 - }, 50 - info: { title: 'Test API', version: '1.0.0' }, 51 - openapi: '3.1.0', 52 - }; 53 - 54 - patchSchemas({ data: mockSpec }); 55 - 56 - expect(mockSpec.components?.schemas?.TestSchema).toEqual({ 57 - properties: { 58 - id: { type: 'string' }, 59 - }, 60 - type: 'object', 61 - }); 62 - }); 63 - 64 - it('should return early when fix.schema is undefined', () => { 65 - const fixFn = vi.fn(); 66 - const mockSpec: OpenApi.V3_1_X = { 67 - components: { 68 - schemas: { 69 - TestSchema: { 70 - properties: { 71 - id: { type: 'string' }, 72 - }, 73 - type: 'object', 74 - }, 75 - }, 76 - }, 77 - info: { title: 'Test API', version: '1.0.0' }, 78 - openapi: '3.1.0', 79 - }; 80 - 81 - patchSchemas({ 82 - data: mockSpec, 83 - patch: {}, 84 - }); 85 - 86 - expect(fixFn).not.toHaveBeenCalled(); 87 - }); 88 - }); 89 - 90 - describe('OpenAPI v3 schema fixing', () => { 91 - it('should apply fix function to matching schema in components.schemas', () => { 92 - const fixFn = vi.fn(); 93 - const mockSpec: OpenApi.V3_1_X = { 94 - components: { 95 - schemas: { 96 - TestSchema: { 97 - properties: { 98 - id: { type: 'string' }, 99 - updatedAt: { format: 'date-time', type: 'string' }, 100 - }, 101 - type: 'object', 102 - }, 103 - }, 104 - }, 105 - info: { title: 'Test API', version: '1.0.0' }, 106 - openapi: '3.1.0', 107 - }; 108 - 109 - patchSchemas({ 110 - data: mockSpec, 111 - patch: { 112 - schemas: { 113 - TestSchema: fixFn, 114 - }, 115 - }, 116 - }); 117 - 118 - expect(fixFn).toHaveBeenCalledWith({ 119 - properties: { 120 - id: { type: 'string' }, 121 - updatedAt: { format: 'date-time', type: 'string' }, 122 - }, 123 - type: 'object', 124 - }); 125 - }); 126 - 127 - it('should mutate schema object when fix function modifies it', () => { 128 - const mockSpec: OpenApi.V3_1_X = { 129 - components: { 130 - schemas: { 131 - TestSchema: { 132 - properties: { 133 - id: { type: 'string' }, 134 - updatedAt: { format: 'date-time', type: 'string' }, 135 - }, 136 - type: 'object', 137 - }, 138 - }, 139 - }, 140 - info: { title: 'Test API', version: '1.0.0' }, 141 - openapi: '3.1.0', 142 - }; 143 - 144 - patchSchemas({ 145 - data: mockSpec, 146 - patch: { 147 - schemas: { 148 - TestSchema: (schema: any) => { 149 - if (schema.properties?.updatedAt) { 150 - delete schema.properties.updatedAt.format; 151 - schema.properties.updatedAt.type = 'number'; 152 - } 153 - }, 154 - }, 155 - }, 156 - }); 157 - 158 - expect(mockSpec.components?.schemas?.TestSchema).toEqual({ 159 - properties: { 160 - id: { type: 'string' }, 161 - updatedAt: { type: 'number' }, 162 - }, 163 - type: 'object', 164 - }); 165 - }); 166 - 167 - it('should apply multiple fix functions to different schemas', () => { 168 - const fixFn1 = vi.fn(); 169 - const fixFn2 = vi.fn(); 170 - const mockSpec: OpenApi.V3_1_X = { 171 - components: { 172 - schemas: { 173 - ProductSchema: { 174 - properties: { name: { type: 'string' } }, 175 - type: 'object', 176 - }, 177 - UserSchema: { 178 - properties: { id: { type: 'string' } }, 179 - type: 'object', 180 - }, 181 - }, 182 - }, 183 - info: { title: 'Test API', version: '1.0.0' }, 184 - openapi: '3.1.0', 185 - }; 186 - 187 - patchSchemas({ 188 - data: mockSpec, 189 - patch: { 190 - schemas: { 191 - ProductSchema: fixFn2, 192 - UserSchema: fixFn1, 193 - }, 194 - }, 195 - }); 196 - 197 - expect(fixFn1).toHaveBeenCalledWith({ 198 - properties: { id: { type: 'string' } }, 199 - type: 'object', 200 - }); 201 - expect(fixFn2).toHaveBeenCalledWith({ 202 - properties: { name: { type: 'string' } }, 203 - type: 'object', 204 - }); 205 - }); 206 - 207 - it('should skip schemas that do not have matching fix functions', () => { 208 - const fixFn = vi.fn(); 209 - const mockSpec: OpenApi.V3_1_X = { 210 - components: { 211 - schemas: { 212 - TestSchema: { 213 - properties: { id: { type: 'string' } }, 214 - type: 'object', 215 - }, 216 - UnmatchedSchema: { 217 - properties: { name: { type: 'string' } }, 218 - type: 'object', 219 - }, 220 - }, 221 - }, 222 - info: { title: 'Test API', version: '1.0.0' }, 223 - openapi: '3.1.0', 224 - }; 225 - 226 - patchSchemas({ 227 - data: mockSpec, 228 - patch: { 229 - schemas: { 230 - TestSchema: fixFn, 231 - }, 232 - }, 233 - }); 234 - 235 - expect(fixFn).toHaveBeenCalledOnce(); 236 - expect(fixFn).toHaveBeenCalledWith({ 237 - properties: { id: { type: 'string' } }, 238 - type: 'object', 239 - }); 240 - }); 241 - 242 - it('should handle spec without components', () => { 243 - const fixFn = vi.fn(); 244 - const mockSpec: OpenApi.V3_1_X = { 245 - info: { title: 'Test API', version: '1.0.0' }, 246 - openapi: '3.1.0', 247 - }; 248 - 249 - patchSchemas({ 250 - data: mockSpec, 251 - patch: { 252 - schemas: { 253 - TestSchema: fixFn, 254 - }, 255 - }, 256 - }); 257 - 258 - expect(fixFn).not.toHaveBeenCalled(); 259 - }); 260 - 261 - it('should handle spec with components but no schemas', () => { 262 - const fixFn = vi.fn(); 263 - const mockSpec: OpenApi.V3_1_X = { 264 - components: {}, 265 - info: { title: 'Test API', version: '1.0.0' }, 266 - openapi: '3.1.0', 267 - }; 268 - 269 - patchSchemas({ 270 - data: mockSpec, 271 - patch: { 272 - schemas: { 273 - TestSchema: fixFn, 274 - }, 275 - }, 276 - }); 277 - 278 - expect(fixFn).not.toHaveBeenCalled(); 279 - }); 280 - 281 - it('should skip non-object schema values', () => { 282 - const fixFn = vi.fn(); 283 - const mockSpec: OpenApi.V3_1_X = { 284 - components: { 285 - schemas: { 286 - NumberSchema: 123 as any, 287 - StringSchema: 'invalid' as any, 288 - TestSchema: null as any, 289 - ValidSchema: { 290 - properties: { id: { type: 'string' } }, 291 - type: 'object', 292 - }, 293 - }, 294 - }, 295 - info: { title: 'Test API', version: '1.0.0' }, 296 - openapi: '3.1.0', 297 - }; 298 - 299 - patchSchemas({ 300 - data: mockSpec, 301 - patch: { 302 - schemas: { 303 - NumberSchema: fixFn, 304 - StringSchema: fixFn, 305 - TestSchema: fixFn, 306 - ValidSchema: fixFn, 307 - }, 308 - }, 309 - }); 310 - 311 - expect(fixFn).toHaveBeenCalledOnce(); 312 - expect(fixFn).toHaveBeenCalledWith({ 313 - properties: { id: { type: 'string' } }, 314 - type: 'object', 315 - }); 316 - }); 317 - }); 318 - 319 - describe('OpenAPI v2 (Swagger) schema fixing', () => { 320 - it('should apply fix function to matching schema in definitions', () => { 321 - const fixFn = vi.fn(); 322 - const mockSpec: OpenApi.V2_0_X = { 323 - definitions: { 324 - TestModel: { 325 - properties: { 326 - createdAt: { format: 'date-time', type: 'string' }, 327 - id: { type: 'string' }, 328 - }, 329 - type: 'object', 330 - }, 331 - }, 332 - info: { title: 'Test API', version: '1.0.0' }, 333 - paths: {}, 334 - swagger: '2.0', 335 - }; 336 - 337 - patchSchemas({ 338 - data: mockSpec, 339 - patch: { 340 - schemas: { 341 - TestModel: fixFn, 342 - }, 343 - }, 344 - }); 345 - 346 - expect(fixFn).toHaveBeenCalledWith({ 347 - properties: { 348 - createdAt: { format: 'date-time', type: 'string' }, 349 - id: { type: 'string' }, 350 - }, 351 - type: 'object', 352 - }); 353 - }); 354 - 355 - it('should mutate schema object in Swagger v2 spec', () => { 356 - const mockSpec: OpenApi.V2_0_X = { 357 - definitions: { 358 - TestModel: { 359 - properties: { 360 - id: { type: 'string' }, 361 - timestamp: { format: 'date-time', type: 'string' }, 362 - }, 363 - type: 'object', 364 - }, 365 - }, 366 - info: { title: 'Test API', version: '1.0.0' }, 367 - paths: {}, 368 - swagger: '2.0', 369 - }; 370 - 371 - patchSchemas({ 372 - data: mockSpec, 373 - patch: { 374 - schemas: { 375 - TestModel: (schema: any) => { 376 - if (schema.properties?.timestamp) { 377 - delete schema.properties.timestamp.format; 378 - schema.properties.timestamp.type = 'integer'; 379 - } 380 - }, 381 - }, 382 - }, 383 - }); 384 - 385 - expect(mockSpec.definitions?.TestModel).toEqual({ 386 - properties: { 387 - id: { type: 'string' }, 388 - timestamp: { type: 'integer' }, 389 - }, 390 - type: 'object', 391 - }); 392 - }); 393 - 394 - it('should handle Swagger spec without definitions', () => { 395 - const fixFn = vi.fn(); 396 - const mockSpec: OpenApi.V2_0_X = { 397 - info: { title: 'Test API', version: '1.0.0' }, 398 - paths: {}, 399 - swagger: '2.0', 400 - }; 401 - 402 - patchSchemas({ 403 - data: mockSpec, 404 - patch: { 405 - schemas: { 406 - TestModel: fixFn, 407 - }, 408 - }, 409 - }); 410 - 411 - expect(fixFn).not.toHaveBeenCalled(); 412 - }); 413 - 414 - it('should apply multiple fix functions in Swagger v2', () => { 415 - const fixFn1 = vi.fn(); 416 - const fixFn2 = vi.fn(); 417 - const mockSpec: OpenApi.V2_0_X = { 418 - definitions: { 419 - Product: { 420 - properties: { name: { type: 'string' } }, 421 - type: 'object', 422 - }, 423 - User: { 424 - properties: { id: { type: 'string' } }, 425 - type: 'object', 426 - }, 427 - }, 428 - info: { title: 'Test API', version: '1.0.0' }, 429 - paths: {}, 430 - swagger: '2.0', 431 - }; 432 - 433 - patchSchemas({ 434 - data: mockSpec, 435 - patch: { 436 - schemas: { 437 - Product: fixFn2, 438 - User: fixFn1, 439 - }, 440 - }, 441 - }); 442 - 443 - expect(fixFn1).toHaveBeenCalledWith({ 444 - properties: { id: { type: 'string' } }, 445 - type: 'object', 446 - }); 447 - expect(fixFn2).toHaveBeenCalledWith({ 448 - properties: { name: { type: 'string' } }, 449 - type: 'object', 450 - }); 451 - }); 452 - 453 - it('should skip non-object schema values in Swagger v2', () => { 454 - const fixFn = vi.fn(); 455 - const mockSpec: OpenApi.V2_0_X = { 456 - definitions: { 457 - InvalidModel: null as any, 458 - StringModel: 'invalid' as any, 459 - ValidModel: { 460 - properties: { id: { type: 'string' } }, 461 - type: 'object', 462 - }, 463 - }, 464 - info: { title: 'Test API', version: '1.0.0' }, 465 - paths: {}, 466 - swagger: '2.0', 467 - }; 468 - 469 - patchSchemas({ 470 - data: mockSpec, 471 - patch: { 472 - schemas: { 473 - InvalidModel: fixFn, 474 - StringModel: fixFn, 475 - ValidModel: fixFn, 476 - }, 477 - }, 478 - }); 479 - 480 - expect(fixFn).toHaveBeenCalledOnce(); 481 - expect(fixFn).toHaveBeenCalledWith({ 482 - properties: { id: { type: 'string' } }, 483 - type: 'object', 484 - }); 485 - }); 486 - }); 487 - 488 - describe('real-world usage scenarios', () => { 489 - it('should handle complex schema transformation like in config example', () => { 490 - const mockSpec: OpenApi.V3_1_X = { 491 - components: { 492 - schemas: { 493 - ChatAgentSkinItemAdminResponseDto: { 494 - properties: { 495 - id: { type: 'string' }, 496 - name: { type: 'string' }, 497 - updatedAt: { 498 - format: 'date-time', 499 - type: 'string', 500 - }, 501 - }, 502 - type: 'object', 503 - }, 504 - }, 505 - }, 506 - info: { title: 'Test API', version: '1.0.0' }, 507 - openapi: '3.1.0', 508 - }; 509 - 510 - patchSchemas({ 511 - data: mockSpec, 512 - patch: { 513 - schemas: { 514 - ChatAgentSkinItemAdminResponseDto: (schema: any) => { 515 - if (typeof schema.properties?.updatedAt === 'object') { 516 - delete schema.properties.updatedAt.format; 517 - schema.properties.updatedAt.type = 'number'; 518 - } 519 - }, 520 - }, 521 - }, 522 - }); 523 - 524 - expect( 525 - mockSpec.components?.schemas?.ChatAgentSkinItemAdminResponseDto, 526 - ).toEqual({ 527 - properties: { 528 - id: { type: 'string' }, 529 - name: { type: 'string' }, 530 - updatedAt: { 531 - type: 'number', 532 - }, 533 - }, 534 - type: 'object', 535 - }); 536 - }); 537 - 538 - it('should handle adding new properties to schema', () => { 539 - const mockSpec: OpenApi.V3_1_X = { 540 - components: { 541 - schemas: { 542 - UserModel: { 543 - properties: { 544 - id: { type: 'string' }, 545 - }, 546 - type: 'object', 547 - }, 548 - }, 549 - }, 550 - info: { title: 'Test API', version: '1.0.0' }, 551 - openapi: '3.1.0', 552 - }; 553 - 554 - patchSchemas({ 555 - data: mockSpec, 556 - patch: { 557 - schemas: { 558 - UserModel: (schema: any) => { 559 - schema.properties.metadata = { 560 - additionalProperties: true, 561 - type: 'object', 562 - }; 563 - schema.required = ['id']; 564 - }, 565 - }, 566 - }, 567 - }); 568 - 569 - expect(mockSpec.components?.schemas?.UserModel).toEqual({ 570 - properties: { 571 - id: { type: 'string' }, 572 - metadata: { 573 - additionalProperties: true, 574 - type: 'object', 575 - }, 576 - }, 577 - required: ['id'], 578 - type: 'object', 579 - }); 580 - }); 581 - 582 - it('should handle removing properties from schema', () => { 583 - const mockSpec: OpenApi.V3_1_X = { 584 - components: { 585 - schemas: { 586 - UserModel: { 587 - properties: { 588 - id: { type: 'string' }, 589 - internalField: { type: 'string' }, 590 - publicField: { type: 'string' }, 591 - }, 592 - type: 'object', 593 - }, 594 - }, 595 - }, 596 - info: { title: 'Test API', version: '1.0.0' }, 597 - openapi: '3.1.0', 598 - }; 599 - 600 - patchSchemas({ 601 - data: mockSpec, 602 - patch: { 603 - schemas: { 604 - UserModel: (schema: any) => { 605 - delete schema.properties.internalField; 606 - }, 607 - }, 608 - }, 609 - }); 610 - 611 - expect(mockSpec.components?.schemas?.UserModel).toEqual({ 612 - properties: { 613 - id: { type: 'string' }, 614 - publicField: { type: 'string' }, 615 - }, 616 - type: 'object', 617 - }); 618 - }); 619 - }); 620 - });
+5 -7
packages/openapi-ts/src/createClient.ts
··· 6 6 import { getSpec } from './getSpec'; 7 7 import type { IR } from './ir/types'; 8 8 import { parseLegacy, parseOpenApiSpec } from './openApi'; 9 - import { patchSchemas } from './patchSchemas'; 9 + import { patchOpenApiSpec } from './openApi/shared/utils/patch'; 10 10 import { processOutput } from './processOutput'; 11 11 import type { Client } from './types/client'; 12 12 import type { Config } from './types/config'; ··· 213 213 }); 214 214 Performance.end('spec'); 215 215 216 - if (config.input.patch) { 217 - Performance.start('patch'); 218 - patchSchemas({ data, patch: config.input.patch }); 219 - Performance.end('patch'); 220 - } 221 - 222 216 // throw on first run if there's an error to preserve user experience 223 217 // if in watch mode, subsequent errors won't throw to gracefully handle 224 218 // cases where server might be reloading ··· 232 226 let context: IR.Context | undefined; 233 227 234 228 if (data) { 229 + Performance.start('input.patch'); 230 + patchOpenApiSpec({ patchOptions: config.input.patch, spec: data }); 231 + Performance.end('input.patch'); 232 + 235 233 Performance.start('parser'); 236 234 if ( 237 235 config.experimentalParser &&
+6
packages/openapi-ts/src/openApi/2.0.x/index.ts
··· 1 1 export { parseV2_0_X } from './parser'; 2 2 export type { OpenApiV2_0_X } from './types/spec'; 3 + 4 + import type { SchemaObject } from './types/spec'; 5 + 6 + export interface OpenApiV2_0_XTypes { 7 + SchemaObject: SchemaObject; 8 + }
+16
packages/openapi-ts/src/openApi/3.0.x/index.ts
··· 1 1 export { parseV3_0_X } from './parser'; 2 2 export type { OpenApiV3_0_X } from './types/spec'; 3 + 4 + import type { 5 + ParameterObject, 6 + ReferenceObject, 7 + RequestBodyObject, 8 + ResponseObject, 9 + SchemaObject, 10 + } from './types/spec'; 11 + 12 + export interface OpenApiV3_0_XTypes { 13 + ParameterObject: ParameterObject; 14 + ReferenceObject: ReferenceObject; 15 + RequestBodyObject: RequestBodyObject; 16 + ResponseObject: ResponseObject; 17 + SchemaObject: SchemaObject; 18 + }
+16
packages/openapi-ts/src/openApi/3.1.x/index.ts
··· 1 1 export { parseV3_1_X } from './parser'; 2 2 export type { OpenApiV3_1_X } from './types/spec'; 3 + 4 + import type { 5 + ParameterObject, 6 + ReferenceObject, 7 + RequestBodyObject, 8 + ResponseObject, 9 + SchemaObject, 10 + } from './types/spec'; 11 + 12 + export interface OpenApiV3_1_XTypes { 13 + ParameterObject: ParameterObject; 14 + ReferenceObject: ReferenceObject; 15 + RequestBodyObject: RequestBodyObject; 16 + ResponseObject: ResponseObject; 17 + SchemaObject: SchemaObject; 18 + }
+695
packages/openapi-ts/src/openApi/shared/utils/__tests__/patch.test.ts
··· 1 + import { describe, expect, it, vi } from 'vitest'; 2 + 3 + import type { OpenApi } from '../../../../openApi/types'; 4 + import { patchOpenApiSpec } from '../patch'; 5 + 6 + const specMetadataV2: Pick<OpenApi.V2_0_X, 'info' | 'paths' | 'swagger'> = { 7 + info: { 8 + title: 'Test API', 9 + version: '1.0.0', 10 + }, 11 + paths: {}, 12 + swagger: '2.0', 13 + }; 14 + 15 + const specMetadataV3: Pick<OpenApi.V3_1_X, 'info' | 'openapi'> = { 16 + info: { 17 + title: 'Test API', 18 + version: '1.0.0', 19 + }, 20 + openapi: '3.1.0', 21 + }; 22 + 23 + describe('patchOpenApiSpec', () => { 24 + describe('edge cases', () => { 25 + it('does not modify spec', () => { 26 + const spec: OpenApi.V3_1_X = { 27 + ...specMetadataV3, 28 + components: { 29 + schemas: { 30 + Foo: { 31 + type: 'string', 32 + }, 33 + }, 34 + }, 35 + }; 36 + 37 + patchOpenApiSpec({ 38 + patchOptions: undefined, 39 + spec, 40 + }); 41 + 42 + expect(spec).toEqual({ 43 + ...specMetadataV3, 44 + components: { 45 + schemas: { 46 + Foo: { 47 + type: 'string', 48 + }, 49 + }, 50 + }, 51 + }); 52 + }); 53 + 54 + it('does not modify spec', () => { 55 + const spec: OpenApi.V3_1_X = { 56 + ...specMetadataV3, 57 + components: { 58 + schemas: { 59 + Foo: { 60 + type: 'string', 61 + }, 62 + }, 63 + }, 64 + }; 65 + 66 + patchOpenApiSpec({ 67 + patchOptions: {}, 68 + spec, 69 + }); 70 + 71 + expect(spec).toEqual({ 72 + ...specMetadataV3, 73 + components: { 74 + schemas: { 75 + Foo: { 76 + type: 'string', 77 + }, 78 + }, 79 + }, 80 + }); 81 + }); 82 + }); 83 + 84 + describe('OpenAPI v3', () => { 85 + it('calls patch function', () => { 86 + const fnBar = vi.fn(); 87 + const fnFoo = vi.fn(); 88 + 89 + const spec: OpenApi.V3_1_X = { 90 + ...specMetadataV3, 91 + components: { 92 + schemas: { 93 + Bar: { 94 + type: 'object', 95 + }, 96 + Foo: { 97 + type: 'string', 98 + }, 99 + }, 100 + }, 101 + }; 102 + 103 + patchOpenApiSpec({ 104 + patchOptions: { 105 + schemas: { 106 + Bar: fnBar, 107 + Foo: fnFoo, 108 + }, 109 + }, 110 + spec, 111 + }); 112 + 113 + expect(fnBar).toHaveBeenCalledOnce(); 114 + expect(fnBar).toHaveBeenCalledWith({ 115 + type: 'object', 116 + }); 117 + expect(fnFoo).toHaveBeenCalledOnce(); 118 + expect(fnFoo).toHaveBeenCalledWith({ 119 + type: 'string', 120 + }); 121 + }); 122 + 123 + it('patch function mutates spec', () => { 124 + const spec: OpenApi.V3_1_X = { 125 + ...specMetadataV3, 126 + components: { 127 + parameters: { 128 + Foo: { 129 + in: 'path', 130 + name: 'foo', 131 + schema: { 132 + type: 'string', 133 + }, 134 + }, 135 + }, 136 + requestBodies: { 137 + Foo: { 138 + content: { 139 + 'application/json': { 140 + schema: { 141 + type: 'string', 142 + }, 143 + }, 144 + }, 145 + }, 146 + }, 147 + responses: { 148 + Foo: { 149 + $ref: 'foo', 150 + content: { 151 + 'application/json': { 152 + schema: { 153 + type: 'string', 154 + }, 155 + }, 156 + }, 157 + }, 158 + }, 159 + schemas: { 160 + Foo: { 161 + type: 'string', 162 + }, 163 + }, 164 + }, 165 + }; 166 + 167 + patchOpenApiSpec({ 168 + patchOptions: { 169 + parameters: { 170 + Foo: (schema) => { 171 + if ('in' in schema) { 172 + schema.in = 'query'; 173 + if (schema.schema && 'type' in schema.schema) { 174 + schema.schema.type = 'number'; 175 + } 176 + } 177 + }, 178 + }, 179 + requestBodies: { 180 + Foo: (schema) => { 181 + if ('content' in schema) { 182 + if ( 183 + schema.content['application/json'] && 184 + schema.content['application/json'].schema 185 + ) { 186 + if ('type' in schema.content['application/json'].schema) { 187 + schema.content['application/json'].schema.type = 'number'; 188 + } 189 + } 190 + } 191 + }, 192 + }, 193 + responses: { 194 + Foo: (schema) => { 195 + if ('content' in schema) { 196 + if ( 197 + schema.content && 198 + schema.content['application/json'] && 199 + schema.content['application/json'].schema 200 + ) { 201 + if ('type' in schema.content['application/json'].schema) { 202 + schema.content['application/json'].schema.type = 'number'; 203 + } 204 + } 205 + } 206 + }, 207 + }, 208 + schemas: { 209 + Foo: (schema) => { 210 + schema.type = 'number'; 211 + }, 212 + }, 213 + }, 214 + spec, 215 + }); 216 + 217 + expect(spec).toEqual({ 218 + ...specMetadataV3, 219 + components: { 220 + parameters: { 221 + Foo: { 222 + in: 'query', 223 + name: 'foo', 224 + schema: { 225 + type: 'number', 226 + }, 227 + }, 228 + }, 229 + requestBodies: { 230 + Foo: { 231 + content: { 232 + 'application/json': { 233 + schema: { 234 + type: 'number', 235 + }, 236 + }, 237 + }, 238 + }, 239 + }, 240 + responses: { 241 + Foo: { 242 + $ref: 'foo', 243 + content: { 244 + 'application/json': { 245 + schema: { 246 + type: 'number', 247 + }, 248 + }, 249 + }, 250 + }, 251 + }, 252 + schemas: { 253 + Foo: { 254 + type: 'number', 255 + }, 256 + }, 257 + }, 258 + }); 259 + }); 260 + 261 + it('handles spec without components', () => { 262 + const fn = vi.fn(); 263 + 264 + const spec: OpenApi.V3_1_X = { 265 + ...specMetadataV3, 266 + }; 267 + 268 + patchOpenApiSpec({ 269 + patchOptions: { 270 + parameters: { 271 + Foo: fn, 272 + }, 273 + requestBodies: { 274 + Foo: fn, 275 + }, 276 + responses: { 277 + Foo: fn, 278 + }, 279 + schemas: { 280 + Foo: fn, 281 + }, 282 + }, 283 + spec, 284 + }); 285 + 286 + expect(fn).not.toHaveBeenCalled(); 287 + }); 288 + 289 + it('handles spec without component namespaces', () => { 290 + const fn = vi.fn(); 291 + 292 + const spec: OpenApi.V3_1_X = { 293 + ...specMetadataV3, 294 + components: {}, 295 + }; 296 + 297 + patchOpenApiSpec({ 298 + patchOptions: { 299 + parameters: { 300 + Foo: fn, 301 + }, 302 + requestBodies: { 303 + Foo: fn, 304 + }, 305 + responses: { 306 + Foo: fn, 307 + }, 308 + schemas: { 309 + Foo: fn, 310 + }, 311 + }, 312 + spec, 313 + }); 314 + 315 + expect(fn).not.toHaveBeenCalled(); 316 + }); 317 + 318 + it('handles spec without matching components', () => { 319 + const fn = vi.fn(); 320 + 321 + const spec: OpenApi.V3_1_X = { 322 + ...specMetadataV3, 323 + components: { 324 + parameters: {}, 325 + requestBodies: {}, 326 + responses: {}, 327 + schemas: {}, 328 + }, 329 + }; 330 + 331 + patchOpenApiSpec({ 332 + patchOptions: { 333 + parameters: { 334 + Foo: fn, 335 + }, 336 + requestBodies: { 337 + Foo: fn, 338 + }, 339 + responses: { 340 + Foo: fn, 341 + }, 342 + schemas: { 343 + Foo: fn, 344 + }, 345 + }, 346 + spec, 347 + }); 348 + 349 + expect(fn).not.toHaveBeenCalled(); 350 + }); 351 + 352 + it('skips invalid schemas', () => { 353 + const fn = vi.fn(); 354 + 355 + const spec: OpenApi.V3_1_X = { 356 + ...specMetadataV3, 357 + components: { 358 + schemas: { 359 + Bar: 123 as any, 360 + Baz: 'invalid' as any, 361 + Foo: null as any, 362 + Qux: { 363 + type: 'string', 364 + }, 365 + }, 366 + }, 367 + }; 368 + 369 + patchOpenApiSpec({ 370 + patchOptions: { 371 + schemas: { 372 + Bar: fn, 373 + Baz: fn, 374 + Foo: fn, 375 + Qux: fn, 376 + }, 377 + }, 378 + spec, 379 + }); 380 + 381 + expect(fn).toHaveBeenCalledOnce(); 382 + expect(fn).toHaveBeenCalledWith({ 383 + type: 'string', 384 + }); 385 + }); 386 + }); 387 + 388 + describe('OpenAPI v2', () => { 389 + it('calls patch function', () => { 390 + const fnBar = vi.fn(); 391 + const fnFoo = vi.fn(); 392 + 393 + const spec: OpenApi.V2_0_X = { 394 + ...specMetadataV2, 395 + definitions: { 396 + Bar: { 397 + type: 'object', 398 + }, 399 + Foo: { 400 + type: 'string', 401 + }, 402 + }, 403 + }; 404 + 405 + patchOpenApiSpec({ 406 + patchOptions: { 407 + schemas: { 408 + Bar: fnBar, 409 + Foo: fnFoo, 410 + }, 411 + }, 412 + spec, 413 + }); 414 + 415 + expect(fnBar).toHaveBeenCalledOnce(); 416 + expect(fnBar).toHaveBeenCalledWith({ 417 + type: 'object', 418 + }); 419 + expect(fnFoo).toHaveBeenCalledOnce(); 420 + expect(fnFoo).toHaveBeenCalledWith({ 421 + type: 'string', 422 + }); 423 + }); 424 + 425 + it('patch function mutates schema', () => { 426 + const spec: OpenApi.V2_0_X = { 427 + ...specMetadataV2, 428 + definitions: { 429 + Foo: { 430 + type: 'string', 431 + }, 432 + }, 433 + }; 434 + 435 + patchOpenApiSpec({ 436 + patchOptions: { 437 + schemas: { 438 + Foo: (schema) => { 439 + schema.type = 'number'; 440 + }, 441 + }, 442 + }, 443 + spec, 444 + }); 445 + 446 + expect(spec).toEqual({ 447 + ...specMetadataV2, 448 + definitions: { 449 + Foo: { 450 + type: 'number', 451 + }, 452 + }, 453 + }); 454 + }); 455 + 456 + it('handles spec without definitions', () => { 457 + const fn = vi.fn(); 458 + 459 + const spec: OpenApi.V2_0_X = { 460 + ...specMetadataV2, 461 + }; 462 + 463 + patchOpenApiSpec({ 464 + patchOptions: { 465 + parameters: { 466 + Foo: fn, 467 + }, 468 + requestBodies: { 469 + Foo: fn, 470 + }, 471 + responses: { 472 + Foo: fn, 473 + }, 474 + schemas: { 475 + Foo: fn, 476 + }, 477 + }, 478 + spec, 479 + }); 480 + 481 + expect(fn).not.toHaveBeenCalled(); 482 + }); 483 + 484 + it('handles spec without matching definitions', () => { 485 + const fn = vi.fn(); 486 + 487 + const spec: OpenApi.V2_0_X = { 488 + ...specMetadataV2, 489 + definitions: {}, 490 + }; 491 + 492 + patchOpenApiSpec({ 493 + patchOptions: { 494 + parameters: { 495 + Foo: fn, 496 + }, 497 + requestBodies: { 498 + Foo: fn, 499 + }, 500 + responses: { 501 + Foo: fn, 502 + }, 503 + schemas: { 504 + Foo: fn, 505 + }, 506 + }, 507 + spec, 508 + }); 509 + 510 + expect(fn).not.toHaveBeenCalled(); 511 + }); 512 + 513 + it('skips invalid schemas', () => { 514 + const fn = vi.fn(); 515 + 516 + const spec: OpenApi.V2_0_X = { 517 + ...specMetadataV2, 518 + definitions: { 519 + Bar: 123 as any, 520 + Baz: 'invalid' as any, 521 + Foo: null as any, 522 + Qux: { 523 + type: 'string', 524 + }, 525 + }, 526 + }; 527 + 528 + patchOpenApiSpec({ 529 + patchOptions: { 530 + schemas: { 531 + Bar: fn, 532 + Baz: fn, 533 + Foo: fn, 534 + Qux: fn, 535 + }, 536 + }, 537 + spec, 538 + }); 539 + 540 + expect(fn).toHaveBeenCalledOnce(); 541 + expect(fn).toHaveBeenCalledWith({ 542 + type: 'string', 543 + }); 544 + }); 545 + }); 546 + 547 + describe('real-world usage', () => { 548 + it('handles complex schema example from docs', () => { 549 + const spec: OpenApi.V3_1_X = { 550 + ...specMetadataV3, 551 + components: { 552 + schemas: { 553 + Foo: { 554 + properties: { 555 + id: { type: 'string' }, 556 + name: { type: 'string' }, 557 + updatedAt: { 558 + format: 'date-time', 559 + type: 'string', 560 + }, 561 + }, 562 + type: 'object', 563 + }, 564 + }, 565 + }, 566 + }; 567 + 568 + patchOpenApiSpec({ 569 + patchOptions: { 570 + schemas: { 571 + Foo: (schema: any) => { 572 + if (typeof schema.properties?.updatedAt === 'object') { 573 + delete schema.properties.updatedAt.format; 574 + schema.properties.updatedAt.type = 'number'; 575 + } 576 + }, 577 + }, 578 + }, 579 + spec, 580 + }); 581 + 582 + expect(spec).toEqual({ 583 + ...specMetadataV3, 584 + components: { 585 + schemas: { 586 + Foo: { 587 + properties: { 588 + id: { type: 'string' }, 589 + name: { type: 'string' }, 590 + updatedAt: { 591 + type: 'number', 592 + }, 593 + }, 594 + type: 'object', 595 + }, 596 + }, 597 + }, 598 + }); 599 + }); 600 + 601 + it('handles adding new schema properties', () => { 602 + const spec: OpenApi.V3_1_X = { 603 + ...specMetadataV3, 604 + components: { 605 + schemas: { 606 + Foo: { 607 + properties: { 608 + id: { type: 'string' }, 609 + }, 610 + type: 'object', 611 + }, 612 + }, 613 + }, 614 + }; 615 + 616 + patchOpenApiSpec({ 617 + patchOptions: { 618 + schemas: { 619 + Foo: (schema: any) => { 620 + schema.properties.meta = { 621 + additionalProperties: true, 622 + type: 'object', 623 + }; 624 + schema.required = ['meta']; 625 + }, 626 + }, 627 + }, 628 + spec, 629 + }); 630 + 631 + expect(spec).toEqual({ 632 + ...specMetadataV3, 633 + components: { 634 + schemas: { 635 + Foo: { 636 + properties: { 637 + id: { type: 'string' }, 638 + meta: { 639 + additionalProperties: true, 640 + type: 'object', 641 + }, 642 + }, 643 + required: ['meta'], 644 + type: 'object', 645 + }, 646 + }, 647 + }, 648 + }); 649 + }); 650 + 651 + it('handles removing schema properties', () => { 652 + const spec: OpenApi.V3_1_X = { 653 + ...specMetadataV3, 654 + components: { 655 + schemas: { 656 + Foo: { 657 + properties: { 658 + id: { type: 'string' }, 659 + internalField: { type: 'string' }, 660 + publicField: { type: 'string' }, 661 + }, 662 + type: 'object', 663 + }, 664 + }, 665 + }, 666 + }; 667 + 668 + patchOpenApiSpec({ 669 + patchOptions: { 670 + schemas: { 671 + Foo: (schema: any) => { 672 + delete schema.properties.internalField; 673 + }, 674 + }, 675 + }, 676 + spec, 677 + }); 678 + 679 + expect(spec).toEqual({ 680 + ...specMetadataV3, 681 + components: { 682 + schemas: { 683 + Foo: { 684 + properties: { 685 + id: { type: 'string' }, 686 + publicField: { type: 'string' }, 687 + }, 688 + type: 'object', 689 + }, 690 + }, 691 + }, 692 + }); 693 + }); 694 + }); 695 + });
+71
packages/openapi-ts/src/openApi/shared/utils/patch.ts
··· 1 + import type { Patch } from '../../../types/input'; 2 + import type { OpenApi } from '../../types'; 3 + 4 + export const patchOpenApiSpec = ({ 5 + patchOptions, 6 + spec: _spec, 7 + }: { 8 + patchOptions: Patch | undefined; 9 + spec: unknown; 10 + }) => { 11 + if (!patchOptions) { 12 + return; 13 + } 14 + 15 + const spec = _spec as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X; 16 + 17 + if ('swagger' in spec) { 18 + if (spec.definitions && patchOptions?.schemas) { 19 + for (const key in patchOptions.schemas) { 20 + const patchFn = patchOptions.schemas[key]!; 21 + const schema = spec.definitions[key]; 22 + if (schema && typeof schema === 'object') { 23 + patchFn(schema); 24 + } 25 + } 26 + } 27 + return; 28 + } 29 + 30 + if (spec.components) { 31 + if (spec.components.parameters && patchOptions.parameters) { 32 + for (const key in patchOptions.parameters) { 33 + const patchFn = patchOptions.parameters[key]!; 34 + const schema = spec.components.parameters[key]; 35 + if (schema && typeof schema === 'object') { 36 + patchFn(schema); 37 + } 38 + } 39 + } 40 + 41 + if (spec.components.requestBodies && patchOptions.requestBodies) { 42 + for (const key in patchOptions.requestBodies) { 43 + const patchFn = patchOptions.requestBodies[key]!; 44 + const schema = spec.components.requestBodies[key]; 45 + if (schema && typeof schema === 'object') { 46 + patchFn(schema); 47 + } 48 + } 49 + } 50 + 51 + if (spec.components.responses && patchOptions.responses) { 52 + for (const key in patchOptions.responses) { 53 + const patchFn = patchOptions.responses[key]!; 54 + const schema = spec.components.responses[key]; 55 + if (schema && typeof schema === 'object') { 56 + patchFn(schema); 57 + } 58 + } 59 + } 60 + 61 + if (spec.components.schemas && patchOptions?.schemas) { 62 + for (const key in patchOptions.schemas) { 63 + const patchFn = patchOptions.schemas[key]!; 64 + const schema = spec.components.schemas[key]; 65 + if (schema && typeof schema === 'object') { 66 + patchFn(schema); 67 + } 68 + } 69 + } 70 + } 71 + };
+36 -9
packages/openapi-ts/src/openApi/types.d.ts
··· 1 - import type { OpenApiV2_0_X } from './2.0.x'; 2 - import type { SchemaObject as OpenApiV2SchemaObject } from './2.0.x/types/spec'; 3 - import type { OpenApiV3_0_X } from './3.0.x'; 4 - import type { SchemaObject as OpenApiV3SchemaObject } from './3.0.x/types/spec'; 5 - import type { OpenApiV3_1_X } from './3.1.x'; 6 - import type { SchemaObject as OpenApiV3_1SchemaObject } from './3.1.x/types/spec'; 1 + import type { OpenApiV2_0_X, OpenApiV2_0_XTypes } from './2.0.x'; 2 + import type { OpenApiV3_0_X, OpenApiV3_0_XTypes } from './3.0.x'; 3 + import type { OpenApiV3_1_X, OpenApiV3_1_XTypes } from './3.1.x'; 7 4 8 5 export namespace OpenApi { 9 6 export type V2_0_X = OpenApiV2_0_X; ··· 13 10 export type V3_1_X = OpenApiV3_1_X; 14 11 } 15 12 13 + export namespace OpenApiParameterObject { 14 + export type V3_0_X = 15 + | OpenApiV3_0_XTypes['ParameterObject'] 16 + | OpenApiV3_0_XTypes['ReferenceObject']; 17 + 18 + export type V3_1_X = 19 + | OpenApiV3_1_XTypes['ParameterObject'] 20 + | OpenApiV3_1_XTypes['ReferenceObject']; 21 + } 22 + 23 + export namespace OpenApiRequestBodyObject { 24 + export type V3_0_X = 25 + | OpenApiV3_0_XTypes['RequestBodyObject'] 26 + | OpenApiV3_0_XTypes['ReferenceObject']; 27 + 28 + export type V3_1_X = 29 + | OpenApiV3_1_XTypes['RequestBodyObject'] 30 + | OpenApiV3_1_XTypes['ReferenceObject']; 31 + } 32 + 33 + export namespace OpenApiResponseObject { 34 + export type V3_0_X = 35 + | OpenApiV3_0_XTypes['ResponseObject'] 36 + | OpenApiV3_0_XTypes['ReferenceObject']; 37 + 38 + export type V3_1_X = 39 + | OpenApiV3_1_XTypes['ResponseObject'] 40 + | OpenApiV3_1_XTypes['ReferenceObject']; 41 + } 42 + 16 43 export namespace OpenApiSchemaObject { 17 - export type V2_0_X = OpenApiV2SchemaObject; 44 + export type V2_0_X = OpenApiV2_0_XTypes['SchemaObject']; 18 45 19 - export type V3_0_X = OpenApiV3SchemaObject; 46 + export type V3_0_X = OpenApiV3_0_XTypes['SchemaObject']; 20 47 21 - export type V3_1_X = OpenApiV3_1SchemaObject; 48 + export type V3_1_X = OpenApiV3_1_XTypes['SchemaObject']; 22 49 }
-50
packages/openapi-ts/src/patchSchemas.ts
··· 1 - import type { OpenApi } from './openApi/types'; 2 - import type { Config } from './types/config'; 3 - 4 - interface PatchSchemasArgs { 5 - data: unknown; 6 - patch?: Config['input']['patch']; 7 - } 8 - 9 - export const patchSchemas = ({ data, patch }: PatchSchemasArgs) => { 10 - if (!data || !patch?.schemas) { 11 - return; 12 - } 13 - 14 - const spec = data as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X; 15 - const schemas = patch.schemas; 16 - 17 - const processSchemas = (targets: Record<string, any>) => { 18 - for (const schemaName in schemas) { 19 - const fixFn = schemas[schemaName]; 20 - 21 - if (!fixFn) { 22 - continue; 23 - } 24 - 25 - const target = targets[schemaName]; 26 - 27 - if (!target || typeof target !== 'object') { 28 - continue; 29 - } 30 - 31 - fixFn(target); 32 - } 33 - }; 34 - 35 - if ('swagger' in spec) { 36 - const { definitions } = spec; 37 - 38 - if (definitions) { 39 - processSchemas(definitions); 40 - } 41 - 42 - return; 43 - } 44 - 45 - const _schemas = spec.components?.schemas; 46 - 47 - if (_schemas) { 48 - processSchemas(_schemas); 49 - } 50 - };
+26 -23
packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts
··· 1 1 import { compiler } from '../../../compiler'; 2 2 import type { IR } from '../../../ir/types'; 3 - import type { SchemaObject as OpenApiV2_0_XSchemaObject } from '../../../openApi/2.0.x/types/spec'; 4 - import type { 5 - ReferenceObject as OpenApiV3_0_XReferenceObject, 6 - SchemaObject as OpenApiV3_0_XSchemaObject, 7 - } from '../../../openApi/3.0.x/types/spec'; 8 - import type { SchemaObject as OpenApiV3_1_XSchemaObject } from '../../../openApi/3.1.x/types/spec'; 3 + import type { OpenApiV2_0_XTypes } from '../../../openApi/2.0.x'; 4 + import type { OpenApiV3_0_XTypes } from '../../../openApi/3.0.x'; 5 + import type { OpenApiV3_1_XTypes } from '../../../openApi/3.1.x'; 9 6 import { ensureValidIdentifier } from '../../../openApi/shared/utils/identifier'; 10 7 import type { OpenApi } from '../../../openApi/types'; 11 8 import type { Plugin } from '../../types'; ··· 19 16 }: { 20 17 plugin: Plugin.Instance<Config>; 21 18 schema: 22 - | OpenApiV2_0_XSchemaObject 23 - | OpenApiV3_0_XSchemaObject 24 - | OpenApiV3_1_XSchemaObject; 19 + | OpenApiV2_0_XTypes['SchemaObject'] 20 + | OpenApiV3_0_XTypes['SchemaObject'] 21 + | OpenApiV3_1_XTypes['SchemaObject']; 25 22 }) => { 26 23 if (plugin.type === 'form') { 27 24 if (schema.description) { ··· 53 50 }: { 54 51 context: IR.Context; 55 52 plugin: Plugin.Instance<Config>; 56 - schema: OpenApiV2_0_XSchemaObject; 57 - }): OpenApiV2_0_XSchemaObject => { 53 + schema: OpenApiV2_0_XTypes['SchemaObject']; 54 + }): OpenApiV2_0_XTypes['SchemaObject'] => { 58 55 if (Array.isArray(_schema)) { 59 56 return _schema.map((item) => 60 57 schemaToJsonSchemaDraft_04({ ··· 62 59 plugin, 63 60 schema: item, 64 61 }), 65 - ) as unknown as OpenApiV2_0_XSchemaObject; 62 + ) as unknown as OpenApiV2_0_XTypes['SchemaObject']; 66 63 } 67 64 68 65 const schema = structuredClone(_schema); ··· 101 98 schema.items = schemaToJsonSchemaDraft_04({ 102 99 context, 103 100 plugin, 104 - schema: schema.items as OpenApiV2_0_XSchemaObject, 101 + schema: schema.items as OpenApiV2_0_XTypes['SchemaObject'], 105 102 }); 106 103 } 107 104 ··· 129 126 }: { 130 127 context: IR.Context; 131 128 plugin: Plugin.Instance<Config>; 132 - schema: OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject; 133 - }): OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject => { 129 + schema: 130 + | OpenApiV3_0_XTypes['SchemaObject'] 131 + | OpenApiV3_0_XTypes['ReferenceObject']; 132 + }): 133 + | OpenApiV3_0_XTypes['SchemaObject'] 134 + | OpenApiV3_0_XTypes['ReferenceObject'] => { 134 135 if (Array.isArray(_schema)) { 135 136 return _schema.map((item) => 136 137 schemaToJsonSchemaDraft_05({ ··· 138 139 plugin, 139 140 schema: item, 140 141 }), 141 - ) as OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject; 142 + ) as 143 + | OpenApiV3_0_XTypes['SchemaObject'] 144 + | OpenApiV3_0_XTypes['ReferenceObject']; 142 145 } 143 146 144 147 const schema = structuredClone(_schema); ··· 225 228 }: { 226 229 context: IR.Context; 227 230 plugin: Plugin.Instance<Config>; 228 - schema: OpenApiV3_1_XSchemaObject; 229 - }): OpenApiV3_1_XSchemaObject => { 231 + schema: OpenApiV3_1_XTypes['SchemaObject']; 232 + }): OpenApiV3_1_XTypes['SchemaObject'] => { 230 233 if (Array.isArray(_schema)) { 231 234 return _schema.map((item) => 232 235 schemaToJsonSchema2020_12({ ··· 234 237 plugin, 235 238 schema: item, 236 239 }), 237 - ) as OpenApiV3_1_XSchemaObject; 240 + ) as OpenApiV3_1_XTypes['SchemaObject']; 238 241 } 239 242 240 243 const schema = structuredClone(_schema); ··· 331 334 name: string; 332 335 plugin: Plugin.Instance<Config>; 333 336 schema: 334 - | OpenApiV2_0_XSchemaObject 335 - | OpenApiV3_0_XReferenceObject 336 - | OpenApiV3_0_XSchemaObject 337 - | OpenApiV3_1_XSchemaObject; 337 + | OpenApiV2_0_XTypes['SchemaObject'] 338 + | OpenApiV3_0_XTypes['ReferenceObject'] 339 + | OpenApiV3_0_XTypes['SchemaObject'] 340 + | OpenApiV3_1_XTypes['SchemaObject']; 338 341 }): string => { 339 342 let customName = ''; 340 343
+7 -10
packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts
··· 1 1 import type { OpenApiV2Schema, OpenApiV3Schema } from '../../../openApi'; 2 - import type { SchemaObject as OpenApiV2_0_XSchemaObject } from '../../../openApi/2.0.x/types/spec'; 3 - import type { 4 - ReferenceObject as OpenApiV3_0_XReferenceObject, 5 - SchemaObject as OpenApiV3_0_XSchemaObject, 6 - } from '../../../openApi/3.0.x/types/spec'; 7 - import type { SchemaObject as OpenApiV3_1_XSchemaObject } from '../../../openApi/3.1.x/types/spec'; 2 + import type { OpenApiV2_0_XTypes } from '../../../openApi/2.0.x'; 3 + import type { OpenApiV3_0_XTypes } from '../../../openApi/3.0.x'; 4 + import type { OpenApiV3_1_XTypes } from '../../../openApi/3.1.x'; 8 5 import type { Plugin } from '../../types'; 9 6 10 7 export interface Config extends Plugin.Name<'@hey-api/schemas'> { ··· 29 26 schema: 30 27 | OpenApiV2Schema 31 28 | OpenApiV3Schema 32 - | OpenApiV2_0_XSchemaObject 33 - | OpenApiV3_0_XReferenceObject 34 - | OpenApiV3_0_XSchemaObject 35 - | OpenApiV3_1_XSchemaObject, 29 + | OpenApiV2_0_XTypes['SchemaObject'] 30 + | OpenApiV3_0_XTypes['ReferenceObject'] 31 + | OpenApiV3_0_XTypes['SchemaObject'] 32 + | OpenApiV3_1_XTypes['SchemaObject'], 36 33 ) => string); 37 34 /** 38 35 * Name of the generated file.
+1 -304
packages/openapi-ts/src/types/config.d.ts
··· 1 - import type { OpenApiSchemaObject } from '../openApi/types'; 2 1 import type { ClientPlugins, UserPlugins } from '../plugins'; 2 + import type { Input, Watch } from './input'; 3 3 import type { ArrayOfObjectsToObjectMap, ExtractArrayOfObjects } from './utils'; 4 4 5 5 export type Formatters = 'biome' | 'prettier'; ··· 12 12 | 'preserve' 13 13 | 'snake_case' 14 14 | 'SCREAMING_SNAKE_CASE'; 15 - 16 - interface Watch { 17 - /** 18 - * Regenerate the client when the input file changes? 19 - * 20 - * @default false 21 - */ 22 - enabled?: boolean; 23 - /** 24 - * How often should we attempt to detect the input file change? (in ms) 25 - * 26 - * @default 1000 27 - */ 28 - interval?: number; 29 - /** 30 - * How long will we wait before the request times out? 31 - * 32 - * @default 60_000 33 - */ 34 - timeout?: number; 35 - } 36 - 37 - interface Input { 38 - /** 39 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 40 - * 41 - * Projects are private by default, you will need to be authenticated 42 - * to download OpenAPI specifications. We recommend using project API 43 - * keys in CI workflows and personal API keys for local development. 44 - * 45 - * API key isn't required for public projects. You can also omit this 46 - * parameter and provide an environment variable `HEY_API_TOKEN`. 47 - */ 48 - api_key?: string; 49 - /** 50 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 51 - * 52 - * You can fetch the last build from branch by providing the branch 53 - * name. 54 - */ 55 - branch?: string; 56 - /** 57 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 58 - * 59 - * You can fetch an exact specification by providing a commit sha. 60 - * This will always return the same file. 61 - */ 62 - commit_sha?: string; 63 - /** 64 - * You pass any valid Fetch API options to the request for fetching your 65 - * specification. This is useful if your file is behind auth for example. 66 - */ 67 - fetch?: RequestInit; 68 - /** 69 - * Filters can be used to select a subset of your input before it's processed 70 - * by plugins. 71 - */ 72 - filters?: { 73 - /** 74 - * Include deprecated resources in the output? 75 - * 76 - * @default true 77 - */ 78 - deprecated?: boolean; 79 - operations?: { 80 - /** 81 - * Prevent operations matching the `exclude` filters from being processed. 82 - * 83 - * In case of conflicts, `exclude` takes precedence over `include`. 84 - * 85 - * @example ['GET /api/v1/foo'] 86 - */ 87 - exclude?: ReadonlyArray<string>; 88 - /** 89 - * Process only operations matching the `include` filters. 90 - * 91 - * In case of conflicts, `exclude` takes precedence over `include`. 92 - * 93 - * @example ['GET /api/v1/foo'] 94 - */ 95 - include?: ReadonlyArray<string>; 96 - }; 97 - /** 98 - * Keep reusable components without any references in the output? By 99 - * default, we exclude orphaned resources. 100 - * 101 - * @default false 102 - */ 103 - orphans?: boolean; 104 - parameters?: { 105 - /** 106 - * Prevent parameters matching the `exclude` filters from being processed. 107 - * 108 - * In case of conflicts, `exclude` takes precedence over `include`. 109 - * 110 - * @example ['QueryParam'] 111 - */ 112 - exclude?: ReadonlyArray<string>; 113 - /** 114 - * Process only parameters matching the `include` filters. 115 - * 116 - * In case of conflicts, `exclude` takes precedence over `include`. 117 - * 118 - * @example ['QueryParam'] 119 - */ 120 - include?: ReadonlyArray<string>; 121 - }; 122 - /** 123 - * Should we preserve the key order when overwriting your input? This 124 - * option is disabled by default to improve performance. 125 - * 126 - * @default false 127 - */ 128 - preserveOrder?: boolean; 129 - requestBodies?: { 130 - /** 131 - * Prevent request bodies matching the `exclude` filters from being processed. 132 - * 133 - * In case of conflicts, `exclude` takes precedence over `include`. 134 - * 135 - * @example ['Foo'] 136 - */ 137 - exclude?: ReadonlyArray<string>; 138 - /** 139 - * Process only request bodies matching the `include` filters. 140 - * 141 - * In case of conflicts, `exclude` takes precedence over `include`. 142 - * 143 - * @example ['Foo'] 144 - */ 145 - include?: ReadonlyArray<string>; 146 - }; 147 - responses?: { 148 - /** 149 - * Prevent responses matching the `exclude` filters from being processed. 150 - * 151 - * In case of conflicts, `exclude` takes precedence over `include`. 152 - * 153 - * @example ['Foo'] 154 - */ 155 - exclude?: ReadonlyArray<string>; 156 - /** 157 - * Process only responses matching the `include` filters. 158 - * 159 - * In case of conflicts, `exclude` takes precedence over `include`. 160 - * 161 - * @example ['Foo'] 162 - */ 163 - include?: ReadonlyArray<string>; 164 - }; 165 - schemas?: { 166 - /** 167 - * Prevent schemas matching the `exclude` filters from being processed. 168 - * 169 - * In case of conflicts, `exclude` takes precedence over `include`. 170 - * 171 - * @example ['Foo'] 172 - */ 173 - exclude?: ReadonlyArray<string>; 174 - /** 175 - * Process only schemas matching the `include` filters. 176 - * 177 - * In case of conflicts, `exclude` takes precedence over `include`. 178 - * 179 - * @example ['Foo'] 180 - */ 181 - include?: ReadonlyArray<string>; 182 - }; 183 - tags?: { 184 - /** 185 - * Prevent tags matching the `exclude` filters from being processed. 186 - * 187 - * In case of conflicts, `exclude` takes precedence over `include`. 188 - * 189 - * @example ['foo'] 190 - */ 191 - exclude?: ReadonlyArray<string>; 192 - /** 193 - * Process only tags matching the `include` filters. 194 - * 195 - * In case of conflicts, `exclude` takes precedence over `include`. 196 - * 197 - * @example ['foo'] 198 - */ 199 - include?: ReadonlyArray<string>; 200 - }; 201 - }; 202 - /** 203 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 204 - * 205 - * Organization created in Hey API platform. 206 - */ 207 - organization?: string; 208 - /** 209 - * Pagination configuration 210 - */ 211 - pagination?: { 212 - /** 213 - * Array of keywords to be considered as pagination field names. 214 - * These will be used to detect pagination fields in schemas and parameters. 215 - * 216 - * @default ['after', 'before', 'cursor', 'offset', 'page', 'start'] 217 - */ 218 - keywords?: ReadonlyArray<string>; 219 - }; 220 - patch?: { 221 - /** 222 - * Apply custom transformations to schemas after parsing the OpenAPI specification. 223 - * This allows you to modify, fix, or enhance schema definitions before code generation. 224 - * 225 - * Each function receives the schema object and can modify it in place. Common use cases 226 - * include fixing incorrect data types, removing unwanted properties, adding missing 227 - * fields, or standardizing date/time formats. 228 - * 229 - * Works with both OpenAPI v2 (Swagger) and v3.x specifications, automatically 230 - * detecting the correct schema location (`definitions` for v2, `components.schemas` for v3). 231 - * 232 - * @example 233 - * ```js 234 - * patch: { 235 - * schemas: { 236 - * // Fix date-time format issues 237 - * 'UserResponseDto': (schema) => { 238 - * if (schema.properties?.updatedAt) { 239 - * delete schema.properties.updatedAt.format; 240 - * schema.properties.updatedAt.type = 'number'; 241 - * } 242 - * }, 243 - * // Add missing required fields 244 - * 'ProductModel': (schema) => { 245 - * schema.required = ['id', 'name']; 246 - * schema.properties.metadata = { 247 - * type: 'object', 248 - * additionalProperties: true 249 - * }; 250 - * }, 251 - * // Remove internal fields 252 - * 'PublicApiModel': (schema) => { 253 - * delete schema.properties.internalId; 254 - * delete schema.properties.secretKey; 255 - * } 256 - * } 257 - * } 258 - * ``` 259 - */ 260 - schemas?: Record< 261 - string, 262 - ( 263 - schema: 264 - | OpenApiSchemaObject.V2_0_X 265 - | OpenApiSchemaObject.V3_0_X 266 - | OpenApiSchemaObject.V3_1_X, 267 - ) => void 268 - >; 269 - }; 270 - /** 271 - * Path to the OpenAPI specification. This can be either local or remote path. 272 - * Both JSON and YAML file formats are supported. You can also pass the parsed 273 - * object directly if you're fetching the file yourself. 274 - */ 275 - path?: 276 - | 'https://get.heyapi.dev/<organization>/<project>' 277 - | (string & {}) 278 - | Record<string, unknown>; 279 - /** 280 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 281 - * 282 - * Project created in Hey API platform. 283 - */ 284 - project?: string; 285 - /** 286 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 287 - * 288 - * If you're tagging your specifications with custom tags, you can use 289 - * them to filter the results. When you provide multiple tags, only 290 - * the first match will be returned. 291 - */ 292 - tags?: ReadonlyArray<string>; 293 - /** 294 - * **This is an experimental feature.** 295 - * 296 - * Validate the input before generating output? This is an experimental, 297 - * lightweight feature and support will be added on an ad hoc basis. 298 - * 299 - * @default false 300 - */ 301 - validate_EXPERIMENTAL?: boolean; 302 - /** 303 - * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 304 - * 305 - * Every OpenAPI document contains a required version field. You can 306 - * use this value to fetch the last uploaded specification matching 307 - * the value. 308 - */ 309 - version?: string; 310 - /** 311 - * Regenerate the client when the input file changes? You can alternatively 312 - * pass a numeric value for the interval in ms. 313 - * 314 - * @default false 315 - */ 316 - watch?: boolean | number | Watch; 317 - } 318 15 319 16 export interface UserConfig { 320 17 /**
+326
packages/openapi-ts/src/types/input.d.ts
··· 1 + import type { 2 + OpenApiParameterObject, 3 + OpenApiRequestBodyObject, 4 + OpenApiResponseObject, 5 + OpenApiSchemaObject, 6 + } from '../openApi/types'; 7 + 8 + export interface Input { 9 + /** 10 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 11 + * 12 + * Projects are private by default, you will need to be authenticated 13 + * to download OpenAPI specifications. We recommend using project API 14 + * keys in CI workflows and personal API keys for local development. 15 + * 16 + * API key isn't required for public projects. You can also omit this 17 + * parameter and provide an environment variable `HEY_API_TOKEN`. 18 + */ 19 + api_key?: string; 20 + /** 21 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 22 + * 23 + * You can fetch the last build from branch by providing the branch 24 + * name. 25 + */ 26 + branch?: string; 27 + /** 28 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 29 + * 30 + * You can fetch an exact specification by providing a commit sha. 31 + * This will always return the same file. 32 + */ 33 + commit_sha?: string; 34 + /** 35 + * You pass any valid Fetch API options to the request for fetching your 36 + * specification. This is useful if your file is behind auth for example. 37 + */ 38 + fetch?: RequestInit; 39 + /** 40 + * Filters can be used to select a subset of your input before it's processed 41 + * by plugins. 42 + */ 43 + filters?: Filters; 44 + /** 45 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 46 + * 47 + * Organization created in Hey API platform. 48 + */ 49 + organization?: string; 50 + /** 51 + * Pagination configuration. 52 + */ 53 + pagination?: { 54 + /** 55 + * Array of keywords to be considered as pagination field names. 56 + * These will be used to detect pagination fields in schemas and parameters. 57 + * 58 + * @default ['after', 'before', 'cursor', 'offset', 'page', 'start'] 59 + */ 60 + keywords?: ReadonlyArray<string>; 61 + }; 62 + /** 63 + * Custom input transformations to execute before parsing. This allows you 64 + * to modify, fix, or enhance input definitions before code generation. 65 + */ 66 + patch?: Patch; 67 + /** 68 + * Path to the OpenAPI specification. This can be either local or remote path. 69 + * Both JSON and YAML file formats are supported. You can also pass the parsed 70 + * object directly if you're fetching the file yourself. 71 + */ 72 + path?: 73 + | 'https://get.heyapi.dev/<organization>/<project>' 74 + | (string & {}) 75 + | Record<string, unknown>; 76 + /** 77 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 78 + * 79 + * Project created in Hey API platform. 80 + */ 81 + project?: string; 82 + /** 83 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 84 + * 85 + * If you're tagging your specifications with custom tags, you can use 86 + * them to filter the results. When you provide multiple tags, only 87 + * the first match will be returned. 88 + */ 89 + tags?: ReadonlyArray<string>; 90 + /** 91 + * **This is an experimental feature.** 92 + * 93 + * Validate the input before generating output? This is an experimental, 94 + * lightweight feature and support will be added on an ad hoc basis. 95 + * 96 + * @default false 97 + */ 98 + validate_EXPERIMENTAL?: boolean; 99 + /** 100 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 101 + * 102 + * Every OpenAPI document contains a required version field. You can 103 + * use this value to fetch the last uploaded specification matching 104 + * the value. 105 + */ 106 + version?: string; 107 + /** 108 + * Regenerate the client when the input file changes? You can alternatively 109 + * pass a numeric value for the interval in ms. 110 + * 111 + * @default false 112 + */ 113 + watch?: boolean | number | Watch; 114 + } 115 + 116 + export interface Filters { 117 + /** 118 + * Include deprecated resources in the output? 119 + * 120 + * @default true 121 + */ 122 + deprecated?: boolean; 123 + operations?: { 124 + /** 125 + * Prevent operations matching the `exclude` filters from being processed. 126 + * 127 + * In case of conflicts, `exclude` takes precedence over `include`. 128 + * 129 + * @example ['GET /api/v1/foo'] 130 + */ 131 + exclude?: ReadonlyArray<string>; 132 + /** 133 + * Process only operations matching the `include` filters. 134 + * 135 + * In case of conflicts, `exclude` takes precedence over `include`. 136 + * 137 + * @example ['GET /api/v1/foo'] 138 + */ 139 + include?: ReadonlyArray<string>; 140 + }; 141 + /** 142 + * Keep reusable components without any references in the output? By 143 + * default, we exclude orphaned resources. 144 + * 145 + * @default false 146 + */ 147 + orphans?: boolean; 148 + parameters?: { 149 + /** 150 + * Prevent parameters matching the `exclude` filters from being processed. 151 + * 152 + * In case of conflicts, `exclude` takes precedence over `include`. 153 + * 154 + * @example ['QueryParam'] 155 + */ 156 + exclude?: ReadonlyArray<string>; 157 + /** 158 + * Process only parameters matching the `include` filters. 159 + * 160 + * In case of conflicts, `exclude` takes precedence over `include`. 161 + * 162 + * @example ['QueryParam'] 163 + */ 164 + include?: ReadonlyArray<string>; 165 + }; 166 + /** 167 + * Should we preserve the key order when overwriting your input? This 168 + * option is disabled by default to improve performance. 169 + * 170 + * @default false 171 + */ 172 + preserveOrder?: boolean; 173 + requestBodies?: { 174 + /** 175 + * Prevent request bodies matching the `exclude` filters from being processed. 176 + * 177 + * In case of conflicts, `exclude` takes precedence over `include`. 178 + * 179 + * @example ['Foo'] 180 + */ 181 + exclude?: ReadonlyArray<string>; 182 + /** 183 + * Process only request bodies matching the `include` filters. 184 + * 185 + * In case of conflicts, `exclude` takes precedence over `include`. 186 + * 187 + * @example ['Foo'] 188 + */ 189 + include?: ReadonlyArray<string>; 190 + }; 191 + responses?: { 192 + /** 193 + * Prevent responses matching the `exclude` filters from being processed. 194 + * 195 + * In case of conflicts, `exclude` takes precedence over `include`. 196 + * 197 + * @example ['Foo'] 198 + */ 199 + exclude?: ReadonlyArray<string>; 200 + /** 201 + * Process only responses matching the `include` filters. 202 + * 203 + * In case of conflicts, `exclude` takes precedence over `include`. 204 + * 205 + * @example ['Foo'] 206 + */ 207 + include?: ReadonlyArray<string>; 208 + }; 209 + schemas?: { 210 + /** 211 + * Prevent schemas matching the `exclude` filters from being processed. 212 + * 213 + * In case of conflicts, `exclude` takes precedence over `include`. 214 + * 215 + * @example ['Foo'] 216 + */ 217 + exclude?: ReadonlyArray<string>; 218 + /** 219 + * Process only schemas matching the `include` filters. 220 + * 221 + * In case of conflicts, `exclude` takes precedence over `include`. 222 + * 223 + * @example ['Foo'] 224 + */ 225 + include?: ReadonlyArray<string>; 226 + }; 227 + tags?: { 228 + /** 229 + * Prevent tags matching the `exclude` filters from being processed. 230 + * 231 + * In case of conflicts, `exclude` takes precedence over `include`. 232 + * 233 + * @example ['foo'] 234 + */ 235 + exclude?: ReadonlyArray<string>; 236 + /** 237 + * Process only tags matching the `include` filters. 238 + * 239 + * In case of conflicts, `exclude` takes precedence over `include`. 240 + * 241 + * @example ['foo'] 242 + */ 243 + include?: ReadonlyArray<string>; 244 + }; 245 + } 246 + 247 + export interface Patch { 248 + parameters?: Record< 249 + string, 250 + ( 251 + parameter: OpenApiParameterObject.V3_0_X | OpenApiParameterObject.V3_1_X, 252 + ) => void 253 + >; 254 + requestBodies?: Record< 255 + string, 256 + ( 257 + requestBody: 258 + | OpenApiRequestBodyObject.V3_0_X 259 + | OpenApiRequestBodyObject.V3_1_X, 260 + ) => void 261 + >; 262 + responses?: Record< 263 + string, 264 + ( 265 + response: OpenApiResponseObject.V3_0_X | OpenApiResponseObject.V3_1_X, 266 + ) => void 267 + >; 268 + /** 269 + * Each function receives the schema object to be modified in place. Common 270 + * use cases include fixing incorrect data types, removing unwanted 271 + * properties, adding missing fields, or standardizing date/time formats. 272 + * 273 + * @example 274 + * ```js 275 + * schemas: { 276 + * Foo: (schema) => { 277 + * // convert date-time format to timestamp 278 + * delete schema.properties.updatedAt.format; 279 + * schema.properties.updatedAt.type = 'number'; 280 + * }, 281 + * Bar: (schema) => { 282 + * // add missing property 283 + * schema.properties.metadata = { 284 + * additionalProperties: true, 285 + * type: 'object', 286 + * }; 287 + * schema.required = ['metadata']; 288 + * }, 289 + * Baz: (schema) => { 290 + * // remove property 291 + * delete schema.properties.internalField; 292 + * } 293 + * } 294 + * ``` 295 + */ 296 + schemas?: Record< 297 + string, 298 + ( 299 + schema: 300 + | OpenApiSchemaObject.V2_0_X 301 + | OpenApiSchemaObject.V3_0_X 302 + | OpenApiSchemaObject.V3_1_X, 303 + ) => void 304 + >; 305 + } 306 + 307 + export interface Watch { 308 + /** 309 + * Regenerate the client when the input file changes? 310 + * 311 + * @default false 312 + */ 313 + enabled?: boolean; 314 + /** 315 + * How often should we attempt to detect the input file change? (in ms) 316 + * 317 + * @default 1000 318 + */ 319 + interval?: number; 320 + /** 321 + * How long will we wait before the request times out? 322 + * 323 + * @default 60_000 324 + */ 325 + timeout?: number; 326 + }