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

feat: initialize Angular client with OpenAPI integration

- Added main entry point for the Angular application with Radix UI theme support.
- Configured internal service client with default base URL and authorization headers.
- Created Vite configuration for the Angular project.
- Implemented TypeScript configuration for strict type checking and module resolution.
- Added Tailwind CSS configuration for styling.
- Developed comprehensive tests for client functionality, including URL building and authentication handling.
- Implemented client utilities for request handling, including query serialization and response parsing.
- Defined plugin configuration for the Angular client, allowing for customizable settings.
- Established types for the Angular client plugin to ensure type safety and clarity.

Max Scopp ee367da3 078d957d

+4925 -12
+24
examples/openapi-ts-angular/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+405
examples/openapi-ts-angular/CHANGELOG.md
··· 1 + # @example/openapi-ts-fetch 2 + 3 + ## 0.0.57 4 + 5 + ### Patch Changes 6 + 7 + - Updated dependencies [[`f23f3ae`](https://github.com/hey-api/openapi-ts/commit/f23f3ae874385e758eb8d43bb4e274c9858a4e40)]: 8 + - @hey-api/client-fetch@0.13.1 9 + 10 + ## 0.0.56 11 + 12 + ### Patch Changes 13 + 14 + - Updated dependencies [[`10d2e03`](https://github.com/hey-api/openapi-ts/commit/10d2e03b8295e4e887fab8d023aa823699efbae8)]: 15 + - @hey-api/client-fetch@0.13.0 16 + 17 + ## 0.0.55 18 + 19 + ### Patch Changes 20 + 21 + - Updated dependencies [[`4d8c030`](https://github.com/hey-api/openapi-ts/commit/4d8c03038979c9a75315cc158789b3c198c62f90)]: 22 + - @hey-api/client-fetch@0.12.0 23 + 24 + ## 0.0.54 25 + 26 + ### Patch Changes 27 + 28 + - Updated dependencies [[`8152aaf`](https://github.com/hey-api/openapi-ts/commit/8152aaf4892c48b79fd3dc486eb3c0ea333dc3e6)]: 29 + - @hey-api/client-fetch@0.11.0 30 + 31 + ## 0.0.53 32 + 33 + ### Patch Changes 34 + 35 + - Updated dependencies [[`1f99066`](https://github.com/hey-api/openapi-ts/commit/1f99066efbb2d0e6b9e3710c701293c2cc09d65e)]: 36 + - @hey-api/client-fetch@0.10.2 37 + 38 + ## 0.0.52 39 + 40 + ### Patch Changes 41 + 42 + - Updated dependencies [[`565e0b8`](https://github.com/hey-api/openapi-ts/commit/565e0b89fbab4556ecdc63dfe08250942681140e)]: 43 + - @hey-api/client-fetch@0.10.1 44 + 45 + ## 0.0.51 46 + 47 + ### Patch Changes 48 + 49 + - Updated dependencies [[`fed9699`](https://github.com/hey-api/openapi-ts/commit/fed969985275621c7c2b65ffc760c7c66fafaf72)]: 50 + - @hey-api/client-fetch@0.10.0 51 + 52 + ## 0.0.50 53 + 54 + ### Patch Changes 55 + 56 + - Updated dependencies [[`67c385b`](https://github.com/hey-api/openapi-ts/commit/67c385bf6289a79726b0cdd85fd81ca501cf2248)]: 57 + - @hey-api/client-fetch@0.9.0 58 + 59 + ## 0.0.49 60 + 61 + ### Patch Changes 62 + 63 + - Updated dependencies [[`fe43b88`](https://github.com/hey-api/openapi-ts/commit/fe43b889c20a2001f56e259f93f64851a1caa1d1)]: 64 + - @hey-api/client-fetch@0.8.4 65 + 66 + ## 0.0.48 67 + 68 + ### Patch Changes 69 + 70 + - Updated dependencies [[`c0b36b9`](https://github.com/hey-api/openapi-ts/commit/c0b36b95645d484034c3af145c5554867568979b)]: 71 + - @hey-api/client-fetch@0.8.3 72 + 73 + ## 0.0.47 74 + 75 + ### Patch Changes 76 + 77 + - Updated dependencies [[`b8cc9f8`](https://github.com/hey-api/openapi-ts/commit/b8cc9f8a5eaf4f4ff345abc49c14c6b96744c2ea)]: 78 + - @hey-api/client-fetch@0.8.2 79 + 80 + ## 0.0.46 81 + 82 + ### Patch Changes 83 + 84 + - Updated dependencies [[`7f0f4a7`](https://github.com/hey-api/openapi-ts/commit/7f0f4a76b06c8fafb33581b522faf8efc6fd85ac)]: 85 + - @hey-api/client-fetch@0.8.1 86 + 87 + ## 0.0.45 88 + 89 + ### Patch Changes 90 + 91 + - Updated dependencies [[`bb6d46a`](https://github.com/hey-api/openapi-ts/commit/bb6d46ae119ce4e7e3a2ab3fded74ac4fb4cdff2)]: 92 + - @hey-api/client-fetch@0.8.0 93 + 94 + ## 0.0.44 95 + 96 + ### Patch Changes 97 + 98 + - Updated dependencies [[`2dc380e`](https://github.com/hey-api/openapi-ts/commit/2dc380eabc17c723654beb04ecd7bce6d33d3b49), [`603541e`](https://github.com/hey-api/openapi-ts/commit/603541e307dc2953da7dddd300176865629b50bb), [`2cbffeb`](https://github.com/hey-api/openapi-ts/commit/2cbffeb2cdd6c6143cd68cac68369584879dda31), [`2cbffeb`](https://github.com/hey-api/openapi-ts/commit/2cbffeb2cdd6c6143cd68cac68369584879dda31)]: 99 + - @hey-api/client-fetch@0.7.3 100 + 101 + ## 0.0.43 102 + 103 + ### Patch Changes 104 + 105 + - Updated dependencies [[`8eba19d`](https://github.com/hey-api/openapi-ts/commit/8eba19d4092fc0903572ab9fdadf0b4c26928ba2)]: 106 + - @hey-api/client-fetch@0.7.2 107 + 108 + ## 0.0.42 109 + 110 + ### Patch Changes 111 + 112 + - Updated dependencies [[`0432418`](https://github.com/hey-api/openapi-ts/commit/0432418d72c94ef94865f8216ed2f723ad5191f9), [`4784727`](https://github.com/hey-api/openapi-ts/commit/47847276e8bc854045044dd414382080270dd779)]: 113 + - @hey-api/client-fetch@0.7.1 114 + 115 + ## 0.0.41 116 + 117 + ### Patch Changes 118 + 119 + - Updated dependencies [[`465410c`](https://github.com/hey-api/openapi-ts/commit/465410c201eb19e737e3143ad53a146e95f80107)]: 120 + - @hey-api/client-fetch@0.7.0 121 + 122 + ## 0.0.40 123 + 124 + ### Patch Changes 125 + 126 + - Updated dependencies [[`e2e1410`](https://github.com/hey-api/openapi-ts/commit/e2e1410b22c0c84c40d1b1803e9650d546350cb7)]: 127 + - @hey-api/client-fetch@0.6.0 128 + 129 + ## 0.0.39 130 + 131 + ### Patch Changes 132 + 133 + - Updated dependencies [[`20d7497`](https://github.com/hey-api/openapi-ts/commit/20d7497acb6c046f6a4206c2d8137414e17b2263), [`f86d293`](https://github.com/hey-api/openapi-ts/commit/f86d293f18f133ef6dd2f4864d037611b81edd26)]: 134 + - @hey-api/client-fetch@0.5.7 135 + 136 + ## 0.0.38 137 + 138 + ### Patch Changes 139 + 140 + - Updated dependencies [[`ba56424`](https://github.com/hey-api/openapi-ts/commit/ba5642486cdd5461c2372c34b63019c02bc6874e)]: 141 + - @hey-api/client-fetch@0.5.6 142 + 143 + ## 0.0.37 144 + 145 + ### Patch Changes 146 + 147 + - Updated dependencies [[`9cec9e8`](https://github.com/hey-api/openapi-ts/commit/9cec9e8582c12a8c041b922d9587e16f6f19782a)]: 148 + - @hey-api/client-fetch@0.5.5 149 + 150 + ## 0.0.36 151 + 152 + ### Patch Changes 153 + 154 + - Updated dependencies [[`cbf4e84`](https://github.com/hey-api/openapi-ts/commit/cbf4e84db7f3a47f19d8c3eaa87c71b27912c1a2)]: 155 + - @hey-api/client-fetch@0.5.4 156 + 157 + ## 0.0.35 158 + 159 + ### Patch Changes 160 + 161 + - Updated dependencies [[`646064d`](https://github.com/hey-api/openapi-ts/commit/646064d1aecea988d2b4df73bd24b2ee83394ae0)]: 162 + - @hey-api/client-fetch@0.5.3 163 + 164 + ## 0.0.34 165 + 166 + ### Patch Changes 167 + 168 + - Updated dependencies [[`ec48d32`](https://github.com/hey-api/openapi-ts/commit/ec48d323d80de8e6a47ce7ecd732288f0a47e17a)]: 169 + - @hey-api/client-fetch@0.5.2 170 + 171 + ## 0.0.33 172 + 173 + ### Patch Changes 174 + 175 + - Updated dependencies [[`fa8b0f1`](https://github.com/hey-api/openapi-ts/commit/fa8b0f11ed99c63f694a494944ccc2fbfa9706cc)]: 176 + - @hey-api/client-fetch@0.5.1 177 + 178 + ## 0.0.32 179 + 180 + ### Patch Changes 181 + 182 + - Updated dependencies [[`734a62d`](https://github.com/hey-api/openapi-ts/commit/734a62dd8d594b8266964fe16766a481d37eb7df), [`734a62d`](https://github.com/hey-api/openapi-ts/commit/734a62dd8d594b8266964fe16766a481d37eb7df)]: 183 + - @hey-api/client-fetch@0.5.0 184 + 185 + ## 0.0.31 186 + 187 + ### Patch Changes 188 + 189 + - Updated dependencies [[`4c853d0`](https://github.com/hey-api/openapi-ts/commit/4c853d090b79245854d13831f64731db4a92978b)]: 190 + - @hey-api/client-fetch@0.4.4 191 + 192 + ## 0.0.30 193 + 194 + ### Patch Changes 195 + 196 + - Updated dependencies [[`01dee3d`](https://github.com/hey-api/openapi-ts/commit/01dee3df879232939e43355231147b3d910fb482)]: 197 + - @hey-api/client-fetch@0.4.3 198 + 199 + ## 0.0.29 200 + 201 + ### Patch Changes 202 + 203 + - [#1151](https://github.com/hey-api/openapi-ts/pull/1151) [`587791d`](https://github.com/hey-api/openapi-ts/commit/587791dfede0167fbed229281467e4c4875936f5) Thanks [@mrlubos](https://github.com/mrlubos)! - fix: update website domain, add license documentation 204 + 205 + - Updated dependencies [[`587791d`](https://github.com/hey-api/openapi-ts/commit/587791dfede0167fbed229281467e4c4875936f5)]: 206 + - @hey-api/client-fetch@0.4.2 207 + 208 + ## 0.0.28 209 + 210 + ### Patch Changes 211 + 212 + - Updated dependencies [[`a0a5551`](https://github.com/hey-api/openapi-ts/commit/a0a55510d30a1a8dea0ade4908b5b13d51b5f9e6)]: 213 + - @hey-api/client-fetch@0.4.1 214 + 215 + ## 0.0.27 216 + 217 + ### Patch Changes 218 + 219 + - Updated dependencies [[`df5c690`](https://github.com/hey-api/openapi-ts/commit/df5c69048a03a1c7729a5200c586164287a8a6fa), [`df5c690`](https://github.com/hey-api/openapi-ts/commit/df5c69048a03a1c7729a5200c586164287a8a6fa)]: 220 + - @hey-api/client-fetch@0.4.0 221 + 222 + ## 0.0.26 223 + 224 + ### Patch Changes 225 + 226 + - Updated dependencies [[`7f986c2`](https://github.com/hey-api/openapi-ts/commit/7f986c2c7726ed8fbf16f8b235b7769c7d990502)]: 227 + - @hey-api/client-fetch@0.3.4 228 + 229 + ## 0.0.25 230 + 231 + ### Patch Changes 232 + 233 + - Updated dependencies [[`fe743c2`](https://github.com/hey-api/openapi-ts/commit/fe743c2d41c23bf7e1706bceedd6319299131197)]: 234 + - @hey-api/client-fetch@0.3.3 235 + 236 + ## 0.0.24 237 + 238 + ### Patch Changes 239 + 240 + - Updated dependencies [[`11a276a`](https://github.com/hey-api/openapi-ts/commit/11a276a1e35dde0735363e892d8142016fd87eec)]: 241 + - @hey-api/client-fetch@0.3.2 242 + 243 + ## 0.0.23 244 + 245 + ### Patch Changes 246 + 247 + - Updated dependencies [[`7ae2b1d`](https://github.com/hey-api/openapi-ts/commit/7ae2b1db047f3b6efe917a8b43ac7c851fb86c8f), [`2079c6e`](https://github.com/hey-api/openapi-ts/commit/2079c6e83a6b71e157c8e7ea56260b4e9ff8411d)]: 248 + - @hey-api/client-fetch@0.3.1 249 + 250 + ## 0.0.22 251 + 252 + ### Patch Changes 253 + 254 + - Updated dependencies [[`7ebc1d4`](https://github.com/hey-api/openapi-ts/commit/7ebc1d44af74db2522219d71d240325f6bc5689d)]: 255 + - @hey-api/client-fetch@0.3.0 256 + 257 + ## 0.0.21 258 + 259 + ### Patch Changes 260 + 261 + - [#899](https://github.com/hey-api/openapi-ts/pull/899) [`a8c84c0`](https://github.com/hey-api/openapi-ts/commit/a8c84c02dbb5ef1a59f5d414dff425e135c7a446) Thanks [@mrlubos](https://github.com/mrlubos)! - fix: preserve key name in Date transformation 262 + 263 + - Updated dependencies [[`a8c84c0`](https://github.com/hey-api/openapi-ts/commit/a8c84c02dbb5ef1a59f5d414dff425e135c7a446), [`7825a2f`](https://github.com/hey-api/openapi-ts/commit/7825a2fba566a76c63775172ef0569ef375406b6)]: 264 + - @hey-api/client-fetch@0.2.4 265 + 266 + ## 0.0.20 267 + 268 + ### Patch Changes 269 + 270 + - [#895](https://github.com/hey-api/openapi-ts/pull/895) [`44de8d8`](https://github.com/hey-api/openapi-ts/commit/44de8d89556b3abf48acc4e23c9b9c198059c757) Thanks [@mrlubos](https://github.com/mrlubos)! - fix: define ThrowOnError generic as the last argument 271 + 272 + - Updated dependencies [[`44de8d8`](https://github.com/hey-api/openapi-ts/commit/44de8d89556b3abf48acc4e23c9b9c198059c757)]: 273 + - @hey-api/client-fetch@0.2.3 274 + 275 + ## 0.0.19 276 + 277 + ### Patch Changes 278 + 279 + - Updated dependencies [[`72e2c4f`](https://github.com/hey-api/openapi-ts/commit/72e2c4fd7d07e532a848078c034bf33b6558ad3c)]: 280 + - @hey-api/client-fetch@0.2.2 281 + 282 + ## 0.0.18 283 + 284 + ### Patch Changes 285 + 286 + - Updated dependencies [[`ec6bfc8`](https://github.com/hey-api/openapi-ts/commit/ec6bfc8292cce7663dfc6e0fcd89b44c56f08bb4), [`93e2d11`](https://github.com/hey-api/openapi-ts/commit/93e2d11d2a8ddd1f78dde46eceeb5543cae07e36), [`a73da1c`](https://github.com/hey-api/openapi-ts/commit/a73da1c854503246b6c58f1abea5dd77727eedca), [`da92c53`](https://github.com/hey-api/openapi-ts/commit/da92c535c14e3217d565472fe65c687243bc0dd8)]: 287 + - @hey-api/client-fetch@0.2.1 288 + 289 + ## 0.0.17 290 + 291 + ### Patch Changes 292 + 293 + - Updated dependencies [[`babf11a`](https://github.com/hey-api/openapi-ts/commit/babf11ae082af642ac71cfee9c523cc976132a50), [`babf11a`](https://github.com/hey-api/openapi-ts/commit/babf11ae082af642ac71cfee9c523cc976132a50)]: 294 + - @hey-api/client-fetch@0.2.0 295 + 296 + ## 0.0.16 297 + 298 + ### Patch Changes 299 + 300 + - Updated dependencies [[`8c9c874`](https://github.com/hey-api/openapi-ts/commit/8c9c8749594622283eed2c37bddfa0f1b8cf23a4)]: 301 + - @hey-api/client-fetch@0.1.14 302 + 303 + ## 0.0.15 304 + 305 + ### Patch Changes 306 + 307 + - Updated dependencies [[`8e3c634`](https://github.com/hey-api/openapi-ts/commit/8e3c6343672b9280365c3266f94e4acba533bf29)]: 308 + - @hey-api/client-fetch@0.1.13 309 + 310 + ## 0.0.14 311 + 312 + ### Patch Changes 313 + 314 + - [#823](https://github.com/hey-api/openapi-ts/pull/823) [`23c9dcd`](https://github.com/hey-api/openapi-ts/commit/23c9dcd5de19de62d745cc539674c815b2588cd2) Thanks [@mrlubos](https://github.com/mrlubos)! - fix: correctly process body parameter for OpenAPI 2.0 specs 315 + 316 + ## 0.0.13 317 + 318 + ### Patch Changes 319 + 320 + - Updated dependencies [[`0c4ee06`](https://github.com/hey-api/openapi-ts/commit/0c4ee06548f177ce83d73802471c659834c63566)]: 321 + - @hey-api/client-fetch@0.1.12 322 + 323 + ## 0.0.12 324 + 325 + ### Patch Changes 326 + 327 + - Updated dependencies [[`c0ee1e3`](https://github.com/hey-api/openapi-ts/commit/c0ee1e3b56d67ab922491c488233bd89c8902986)]: 328 + - @hey-api/client-fetch@0.1.11 329 + 330 + ## 0.0.11 331 + 332 + ### Patch Changes 333 + 334 + - Updated dependencies [[`ecd94f2`](https://github.com/hey-api/openapi-ts/commit/ecd94f2adab1dbe10e7a9c310d1fb6d1f170d332)]: 335 + - @hey-api/client-fetch@0.1.10 336 + 337 + ## 0.0.10 338 + 339 + ### Patch Changes 340 + 341 + - Updated dependencies [[`e7e98d2`](https://github.com/hey-api/openapi-ts/commit/e7e98d279fe0ee4c71ae72a7b57afdd517a89641)]: 342 + - @hey-api/client-fetch@0.1.9 343 + 344 + ## 0.0.9 345 + 346 + ### Patch Changes 347 + 348 + - Updated dependencies [[`d546a3f`](https://github.com/hey-api/openapi-ts/commit/d546a3f9fd0a6ff5181deb50ed467acd75370889)]: 349 + - @hey-api/client-fetch@0.1.8 350 + 351 + ## 0.0.8 352 + 353 + ### Patch Changes 354 + 355 + - Updated dependencies [[`8410046`](https://github.com/hey-api/openapi-ts/commit/8410046c45d25db48ba940a0c6c7a7cda9e86b6a)]: 356 + - @hey-api/client-fetch@0.1.7 357 + 358 + ## 0.0.7 359 + 360 + ### Patch Changes 361 + 362 + - Updated dependencies [[`aa661a1`](https://github.com/hey-api/openapi-ts/commit/aa661a136d1174eadf4d11538e473b0d96b91b81)]: 363 + - @hey-api/client-fetch@0.1.6 364 + 365 + ## 0.0.6 366 + 367 + ### Patch Changes 368 + 369 + - Updated dependencies [[`fc2b166`](https://github.com/hey-api/openapi-ts/commit/fc2b166c8f683ece948284cf7a629fcd5b096b40)]: 370 + - @hey-api/client-fetch@0.1.5 371 + 372 + ## 0.0.5 373 + 374 + ### Patch Changes 375 + 376 + - Updated dependencies [[`da31b74`](https://github.com/hey-api/openapi-ts/commit/da31b7424b30e00233df5a3867022832c4981312), [`34980a4`](https://github.com/hey-api/openapi-ts/commit/34980a4dc8269c9256d65984ff29270851689c43), [`34980a4`](https://github.com/hey-api/openapi-ts/commit/34980a4dc8269c9256d65984ff29270851689c43)]: 377 + - @hey-api/client-fetch@0.1.4 378 + 379 + ## 0.0.4 380 + 381 + ### Patch Changes 382 + 383 + - Updated dependencies [[`820002f`](https://github.com/hey-api/openapi-ts/commit/820002ffe687b01c7a9b2250e19ddbafd1aaed71)]: 384 + - @hey-api/client-fetch@0.1.3 385 + 386 + ## 0.0.3 387 + 388 + ### Patch Changes 389 + 390 + - Updated dependencies [[`735561c82dbe0979f2c175d274159c20ba8e622d`](https://github.com/hey-api/openapi-ts/commit/735561c82dbe0979f2c175d274159c20ba8e622d)]: 391 + - @hey-api/client-fetch@0.1.2 392 + 393 + ## 0.0.2 394 + 395 + ### Patch Changes 396 + 397 + - Updated dependencies [[`616a4ea0265e09f3997ac2156c341a5cc0b49029`](https://github.com/hey-api/openapi-ts/commit/616a4ea0265e09f3997ac2156c341a5cc0b49029)]: 398 + - @hey-api/client-fetch@0.1.1 399 + 400 + ## 0.0.1 401 + 402 + ### Patch Changes 403 + 404 + - Updated dependencies [[`c416343eb499791dd4771866de1862a2d5d69350`](https://github.com/hey-api/openapi-ts/commit/c416343eb499791dd4771866de1862a2d5d69350)]: 405 + - @hey-api/client-fetch@0.1.0
+13
examples/openapi-ts-angular/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> --> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Hey API + Fetch API Demo</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+20
examples/openapi-ts-angular/openapi-ts.config.ts
··· 1 + import { defineConfig } from '@hey-api/openapi-ts'; 2 + 3 + export default defineConfig({ 4 + input: 5 + 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 6 + output: { 7 + format: 'prettier', 8 + lint: 'eslint', 9 + path: './src/client', 10 + }, 11 + plugins: [ 12 + '@hey-api/client-angular', 13 + '@hey-api/schemas', 14 + '@hey-api/sdk', 15 + { 16 + enums: 'javascript', 17 + name: '@hey-api/typescript', 18 + }, 19 + ], 20 + });
+39
examples/openapi-ts-angular/package.json
··· 1 + { 2 + "name": "@example/openapi-ts-angular", 3 + "private": true, 4 + "version": "0.0.57", 5 + "type": "module", 6 + "scripts": { 7 + "build": "tsc && vite build", 8 + "dev": "vite", 9 + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 + "openapi-ts": "openapi-ts", 11 + "preview": "vite preview", 12 + "typecheck": "tsc --noEmit" 13 + }, 14 + "dependencies": { 15 + "@radix-ui/react-form": "0.1.1", 16 + "@radix-ui/react-icons": "1.3.2", 17 + "@radix-ui/themes": "3.1.6", 18 + "react": "19.0.0", 19 + "react-dom": "19.0.0" 20 + }, 21 + "devDependencies": { 22 + "@config/vite-base": "workspace:*", 23 + "@hey-api/openapi-ts": "workspace:*", 24 + "@types/react": "19.0.1", 25 + "@types/react-dom": "19.0.1", 26 + "@typescript-eslint/eslint-plugin": "8.29.1", 27 + "@typescript-eslint/parser": "8.29.1", 28 + "@vitejs/plugin-react": "4.4.0-beta.1", 29 + "autoprefixer": "10.4.19", 30 + "eslint": "9.17.0", 31 + "eslint-plugin-react-hooks": "5.2.0", 32 + "eslint-plugin-react-refresh": "0.4.7", 33 + "postcss": "8.4.41", 34 + "prettier": "3.4.2", 35 + "tailwindcss": "3.4.9", 36 + "typescript": "5.8.3", 37 + "vite": "6.2.7" 38 + } 39 + }
+6
examples/openapi-ts-angular/postcss.config.js
··· 1 + export default { 2 + plugins: { 3 + autoprefixer: {}, 4 + tailwindcss: {}, 5 + }, 6 + };
+3
examples/openapi-ts-angular/src/App.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+243
examples/openapi-ts-angular/src/App.tsx
··· 1 + import './App.css'; 2 + 3 + import * as Form from '@radix-ui/react-form'; 4 + import { DownloadIcon, PlusIcon, ReloadIcon } from '@radix-ui/react-icons'; 5 + import { 6 + Avatar, 7 + Box, 8 + Button, 9 + Card, 10 + Container, 11 + Flex, 12 + Heading, 13 + Section, 14 + Text, 15 + TextField, 16 + } from '@radix-ui/themes'; 17 + import { useState } from 'react'; 18 + 19 + import { createClient } from './client/client'; 20 + import { PetSchema } from './client/schemas.gen'; 21 + import { addPet, getPetById, updatePet } from './client/sdk.gen'; 22 + import type { Pet } from './client/types.gen'; 23 + 24 + const localClient = createClient({ 25 + // set default base url for requests made by this client 26 + baseUrl: 'https://petstore3.swagger.io/api/v3', 27 + /** 28 + * Set default headers only for requests made by this client. This is to 29 + * demonstrate local clients and their configuration taking precedence over 30 + * internal service client. 31 + */ 32 + headers: { 33 + Authorization: 'Bearer <token_from_local_client>', 34 + }, 35 + }); 36 + 37 + localClient.interceptors.request.use((request, options) => { 38 + // Middleware is great for adding authorization tokens to requests made to 39 + // protected paths. Headers are set randomly here to allow surfacing the 40 + // default headers, too. 41 + if ( 42 + options.url === '/pet/{petId}' && 43 + options.method === 'GET' && 44 + Math.random() < 0.5 45 + ) { 46 + request.headers.set('Authorization', 'Bearer <token_from_interceptor>'); 47 + } 48 + return request; 49 + }); 50 + 51 + localClient.interceptors.error.use((error) => { 52 + console.log(error); 53 + return error; 54 + }); 55 + 56 + function App() { 57 + const [pet, setPet] = useState<Pet>(); 58 + const [isRequiredNameError, setIsRequiredNameError] = useState(false); 59 + 60 + const onAddPet = async (formData: FormData) => { 61 + // simple form field validation to demonstrate using schemas 62 + if (PetSchema.required.includes('name') && !formData.get('name')) { 63 + setIsRequiredNameError(true); 64 + return; 65 + } 66 + 67 + const { data, error } = await addPet({ 68 + body: { 69 + category: { 70 + id: 0, 71 + name: formData.get('category') as string, 72 + }, 73 + id: 0, 74 + name: formData.get('name') as string, 75 + photoUrls: ['string'], 76 + status: 'available', 77 + tags: [ 78 + { 79 + id: 0, 80 + name: 'string', 81 + }, 82 + ], 83 + }, 84 + }); 85 + if (error) { 86 + console.log(error); 87 + return; 88 + } 89 + setPet(data!); 90 + setIsRequiredNameError(false); 91 + }; 92 + 93 + const onGetPetById = async () => { 94 + const { data, error } = await getPetById({ 95 + client: localClient, 96 + path: { 97 + // random id 1-10 98 + petId: Math.floor(Math.random() * (10 - 1 + 1) + 1), 99 + }, 100 + }); 101 + if (error) { 102 + console.log(error); 103 + return; 104 + } 105 + setPet(data!); 106 + }; 107 + 108 + const onUpdatePet = async () => { 109 + const { data, error } = await updatePet({ 110 + body: { 111 + category: { 112 + id: 0, 113 + name: 'Cats', 114 + }, 115 + id: 2, 116 + name: 'Updated Kitty', 117 + photoUrls: ['string'], 118 + status: 'available', 119 + tags: [ 120 + { 121 + id: 0, 122 + name: 'string', 123 + }, 124 + ], 125 + }, 126 + // setting headers per request 127 + headers: { 128 + Authorization: 'Bearer <token_from_method>', 129 + }, 130 + }); 131 + if (error) { 132 + console.log(error); 133 + return; 134 + } 135 + setPet(data!); 136 + }; 137 + 138 + return ( 139 + <Box 140 + style={{ background: 'var(--gray-a2)', borderRadius: 'var(--radius-3)' }} 141 + > 142 + <Container size="1"> 143 + <Section size="1" /> 144 + <Flex align="center"> 145 + <a className="shrink-0" href="https://heyapi.dev/" target="_blank"> 146 + <img 147 + src="https://heyapi.dev/logo.png" 148 + className="h-16 w-16 transition duration-300 will-change-auto" 149 + alt="Hey API logo" 150 + /> 151 + </a> 152 + <Heading>@hey-api/openapi-ts 🤝 Fetch API</Heading> 153 + </Flex> 154 + <Section size="1" /> 155 + <Flex direction="column" gapY="2"> 156 + <Box maxWidth="240px"> 157 + <Card> 158 + <Flex gap="3" align="center"> 159 + <Avatar 160 + size="3" 161 + src={pet?.photoUrls[0]} 162 + radius="full" 163 + fallback={pet?.name.slice(0, 1) ?? 'N'} 164 + /> 165 + <Box> 166 + <Text as="div" size="2" weight="bold"> 167 + Name: {pet?.name ?? 'N/A'} 168 + </Text> 169 + <Text as="div" size="2" color="gray"> 170 + Category: {pet?.category?.name ?? 'N/A'} 171 + </Text> 172 + </Box> 173 + </Flex> 174 + </Card> 175 + </Box> 176 + <Button onClick={onGetPetById}> 177 + <DownloadIcon /> Get Random Pet 178 + </Button> 179 + </Flex> 180 + <Section size="1" /> 181 + <Flex direction="column" gapY="2"> 182 + <Form.Root 183 + className="w-[260px]" 184 + onSubmit={(event) => { 185 + event.preventDefault(); 186 + onAddPet(new FormData(event.currentTarget)); 187 + }} 188 + > 189 + <Form.Field className="grid mb-[10px]" name="email"> 190 + <div className="flex items-baseline justify-between"> 191 + <Form.Label className="text-[15px] font-medium leading-[35px] text-white"> 192 + Name 193 + </Form.Label> 194 + {isRequiredNameError && ( 195 + <Form.Message className="text-[13px] text-white opacity-[0.8]"> 196 + Please enter a name 197 + </Form.Message> 198 + )} 199 + </div> 200 + <Form.Control asChild> 201 + <TextField.Root placeholder="Kitty" name="name" type="text" /> 202 + </Form.Control> 203 + </Form.Field> 204 + <Form.Field className="grid mb-[10px]" name="question"> 205 + <div className="flex items-baseline justify-between"> 206 + <Form.Label className="text-[15px] font-medium leading-[35px] text-white"> 207 + Category 208 + </Form.Label> 209 + <Form.Message 210 + className="text-[13px] text-white opacity-[0.8]" 211 + match="valueMissing" 212 + > 213 + Please enter a category 214 + </Form.Message> 215 + </div> 216 + <Form.Control asChild> 217 + <TextField.Root 218 + placeholder="Cats" 219 + name="category" 220 + type="text" 221 + required 222 + /> 223 + </Form.Control> 224 + </Form.Field> 225 + <Flex gapX="2"> 226 + <Form.Submit asChild> 227 + <Button type="submit"> 228 + <PlusIcon /> Add Pet 229 + </Button> 230 + </Form.Submit> 231 + <Button onClick={onUpdatePet} type="button"> 232 + <ReloadIcon /> Update Pet 233 + </Button> 234 + </Flex> 235 + </Form.Root> 236 + </Flex> 237 + <Section size="1" /> 238 + </Container> 239 + </Box> 240 + ); 241 + } 242 + 243 + export default App;
+28
examples/openapi-ts-angular/src/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { 4 + type ClientOptions as DefaultClientOptions, 5 + type Config, 6 + createClient, 7 + createConfig, 8 + } from './client'; 9 + import type { ClientOptions } from './types.gen'; 10 + 11 + /** 12 + * The `createClientConfig()` function will be called on client initialization 13 + * and the returned object will become the client's initial configuration. 14 + * 15 + * You may want to initialize your client this way instead of calling 16 + * `setConfig()`. This is useful for example if you're using Next.js 17 + * to ensure your client always has the correct values. 18 + */ 19 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = 20 + ( 21 + override?: Config<DefaultClientOptions & T>, 22 + ) => Config<Required<DefaultClientOptions> & T>; 23 + 24 + export const client = createClient( 25 + createConfig<ClientOptions>({ 26 + baseUrl: 'https://petstore3.swagger.io/api/v3', 27 + }), 28 + );
+193
examples/openapi-ts-angular/src/client/client/client.ts
··· 1 + import type { Client, Config, RequestOptions } from './types'; 2 + import { 3 + buildUrl, 4 + createConfig, 5 + createInterceptors, 6 + getParseAs, 7 + mergeConfigs, 8 + mergeHeaders, 9 + setAuthParams, 10 + } from './utils'; 11 + 12 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 + body?: any; 14 + headers: ReturnType<typeof mergeHeaders>; 15 + }; 16 + 17 + export const createClient = (config: Config = {}): Client => { 18 + let _config = mergeConfigs(createConfig(), config); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + return getConfig(); 25 + }; 26 + 27 + const interceptors = createInterceptors< 28 + Request, 29 + Response, 30 + unknown, 31 + RequestOptions 32 + >(); 33 + 34 + const request: Client['request'] = async (options) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + }; 41 + 42 + if (opts.security) { 43 + await setAuthParams({ 44 + ...opts, 45 + security: opts.security, 46 + }); 47 + } 48 + 49 + if (opts.requestValidator) { 50 + await opts.requestValidator(opts); 51 + } 52 + 53 + if (opts.body && opts.bodySerializer) { 54 + opts.body = opts.bodySerializer(opts.body); 55 + } 56 + 57 + // remove Content-Type header if body is empty to avoid sending invalid requests 58 + if (opts.body === undefined || opts.body === '') { 59 + opts.headers.delete('Content-Type'); 60 + } 61 + 62 + const url = buildUrl(opts); 63 + const requestInit: ReqInit = { 64 + redirect: 'follow', 65 + ...opts, 66 + }; 67 + 68 + let request = new Request(url, requestInit); 69 + 70 + for (const fn of interceptors.request._fns) { 71 + if (fn) { 72 + request = await fn(request, opts); 73 + } 74 + } 75 + 76 + // fetch must be assigned here, otherwise it would throw the error: 77 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 78 + const _fetch = opts.fetch!; 79 + let response = await _fetch(request); 80 + 81 + for (const fn of interceptors.response._fns) { 82 + if (fn) { 83 + response = await fn(response, request, opts); 84 + } 85 + } 86 + 87 + const result = { 88 + request, 89 + response, 90 + }; 91 + 92 + if (response.ok) { 93 + if ( 94 + response.status === 204 || 95 + response.headers.get('Content-Length') === '0' 96 + ) { 97 + return opts.responseStyle === 'data' 98 + ? {} 99 + : { 100 + data: {}, 101 + ...result, 102 + }; 103 + } 104 + 105 + const parseAs = 106 + (opts.parseAs === 'auto' 107 + ? getParseAs(response.headers.get('Content-Type')) 108 + : opts.parseAs) ?? 'json'; 109 + 110 + let data: any; 111 + switch (parseAs) { 112 + case 'arrayBuffer': 113 + case 'blob': 114 + case 'formData': 115 + case 'json': 116 + case 'text': 117 + data = await response[parseAs](); 118 + break; 119 + case 'stream': 120 + return opts.responseStyle === 'data' 121 + ? response.body 122 + : { 123 + data: response.body, 124 + ...result, 125 + }; 126 + } 127 + 128 + if (parseAs === 'json') { 129 + if (opts.responseValidator) { 130 + await opts.responseValidator(data); 131 + } 132 + 133 + if (opts.responseTransformer) { 134 + data = await opts.responseTransformer(data); 135 + } 136 + } 137 + 138 + return opts.responseStyle === 'data' 139 + ? data 140 + : { 141 + data, 142 + ...result, 143 + }; 144 + } 145 + 146 + let error = await response.text(); 147 + 148 + try { 149 + error = JSON.parse(error); 150 + } catch { 151 + // noop 152 + } 153 + 154 + let finalError = error; 155 + 156 + for (const fn of interceptors.error._fns) { 157 + if (fn) { 158 + finalError = (await fn(error, response, request, opts)) as string; 159 + } 160 + } 161 + 162 + finalError = finalError || ({} as string); 163 + 164 + if (opts.throwOnError) { 165 + throw finalError; 166 + } 167 + 168 + // TODO: we probably want to return error and improve types 169 + return opts.responseStyle === 'data' 170 + ? undefined 171 + : { 172 + error: finalError, 173 + ...result, 174 + }; 175 + }; 176 + 177 + return { 178 + buildUrl, 179 + connect: (options) => request({ ...options, method: 'CONNECT' }), 180 + delete: (options) => request({ ...options, method: 'DELETE' }), 181 + get: (options) => request({ ...options, method: 'GET' }), 182 + getConfig, 183 + head: (options) => request({ ...options, method: 'HEAD' }), 184 + interceptors, 185 + options: (options) => request({ ...options, method: 'OPTIONS' }), 186 + patch: (options) => request({ ...options, method: 'PATCH' }), 187 + post: (options) => request({ ...options, method: 'POST' }), 188 + put: (options) => request({ ...options, method: 'PUT' }), 189 + request, 190 + setConfig, 191 + trace: (options) => request({ ...options, method: 'TRACE' }), 192 + }; 193 + };
+22
examples/openapi-ts-angular/src/client/client/index.ts
··· 1 + export type { Auth } from '../core/auth'; 2 + export type { QuerySerializerOptions } from '../core/bodySerializer'; 3 + export { 4 + formDataBodySerializer, 5 + jsonBodySerializer, 6 + urlSearchParamsBodySerializer, 7 + } from '../core/bodySerializer'; 8 + export { buildClientParams } from '../core/params'; 9 + export { createClient } from './client'; 10 + export type { 11 + Client, 12 + ClientOptions, 13 + Config, 14 + CreateClientConfig, 15 + Options, 16 + OptionsLegacyParser, 17 + RequestOptions, 18 + RequestResult, 19 + ResponseStyle, 20 + TDataShape, 21 + } from './types'; 22 + export { createConfig, mergeHeaders } from './utils';
+219
examples/openapi-ts-angular/src/client/client/types.ts
··· 1 + import type { Auth } from '../core/auth'; 2 + import type { Client as CoreClient, Config as CoreConfig } from '../core/types'; 3 + import type { Middleware } from './utils'; 4 + 5 + export type ResponseStyle = 'data' | 'fields'; 6 + 7 + export interface Config<T extends ClientOptions = ClientOptions> 8 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 9 + CoreConfig { 10 + /** 11 + * Base URL for all requests made by this client. 12 + */ 13 + baseUrl?: T['baseUrl']; 14 + /** 15 + * Fetch API implementation. You can use this option to provide a custom 16 + * fetch instance. 17 + * 18 + * @default globalThis.fetch 19 + */ 20 + fetch?: (request: Request) => ReturnType<typeof fetch>; 21 + /** 22 + * Please don't use the Fetch client for Next.js applications. The `next` 23 + * options won't have any effect. 24 + * 25 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 26 + */ 27 + next?: never; 28 + /** 29 + * Return the response data parsed in a specified format. By default, `auto` 30 + * will infer the appropriate method from the `Content-Type` response header. 31 + * You can override this behavior with any of the {@link Body} methods. 32 + * Select `stream` if you don't want to parse response data at all. 33 + * 34 + * @default 'auto' 35 + */ 36 + parseAs?: 37 + | 'arrayBuffer' 38 + | 'auto' 39 + | 'blob' 40 + | 'formData' 41 + | 'json' 42 + | 'stream' 43 + | 'text'; 44 + /** 45 + * Should we return only data or multiple fields (data, error, response, etc.)? 46 + * 47 + * @default 'fields' 48 + */ 49 + responseStyle?: ResponseStyle; 50 + /** 51 + * Throw an error instead of returning it in the response? 52 + * 53 + * @default false 54 + */ 55 + throwOnError?: T['throwOnError']; 56 + } 57 + 58 + export interface RequestOptions< 59 + TResponseStyle extends ResponseStyle = 'fields', 60 + ThrowOnError extends boolean = boolean, 61 + Url extends string = string, 62 + > extends Config<{ 63 + responseStyle: TResponseStyle; 64 + throwOnError: ThrowOnError; 65 + }> { 66 + /** 67 + * Any body that you want to add to your request. 68 + * 69 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 70 + */ 71 + body?: unknown; 72 + path?: Record<string, unknown>; 73 + query?: Record<string, unknown>; 74 + /** 75 + * Security mechanism(s) to use for the request. 76 + */ 77 + security?: ReadonlyArray<Auth>; 78 + url: Url; 79 + } 80 + 81 + export type RequestResult< 82 + TData = unknown, 83 + TError = unknown, 84 + ThrowOnError extends boolean = boolean, 85 + TResponseStyle extends ResponseStyle = 'fields', 86 + > = ThrowOnError extends true 87 + ? Promise< 88 + TResponseStyle extends 'data' 89 + ? TData extends Record<string, unknown> 90 + ? TData[keyof TData] 91 + : TData 92 + : { 93 + data: TData extends Record<string, unknown> 94 + ? TData[keyof TData] 95 + : TData; 96 + request: Request; 97 + response: Response; 98 + } 99 + > 100 + : Promise< 101 + TResponseStyle extends 'data' 102 + ? 103 + | (TData extends Record<string, unknown> 104 + ? TData[keyof TData] 105 + : TData) 106 + | undefined 107 + : ( 108 + | { 109 + data: TData extends Record<string, unknown> 110 + ? TData[keyof TData] 111 + : TData; 112 + error: undefined; 113 + } 114 + | { 115 + data: undefined; 116 + error: TError extends Record<string, unknown> 117 + ? TError[keyof TError] 118 + : TError; 119 + } 120 + ) & { 121 + request: Request; 122 + response: Response; 123 + } 124 + >; 125 + 126 + export interface ClientOptions { 127 + baseUrl?: string; 128 + responseStyle?: ResponseStyle; 129 + throwOnError?: boolean; 130 + } 131 + 132 + type MethodFn = < 133 + TData = unknown, 134 + TError = unknown, 135 + ThrowOnError extends boolean = false, 136 + TResponseStyle extends ResponseStyle = 'fields', 137 + >( 138 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 139 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 140 + 141 + type RequestFn = < 142 + TData = unknown, 143 + TError = unknown, 144 + ThrowOnError extends boolean = false, 145 + TResponseStyle extends ResponseStyle = 'fields', 146 + >( 147 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 148 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 149 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 150 + 151 + type BuildUrlFn = < 152 + TData extends { 153 + body?: unknown; 154 + path?: Record<string, unknown>; 155 + query?: Record<string, unknown>; 156 + url: string; 157 + }, 158 + >( 159 + options: Pick<TData, 'url'> & Options<TData>, 160 + ) => string; 161 + 162 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 163 + interceptors: Middleware<Request, Response, unknown, RequestOptions>; 164 + }; 165 + 166 + /** 167 + * The `createClientConfig()` function will be called on client initialization 168 + * and the returned object will become the client's initial configuration. 169 + * 170 + * You may want to initialize your client this way instead of calling 171 + * `setConfig()`. This is useful for example if you're using Next.js 172 + * to ensure your client always has the correct values. 173 + */ 174 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 175 + override?: Config<ClientOptions & T>, 176 + ) => Config<Required<ClientOptions> & T>; 177 + 178 + export interface TDataShape { 179 + body?: unknown; 180 + headers?: unknown; 181 + path?: unknown; 182 + query?: unknown; 183 + url: string; 184 + } 185 + 186 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 187 + 188 + export type Options< 189 + TData extends TDataShape = TDataShape, 190 + ThrowOnError extends boolean = boolean, 191 + TResponseStyle extends ResponseStyle = 'fields', 192 + > = OmitKeys< 193 + RequestOptions<TResponseStyle, ThrowOnError>, 194 + 'body' | 'path' | 'query' | 'url' 195 + > & 196 + Omit<TData, 'url'>; 197 + 198 + export type OptionsLegacyParser< 199 + TData = unknown, 200 + ThrowOnError extends boolean = boolean, 201 + TResponseStyle extends ResponseStyle = 'fields', 202 + > = TData extends { body?: any } 203 + ? TData extends { headers?: any } 204 + ? OmitKeys< 205 + RequestOptions<TResponseStyle, ThrowOnError>, 206 + 'body' | 'headers' | 'url' 207 + > & 208 + TData 209 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 210 + TData & 211 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 212 + : TData extends { headers?: any } 213 + ? OmitKeys< 214 + RequestOptions<TResponseStyle, ThrowOnError>, 215 + 'headers' | 'url' 216 + > & 217 + TData & 218 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 219 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+417
examples/openapi-ts-angular/src/client/client/utils.ts
··· 1 + import { getAuthToken } from '../core/auth'; 2 + import type { 3 + QuerySerializer, 4 + QuerySerializerOptions, 5 + } from '../core/bodySerializer'; 6 + import { jsonBodySerializer } from '../core/bodySerializer'; 7 + import { 8 + serializeArrayParam, 9 + serializeObjectParam, 10 + serializePrimitiveParam, 11 + } from '../core/pathSerializer'; 12 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 13 + 14 + interface PathSerializer { 15 + path: Record<string, unknown>; 16 + url: string; 17 + } 18 + 19 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 20 + 21 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 22 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 23 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 24 + 25 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 26 + let url = _url; 27 + const matches = _url.match(PATH_PARAM_RE); 28 + if (matches) { 29 + for (const match of matches) { 30 + let explode = false; 31 + let name = match.substring(1, match.length - 1); 32 + let style: ArraySeparatorStyle = 'simple'; 33 + 34 + if (name.endsWith('*')) { 35 + explode = true; 36 + name = name.substring(0, name.length - 1); 37 + } 38 + 39 + if (name.startsWith('.')) { 40 + name = name.substring(1); 41 + style = 'label'; 42 + } else if (name.startsWith(';')) { 43 + name = name.substring(1); 44 + style = 'matrix'; 45 + } 46 + 47 + const value = path[name]; 48 + 49 + if (value === undefined || value === null) { 50 + continue; 51 + } 52 + 53 + if (Array.isArray(value)) { 54 + url = url.replace( 55 + match, 56 + serializeArrayParam({ explode, name, style, value }), 57 + ); 58 + continue; 59 + } 60 + 61 + if (typeof value === 'object') { 62 + url = url.replace( 63 + match, 64 + serializeObjectParam({ 65 + explode, 66 + name, 67 + style, 68 + value: value as Record<string, unknown>, 69 + valueOnly: true, 70 + }), 71 + ); 72 + continue; 73 + } 74 + 75 + if (style === 'matrix') { 76 + url = url.replace( 77 + match, 78 + `;${serializePrimitiveParam({ 79 + name, 80 + value: value as string, 81 + })}`, 82 + ); 83 + continue; 84 + } 85 + 86 + const replaceValue = encodeURIComponent( 87 + style === 'label' ? `.${value as string}` : (value as string), 88 + ); 89 + url = url.replace(match, replaceValue); 90 + } 91 + } 92 + return url; 93 + }; 94 + 95 + export const createQuerySerializer = <T = unknown>({ 96 + allowReserved, 97 + array, 98 + object, 99 + }: QuerySerializerOptions = {}) => { 100 + const querySerializer = (queryParams: T) => { 101 + const search: string[] = []; 102 + if (queryParams && typeof queryParams === 'object') { 103 + for (const name in queryParams) { 104 + const value = queryParams[name]; 105 + 106 + if (value === undefined || value === null) { 107 + continue; 108 + } 109 + 110 + if (Array.isArray(value)) { 111 + const serializedArray = serializeArrayParam({ 112 + allowReserved, 113 + explode: true, 114 + name, 115 + style: 'form', 116 + value, 117 + ...array, 118 + }); 119 + if (serializedArray) search.push(serializedArray); 120 + } else if (typeof value === 'object') { 121 + const serializedObject = serializeObjectParam({ 122 + allowReserved, 123 + explode: true, 124 + name, 125 + style: 'deepObject', 126 + value: value as Record<string, unknown>, 127 + ...object, 128 + }); 129 + if (serializedObject) search.push(serializedObject); 130 + } else { 131 + const serializedPrimitive = serializePrimitiveParam({ 132 + allowReserved, 133 + name, 134 + value: value as string, 135 + }); 136 + if (serializedPrimitive) search.push(serializedPrimitive); 137 + } 138 + } 139 + } 140 + return search.join('&'); 141 + }; 142 + return querySerializer; 143 + }; 144 + 145 + /** 146 + * Infers parseAs value from provided Content-Type header. 147 + */ 148 + export const getParseAs = ( 149 + contentType: string | null, 150 + ): Exclude<Config['parseAs'], 'auto'> => { 151 + if (!contentType) { 152 + // If no Content-Type header is provided, the best we can do is return the raw response body, 153 + // which is effectively the same as the 'stream' option. 154 + return 'stream'; 155 + } 156 + 157 + const cleanContent = contentType.split(';')[0]?.trim(); 158 + 159 + if (!cleanContent) { 160 + return; 161 + } 162 + 163 + if ( 164 + cleanContent.startsWith('application/json') || 165 + cleanContent.endsWith('+json') 166 + ) { 167 + return 'json'; 168 + } 169 + 170 + if (cleanContent === 'multipart/form-data') { 171 + return 'formData'; 172 + } 173 + 174 + if ( 175 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 176 + cleanContent.startsWith(type), 177 + ) 178 + ) { 179 + return 'blob'; 180 + } 181 + 182 + if (cleanContent.startsWith('text/')) { 183 + return 'text'; 184 + } 185 + 186 + return; 187 + }; 188 + 189 + export const setAuthParams = async ({ 190 + security, 191 + ...options 192 + }: Pick<Required<RequestOptions>, 'security'> & 193 + Pick<RequestOptions, 'auth' | 'query'> & { 194 + headers: Headers; 195 + }) => { 196 + for (const auth of security) { 197 + const token = await getAuthToken(auth, options.auth); 198 + 199 + if (!token) { 200 + continue; 201 + } 202 + 203 + const name = auth.name ?? 'Authorization'; 204 + 205 + switch (auth.in) { 206 + case 'query': 207 + if (!options.query) { 208 + options.query = {}; 209 + } 210 + options.query[name] = token; 211 + break; 212 + case 'cookie': 213 + options.headers.append('Cookie', `${name}=${token}`); 214 + break; 215 + case 'header': 216 + default: 217 + options.headers.set(name, token); 218 + break; 219 + } 220 + 221 + return; 222 + } 223 + }; 224 + 225 + export const buildUrl: Client['buildUrl'] = (options) => { 226 + const url = getUrl({ 227 + baseUrl: options.baseUrl as string, 228 + path: options.path, 229 + query: options.query, 230 + querySerializer: 231 + typeof options.querySerializer === 'function' 232 + ? options.querySerializer 233 + : createQuerySerializer(options.querySerializer), 234 + url: options.url, 235 + }); 236 + return url; 237 + }; 238 + 239 + export const getUrl = ({ 240 + baseUrl, 241 + path, 242 + query, 243 + querySerializer, 244 + url: _url, 245 + }: { 246 + baseUrl?: string; 247 + path?: Record<string, unknown>; 248 + query?: Record<string, unknown>; 249 + querySerializer: QuerySerializer; 250 + url: string; 251 + }) => { 252 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 253 + let url = (baseUrl ?? '') + pathUrl; 254 + if (path) { 255 + url = defaultPathSerializer({ path, url }); 256 + } 257 + let search = query ? querySerializer(query) : ''; 258 + if (search.startsWith('?')) { 259 + search = search.substring(1); 260 + } 261 + if (search) { 262 + url += `?${search}`; 263 + } 264 + return url; 265 + }; 266 + 267 + export const mergeConfigs = (a: Config, b: Config): Config => { 268 + const config = { ...a, ...b }; 269 + if (config.baseUrl?.endsWith('/')) { 270 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 271 + } 272 + config.headers = mergeHeaders(a.headers, b.headers); 273 + return config; 274 + }; 275 + 276 + export const mergeHeaders = ( 277 + ...headers: Array<Required<Config>['headers'] | undefined> 278 + ): Headers => { 279 + const mergedHeaders = new Headers(); 280 + for (const header of headers) { 281 + if (!header || typeof header !== 'object') { 282 + continue; 283 + } 284 + 285 + const iterator = 286 + header instanceof Headers ? header.entries() : Object.entries(header); 287 + 288 + for (const [key, value] of iterator) { 289 + if (value === null) { 290 + mergedHeaders.delete(key); 291 + } else if (Array.isArray(value)) { 292 + for (const v of value) { 293 + mergedHeaders.append(key, v as string); 294 + } 295 + } else if (value !== undefined) { 296 + // assume object headers are meant to be JSON stringified, i.e. their 297 + // content value in OpenAPI specification is 'application/json' 298 + mergedHeaders.set( 299 + key, 300 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 301 + ); 302 + } 303 + } 304 + } 305 + return mergedHeaders; 306 + }; 307 + 308 + type ErrInterceptor<Err, Res, Req, Options> = ( 309 + error: Err, 310 + response: Res, 311 + request: Req, 312 + options: Options, 313 + ) => Err | Promise<Err>; 314 + 315 + type ReqInterceptor<Req, Options> = ( 316 + request: Req, 317 + options: Options, 318 + ) => Req | Promise<Req>; 319 + 320 + type ResInterceptor<Res, Req, Options> = ( 321 + response: Res, 322 + request: Req, 323 + options: Options, 324 + ) => Res | Promise<Res>; 325 + 326 + class Interceptors<Interceptor> { 327 + _fns: (Interceptor | null)[]; 328 + 329 + constructor() { 330 + this._fns = []; 331 + } 332 + 333 + clear() { 334 + this._fns = []; 335 + } 336 + 337 + getInterceptorIndex(id: number | Interceptor): number { 338 + if (typeof id === 'number') { 339 + return this._fns[id] ? id : -1; 340 + } else { 341 + return this._fns.indexOf(id); 342 + } 343 + } 344 + exists(id: number | Interceptor) { 345 + const index = this.getInterceptorIndex(id); 346 + return !!this._fns[index]; 347 + } 348 + 349 + eject(id: number | Interceptor) { 350 + const index = this.getInterceptorIndex(id); 351 + if (this._fns[index]) { 352 + this._fns[index] = null; 353 + } 354 + } 355 + 356 + update(id: number | Interceptor, fn: Interceptor) { 357 + const index = this.getInterceptorIndex(id); 358 + if (this._fns[index]) { 359 + this._fns[index] = fn; 360 + return id; 361 + } else { 362 + return false; 363 + } 364 + } 365 + 366 + use(fn: Interceptor) { 367 + this._fns = [...this._fns, fn]; 368 + return this._fns.length - 1; 369 + } 370 + } 371 + 372 + // `createInterceptors()` response, meant for external use as it does not 373 + // expose internals 374 + export interface Middleware<Req, Res, Err, Options> { 375 + error: Pick< 376 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 377 + 'eject' | 'use' 378 + >; 379 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 380 + response: Pick< 381 + Interceptors<ResInterceptor<Res, Req, Options>>, 382 + 'eject' | 'use' 383 + >; 384 + } 385 + 386 + // do not add `Middleware` as return type so we can use _fns internally 387 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 388 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 389 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 390 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 391 + }); 392 + 393 + const defaultQuerySerializer = createQuerySerializer({ 394 + allowReserved: false, 395 + array: { 396 + explode: true, 397 + style: 'form', 398 + }, 399 + object: { 400 + explode: true, 401 + style: 'deepObject', 402 + }, 403 + }); 404 + 405 + const defaultHeaders = { 406 + 'Content-Type': 'application/json', 407 + }; 408 + 409 + export const createConfig = <T extends ClientOptions = ClientOptions>( 410 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 411 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 412 + ...jsonBodySerializer, 413 + headers: defaultHeaders, 414 + parseAs: 'auto', 415 + querySerializer: defaultQuerySerializer, 416 + ...override, 417 + });
+40
examples/openapi-ts-angular/src/client/core/auth.ts
··· 1 + export type AuthToken = string | undefined; 2 + 3 + export interface Auth { 4 + /** 5 + * Which part of the request do we use to send the auth? 6 + * 7 + * @default 'header' 8 + */ 9 + in?: 'header' | 'query' | 'cookie'; 10 + /** 11 + * Header or query parameter name. 12 + * 13 + * @default 'Authorization' 14 + */ 15 + name?: string; 16 + scheme?: 'basic' | 'bearer'; 17 + type: 'apiKey' | 'http'; 18 + } 19 + 20 + export const getAuthToken = async ( 21 + auth: Auth, 22 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 23 + ): Promise<string | undefined> => { 24 + const token = 25 + typeof callback === 'function' ? await callback(auth) : callback; 26 + 27 + if (!token) { 28 + return; 29 + } 30 + 31 + if (auth.scheme === 'bearer') { 32 + return `Bearer ${token}`; 33 + } 34 + 35 + if (auth.scheme === 'basic') { 36 + return `Basic ${btoa(token)}`; 37 + } 38 + 39 + return token; 40 + };
+84
examples/openapi-ts-angular/src/client/core/bodySerializer.ts
··· 1 + import type { 2 + ArrayStyle, 3 + ObjectStyle, 4 + SerializerOptions, 5 + } from './pathSerializer'; 6 + 7 + export type QuerySerializer = (query: Record<string, unknown>) => string; 8 + 9 + export type BodySerializer = (body: any) => any; 10 + 11 + export interface QuerySerializerOptions { 12 + allowReserved?: boolean; 13 + array?: SerializerOptions<ArrayStyle>; 14 + object?: SerializerOptions<ObjectStyle>; 15 + } 16 + 17 + const serializeFormDataPair = (data: FormData, key: string, value: unknown) => { 18 + if (typeof value === 'string' || value instanceof Blob) { 19 + data.append(key, value); 20 + } else { 21 + data.append(key, JSON.stringify(value)); 22 + } 23 + }; 24 + 25 + const serializeUrlSearchParamsPair = ( 26 + data: URLSearchParams, 27 + key: string, 28 + value: unknown, 29 + ) => { 30 + if (typeof value === 'string') { 31 + data.append(key, value); 32 + } else { 33 + data.append(key, JSON.stringify(value)); 34 + } 35 + }; 36 + 37 + export const formDataBodySerializer = { 38 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 39 + body: T, 40 + ) => { 41 + const data = new FormData(); 42 + 43 + Object.entries(body).forEach(([key, value]) => { 44 + if (value === undefined || value === null) { 45 + return; 46 + } 47 + if (Array.isArray(value)) { 48 + value.forEach((v) => serializeFormDataPair(data, key, v)); 49 + } else { 50 + serializeFormDataPair(data, key, value); 51 + } 52 + }); 53 + 54 + return data; 55 + }, 56 + }; 57 + 58 + export const jsonBodySerializer = { 59 + bodySerializer: <T>(body: T) => 60 + JSON.stringify(body, (_key, value) => 61 + typeof value === 'bigint' ? value.toString() : value, 62 + ), 63 + }; 64 + 65 + export const urlSearchParamsBodySerializer = { 66 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 67 + body: T, 68 + ) => { 69 + const data = new URLSearchParams(); 70 + 71 + Object.entries(body).forEach(([key, value]) => { 72 + if (value === undefined || value === null) { 73 + return; 74 + } 75 + if (Array.isArray(value)) { 76 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 77 + } else { 78 + serializeUrlSearchParamsPair(data, key, value); 79 + } 80 + }); 81 + 82 + return data.toString(); 83 + }, 84 + };
+141
examples/openapi-ts-angular/src/client/core/params.ts
··· 1 + type Slot = 'body' | 'headers' | 'path' | 'query'; 2 + 3 + export type Field = 4 + | { 5 + in: Exclude<Slot, 'body'>; 6 + key: string; 7 + map?: string; 8 + } 9 + | { 10 + in: Extract<Slot, 'body'>; 11 + key?: string; 12 + map?: string; 13 + }; 14 + 15 + export interface Fields { 16 + allowExtra?: Partial<Record<Slot, boolean>>; 17 + args?: ReadonlyArray<Field>; 18 + } 19 + 20 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 21 + 22 + const extraPrefixesMap: Record<string, Slot> = { 23 + $body_: 'body', 24 + $headers_: 'headers', 25 + $path_: 'path', 26 + $query_: 'query', 27 + }; 28 + const extraPrefixes = Object.entries(extraPrefixesMap); 29 + 30 + type KeyMap = Map< 31 + string, 32 + { 33 + in: Slot; 34 + map?: string; 35 + } 36 + >; 37 + 38 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 39 + if (!map) { 40 + map = new Map(); 41 + } 42 + 43 + for (const config of fields) { 44 + if ('in' in config) { 45 + if (config.key) { 46 + map.set(config.key, { 47 + in: config.in, 48 + map: config.map, 49 + }); 50 + } 51 + } else if (config.args) { 52 + buildKeyMap(config.args, map); 53 + } 54 + } 55 + 56 + return map; 57 + }; 58 + 59 + interface Params { 60 + body: unknown; 61 + headers: Record<string, unknown>; 62 + path: Record<string, unknown>; 63 + query: Record<string, unknown>; 64 + } 65 + 66 + const stripEmptySlots = (params: Params) => { 67 + for (const [slot, value] of Object.entries(params)) { 68 + if (value && typeof value === 'object' && !Object.keys(value).length) { 69 + delete params[slot as Slot]; 70 + } 71 + } 72 + }; 73 + 74 + export const buildClientParams = ( 75 + args: ReadonlyArray<unknown>, 76 + fields: FieldsConfig, 77 + ) => { 78 + const params: Params = { 79 + body: {}, 80 + headers: {}, 81 + path: {}, 82 + query: {}, 83 + }; 84 + 85 + const map = buildKeyMap(fields); 86 + 87 + let config: FieldsConfig[number] | undefined; 88 + 89 + for (const [index, arg] of args.entries()) { 90 + if (fields[index]) { 91 + config = fields[index]; 92 + } 93 + 94 + if (!config) { 95 + continue; 96 + } 97 + 98 + if ('in' in config) { 99 + if (config.key) { 100 + const field = map.get(config.key)!; 101 + const name = field.map || config.key; 102 + (params[field.in] as Record<string, unknown>)[name] = arg; 103 + } else { 104 + params.body = arg; 105 + } 106 + } else { 107 + for (const [key, value] of Object.entries(arg ?? {})) { 108 + const field = map.get(key); 109 + 110 + if (field) { 111 + const name = field.map || key; 112 + (params[field.in] as Record<string, unknown>)[name] = value; 113 + } else { 114 + const extra = extraPrefixes.find(([prefix]) => 115 + key.startsWith(prefix), 116 + ); 117 + 118 + if (extra) { 119 + const [prefix, slot] = extra; 120 + (params[slot] as Record<string, unknown>)[ 121 + key.slice(prefix.length) 122 + ] = value; 123 + } else { 124 + for (const [slot, allowed] of Object.entries( 125 + config.allowExtra ?? {}, 126 + )) { 127 + if (allowed) { 128 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 129 + break; 130 + } 131 + } 132 + } 133 + } 134 + } 135 + } 136 + } 137 + 138 + stripEmptySlots(params); 139 + 140 + return params; 141 + };
+179
examples/openapi-ts-angular/src/client/core/pathSerializer.ts
··· 1 + interface SerializeOptions<T> 2 + extends SerializePrimitiveOptions, 3 + SerializerOptions<T> {} 4 + 5 + interface SerializePrimitiveOptions { 6 + allowReserved?: boolean; 7 + name: string; 8 + } 9 + 10 + export interface SerializerOptions<T> { 11 + /** 12 + * @default true 13 + */ 14 + explode: boolean; 15 + style: T; 16 + } 17 + 18 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 19 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 20 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 21 + export type ObjectStyle = 'form' | 'deepObject'; 22 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 23 + 24 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 25 + value: string; 26 + } 27 + 28 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 29 + switch (style) { 30 + case 'label': 31 + return '.'; 32 + case 'matrix': 33 + return ';'; 34 + case 'simple': 35 + return ','; 36 + default: 37 + return '&'; 38 + } 39 + }; 40 + 41 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 42 + switch (style) { 43 + case 'form': 44 + return ','; 45 + case 'pipeDelimited': 46 + return '|'; 47 + case 'spaceDelimited': 48 + return '%20'; 49 + default: 50 + return ','; 51 + } 52 + }; 53 + 54 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 55 + switch (style) { 56 + case 'label': 57 + return '.'; 58 + case 'matrix': 59 + return ';'; 60 + case 'simple': 61 + return ','; 62 + default: 63 + return '&'; 64 + } 65 + }; 66 + 67 + export const serializeArrayParam = ({ 68 + allowReserved, 69 + explode, 70 + name, 71 + style, 72 + value, 73 + }: SerializeOptions<ArraySeparatorStyle> & { 74 + value: unknown[]; 75 + }) => { 76 + if (!explode) { 77 + const joinedValues = ( 78 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 79 + ).join(separatorArrayNoExplode(style)); 80 + switch (style) { 81 + case 'label': 82 + return `.${joinedValues}`; 83 + case 'matrix': 84 + return `;${name}=${joinedValues}`; 85 + case 'simple': 86 + return joinedValues; 87 + default: 88 + return `${name}=${joinedValues}`; 89 + } 90 + } 91 + 92 + const separator = separatorArrayExplode(style); 93 + const joinedValues = value 94 + .map((v) => { 95 + if (style === 'label' || style === 'simple') { 96 + return allowReserved ? v : encodeURIComponent(v as string); 97 + } 98 + 99 + return serializePrimitiveParam({ 100 + allowReserved, 101 + name, 102 + value: v as string, 103 + }); 104 + }) 105 + .join(separator); 106 + return style === 'label' || style === 'matrix' 107 + ? separator + joinedValues 108 + : joinedValues; 109 + }; 110 + 111 + export const serializePrimitiveParam = ({ 112 + allowReserved, 113 + name, 114 + value, 115 + }: SerializePrimitiveParam) => { 116 + if (value === undefined || value === null) { 117 + return ''; 118 + } 119 + 120 + if (typeof value === 'object') { 121 + throw new Error( 122 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 123 + ); 124 + } 125 + 126 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 127 + }; 128 + 129 + export const serializeObjectParam = ({ 130 + allowReserved, 131 + explode, 132 + name, 133 + style, 134 + value, 135 + valueOnly, 136 + }: SerializeOptions<ObjectSeparatorStyle> & { 137 + value: Record<string, unknown> | Date; 138 + valueOnly?: boolean; 139 + }) => { 140 + if (value instanceof Date) { 141 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 142 + } 143 + 144 + if (style !== 'deepObject' && !explode) { 145 + let values: string[] = []; 146 + Object.entries(value).forEach(([key, v]) => { 147 + values = [ 148 + ...values, 149 + key, 150 + allowReserved ? (v as string) : encodeURIComponent(v as string), 151 + ]; 152 + }); 153 + const joinedValues = values.join(','); 154 + switch (style) { 155 + case 'form': 156 + return `${name}=${joinedValues}`; 157 + case 'label': 158 + return `.${joinedValues}`; 159 + case 'matrix': 160 + return `;${name}=${joinedValues}`; 161 + default: 162 + return joinedValues; 163 + } 164 + } 165 + 166 + const separator = separatorObjectExplode(style); 167 + const joinedValues = Object.entries(value) 168 + .map(([key, v]) => 169 + serializePrimitiveParam({ 170 + allowReserved, 171 + name: style === 'deepObject' ? `${name}[${key}]` : key, 172 + value: v as string, 173 + }), 174 + ) 175 + .join(separator); 176 + return style === 'label' || style === 'matrix' 177 + ? separator + joinedValues 178 + : joinedValues; 179 + };
+104
examples/openapi-ts-angular/src/client/core/types.ts
··· 1 + import type { Auth, AuthToken } from './auth'; 2 + import type { 3 + BodySerializer, 4 + QuerySerializer, 5 + QuerySerializerOptions, 6 + } from './bodySerializer'; 7 + 8 + export interface Client< 9 + RequestFn = never, 10 + Config = unknown, 11 + MethodFn = never, 12 + BuildUrlFn = never, 13 + > { 14 + /** 15 + * Returns the final request URL. 16 + */ 17 + buildUrl: BuildUrlFn; 18 + connect: MethodFn; 19 + delete: MethodFn; 20 + get: MethodFn; 21 + getConfig: () => Config; 22 + head: MethodFn; 23 + options: MethodFn; 24 + patch: MethodFn; 25 + post: MethodFn; 26 + put: MethodFn; 27 + request: RequestFn; 28 + setConfig: (config: Config) => Config; 29 + trace: MethodFn; 30 + } 31 + 32 + export interface Config { 33 + /** 34 + * Auth token or a function returning auth token. The resolved value will be 35 + * added to the request payload as defined by its `security` array. 36 + */ 37 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 38 + /** 39 + * A function for serializing request body parameter. By default, 40 + * {@link JSON.stringify()} will be used. 41 + */ 42 + bodySerializer?: BodySerializer | null; 43 + /** 44 + * An object containing any HTTP headers that you want to pre-populate your 45 + * `Headers` object with. 46 + * 47 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 48 + */ 49 + headers?: 50 + | RequestInit['headers'] 51 + | Record< 52 + string, 53 + | string 54 + | number 55 + | boolean 56 + | (string | number | boolean)[] 57 + | null 58 + | undefined 59 + | unknown 60 + >; 61 + /** 62 + * The request method. 63 + * 64 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 65 + */ 66 + method?: 67 + | 'CONNECT' 68 + | 'DELETE' 69 + | 'GET' 70 + | 'HEAD' 71 + | 'OPTIONS' 72 + | 'PATCH' 73 + | 'POST' 74 + | 'PUT' 75 + | 'TRACE'; 76 + /** 77 + * A function for serializing request query parameters. By default, arrays 78 + * will be exploded in form style, objects will be exploded in deepObject 79 + * style, and reserved characters are percent-encoded. 80 + * 81 + * This method will have no effect if the native `paramsSerializer()` Axios 82 + * API function is used. 83 + * 84 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 85 + */ 86 + querySerializer?: QuerySerializer | QuerySerializerOptions; 87 + /** 88 + * A function validating request data. This is useful if you want to ensure 89 + * the request conforms to the desired shape, so it can be safely sent to 90 + * the server. 91 + */ 92 + requestValidator?: (data: unknown) => Promise<unknown>; 93 + /** 94 + * A function transforming response data before it's returned. This is useful 95 + * for post-processing data, e.g. converting ISO strings into Date objects. 96 + */ 97 + responseTransformer?: (data: unknown) => Promise<unknown>; 98 + /** 99 + * A function validating response data. This is useful if you want to ensure 100 + * the response conforms to the desired shape, so it can be safely passed to 101 + * the transformers and returned to the user. 102 + */ 103 + responseValidator?: (data: unknown) => Promise<unknown>; 104 + }
+3
examples/openapi-ts-angular/src/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './sdk.gen'; 3 + export * from './types.gen';
+188
examples/openapi-ts-angular/src/client/schemas.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export const OrderSchema = { 4 + properties: { 5 + complete: { 6 + type: 'boolean', 7 + }, 8 + id: { 9 + example: 10, 10 + format: 'int64', 11 + type: 'integer', 12 + }, 13 + petId: { 14 + example: 198772, 15 + format: 'int64', 16 + type: 'integer', 17 + }, 18 + quantity: { 19 + example: 7, 20 + format: 'int32', 21 + type: 'integer', 22 + }, 23 + shipDate: { 24 + format: 'date-time', 25 + type: 'string', 26 + }, 27 + status: { 28 + description: 'Order Status', 29 + enum: ['placed', 'approved', 'delivered'], 30 + example: 'approved', 31 + type: 'string', 32 + }, 33 + }, 34 + type: 'object', 35 + 'x-swagger-router-model': 'io.swagger.petstore.model.Order', 36 + xml: { 37 + name: 'order', 38 + }, 39 + } as const; 40 + 41 + export const CategorySchema = { 42 + properties: { 43 + id: { 44 + example: 1, 45 + format: 'int64', 46 + type: 'integer', 47 + }, 48 + name: { 49 + example: 'Dogs', 50 + type: 'string', 51 + }, 52 + }, 53 + type: 'object', 54 + 'x-swagger-router-model': 'io.swagger.petstore.model.Category', 55 + xml: { 56 + name: 'category', 57 + }, 58 + } as const; 59 + 60 + export const UserSchema = { 61 + properties: { 62 + email: { 63 + example: 'john@email.com', 64 + type: 'string', 65 + }, 66 + firstName: { 67 + example: 'John', 68 + type: 'string', 69 + }, 70 + id: { 71 + example: 10, 72 + format: 'int64', 73 + type: 'integer', 74 + }, 75 + lastName: { 76 + example: 'James', 77 + type: 'string', 78 + }, 79 + password: { 80 + example: '12345', 81 + type: 'string', 82 + }, 83 + phone: { 84 + example: '12345', 85 + type: 'string', 86 + }, 87 + userStatus: { 88 + description: 'User Status', 89 + example: 1, 90 + format: 'int32', 91 + type: 'integer', 92 + }, 93 + username: { 94 + example: 'theUser', 95 + type: 'string', 96 + }, 97 + }, 98 + type: 'object', 99 + 'x-swagger-router-model': 'io.swagger.petstore.model.User', 100 + xml: { 101 + name: 'user', 102 + }, 103 + } as const; 104 + 105 + export const TagSchema = { 106 + properties: { 107 + id: { 108 + format: 'int64', 109 + type: 'integer', 110 + }, 111 + name: { 112 + type: 'string', 113 + }, 114 + }, 115 + type: 'object', 116 + 'x-swagger-router-model': 'io.swagger.petstore.model.Tag', 117 + xml: { 118 + name: 'tag', 119 + }, 120 + } as const; 121 + 122 + export const PetSchema = { 123 + properties: { 124 + category: { 125 + $ref: '#/components/schemas/Category', 126 + }, 127 + id: { 128 + example: 10, 129 + format: 'int64', 130 + type: 'integer', 131 + }, 132 + name: { 133 + example: 'doggie', 134 + type: 'string', 135 + }, 136 + photoUrls: { 137 + items: { 138 + type: 'string', 139 + xml: { 140 + name: 'photoUrl', 141 + }, 142 + }, 143 + type: 'array', 144 + xml: { 145 + wrapped: true, 146 + }, 147 + }, 148 + status: { 149 + description: 'pet status in the store', 150 + enum: ['available', 'pending', 'sold'], 151 + type: 'string', 152 + }, 153 + tags: { 154 + items: { 155 + $ref: '#/components/schemas/Tag', 156 + }, 157 + type: 'array', 158 + xml: { 159 + wrapped: true, 160 + }, 161 + }, 162 + }, 163 + required: ['name', 'photoUrls'], 164 + type: 'object', 165 + 'x-swagger-router-model': 'io.swagger.petstore.model.Pet', 166 + xml: { 167 + name: 'pet', 168 + }, 169 + } as const; 170 + 171 + export const ApiResponseSchema = { 172 + properties: { 173 + code: { 174 + format: 'int32', 175 + type: 'integer', 176 + }, 177 + message: { 178 + type: 'string', 179 + }, 180 + type: { 181 + type: 'string', 182 + }, 183 + }, 184 + type: 'object', 185 + xml: { 186 + name: '##default', 187 + }, 188 + } as const;
+471
examples/openapi-ts-angular/src/client/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Options as ClientOptions, TDataShape } from './client'; 4 + import { client as _heyApiClient } from './client.gen'; 5 + import type { 6 + AddPetData, 7 + AddPetErrors, 8 + AddPetResponses, 9 + CreateUserData, 10 + CreateUserErrors, 11 + CreateUserResponses, 12 + CreateUsersWithListInputData, 13 + CreateUsersWithListInputErrors, 14 + CreateUsersWithListInputResponses, 15 + DeleteOrderData, 16 + DeleteOrderErrors, 17 + DeleteOrderResponses, 18 + DeletePetData, 19 + DeletePetErrors, 20 + DeletePetResponses, 21 + DeleteUserData, 22 + DeleteUserErrors, 23 + DeleteUserResponses, 24 + FindPetsByStatusData, 25 + FindPetsByStatusErrors, 26 + FindPetsByStatusResponses, 27 + FindPetsByTagsData, 28 + FindPetsByTagsErrors, 29 + FindPetsByTagsResponses, 30 + GetInventoryData, 31 + GetInventoryErrors, 32 + GetInventoryResponses, 33 + GetOrderByIdData, 34 + GetOrderByIdErrors, 35 + GetOrderByIdResponses, 36 + GetPetByIdData, 37 + GetPetByIdErrors, 38 + GetPetByIdResponses, 39 + GetUserByNameData, 40 + GetUserByNameErrors, 41 + GetUserByNameResponses, 42 + LoginUserData, 43 + LoginUserErrors, 44 + LoginUserResponses, 45 + LogoutUserData, 46 + LogoutUserErrors, 47 + LogoutUserResponses, 48 + PlaceOrderData, 49 + PlaceOrderErrors, 50 + PlaceOrderResponses, 51 + UpdatePetData, 52 + UpdatePetErrors, 53 + UpdatePetResponses, 54 + UpdatePetWithFormData, 55 + UpdatePetWithFormErrors, 56 + UpdatePetWithFormResponses, 57 + UpdateUserData, 58 + UpdateUserErrors, 59 + UpdateUserResponses, 60 + UploadFileData, 61 + UploadFileErrors, 62 + UploadFileResponses, 63 + } from './types.gen'; 64 + 65 + export type Options< 66 + TData extends TDataShape = TDataShape, 67 + ThrowOnError extends boolean = boolean, 68 + > = ClientOptions<TData, ThrowOnError> & { 69 + /** 70 + * You can provide a client instance returned by `createClient()` instead of 71 + * individual options. This might be also useful if you want to implement a 72 + * custom client. 73 + */ 74 + client?: Client; 75 + /** 76 + * You can pass arbitrary values through the `meta` object. This can be 77 + * used to access values that aren't defined as part of the SDK function. 78 + */ 79 + meta?: Record<string, unknown>; 80 + }; 81 + 82 + /** 83 + * Add a new pet to the store. 84 + * Add a new pet to the store. 85 + */ 86 + export const addPet = <ThrowOnError extends boolean = false>( 87 + options: Options<AddPetData, ThrowOnError>, 88 + ) => 89 + (options.client ?? _heyApiClient).post< 90 + AddPetResponses, 91 + AddPetErrors, 92 + ThrowOnError 93 + >({ 94 + security: [ 95 + { 96 + scheme: 'bearer', 97 + type: 'http', 98 + }, 99 + ], 100 + url: '/pet', 101 + ...options, 102 + headers: { 103 + 'Content-Type': 'application/json', 104 + ...options.headers, 105 + }, 106 + }); 107 + 108 + /** 109 + * Update an existing pet. 110 + * Update an existing pet by Id. 111 + */ 112 + export const updatePet = <ThrowOnError extends boolean = false>( 113 + options: Options<UpdatePetData, ThrowOnError>, 114 + ) => 115 + (options.client ?? _heyApiClient).put< 116 + UpdatePetResponses, 117 + UpdatePetErrors, 118 + ThrowOnError 119 + >({ 120 + security: [ 121 + { 122 + scheme: 'bearer', 123 + type: 'http', 124 + }, 125 + ], 126 + url: '/pet', 127 + ...options, 128 + headers: { 129 + 'Content-Type': 'application/json', 130 + ...options.headers, 131 + }, 132 + }); 133 + 134 + /** 135 + * Finds Pets by status. 136 + * Multiple status values can be provided with comma separated strings. 137 + */ 138 + export const findPetsByStatus = <ThrowOnError extends boolean = false>( 139 + options?: Options<FindPetsByStatusData, ThrowOnError>, 140 + ) => 141 + (options?.client ?? _heyApiClient).get< 142 + FindPetsByStatusResponses, 143 + FindPetsByStatusErrors, 144 + ThrowOnError 145 + >({ 146 + security: [ 147 + { 148 + scheme: 'bearer', 149 + type: 'http', 150 + }, 151 + ], 152 + url: '/pet/findByStatus', 153 + ...options, 154 + }); 155 + 156 + /** 157 + * Finds Pets by tags. 158 + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 159 + */ 160 + export const findPetsByTags = <ThrowOnError extends boolean = false>( 161 + options?: Options<FindPetsByTagsData, ThrowOnError>, 162 + ) => 163 + (options?.client ?? _heyApiClient).get< 164 + FindPetsByTagsResponses, 165 + FindPetsByTagsErrors, 166 + ThrowOnError 167 + >({ 168 + security: [ 169 + { 170 + scheme: 'bearer', 171 + type: 'http', 172 + }, 173 + ], 174 + url: '/pet/findByTags', 175 + ...options, 176 + }); 177 + 178 + /** 179 + * Deletes a pet. 180 + * Delete a pet. 181 + */ 182 + export const deletePet = <ThrowOnError extends boolean = false>( 183 + options: Options<DeletePetData, ThrowOnError>, 184 + ) => 185 + (options.client ?? _heyApiClient).delete< 186 + DeletePetResponses, 187 + DeletePetErrors, 188 + ThrowOnError 189 + >({ 190 + security: [ 191 + { 192 + scheme: 'bearer', 193 + type: 'http', 194 + }, 195 + ], 196 + url: '/pet/{petId}', 197 + ...options, 198 + }); 199 + 200 + /** 201 + * Find pet by ID. 202 + * Returns a single pet. 203 + */ 204 + export const getPetById = <ThrowOnError extends boolean = false>( 205 + options: Options<GetPetByIdData, ThrowOnError>, 206 + ) => 207 + (options.client ?? _heyApiClient).get< 208 + GetPetByIdResponses, 209 + GetPetByIdErrors, 210 + ThrowOnError 211 + >({ 212 + security: [ 213 + { 214 + name: 'api_key', 215 + type: 'apiKey', 216 + }, 217 + { 218 + scheme: 'bearer', 219 + type: 'http', 220 + }, 221 + ], 222 + url: '/pet/{petId}', 223 + ...options, 224 + }); 225 + 226 + /** 227 + * Updates a pet in the store with form data. 228 + * Updates a pet resource based on the form data. 229 + */ 230 + export const updatePetWithForm = <ThrowOnError extends boolean = false>( 231 + options: Options<UpdatePetWithFormData, ThrowOnError>, 232 + ) => 233 + (options.client ?? _heyApiClient).post< 234 + UpdatePetWithFormResponses, 235 + UpdatePetWithFormErrors, 236 + ThrowOnError 237 + >({ 238 + security: [ 239 + { 240 + scheme: 'bearer', 241 + type: 'http', 242 + }, 243 + ], 244 + url: '/pet/{petId}', 245 + ...options, 246 + }); 247 + 248 + /** 249 + * Uploads an image. 250 + * Upload image of the pet. 251 + */ 252 + export const uploadFile = <ThrowOnError extends boolean = false>( 253 + options: Options<UploadFileData, ThrowOnError>, 254 + ) => 255 + (options.client ?? _heyApiClient).post< 256 + UploadFileResponses, 257 + UploadFileErrors, 258 + ThrowOnError 259 + >({ 260 + bodySerializer: null, 261 + security: [ 262 + { 263 + scheme: 'bearer', 264 + type: 'http', 265 + }, 266 + ], 267 + url: '/pet/{petId}/uploadImage', 268 + ...options, 269 + headers: { 270 + 'Content-Type': 'application/octet-stream', 271 + ...options.headers, 272 + }, 273 + }); 274 + 275 + /** 276 + * Returns pet inventories by status. 277 + * Returns a map of status codes to quantities. 278 + */ 279 + export const getInventory = <ThrowOnError extends boolean = false>( 280 + options?: Options<GetInventoryData, ThrowOnError>, 281 + ) => 282 + (options?.client ?? _heyApiClient).get< 283 + GetInventoryResponses, 284 + GetInventoryErrors, 285 + ThrowOnError 286 + >({ 287 + security: [ 288 + { 289 + name: 'api_key', 290 + type: 'apiKey', 291 + }, 292 + ], 293 + url: '/store/inventory', 294 + ...options, 295 + }); 296 + 297 + /** 298 + * Place an order for a pet. 299 + * Place a new order in the store. 300 + */ 301 + export const placeOrder = <ThrowOnError extends boolean = false>( 302 + options?: Options<PlaceOrderData, ThrowOnError>, 303 + ) => 304 + (options?.client ?? _heyApiClient).post< 305 + PlaceOrderResponses, 306 + PlaceOrderErrors, 307 + ThrowOnError 308 + >({ 309 + url: '/store/order', 310 + ...options, 311 + headers: { 312 + 'Content-Type': 'application/json', 313 + ...options?.headers, 314 + }, 315 + }); 316 + 317 + /** 318 + * Delete purchase order by identifier. 319 + * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors. 320 + */ 321 + export const deleteOrder = <ThrowOnError extends boolean = false>( 322 + options: Options<DeleteOrderData, ThrowOnError>, 323 + ) => 324 + (options.client ?? _heyApiClient).delete< 325 + DeleteOrderResponses, 326 + DeleteOrderErrors, 327 + ThrowOnError 328 + >({ 329 + url: '/store/order/{orderId}', 330 + ...options, 331 + }); 332 + 333 + /** 334 + * Find purchase order by ID. 335 + * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. 336 + */ 337 + export const getOrderById = <ThrowOnError extends boolean = false>( 338 + options: Options<GetOrderByIdData, ThrowOnError>, 339 + ) => 340 + (options.client ?? _heyApiClient).get< 341 + GetOrderByIdResponses, 342 + GetOrderByIdErrors, 343 + ThrowOnError 344 + >({ 345 + url: '/store/order/{orderId}', 346 + ...options, 347 + }); 348 + 349 + /** 350 + * Create user. 351 + * This can only be done by the logged in user. 352 + */ 353 + export const createUser = <ThrowOnError extends boolean = false>( 354 + options?: Options<CreateUserData, ThrowOnError>, 355 + ) => 356 + (options?.client ?? _heyApiClient).post< 357 + CreateUserResponses, 358 + CreateUserErrors, 359 + ThrowOnError 360 + >({ 361 + url: '/user', 362 + ...options, 363 + headers: { 364 + 'Content-Type': 'application/json', 365 + ...options?.headers, 366 + }, 367 + }); 368 + 369 + /** 370 + * Creates list of users with given input array. 371 + * Creates list of users with given input array. 372 + */ 373 + export const createUsersWithListInput = <ThrowOnError extends boolean = false>( 374 + options?: Options<CreateUsersWithListInputData, ThrowOnError>, 375 + ) => 376 + (options?.client ?? _heyApiClient).post< 377 + CreateUsersWithListInputResponses, 378 + CreateUsersWithListInputErrors, 379 + ThrowOnError 380 + >({ 381 + url: '/user/createWithList', 382 + ...options, 383 + headers: { 384 + 'Content-Type': 'application/json', 385 + ...options?.headers, 386 + }, 387 + }); 388 + 389 + /** 390 + * Logs user into the system. 391 + * Log into the system. 392 + */ 393 + export const loginUser = <ThrowOnError extends boolean = false>( 394 + options?: Options<LoginUserData, ThrowOnError>, 395 + ) => 396 + (options?.client ?? _heyApiClient).get< 397 + LoginUserResponses, 398 + LoginUserErrors, 399 + ThrowOnError 400 + >({ 401 + url: '/user/login', 402 + ...options, 403 + }); 404 + 405 + /** 406 + * Logs out current logged in user session. 407 + * Log user out of the system. 408 + */ 409 + export const logoutUser = <ThrowOnError extends boolean = false>( 410 + options?: Options<LogoutUserData, ThrowOnError>, 411 + ) => 412 + (options?.client ?? _heyApiClient).get< 413 + LogoutUserResponses, 414 + LogoutUserErrors, 415 + ThrowOnError 416 + >({ 417 + url: '/user/logout', 418 + ...options, 419 + }); 420 + 421 + /** 422 + * Delete user resource. 423 + * This can only be done by the logged in user. 424 + */ 425 + export const deleteUser = <ThrowOnError extends boolean = false>( 426 + options: Options<DeleteUserData, ThrowOnError>, 427 + ) => 428 + (options.client ?? _heyApiClient).delete< 429 + DeleteUserResponses, 430 + DeleteUserErrors, 431 + ThrowOnError 432 + >({ 433 + url: '/user/{username}', 434 + ...options, 435 + }); 436 + 437 + /** 438 + * Get user by user name. 439 + * Get user detail based on username. 440 + */ 441 + export const getUserByName = <ThrowOnError extends boolean = false>( 442 + options: Options<GetUserByNameData, ThrowOnError>, 443 + ) => 444 + (options.client ?? _heyApiClient).get< 445 + GetUserByNameResponses, 446 + GetUserByNameErrors, 447 + ThrowOnError 448 + >({ 449 + url: '/user/{username}', 450 + ...options, 451 + }); 452 + 453 + /** 454 + * Update user resource. 455 + * This can only be done by the logged in user. 456 + */ 457 + export const updateUser = <ThrowOnError extends boolean = false>( 458 + options: Options<UpdateUserData, ThrowOnError>, 459 + ) => 460 + (options.client ?? _heyApiClient).put< 461 + UpdateUserResponses, 462 + UpdateUserErrors, 463 + ThrowOnError 464 + >({ 465 + url: '/user/{username}', 466 + ...options, 467 + headers: { 468 + 'Content-Type': 'application/json', 469 + ...options.headers, 470 + }, 471 + });
+699
examples/openapi-ts-angular/src/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type Order = { 4 + complete?: boolean; 5 + id?: number; 6 + petId?: number; 7 + quantity?: number; 8 + shipDate?: string; 9 + /** 10 + * Order Status 11 + */ 12 + status?: 'placed' | 'approved' | 'delivered'; 13 + }; 14 + 15 + export type Category = { 16 + id?: number; 17 + name?: string; 18 + }; 19 + 20 + export type User = { 21 + email?: string; 22 + firstName?: string; 23 + id?: number; 24 + lastName?: string; 25 + password?: string; 26 + phone?: string; 27 + /** 28 + * User Status 29 + */ 30 + userStatus?: number; 31 + username?: string; 32 + }; 33 + 34 + export type Tag = { 35 + id?: number; 36 + name?: string; 37 + }; 38 + 39 + export type Pet = { 40 + category?: Category; 41 + id?: number; 42 + name: string; 43 + photoUrls: Array<string>; 44 + /** 45 + * pet status in the store 46 + */ 47 + status?: 'available' | 'pending' | 'sold'; 48 + tags?: Array<Tag>; 49 + }; 50 + 51 + export type ApiResponse = { 52 + code?: number; 53 + message?: string; 54 + type?: string; 55 + }; 56 + 57 + export type Pet2 = Pet; 58 + 59 + /** 60 + * List of user object 61 + */ 62 + export type UserArray = Array<User>; 63 + 64 + export type AddPetData = { 65 + /** 66 + * Create a new pet in the store 67 + */ 68 + body: Pet; 69 + path?: never; 70 + query?: never; 71 + url: '/pet'; 72 + }; 73 + 74 + export type AddPetErrors = { 75 + /** 76 + * Invalid input 77 + */ 78 + 400: unknown; 79 + /** 80 + * Validation exception 81 + */ 82 + 422: unknown; 83 + /** 84 + * Unexpected error 85 + */ 86 + default: unknown; 87 + }; 88 + 89 + export type AddPetResponses = { 90 + /** 91 + * Successful operation 92 + */ 93 + 200: Pet; 94 + }; 95 + 96 + export type AddPetResponse = AddPetResponses[keyof AddPetResponses]; 97 + 98 + export type UpdatePetData = { 99 + /** 100 + * Update an existent pet in the store 101 + */ 102 + body: Pet; 103 + path?: never; 104 + query?: never; 105 + url: '/pet'; 106 + }; 107 + 108 + export type UpdatePetErrors = { 109 + /** 110 + * Invalid ID supplied 111 + */ 112 + 400: unknown; 113 + /** 114 + * Pet not found 115 + */ 116 + 404: unknown; 117 + /** 118 + * Validation exception 119 + */ 120 + 422: unknown; 121 + /** 122 + * Unexpected error 123 + */ 124 + default: unknown; 125 + }; 126 + 127 + export type UpdatePetResponses = { 128 + /** 129 + * Successful operation 130 + */ 131 + 200: Pet; 132 + }; 133 + 134 + export type UpdatePetResponse = UpdatePetResponses[keyof UpdatePetResponses]; 135 + 136 + export type FindPetsByStatusData = { 137 + body?: never; 138 + path?: never; 139 + query?: { 140 + /** 141 + * Status values that need to be considered for filter 142 + */ 143 + status?: 'available' | 'pending' | 'sold'; 144 + }; 145 + url: '/pet/findByStatus'; 146 + }; 147 + 148 + export type FindPetsByStatusErrors = { 149 + /** 150 + * Invalid status value 151 + */ 152 + 400: unknown; 153 + /** 154 + * Unexpected error 155 + */ 156 + default: unknown; 157 + }; 158 + 159 + export type FindPetsByStatusResponses = { 160 + /** 161 + * successful operation 162 + */ 163 + 200: Array<Pet>; 164 + }; 165 + 166 + export type FindPetsByStatusResponse = 167 + FindPetsByStatusResponses[keyof FindPetsByStatusResponses]; 168 + 169 + export type FindPetsByTagsData = { 170 + body?: never; 171 + path?: never; 172 + query?: { 173 + /** 174 + * Tags to filter by 175 + */ 176 + tags?: Array<string>; 177 + }; 178 + url: '/pet/findByTags'; 179 + }; 180 + 181 + export type FindPetsByTagsErrors = { 182 + /** 183 + * Invalid tag value 184 + */ 185 + 400: unknown; 186 + /** 187 + * Unexpected error 188 + */ 189 + default: unknown; 190 + }; 191 + 192 + export type FindPetsByTagsResponses = { 193 + /** 194 + * successful operation 195 + */ 196 + 200: Array<Pet>; 197 + }; 198 + 199 + export type FindPetsByTagsResponse = 200 + FindPetsByTagsResponses[keyof FindPetsByTagsResponses]; 201 + 202 + export type DeletePetData = { 203 + body?: never; 204 + headers?: { 205 + api_key?: string; 206 + }; 207 + path: { 208 + /** 209 + * Pet id to delete 210 + */ 211 + petId: number; 212 + }; 213 + query?: never; 214 + url: '/pet/{petId}'; 215 + }; 216 + 217 + export type DeletePetErrors = { 218 + /** 219 + * Invalid pet value 220 + */ 221 + 400: unknown; 222 + /** 223 + * Unexpected error 224 + */ 225 + default: unknown; 226 + }; 227 + 228 + export type DeletePetResponses = { 229 + /** 230 + * Pet deleted 231 + */ 232 + 200: unknown; 233 + }; 234 + 235 + export type GetPetByIdData = { 236 + body?: never; 237 + path: { 238 + /** 239 + * ID of pet to return 240 + */ 241 + petId: number; 242 + }; 243 + query?: never; 244 + url: '/pet/{petId}'; 245 + }; 246 + 247 + export type GetPetByIdErrors = { 248 + /** 249 + * Invalid ID supplied 250 + */ 251 + 400: unknown; 252 + /** 253 + * Pet not found 254 + */ 255 + 404: unknown; 256 + /** 257 + * Unexpected error 258 + */ 259 + default: unknown; 260 + }; 261 + 262 + export type GetPetByIdResponses = { 263 + /** 264 + * successful operation 265 + */ 266 + 200: Pet; 267 + }; 268 + 269 + export type GetPetByIdResponse = GetPetByIdResponses[keyof GetPetByIdResponses]; 270 + 271 + export type UpdatePetWithFormData = { 272 + body?: never; 273 + path: { 274 + /** 275 + * ID of pet that needs to be updated 276 + */ 277 + petId: number; 278 + }; 279 + query?: { 280 + /** 281 + * Name of pet that needs to be updated 282 + */ 283 + name?: string; 284 + /** 285 + * Status of pet that needs to be updated 286 + */ 287 + status?: string; 288 + }; 289 + url: '/pet/{petId}'; 290 + }; 291 + 292 + export type UpdatePetWithFormErrors = { 293 + /** 294 + * Invalid input 295 + */ 296 + 400: unknown; 297 + /** 298 + * Unexpected error 299 + */ 300 + default: unknown; 301 + }; 302 + 303 + export type UpdatePetWithFormResponses = { 304 + /** 305 + * successful operation 306 + */ 307 + 200: Pet; 308 + }; 309 + 310 + export type UpdatePetWithFormResponse = 311 + UpdatePetWithFormResponses[keyof UpdatePetWithFormResponses]; 312 + 313 + export type UploadFileData = { 314 + body?: Blob | File; 315 + path: { 316 + /** 317 + * ID of pet to update 318 + */ 319 + petId: number; 320 + }; 321 + query?: { 322 + /** 323 + * Additional Metadata 324 + */ 325 + additionalMetadata?: string; 326 + }; 327 + url: '/pet/{petId}/uploadImage'; 328 + }; 329 + 330 + export type UploadFileErrors = { 331 + /** 332 + * No file uploaded 333 + */ 334 + 400: unknown; 335 + /** 336 + * Pet not found 337 + */ 338 + 404: unknown; 339 + /** 340 + * Unexpected error 341 + */ 342 + default: unknown; 343 + }; 344 + 345 + export type UploadFileResponses = { 346 + /** 347 + * successful operation 348 + */ 349 + 200: ApiResponse; 350 + }; 351 + 352 + export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; 353 + 354 + export type GetInventoryData = { 355 + body?: never; 356 + path?: never; 357 + query?: never; 358 + url: '/store/inventory'; 359 + }; 360 + 361 + export type GetInventoryErrors = { 362 + /** 363 + * Unexpected error 364 + */ 365 + default: unknown; 366 + }; 367 + 368 + export type GetInventoryResponses = { 369 + /** 370 + * successful operation 371 + */ 372 + 200: { 373 + [key: string]: number; 374 + }; 375 + }; 376 + 377 + export type GetInventoryResponse = 378 + GetInventoryResponses[keyof GetInventoryResponses]; 379 + 380 + export type PlaceOrderData = { 381 + body?: Order; 382 + path?: never; 383 + query?: never; 384 + url: '/store/order'; 385 + }; 386 + 387 + export type PlaceOrderErrors = { 388 + /** 389 + * Invalid input 390 + */ 391 + 400: unknown; 392 + /** 393 + * Validation exception 394 + */ 395 + 422: unknown; 396 + /** 397 + * Unexpected error 398 + */ 399 + default: unknown; 400 + }; 401 + 402 + export type PlaceOrderResponses = { 403 + /** 404 + * successful operation 405 + */ 406 + 200: Order; 407 + }; 408 + 409 + export type PlaceOrderResponse = PlaceOrderResponses[keyof PlaceOrderResponses]; 410 + 411 + export type DeleteOrderData = { 412 + body?: never; 413 + path: { 414 + /** 415 + * ID of the order that needs to be deleted 416 + */ 417 + orderId: number; 418 + }; 419 + query?: never; 420 + url: '/store/order/{orderId}'; 421 + }; 422 + 423 + export type DeleteOrderErrors = { 424 + /** 425 + * Invalid ID supplied 426 + */ 427 + 400: unknown; 428 + /** 429 + * Order not found 430 + */ 431 + 404: unknown; 432 + /** 433 + * Unexpected error 434 + */ 435 + default: unknown; 436 + }; 437 + 438 + export type DeleteOrderResponses = { 439 + /** 440 + * order deleted 441 + */ 442 + 200: unknown; 443 + }; 444 + 445 + export type GetOrderByIdData = { 446 + body?: never; 447 + path: { 448 + /** 449 + * ID of order that needs to be fetched 450 + */ 451 + orderId: number; 452 + }; 453 + query?: never; 454 + url: '/store/order/{orderId}'; 455 + }; 456 + 457 + export type GetOrderByIdErrors = { 458 + /** 459 + * Invalid ID supplied 460 + */ 461 + 400: unknown; 462 + /** 463 + * Order not found 464 + */ 465 + 404: unknown; 466 + /** 467 + * Unexpected error 468 + */ 469 + default: unknown; 470 + }; 471 + 472 + export type GetOrderByIdResponses = { 473 + /** 474 + * successful operation 475 + */ 476 + 200: Order; 477 + }; 478 + 479 + export type GetOrderByIdResponse = 480 + GetOrderByIdResponses[keyof GetOrderByIdResponses]; 481 + 482 + export type CreateUserData = { 483 + /** 484 + * Created user object 485 + */ 486 + body?: User; 487 + path?: never; 488 + query?: never; 489 + url: '/user'; 490 + }; 491 + 492 + export type CreateUserErrors = { 493 + /** 494 + * Unexpected error 495 + */ 496 + default: unknown; 497 + }; 498 + 499 + export type CreateUserResponses = { 500 + /** 501 + * successful operation 502 + */ 503 + 200: User; 504 + }; 505 + 506 + export type CreateUserResponse = CreateUserResponses[keyof CreateUserResponses]; 507 + 508 + export type CreateUsersWithListInputData = { 509 + body?: Array<User>; 510 + path?: never; 511 + query?: never; 512 + url: '/user/createWithList'; 513 + }; 514 + 515 + export type CreateUsersWithListInputErrors = { 516 + /** 517 + * Unexpected error 518 + */ 519 + default: unknown; 520 + }; 521 + 522 + export type CreateUsersWithListInputResponses = { 523 + /** 524 + * Successful operation 525 + */ 526 + 200: User; 527 + }; 528 + 529 + export type CreateUsersWithListInputResponse = 530 + CreateUsersWithListInputResponses[keyof CreateUsersWithListInputResponses]; 531 + 532 + export type LoginUserData = { 533 + body?: never; 534 + path?: never; 535 + query?: { 536 + /** 537 + * The password for login in clear text 538 + */ 539 + password?: string; 540 + /** 541 + * The user name for login 542 + */ 543 + username?: string; 544 + }; 545 + url: '/user/login'; 546 + }; 547 + 548 + export type LoginUserErrors = { 549 + /** 550 + * Invalid username/password supplied 551 + */ 552 + 400: unknown; 553 + /** 554 + * Unexpected error 555 + */ 556 + default: unknown; 557 + }; 558 + 559 + export type LoginUserResponses = { 560 + /** 561 + * successful operation 562 + */ 563 + 200: string; 564 + }; 565 + 566 + export type LoginUserResponse = LoginUserResponses[keyof LoginUserResponses]; 567 + 568 + export type LogoutUserData = { 569 + body?: never; 570 + path?: never; 571 + query?: never; 572 + url: '/user/logout'; 573 + }; 574 + 575 + export type LogoutUserErrors = { 576 + /** 577 + * Unexpected error 578 + */ 579 + default: unknown; 580 + }; 581 + 582 + export type LogoutUserResponses = { 583 + /** 584 + * successful operation 585 + */ 586 + 200: unknown; 587 + }; 588 + 589 + export type DeleteUserData = { 590 + body?: never; 591 + path: { 592 + /** 593 + * The name that needs to be deleted 594 + */ 595 + username: string; 596 + }; 597 + query?: never; 598 + url: '/user/{username}'; 599 + }; 600 + 601 + export type DeleteUserErrors = { 602 + /** 603 + * Invalid username supplied 604 + */ 605 + 400: unknown; 606 + /** 607 + * User not found 608 + */ 609 + 404: unknown; 610 + /** 611 + * Unexpected error 612 + */ 613 + default: unknown; 614 + }; 615 + 616 + export type DeleteUserResponses = { 617 + /** 618 + * User deleted 619 + */ 620 + 200: unknown; 621 + }; 622 + 623 + export type GetUserByNameData = { 624 + body?: never; 625 + path: { 626 + /** 627 + * The name that needs to be fetched. Use user1 for testing 628 + */ 629 + username: string; 630 + }; 631 + query?: never; 632 + url: '/user/{username}'; 633 + }; 634 + 635 + export type GetUserByNameErrors = { 636 + /** 637 + * Invalid username supplied 638 + */ 639 + 400: unknown; 640 + /** 641 + * User not found 642 + */ 643 + 404: unknown; 644 + /** 645 + * Unexpected error 646 + */ 647 + default: unknown; 648 + }; 649 + 650 + export type GetUserByNameResponses = { 651 + /** 652 + * successful operation 653 + */ 654 + 200: User; 655 + }; 656 + 657 + export type GetUserByNameResponse = 658 + GetUserByNameResponses[keyof GetUserByNameResponses]; 659 + 660 + export type UpdateUserData = { 661 + /** 662 + * Update an existent user in the store 663 + */ 664 + body?: User; 665 + path: { 666 + /** 667 + * name that need to be deleted 668 + */ 669 + username: string; 670 + }; 671 + query?: never; 672 + url: '/user/{username}'; 673 + }; 674 + 675 + export type UpdateUserErrors = { 676 + /** 677 + * bad request 678 + */ 679 + 400: unknown; 680 + /** 681 + * user not found 682 + */ 683 + 404: unknown; 684 + /** 685 + * Unexpected error 686 + */ 687 + default: unknown; 688 + }; 689 + 690 + export type UpdateUserResponses = { 691 + /** 692 + * successful operation 693 + */ 694 + 200: unknown; 695 + }; 696 + 697 + export type ClientOptions = { 698 + baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {}); 699 + };
+26
examples/openapi-ts-angular/src/main.tsx
··· 1 + import '@radix-ui/themes/styles.css'; 2 + 3 + import { Theme } from '@radix-ui/themes'; 4 + import React from 'react'; 5 + import ReactDOM from 'react-dom/client'; 6 + 7 + import App from './App.tsx'; 8 + import { client } from './client/client.gen.ts'; 9 + 10 + // configure internal service client 11 + client.setConfig({ 12 + // set default base url for requests 13 + baseUrl: 'https://petstore3.swagger.io/api/v3', 14 + // set default headers for requests 15 + headers: { 16 + Authorization: 'Bearer <token_from_service_client>', 17 + }, 18 + }); 19 + 20 + ReactDOM.createRoot(document.getElementById('root')!).render( 21 + <React.StrictMode> 22 + <Theme appearance="dark"> 23 + <App /> 24 + </Theme> 25 + </React.StrictMode>, 26 + );
+1
examples/openapi-ts-angular/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+8
examples/openapi-ts-angular/tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + export default { 3 + content: ['./index.html', './src/**/*.{html,js,ts,jsx,tsx}'], 4 + plugins: [], 5 + theme: { 6 + extend: {}, 7 + }, 8 + };
+25
examples/openapi-ts-angular/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + 9 + /* Bundler mode */ 10 + "moduleResolution": "bundler", 11 + "allowImportingTsExtensions": true, 12 + "resolveJsonModule": true, 13 + "isolatedModules": true, 14 + "noEmit": true, 15 + "jsx": "react-jsx", 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": false, 21 + "noFallthroughCasesInSwitch": true 22 + }, 23 + "include": ["src"], 24 + "references": [{ "path": "./tsconfig.node.json" }] 25 + }
+11
examples/openapi-ts-angular/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true, 8 + "strict": true 9 + }, 10 + "include": ["vite.config.ts"] 11 + }
+7
examples/openapi-ts-angular/vite.config.ts
··· 1 + import { createViteConfig } from '@config/vite-base'; 2 + import react from '@vitejs/plugin-react'; 3 + 4 + // https://vitejs.dev/config/ 5 + export default createViteConfig({ 6 + plugins: [react()], 7 + });
+70
packages/openapi-ts/src/plugins/@hey-api/client-angular/__tests__/client.test.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { createClient } from '../bundle/client'; 4 + 5 + describe('buildUrl', () => { 6 + const client = createClient(); 7 + 8 + const scenarios: { 9 + options: Parameters<typeof client.buildUrl>[0]; 10 + url: string; 11 + }[] = [ 12 + { 13 + options: { 14 + url: '', 15 + }, 16 + url: '/', 17 + }, 18 + { 19 + options: { 20 + url: '/foo', 21 + }, 22 + url: '/foo', 23 + }, 24 + { 25 + options: { 26 + path: { 27 + fooId: 1, 28 + }, 29 + url: '/foo/{fooId}', 30 + }, 31 + url: '/foo/1', 32 + }, 33 + { 34 + options: { 35 + path: { 36 + fooId: 1, 37 + }, 38 + query: { 39 + bar: 'baz', 40 + }, 41 + url: '/foo/{fooId}', 42 + }, 43 + url: '/foo/1?bar=baz', 44 + }, 45 + { 46 + options: { 47 + query: { 48 + bar: [], 49 + foo: [], 50 + }, 51 + url: '/', 52 + }, 53 + url: '/', 54 + }, 55 + { 56 + options: { 57 + query: { 58 + bar: [], 59 + foo: ['abc', 'def'], 60 + }, 61 + url: '/', 62 + }, 63 + url: '/?foo=abc&foo=def', 64 + }, 65 + ]; 66 + 67 + it.each(scenarios)('returns $url', ({ options, url }) => { 68 + expect(client.buildUrl(options)).toBe(url); 69 + }); 70 + });
+232
packages/openapi-ts/src/plugins/@hey-api/client-angular/__tests__/utils.test.ts
··· 1 + import { describe, expect, it, vi } from 'vitest'; 2 + 3 + import type { Auth } from '../../client-core/bundle/auth'; 4 + import type { Client } from '../bundle/types'; 5 + import { buildUrl, getParseAs, setAuthParams } from '../bundle/utils'; 6 + 7 + describe('buildUrl', () => { 8 + const scenarios: Array<{ 9 + options: Parameters<Client['buildUrl']>[0]; 10 + url: string; 11 + }> = [ 12 + { 13 + options: { 14 + path: { 15 + id: new Date('2025-01-01T00:00:00.000Z'), 16 + }, 17 + url: '/foo/{id}', 18 + }, 19 + url: '/foo/2025-01-01T00:00:00.000Z', 20 + }, 21 + ]; 22 + 23 + it.each(scenarios)('builds $url', async ({ options, url }) => { 24 + expect(buildUrl(options)).toEqual(url); 25 + }); 26 + }); 27 + 28 + describe('getParseAs', () => { 29 + const scenarios: Array<{ 30 + content: Parameters<typeof getParseAs>[0]; 31 + parseAs: ReturnType<typeof getParseAs>; 32 + }> = [ 33 + { 34 + content: null, 35 + parseAs: 'stream', 36 + }, 37 + { 38 + content: 'application/json', 39 + parseAs: 'json', 40 + }, 41 + { 42 + content: 'application/ld+json', 43 + parseAs: 'json', 44 + }, 45 + { 46 + content: 'application/ld+json;charset=utf-8', 47 + parseAs: 'json', 48 + }, 49 + { 50 + content: 'application/ld+json; charset=utf-8', 51 + parseAs: 'json', 52 + }, 53 + { 54 + content: 'multipart/form-data', 55 + parseAs: 'formData', 56 + }, 57 + { 58 + content: 'application/*', 59 + parseAs: 'blob', 60 + }, 61 + { 62 + content: 'audio/*', 63 + parseAs: 'blob', 64 + }, 65 + { 66 + content: 'image/*', 67 + parseAs: 'blob', 68 + }, 69 + { 70 + content: 'video/*', 71 + parseAs: 'blob', 72 + }, 73 + { 74 + content: 'text/*', 75 + parseAs: 'text', 76 + }, 77 + { 78 + content: 'unsupported', 79 + parseAs: undefined, 80 + }, 81 + ]; 82 + 83 + it.each(scenarios)( 84 + 'detects $content as $parseAs', 85 + async ({ content, parseAs }) => { 86 + expect(getParseAs(content)).toEqual(parseAs); 87 + }, 88 + ); 89 + }); 90 + 91 + describe('setAuthParams', () => { 92 + it('sets bearer token in headers', async () => { 93 + const auth = vi.fn().mockReturnValue('foo'); 94 + const headers = new Headers(); 95 + const query: Record<any, unknown> = {}; 96 + await setAuthParams({ 97 + auth, 98 + headers, 99 + query, 100 + security: [ 101 + { 102 + name: 'baz', 103 + scheme: 'bearer', 104 + type: 'http', 105 + }, 106 + ], 107 + }); 108 + expect(auth).toHaveBeenCalled(); 109 + expect(headers.get('baz')).toBe('Bearer foo'); 110 + expect(Object.keys(query).length).toBe(0); 111 + }); 112 + 113 + it('sets access token in query', async () => { 114 + const auth = vi.fn().mockReturnValue('foo'); 115 + const headers = new Headers(); 116 + const query: Record<any, unknown> = {}; 117 + await setAuthParams({ 118 + auth, 119 + headers, 120 + query, 121 + security: [ 122 + { 123 + in: 'query', 124 + name: 'baz', 125 + scheme: 'bearer', 126 + type: 'http', 127 + }, 128 + ], 129 + }); 130 + expect(auth).toHaveBeenCalled(); 131 + expect(headers.get('baz')).toBeNull(); 132 + expect(query.baz).toBe('Bearer foo'); 133 + }); 134 + 135 + it('sets Authorization header when `in` and `name` are undefined', async () => { 136 + const auth = vi.fn().mockReturnValue('foo'); 137 + const headers = new Headers(); 138 + const query: Record<any, unknown> = {}; 139 + await setAuthParams({ 140 + auth, 141 + headers, 142 + query, 143 + security: [ 144 + { 145 + type: 'http', 146 + }, 147 + ], 148 + }); 149 + expect(auth).toHaveBeenCalled(); 150 + expect(headers.get('Authorization')).toBe('foo'); 151 + expect(query).toEqual({}); 152 + }); 153 + 154 + it('sets first scheme only', async () => { 155 + const auth = vi.fn().mockReturnValue('foo'); 156 + const headers = new Headers(); 157 + const query: Record<any, unknown> = {}; 158 + await setAuthParams({ 159 + auth, 160 + headers, 161 + query, 162 + security: [ 163 + { 164 + name: 'baz', 165 + scheme: 'bearer', 166 + type: 'http', 167 + }, 168 + { 169 + in: 'query', 170 + name: 'baz', 171 + scheme: 'bearer', 172 + type: 'http', 173 + }, 174 + ], 175 + }); 176 + expect(auth).toHaveBeenCalled(); 177 + expect(headers.get('baz')).toBe('Bearer foo'); 178 + expect(Object.keys(query).length).toBe(0); 179 + }); 180 + 181 + it('sets first scheme with token', async () => { 182 + const auth = vi.fn().mockImplementation((auth: Auth) => { 183 + if (auth.type === 'apiKey') { 184 + return; 185 + } 186 + return 'foo'; 187 + }); 188 + const headers = new Headers(); 189 + const query: Record<any, unknown> = {}; 190 + await setAuthParams({ 191 + auth, 192 + headers, 193 + query, 194 + security: [ 195 + { 196 + name: 'baz', 197 + type: 'apiKey', 198 + }, 199 + { 200 + in: 'query', 201 + name: 'baz', 202 + scheme: 'bearer', 203 + type: 'http', 204 + }, 205 + ], 206 + }); 207 + expect(auth).toHaveBeenCalled(); 208 + expect(headers.get('baz')).toBeNull(); 209 + expect(query.baz).toBe('Bearer foo'); 210 + }); 211 + 212 + it('sets an API key in a cookie', async () => { 213 + const auth = vi.fn().mockReturnValue('foo'); 214 + const headers = new Headers(); 215 + const query: Record<any, unknown> = {}; 216 + await setAuthParams({ 217 + auth, 218 + headers, 219 + query, 220 + security: [ 221 + { 222 + in: 'cookie', 223 + name: 'baz', 224 + type: 'apiKey', 225 + }, 226 + ], 227 + }); 228 + expect(auth).toHaveBeenCalled(); 229 + expect(headers.get('Cookie')).toBe('baz=foo'); 230 + expect(query).toEqual({}); 231 + }); 232 + });
+197
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/client.ts
··· 1 + import type { Client, Config, ResolvedRequestOptions } from './types'; 2 + import { 3 + buildUrl, 4 + createConfig, 5 + createInterceptors, 6 + getParseAs, 7 + mergeConfigs, 8 + mergeHeaders, 9 + setAuthParams, 10 + } from './utils'; 11 + 12 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 + body?: any; 14 + headers: ReturnType<typeof mergeHeaders>; 15 + }; 16 + 17 + export const createClient = (config: Config = {}): Client => { 18 + let _config = mergeConfigs(createConfig(), config); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + return getConfig(); 25 + }; 26 + 27 + const interceptors = createInterceptors< 28 + Request, 29 + Response, 30 + unknown, 31 + ResolvedRequestOptions 32 + >(); 33 + 34 + const request: Client['request'] = async (options) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + serializedBody: undefined, 41 + }; 42 + 43 + if (opts.security) { 44 + await setAuthParams({ 45 + ...opts, 46 + security: opts.security, 47 + }); 48 + } 49 + 50 + if (opts.requestValidator) { 51 + await opts.requestValidator(opts); 52 + } 53 + 54 + if (opts.body && opts.bodySerializer) { 55 + opts.serializedBody = opts.bodySerializer(opts.body); 56 + } 57 + 58 + // remove Content-Type header if body is empty to avoid sending invalid requests 59 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 60 + opts.headers.delete('Content-Type'); 61 + } 62 + 63 + const url = buildUrl(opts); 64 + const requestInit: ReqInit = { 65 + redirect: 'follow', 66 + ...opts, 67 + body: opts.serializedBody, 68 + }; 69 + 70 + let request = new Request(url, requestInit); 71 + 72 + for (const fn of interceptors.request._fns) { 73 + if (fn) { 74 + request = await fn(request, opts); 75 + } 76 + } 77 + 78 + // fetch must be assigned here, otherwise it would throw the error: 79 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 80 + const _fetch = opts.fetch!; 81 + let response = await _fetch(request); 82 + 83 + for (const fn of interceptors.response._fns) { 84 + if (fn) { 85 + response = await fn(response, request, opts); 86 + } 87 + } 88 + 89 + const result = { 90 + request, 91 + response, 92 + }; 93 + 94 + if (response.ok) { 95 + if ( 96 + response.status === 204 || 97 + response.headers.get('Content-Length') === '0' 98 + ) { 99 + return opts.responseStyle === 'data' 100 + ? {} 101 + : { 102 + data: {}, 103 + ...result, 104 + }; 105 + } 106 + 107 + const parseAs = 108 + (opts.parseAs === 'auto' 109 + ? getParseAs(response.headers.get('Content-Type')) 110 + : opts.parseAs) ?? 'json'; 111 + 112 + let data: any; 113 + switch (parseAs) { 114 + case 'arrayBuffer': 115 + case 'blob': 116 + case 'formData': 117 + case 'json': 118 + case 'text': 119 + data = await response[parseAs](); 120 + break; 121 + case 'stream': 122 + return opts.responseStyle === 'data' 123 + ? response.body 124 + : { 125 + data: response.body, 126 + ...result, 127 + }; 128 + } 129 + 130 + if (parseAs === 'json') { 131 + if (opts.responseValidator) { 132 + await opts.responseValidator(data); 133 + } 134 + 135 + if (opts.responseTransformer) { 136 + data = await opts.responseTransformer(data); 137 + } 138 + } 139 + 140 + return opts.responseStyle === 'data' 141 + ? data 142 + : { 143 + data, 144 + ...result, 145 + }; 146 + } 147 + 148 + const textError = await response.text(); 149 + let jsonError: unknown; 150 + 151 + try { 152 + jsonError = JSON.parse(textError); 153 + } catch { 154 + // noop 155 + } 156 + 157 + const error = jsonError ?? textError; 158 + let finalError = error; 159 + 160 + for (const fn of interceptors.error._fns) { 161 + if (fn) { 162 + finalError = (await fn(error, response, request, opts)) as string; 163 + } 164 + } 165 + 166 + finalError = finalError || ({} as string); 167 + 168 + if (opts.throwOnError) { 169 + throw finalError; 170 + } 171 + 172 + // TODO: we probably want to return error and improve types 173 + return opts.responseStyle === 'data' 174 + ? undefined 175 + : { 176 + error: finalError, 177 + ...result, 178 + }; 179 + }; 180 + 181 + return { 182 + buildUrl, 183 + connect: (options) => request({ ...options, method: 'CONNECT' }), 184 + delete: (options) => request({ ...options, method: 'DELETE' }), 185 + get: (options) => request({ ...options, method: 'GET' }), 186 + getConfig, 187 + head: (options) => request({ ...options, method: 'HEAD' }), 188 + interceptors, 189 + options: (options) => request({ ...options, method: 'OPTIONS' }), 190 + patch: (options) => request({ ...options, method: 'PATCH' }), 191 + post: (options) => request({ ...options, method: 'POST' }), 192 + put: (options) => request({ ...options, method: 'PUT' }), 193 + request, 194 + setConfig, 195 + trace: (options) => request({ ...options, method: 'TRACE' }), 196 + }; 197 + };
+23
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/index.ts
··· 1 + export type { Auth } from '../../client-core/bundle/auth'; 2 + export type { QuerySerializerOptions } from '../../client-core/bundle/bodySerializer'; 3 + export { 4 + formDataBodySerializer, 5 + jsonBodySerializer, 6 + urlSearchParamsBodySerializer, 7 + } from '../../client-core/bundle/bodySerializer'; 8 + export { buildClientParams } from '../../client-core/bundle/params'; 9 + export { createClient } from './client'; 10 + export type { 11 + Client, 12 + ClientOptions, 13 + Config, 14 + CreateClientConfig, 15 + Options, 16 + OptionsLegacyParser, 17 + RequestOptions, 18 + RequestResult, 19 + ResolvedRequestOptions, 20 + ResponseStyle, 21 + TDataShape, 22 + } from './types'; 23 + export { createConfig, mergeHeaders } from './utils';
+230
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/types.ts
··· 1 + import type { Auth } from '../../client-core/bundle/auth'; 2 + import type { 3 + Client as CoreClient, 4 + Config as CoreConfig, 5 + } from '../../client-core/bundle/types'; 6 + import type { Middleware } from './utils'; 7 + 8 + export type ResponseStyle = 'data' | 'fields'; 9 + 10 + export interface Config<T extends ClientOptions = ClientOptions> 11 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 12 + CoreConfig { 13 + /** 14 + * Base URL for all requests made by this client. 15 + */ 16 + baseUrl?: T['baseUrl']; 17 + /** 18 + * Fetch API implementation. You can use this option to provide a custom 19 + * fetch instance. 20 + * 21 + * @default globalThis.fetch 22 + */ 23 + fetch?: (request: Request) => ReturnType<typeof fetch>; 24 + /** 25 + * Please don't use the Fetch client for Next.js applications. The `next` 26 + * options won't have any effect. 27 + * 28 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 29 + */ 30 + next?: never; 31 + /** 32 + * Return the response data parsed in a specified format. By default, `auto` 33 + * will infer the appropriate method from the `Content-Type` response header. 34 + * You can override this behavior with any of the {@link Body} methods. 35 + * Select `stream` if you don't want to parse response data at all. 36 + * 37 + * @default 'auto' 38 + */ 39 + parseAs?: 40 + | 'arrayBuffer' 41 + | 'auto' 42 + | 'blob' 43 + | 'formData' 44 + | 'json' 45 + | 'stream' 46 + | 'text'; 47 + /** 48 + * Should we return only data or multiple fields (data, error, response, etc.)? 49 + * 50 + * @default 'fields' 51 + */ 52 + responseStyle?: ResponseStyle; 53 + /** 54 + * Throw an error instead of returning it in the response? 55 + * 56 + * @default false 57 + */ 58 + throwOnError?: T['throwOnError']; 59 + } 60 + 61 + export interface RequestOptions< 62 + TResponseStyle extends ResponseStyle = 'fields', 63 + ThrowOnError extends boolean = boolean, 64 + Url extends string = string, 65 + > extends Config<{ 66 + responseStyle: TResponseStyle; 67 + throwOnError: ThrowOnError; 68 + }> { 69 + /** 70 + * Any body that you want to add to your request. 71 + * 72 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 73 + */ 74 + body?: unknown; 75 + path?: Record<string, unknown>; 76 + query?: Record<string, unknown>; 77 + /** 78 + * Security mechanism(s) to use for the request. 79 + */ 80 + security?: ReadonlyArray<Auth>; 81 + url: Url; 82 + } 83 + 84 + export interface ResolvedRequestOptions< 85 + TResponseStyle extends ResponseStyle = 'fields', 86 + ThrowOnError extends boolean = boolean, 87 + Url extends string = string, 88 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 89 + serializedBody?: string; 90 + } 91 + 92 + export type RequestResult< 93 + TData = unknown, 94 + TError = unknown, 95 + ThrowOnError extends boolean = boolean, 96 + TResponseStyle extends ResponseStyle = 'fields', 97 + > = ThrowOnError extends true 98 + ? Promise< 99 + TResponseStyle extends 'data' 100 + ? TData extends Record<string, unknown> 101 + ? TData[keyof TData] 102 + : TData 103 + : { 104 + data: TData extends Record<string, unknown> 105 + ? TData[keyof TData] 106 + : TData; 107 + request: Request; 108 + response: Response; 109 + } 110 + > 111 + : Promise< 112 + TResponseStyle extends 'data' 113 + ? 114 + | (TData extends Record<string, unknown> 115 + ? TData[keyof TData] 116 + : TData) 117 + | undefined 118 + : ( 119 + | { 120 + data: TData extends Record<string, unknown> 121 + ? TData[keyof TData] 122 + : TData; 123 + error: undefined; 124 + } 125 + | { 126 + data: undefined; 127 + error: TError extends Record<string, unknown> 128 + ? TError[keyof TError] 129 + : TError; 130 + } 131 + ) & { 132 + request: Request; 133 + response: Response; 134 + } 135 + >; 136 + 137 + export interface ClientOptions { 138 + baseUrl?: string; 139 + responseStyle?: ResponseStyle; 140 + throwOnError?: boolean; 141 + } 142 + 143 + type MethodFn = < 144 + TData = unknown, 145 + TError = unknown, 146 + ThrowOnError extends boolean = false, 147 + TResponseStyle extends ResponseStyle = 'fields', 148 + >( 149 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 150 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 151 + 152 + type RequestFn = < 153 + TData = unknown, 154 + TError = unknown, 155 + ThrowOnError extends boolean = false, 156 + TResponseStyle extends ResponseStyle = 'fields', 157 + >( 158 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 159 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 160 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 161 + 162 + type BuildUrlFn = < 163 + TData extends { 164 + body?: unknown; 165 + path?: Record<string, unknown>; 166 + query?: Record<string, unknown>; 167 + url: string; 168 + }, 169 + >( 170 + options: Pick<TData, 'url'> & Options<TData>, 171 + ) => string; 172 + 173 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 174 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 175 + }; 176 + 177 + /** 178 + * The `createClientConfig()` function will be called on client initialization 179 + * and the returned object will become the client's initial configuration. 180 + * 181 + * You may want to initialize your client this way instead of calling 182 + * `setConfig()`. This is useful for example if you're using Next.js 183 + * to ensure your client always has the correct values. 184 + */ 185 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 186 + override?: Config<ClientOptions & T>, 187 + ) => Config<Required<ClientOptions> & T>; 188 + 189 + export interface TDataShape { 190 + body?: unknown; 191 + headers?: unknown; 192 + path?: unknown; 193 + query?: unknown; 194 + url: string; 195 + } 196 + 197 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 198 + 199 + export type Options< 200 + TData extends TDataShape = TDataShape, 201 + ThrowOnError extends boolean = boolean, 202 + TResponseStyle extends ResponseStyle = 'fields', 203 + > = OmitKeys< 204 + RequestOptions<TResponseStyle, ThrowOnError>, 205 + 'body' | 'path' | 'query' | 'url' 206 + > & 207 + Omit<TData, 'url'>; 208 + 209 + export type OptionsLegacyParser< 210 + TData = unknown, 211 + ThrowOnError extends boolean = boolean, 212 + TResponseStyle extends ResponseStyle = 'fields', 213 + > = TData extends { body?: any } 214 + ? TData extends { headers?: any } 215 + ? OmitKeys< 216 + RequestOptions<TResponseStyle, ThrowOnError>, 217 + 'body' | 'headers' | 'url' 218 + > & 219 + TData 220 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 221 + TData & 222 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 223 + : TData extends { headers?: any } 224 + ? OmitKeys< 225 + RequestOptions<TResponseStyle, ThrowOnError>, 226 + 'headers' | 'url' 227 + > & 228 + TData & 229 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 230 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+417
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/utils.ts
··· 1 + import { getAuthToken } from '../../client-core/bundle/auth'; 2 + import type { 3 + QuerySerializer, 4 + QuerySerializerOptions, 5 + } from '../../client-core/bundle/bodySerializer'; 6 + import { jsonBodySerializer } from '../../client-core/bundle/bodySerializer'; 7 + import { 8 + serializeArrayParam, 9 + serializeObjectParam, 10 + serializePrimitiveParam, 11 + } from '../../client-core/bundle/pathSerializer'; 12 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 13 + 14 + interface PathSerializer { 15 + path: Record<string, unknown>; 16 + url: string; 17 + } 18 + 19 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 20 + 21 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 22 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 23 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 24 + 25 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 26 + let url = _url; 27 + const matches = _url.match(PATH_PARAM_RE); 28 + if (matches) { 29 + for (const match of matches) { 30 + let explode = false; 31 + let name = match.substring(1, match.length - 1); 32 + let style: ArraySeparatorStyle = 'simple'; 33 + 34 + if (name.endsWith('*')) { 35 + explode = true; 36 + name = name.substring(0, name.length - 1); 37 + } 38 + 39 + if (name.startsWith('.')) { 40 + name = name.substring(1); 41 + style = 'label'; 42 + } else if (name.startsWith(';')) { 43 + name = name.substring(1); 44 + style = 'matrix'; 45 + } 46 + 47 + const value = path[name]; 48 + 49 + if (value === undefined || value === null) { 50 + continue; 51 + } 52 + 53 + if (Array.isArray(value)) { 54 + url = url.replace( 55 + match, 56 + serializeArrayParam({ explode, name, style, value }), 57 + ); 58 + continue; 59 + } 60 + 61 + if (typeof value === 'object') { 62 + url = url.replace( 63 + match, 64 + serializeObjectParam({ 65 + explode, 66 + name, 67 + style, 68 + value: value as Record<string, unknown>, 69 + valueOnly: true, 70 + }), 71 + ); 72 + continue; 73 + } 74 + 75 + if (style === 'matrix') { 76 + url = url.replace( 77 + match, 78 + `;${serializePrimitiveParam({ 79 + name, 80 + value: value as string, 81 + })}`, 82 + ); 83 + continue; 84 + } 85 + 86 + const replaceValue = encodeURIComponent( 87 + style === 'label' ? `.${value as string}` : (value as string), 88 + ); 89 + url = url.replace(match, replaceValue); 90 + } 91 + } 92 + return url; 93 + }; 94 + 95 + export const createQuerySerializer = <T = unknown>({ 96 + allowReserved, 97 + array, 98 + object, 99 + }: QuerySerializerOptions = {}) => { 100 + const querySerializer = (queryParams: T) => { 101 + const search: string[] = []; 102 + if (queryParams && typeof queryParams === 'object') { 103 + for (const name in queryParams) { 104 + const value = queryParams[name]; 105 + 106 + if (value === undefined || value === null) { 107 + continue; 108 + } 109 + 110 + if (Array.isArray(value)) { 111 + const serializedArray = serializeArrayParam({ 112 + allowReserved, 113 + explode: true, 114 + name, 115 + style: 'form', 116 + value, 117 + ...array, 118 + }); 119 + if (serializedArray) search.push(serializedArray); 120 + } else if (typeof value === 'object') { 121 + const serializedObject = serializeObjectParam({ 122 + allowReserved, 123 + explode: true, 124 + name, 125 + style: 'deepObject', 126 + value: value as Record<string, unknown>, 127 + ...object, 128 + }); 129 + if (serializedObject) search.push(serializedObject); 130 + } else { 131 + const serializedPrimitive = serializePrimitiveParam({ 132 + allowReserved, 133 + name, 134 + value: value as string, 135 + }); 136 + if (serializedPrimitive) search.push(serializedPrimitive); 137 + } 138 + } 139 + } 140 + return search.join('&'); 141 + }; 142 + return querySerializer; 143 + }; 144 + 145 + /** 146 + * Infers parseAs value from provided Content-Type header. 147 + */ 148 + export const getParseAs = ( 149 + contentType: string | null, 150 + ): Exclude<Config['parseAs'], 'auto'> => { 151 + if (!contentType) { 152 + // If no Content-Type header is provided, the best we can do is return the raw response body, 153 + // which is effectively the same as the 'stream' option. 154 + return 'stream'; 155 + } 156 + 157 + const cleanContent = contentType.split(';')[0]?.trim(); 158 + 159 + if (!cleanContent) { 160 + return; 161 + } 162 + 163 + if ( 164 + cleanContent.startsWith('application/json') || 165 + cleanContent.endsWith('+json') 166 + ) { 167 + return 'json'; 168 + } 169 + 170 + if (cleanContent === 'multipart/form-data') { 171 + return 'formData'; 172 + } 173 + 174 + if ( 175 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 176 + cleanContent.startsWith(type), 177 + ) 178 + ) { 179 + return 'blob'; 180 + } 181 + 182 + if (cleanContent.startsWith('text/')) { 183 + return 'text'; 184 + } 185 + 186 + return; 187 + }; 188 + 189 + export const setAuthParams = async ({ 190 + security, 191 + ...options 192 + }: Pick<Required<RequestOptions>, 'security'> & 193 + Pick<RequestOptions, 'auth' | 'query'> & { 194 + headers: Headers; 195 + }) => { 196 + for (const auth of security) { 197 + const token = await getAuthToken(auth, options.auth); 198 + 199 + if (!token) { 200 + continue; 201 + } 202 + 203 + const name = auth.name ?? 'Authorization'; 204 + 205 + switch (auth.in) { 206 + case 'query': 207 + if (!options.query) { 208 + options.query = {}; 209 + } 210 + options.query[name] = token; 211 + break; 212 + case 'cookie': 213 + options.headers.append('Cookie', `${name}=${token}`); 214 + break; 215 + case 'header': 216 + default: 217 + options.headers.set(name, token); 218 + break; 219 + } 220 + 221 + return; 222 + } 223 + }; 224 + 225 + export const buildUrl: Client['buildUrl'] = (options) => { 226 + const url = getUrl({ 227 + baseUrl: options.baseUrl as string, 228 + path: options.path, 229 + query: options.query, 230 + querySerializer: 231 + typeof options.querySerializer === 'function' 232 + ? options.querySerializer 233 + : createQuerySerializer(options.querySerializer), 234 + url: options.url, 235 + }); 236 + return url; 237 + }; 238 + 239 + export const getUrl = ({ 240 + baseUrl, 241 + path, 242 + query, 243 + querySerializer, 244 + url: _url, 245 + }: { 246 + baseUrl?: string; 247 + path?: Record<string, unknown>; 248 + query?: Record<string, unknown>; 249 + querySerializer: QuerySerializer; 250 + url: string; 251 + }) => { 252 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 253 + let url = (baseUrl ?? '') + pathUrl; 254 + if (path) { 255 + url = defaultPathSerializer({ path, url }); 256 + } 257 + let search = query ? querySerializer(query) : ''; 258 + if (search.startsWith('?')) { 259 + search = search.substring(1); 260 + } 261 + if (search) { 262 + url += `?${search}`; 263 + } 264 + return url; 265 + }; 266 + 267 + export const mergeConfigs = (a: Config, b: Config): Config => { 268 + const config = { ...a, ...b }; 269 + if (config.baseUrl?.endsWith('/')) { 270 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 271 + } 272 + config.headers = mergeHeaders(a.headers, b.headers); 273 + return config; 274 + }; 275 + 276 + export const mergeHeaders = ( 277 + ...headers: Array<Required<Config>['headers'] | undefined> 278 + ): Headers => { 279 + const mergedHeaders = new Headers(); 280 + for (const header of headers) { 281 + if (!header || typeof header !== 'object') { 282 + continue; 283 + } 284 + 285 + const iterator = 286 + header instanceof Headers ? header.entries() : Object.entries(header); 287 + 288 + for (const [key, value] of iterator) { 289 + if (value === null) { 290 + mergedHeaders.delete(key); 291 + } else if (Array.isArray(value)) { 292 + for (const v of value) { 293 + mergedHeaders.append(key, v as string); 294 + } 295 + } else if (value !== undefined) { 296 + // assume object headers are meant to be JSON stringified, i.e. their 297 + // content value in OpenAPI specification is 'application/json' 298 + mergedHeaders.set( 299 + key, 300 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 301 + ); 302 + } 303 + } 304 + } 305 + return mergedHeaders; 306 + }; 307 + 308 + type ErrInterceptor<Err, Res, Req, Options> = ( 309 + error: Err, 310 + response: Res, 311 + request: Req, 312 + options: Options, 313 + ) => Err | Promise<Err>; 314 + 315 + type ReqInterceptor<Req, Options> = ( 316 + request: Req, 317 + options: Options, 318 + ) => Req | Promise<Req>; 319 + 320 + type ResInterceptor<Res, Req, Options> = ( 321 + response: Res, 322 + request: Req, 323 + options: Options, 324 + ) => Res | Promise<Res>; 325 + 326 + class Interceptors<Interceptor> { 327 + _fns: (Interceptor | null)[]; 328 + 329 + constructor() { 330 + this._fns = []; 331 + } 332 + 333 + clear() { 334 + this._fns = []; 335 + } 336 + 337 + getInterceptorIndex(id: number | Interceptor): number { 338 + if (typeof id === 'number') { 339 + return this._fns[id] ? id : -1; 340 + } else { 341 + return this._fns.indexOf(id); 342 + } 343 + } 344 + exists(id: number | Interceptor) { 345 + const index = this.getInterceptorIndex(id); 346 + return !!this._fns[index]; 347 + } 348 + 349 + eject(id: number | Interceptor) { 350 + const index = this.getInterceptorIndex(id); 351 + if (this._fns[index]) { 352 + this._fns[index] = null; 353 + } 354 + } 355 + 356 + update(id: number | Interceptor, fn: Interceptor) { 357 + const index = this.getInterceptorIndex(id); 358 + if (this._fns[index]) { 359 + this._fns[index] = fn; 360 + return id; 361 + } else { 362 + return false; 363 + } 364 + } 365 + 366 + use(fn: Interceptor) { 367 + this._fns = [...this._fns, fn]; 368 + return this._fns.length - 1; 369 + } 370 + } 371 + 372 + // `createInterceptors()` response, meant for external use as it does not 373 + // expose internals 374 + export interface Middleware<Req, Res, Err, Options> { 375 + error: Pick< 376 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 377 + 'eject' | 'use' 378 + >; 379 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 380 + response: Pick< 381 + Interceptors<ResInterceptor<Res, Req, Options>>, 382 + 'eject' | 'use' 383 + >; 384 + } 385 + 386 + // do not add `Middleware` as return type so we can use _fns internally 387 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 388 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 389 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 390 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 391 + }); 392 + 393 + const defaultQuerySerializer = createQuerySerializer({ 394 + allowReserved: false, 395 + array: { 396 + explode: true, 397 + style: 'form', 398 + }, 399 + object: { 400 + explode: true, 401 + style: 'deepObject', 402 + }, 403 + }); 404 + 405 + const defaultHeaders = { 406 + 'Content-Type': 'application/json', 407 + }; 408 + 409 + export const createConfig = <T extends ClientOptions = ClientOptions>( 410 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 411 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 412 + ...jsonBodySerializer, 413 + headers: defaultHeaders, 414 + parseAs: 'auto', 415 + querySerializer: defaultQuerySerializer, 416 + ...override, 417 + });
+19
packages/openapi-ts/src/plugins/@hey-api/client-angular/config.ts
··· 1 + import { definePluginConfig } from '../../shared/utils/config'; 2 + import { clientDefaultConfig, clientDefaultMeta } from '../client-core/config'; 3 + import { clientPluginHandler } from '../client-core/plugin'; 4 + import type { HeyApiClientAngularPlugin } from './types'; 5 + 6 + export const defaultConfig: HeyApiClientAngularPlugin['Config'] = { 7 + ...clientDefaultMeta, 8 + config: { 9 + ...clientDefaultConfig, 10 + throwOnError: false, 11 + }, 12 + handler: clientPluginHandler as HeyApiClientAngularPlugin['Handler'], 13 + name: '@hey-api/client-angular', 14 + }; 15 + 16 + /** 17 + * Type helper for `@hey-api/client-angular` plugin, returns {@link Plugin.Config} object 18 + */ 19 + export const defineConfig = definePluginConfig(defaultConfig);
+2
packages/openapi-ts/src/plugins/@hey-api/client-angular/index.ts
··· 1 + export { defaultConfig, defineConfig } from './config'; 2 + export type { HeyApiClientAngularPlugin } from './types';
+19
packages/openapi-ts/src/plugins/@hey-api/client-angular/types.d.ts
··· 1 + import type { DefinePlugin, Plugin } from '../../types'; 2 + import type { Client } from '../client-core/types'; 3 + 4 + export type UserConfig = Plugin.Name<'@hey-api/client-angular'> & 5 + Client.Config & { 6 + /** 7 + * TODO: TBD 8 + */ 9 + httpResource?: boolean; 10 + 11 + /** 12 + * Throw an error instead of returning it in the response? 13 + * 14 + * @default false 15 + */ 16 + throwOnError?: boolean; 17 + }; 18 + 19 + export type HeyApiClientAngularPlugin = DefinePlugin<UserConfig>;
+2
packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts
··· 1 + import type { HeyApiClientAngularPlugin } from '../client-angular'; 1 2 import type { HeyApiClientAxiosPlugin } from '../client-axios'; 2 3 import type { HeyApiClientFetchPlugin } from '../client-fetch'; 3 4 import type { HeyApiClientNextPlugin } from '../client-next'; ··· 6 7 export type PluginHandler = 7 8 | HeyApiClientAxiosPlugin['Handler'] 8 9 | HeyApiClientFetchPlugin['Handler'] 10 + | HeyApiClientAngularPlugin['Handler'] 9 11 | HeyApiClientNextPlugin['Handler'] 10 12 | HeyApiClientNuxtPlugin['Handler']; 11 13
+4
packages/openapi-ts/src/plugins/config.ts
··· 1 + import type { HeyApiClientAngularPlugin } from './@hey-api/client-angular'; 2 + import { defaultConfig as heyApiClientAngular } from './@hey-api/client-angular'; 1 3 import type { HeyApiClientAxiosPlugin } from './@hey-api/client-axios'; 2 4 import { defaultConfig as heyApiClientAxios } from './@hey-api/client-axios'; 3 5 import type { HeyApiClientFetchPlugin } from './@hey-api/client-fetch'; ··· 43 45 import { defaultConfig as zod } from './zod'; 44 46 45 47 export interface PluginConfigMap { 48 + '@hey-api/client-angular': HeyApiClientAngularPlugin['Types']; 46 49 '@hey-api/client-axios': HeyApiClientAxiosPlugin['Types']; 47 50 '@hey-api/client-fetch': HeyApiClientFetchPlugin['Types']; 48 51 '@hey-api/client-next': HeyApiClientNextPlugin['Types']; ··· 69 72 export const defaultPluginConfigs: { 70 73 [K in PluginNames]: Plugin.Config<PluginConfigMap[K]>; 71 74 } = { 75 + '@hey-api/client-angular': heyApiClientAngular, 72 76 '@hey-api/client-axios': heyApiClientAxios, 73 77 '@hey-api/client-fetch': heyApiClientFetch, 74 78 '@hey-api/client-next': heyApiClientNext,
+1
packages/openapi-ts/src/plugins/types.d.ts
··· 8 8 export type PluginClientNames = 9 9 | '@hey-api/client-axios' 10 10 | '@hey-api/client-fetch' 11 + | '@hey-api/client-angular' 11 12 | '@hey-api/client-next' 12 13 | '@hey-api/client-nuxt' 13 14 | 'legacy/angular'
+1
packages/openapi-ts/src/utils/getHttpRequestName.ts
··· 6 6 */ 7 7 export const getHttpRequestName = (clientName: PluginClientNames): string => { 8 8 switch (clientName) { 9 + case '@hey-api/client-angular': 9 10 case 'legacy/angular': 10 11 return 'AngularHttpRequest'; 11 12 case 'legacy/axios':
+6 -1
packages/openapi-ts/tsconfig.json
··· 6 6 "resolveJsonModule": true, 7 7 "skipLibCheck": true 8 8 }, 9 - "exclude": ["test/custom/request.ts", "test/e2e/**", "test/generated/**"] 9 + "exclude": [ 10 + "test/custom/request.ts", 11 + "test/e2e/**", 12 + "test/generated/**", 13 + "node_modules" 14 + ] 10 15 }
+83 -11
pnpm-lock.yaml
··· 106 106 specifier: 3.5.13 107 107 version: 3.5.13(typescript@5.8.3) 108 108 109 + examples/openapi-ts-angular: 110 + dependencies: 111 + '@radix-ui/react-form': 112 + specifier: 0.1.1 113 + version: 0.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 114 + '@radix-ui/react-icons': 115 + specifier: 1.3.2 116 + version: 1.3.2(react@19.0.0) 117 + '@radix-ui/themes': 118 + specifier: 3.1.6 119 + version: 3.1.6(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 120 + react: 121 + specifier: 19.0.0 122 + version: 19.0.0 123 + react-dom: 124 + specifier: 19.0.0 125 + version: 19.0.0(react@19.0.0) 126 + devDependencies: 127 + '@config/vite-base': 128 + specifier: workspace:* 129 + version: link:../../packages/config-vite-base 130 + '@hey-api/openapi-ts': 131 + specifier: workspace:* 132 + version: link:../../packages/openapi-ts 133 + '@types/react': 134 + specifier: 19.0.1 135 + version: 19.0.1 136 + '@types/react-dom': 137 + specifier: 19.0.1 138 + version: 19.0.1 139 + '@typescript-eslint/eslint-plugin': 140 + specifier: 8.29.1 141 + version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.8.3) 142 + '@typescript-eslint/parser': 143 + specifier: 8.29.1 144 + version: 8.29.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.8.3) 145 + '@vitejs/plugin-react': 146 + specifier: 4.4.0-beta.1 147 + version: 4.4.0-beta.1(vite@6.2.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 148 + autoprefixer: 149 + specifier: 10.4.19 150 + version: 10.4.19(postcss@8.4.41) 151 + eslint: 152 + specifier: 9.17.0 153 + version: 9.17.0(jiti@2.4.2) 154 + eslint-plugin-react-hooks: 155 + specifier: 5.2.0 156 + version: 5.2.0(eslint@9.17.0(jiti@2.4.2)) 157 + eslint-plugin-react-refresh: 158 + specifier: 0.4.7 159 + version: 0.4.7(eslint@9.17.0(jiti@2.4.2)) 160 + postcss: 161 + specifier: 8.4.41 162 + version: 8.4.41 163 + prettier: 164 + specifier: 3.4.2 165 + version: 3.4.2 166 + tailwindcss: 167 + specifier: 3.4.9 168 + version: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) 169 + typescript: 170 + specifier: 5.8.3 171 + version: 5.8.3 172 + vite: 173 + specifier: 6.2.7 174 + version: 6.2.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 175 + 109 176 examples/openapi-ts-axios: 110 177 dependencies: 111 178 '@radix-ui/react-form': ··· 22122 22189 dependencies: 22123 22190 postcss: 8.5.4 22124 22191 22125 - postcss-import@15.1.0(postcss@8.5.4): 22192 + postcss-import@15.1.0(postcss@8.4.41): 22126 22193 dependencies: 22127 - postcss: 8.5.4 22194 + postcss: 8.4.41 22128 22195 postcss-value-parser: 4.2.0 22129 22196 read-cache: 1.0.0 22130 22197 resolve: 1.22.10 22131 22198 22132 - postcss-js@4.0.1(postcss@8.5.4): 22199 + postcss-js@4.0.1(postcss@8.4.41): 22133 22200 dependencies: 22134 22201 camelcase-css: 2.0.1 22135 - postcss: 8.5.4 22202 + postcss: 8.4.41 22136 22203 22137 22204 postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)): 22138 22205 dependencies: ··· 22142 22209 postcss: 8.4.41 22143 22210 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.8.3) 22144 22211 22145 - postcss-load-config@4.0.2(postcss@8.5.4)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)): 22212 + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)): 22146 22213 dependencies: 22147 22214 lilconfig: 3.1.3 22148 22215 yaml: 2.8.0 22149 22216 optionalDependencies: 22150 - postcss: 8.5.4 22217 + postcss: 8.4.41 22151 22218 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.8.3) 22152 22219 22153 22220 postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.4)(yaml@2.8.0): ··· 22230 22297 dependencies: 22231 22298 icss-utils: 5.1.0(postcss@8.4.41) 22232 22299 postcss: 8.4.41 22300 + 22301 + postcss-nested@6.2.0(postcss@8.4.41): 22302 + dependencies: 22303 + postcss: 8.4.41 22304 + postcss-selector-parser: 6.1.2 22233 22305 22234 22306 postcss-nested@6.2.0(postcss@8.5.4): 22235 22307 dependencies: ··· 23596 23668 normalize-path: 3.0.0 23597 23669 object-hash: 3.0.0 23598 23670 picocolors: 1.1.1 23599 - postcss: 8.5.4 23600 - postcss-import: 15.1.0(postcss@8.5.4) 23601 - postcss-js: 4.0.1(postcss@8.5.4) 23602 - postcss-load-config: 4.0.2(postcss@8.5.4)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) 23603 - postcss-nested: 6.2.0(postcss@8.5.4) 23671 + postcss: 8.4.41 23672 + postcss-import: 15.1.0(postcss@8.4.41) 23673 + postcss-js: 4.0.1(postcss@8.4.41) 23674 + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) 23675 + postcss-nested: 6.2.0(postcss@8.4.41) 23604 23676 postcss-selector-parser: 6.1.2 23605 23677 resolve: 1.22.10 23606 23678 sucrase: 3.35.0