├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── biome.json ├── examples ├── generated_client.ts ├── schema.d.ts └── schema.yaml ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── src ├── cli.ts ├── index.ts ├── integration.test.ts └── test_resources │ ├── expected_client.ts │ ├── expected_client_with_operation_id.ts │ ├── schema.d.ts │ ├── schema.yaml │ └── verifications.ts ├── tsconfig.json └── vitest.config.ts /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node: [ 18, 20, 22 ] 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ matrix.node }} 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v4 19 | with: 20 | version: 10 21 | - name: Install dependencies 22 | run: pnpm install 23 | - name: Run tests 24 | run: pnpm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | src/test_resources/generated_client*.ts 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Taiki Kawakami (a.k.a moznion) https://moznion.net 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openapi-fetch-gen [![Run Tests](https://github.com/moznion/openapi-fetch-gen/actions/workflows/test.yml/badge.svg)](https://github.com/moznion/openapi-fetch-gen/actions/workflows/test.yml) 2 | 3 | Generate TypeScript API client from OpenAPI TypeScript interface definitions created by [openapi-typescript](https://github.com/openapi-ts/openapi-typescript). 4 | 5 | This tool takes TypeScript interface definitions generated by `openapi-typescript` and creates a fully typed API client using [openapi-fetch](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch). 6 | 7 | ## How It Works 8 | 9 | 1. Parse the TypeScript schema file generated by `openapi-typescript` 10 | 2. Extract all API endpoints, their HTTP methods, and parameter structures from the schema 11 | 3. Generate typed wrapper functions for each endpoint 12 | 4. Export a fully-typed client that provides: 13 | - A base client instance created with `createClient` from `openapi-fetch` 14 | - Individual typed functions for each API endpoint 15 | - Type helpers for parameters and responses 16 | 17 | ## Installation 18 | 19 | ```bash 20 | npm install --save-dev @moznion/openapi-fetch-gen 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### CLI 26 | 27 | ```bash 28 | npx openapi-fetch-gen --input ./schema.d.ts --output ./client.ts 29 | ``` 30 | 31 | Options: 32 | 33 | ``` 34 | -V, --version output the version number 35 | -i, --input path to input OpenAPI TypeScript definition file 36 | -o, --output path to output generated client file (default: "./client.ts") 37 | --use-operation-id use operationId from OpenAPI schema for method names instead of generating from path (default: false) 38 | -h, --help display help for command 39 | ``` 40 | 41 | ### Example 42 | 43 | Please refer to the [examples](./examples/). 44 | 45 | `schema.d.ts` is generated from `schema.yaml` by `openapi-typescript`, and `generated_client.ts` is generated by this tool according to the `schema.d.ts`. 46 | 47 | FYI, you can use the generated client as follows: 48 | 49 | ```typescript 50 | import { Client } from "./generated_client"; 51 | 52 | async function doSomething() { 53 | const client = new Client({ baseUrl: "https://api.example.com" }); 54 | const users = await client.getUsers({ 55 | query: { 56 | page: 1, 57 | pageSize: 10, 58 | membershipType: "PREMIUM", 59 | }, 60 | }); 61 | 62 | for (const user of users.data?.items ?? []) { 63 | console.log(`User: ${user.name}, Email: ${user.email}`); 64 | } 65 | } 66 | ``` 67 | 68 | ### Default HTTP Headers 69 | 70 | Generated clients support a generic type for default HTTP headers. 71 | 72 | Example: 73 | 74 | ```typescript 75 | export class Client> { 76 | constructor(clientOptions: ClientOptions, defaultHeaders?: HT) { 77 | this.client = createClient(clientOptions); 78 | this.defaultHeaders = defaultHeaders ?? ({} as HT); 79 | } 80 | ... 81 | } 82 | ``` 83 | 84 | You can create a client instance with default headers like this: 85 | 86 | ```typescript 87 | new Client({}, {"Authorization": "Bearer your-token", "Application-Version": "1.0.0"}); 88 | ``` 89 | 90 | With this setup, endpoint methods that require these headers no longer need them to be explicitly passed each time. 91 | 92 | For example, given the following schema: 93 | 94 | ```typescript 95 | "/users/bulk/{jobId}": { 96 | get: { 97 | parameters: { 98 | query?: never; 99 | header: { 100 | /** @description Authorization Header */ 101 | Authorization: string; 102 | /** @description Application version */ 103 | "Application-Version": string; 104 | /** @description Identifier of something */ 105 | "Something-Id": string; 106 | }; 107 | path: { 108 | /** @description Bulk import job identifier */ 109 | jobId: string; 110 | }; 111 | cookie?: never; 112 | }; 113 | ``` 114 | 115 | This tool generates an endpoint method using a type-level trick like this: 116 | 117 | ```typescript 118 | async getUsersBulkJobid( 119 | params: [ 120 | Exclude< 121 | // Missed Header Keys for default headers 122 | keyof { 123 | Authorization: string; 124 | "Application-Version": string; 125 | "Something-Id": string; 126 | }, 127 | Extract< 128 | // Provided header keys by default headers' keys 129 | keyof HT, 130 | keyof { 131 | Authorization: string; 132 | "Application-Version": string; 133 | "Something-Id": string; 134 | } 135 | > 136 | >, 137 | ] extends [never] 138 | ? { 139 | header?: { 140 | Authorization: string; 141 | "Application-Version": string; 142 | "Something-Id": string; 143 | }; 144 | path: { jobId: string }; 145 | } 146 | : { 147 | header: 148 | | (Pick< 149 | // Pick the header keys that are not in the default headers 150 | { 151 | Authorization: string; 152 | "Application-Version": string; 153 | "Something-Id": string; 154 | }, 155 | Exclude< 156 | // Missed Header Keys for default headers 157 | keyof { 158 | Authorization: string; 159 | "Application-Version": string; 160 | "Something-Id": string; 161 | }, 162 | Extract< 163 | // Provided header keys by default headers' keys 164 | keyof HT, 165 | keyof { 166 | Authorization: string; 167 | "Application-Version": string; 168 | "Something-Id": string; 169 | } 170 | > 171 | > 172 | > & 173 | Partial< 174 | // Disallow default headers' keys to be in the header param 175 | Record< 176 | Extract< 177 | // Provided header keys by default headers' keys 178 | keyof HT, 179 | keyof { 180 | Authorization: string; 181 | "Application-Version": string; 182 | "Something-Id": string; 183 | } 184 | >, 185 | never 186 | > 187 | >) 188 | | { 189 | Authorization: string; 190 | "Application-Version": string; 191 | "Something-Id": string; 192 | }; 193 | path: { jobId: string }; 194 | }, 195 | ) { 196 | return await this.client.GET("/users/bulk/{jobId}", { 197 | params: { 198 | ...params, 199 | header: { ...this.defaultHeaders, ...params.header } as { 200 | Authorization: string; 201 | "Application-Version": string; 202 | "Something-Id": string; 203 | }, 204 | }, 205 | }); 206 | } 207 | ``` 208 | 209 | This signature allows you to: 210 | 211 | - **Omit** the defaulted headers and only pass additional ones (here, `Something-Id`): 212 | 213 | ```typescript 214 | client.getUsersBulkJobid({header: {"Something-Id": "foobar"}, path: {jobId: "123"}}); 215 | ``` 216 | 217 | - **Override** all headers, including the defaults: 218 | 219 | ```typescript 220 | client.getUsersBulkJobid({header: {"Authorization": "foo", "Application-Version": "bar", "Something-Id": "foobar"}, path: {jobId: "123"}}); 221 | ``` 222 | 223 | If your default headers already include **all** required headers for the endpoint (e.g. `{"Authorization": "Bearer your-token", "Application-Version": "1.0.0", "Something-Id": "123"}` as the second constructor argument), you can omit the `header` parameter entirely: 224 | 225 | ```typescript 226 | client.getUsersBulkJobid({path: {jobId: "123"}}); 227 | ``` 228 | 229 | NOTE: 230 | 231 | In this context, the "default HTTP headers" are different from the headers in `ClientOptions`. 232 | The headers in `ClientOptions` are always sent implicitly, regardless of the header parameters specified in endpoint methods. 233 | In contrast, the "default HTTP headers" mechanism is intended to reduce the need to repeatedly specify common header parameters 234 | in each endpoint method when their values are already known. 235 | 236 | ## Articles 237 | 238 | - [openapi-fetch-gen – Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript - DEV Community](https://dev.to/moznion/openapi-fetch-gen-generate-typescript-api-client-from-openapi-typescript-interface-definitions-kjd) 239 | 240 | ## For developers 241 | 242 | ### How to publish this packages 243 | 244 | ``` 245 | $ pnpm ship 246 | ``` 247 | 248 | ## License 249 | 250 | MIT 251 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.6.0/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "complexity": { 11 | "useLiteralKeys": "off" 12 | } 13 | } 14 | }, 15 | "formatter": { 16 | "enabled": true, 17 | "indentStyle": "space", 18 | "indentWidth": 2 19 | }, 20 | "files": { 21 | "maxSize": 10485760 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/generated_client.ts: -------------------------------------------------------------------------------- 1 | ../src/test_resources/expected_client.ts -------------------------------------------------------------------------------- /examples/schema.d.ts: -------------------------------------------------------------------------------- 1 | ../src/test_resources/schema.d.ts -------------------------------------------------------------------------------- /examples/schema.yaml: -------------------------------------------------------------------------------- 1 | ../src/test_resources/schema.yaml -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@moznion/openapi-fetch-gen", 3 | "version": "0.4.0", 4 | "description": "Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist/*.js", 9 | "dist/*.js.map", 10 | "dist/*.ts.map", 11 | "dist/*.d.ts", 12 | "LICENSE", 13 | "README.md", 14 | "package.json" 15 | ], 16 | "scripts": { 17 | "clean": "rm -rf ./dist/*", 18 | "build": "pnpm clean && tsc", 19 | "lint": "biome check src", 20 | "fix": "biome check --write src", 21 | "test": "pnpm test:clean && vitest run", 22 | "test:watch": "vitest", 23 | "test:clean": "rm -f ./src/test_resources/generated_client*", 24 | "generate_test_resource": "openapi-typescript -o ./src/test_resources/schema.d.ts ./src/test_resources/schema.yaml", 25 | "ship": "pnpm build && pnpm publish" 26 | }, 27 | "bin": { 28 | "openapi-fetch-gen": "./dist/cli.js" 29 | }, 30 | "keywords": [ 31 | "openapi", 32 | "typescript", 33 | "api", 34 | "client", 35 | "generator", 36 | "openapi-typescript", 37 | "openapi-fetch" 38 | ], 39 | "author": "moznion", 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/moznion/openapi-fetch-gen.git" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/moznion/openapi-fetch-gen/issues" 46 | }, 47 | "homepage": "https://github.com/moznion/openapi-fetch-gen#readme", 48 | "license": "MIT", 49 | "dependencies": { 50 | "tmp": "^0.2.3", 51 | "commander": "^14.0.0", 52 | "ts-morph": "^26.0.0" 53 | }, 54 | "devDependencies": { 55 | "@biomejs/biome": "^1.9.4", 56 | "@types/node": "^22.14.1", 57 | "@types/tmp": "^0.2.6", 58 | "openapi-fetch": "^0.14.0", 59 | "openapi-typescript": "^7.6.1", 60 | "typescript": "^5.8.3", 61 | "vitest": "^3.1.1" 62 | }, 63 | "peerDependencies": { 64 | "@biomejs/biome": ">=1.9.4", 65 | "openapi-fetch": ">=0.13.5" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | commander: 12 | specifier: ^14.0.0 13 | version: 14.0.0 14 | tmp: 15 | specifier: ^0.2.3 16 | version: 0.2.3 17 | ts-morph: 18 | specifier: ^26.0.0 19 | version: 26.0.0 20 | devDependencies: 21 | '@biomejs/biome': 22 | specifier: ^1.9.4 23 | version: 1.9.4 24 | '@types/node': 25 | specifier: ^22.14.1 26 | version: 22.15.29 27 | '@types/tmp': 28 | specifier: ^0.2.6 29 | version: 0.2.6 30 | openapi-fetch: 31 | specifier: ^0.14.0 32 | version: 0.14.0 33 | openapi-typescript: 34 | specifier: ^7.6.1 35 | version: 7.8.0(typescript@5.8.3) 36 | typescript: 37 | specifier: ^5.8.3 38 | version: 5.8.3 39 | vitest: 40 | specifier: ^3.1.1 41 | version: 3.1.4(@types/node@22.15.29) 42 | 43 | packages: 44 | 45 | '@babel/code-frame@7.27.1': 46 | resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 47 | engines: {node: '>=6.9.0'} 48 | 49 | '@babel/helper-validator-identifier@7.27.1': 50 | resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} 51 | engines: {node: '>=6.9.0'} 52 | 53 | '@biomejs/biome@1.9.4': 54 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 55 | engines: {node: '>=14.21.3'} 56 | hasBin: true 57 | 58 | '@biomejs/cli-darwin-arm64@1.9.4': 59 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 60 | engines: {node: '>=14.21.3'} 61 | cpu: [arm64] 62 | os: [darwin] 63 | 64 | '@biomejs/cli-darwin-x64@1.9.4': 65 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 66 | engines: {node: '>=14.21.3'} 67 | cpu: [x64] 68 | os: [darwin] 69 | 70 | '@biomejs/cli-linux-arm64-musl@1.9.4': 71 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 72 | engines: {node: '>=14.21.3'} 73 | cpu: [arm64] 74 | os: [linux] 75 | 76 | '@biomejs/cli-linux-arm64@1.9.4': 77 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 78 | engines: {node: '>=14.21.3'} 79 | cpu: [arm64] 80 | os: [linux] 81 | 82 | '@biomejs/cli-linux-x64-musl@1.9.4': 83 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 84 | engines: {node: '>=14.21.3'} 85 | cpu: [x64] 86 | os: [linux] 87 | 88 | '@biomejs/cli-linux-x64@1.9.4': 89 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 90 | engines: {node: '>=14.21.3'} 91 | cpu: [x64] 92 | os: [linux] 93 | 94 | '@biomejs/cli-win32-arm64@1.9.4': 95 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 96 | engines: {node: '>=14.21.3'} 97 | cpu: [arm64] 98 | os: [win32] 99 | 100 | '@biomejs/cli-win32-x64@1.9.4': 101 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 102 | engines: {node: '>=14.21.3'} 103 | cpu: [x64] 104 | os: [win32] 105 | 106 | '@esbuild/aix-ppc64@0.25.4': 107 | resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} 108 | engines: {node: '>=18'} 109 | cpu: [ppc64] 110 | os: [aix] 111 | 112 | '@esbuild/android-arm64@0.25.4': 113 | resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} 114 | engines: {node: '>=18'} 115 | cpu: [arm64] 116 | os: [android] 117 | 118 | '@esbuild/android-arm@0.25.4': 119 | resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} 120 | engines: {node: '>=18'} 121 | cpu: [arm] 122 | os: [android] 123 | 124 | '@esbuild/android-x64@0.25.4': 125 | resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} 126 | engines: {node: '>=18'} 127 | cpu: [x64] 128 | os: [android] 129 | 130 | '@esbuild/darwin-arm64@0.25.4': 131 | resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} 132 | engines: {node: '>=18'} 133 | cpu: [arm64] 134 | os: [darwin] 135 | 136 | '@esbuild/darwin-x64@0.25.4': 137 | resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} 138 | engines: {node: '>=18'} 139 | cpu: [x64] 140 | os: [darwin] 141 | 142 | '@esbuild/freebsd-arm64@0.25.4': 143 | resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} 144 | engines: {node: '>=18'} 145 | cpu: [arm64] 146 | os: [freebsd] 147 | 148 | '@esbuild/freebsd-x64@0.25.4': 149 | resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} 150 | engines: {node: '>=18'} 151 | cpu: [x64] 152 | os: [freebsd] 153 | 154 | '@esbuild/linux-arm64@0.25.4': 155 | resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} 156 | engines: {node: '>=18'} 157 | cpu: [arm64] 158 | os: [linux] 159 | 160 | '@esbuild/linux-arm@0.25.4': 161 | resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} 162 | engines: {node: '>=18'} 163 | cpu: [arm] 164 | os: [linux] 165 | 166 | '@esbuild/linux-ia32@0.25.4': 167 | resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} 168 | engines: {node: '>=18'} 169 | cpu: [ia32] 170 | os: [linux] 171 | 172 | '@esbuild/linux-loong64@0.25.4': 173 | resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} 174 | engines: {node: '>=18'} 175 | cpu: [loong64] 176 | os: [linux] 177 | 178 | '@esbuild/linux-mips64el@0.25.4': 179 | resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} 180 | engines: {node: '>=18'} 181 | cpu: [mips64el] 182 | os: [linux] 183 | 184 | '@esbuild/linux-ppc64@0.25.4': 185 | resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} 186 | engines: {node: '>=18'} 187 | cpu: [ppc64] 188 | os: [linux] 189 | 190 | '@esbuild/linux-riscv64@0.25.4': 191 | resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} 192 | engines: {node: '>=18'} 193 | cpu: [riscv64] 194 | os: [linux] 195 | 196 | '@esbuild/linux-s390x@0.25.4': 197 | resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} 198 | engines: {node: '>=18'} 199 | cpu: [s390x] 200 | os: [linux] 201 | 202 | '@esbuild/linux-x64@0.25.4': 203 | resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} 204 | engines: {node: '>=18'} 205 | cpu: [x64] 206 | os: [linux] 207 | 208 | '@esbuild/netbsd-arm64@0.25.4': 209 | resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} 210 | engines: {node: '>=18'} 211 | cpu: [arm64] 212 | os: [netbsd] 213 | 214 | '@esbuild/netbsd-x64@0.25.4': 215 | resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} 216 | engines: {node: '>=18'} 217 | cpu: [x64] 218 | os: [netbsd] 219 | 220 | '@esbuild/openbsd-arm64@0.25.4': 221 | resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} 222 | engines: {node: '>=18'} 223 | cpu: [arm64] 224 | os: [openbsd] 225 | 226 | '@esbuild/openbsd-x64@0.25.4': 227 | resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} 228 | engines: {node: '>=18'} 229 | cpu: [x64] 230 | os: [openbsd] 231 | 232 | '@esbuild/sunos-x64@0.25.4': 233 | resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} 234 | engines: {node: '>=18'} 235 | cpu: [x64] 236 | os: [sunos] 237 | 238 | '@esbuild/win32-arm64@0.25.4': 239 | resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} 240 | engines: {node: '>=18'} 241 | cpu: [arm64] 242 | os: [win32] 243 | 244 | '@esbuild/win32-ia32@0.25.4': 245 | resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} 246 | engines: {node: '>=18'} 247 | cpu: [ia32] 248 | os: [win32] 249 | 250 | '@esbuild/win32-x64@0.25.4': 251 | resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} 252 | engines: {node: '>=18'} 253 | cpu: [x64] 254 | os: [win32] 255 | 256 | '@jridgewell/sourcemap-codec@1.5.0': 257 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 258 | 259 | '@nodelib/fs.scandir@2.1.5': 260 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 261 | engines: {node: '>= 8'} 262 | 263 | '@nodelib/fs.stat@2.0.5': 264 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 265 | engines: {node: '>= 8'} 266 | 267 | '@nodelib/fs.walk@1.2.8': 268 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 269 | engines: {node: '>= 8'} 270 | 271 | '@redocly/ajv@8.11.2': 272 | resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} 273 | 274 | '@redocly/config@0.22.2': 275 | resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} 276 | 277 | '@redocly/openapi-core@1.34.3': 278 | resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==} 279 | engines: {node: '>=18.17.0', npm: '>=9.5.0'} 280 | 281 | '@rollup/rollup-android-arm-eabi@4.41.0': 282 | resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} 283 | cpu: [arm] 284 | os: [android] 285 | 286 | '@rollup/rollup-android-arm64@4.41.0': 287 | resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} 288 | cpu: [arm64] 289 | os: [android] 290 | 291 | '@rollup/rollup-darwin-arm64@4.41.0': 292 | resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} 293 | cpu: [arm64] 294 | os: [darwin] 295 | 296 | '@rollup/rollup-darwin-x64@4.41.0': 297 | resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} 298 | cpu: [x64] 299 | os: [darwin] 300 | 301 | '@rollup/rollup-freebsd-arm64@4.41.0': 302 | resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} 303 | cpu: [arm64] 304 | os: [freebsd] 305 | 306 | '@rollup/rollup-freebsd-x64@4.41.0': 307 | resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} 308 | cpu: [x64] 309 | os: [freebsd] 310 | 311 | '@rollup/rollup-linux-arm-gnueabihf@4.41.0': 312 | resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} 313 | cpu: [arm] 314 | os: [linux] 315 | 316 | '@rollup/rollup-linux-arm-musleabihf@4.41.0': 317 | resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} 318 | cpu: [arm] 319 | os: [linux] 320 | 321 | '@rollup/rollup-linux-arm64-gnu@4.41.0': 322 | resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} 323 | cpu: [arm64] 324 | os: [linux] 325 | 326 | '@rollup/rollup-linux-arm64-musl@4.41.0': 327 | resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} 328 | cpu: [arm64] 329 | os: [linux] 330 | 331 | '@rollup/rollup-linux-loongarch64-gnu@4.41.0': 332 | resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} 333 | cpu: [loong64] 334 | os: [linux] 335 | 336 | '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': 337 | resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} 338 | cpu: [ppc64] 339 | os: [linux] 340 | 341 | '@rollup/rollup-linux-riscv64-gnu@4.41.0': 342 | resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} 343 | cpu: [riscv64] 344 | os: [linux] 345 | 346 | '@rollup/rollup-linux-riscv64-musl@4.41.0': 347 | resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} 348 | cpu: [riscv64] 349 | os: [linux] 350 | 351 | '@rollup/rollup-linux-s390x-gnu@4.41.0': 352 | resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} 353 | cpu: [s390x] 354 | os: [linux] 355 | 356 | '@rollup/rollup-linux-x64-gnu@4.41.0': 357 | resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} 358 | cpu: [x64] 359 | os: [linux] 360 | 361 | '@rollup/rollup-linux-x64-musl@4.41.0': 362 | resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} 363 | cpu: [x64] 364 | os: [linux] 365 | 366 | '@rollup/rollup-win32-arm64-msvc@4.41.0': 367 | resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} 368 | cpu: [arm64] 369 | os: [win32] 370 | 371 | '@rollup/rollup-win32-ia32-msvc@4.41.0': 372 | resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} 373 | cpu: [ia32] 374 | os: [win32] 375 | 376 | '@rollup/rollup-win32-x64-msvc@4.41.0': 377 | resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} 378 | cpu: [x64] 379 | os: [win32] 380 | 381 | '@ts-morph/common@0.27.0': 382 | resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} 383 | 384 | '@types/estree@1.0.7': 385 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 386 | 387 | '@types/node@22.15.29': 388 | resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} 389 | 390 | '@types/tmp@0.2.6': 391 | resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} 392 | 393 | '@vitest/expect@3.1.4': 394 | resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} 395 | 396 | '@vitest/mocker@3.1.4': 397 | resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} 398 | peerDependencies: 399 | msw: ^2.4.9 400 | vite: ^5.0.0 || ^6.0.0 401 | peerDependenciesMeta: 402 | msw: 403 | optional: true 404 | vite: 405 | optional: true 406 | 407 | '@vitest/pretty-format@3.1.4': 408 | resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==} 409 | 410 | '@vitest/runner@3.1.4': 411 | resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==} 412 | 413 | '@vitest/snapshot@3.1.4': 414 | resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} 415 | 416 | '@vitest/spy@3.1.4': 417 | resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} 418 | 419 | '@vitest/utils@3.1.4': 420 | resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==} 421 | 422 | agent-base@7.1.3: 423 | resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} 424 | engines: {node: '>= 14'} 425 | 426 | ansi-colors@4.1.3: 427 | resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} 428 | engines: {node: '>=6'} 429 | 430 | argparse@2.0.1: 431 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 432 | 433 | assertion-error@2.0.1: 434 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 435 | engines: {node: '>=12'} 436 | 437 | balanced-match@1.0.2: 438 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 439 | 440 | brace-expansion@2.0.1: 441 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 442 | 443 | braces@3.0.3: 444 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 445 | engines: {node: '>=8'} 446 | 447 | cac@6.7.14: 448 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 449 | engines: {node: '>=8'} 450 | 451 | chai@5.2.0: 452 | resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} 453 | engines: {node: '>=12'} 454 | 455 | change-case@5.4.4: 456 | resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 457 | 458 | check-error@2.1.1: 459 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 460 | engines: {node: '>= 16'} 461 | 462 | code-block-writer@13.0.3: 463 | resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} 464 | 465 | colorette@1.4.0: 466 | resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} 467 | 468 | commander@14.0.0: 469 | resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} 470 | engines: {node: '>=20'} 471 | 472 | debug@4.4.0: 473 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 474 | engines: {node: '>=6.0'} 475 | peerDependencies: 476 | supports-color: '*' 477 | peerDependenciesMeta: 478 | supports-color: 479 | optional: true 480 | 481 | debug@4.4.1: 482 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 483 | engines: {node: '>=6.0'} 484 | peerDependencies: 485 | supports-color: '*' 486 | peerDependenciesMeta: 487 | supports-color: 488 | optional: true 489 | 490 | deep-eql@5.0.2: 491 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 492 | engines: {node: '>=6'} 493 | 494 | es-module-lexer@1.7.0: 495 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 496 | 497 | esbuild@0.25.4: 498 | resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} 499 | engines: {node: '>=18'} 500 | hasBin: true 501 | 502 | estree-walker@3.0.3: 503 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 504 | 505 | expect-type@1.2.1: 506 | resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} 507 | engines: {node: '>=12.0.0'} 508 | 509 | fast-deep-equal@3.1.3: 510 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 511 | 512 | fast-glob@3.3.3: 513 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 514 | engines: {node: '>=8.6.0'} 515 | 516 | fastq@1.19.1: 517 | resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 518 | 519 | fdir@6.4.4: 520 | resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} 521 | peerDependencies: 522 | picomatch: ^3 || ^4 523 | peerDependenciesMeta: 524 | picomatch: 525 | optional: true 526 | 527 | fill-range@7.1.1: 528 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 529 | engines: {node: '>=8'} 530 | 531 | fsevents@2.3.3: 532 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 533 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 534 | os: [darwin] 535 | 536 | glob-parent@5.1.2: 537 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 538 | engines: {node: '>= 6'} 539 | 540 | https-proxy-agent@7.0.6: 541 | resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} 542 | engines: {node: '>= 14'} 543 | 544 | index-to-position@1.1.0: 545 | resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==} 546 | engines: {node: '>=18'} 547 | 548 | is-extglob@2.1.1: 549 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 550 | engines: {node: '>=0.10.0'} 551 | 552 | is-glob@4.0.3: 553 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 554 | engines: {node: '>=0.10.0'} 555 | 556 | is-number@7.0.0: 557 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 558 | engines: {node: '>=0.12.0'} 559 | 560 | js-levenshtein@1.1.6: 561 | resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} 562 | engines: {node: '>=0.10.0'} 563 | 564 | js-tokens@4.0.0: 565 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 566 | 567 | js-yaml@4.1.0: 568 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 569 | hasBin: true 570 | 571 | json-schema-traverse@1.0.0: 572 | resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} 573 | 574 | loupe@3.1.3: 575 | resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} 576 | 577 | magic-string@0.30.17: 578 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 579 | 580 | merge2@1.4.1: 581 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 582 | engines: {node: '>= 8'} 583 | 584 | micromatch@4.0.8: 585 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 586 | engines: {node: '>=8.6'} 587 | 588 | minimatch@10.0.1: 589 | resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} 590 | engines: {node: 20 || >=22} 591 | 592 | minimatch@5.1.6: 593 | resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} 594 | engines: {node: '>=10'} 595 | 596 | ms@2.1.3: 597 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 598 | 599 | nanoid@3.3.11: 600 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 601 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 602 | hasBin: true 603 | 604 | openapi-fetch@0.14.0: 605 | resolution: {integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==} 606 | 607 | openapi-typescript-helpers@0.0.15: 608 | resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} 609 | 610 | openapi-typescript@7.8.0: 611 | resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==} 612 | hasBin: true 613 | peerDependencies: 614 | typescript: ^5.x 615 | 616 | parse-json@8.3.0: 617 | resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} 618 | engines: {node: '>=18'} 619 | 620 | path-browserify@1.0.1: 621 | resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 622 | 623 | pathe@2.0.3: 624 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 625 | 626 | pathval@2.0.0: 627 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 628 | engines: {node: '>= 14.16'} 629 | 630 | picocolors@1.1.1: 631 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 632 | 633 | picomatch@2.3.1: 634 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 635 | engines: {node: '>=8.6'} 636 | 637 | picomatch@4.0.2: 638 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 639 | engines: {node: '>=12'} 640 | 641 | pluralize@8.0.0: 642 | resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} 643 | engines: {node: '>=4'} 644 | 645 | postcss@8.5.3: 646 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 647 | engines: {node: ^10 || ^12 || >=14} 648 | 649 | queue-microtask@1.2.3: 650 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 651 | 652 | require-from-string@2.0.2: 653 | resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 654 | engines: {node: '>=0.10.0'} 655 | 656 | reusify@1.1.0: 657 | resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 658 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 659 | 660 | rollup@4.41.0: 661 | resolution: {integrity: sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==} 662 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 663 | hasBin: true 664 | 665 | run-parallel@1.2.0: 666 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 667 | 668 | siginfo@2.0.0: 669 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 670 | 671 | source-map-js@1.2.1: 672 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 673 | engines: {node: '>=0.10.0'} 674 | 675 | stackback@0.0.2: 676 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 677 | 678 | std-env@3.9.0: 679 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} 680 | 681 | supports-color@10.0.0: 682 | resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==} 683 | engines: {node: '>=18'} 684 | 685 | tinybench@2.9.0: 686 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 687 | 688 | tinyexec@0.3.2: 689 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 690 | 691 | tinyglobby@0.2.13: 692 | resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} 693 | engines: {node: '>=12.0.0'} 694 | 695 | tinypool@1.0.2: 696 | resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} 697 | engines: {node: ^18.0.0 || >=20.0.0} 698 | 699 | tinyrainbow@2.0.0: 700 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 701 | engines: {node: '>=14.0.0'} 702 | 703 | tinyspy@3.0.2: 704 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 705 | engines: {node: '>=14.0.0'} 706 | 707 | tmp@0.2.3: 708 | resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} 709 | engines: {node: '>=14.14'} 710 | 711 | to-regex-range@5.0.1: 712 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 713 | engines: {node: '>=8.0'} 714 | 715 | ts-morph@26.0.0: 716 | resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} 717 | 718 | type-fest@4.41.0: 719 | resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} 720 | engines: {node: '>=16'} 721 | 722 | typescript@5.8.3: 723 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 724 | engines: {node: '>=14.17'} 725 | hasBin: true 726 | 727 | undici-types@6.21.0: 728 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 729 | 730 | uri-js-replace@1.0.1: 731 | resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} 732 | 733 | vite-node@3.1.4: 734 | resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} 735 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 736 | hasBin: true 737 | 738 | vite@6.3.5: 739 | resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} 740 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 741 | hasBin: true 742 | peerDependencies: 743 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 744 | jiti: '>=1.21.0' 745 | less: '*' 746 | lightningcss: ^1.21.0 747 | sass: '*' 748 | sass-embedded: '*' 749 | stylus: '*' 750 | sugarss: '*' 751 | terser: ^5.16.0 752 | tsx: ^4.8.1 753 | yaml: ^2.4.2 754 | peerDependenciesMeta: 755 | '@types/node': 756 | optional: true 757 | jiti: 758 | optional: true 759 | less: 760 | optional: true 761 | lightningcss: 762 | optional: true 763 | sass: 764 | optional: true 765 | sass-embedded: 766 | optional: true 767 | stylus: 768 | optional: true 769 | sugarss: 770 | optional: true 771 | terser: 772 | optional: true 773 | tsx: 774 | optional: true 775 | yaml: 776 | optional: true 777 | 778 | vitest@3.1.4: 779 | resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} 780 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 781 | hasBin: true 782 | peerDependencies: 783 | '@edge-runtime/vm': '*' 784 | '@types/debug': ^4.1.12 785 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 786 | '@vitest/browser': 3.1.4 787 | '@vitest/ui': 3.1.4 788 | happy-dom: '*' 789 | jsdom: '*' 790 | peerDependenciesMeta: 791 | '@edge-runtime/vm': 792 | optional: true 793 | '@types/debug': 794 | optional: true 795 | '@types/node': 796 | optional: true 797 | '@vitest/browser': 798 | optional: true 799 | '@vitest/ui': 800 | optional: true 801 | happy-dom: 802 | optional: true 803 | jsdom: 804 | optional: true 805 | 806 | why-is-node-running@2.3.0: 807 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 808 | engines: {node: '>=8'} 809 | hasBin: true 810 | 811 | yaml-ast-parser@0.0.43: 812 | resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} 813 | 814 | yargs-parser@21.1.1: 815 | resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 816 | engines: {node: '>=12'} 817 | 818 | snapshots: 819 | 820 | '@babel/code-frame@7.27.1': 821 | dependencies: 822 | '@babel/helper-validator-identifier': 7.27.1 823 | js-tokens: 4.0.0 824 | picocolors: 1.1.1 825 | 826 | '@babel/helper-validator-identifier@7.27.1': {} 827 | 828 | '@biomejs/biome@1.9.4': 829 | optionalDependencies: 830 | '@biomejs/cli-darwin-arm64': 1.9.4 831 | '@biomejs/cli-darwin-x64': 1.9.4 832 | '@biomejs/cli-linux-arm64': 1.9.4 833 | '@biomejs/cli-linux-arm64-musl': 1.9.4 834 | '@biomejs/cli-linux-x64': 1.9.4 835 | '@biomejs/cli-linux-x64-musl': 1.9.4 836 | '@biomejs/cli-win32-arm64': 1.9.4 837 | '@biomejs/cli-win32-x64': 1.9.4 838 | 839 | '@biomejs/cli-darwin-arm64@1.9.4': 840 | optional: true 841 | 842 | '@biomejs/cli-darwin-x64@1.9.4': 843 | optional: true 844 | 845 | '@biomejs/cli-linux-arm64-musl@1.9.4': 846 | optional: true 847 | 848 | '@biomejs/cli-linux-arm64@1.9.4': 849 | optional: true 850 | 851 | '@biomejs/cli-linux-x64-musl@1.9.4': 852 | optional: true 853 | 854 | '@biomejs/cli-linux-x64@1.9.4': 855 | optional: true 856 | 857 | '@biomejs/cli-win32-arm64@1.9.4': 858 | optional: true 859 | 860 | '@biomejs/cli-win32-x64@1.9.4': 861 | optional: true 862 | 863 | '@esbuild/aix-ppc64@0.25.4': 864 | optional: true 865 | 866 | '@esbuild/android-arm64@0.25.4': 867 | optional: true 868 | 869 | '@esbuild/android-arm@0.25.4': 870 | optional: true 871 | 872 | '@esbuild/android-x64@0.25.4': 873 | optional: true 874 | 875 | '@esbuild/darwin-arm64@0.25.4': 876 | optional: true 877 | 878 | '@esbuild/darwin-x64@0.25.4': 879 | optional: true 880 | 881 | '@esbuild/freebsd-arm64@0.25.4': 882 | optional: true 883 | 884 | '@esbuild/freebsd-x64@0.25.4': 885 | optional: true 886 | 887 | '@esbuild/linux-arm64@0.25.4': 888 | optional: true 889 | 890 | '@esbuild/linux-arm@0.25.4': 891 | optional: true 892 | 893 | '@esbuild/linux-ia32@0.25.4': 894 | optional: true 895 | 896 | '@esbuild/linux-loong64@0.25.4': 897 | optional: true 898 | 899 | '@esbuild/linux-mips64el@0.25.4': 900 | optional: true 901 | 902 | '@esbuild/linux-ppc64@0.25.4': 903 | optional: true 904 | 905 | '@esbuild/linux-riscv64@0.25.4': 906 | optional: true 907 | 908 | '@esbuild/linux-s390x@0.25.4': 909 | optional: true 910 | 911 | '@esbuild/linux-x64@0.25.4': 912 | optional: true 913 | 914 | '@esbuild/netbsd-arm64@0.25.4': 915 | optional: true 916 | 917 | '@esbuild/netbsd-x64@0.25.4': 918 | optional: true 919 | 920 | '@esbuild/openbsd-arm64@0.25.4': 921 | optional: true 922 | 923 | '@esbuild/openbsd-x64@0.25.4': 924 | optional: true 925 | 926 | '@esbuild/sunos-x64@0.25.4': 927 | optional: true 928 | 929 | '@esbuild/win32-arm64@0.25.4': 930 | optional: true 931 | 932 | '@esbuild/win32-ia32@0.25.4': 933 | optional: true 934 | 935 | '@esbuild/win32-x64@0.25.4': 936 | optional: true 937 | 938 | '@jridgewell/sourcemap-codec@1.5.0': {} 939 | 940 | '@nodelib/fs.scandir@2.1.5': 941 | dependencies: 942 | '@nodelib/fs.stat': 2.0.5 943 | run-parallel: 1.2.0 944 | 945 | '@nodelib/fs.stat@2.0.5': {} 946 | 947 | '@nodelib/fs.walk@1.2.8': 948 | dependencies: 949 | '@nodelib/fs.scandir': 2.1.5 950 | fastq: 1.19.1 951 | 952 | '@redocly/ajv@8.11.2': 953 | dependencies: 954 | fast-deep-equal: 3.1.3 955 | json-schema-traverse: 1.0.0 956 | require-from-string: 2.0.2 957 | uri-js-replace: 1.0.1 958 | 959 | '@redocly/config@0.22.2': {} 960 | 961 | '@redocly/openapi-core@1.34.3(supports-color@10.0.0)': 962 | dependencies: 963 | '@redocly/ajv': 8.11.2 964 | '@redocly/config': 0.22.2 965 | colorette: 1.4.0 966 | https-proxy-agent: 7.0.6(supports-color@10.0.0) 967 | js-levenshtein: 1.1.6 968 | js-yaml: 4.1.0 969 | minimatch: 5.1.6 970 | pluralize: 8.0.0 971 | yaml-ast-parser: 0.0.43 972 | transitivePeerDependencies: 973 | - supports-color 974 | 975 | '@rollup/rollup-android-arm-eabi@4.41.0': 976 | optional: true 977 | 978 | '@rollup/rollup-android-arm64@4.41.0': 979 | optional: true 980 | 981 | '@rollup/rollup-darwin-arm64@4.41.0': 982 | optional: true 983 | 984 | '@rollup/rollup-darwin-x64@4.41.0': 985 | optional: true 986 | 987 | '@rollup/rollup-freebsd-arm64@4.41.0': 988 | optional: true 989 | 990 | '@rollup/rollup-freebsd-x64@4.41.0': 991 | optional: true 992 | 993 | '@rollup/rollup-linux-arm-gnueabihf@4.41.0': 994 | optional: true 995 | 996 | '@rollup/rollup-linux-arm-musleabihf@4.41.0': 997 | optional: true 998 | 999 | '@rollup/rollup-linux-arm64-gnu@4.41.0': 1000 | optional: true 1001 | 1002 | '@rollup/rollup-linux-arm64-musl@4.41.0': 1003 | optional: true 1004 | 1005 | '@rollup/rollup-linux-loongarch64-gnu@4.41.0': 1006 | optional: true 1007 | 1008 | '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': 1009 | optional: true 1010 | 1011 | '@rollup/rollup-linux-riscv64-gnu@4.41.0': 1012 | optional: true 1013 | 1014 | '@rollup/rollup-linux-riscv64-musl@4.41.0': 1015 | optional: true 1016 | 1017 | '@rollup/rollup-linux-s390x-gnu@4.41.0': 1018 | optional: true 1019 | 1020 | '@rollup/rollup-linux-x64-gnu@4.41.0': 1021 | optional: true 1022 | 1023 | '@rollup/rollup-linux-x64-musl@4.41.0': 1024 | optional: true 1025 | 1026 | '@rollup/rollup-win32-arm64-msvc@4.41.0': 1027 | optional: true 1028 | 1029 | '@rollup/rollup-win32-ia32-msvc@4.41.0': 1030 | optional: true 1031 | 1032 | '@rollup/rollup-win32-x64-msvc@4.41.0': 1033 | optional: true 1034 | 1035 | '@ts-morph/common@0.27.0': 1036 | dependencies: 1037 | fast-glob: 3.3.3 1038 | minimatch: 10.0.1 1039 | path-browserify: 1.0.1 1040 | 1041 | '@types/estree@1.0.7': {} 1042 | 1043 | '@types/node@22.15.29': 1044 | dependencies: 1045 | undici-types: 6.21.0 1046 | 1047 | '@types/tmp@0.2.6': {} 1048 | 1049 | '@vitest/expect@3.1.4': 1050 | dependencies: 1051 | '@vitest/spy': 3.1.4 1052 | '@vitest/utils': 3.1.4 1053 | chai: 5.2.0 1054 | tinyrainbow: 2.0.0 1055 | 1056 | '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@22.15.29))': 1057 | dependencies: 1058 | '@vitest/spy': 3.1.4 1059 | estree-walker: 3.0.3 1060 | magic-string: 0.30.17 1061 | optionalDependencies: 1062 | vite: 6.3.5(@types/node@22.15.29) 1063 | 1064 | '@vitest/pretty-format@3.1.4': 1065 | dependencies: 1066 | tinyrainbow: 2.0.0 1067 | 1068 | '@vitest/runner@3.1.4': 1069 | dependencies: 1070 | '@vitest/utils': 3.1.4 1071 | pathe: 2.0.3 1072 | 1073 | '@vitest/snapshot@3.1.4': 1074 | dependencies: 1075 | '@vitest/pretty-format': 3.1.4 1076 | magic-string: 0.30.17 1077 | pathe: 2.0.3 1078 | 1079 | '@vitest/spy@3.1.4': 1080 | dependencies: 1081 | tinyspy: 3.0.2 1082 | 1083 | '@vitest/utils@3.1.4': 1084 | dependencies: 1085 | '@vitest/pretty-format': 3.1.4 1086 | loupe: 3.1.3 1087 | tinyrainbow: 2.0.0 1088 | 1089 | agent-base@7.1.3: {} 1090 | 1091 | ansi-colors@4.1.3: {} 1092 | 1093 | argparse@2.0.1: {} 1094 | 1095 | assertion-error@2.0.1: {} 1096 | 1097 | balanced-match@1.0.2: {} 1098 | 1099 | brace-expansion@2.0.1: 1100 | dependencies: 1101 | balanced-match: 1.0.2 1102 | 1103 | braces@3.0.3: 1104 | dependencies: 1105 | fill-range: 7.1.1 1106 | 1107 | cac@6.7.14: {} 1108 | 1109 | chai@5.2.0: 1110 | dependencies: 1111 | assertion-error: 2.0.1 1112 | check-error: 2.1.1 1113 | deep-eql: 5.0.2 1114 | loupe: 3.1.3 1115 | pathval: 2.0.0 1116 | 1117 | change-case@5.4.4: {} 1118 | 1119 | check-error@2.1.1: {} 1120 | 1121 | code-block-writer@13.0.3: {} 1122 | 1123 | colorette@1.4.0: {} 1124 | 1125 | commander@14.0.0: {} 1126 | 1127 | debug@4.4.0(supports-color@10.0.0): 1128 | dependencies: 1129 | ms: 2.1.3 1130 | optionalDependencies: 1131 | supports-color: 10.0.0 1132 | 1133 | debug@4.4.1: 1134 | dependencies: 1135 | ms: 2.1.3 1136 | 1137 | deep-eql@5.0.2: {} 1138 | 1139 | es-module-lexer@1.7.0: {} 1140 | 1141 | esbuild@0.25.4: 1142 | optionalDependencies: 1143 | '@esbuild/aix-ppc64': 0.25.4 1144 | '@esbuild/android-arm': 0.25.4 1145 | '@esbuild/android-arm64': 0.25.4 1146 | '@esbuild/android-x64': 0.25.4 1147 | '@esbuild/darwin-arm64': 0.25.4 1148 | '@esbuild/darwin-x64': 0.25.4 1149 | '@esbuild/freebsd-arm64': 0.25.4 1150 | '@esbuild/freebsd-x64': 0.25.4 1151 | '@esbuild/linux-arm': 0.25.4 1152 | '@esbuild/linux-arm64': 0.25.4 1153 | '@esbuild/linux-ia32': 0.25.4 1154 | '@esbuild/linux-loong64': 0.25.4 1155 | '@esbuild/linux-mips64el': 0.25.4 1156 | '@esbuild/linux-ppc64': 0.25.4 1157 | '@esbuild/linux-riscv64': 0.25.4 1158 | '@esbuild/linux-s390x': 0.25.4 1159 | '@esbuild/linux-x64': 0.25.4 1160 | '@esbuild/netbsd-arm64': 0.25.4 1161 | '@esbuild/netbsd-x64': 0.25.4 1162 | '@esbuild/openbsd-arm64': 0.25.4 1163 | '@esbuild/openbsd-x64': 0.25.4 1164 | '@esbuild/sunos-x64': 0.25.4 1165 | '@esbuild/win32-arm64': 0.25.4 1166 | '@esbuild/win32-ia32': 0.25.4 1167 | '@esbuild/win32-x64': 0.25.4 1168 | 1169 | estree-walker@3.0.3: 1170 | dependencies: 1171 | '@types/estree': 1.0.7 1172 | 1173 | expect-type@1.2.1: {} 1174 | 1175 | fast-deep-equal@3.1.3: {} 1176 | 1177 | fast-glob@3.3.3: 1178 | dependencies: 1179 | '@nodelib/fs.stat': 2.0.5 1180 | '@nodelib/fs.walk': 1.2.8 1181 | glob-parent: 5.1.2 1182 | merge2: 1.4.1 1183 | micromatch: 4.0.8 1184 | 1185 | fastq@1.19.1: 1186 | dependencies: 1187 | reusify: 1.1.0 1188 | 1189 | fdir@6.4.4(picomatch@4.0.2): 1190 | optionalDependencies: 1191 | picomatch: 4.0.2 1192 | 1193 | fill-range@7.1.1: 1194 | dependencies: 1195 | to-regex-range: 5.0.1 1196 | 1197 | fsevents@2.3.3: 1198 | optional: true 1199 | 1200 | glob-parent@5.1.2: 1201 | dependencies: 1202 | is-glob: 4.0.3 1203 | 1204 | https-proxy-agent@7.0.6(supports-color@10.0.0): 1205 | dependencies: 1206 | agent-base: 7.1.3 1207 | debug: 4.4.0(supports-color@10.0.0) 1208 | transitivePeerDependencies: 1209 | - supports-color 1210 | 1211 | index-to-position@1.1.0: {} 1212 | 1213 | is-extglob@2.1.1: {} 1214 | 1215 | is-glob@4.0.3: 1216 | dependencies: 1217 | is-extglob: 2.1.1 1218 | 1219 | is-number@7.0.0: {} 1220 | 1221 | js-levenshtein@1.1.6: {} 1222 | 1223 | js-tokens@4.0.0: {} 1224 | 1225 | js-yaml@4.1.0: 1226 | dependencies: 1227 | argparse: 2.0.1 1228 | 1229 | json-schema-traverse@1.0.0: {} 1230 | 1231 | loupe@3.1.3: {} 1232 | 1233 | magic-string@0.30.17: 1234 | dependencies: 1235 | '@jridgewell/sourcemap-codec': 1.5.0 1236 | 1237 | merge2@1.4.1: {} 1238 | 1239 | micromatch@4.0.8: 1240 | dependencies: 1241 | braces: 3.0.3 1242 | picomatch: 2.3.1 1243 | 1244 | minimatch@10.0.1: 1245 | dependencies: 1246 | brace-expansion: 2.0.1 1247 | 1248 | minimatch@5.1.6: 1249 | dependencies: 1250 | brace-expansion: 2.0.1 1251 | 1252 | ms@2.1.3: {} 1253 | 1254 | nanoid@3.3.11: {} 1255 | 1256 | openapi-fetch@0.14.0: 1257 | dependencies: 1258 | openapi-typescript-helpers: 0.0.15 1259 | 1260 | openapi-typescript-helpers@0.0.15: {} 1261 | 1262 | openapi-typescript@7.8.0(typescript@5.8.3): 1263 | dependencies: 1264 | '@redocly/openapi-core': 1.34.3(supports-color@10.0.0) 1265 | ansi-colors: 4.1.3 1266 | change-case: 5.4.4 1267 | parse-json: 8.3.0 1268 | supports-color: 10.0.0 1269 | typescript: 5.8.3 1270 | yargs-parser: 21.1.1 1271 | 1272 | parse-json@8.3.0: 1273 | dependencies: 1274 | '@babel/code-frame': 7.27.1 1275 | index-to-position: 1.1.0 1276 | type-fest: 4.41.0 1277 | 1278 | path-browserify@1.0.1: {} 1279 | 1280 | pathe@2.0.3: {} 1281 | 1282 | pathval@2.0.0: {} 1283 | 1284 | picocolors@1.1.1: {} 1285 | 1286 | picomatch@2.3.1: {} 1287 | 1288 | picomatch@4.0.2: {} 1289 | 1290 | pluralize@8.0.0: {} 1291 | 1292 | postcss@8.5.3: 1293 | dependencies: 1294 | nanoid: 3.3.11 1295 | picocolors: 1.1.1 1296 | source-map-js: 1.2.1 1297 | 1298 | queue-microtask@1.2.3: {} 1299 | 1300 | require-from-string@2.0.2: {} 1301 | 1302 | reusify@1.1.0: {} 1303 | 1304 | rollup@4.41.0: 1305 | dependencies: 1306 | '@types/estree': 1.0.7 1307 | optionalDependencies: 1308 | '@rollup/rollup-android-arm-eabi': 4.41.0 1309 | '@rollup/rollup-android-arm64': 4.41.0 1310 | '@rollup/rollup-darwin-arm64': 4.41.0 1311 | '@rollup/rollup-darwin-x64': 4.41.0 1312 | '@rollup/rollup-freebsd-arm64': 4.41.0 1313 | '@rollup/rollup-freebsd-x64': 4.41.0 1314 | '@rollup/rollup-linux-arm-gnueabihf': 4.41.0 1315 | '@rollup/rollup-linux-arm-musleabihf': 4.41.0 1316 | '@rollup/rollup-linux-arm64-gnu': 4.41.0 1317 | '@rollup/rollup-linux-arm64-musl': 4.41.0 1318 | '@rollup/rollup-linux-loongarch64-gnu': 4.41.0 1319 | '@rollup/rollup-linux-powerpc64le-gnu': 4.41.0 1320 | '@rollup/rollup-linux-riscv64-gnu': 4.41.0 1321 | '@rollup/rollup-linux-riscv64-musl': 4.41.0 1322 | '@rollup/rollup-linux-s390x-gnu': 4.41.0 1323 | '@rollup/rollup-linux-x64-gnu': 4.41.0 1324 | '@rollup/rollup-linux-x64-musl': 4.41.0 1325 | '@rollup/rollup-win32-arm64-msvc': 4.41.0 1326 | '@rollup/rollup-win32-ia32-msvc': 4.41.0 1327 | '@rollup/rollup-win32-x64-msvc': 4.41.0 1328 | fsevents: 2.3.3 1329 | 1330 | run-parallel@1.2.0: 1331 | dependencies: 1332 | queue-microtask: 1.2.3 1333 | 1334 | siginfo@2.0.0: {} 1335 | 1336 | source-map-js@1.2.1: {} 1337 | 1338 | stackback@0.0.2: {} 1339 | 1340 | std-env@3.9.0: {} 1341 | 1342 | supports-color@10.0.0: {} 1343 | 1344 | tinybench@2.9.0: {} 1345 | 1346 | tinyexec@0.3.2: {} 1347 | 1348 | tinyglobby@0.2.13: 1349 | dependencies: 1350 | fdir: 6.4.4(picomatch@4.0.2) 1351 | picomatch: 4.0.2 1352 | 1353 | tinypool@1.0.2: {} 1354 | 1355 | tinyrainbow@2.0.0: {} 1356 | 1357 | tinyspy@3.0.2: {} 1358 | 1359 | tmp@0.2.3: {} 1360 | 1361 | to-regex-range@5.0.1: 1362 | dependencies: 1363 | is-number: 7.0.0 1364 | 1365 | ts-morph@26.0.0: 1366 | dependencies: 1367 | '@ts-morph/common': 0.27.0 1368 | code-block-writer: 13.0.3 1369 | 1370 | type-fest@4.41.0: {} 1371 | 1372 | typescript@5.8.3: {} 1373 | 1374 | undici-types@6.21.0: {} 1375 | 1376 | uri-js-replace@1.0.1: {} 1377 | 1378 | vite-node@3.1.4(@types/node@22.15.29): 1379 | dependencies: 1380 | cac: 6.7.14 1381 | debug: 4.4.1 1382 | es-module-lexer: 1.7.0 1383 | pathe: 2.0.3 1384 | vite: 6.3.5(@types/node@22.15.29) 1385 | transitivePeerDependencies: 1386 | - '@types/node' 1387 | - jiti 1388 | - less 1389 | - lightningcss 1390 | - sass 1391 | - sass-embedded 1392 | - stylus 1393 | - sugarss 1394 | - supports-color 1395 | - terser 1396 | - tsx 1397 | - yaml 1398 | 1399 | vite@6.3.5(@types/node@22.15.29): 1400 | dependencies: 1401 | esbuild: 0.25.4 1402 | fdir: 6.4.4(picomatch@4.0.2) 1403 | picomatch: 4.0.2 1404 | postcss: 8.5.3 1405 | rollup: 4.41.0 1406 | tinyglobby: 0.2.13 1407 | optionalDependencies: 1408 | '@types/node': 22.15.29 1409 | fsevents: 2.3.3 1410 | 1411 | vitest@3.1.4(@types/node@22.15.29): 1412 | dependencies: 1413 | '@vitest/expect': 3.1.4 1414 | '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@22.15.29)) 1415 | '@vitest/pretty-format': 3.1.4 1416 | '@vitest/runner': 3.1.4 1417 | '@vitest/snapshot': 3.1.4 1418 | '@vitest/spy': 3.1.4 1419 | '@vitest/utils': 3.1.4 1420 | chai: 5.2.0 1421 | debug: 4.4.1 1422 | expect-type: 1.2.1 1423 | magic-string: 0.30.17 1424 | pathe: 2.0.3 1425 | std-env: 3.9.0 1426 | tinybench: 2.9.0 1427 | tinyexec: 0.3.2 1428 | tinyglobby: 0.2.13 1429 | tinypool: 1.0.2 1430 | tinyrainbow: 2.0.0 1431 | vite: 6.3.5(@types/node@22.15.29) 1432 | vite-node: 3.1.4(@types/node@22.15.29) 1433 | why-is-node-running: 2.3.0 1434 | optionalDependencies: 1435 | '@types/node': 22.15.29 1436 | transitivePeerDependencies: 1437 | - jiti 1438 | - less 1439 | - lightningcss 1440 | - msw 1441 | - sass 1442 | - sass-embedded 1443 | - stylus 1444 | - sugarss 1445 | - supports-color 1446 | - terser 1447 | - tsx 1448 | - yaml 1449 | 1450 | why-is-node-running@2.3.0: 1451 | dependencies: 1452 | siginfo: 2.0.0 1453 | stackback: 0.0.2 1454 | 1455 | yaml-ast-parser@0.0.43: {} 1456 | 1457 | yargs-parser@21.1.1: {} 1458 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | ignoredBuiltDependencies: 2 | - esbuild 3 | 4 | onlyBuiltDependencies: 5 | - '@biomejs/biome' 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "node:fs"; 4 | import path from "node:path"; 5 | import { Command } from "commander"; 6 | import { version } from "../package.json"; 7 | import { generateClient } from "./index"; 8 | 9 | const program = new Command(); 10 | 11 | program 12 | .name("openapi-fetch-gen") 13 | .description( 14 | "Generate TypeScript API client from OpenAPI TypeScript definitions", 15 | ) 16 | .version(version) 17 | .requiredOption( 18 | "-i, --input ", 19 | "path to input OpenAPI TypeScript definition file", 20 | ) 21 | .option( 22 | "-o, --output ", 23 | "path to output generated client file", 24 | "./client.ts", 25 | ) 26 | .option( 27 | "--use-operation-id", 28 | "use operationId from OpenAPI schema for method names instead of generating from path", 29 | false, 30 | ) 31 | .parse(process.argv); 32 | 33 | const options = program.opts(); 34 | 35 | try { 36 | const start = new Date().getTime() / 1000.0; 37 | 38 | const inputPath = path.resolve(options["input"]); 39 | const outputPath = path.resolve(options["output"]); 40 | const useOperationId = options["useOperationId"] || false; 41 | 42 | if (!fs.existsSync(inputPath)) { 43 | console.error(`Error: Input file not found: ${inputPath}`); 44 | process.exit(1); 45 | } 46 | 47 | const clientCode = generateClient(inputPath, { useOperationId }); 48 | 49 | fs.writeFileSync(outputPath, clientCode); 50 | 51 | const end = new Date().getTime() / 1000.0; 52 | 53 | console.log( 54 | `🏁 Successfully generated client at [${(end - start).toFixed(2)}ms]: ${outputPath}`, 55 | ); 56 | } catch (error) { 57 | console.error( 58 | "😵 Error:", 59 | error instanceof Error ? error.message : String(error), 60 | ); 61 | process.exit(1); 62 | } 63 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | import tmp from "tmp"; 5 | import { 6 | type InterfaceDeclaration, 7 | ModuleKind, 8 | Node, 9 | Project, 10 | ScriptTarget, 11 | type SourceFile, 12 | SyntaxKind, 13 | } from "ts-morph"; 14 | 15 | /** 16 | * Generates a TypeScript API client using openapi-fetch based on TypeScript interface definitions 17 | * generated by openapi-typescript. 18 | */ 19 | export function generateClient( 20 | schemaFilePath: string, 21 | options: { useOperationId?: boolean } = {}, 22 | ): string { 23 | const project = new Project({ 24 | compilerOptions: { 25 | target: ScriptTarget.Latest, 26 | module: ModuleKind.ESNext, 27 | }, 28 | }); 29 | const schemaFile = project.addSourceFileAtPath(schemaFilePath); 30 | 31 | const pathsInterface = findInterface(schemaFile, "paths"); 32 | if (!pathsInterface) { 33 | throw new Error(`Interface "paths" not found in ${schemaFilePath}`); 34 | } 35 | const componentsInterface = findInterface(schemaFile, "components"); 36 | 37 | return generateClientCode( 38 | pathsInterface, 39 | componentsInterface, 40 | path.basename(schemaFilePath), 41 | options, 42 | ); 43 | } 44 | 45 | function findInterface( 46 | sourceFile: SourceFile, 47 | interfaceName: string, 48 | ): InterfaceDeclaration | undefined { 49 | return sourceFile.getInterfaces().find((i) => i.getName() === interfaceName); 50 | } 51 | 52 | interface EndpointInfo { 53 | path: string; 54 | httpMethod: string; 55 | operationName: string; 56 | operationId: string | null; 57 | commentLines: string[]; 58 | paramsType: string | null; 59 | bodyType: string | null; 60 | headerType: string | null; 61 | } 62 | 63 | function generateClientCode( 64 | pathsInterface: InterfaceDeclaration, 65 | componentsInterface: InterfaceDeclaration | undefined, 66 | schemaFileName: string, 67 | options: { useOperationId?: boolean } = {}, 68 | ): string { 69 | const endpoints = extractEndpointsInfo(pathsInterface); 70 | 71 | const clientClass = generateClientClass(endpoints, options); 72 | 73 | const code = [ 74 | "// THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen.", 75 | "// DO NOT EDIT THIS FILE MANUALLY.", 76 | "// See Also: https://github.com/moznion/openapi-fetch-gen", 77 | `import createClient, { type ClientOptions } from "openapi-fetch";`, 78 | `import type { components, paths } from "./${schemaFileName}"; // generated by openapi-typescript`, 79 | "", 80 | clientClass, 81 | "", 82 | ...generateSchemaTypes(componentsInterface), 83 | ].join("\n"); 84 | 85 | const tempFile = tmp.fileSync({ postfix: ".ts" }).name; 86 | fs.writeFileSync(tempFile, code); 87 | execSync(`npx biome check --write ${tempFile}`); 88 | return fs.readFileSync(tempFile, "utf-8"); 89 | } 90 | 91 | function generateSchemaTypes( 92 | componentsInterface: InterfaceDeclaration | undefined, 93 | ): string[] { 94 | const schemasProperty = componentsInterface?.getProperty("schemas"); 95 | 96 | const types = []; 97 | if (schemasProperty) { 98 | const schemasType = schemasProperty.getType(); 99 | for (const schema of schemasType.getProperties()) { 100 | const schemaType = schema.getValueDeclaration()?.getType(); 101 | if (schemaType) { 102 | const schemaName = schema.getName(); 103 | const schemaTypeName = ( 104 | schemaName + (schemaName === "Client" ? "Schema" : "") 105 | ).replaceAll(/-/g, "_"); 106 | types.push( 107 | `export type ${schemaTypeName} = components["schemas"]["${schema.getName()}"];`, 108 | ); 109 | } 110 | } 111 | } 112 | 113 | return types; 114 | } 115 | 116 | function extractEndpointsInfo( 117 | pathsInterface: InterfaceDeclaration, 118 | ): EndpointInfo[] { 119 | const endpoints: EndpointInfo[] = []; 120 | 121 | for (const property of pathsInterface.getProperties()) { 122 | const path = property.getName().replace(/['"]/g, ""); 123 | const propertyType = property.getType(); 124 | 125 | const httpMethods = [ 126 | "get", 127 | "post", 128 | "put", 129 | "delete", 130 | "patch", 131 | "head", 132 | "options", 133 | ]; 134 | 135 | for (const httpMethod of httpMethods) { 136 | // Get the operation type 137 | const methodProperty = propertyType.getProperty(httpMethod); 138 | if (!methodProperty) { 139 | continue; 140 | } 141 | if (methodProperty.getTypeAtLocation(property).getText() === "never") { 142 | continue; 143 | } 144 | 145 | const commentLines: string[] = []; 146 | let paramsType: string | null = null; 147 | let requestBodyType: string | null = null; 148 | 149 | const declarations = methodProperty.getDeclarations(); 150 | if (declarations.length <= 0) { 151 | continue; 152 | } 153 | const decl = declarations[0]; 154 | 155 | if (!Node.isPropertySignature(decl)) { 156 | continue; 157 | } 158 | 159 | const typeNode = decl.getTypeNodeOrThrow(); 160 | 161 | const operationId = (() => { 162 | if ( 163 | Node.isIndexedAccessTypeNode(typeNode) && 164 | typeNode.getObjectTypeNode().getText() === "operations" 165 | ) { 166 | const indexTypeNode = typeNode.getIndexTypeNode(); 167 | if ( 168 | Node.isLiteralTypeNode(indexTypeNode) && 169 | indexTypeNode.getLiteral().getKind() === SyntaxKind.StringLiteral 170 | ) { 171 | return sanitizeForOperation( 172 | indexTypeNode.getLiteral().getText().slice(1, -1), 173 | ); 174 | } 175 | } 176 | return null; 177 | })(); 178 | 179 | const paramProp = decl.getType().getPropertyOrThrow("parameters"); 180 | const paramTypes = paramProp 181 | .getTypeAtLocation(decl) 182 | .getProperties() 183 | .map((prop) => { 184 | const propType = prop.getTypeAtLocation(decl); 185 | const text = propType.getText(); 186 | const name = prop.getName(); 187 | if (text === "never") { 188 | return { name, text: "" }; 189 | } 190 | return { name, text }; 191 | }) 192 | .filter((kv) => kv["text"] !== ""); 193 | 194 | const headerType = paramTypes.find((kv) => kv.name === "header"); 195 | if (headerType) { 196 | // header exists in params 197 | const nonHeaderParams = paramTypes 198 | .filter((kv) => kv.name !== "header") 199 | .map((kv) => `${kv.name}: ${kv.text}`) 200 | .join("\n"); 201 | 202 | paramsType = 203 | `[ 204 | Exclude< // Missed Header Keys for default headers 205 | keyof ${headerType["text"]}, 206 | Extract< // Provided header keys by default headers' keys 207 | keyof HT, keyof ${headerType["text"]} 208 | > 209 | >, 210 | ] extends [never] ? ` + 211 | // When the default headers cover all the headers (i.e. `Exclude<...>` derived as `never`), 212 | // header parameter becomes optional (omitting or )overriding default headers. 213 | `{ 214 | header?: ${headerType["text"]}, 215 | ${nonHeaderParams} 216 | } : ` + // Else, header parameter is required as either follows: 217 | // 1. requires sorely missed header values 218 | // 2. requires all the header values (overriding default headers) 219 | `{ 220 | header: 221 | | (Pick< // Pick the header keys that are not in the default headers 222 | ${headerType["text"]}, 223 | Exclude< // Missed Header Keys for default headers 224 | keyof ${headerType["text"]}, 225 | Extract< // Provided header keys by default headers' keys 226 | keyof HT, keyof ${headerType["text"]} 227 | > 228 | > 229 | > & 230 | Partial< // Disallow default headers' keys to be in the header param 231 | Record< 232 | Extract< // Provided header keys by default headers' keys 233 | keyof HT, keyof ${headerType["text"]} 234 | >, 235 | never 236 | > 237 | >) 238 | | ${headerType["text"]}, 239 | ${nonHeaderParams} 240 | } 241 | `; 242 | } else { 243 | const params = paramTypes 244 | .map((kv) => `${kv.name}: ${kv.text}`) 245 | .join("\n"); 246 | if (params !== "") { 247 | paramsType = `{${params}}`; 248 | } 249 | } 250 | 251 | const requestBodyProp = decl.getType().getProperty("requestBody"); 252 | if (requestBodyProp) { 253 | const t = requestBodyProp.getTypeAtLocation(decl); 254 | if (t.getText() !== "never") { 255 | const contentProp = t.getPropertyOrThrow("content"); 256 | const contentType = contentProp.getTypeAtLocation(decl); 257 | const contentTypeProps = contentType.getProperties(); 258 | 259 | if (contentTypeProps.length > 0 && contentTypeProps[0]) { 260 | requestBodyType = contentTypeProps[0] 261 | .getTypeAtLocation(decl) 262 | .getText(); 263 | } 264 | } 265 | } 266 | 267 | if ("getJsDocs" in decl && typeof decl.getJsDocs === "function") { 268 | const jsDocs = decl.getJsDocs(); 269 | for (const d of jsDocs) { 270 | const description = d.getDescription().trim(); 271 | if (description) { 272 | commentLines.push(`* ${description}`); 273 | } 274 | } 275 | } 276 | 277 | const sanitizedPath = sanitizeForOperation(path); 278 | 279 | // Generate a camelCase operation name 280 | const pathSegments = sanitizedPath.split("_"); 281 | const camelCasePath = pathSegments 282 | .map((segment, index) => { 283 | // First segment is lowercase, rest are capitalized 284 | if (index === 0) return segment.toLowerCase(); 285 | return ( 286 | segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase() 287 | ); 288 | }) 289 | .join(""); 290 | 291 | const defaultOperationName = `${httpMethod}${camelCasePath.charAt(0).toUpperCase()}${camelCasePath.slice(1)}`; 292 | 293 | endpoints.push({ 294 | path, 295 | httpMethod, 296 | operationName: defaultOperationName, 297 | operationId, 298 | commentLines, 299 | paramsType, 300 | bodyType: requestBodyType, 301 | headerType: headerType ? headerType["text"] : null, 302 | }); 303 | } 304 | } 305 | 306 | return endpoints; 307 | } 308 | 309 | function generateClientClass( 310 | endpoints: EndpointInfo[], 311 | options: { useOperationId?: boolean } = {}, 312 | ): string { 313 | const classCode = [ 314 | `export class Client> { 315 | private readonly client; 316 | private readonly defaultHeaders: HT; 317 | 318 | constructor(clientOptions: ClientOptions, defaultHeaders?: HT) { 319 | this.client = createClient(clientOptions); 320 | this.defaultHeaders = defaultHeaders ?? ({} as HT); 321 | } 322 | `, 323 | ]; 324 | 325 | // Generate class methods for each endpoint 326 | for (const endpoint of endpoints) { 327 | const { 328 | path, 329 | httpMethod, 330 | operationName, 331 | operationId, 332 | commentLines, 333 | paramsType, 334 | bodyType, 335 | } = endpoint; 336 | 337 | const methodName = 338 | options.useOperationId && operationId ? operationId : operationName; 339 | 340 | // Add JSDoc comment if available 341 | if (commentLines.length > 0) { 342 | classCode.push(" /**"); 343 | for (const line of commentLines) { 344 | classCode.push(` ${line}`); 345 | } 346 | classCode.push(" */"); 347 | } 348 | 349 | // Method signature 350 | classCode.push(` async ${methodName}(`); 351 | 352 | // Add method parameters with proper indentation 353 | const paramsList = []; 354 | if (paramsType) { 355 | paramsList.push(`params: ${paramsType}`); 356 | } 357 | 358 | if (bodyType) { 359 | // Properly indent the body by ensuring it starts with correct indentation 360 | paramsList.push(`body: ${bodyType}`); 361 | } 362 | 363 | if (paramsList.length > 0) { 364 | classCode.push(paramsList.join(",\n")); 365 | } 366 | 367 | classCode.push(" ) {"); 368 | 369 | // Method body 370 | classCode.push( 371 | ` return await this.client.${httpMethod.toUpperCase()}("${path}", {`, 372 | ); 373 | 374 | if (paramsType) { 375 | if (endpoint.headerType) { 376 | classCode.push(`params: { 377 | ...params, 378 | header: {...this.defaultHeaders, ...params.header} as ${endpoint.headerType}, 379 | },`); 380 | } else { 381 | classCode.push("params,"); 382 | } 383 | } 384 | 385 | if (bodyType) { 386 | classCode.push(" body,"); 387 | } 388 | 389 | classCode.push(" });"); 390 | classCode.push(" }"); 391 | classCode.push(""); 392 | } 393 | 394 | classCode.push("}"); 395 | classCode.push(""); 396 | 397 | return classCode.join("\n"); 398 | } 399 | 400 | function sanitizeForOperation(operation: string): string { 401 | return operation 402 | .replace(/[{}]/g, "") 403 | .replace(/[-/.]/g, "_") 404 | .replace(/^_/, ""); // Remove leading underscore if present 405 | } 406 | -------------------------------------------------------------------------------- /src/integration.test.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | import { beforeAll, describe, expect, it } from "vitest"; 5 | 6 | describe("integration test", () => { 7 | beforeAll(() => { 8 | execSync("pnpm generate_test_resource", { stdio: "inherit" }); 9 | execSync("pnpm fix", { stdio: "inherit" }); 10 | execSync("pnpm build", { stdio: "inherit" }); 11 | }); 12 | 13 | it("should generate client code correctly", () => { 14 | execSync( 15 | "node ./dist/cli.js --input ./src/test_resources/schema.d.ts --output ./src/test_resources/generated_client.ts", 16 | { stdio: "inherit" }, 17 | ); 18 | execSync("pnpm build", { stdio: "inherit" }); 19 | 20 | const generatedPath = path.resolve( 21 | "./src/test_resources/generated_client.ts", 22 | ); 23 | const expectedPath = path.resolve( 24 | "./src/test_resources/expected_client.ts", 25 | ); 26 | 27 | const generatedContent = fs.readFileSync(generatedPath, "utf8"); 28 | const expectedContent = fs.readFileSync(expectedPath, "utf8"); 29 | 30 | expect(generatedContent).toBe(expectedContent); 31 | }, 30000); 32 | 33 | it("should generate client code correctly", () => { 34 | execSync( 35 | "node ./dist/cli.js --input ./src/test_resources/schema.d.ts --output ./src/test_resources/generated_client.ts --use-operation-id", 36 | { stdio: "inherit" }, 37 | ); 38 | execSync("pnpm build", { stdio: "inherit" }); 39 | 40 | const generatedPath = path.resolve( 41 | "./src/test_resources/generated_client.ts", 42 | ); 43 | const expectedPath = path.resolve( 44 | "./src/test_resources/expected_client_with_operation_id.ts", 45 | ); 46 | 47 | const generatedContent = fs.readFileSync(generatedPath, "utf8"); 48 | const expectedContent = fs.readFileSync(expectedPath, "utf8"); 49 | 50 | expect(generatedContent).toBe(expectedContent); 51 | }, 30000); 52 | }); 53 | -------------------------------------------------------------------------------- /src/test_resources/expected_client.ts: -------------------------------------------------------------------------------- 1 | // THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen. 2 | // DO NOT EDIT THIS FILE MANUALLY. 3 | // See Also: https://github.com/moznion/openapi-fetch-gen 4 | import createClient, { type ClientOptions } from "openapi-fetch"; 5 | import type { components, paths } from "./schema.d.ts"; // generated by openapi-typescript 6 | 7 | export class Client> { 8 | private readonly client; 9 | private readonly defaultHeaders: HT; 10 | 11 | constructor(clientOptions: ClientOptions, defaultHeaders?: HT) { 12 | this.client = createClient(clientOptions); 13 | this.defaultHeaders = defaultHeaders ?? ({} as HT); 14 | } 15 | 16 | /** 17 | * List users 18 | */ 19 | async getUsers(params: { 20 | query: { 21 | page?: number; 22 | pageSize?: number; 23 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 24 | }; 25 | }) { 26 | return await this.client.GET("/users", { 27 | params, 28 | }); 29 | } 30 | 31 | /** 32 | * Create a new user 33 | */ 34 | async postUsers(body: { 35 | name: string; 36 | email: string; 37 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 38 | address?: { 39 | postalCode?: string; 40 | street: string; 41 | city: string; 42 | country: string; 43 | }; 44 | }) { 45 | return await this.client.POST("/users", { 46 | body, 47 | }); 48 | } 49 | 50 | /** 51 | * Get user details 52 | */ 53 | async getUsersUserid(params: { path: { userId: string } }) { 54 | return await this.client.GET("/users/{userId}", { 55 | params, 56 | }); 57 | } 58 | 59 | /** 60 | * Replace user 61 | */ 62 | async putUsersUserid( 63 | params: { path: { userId: string } }, 64 | body: { 65 | name: string; 66 | email: string; 67 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 68 | address?: { 69 | postalCode?: string; 70 | street: string; 71 | city: string; 72 | country: string; 73 | }; 74 | } & { id: string }, 75 | ) { 76 | return await this.client.PUT("/users/{userId}", { 77 | params, 78 | body, 79 | }); 80 | } 81 | 82 | /** 83 | * Delete user 84 | */ 85 | async deleteUsersUserid(params: { path: { userId: string } }) { 86 | return await this.client.DELETE("/users/{userId}", { 87 | params, 88 | }); 89 | } 90 | 91 | /** 92 | * Update user fields 93 | */ 94 | async patchUsersUserid( 95 | params: { path: { userId: string } }, 96 | body: { 97 | name?: string; 98 | email?: string; 99 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 100 | address?: { 101 | postalCode?: string; 102 | street: string; 103 | city: string; 104 | country: string; 105 | }; 106 | }, 107 | ) { 108 | return await this.client.PATCH("/users/{userId}", { 109 | params, 110 | body, 111 | }); 112 | } 113 | 114 | /** 115 | * List user loans 116 | */ 117 | async getUsersUseridLoans(params: { path: { userId: string } }) { 118 | return await this.client.GET("/users/{userId}/loans", { 119 | params, 120 | }); 121 | } 122 | 123 | /** 124 | * Search users 125 | */ 126 | async postUsersSearch(body: { 127 | name?: string; 128 | email?: string; 129 | joinedAfter?: string; 130 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 131 | }) { 132 | return await this.client.POST("/users/search", { 133 | body, 134 | }); 135 | } 136 | 137 | /** 138 | * Bulk user import 139 | */ 140 | async postUsersBulk(body: { file?: string }) { 141 | return await this.client.POST("/users/bulk", { 142 | body, 143 | }); 144 | } 145 | 146 | /** 147 | * Get bulk job status 148 | */ 149 | async getUsersBulkJobid( 150 | params: [ 151 | Exclude< 152 | // Missed Header Keys for default headers 153 | keyof { 154 | Authorization: string; 155 | "Application-Version": string; 156 | "Something-Id": string; 157 | }, 158 | Extract< 159 | // Provided header keys by default headers' keys 160 | keyof HT, 161 | keyof { 162 | Authorization: string; 163 | "Application-Version": string; 164 | "Something-Id": string; 165 | } 166 | > 167 | >, 168 | ] extends [never] 169 | ? { 170 | header?: { 171 | Authorization: string; 172 | "Application-Version": string; 173 | "Something-Id": string; 174 | }; 175 | path: { jobId: string }; 176 | } 177 | : { 178 | header: 179 | | (Pick< 180 | // Pick the header keys that are not in the default headers 181 | { 182 | Authorization: string; 183 | "Application-Version": string; 184 | "Something-Id": string; 185 | }, 186 | Exclude< 187 | // Missed Header Keys for default headers 188 | keyof { 189 | Authorization: string; 190 | "Application-Version": string; 191 | "Something-Id": string; 192 | }, 193 | Extract< 194 | // Provided header keys by default headers' keys 195 | keyof HT, 196 | keyof { 197 | Authorization: string; 198 | "Application-Version": string; 199 | "Something-Id": string; 200 | } 201 | > 202 | > 203 | > & 204 | Partial< 205 | // Disallow default headers' keys to be in the header param 206 | Record< 207 | Extract< 208 | // Provided header keys by default headers' keys 209 | keyof HT, 210 | keyof { 211 | Authorization: string; 212 | "Application-Version": string; 213 | "Something-Id": string; 214 | } 215 | >, 216 | never 217 | > 218 | >) 219 | | { 220 | Authorization: string; 221 | "Application-Version": string; 222 | "Something-Id": string; 223 | }; 224 | path: { jobId: string }; 225 | }, 226 | ) { 227 | return await this.client.GET("/users/bulk/{jobId}", { 228 | params: { 229 | ...params, 230 | header: { ...this.defaultHeaders, ...params.header } as { 231 | Authorization: string; 232 | "Application-Version": string; 233 | "Something-Id": string; 234 | }, 235 | }, 236 | }); 237 | } 238 | } 239 | 240 | export type Error = components["schemas"]["Error"]; 241 | export type User = components["schemas"]["User"]; 242 | export type Address = components["schemas"]["Address"]; 243 | export type UserCreate = components["schemas"]["UserCreate"]; 244 | export type UserUpdate = components["schemas"]["UserUpdate"]; 245 | export type UserPatch = components["schemas"]["UserPatch"]; 246 | export type UserList = components["schemas"]["UserList"]; 247 | export type UserPage = components["schemas"]["UserPage"]; 248 | export type Loan = components["schemas"]["Loan"]; 249 | export type Book = components["schemas"]["Book"]; 250 | export type BulkJobStatus = components["schemas"]["BulkJobStatus"]; 251 | export type ClientSchema = components["schemas"]["Client"]; 252 | export type schema_Something = components["schemas"]["schema-Something"]; 253 | -------------------------------------------------------------------------------- /src/test_resources/expected_client_with_operation_id.ts: -------------------------------------------------------------------------------- 1 | // THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen. 2 | // DO NOT EDIT THIS FILE MANUALLY. 3 | // See Also: https://github.com/moznion/openapi-fetch-gen 4 | import createClient, { type ClientOptions } from "openapi-fetch"; 5 | import type { components, paths } from "./schema.d.ts"; // generated by openapi-typescript 6 | 7 | export class Client> { 8 | private readonly client; 9 | private readonly defaultHeaders: HT; 10 | 11 | constructor(clientOptions: ClientOptions, defaultHeaders?: HT) { 12 | this.client = createClient(clientOptions); 13 | this.defaultHeaders = defaultHeaders ?? ({} as HT); 14 | } 15 | 16 | /** 17 | * List users 18 | */ 19 | async listUsers(params: { 20 | query: { 21 | page?: number; 22 | pageSize?: number; 23 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 24 | }; 25 | }) { 26 | return await this.client.GET("/users", { 27 | params, 28 | }); 29 | } 30 | 31 | /** 32 | * Create a new user 33 | */ 34 | async createUser(body: { 35 | name: string; 36 | email: string; 37 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 38 | address?: { 39 | postalCode?: string; 40 | street: string; 41 | city: string; 42 | country: string; 43 | }; 44 | }) { 45 | return await this.client.POST("/users", { 46 | body, 47 | }); 48 | } 49 | 50 | /** 51 | * Get user details 52 | */ 53 | async getUserDetails(params: { path: { userId: string } }) { 54 | return await this.client.GET("/users/{userId}", { 55 | params, 56 | }); 57 | } 58 | 59 | /** 60 | * Replace user 61 | */ 62 | async replaceUser( 63 | params: { path: { userId: string } }, 64 | body: { 65 | name: string; 66 | email: string; 67 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 68 | address?: { 69 | postalCode?: string; 70 | street: string; 71 | city: string; 72 | country: string; 73 | }; 74 | } & { id: string }, 75 | ) { 76 | return await this.client.PUT("/users/{userId}", { 77 | params, 78 | body, 79 | }); 80 | } 81 | 82 | /** 83 | * Delete user 84 | */ 85 | async deleteUser(params: { path: { userId: string } }) { 86 | return await this.client.DELETE("/users/{userId}", { 87 | params, 88 | }); 89 | } 90 | 91 | /** 92 | * Update user fields 93 | */ 94 | async updateUserFields( 95 | params: { path: { userId: string } }, 96 | body: { 97 | name?: string; 98 | email?: string; 99 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 100 | address?: { 101 | postalCode?: string; 102 | street: string; 103 | city: string; 104 | country: string; 105 | }; 106 | }, 107 | ) { 108 | return await this.client.PATCH("/users/{userId}", { 109 | params, 110 | body, 111 | }); 112 | } 113 | 114 | /** 115 | * List user loans 116 | */ 117 | async listUserLoans(params: { path: { userId: string } }) { 118 | return await this.client.GET("/users/{userId}/loans", { 119 | params, 120 | }); 121 | } 122 | 123 | /** 124 | * Search users 125 | */ 126 | async searchUsers(body: { 127 | name?: string; 128 | email?: string; 129 | joinedAfter?: string; 130 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 131 | }) { 132 | return await this.client.POST("/users/search", { 133 | body, 134 | }); 135 | } 136 | 137 | /** 138 | * Bulk user import 139 | */ 140 | async bulk_User_Imp_ort(body: { file?: string }) { 141 | return await this.client.POST("/users/bulk", { 142 | body, 143 | }); 144 | } 145 | 146 | /** 147 | * Get bulk job status 148 | */ 149 | async getUsersBulkJobid( 150 | params: [ 151 | Exclude< 152 | // Missed Header Keys for default headers 153 | keyof { 154 | Authorization: string; 155 | "Application-Version": string; 156 | "Something-Id": string; 157 | }, 158 | Extract< 159 | // Provided header keys by default headers' keys 160 | keyof HT, 161 | keyof { 162 | Authorization: string; 163 | "Application-Version": string; 164 | "Something-Id": string; 165 | } 166 | > 167 | >, 168 | ] extends [never] 169 | ? { 170 | header?: { 171 | Authorization: string; 172 | "Application-Version": string; 173 | "Something-Id": string; 174 | }; 175 | path: { jobId: string }; 176 | } 177 | : { 178 | header: 179 | | (Pick< 180 | // Pick the header keys that are not in the default headers 181 | { 182 | Authorization: string; 183 | "Application-Version": string; 184 | "Something-Id": string; 185 | }, 186 | Exclude< 187 | // Missed Header Keys for default headers 188 | keyof { 189 | Authorization: string; 190 | "Application-Version": string; 191 | "Something-Id": string; 192 | }, 193 | Extract< 194 | // Provided header keys by default headers' keys 195 | keyof HT, 196 | keyof { 197 | Authorization: string; 198 | "Application-Version": string; 199 | "Something-Id": string; 200 | } 201 | > 202 | > 203 | > & 204 | Partial< 205 | // Disallow default headers' keys to be in the header param 206 | Record< 207 | Extract< 208 | // Provided header keys by default headers' keys 209 | keyof HT, 210 | keyof { 211 | Authorization: string; 212 | "Application-Version": string; 213 | "Something-Id": string; 214 | } 215 | >, 216 | never 217 | > 218 | >) 219 | | { 220 | Authorization: string; 221 | "Application-Version": string; 222 | "Something-Id": string; 223 | }; 224 | path: { jobId: string }; 225 | }, 226 | ) { 227 | return await this.client.GET("/users/bulk/{jobId}", { 228 | params: { 229 | ...params, 230 | header: { ...this.defaultHeaders, ...params.header } as { 231 | Authorization: string; 232 | "Application-Version": string; 233 | "Something-Id": string; 234 | }, 235 | }, 236 | }); 237 | } 238 | } 239 | 240 | export type Error = components["schemas"]["Error"]; 241 | export type User = components["schemas"]["User"]; 242 | export type Address = components["schemas"]["Address"]; 243 | export type UserCreate = components["schemas"]["UserCreate"]; 244 | export type UserUpdate = components["schemas"]["UserUpdate"]; 245 | export type UserPatch = components["schemas"]["UserPatch"]; 246 | export type UserList = components["schemas"]["UserList"]; 247 | export type UserPage = components["schemas"]["UserPage"]; 248 | export type Loan = components["schemas"]["Loan"]; 249 | export type Book = components["schemas"]["Book"]; 250 | export type BulkJobStatus = components["schemas"]["BulkJobStatus"]; 251 | export type ClientSchema = components["schemas"]["Client"]; 252 | export type schema_Something = components["schemas"]["schema-Something"]; 253 | -------------------------------------------------------------------------------- /src/test_resources/schema.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by openapi-typescript. 3 | * Do not make direct changes to the file. 4 | */ 5 | 6 | export interface paths { 7 | "/users": { 8 | parameters: { 9 | query?: never; 10 | header?: never; 11 | path?: never; 12 | cookie?: never; 13 | }; 14 | /** 15 | * List users 16 | * @description Retrieve all users with pagination. 17 | */ 18 | get: operations["listUsers"]; 19 | put?: never; 20 | /** 21 | * Create a new user 22 | * @description Register a new library user. 23 | */ 24 | post: operations["createUser"]; 25 | delete?: never; 26 | options?: never; 27 | head?: never; 28 | patch?: never; 29 | trace?: never; 30 | }; 31 | "/users/{userId}": { 32 | parameters: { 33 | query?: never; 34 | header?: never; 35 | path: { 36 | /** @description Unique user identifier (UUID) */ 37 | userId: components["parameters"]["userId"]; 38 | }; 39 | cookie?: never; 40 | }; 41 | /** 42 | * Get user details 43 | * @description Retrieve detailed information for a specific user. 44 | */ 45 | get: operations["getUserDetails"]; 46 | /** 47 | * Replace user 48 | * @description Replace a user's entire record. 49 | */ 50 | put: operations["replaceUser"]; 51 | post?: never; 52 | /** 53 | * Delete user 54 | * @description Soft-delete a user record. 55 | */ 56 | delete: operations["deleteUser"]; 57 | options?: never; 58 | head?: never; 59 | /** 60 | * Update user fields 61 | * @description Partially update a user's information. 62 | */ 63 | patch: operations["updateUserFields"]; 64 | trace?: never; 65 | }; 66 | "/users/{userId}/loans": { 67 | parameters: { 68 | query?: never; 69 | header?: never; 70 | path: { 71 | /** @description Unique user identifier (UUID) */ 72 | userId: components["parameters"]["userId"]; 73 | }; 74 | cookie?: never; 75 | }; 76 | /** 77 | * List user loans 78 | * @description Retrieve current loans for a specific user. 79 | */ 80 | get: operations["listUserLoans"]; 81 | put?: never; 82 | post?: never; 83 | delete?: never; 84 | options?: never; 85 | head?: never; 86 | patch?: never; 87 | trace?: never; 88 | }; 89 | "/users/search": { 90 | parameters: { 91 | query?: never; 92 | header?: never; 93 | path?: never; 94 | cookie?: never; 95 | }; 96 | get?: never; 97 | put?: never; 98 | /** 99 | * Search users 100 | * @description Search for users by multiple criteria. 101 | */ 102 | post: operations["searchUsers"]; 103 | delete?: never; 104 | options?: never; 105 | head?: never; 106 | patch?: never; 107 | trace?: never; 108 | }; 109 | "/users/bulk": { 110 | parameters: { 111 | query?: never; 112 | header?: never; 113 | path?: never; 114 | cookie?: never; 115 | }; 116 | get?: never; 117 | put?: never; 118 | /** 119 | * Bulk user import 120 | * @description Upload a CSV file to register multiple users at once. 121 | */ 122 | post: operations["_bulk-{User}.Imp/ort"]; 123 | delete?: never; 124 | options?: never; 125 | head?: never; 126 | patch?: never; 127 | trace?: never; 128 | }; 129 | "/users/bulk/{jobId}": { 130 | parameters: { 131 | query?: never; 132 | header: { 133 | /** @description Authorization Header */ 134 | Authorization: string; 135 | /** @description Application version */ 136 | "Application-Version": string; 137 | /** @description Identifier of something */ 138 | "Something-Id": string; 139 | }; 140 | path: { 141 | /** @description Bulk import job identifier */ 142 | jobId: string; 143 | }; 144 | cookie?: never; 145 | }; 146 | /** 147 | * Get bulk job status 148 | * @description Retrieve the status of a bulk import job. 149 | */ 150 | get: { 151 | parameters: { 152 | query?: never; 153 | header: { 154 | /** @description Authorization Header */ 155 | Authorization: string; 156 | /** @description Application version */ 157 | "Application-Version": string; 158 | /** @description Identifier of something */ 159 | "Something-Id": string; 160 | }; 161 | path: { 162 | /** @description Bulk import job identifier */ 163 | jobId: string; 164 | }; 165 | cookie?: never; 166 | }; 167 | requestBody?: never; 168 | responses: { 169 | /** @description Bulk job status */ 170 | 200: { 171 | headers: { 172 | [name: string]: unknown; 173 | }; 174 | content: { 175 | "application/json": components["schemas"]["BulkJobStatus"]; 176 | }; 177 | }; 178 | 400: components["responses"]["BadRequest"]; 179 | 401: components["responses"]["Unauthorized"]; 180 | 403: components["responses"]["Forbidden"]; 181 | 404: components["responses"]["NotFound"]; 182 | }; 183 | }; 184 | put?: never; 185 | post?: never; 186 | delete?: never; 187 | options?: never; 188 | head?: never; 189 | patch?: never; 190 | trace?: never; 191 | }; 192 | } 193 | export type webhooks = Record; 194 | export interface components { 195 | schemas: { 196 | Error: { 197 | /** @description HTTP status code */ 198 | code: number; 199 | /** @description Error message detailing the cause */ 200 | message: string; 201 | }; 202 | User: { 203 | /** Format: uuid */ 204 | id: string; 205 | name: string; 206 | /** Format: email */ 207 | email: string; 208 | /** @enum {string} */ 209 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 210 | /** Format: date-time */ 211 | registeredAt: string; 212 | address?: components["schemas"]["Address"]; 213 | }; 214 | Address: { 215 | postalCode?: string; 216 | street: string; 217 | city: string; 218 | country: string; 219 | }; 220 | UserCreate: { 221 | name: string; 222 | /** Format: email */ 223 | email: string; 224 | /** @enum {string} */ 225 | membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; 226 | address?: components["schemas"]["Address"]; 227 | }; 228 | UserUpdate: components["schemas"]["UserCreate"] & { 229 | /** Format: uuid */ 230 | id: string; 231 | }; 232 | /** @description Schema for partial updates – include only fields to change */ 233 | UserPatch: { 234 | name?: string; 235 | /** Format: email */ 236 | email?: string; 237 | /** @enum {string} */ 238 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 239 | address?: components["schemas"]["Address"]; 240 | }; 241 | UserList: components["schemas"]["User"][]; 242 | UserPage: { 243 | total: number; 244 | page: number; 245 | pageSize: number; 246 | items: components["schemas"]["UserList"]; 247 | }; 248 | Loan: { 249 | /** Format: uuid */ 250 | loanId: string; 251 | book: components["schemas"]["Book"]; 252 | /** Format: date-time */ 253 | borrowedAt: string; 254 | /** Format: date-time */ 255 | dueAt: string; 256 | }; 257 | Book: { 258 | isbn: string; 259 | title: string; 260 | author: string; 261 | }; 262 | BulkJobStatus: { 263 | jobId: string; 264 | /** @enum {string} */ 265 | status: "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED"; 266 | processed: number; 267 | total: number; 268 | }; 269 | Client: { 270 | id?: string; 271 | }; 272 | "schema-Something": { 273 | id?: string; 274 | }; 275 | }; 276 | responses: { 277 | /** @description Bad request due to invalid input */ 278 | BadRequest: { 279 | headers: { 280 | [name: string]: unknown; 281 | }; 282 | content: { 283 | "application/json": components["schemas"]["Error"]; 284 | }; 285 | }; 286 | /** @description Authentication required or failed */ 287 | Unauthorized: { 288 | headers: { 289 | [name: string]: unknown; 290 | }; 291 | content: { 292 | "application/json": components["schemas"]["Error"]; 293 | }; 294 | }; 295 | /** @description Insufficient permissions to access resource */ 296 | Forbidden: { 297 | headers: { 298 | [name: string]: unknown; 299 | }; 300 | content: { 301 | "application/json": components["schemas"]["Error"]; 302 | }; 303 | }; 304 | /** @description Resource not found */ 305 | NotFound: { 306 | headers: { 307 | [name: string]: unknown; 308 | }; 309 | content: { 310 | "application/json": components["schemas"]["Error"]; 311 | }; 312 | }; 313 | /** @description Conflict with current state of the resource */ 314 | Conflict: { 315 | headers: { 316 | [name: string]: unknown; 317 | }; 318 | content: { 319 | "application/json": components["schemas"]["Error"]; 320 | }; 321 | }; 322 | }; 323 | parameters: { 324 | /** @description Page number (starting at 1) */ 325 | page: number; 326 | /** @description Number of items per page */ 327 | pageSize: number; 328 | /** @description Unique user identifier (UUID) */ 329 | userId: string; 330 | }; 331 | requestBodies: never; 332 | headers: never; 333 | pathItems: never; 334 | } 335 | export type $defs = Record; 336 | export interface operations { 337 | listUsers: { 338 | parameters: { 339 | query?: { 340 | /** @description Page number (starting at 1) */ 341 | page?: components["parameters"]["page"]; 342 | /** @description Number of items per page */ 343 | pageSize?: components["parameters"]["pageSize"]; 344 | /** @description Filter by membership type */ 345 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 346 | }; 347 | header?: never; 348 | path?: never; 349 | cookie?: never; 350 | }; 351 | requestBody?: never; 352 | responses: { 353 | /** @description A paginated list of users */ 354 | 200: { 355 | headers: { 356 | [name: string]: unknown; 357 | }; 358 | content: { 359 | "application/json": components["schemas"]["UserPage"]; 360 | }; 361 | }; 362 | 400: components["responses"]["BadRequest"]; 363 | 401: components["responses"]["Unauthorized"]; 364 | 403: components["responses"]["Forbidden"]; 365 | }; 366 | }; 367 | createUser: { 368 | parameters: { 369 | query?: never; 370 | header?: never; 371 | path?: never; 372 | cookie?: never; 373 | }; 374 | requestBody: { 375 | content: { 376 | "application/json": components["schemas"]["UserCreate"]; 377 | }; 378 | }; 379 | responses: { 380 | /** @description User created successfully */ 381 | 201: { 382 | headers: { 383 | [name: string]: unknown; 384 | }; 385 | content: { 386 | "application/json": components["schemas"]["User"]; 387 | }; 388 | }; 389 | 400: components["responses"]["BadRequest"]; 390 | 401: components["responses"]["Unauthorized"]; 391 | 403: components["responses"]["Forbidden"]; 392 | 409: components["responses"]["Conflict"]; 393 | }; 394 | }; 395 | getUserDetails: { 396 | parameters: { 397 | query?: never; 398 | header?: never; 399 | path: { 400 | /** @description Unique user identifier (UUID) */ 401 | userId: components["parameters"]["userId"]; 402 | }; 403 | cookie?: never; 404 | }; 405 | requestBody?: never; 406 | responses: { 407 | /** @description User details */ 408 | 200: { 409 | headers: { 410 | [name: string]: unknown; 411 | }; 412 | content: { 413 | "application/json": components["schemas"]["User"]; 414 | }; 415 | }; 416 | 400: components["responses"]["BadRequest"]; 417 | 401: components["responses"]["Unauthorized"]; 418 | 403: components["responses"]["Forbidden"]; 419 | 404: components["responses"]["NotFound"]; 420 | }; 421 | }; 422 | replaceUser: { 423 | parameters: { 424 | query?: never; 425 | header?: never; 426 | path: { 427 | /** @description Unique user identifier (UUID) */ 428 | userId: components["parameters"]["userId"]; 429 | }; 430 | cookie?: never; 431 | }; 432 | requestBody: { 433 | content: { 434 | "application/json": components["schemas"]["UserUpdate"]; 435 | }; 436 | }; 437 | responses: { 438 | /** @description Updated user record */ 439 | 200: { 440 | headers: { 441 | [name: string]: unknown; 442 | }; 443 | content: { 444 | "application/json": components["schemas"]["User"]; 445 | }; 446 | }; 447 | 400: components["responses"]["BadRequest"]; 448 | 401: components["responses"]["Unauthorized"]; 449 | 403: components["responses"]["Forbidden"]; 450 | 404: components["responses"]["NotFound"]; 451 | }; 452 | }; 453 | deleteUser: { 454 | parameters: { 455 | query?: never; 456 | header?: never; 457 | path: { 458 | /** @description Unique user identifier (UUID) */ 459 | userId: components["parameters"]["userId"]; 460 | }; 461 | cookie?: never; 462 | }; 463 | requestBody?: never; 464 | responses: { 465 | /** @description User deleted (no content) */ 466 | 204: { 467 | headers: { 468 | [name: string]: unknown; 469 | }; 470 | content?: never; 471 | }; 472 | 400: components["responses"]["BadRequest"]; 473 | 401: components["responses"]["Unauthorized"]; 474 | 403: components["responses"]["Forbidden"]; 475 | 404: components["responses"]["NotFound"]; 476 | }; 477 | }; 478 | updateUserFields: { 479 | parameters: { 480 | query?: never; 481 | header?: never; 482 | path: { 483 | /** @description Unique user identifier (UUID) */ 484 | userId: components["parameters"]["userId"]; 485 | }; 486 | cookie?: never; 487 | }; 488 | requestBody: { 489 | content: { 490 | "application/json": components["schemas"]["UserPatch"]; 491 | }; 492 | }; 493 | responses: { 494 | /** @description Updated user record */ 495 | 200: { 496 | headers: { 497 | [name: string]: unknown; 498 | }; 499 | content: { 500 | "application/json": components["schemas"]["User"]; 501 | }; 502 | }; 503 | 400: components["responses"]["BadRequest"]; 504 | 401: components["responses"]["Unauthorized"]; 505 | 403: components["responses"]["Forbidden"]; 506 | 404: components["responses"]["NotFound"]; 507 | }; 508 | }; 509 | listUserLoans: { 510 | parameters: { 511 | query?: never; 512 | header?: never; 513 | path: { 514 | /** @description Unique user identifier (UUID) */ 515 | userId: components["parameters"]["userId"]; 516 | }; 517 | cookie?: never; 518 | }; 519 | requestBody?: never; 520 | responses: { 521 | /** @description List of loans */ 522 | 200: { 523 | headers: { 524 | [name: string]: unknown; 525 | }; 526 | content: { 527 | "application/json": components["schemas"]["Loan"][]; 528 | }; 529 | }; 530 | 400: components["responses"]["BadRequest"]; 531 | 401: components["responses"]["Unauthorized"]; 532 | 403: components["responses"]["Forbidden"]; 533 | 404: components["responses"]["NotFound"]; 534 | }; 535 | }; 536 | searchUsers: { 537 | parameters: { 538 | query?: never; 539 | header?: never; 540 | path?: never; 541 | cookie?: never; 542 | }; 543 | requestBody: { 544 | content: { 545 | "application/json": { 546 | /** @description Partial match on user name */ 547 | name?: string; 548 | /** Format: email */ 549 | email?: string; 550 | /** 551 | * Format: date 552 | * @description Users who joined after this date 553 | */ 554 | joinedAfter?: string; 555 | /** @enum {string} */ 556 | membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; 557 | }; 558 | }; 559 | }; 560 | responses: { 561 | /** @description Matching users */ 562 | 200: { 563 | headers: { 564 | [name: string]: unknown; 565 | }; 566 | content: { 567 | "application/json": components["schemas"]["UserList"]; 568 | }; 569 | }; 570 | 400: components["responses"]["BadRequest"]; 571 | 401: components["responses"]["Unauthorized"]; 572 | 403: components["responses"]["Forbidden"]; 573 | }; 574 | }; 575 | "_bulk-{User}.Imp/ort": { 576 | parameters: { 577 | query?: never; 578 | header?: never; 579 | path?: never; 580 | cookie?: never; 581 | }; 582 | requestBody: { 583 | content: { 584 | "multipart/form-data": { 585 | /** 586 | * Format: binary 587 | * @description CSV file (headers: name,email,membershipType,registeredAt) 588 | */ 589 | file?: string; 590 | }; 591 | }; 592 | }; 593 | responses: { 594 | /** @description Bulk import accepted */ 595 | 202: { 596 | headers: { 597 | /** @description URL to check bulk job status */ 598 | Location?: string; 599 | [name: string]: unknown; 600 | }; 601 | content?: never; 602 | }; 603 | 400: components["responses"]["BadRequest"]; 604 | 401: components["responses"]["Unauthorized"]; 605 | 403: components["responses"]["Forbidden"]; 606 | }; 607 | }; 608 | } 609 | -------------------------------------------------------------------------------- /src/test_resources/schema.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Fictional Library User Management Service API 4 | version: 1.0.0 5 | description: | 6 | A RESTful API for managing library user records and their loan information. 7 | servers: 8 | - url: https://api.fictionallibrary.example.com/v1 9 | description: Production server 10 | 11 | paths: 12 | /users: 13 | get: 14 | summary: List users 15 | description: Retrieve all users with pagination. 16 | operationId: listUsers 17 | parameters: 18 | - $ref: '#/components/parameters/page' 19 | - $ref: '#/components/parameters/pageSize' 20 | - name: membershipType 21 | in: query 22 | description: Filter by membership type 23 | schema: 24 | type: string 25 | enum: [REGULAR, PREMIUM, STUDENT] 26 | responses: 27 | '200': 28 | description: A paginated list of users 29 | content: 30 | application/json: 31 | schema: 32 | $ref: '#/components/schemas/UserPage' 33 | '400': 34 | $ref: '#/components/responses/BadRequest' 35 | '401': 36 | $ref: '#/components/responses/Unauthorized' 37 | '403': 38 | $ref: '#/components/responses/Forbidden' 39 | 40 | post: 41 | summary: Create a new user 42 | description: Register a new library user. 43 | operationId: createUser 44 | requestBody: 45 | required: true 46 | content: 47 | application/json: 48 | schema: 49 | $ref: '#/components/schemas/UserCreate' 50 | responses: 51 | '201': 52 | description: User created successfully 53 | content: 54 | application/json: 55 | schema: 56 | $ref: '#/components/schemas/User' 57 | '400': 58 | $ref: '#/components/responses/BadRequest' 59 | '409': 60 | $ref: '#/components/responses/Conflict' 61 | '401': 62 | $ref: '#/components/responses/Unauthorized' 63 | '403': 64 | $ref: '#/components/responses/Forbidden' 65 | 66 | /users/{userId}: 67 | parameters: 68 | - $ref: '#/components/parameters/userId' 69 | get: 70 | summary: Get user details 71 | description: Retrieve detailed information for a specific user. 72 | operationId: getUserDetails 73 | responses: 74 | '200': 75 | description: User details 76 | content: 77 | application/json: 78 | schema: 79 | $ref: '#/components/schemas/User' 80 | '400': 81 | $ref: '#/components/responses/BadRequest' 82 | '401': 83 | $ref: '#/components/responses/Unauthorized' 84 | '403': 85 | $ref: '#/components/responses/Forbidden' 86 | '404': 87 | $ref: '#/components/responses/NotFound' 88 | 89 | put: 90 | summary: Replace user 91 | description: Replace a user's entire record. 92 | operationId: replaceUser 93 | requestBody: 94 | required: true 95 | content: 96 | application/json: 97 | schema: 98 | $ref: '#/components/schemas/UserUpdate' 99 | responses: 100 | '200': 101 | description: Updated user record 102 | content: 103 | application/json: 104 | schema: 105 | $ref: '#/components/schemas/User' 106 | '400': 107 | $ref: '#/components/responses/BadRequest' 108 | '401': 109 | $ref: '#/components/responses/Unauthorized' 110 | '403': 111 | $ref: '#/components/responses/Forbidden' 112 | '404': 113 | $ref: '#/components/responses/NotFound' 114 | 115 | patch: 116 | summary: Update user fields 117 | description: Partially update a user's information. 118 | operationId: updateUserFields 119 | requestBody: 120 | required: true 121 | content: 122 | application/json: 123 | schema: 124 | $ref: '#/components/schemas/UserPatch' 125 | responses: 126 | '200': 127 | description: Updated user record 128 | content: 129 | application/json: 130 | schema: 131 | $ref: '#/components/schemas/User' 132 | '400': 133 | $ref: '#/components/responses/BadRequest' 134 | '401': 135 | $ref: '#/components/responses/Unauthorized' 136 | '403': 137 | $ref: '#/components/responses/Forbidden' 138 | '404': 139 | $ref: '#/components/responses/NotFound' 140 | 141 | delete: 142 | summary: Delete user 143 | description: Soft-delete a user record. 144 | operationId: deleteUser 145 | responses: 146 | '204': 147 | description: User deleted (no content) 148 | '400': 149 | $ref: '#/components/responses/BadRequest' 150 | '401': 151 | $ref: '#/components/responses/Unauthorized' 152 | '403': 153 | $ref: '#/components/responses/Forbidden' 154 | '404': 155 | $ref: '#/components/responses/NotFound' 156 | 157 | /users/{userId}/loans: 158 | parameters: 159 | - $ref: '#/components/parameters/userId' 160 | get: 161 | summary: List user loans 162 | description: Retrieve current loans for a specific user. 163 | operationId: listUserLoans 164 | responses: 165 | '200': 166 | description: List of loans 167 | content: 168 | application/json: 169 | schema: 170 | type: array 171 | items: 172 | $ref: '#/components/schemas/Loan' 173 | '400': 174 | $ref: '#/components/responses/BadRequest' 175 | '401': 176 | $ref: '#/components/responses/Unauthorized' 177 | '403': 178 | $ref: '#/components/responses/Forbidden' 179 | '404': 180 | $ref: '#/components/responses/NotFound' 181 | 182 | /users/search: 183 | post: 184 | summary: Search users 185 | description: Search for users by multiple criteria. 186 | operationId: searchUsers 187 | requestBody: 188 | required: true 189 | content: 190 | application/json: 191 | schema: 192 | type: object 193 | properties: 194 | name: 195 | type: string 196 | description: Partial match on user name 197 | email: 198 | type: string 199 | format: email 200 | joinedAfter: 201 | type: string 202 | format: date 203 | description: Users who joined after this date 204 | membershipType: 205 | type: string 206 | enum: [REGULAR, PREMIUM, STUDENT] 207 | example: 208 | name: "Smith" 209 | joinedAfter: "2024-01-01" 210 | membershipType: "STUDENT" 211 | responses: 212 | '200': 213 | description: Matching users 214 | content: 215 | application/json: 216 | schema: 217 | $ref: '#/components/schemas/UserList' 218 | '400': 219 | $ref: '#/components/responses/BadRequest' 220 | '401': 221 | $ref: '#/components/responses/Unauthorized' 222 | '403': 223 | $ref: '#/components/responses/Forbidden' 224 | 225 | /users/bulk: 226 | post: 227 | summary: Bulk user import 228 | description: Upload a CSV file to register multiple users at once. 229 | operationId: _bulk-{User}.Imp/ort # contains noises 230 | requestBody: 231 | required: true 232 | content: 233 | multipart/form-data: 234 | schema: 235 | type: object 236 | properties: 237 | file: 238 | type: string 239 | format: binary 240 | description: 'CSV file (headers: name,email,membershipType,registeredAt)' 241 | responses: 242 | '202': 243 | description: Bulk import accepted 244 | headers: 245 | Location: 246 | description: URL to check bulk job status 247 | schema: 248 | type: string 249 | '400': 250 | $ref: '#/components/responses/BadRequest' 251 | '401': 252 | $ref: '#/components/responses/Unauthorized' 253 | '403': 254 | $ref: '#/components/responses/Forbidden' 255 | 256 | /users/bulk/{jobId}: 257 | parameters: 258 | - name: jobId 259 | in: path 260 | required: true 261 | schema: 262 | type: string 263 | description: Bulk import job identifier 264 | - name: Authorization 265 | in: header 266 | required: true 267 | schema: 268 | type: string 269 | description: Authorization Header 270 | - name: Application-Version 271 | in: header 272 | required: true 273 | schema: 274 | type: string 275 | description: Application version 276 | - name: Something-Id 277 | in: header 278 | required: true 279 | schema: 280 | type: string 281 | description: Identifier of something 282 | get: 283 | summary: Get bulk job status 284 | # Note 285 | # intentionally, "operationId" doesn't exist here. 286 | description: Retrieve the status of a bulk import job. 287 | responses: 288 | '200': 289 | description: Bulk job status 290 | content: 291 | application/json: 292 | schema: 293 | $ref: '#/components/schemas/BulkJobStatus' 294 | '400': 295 | $ref: '#/components/responses/BadRequest' 296 | '401': 297 | $ref: '#/components/responses/Unauthorized' 298 | '403': 299 | $ref: '#/components/responses/Forbidden' 300 | '404': 301 | $ref: '#/components/responses/NotFound' 302 | 303 | components: 304 | parameters: 305 | page: 306 | name: page 307 | in: query 308 | description: Page number (starting at 1) 309 | schema: 310 | type: integer 311 | minimum: 1 312 | default: 1 313 | pageSize: 314 | name: pageSize 315 | in: query 316 | description: Number of items per page 317 | schema: 318 | type: integer 319 | minimum: 1 320 | maximum: 100 321 | default: 20 322 | userId: 323 | name: userId 324 | in: path 325 | required: true 326 | description: Unique user identifier (UUID) 327 | schema: 328 | type: string 329 | format: uuid 330 | 331 | responses: 332 | BadRequest: 333 | description: Bad request due to invalid input 334 | content: 335 | application/json: 336 | schema: 337 | $ref: '#/components/schemas/Error' 338 | Unauthorized: 339 | description: Authentication required or failed 340 | content: 341 | application/json: 342 | schema: 343 | $ref: '#/components/schemas/Error' 344 | Forbidden: 345 | description: Insufficient permissions to access resource 346 | content: 347 | application/json: 348 | schema: 349 | $ref: '#/components/schemas/Error' 350 | NotFound: 351 | description: Resource not found 352 | content: 353 | application/json: 354 | schema: 355 | $ref: '#/components/schemas/Error' 356 | Conflict: 357 | description: Conflict with current state of the resource 358 | content: 359 | application/json: 360 | schema: 361 | $ref: '#/components/schemas/Error' 362 | 363 | schemas: 364 | Error: 365 | type: object 366 | properties: 367 | code: 368 | type: integer 369 | description: HTTP status code 370 | message: 371 | type: string 372 | description: Error message detailing the cause 373 | required: 374 | - code 375 | - message 376 | 377 | User: 378 | type: object 379 | properties: 380 | id: 381 | type: string 382 | format: uuid 383 | name: 384 | type: string 385 | email: 386 | type: string 387 | format: email 388 | membershipType: 389 | type: string 390 | enum: [REGULAR, PREMIUM, STUDENT] 391 | registeredAt: 392 | type: string 393 | format: date-time 394 | address: 395 | $ref: '#/components/schemas/Address' 396 | required: 397 | - id 398 | - name 399 | - email 400 | - membershipType 401 | - registeredAt 402 | 403 | Address: 404 | type: object 405 | properties: 406 | postalCode: 407 | type: string 408 | street: 409 | type: string 410 | city: 411 | type: string 412 | country: 413 | type: string 414 | required: 415 | - street 416 | - city 417 | - country 418 | 419 | UserCreate: 420 | type: object 421 | properties: 422 | name: 423 | type: string 424 | email: 425 | type: string 426 | format: email 427 | membershipType: 428 | type: string 429 | enum: [REGULAR, PREMIUM, STUDENT] 430 | address: 431 | $ref: '#/components/schemas/Address' 432 | required: 433 | - name 434 | - email 435 | - membershipType 436 | 437 | UserUpdate: 438 | allOf: 439 | - $ref: '#/components/schemas/UserCreate' 440 | - type: object 441 | properties: 442 | id: 443 | type: string 444 | format: uuid 445 | required: 446 | - id 447 | 448 | UserPatch: 449 | type: object 450 | description: Schema for partial updates – include only fields to change 451 | properties: 452 | name: 453 | type: string 454 | email: 455 | type: string 456 | format: email 457 | membershipType: 458 | type: string 459 | enum: [REGULAR, PREMIUM, STUDENT] 460 | address: 461 | $ref: '#/components/schemas/Address' 462 | 463 | UserList: 464 | type: array 465 | items: 466 | $ref: '#/components/schemas/User' 467 | 468 | UserPage: 469 | type: object 470 | properties: 471 | total: 472 | type: integer 473 | page: 474 | type: integer 475 | pageSize: 476 | type: integer 477 | items: 478 | $ref: '#/components/schemas/UserList' 479 | required: 480 | - total 481 | - page 482 | - pageSize 483 | - items 484 | 485 | Loan: 486 | type: object 487 | properties: 488 | loanId: 489 | type: string 490 | format: uuid 491 | book: 492 | $ref: '#/components/schemas/Book' 493 | borrowedAt: 494 | type: string 495 | format: date-time 496 | dueAt: 497 | type: string 498 | format: date-time 499 | required: 500 | - loanId 501 | - book 502 | - borrowedAt 503 | - dueAt 504 | 505 | Book: 506 | type: object 507 | properties: 508 | isbn: 509 | type: string 510 | title: 511 | type: string 512 | author: 513 | type: string 514 | required: 515 | - isbn 516 | - title 517 | - author 518 | 519 | BulkJobStatus: 520 | type: object 521 | properties: 522 | jobId: 523 | type: string 524 | status: 525 | type: string 526 | enum: [PENDING, PROCESSING, COMPLETED, FAILED] 527 | processed: 528 | type: integer 529 | total: 530 | type: integer 531 | required: 532 | - jobId 533 | - status 534 | - processed 535 | - total 536 | 537 | # this is dummy schema to check name confliction 538 | Client: 539 | type: object 540 | properties: 541 | id: 542 | type: string 543 | 544 | # this is dummy schema to check hyphen replacement 545 | schema-Something: 546 | type: object 547 | properties: 548 | id: 549 | type: string 550 | 551 | -------------------------------------------------------------------------------- /src/test_resources/verifications.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Verification of the expected client 3 | */ 4 | import { Client } from "./expected_client"; 5 | 6 | const client_without_default_headers = new Client({}); 7 | client_without_default_headers.getUsers({ 8 | query: { 9 | page: 1, 10 | pageSize: 10, 11 | membershipType: "REGULAR", 12 | }, 13 | }); 14 | // It must specify the header parameter entirely 15 | client_without_default_headers.getUsersBulkJobid({ 16 | header: { 17 | Authorization: "Bearer token", 18 | "Application-Version": "1.0", 19 | "Something-Id": "123", 20 | }, 21 | path: { jobId: "123" }, 22 | }); 23 | 24 | const client_with_partial_default_headers = new Client( 25 | {}, 26 | { Authorization: "Bearer token", "Application-Version": "1.0" }, 27 | ); 28 | client_with_partial_default_headers.getUsers({ 29 | query: { 30 | page: 1, 31 | pageSize: 10, 32 | membershipType: "REGULAR", 33 | }, 34 | }); 35 | // It can specify the header parameter partially 36 | client_with_partial_default_headers.getUsersBulkJobid({ 37 | header: { 38 | "Something-Id": "123", 39 | }, 40 | path: { jobId: "123" }, 41 | }); 42 | // It also can specify the header parameter entirely 43 | client_with_partial_default_headers.getUsersBulkJobid({ 44 | header: { 45 | Authorization: "Bearer token", 46 | "Application-Version": "1.0", 47 | "Something-Id": "123", 48 | }, 49 | path: { jobId: "123" }, 50 | }); 51 | 52 | const client_with_all_default_headers = new Client( 53 | {}, 54 | { 55 | Authorization: "Bearer token", 56 | "Application-Version": "1.0", 57 | "Something-Id": "123", 58 | }, 59 | ); 60 | client_with_all_default_headers.getUsers({ 61 | query: { 62 | page: 1, 63 | pageSize: 10, 64 | membershipType: "REGULAR", 65 | }, 66 | }); 67 | // It also can omit the header parameter 68 | client_with_all_default_headers.getUsersBulkJobid({ 69 | path: { jobId: "123" }, 70 | }); 71 | // It also can specify the header parameter entirely 72 | client_with_all_default_headers.getUsersBulkJobid({ 73 | header: { 74 | Authorization: "Bearer token", 75 | "Application-Version": "1.0", 76 | "Something-Id": "123", 77 | }, 78 | path: { jobId: "123" }, 79 | }); 80 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "nodenext", 5 | "moduleResolution": "nodenext", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "inlineSources": true, 9 | "removeComments": true, 10 | "outDir": "./dist", 11 | "rootDir": "./src", 12 | "declaration": true, 13 | "declarationMap": true, 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noUncheckedIndexedAccess": true, 20 | "noPropertyAccessFromIndexSignature": true, 21 | "exactOptionalPropertyTypes": true, 22 | "skipLibCheck": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "resolveJsonModule": true, 25 | "allowSyntheticDefaultImports": true 26 | }, 27 | "include": ["src/**/*"], 28 | "exclude": ["node_modules", "dist", "examples", "**/*.test.ts"] 29 | } 30 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true, 7 | environment: 'node', 8 | include: ['**/*.test.ts'], 9 | exclude: ['**/node_modules/**', '**/dist/**'], 10 | coverage: { 11 | provider: 'v8', // Changed from c8 to v8 12 | reporter: ['text', 'json', 'html'], 13 | include: ['src/**/*.ts'], 14 | exclude: ['**/*.test.ts', '**/node_modules/**', '**/dist/**'], 15 | }, 16 | }, 17 | }); --------------------------------------------------------------------------------