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

feat: Implement Angular HTTP Resource and Request plugins

- Added `httpResource` and `httpRequest` companion plugin handlers to generate Angular services and functions for API operations.
- Introduced `PetResources`, `StoreResources`, and `UserResources` classes for managing pet store operations.
- Enhanced configuration options for HTTP resources and requests, allowing customization of class and method names.
- Generated TypeScript definitions for Angular HTTP resource and request options.
- Updated plugin handler to conditionally enable HTTP resource generation based on configuration.

Max Scopp 2f84f752 000eebec

+1158 -337
+5 -2
examples/openapi-ts-angular-resource/openapi-ts.config.ts
··· 14 throwOnError: true, 15 }, 16 { 17 - asClass: true, 18 - name: '@hey-api/angular-resource', 19 }, 20 '@hey-api/schemas', 21 {
··· 14 throwOnError: true, 15 }, 16 { 17 + httpResource: { 18 + asClass: true, 19 + enabled: true, 20 + }, 21 + name: '@angular/common', 22 }, 23 '@hey-api/schemas', 24 {
+1 -1
examples/openapi-ts-angular-resource/src/app/demo/demo.html
··· 4 @if (pet.error()) { 5 <div class="error-message"> 6 <div class="error-title">Error occurred:</div> 7 - <div class="error-details">{{ pet.error() }}</div> 8 </div> 9 } 10
··· 4 @if (pet.error()) { 5 <div class="error-message"> 6 <div class="error-title">Error occurred:</div> 7 + <div class="error-details">{{ pet.error()|json }}</div> 8 </div> 9 } 10
+1 -1
examples/openapi-ts-angular-resource/src/app/demo/demo.ts
··· 2 import { Component, inject, signal } from '@angular/core'; 3 import { RouterOutlet } from '@angular/router'; 4 5 - import { PetResources } from '../../client/@hey-api/angular-resource.gen'; 6 7 @Component({ 8 host: { ngSkipHydration: 'true' },
··· 2 import { Component, inject, signal } from '@angular/core'; 3 import { RouterOutlet } from '@angular/router'; 4 5 + import { PetResources } from '../../client'; 6 7 @Component({ 8 host: { ngSkipHydration: 'true' },
+295
examples/openapi-ts-angular-resource/src/client/@angular/common/http/httpRequests.gen.ts
···
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { HttpRequest } from '@angular/common/http'; 4 + 5 + import { client as _heyApiClient } from '../../../client.gen'; 6 + import type { Options } from '../../../sdk.gen'; 7 + import type { 8 + AddPetData, 9 + CreateUserData, 10 + CreateUsersWithListInputData, 11 + DeleteOrderData, 12 + DeletePetData, 13 + DeleteUserData, 14 + FindPetsByStatusData, 15 + FindPetsByTagsData, 16 + GetInventoryData, 17 + GetOrderByIdData, 18 + GetPetByIdData, 19 + GetUserByNameData, 20 + LoginUserData, 21 + LogoutUserData, 22 + PlaceOrderData, 23 + UpdatePetData, 24 + UpdatePetWithFormData, 25 + UpdateUserData, 26 + UploadFileData, 27 + } from '../../../types.gen'; 28 + 29 + /** 30 + * Add a new pet to the store. 31 + * Add a new pet to the store. 32 + */ 33 + export const addPetRequest = <ThrowOnError extends boolean = false>( 34 + options: Options<AddPetData, ThrowOnError>, 35 + ): HttpRequest<unknown> => 36 + (options?.client ? options.client : _heyApiClient).requestOptions({ 37 + method: 'POST', 38 + responseStyle: 'data', 39 + url: '/pet', 40 + ...options, 41 + }); 42 + 43 + /** 44 + * Update an existing pet. 45 + * Update an existing pet by Id. 46 + */ 47 + export const updatePetRequest = <ThrowOnError extends boolean = false>( 48 + options: Options<UpdatePetData, ThrowOnError>, 49 + ): HttpRequest<unknown> => 50 + (options?.client ? options.client : _heyApiClient).requestOptions({ 51 + method: 'PUT', 52 + responseStyle: 'data', 53 + url: '/pet', 54 + ...options, 55 + }); 56 + 57 + /** 58 + * Finds Pets by status. 59 + * Multiple status values can be provided with comma separated strings. 60 + */ 61 + export const findPetsByStatusRequest = <ThrowOnError extends boolean = false>( 62 + options: Options<FindPetsByStatusData, ThrowOnError>, 63 + ): HttpRequest<unknown> => 64 + (options?.client ? options.client : _heyApiClient).requestOptions({ 65 + method: 'GET', 66 + responseStyle: 'data', 67 + url: '/pet/findByStatus', 68 + ...options, 69 + }); 70 + 71 + /** 72 + * Finds Pets by tags. 73 + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 74 + */ 75 + export const findPetsByTagsRequest = <ThrowOnError extends boolean = false>( 76 + options: Options<FindPetsByTagsData, ThrowOnError>, 77 + ): HttpRequest<unknown> => 78 + (options?.client ? options.client : _heyApiClient).requestOptions({ 79 + method: 'GET', 80 + responseStyle: 'data', 81 + url: '/pet/findByTags', 82 + ...options, 83 + }); 84 + 85 + /** 86 + * Deletes a pet. 87 + * Delete a pet. 88 + */ 89 + export const deletePetRequest = <ThrowOnError extends boolean = false>( 90 + options: Options<DeletePetData, ThrowOnError>, 91 + ): HttpRequest<unknown> => 92 + (options?.client ? options.client : _heyApiClient).requestOptions({ 93 + method: 'DELETE', 94 + responseStyle: 'data', 95 + url: '/pet/{petId}', 96 + ...options, 97 + }); 98 + 99 + /** 100 + * Find pet by ID. 101 + * Returns a single pet. 102 + */ 103 + export const getPetByIdRequest = <ThrowOnError extends boolean = false>( 104 + options: Options<GetPetByIdData, ThrowOnError>, 105 + ): HttpRequest<unknown> => 106 + (options?.client ? options.client : _heyApiClient).requestOptions({ 107 + method: 'GET', 108 + responseStyle: 'data', 109 + url: '/pet/{petId}', 110 + ...options, 111 + }); 112 + 113 + /** 114 + * Updates a pet in the store with form data. 115 + * Updates a pet resource based on the form data. 116 + */ 117 + export const updatePetWithFormRequest = <ThrowOnError extends boolean = false>( 118 + options: Options<UpdatePetWithFormData, ThrowOnError>, 119 + ): HttpRequest<unknown> => 120 + (options?.client ? options.client : _heyApiClient).requestOptions({ 121 + method: 'POST', 122 + responseStyle: 'data', 123 + url: '/pet/{petId}', 124 + ...options, 125 + }); 126 + 127 + /** 128 + * Uploads an image. 129 + * Upload image of the pet. 130 + */ 131 + export const uploadFileRequest = <ThrowOnError extends boolean = false>( 132 + options: Options<UploadFileData, ThrowOnError>, 133 + ): HttpRequest<unknown> => 134 + (options?.client ? options.client : _heyApiClient).requestOptions({ 135 + method: 'POST', 136 + responseStyle: 'data', 137 + url: '/pet/{petId}/uploadImage', 138 + ...options, 139 + }); 140 + 141 + /** 142 + * Returns pet inventories by status. 143 + * Returns a map of status codes to quantities. 144 + */ 145 + export const getInventoryRequest = <ThrowOnError extends boolean = false>( 146 + options?: Options<GetInventoryData, ThrowOnError>, 147 + ): HttpRequest<unknown> => 148 + (options?.client ? options.client : _heyApiClient).requestOptions({ 149 + method: 'GET', 150 + responseStyle: 'data', 151 + url: '/store/inventory', 152 + ...options, 153 + }); 154 + 155 + /** 156 + * Place an order for a pet. 157 + * Place a new order in the store. 158 + */ 159 + export const placeOrderRequest = <ThrowOnError extends boolean = false>( 160 + options?: Options<PlaceOrderData, ThrowOnError>, 161 + ): HttpRequest<unknown> => 162 + (options?.client ? options.client : _heyApiClient).requestOptions({ 163 + method: 'POST', 164 + responseStyle: 'data', 165 + url: '/store/order', 166 + ...options, 167 + }); 168 + 169 + /** 170 + * Delete purchase order by identifier. 171 + * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors. 172 + */ 173 + export const deleteOrderRequest = <ThrowOnError extends boolean = false>( 174 + options: Options<DeleteOrderData, ThrowOnError>, 175 + ): HttpRequest<unknown> => 176 + (options?.client ? options.client : _heyApiClient).requestOptions({ 177 + method: 'DELETE', 178 + responseStyle: 'data', 179 + url: '/store/order/{orderId}', 180 + ...options, 181 + }); 182 + 183 + /** 184 + * Find purchase order by ID. 185 + * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. 186 + */ 187 + export const getOrderByIdRequest = <ThrowOnError extends boolean = false>( 188 + options: Options<GetOrderByIdData, ThrowOnError>, 189 + ): HttpRequest<unknown> => 190 + (options?.client ? options.client : _heyApiClient).requestOptions({ 191 + method: 'GET', 192 + responseStyle: 'data', 193 + url: '/store/order/{orderId}', 194 + ...options, 195 + }); 196 + 197 + /** 198 + * Create user. 199 + * This can only be done by the logged in user. 200 + */ 201 + export const createUserRequest = <ThrowOnError extends boolean = false>( 202 + options?: Options<CreateUserData, ThrowOnError>, 203 + ): HttpRequest<unknown> => 204 + (options?.client ? options.client : _heyApiClient).requestOptions({ 205 + method: 'POST', 206 + responseStyle: 'data', 207 + url: '/user', 208 + ...options, 209 + }); 210 + 211 + /** 212 + * Creates list of users with given input array. 213 + * Creates list of users with given input array. 214 + */ 215 + export const createUsersWithListInputRequest = < 216 + ThrowOnError extends boolean = false, 217 + >( 218 + options?: Options<CreateUsersWithListInputData, ThrowOnError>, 219 + ): HttpRequest<unknown> => 220 + (options?.client ? options.client : _heyApiClient).requestOptions({ 221 + method: 'POST', 222 + responseStyle: 'data', 223 + url: '/user/createWithList', 224 + ...options, 225 + }); 226 + 227 + /** 228 + * Logs user into the system. 229 + * Log into the system. 230 + */ 231 + export const loginUserRequest = <ThrowOnError extends boolean = false>( 232 + options?: Options<LoginUserData, ThrowOnError>, 233 + ): HttpRequest<unknown> => 234 + (options?.client ? options.client : _heyApiClient).requestOptions({ 235 + method: 'GET', 236 + responseStyle: 'data', 237 + url: '/user/login', 238 + ...options, 239 + }); 240 + 241 + /** 242 + * Logs out current logged in user session. 243 + * Log user out of the system. 244 + */ 245 + export const logoutUserRequest = <ThrowOnError extends boolean = false>( 246 + options?: Options<LogoutUserData, ThrowOnError>, 247 + ): HttpRequest<unknown> => 248 + (options?.client ? options.client : _heyApiClient).requestOptions({ 249 + method: 'GET', 250 + responseStyle: 'data', 251 + url: '/user/logout', 252 + ...options, 253 + }); 254 + 255 + /** 256 + * Delete user resource. 257 + * This can only be done by the logged in user. 258 + */ 259 + export const deleteUserRequest = <ThrowOnError extends boolean = false>( 260 + options: Options<DeleteUserData, ThrowOnError>, 261 + ): HttpRequest<unknown> => 262 + (options?.client ? options.client : _heyApiClient).requestOptions({ 263 + method: 'DELETE', 264 + responseStyle: 'data', 265 + url: '/user/{username}', 266 + ...options, 267 + }); 268 + 269 + /** 270 + * Get user by user name. 271 + * Get user detail based on username. 272 + */ 273 + export const getUserByNameRequest = <ThrowOnError extends boolean = false>( 274 + options: Options<GetUserByNameData, ThrowOnError>, 275 + ): HttpRequest<unknown> => 276 + (options?.client ? options.client : _heyApiClient).requestOptions({ 277 + method: 'GET', 278 + responseStyle: 'data', 279 + url: '/user/{username}', 280 + ...options, 281 + }); 282 + 283 + /** 284 + * Update user resource. 285 + * This can only be done by the logged in user. 286 + */ 287 + export const updateUserRequest = <ThrowOnError extends boolean = false>( 288 + options: Options<UpdateUserData, ThrowOnError>, 289 + ): HttpRequest<unknown> => 290 + (options?.client ? options.client : _heyApiClient).requestOptions({ 291 + method: 'PUT', 292 + responseStyle: 'data', 293 + url: '/user/{username}', 294 + ...options, 295 + });
+80 -100
examples/openapi-ts-angular-resource/src/client/@hey-api/angular-resource.gen.ts examples/openapi-ts-angular-resource/src/client/@angular/common/http/httpResource.gen.ts
··· 1 // This file is auto-generated by @hey-api/openapi-ts 2 3 - import { Injectable, resource } from '@angular/core'; 4 5 - import type { Options } from '../sdk.gen'; 6 - import { 7 - addPet, 8 - createUser, 9 - createUsersWithListInput, 10 - deleteOrder, 11 - deletePet, 12 - deleteUser, 13 - findPetsByStatus, 14 - findPetsByTags, 15 - getInventory, 16 - getOrderById, 17 - getPetById, 18 - getUserByName, 19 - loginUser, 20 - logoutUser, 21 - placeOrder, 22 - updatePet, 23 - updatePetWithForm, 24 - updateUser, 25 - uploadFile, 26 - } from '../sdk.gen'; 27 import type { 28 AddPetData, 29 CreateUserData, 30 CreateUsersWithListInputData, 31 DeleteOrderData, 32 DeletePetData, 33 DeleteUserData, 34 FindPetsByStatusData, 35 FindPetsByTagsData, 36 GetInventoryData, 37 GetOrderByIdData, 38 GetPetByIdData, 39 GetUserByNameData, 40 LoginUserData, 41 LogoutUserData, 42 PlaceOrderData, 43 UpdatePetData, 44 UpdatePetWithFormData, 45 UpdateUserData, 46 UploadFileData, 47 - } from '../types.gen'; 48 49 @Injectable({ 50 providedIn: 'root', ··· 57 public addPet<ThrowOnError extends boolean = false>( 58 options: () => Options<AddPetData, ThrowOnError>, 59 ) { 60 - return resource({ 61 - loader: async ({ request }) => addPet(request), 62 - request: options, 63 - }); 64 } 65 66 /** ··· 70 public updatePet<ThrowOnError extends boolean = false>( 71 options: () => Options<UpdatePetData, ThrowOnError>, 72 ) { 73 - return resource({ 74 - loader: async ({ request }) => updatePet(request), 75 - request: options, 76 - }); 77 } 78 79 /** ··· 83 public findPetsByStatus<ThrowOnError extends boolean = false>( 84 options: () => Options<FindPetsByStatusData, ThrowOnError>, 85 ) { 86 - return resource({ 87 - loader: async ({ request }) => findPetsByStatus(request), 88 - request: options, 89 - }); 90 } 91 92 /** ··· 96 public findPetsByTags<ThrowOnError extends boolean = false>( 97 options: () => Options<FindPetsByTagsData, ThrowOnError>, 98 ) { 99 - return resource({ 100 - loader: async ({ request }) => findPetsByTags(request), 101 - request: options, 102 - }); 103 } 104 105 /** ··· 109 public deletePet<ThrowOnError extends boolean = false>( 110 options: () => Options<DeletePetData, ThrowOnError>, 111 ) { 112 - return resource({ 113 - loader: async ({ request }) => deletePet(request), 114 - request: options, 115 - }); 116 } 117 118 /** ··· 122 public getPetById<ThrowOnError extends boolean = false>( 123 options: () => Options<GetPetByIdData, ThrowOnError>, 124 ) { 125 - return resource({ 126 - loader: async ({ request }) => getPetById(request), 127 - request: options, 128 - }); 129 } 130 131 /** ··· 135 public updatePetWithForm<ThrowOnError extends boolean = false>( 136 options: () => Options<UpdatePetWithFormData, ThrowOnError>, 137 ) { 138 - return resource({ 139 - loader: async ({ request }) => updatePetWithForm(request), 140 - request: options, 141 - }); 142 } 143 144 /** ··· 148 public uploadFile<ThrowOnError extends boolean = false>( 149 options: () => Options<UploadFileData, ThrowOnError>, 150 ) { 151 - return resource({ 152 - loader: async ({ request }) => uploadFile(request), 153 - request: options, 154 - }); 155 } 156 } 157 ··· 166 public getInventory<ThrowOnError extends boolean = false>( 167 options?: () => Options<GetInventoryData, ThrowOnError>, 168 ) { 169 - return resource({ 170 - loader: async ({ request }) => getInventory(request), 171 - request: options, 172 - }); 173 } 174 175 /** ··· 179 public placeOrder<ThrowOnError extends boolean = false>( 180 options?: () => Options<PlaceOrderData, ThrowOnError>, 181 ) { 182 - return resource({ 183 - loader: async ({ request }) => placeOrder(request), 184 - request: options, 185 - }); 186 } 187 188 /** ··· 192 public deleteOrder<ThrowOnError extends boolean = false>( 193 options: () => Options<DeleteOrderData, ThrowOnError>, 194 ) { 195 - return resource({ 196 - loader: async ({ request }) => deleteOrder(request), 197 - request: options, 198 - }); 199 } 200 201 /** ··· 205 public getOrderById<ThrowOnError extends boolean = false>( 206 options: () => Options<GetOrderByIdData, ThrowOnError>, 207 ) { 208 - return resource({ 209 - loader: async ({ request }) => getOrderById(request), 210 - request: options, 211 - }); 212 } 213 } 214 ··· 223 public createUser<ThrowOnError extends boolean = false>( 224 options?: () => Options<CreateUserData, ThrowOnError>, 225 ) { 226 - return resource({ 227 - loader: async ({ request }) => createUser(request), 228 - request: options, 229 - }); 230 } 231 232 /** ··· 236 public createUsersWithListInput<ThrowOnError extends boolean = false>( 237 options?: () => Options<CreateUsersWithListInputData, ThrowOnError>, 238 ) { 239 - return resource({ 240 - loader: async ({ request }) => createUsersWithListInput(request), 241 - request: options, 242 - }); 243 } 244 245 /** ··· 249 public loginUser<ThrowOnError extends boolean = false>( 250 options?: () => Options<LoginUserData, ThrowOnError>, 251 ) { 252 - return resource({ 253 - loader: async ({ request }) => loginUser(request), 254 - request: options, 255 - }); 256 } 257 258 /** ··· 262 public logoutUser<ThrowOnError extends boolean = false>( 263 options?: () => Options<LogoutUserData, ThrowOnError>, 264 ) { 265 - return resource({ 266 - loader: async ({ request }) => logoutUser(request), 267 - request: options, 268 - }); 269 } 270 271 /** ··· 275 public deleteUser<ThrowOnError extends boolean = false>( 276 options: () => Options<DeleteUserData, ThrowOnError>, 277 ) { 278 - return resource({ 279 - loader: async ({ request }) => deleteUser(request), 280 - request: options, 281 - }); 282 } 283 284 /** ··· 288 public getUserByName<ThrowOnError extends boolean = false>( 289 options: () => Options<GetUserByNameData, ThrowOnError>, 290 ) { 291 - return resource({ 292 - loader: async ({ request }) => getUserByName(request), 293 - request: options, 294 - }); 295 } 296 297 /** ··· 301 public updateUser<ThrowOnError extends boolean = false>( 302 options: () => Options<UpdateUserData, ThrowOnError>, 303 ) { 304 - return resource({ 305 - loader: async ({ request }) => updateUser(request), 306 - request: options, 307 - }); 308 } 309 }
··· 1 // This file is auto-generated by @hey-api/openapi-ts 2 3 + import { httpResource } from '@angular/common/http'; 4 + import { Injectable } from '@angular/core'; 5 6 + import type { Options } from '../../../sdk.gen'; 7 import type { 8 AddPetData, 9 + AddPetResponse, 10 CreateUserData, 11 + CreateUserResponse, 12 CreateUsersWithListInputData, 13 + CreateUsersWithListInputResponse, 14 DeleteOrderData, 15 DeletePetData, 16 DeleteUserData, 17 FindPetsByStatusData, 18 + FindPetsByStatusResponse, 19 FindPetsByTagsData, 20 + FindPetsByTagsResponse, 21 GetInventoryData, 22 + GetInventoryResponse, 23 GetOrderByIdData, 24 + GetOrderByIdResponse, 25 GetPetByIdData, 26 + GetPetByIdResponse, 27 GetUserByNameData, 28 + GetUserByNameResponse, 29 LoginUserData, 30 + LoginUserResponse, 31 LogoutUserData, 32 PlaceOrderData, 33 + PlaceOrderResponse, 34 UpdatePetData, 35 + UpdatePetResponse, 36 UpdatePetWithFormData, 37 + UpdatePetWithFormResponse, 38 UpdateUserData, 39 UploadFileData, 40 + UploadFileResponse, 41 + } from '../../../types.gen'; 42 + import { 43 + addPetRequest, 44 + createUserRequest, 45 + createUsersWithListInputRequest, 46 + deleteOrderRequest, 47 + deletePetRequest, 48 + deleteUserRequest, 49 + findPetsByStatusRequest, 50 + findPetsByTagsRequest, 51 + getInventoryRequest, 52 + getOrderByIdRequest, 53 + getPetByIdRequest, 54 + getUserByNameRequest, 55 + loginUserRequest, 56 + logoutUserRequest, 57 + placeOrderRequest, 58 + updatePetRequest, 59 + updatePetWithFormRequest, 60 + updateUserRequest, 61 + uploadFileRequest, 62 + } from './httpRequests.gen'; 63 64 @Injectable({ 65 providedIn: 'root', ··· 72 public addPet<ThrowOnError extends boolean = false>( 73 options: () => Options<AddPetData, ThrowOnError>, 74 ) { 75 + return httpResource<AddPetResponse>(() => addPetRequest(options())); 76 } 77 78 /** ··· 82 public updatePet<ThrowOnError extends boolean = false>( 83 options: () => Options<UpdatePetData, ThrowOnError>, 84 ) { 85 + return httpResource<UpdatePetResponse>(() => updatePetRequest(options())); 86 } 87 88 /** ··· 92 public findPetsByStatus<ThrowOnError extends boolean = false>( 93 options: () => Options<FindPetsByStatusData, ThrowOnError>, 94 ) { 95 + return httpResource<FindPetsByStatusResponse>(() => 96 + findPetsByStatusRequest(options()), 97 + ); 98 } 99 100 /** ··· 104 public findPetsByTags<ThrowOnError extends boolean = false>( 105 options: () => Options<FindPetsByTagsData, ThrowOnError>, 106 ) { 107 + return httpResource<FindPetsByTagsResponse>(() => 108 + findPetsByTagsRequest(options()), 109 + ); 110 } 111 112 /** ··· 116 public deletePet<ThrowOnError extends boolean = false>( 117 options: () => Options<DeletePetData, ThrowOnError>, 118 ) { 119 + return httpResource<unknown>(() => deletePetRequest(options())); 120 } 121 122 /** ··· 126 public getPetById<ThrowOnError extends boolean = false>( 127 options: () => Options<GetPetByIdData, ThrowOnError>, 128 ) { 129 + return httpResource<GetPetByIdResponse>(() => getPetByIdRequest(options())); 130 } 131 132 /** ··· 136 public updatePetWithForm<ThrowOnError extends boolean = false>( 137 options: () => Options<UpdatePetWithFormData, ThrowOnError>, 138 ) { 139 + return httpResource<UpdatePetWithFormResponse>(() => 140 + updatePetWithFormRequest(options()), 141 + ); 142 } 143 144 /** ··· 148 public uploadFile<ThrowOnError extends boolean = false>( 149 options: () => Options<UploadFileData, ThrowOnError>, 150 ) { 151 + return httpResource<UploadFileResponse>(() => uploadFileRequest(options())); 152 } 153 } 154 ··· 163 public getInventory<ThrowOnError extends boolean = false>( 164 options?: () => Options<GetInventoryData, ThrowOnError>, 165 ) { 166 + return httpResource<GetInventoryResponse>(() => 167 + getInventoryRequest(options ? options() : undefined), 168 + ); 169 } 170 171 /** ··· 175 public placeOrder<ThrowOnError extends boolean = false>( 176 options?: () => Options<PlaceOrderData, ThrowOnError>, 177 ) { 178 + return httpResource<PlaceOrderResponse>(() => 179 + placeOrderRequest(options ? options() : undefined), 180 + ); 181 } 182 183 /** ··· 187 public deleteOrder<ThrowOnError extends boolean = false>( 188 options: () => Options<DeleteOrderData, ThrowOnError>, 189 ) { 190 + return httpResource<unknown>(() => deleteOrderRequest(options())); 191 } 192 193 /** ··· 197 public getOrderById<ThrowOnError extends boolean = false>( 198 options: () => Options<GetOrderByIdData, ThrowOnError>, 199 ) { 200 + return httpResource<GetOrderByIdResponse>(() => 201 + getOrderByIdRequest(options()), 202 + ); 203 } 204 } 205 ··· 214 public createUser<ThrowOnError extends boolean = false>( 215 options?: () => Options<CreateUserData, ThrowOnError>, 216 ) { 217 + return httpResource<CreateUserResponse>(() => 218 + createUserRequest(options ? options() : undefined), 219 + ); 220 } 221 222 /** ··· 226 public createUsersWithListInput<ThrowOnError extends boolean = false>( 227 options?: () => Options<CreateUsersWithListInputData, ThrowOnError>, 228 ) { 229 + return httpResource<CreateUsersWithListInputResponse>(() => 230 + createUsersWithListInputRequest(options ? options() : undefined), 231 + ); 232 } 233 234 /** ··· 238 public loginUser<ThrowOnError extends boolean = false>( 239 options?: () => Options<LoginUserData, ThrowOnError>, 240 ) { 241 + return httpResource<LoginUserResponse>(() => 242 + loginUserRequest(options ? options() : undefined), 243 + ); 244 } 245 246 /** ··· 250 public logoutUser<ThrowOnError extends boolean = false>( 251 options?: () => Options<LogoutUserData, ThrowOnError>, 252 ) { 253 + return httpResource<unknown>(() => 254 + logoutUserRequest(options ? options() : undefined), 255 + ); 256 } 257 258 /** ··· 262 public deleteUser<ThrowOnError extends boolean = false>( 263 options: () => Options<DeleteUserData, ThrowOnError>, 264 ) { 265 + return httpResource<unknown>(() => deleteUserRequest(options())); 266 } 267 268 /** ··· 272 public getUserByName<ThrowOnError extends boolean = false>( 273 options: () => Options<GetUserByNameData, ThrowOnError>, 274 ) { 275 + return httpResource<GetUserByNameResponse>(() => 276 + getUserByNameRequest(options()), 277 + ); 278 } 279 280 /** ··· 284 public updateUser<ThrowOnError extends boolean = false>( 285 options: () => Options<UpdateUserData, ThrowOnError>, 286 ) { 287 + return httpResource<unknown>(() => updateUserRequest(options())); 288 } 289 }
+54 -21
examples/openapi-ts-angular-resource/src/client/client/client.gen.ts
··· 16 import { firstValueFrom } from 'rxjs'; 17 import { filter } from 'rxjs/operators'; 18 19 - import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 20 import { 21 buildUrl, 22 createConfig, ··· 50 ResolvedRequestOptions 51 >(); 52 53 - const request: Client['request'] = async (options) => { 54 const opts = { 55 ..._config, 56 ...options, 57 headers: mergeHeaders(_config.headers, options.headers), 58 httpClient: options.httpClient ?? _config.httpClient, 59 serializedBody: options.body as any, 60 }; 61 ··· 65 inject(HttpClient), 66 ); 67 } else { 68 - assertInInjectionContext(request); 69 opts.httpClient = inject(HttpClient); 70 } 71 } 72 73 - if (opts.security) { 74 - await setAuthParams({ 75 - ...opts, 76 - security: opts.security, 77 - }); 78 - } 79 - 80 - if (opts.requestValidator) { 81 - await opts.requestValidator(opts); 82 - } 83 - 84 if (opts.body && opts.bodySerializer) { 85 opts.serializedBody = opts.bodySerializer(opts.body); 86 } ··· 90 opts.headers.delete('Content-Type'); 91 } 92 93 - const url = buildUrl(opts); 94 95 - let req = new HttpRequest<unknown>( 96 opts.method, 97 url, 98 opts.serializedBody || null, ··· 102 }, 103 ); 104 105 for (const fn of interceptors.request._fns) { 106 if (fn) { 107 - req = await fn(req, opts); 108 } 109 } 110 ··· 116 117 try { 118 response = await firstValueFrom( 119 - opts.httpClient 120 - .request(req) 121 .pipe(filter((event) => event.type === HttpEventType.Response)), 122 ); 123 124 for (const fn of interceptors.response._fns) { 125 if (fn) { 126 - response = await fn(response, req, opts); 127 } 128 } 129 ··· 153 finalError, 154 response as HttpResponse<unknown>, 155 req, 156 - opts, 157 )) as string; 158 } 159 } ··· 184 post: (options) => request({ ...options, method: 'POST' }), 185 put: (options) => request({ ...options, method: 'PUT' }), 186 request, 187 setConfig, 188 trace: (options) => request({ ...options, method: 'TRACE' }), 189 };
··· 16 import { firstValueFrom } from 'rxjs'; 17 import { filter } from 'rxjs/operators'; 18 19 + import type { 20 + Client, 21 + Config, 22 + RequestOptions, 23 + ResolvedRequestOptions, 24 + ResponseStyle, 25 + } from './types.gen'; 26 import { 27 buildUrl, 28 createConfig, ··· 56 ResolvedRequestOptions 57 >(); 58 59 + const requestOptions = < 60 + ThrowOnError extends boolean = false, 61 + TResponseStyle extends ResponseStyle = 'fields', 62 + >( 63 + options: RequestOptions<TResponseStyle, ThrowOnError>, 64 + ) => { 65 const opts = { 66 ..._config, 67 ...options, 68 headers: mergeHeaders(_config.headers, options.headers), 69 httpClient: options.httpClient ?? _config.httpClient, 70 + method: 'GET', 71 serializedBody: options.body as any, 72 }; 73 ··· 77 inject(HttpClient), 78 ); 79 } else { 80 + assertInInjectionContext(requestOptions); 81 opts.httpClient = inject(HttpClient); 82 } 83 } 84 85 if (opts.body && opts.bodySerializer) { 86 opts.serializedBody = opts.bodySerializer(opts.body); 87 } ··· 91 opts.headers.delete('Content-Type'); 92 } 93 94 + const url = buildUrl(opts as any); 95 96 + const req = new HttpRequest<unknown>( 97 opts.method, 98 url, 99 opts.serializedBody || null, ··· 103 }, 104 ); 105 106 + return { opts, req }; 107 + }; 108 + 109 + const request: Client['request'] = async (options) => { 110 + const { opts, req: initialReq } = requestOptions(options); 111 + 112 + if (opts.security) { 113 + await setAuthParams({ 114 + ...opts, 115 + security: opts.security, 116 + }); 117 + } 118 + 119 + if (opts.requestValidator) { 120 + await opts.requestValidator(opts); 121 + } 122 + 123 + let req = initialReq; 124 + 125 for (const fn of interceptors.request._fns) { 126 if (fn) { 127 + req = await fn(req, opts as any); 128 } 129 } 130 ··· 136 137 try { 138 response = await firstValueFrom( 139 + opts 140 + .httpClient!.request(req) 141 .pipe(filter((event) => event.type === HttpEventType.Response)), 142 ); 143 144 for (const fn of interceptors.response._fns) { 145 if (fn) { 146 + response = await fn(response, req, opts as any); 147 } 148 } 149 ··· 173 finalError, 174 response as HttpResponse<unknown>, 175 req, 176 + opts as any, 177 )) as string; 178 } 179 } ··· 204 post: (options) => request({ ...options, method: 'POST' }), 205 put: (options) => request({ ...options, method: 'PUT' }), 206 request, 207 + requestOptions: (options) => { 208 + if (options.security) { 209 + throw new Error('Security is not supported in requestOptions'); 210 + } 211 + 212 + if (options.requestValidator) { 213 + throw new Error( 214 + 'Request validation is not supported in requestOptions', 215 + ); 216 + } 217 + 218 + return requestOptions(options).req; 219 + }, 220 setConfig, 221 trace: (options) => request({ ...options, method: 'TRACE' }), 222 };
+9
examples/openapi-ts-angular-resource/src/client/client/types.gen.ts
··· 163 Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 164 ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 165 166 type BuildUrlFn = < 167 TData extends { 168 body?: unknown; ··· 181 unknown, 182 ResolvedRequestOptions 183 >; 184 }; 185 186 /**
··· 163 Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 164 ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 165 166 + type RequestOptionsFn = < 167 + ThrowOnError extends boolean = false, 168 + TResponseStyle extends ResponseStyle = 'fields', 169 + >( 170 + options: RequestOptions<TResponseStyle, ThrowOnError>, 171 + ) => HttpRequest<unknown>; 172 + 173 type BuildUrlFn = < 174 TData extends { 175 body?: unknown; ··· 188 unknown, 189 ResolvedRequestOptions 190 >; 191 + 192 + requestOptions: RequestOptionsFn; 193 }; 194 195 /**
+9 -10
examples/openapi-ts-angular-resource/src/client/client/utils.gen.ts
··· 194 return; 195 }; 196 197 - export const setAuthParams = async ({ 198 - security, 199 - ...options 200 - }: Pick<Required<RequestOptions>, 'security'> & 201 - Pick<RequestOptions, 'auth' | 'query'> & { 202 - headers: HttpHeaders; 203 - }) => { 204 - for (const auth of security) { 205 const token = await getAuthToken(auth, options.auth); 206 207 if (!token) { ··· 218 options.query[name] = token; 219 break; 220 case 'cookie': 221 - options.headers.append('Cookie', `${name}=${token}`); 222 break; 223 case 'header': 224 default: 225 - options.headers.set(name, token); 226 break; 227 } 228
··· 194 return; 195 }; 196 197 + export const setAuthParams = async ( 198 + options: Pick<Required<RequestOptions>, 'security'> & 199 + Pick<RequestOptions, 'auth' | 'query'> & { 200 + headers: HttpHeaders; 201 + }, 202 + ) => { 203 + for (const auth of options.security) { 204 const token = await getAuthToken(auth, options.auth); 205 206 if (!token) { ··· 217 options.query[name] = token; 218 break; 219 case 'cookie': 220 + options.headers = options.headers.append('Cookie', `${name}=${token}`); 221 break; 222 case 'header': 223 default: 224 + options.headers = options.headers.set(name, token); 225 break; 226 } 227
+2
examples/openapi-ts-angular-resource/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';
··· 1 // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './@angular/common/http/httpRequests.gen'; 3 + export * from './@angular/common/http/httpResource.gen'; 4 export * from './sdk.gen'; 5 export * from './types.gen';
+1 -1
packages/openapi-ts/src/ir/context.ts
··· 142 dependencies: plugin.dependencies ?? [], 143 handler: plugin.handler, 144 name: plugin.name, 145 - output: plugin.output!, 146 }); 147 this.plugins[instance.name] = instance; 148 return instance;
··· 142 dependencies: plugin.dependencies ?? [], 143 handler: plugin.handler, 144 name: plugin.name, 145 + output: plugin.output! as string, 146 }); 147 this.plugins[instance.name] = instance; 148 return instance;
+398
packages/openapi-ts/src/plugins/@angular/common/companions/angularHttpRequestsCompanionPluginHandler.ts
···
··· 1 + import { tsc } from '../../../../tsc'; 2 + import { stringCase } from '../../../../utils/stringCase'; 3 + import { clientId } from '../../../@hey-api/client-core/utils'; 4 + import { sdkId } from '../../../@hey-api/sdk/constants'; 5 + import { operationClasses } from '../../../@hey-api/sdk/operation'; 6 + import { typesId } from '../../../@hey-api/typescript/ref'; 7 + import { 8 + createOperationComment, 9 + isOperationOptionsRequired, 10 + } from '../../../shared/utils/operation'; 11 + import type { HeyApiAngularCommonPlugin } from '../types'; 12 + 13 + export const REQUEST_APIS_SUFFIX = '/http/httpRequests'; 14 + 15 + export const angularHttpRequestsCompanionPluginHandler: HeyApiAngularCommonPlugin['Handler'] = 16 + ({ plugin }) => { 17 + const sdkPlugin = plugin.getPlugin('@hey-api/sdk'); 18 + 19 + const file = plugin.createFile({ 20 + exportFromIndex: true, 21 + id: plugin.name + REQUEST_APIS_SUFFIX, 22 + path: plugin.output + REQUEST_APIS_SUFFIX, 23 + }); 24 + 25 + if (plugin.config.httpRequest?.asClass) { 26 + file.import({ 27 + module: '@angular/core', 28 + name: 'Injectable', 29 + }); 30 + } 31 + 32 + file.import({ 33 + module: '@angular/common/http', 34 + name: 'HttpRequest', 35 + }); 36 + 37 + file.import({ 38 + module: file.relativePathToFile({ 39 + context: plugin.context, 40 + id: sdkId, 41 + }), 42 + name: 'Options', 43 + }); 44 + 45 + if (plugin.config.httpRequest?.asClass) { 46 + generateAngularClassRequests({ file, plugin, sdkPlugin }); 47 + } else { 48 + generateAngularFunctionRequests({ file, plugin }); 49 + } 50 + }; 51 + 52 + interface AngularRequestClassEntry { 53 + className: string; 54 + classes: Set<string>; 55 + methods: Set<string>; 56 + nodes: Array<any>; 57 + root: boolean; 58 + } 59 + 60 + const generateAngularClassRequests = ({ 61 + file, 62 + plugin, 63 + sdkPlugin, 64 + }: { 65 + file: any; 66 + plugin: HeyApiAngularCommonPlugin['Instance']; 67 + sdkPlugin: any; 68 + }) => { 69 + const requestClasses = new Map<string, AngularRequestClassEntry>(); 70 + const generatedClasses = new Set<string>(); 71 + 72 + // Iterate through operations to build class structure 73 + plugin.forEach('operation', ({ operation }) => { 74 + const isRequiredOptions = isOperationOptionsRequired({ 75 + context: plugin.context, 76 + operation, 77 + }); 78 + 79 + const classes = operationClasses({ 80 + context: plugin.context, 81 + operation, 82 + plugin: sdkPlugin, 83 + }); 84 + 85 + for (const entry of classes.values()) { 86 + entry.path.forEach((currentClassName, index) => { 87 + if (!requestClasses.has(currentClassName)) { 88 + requestClasses.set(currentClassName, { 89 + className: currentClassName, 90 + classes: new Set(), 91 + methods: new Set(), 92 + nodes: [], 93 + root: !index, 94 + }); 95 + } 96 + 97 + const parentClassName = entry.path[index - 1]; 98 + if (parentClassName && parentClassName !== currentClassName) { 99 + const parentClass = requestClasses.get(parentClassName)!; 100 + parentClass.classes.add(currentClassName); 101 + requestClasses.set(parentClassName, parentClass); 102 + } 103 + 104 + const isLast = entry.path.length === index + 1; 105 + if (!isLast) { 106 + return; 107 + } 108 + 109 + const currentClass = requestClasses.get(currentClassName)!; 110 + 111 + // Generate the request method name with "Request" suffix 112 + const requestMethodName = 113 + plugin.config.httpRequest!.methodNameBuilder!(operation); 114 + 115 + // Avoid duplicate methods 116 + if (currentClass.methods.has(requestMethodName)) { 117 + return; 118 + } 119 + 120 + // Generate Angular request method 121 + const methodNode = generateAngularRequestMethod({ 122 + file, 123 + isRequiredOptions, 124 + methodName: requestMethodName, 125 + operation, 126 + plugin, 127 + }); 128 + 129 + if (!currentClass.nodes.length) { 130 + currentClass.nodes.push(methodNode); 131 + } else { 132 + currentClass.nodes.push(tsc.identifier({ text: '\n' }), methodNode); 133 + } 134 + 135 + currentClass.methods.add(requestMethodName); 136 + requestClasses.set(currentClassName, currentClass); 137 + }); 138 + } 139 + }); 140 + 141 + // Generate classes 142 + const generateClass = (currentClass: AngularRequestClassEntry) => { 143 + if (generatedClasses.has(currentClass.className)) { 144 + return; 145 + } 146 + 147 + // Handle child classes 148 + if (currentClass.classes.size) { 149 + for (const childClassName of currentClass.classes) { 150 + const childClass = requestClasses.get(childClassName)!; 151 + generateClass(childClass); 152 + 153 + currentClass.nodes.push( 154 + tsc.propertyDeclaration({ 155 + initializer: tsc.newExpression({ 156 + argumentsArray: [], 157 + expression: tsc.identifier({ 158 + text: plugin.config.httpRequest!.classNameBuilder!( 159 + childClass.className, 160 + ), 161 + }), 162 + }), 163 + name: stringCase({ 164 + case: 'camelCase', 165 + value: childClass.className, 166 + }), 167 + }), 168 + ); 169 + } 170 + } 171 + 172 + const node = tsc.classDeclaration({ 173 + decorator: currentClass.root 174 + ? { 175 + args: [ 176 + { 177 + providedIn: 'root', 178 + }, 179 + ], 180 + name: 'Injectable', 181 + } 182 + : undefined, 183 + exportClass: currentClass.root, 184 + name: plugin.config.httpRequest!.classNameBuilder!( 185 + currentClass.className, 186 + ), 187 + nodes: currentClass.nodes, 188 + }); 189 + 190 + file.add(node); 191 + generatedClasses.add(currentClass.className); 192 + }; 193 + 194 + for (const requestClass of requestClasses.values()) { 195 + generateClass(requestClass); 196 + } 197 + }; 198 + 199 + const generateAngularFunctionRequests = ({ 200 + file, 201 + plugin, 202 + }: { 203 + file: any; 204 + plugin: HeyApiAngularCommonPlugin['Instance']; 205 + }) => { 206 + plugin.forEach('operation', ({ operation }) => { 207 + const isRequiredOptions = isOperationOptionsRequired({ 208 + context: plugin.context, 209 + operation, 210 + }); 211 + 212 + // Generate function name with "Request" suffix 213 + const functionName = 214 + plugin.config.httpRequest!.methodNameBuilder!(operation); 215 + 216 + const node = generateAngularRequestFunction({ 217 + file, 218 + functionName, 219 + isRequiredOptions, 220 + operation, 221 + plugin, 222 + }); 223 + 224 + file.add(node); 225 + }); 226 + }; 227 + 228 + const generateRequestCallExpression = ({ 229 + file, 230 + operation, 231 + plugin, 232 + }: { 233 + file: any; 234 + operation: any; 235 + plugin: any; 236 + }) => { 237 + // Import the client and use requestOptions instead of HTTP methods 238 + const clientImport = file.import({ 239 + alias: '_heyApiClient', 240 + module: file.relativePathToFile({ 241 + context: plugin.context, 242 + id: clientId, 243 + }), 244 + name: 'client', 245 + }); 246 + 247 + return tsc.callExpression({ 248 + functionName: tsc.propertyAccessExpression({ 249 + expression: tsc.conditionalExpression({ 250 + condition: tsc.propertyAccessExpression({ 251 + expression: tsc.identifier({ text: 'options' }), 252 + isOptional: true, 253 + name: 'client', 254 + }), 255 + whenFalse: tsc.identifier({ text: clientImport.name }), 256 + whenTrue: tsc.propertyAccessExpression({ 257 + expression: tsc.identifier({ text: 'options' }), 258 + name: 'client', 259 + }), 260 + }), 261 + name: 'requestOptions', 262 + }), 263 + parameters: [ 264 + tsc.objectExpression({ 265 + obj: [ 266 + { 267 + key: 'responseStyle', 268 + value: tsc.identifier({ text: "'data'" }), 269 + }, 270 + { 271 + key: 'method', 272 + value: tsc.identifier({ 273 + text: `'${operation.method.toUpperCase()}'`, 274 + }), 275 + }, 276 + { 277 + key: 'url', 278 + value: tsc.identifier({ text: `'${operation.path}'` }), 279 + }, 280 + { 281 + spread: 'options', 282 + }, 283 + ], 284 + }), 285 + ], 286 + }); 287 + }; 288 + 289 + const generateAngularRequestMethod = ({ 290 + file, 291 + isRequiredOptions, 292 + methodName, 293 + operation, 294 + plugin, 295 + }: { 296 + file: any; 297 + isRequiredOptions: boolean; 298 + methodName: string; 299 + operation: any; 300 + plugin: any; 301 + }) => { 302 + // Import operation data type 303 + const pluginTypeScript = plugin.getPlugin('@hey-api/typescript')!; 304 + const fileTypeScript = plugin.context.file({ id: typesId })!; 305 + const dataType = file.import({ 306 + asType: true, 307 + module: file.relativePathToFile({ context: plugin.context, id: typesId }), 308 + name: fileTypeScript.getName( 309 + pluginTypeScript.api.getId({ operation, type: 'data' }), 310 + ), 311 + }); 312 + 313 + return tsc.methodDeclaration({ 314 + accessLevel: 'public', 315 + comment: createOperationComment({ operation }), 316 + name: methodName, 317 + parameters: [ 318 + { 319 + isRequired: isRequiredOptions, 320 + name: 'options', 321 + type: `Options<${dataType.name || 'unknown'}, ThrowOnError>`, 322 + }, 323 + ], 324 + returnType: 'HttpRequest<unknown>', 325 + statements: [ 326 + tsc.returnStatement({ 327 + expression: generateRequestCallExpression({ 328 + file, 329 + operation, 330 + plugin, 331 + }), 332 + }), 333 + ], 334 + types: [ 335 + { 336 + default: false, 337 + extends: 'boolean', 338 + name: 'ThrowOnError', 339 + }, 340 + ], 341 + }); 342 + }; 343 + 344 + const generateAngularRequestFunction = ({ 345 + file, 346 + functionName, 347 + isRequiredOptions, 348 + operation, 349 + plugin, 350 + }: { 351 + file: any; 352 + functionName: string; 353 + isRequiredOptions: boolean; 354 + operation: any; 355 + plugin: any; 356 + }) => { 357 + const pluginTypeScript = plugin.getPlugin('@hey-api/typescript')!; 358 + const fileTypeScript = plugin.context.file({ id: typesId })!; 359 + const dataType = file.import({ 360 + asType: true, 361 + module: file.relativePathToFile({ context: plugin.context, id: typesId }), 362 + name: fileTypeScript.getName( 363 + pluginTypeScript.api.getId({ operation, type: 'data' }), 364 + ), 365 + }); 366 + 367 + return tsc.constVariable({ 368 + comment: createOperationComment({ operation }), 369 + exportConst: true, 370 + expression: tsc.arrowFunction({ 371 + parameters: [ 372 + { 373 + isRequired: isRequiredOptions, 374 + name: 'options', 375 + type: `Options<${dataType.name || 'unknown'}, ThrowOnError>`, 376 + }, 377 + ], 378 + returnType: 'HttpRequest<unknown>', 379 + statements: [ 380 + tsc.returnStatement({ 381 + expression: generateRequestCallExpression({ 382 + file, 383 + operation, 384 + plugin, 385 + }), 386 + }), 387 + ], 388 + types: [ 389 + { 390 + default: false, 391 + extends: 'boolean', 392 + name: 'ThrowOnError', 393 + }, 394 + ], 395 + }), 396 + name: functionName, 397 + }); 398 + };
+48
packages/openapi-ts/src/plugins/@angular/common/config.ts
···
··· 1 + import { definePluginConfig } from '../../shared/utils/config'; 2 + import { handler } from './plugin'; 3 + import type { HeyApiAngularCommonPlugin } from './types'; 4 + 5 + export const defaultConfig: HeyApiAngularCommonPlugin['Config'] = { 6 + config: {}, 7 + dependencies: ['@hey-api/client-angular', '@hey-api/sdk'], 8 + handler, 9 + name: '@angular/common', 10 + output: '@angular/common', 11 + resolveConfig(plugin) { 12 + plugin.config.httpResource = { 13 + asClass: false, 14 + classNameBuilder(className) { 15 + return className + 'Resources'; 16 + }, 17 + enabled: false, 18 + methodNameBuilder(operation) { 19 + if (plugin.config.httpResource?.asClass) { 20 + return String(operation.id); 21 + } 22 + 23 + return String(operation.id) + 'Resource'; 24 + }, 25 + ...plugin.config.httpResource, 26 + }; 27 + 28 + plugin.config.httpRequest = { 29 + asClass: false, 30 + classNameBuilder(className) { 31 + return className + 'Requests'; 32 + }, 33 + methodNameBuilder(operation) { 34 + if (plugin.config.httpRequest?.asClass) { 35 + return String(operation.id); 36 + } 37 + 38 + return String(operation.id) + 'Request'; 39 + }, 40 + ...plugin.config.httpRequest, 41 + }; 42 + }, 43 + }; 44 + 45 + /** 46 + * Type helper for `@angular/common` plugin, returns {@link Plugin.Config} object 47 + */ 48 + export const defineConfig = definePluginConfig(defaultConfig);
+2
packages/openapi-ts/src/plugins/@angular/common/index.ts
···
··· 1 + export { defaultConfig, defineConfig } from './config'; 2 + export type { HeyApiAngularCommonPlugin as HeyApiAngularResourcePlugin } from './types';
+11
packages/openapi-ts/src/plugins/@angular/common/plugin.ts
···
··· 1 + import { angularHttpRequestsCompanionPluginHandler } from './companions/angularHttpRequestsCompanionPluginHandler'; 2 + import { angularHttpResourceCompanionPluginHandler } from './companions/angularHttpResourceCompanionPluginHandler'; 3 + import type { HeyApiAngularCommonPlugin } from './types'; 4 + 5 + export const handler: HeyApiAngularCommonPlugin['Handler'] = (args) => { 6 + angularHttpRequestsCompanionPluginHandler(args); 7 + 8 + if (args.plugin.config.httpResource?.enabled) { 9 + angularHttpResourceCompanionPluginHandler(args); 10 + } 11 + };
+61
packages/openapi-ts/src/plugins/@angular/common/types.d.ts
···
··· 1 + import type { Operation } from '../../../types/client'; 2 + import type { DefinePlugin, Plugin } from '../../types'; 3 + 4 + export type AngularHttpResourceOptions = { 5 + /** 6 + * Whether to generate the resource as a class. 7 + * @default false 8 + */ 9 + asClass?: boolean; 10 + 11 + /** 12 + * Builds the class name for the generated resource. 13 + * By default, the class name is suffixed with "Resources". 14 + */ 15 + classNameBuilder?: (className: string) => string; 16 + 17 + /** 18 + * Wether or not to create http resource APIs. 19 + */ 20 + enabled: boolean; 21 + 22 + /** 23 + * Builds the method name for the generated resource. 24 + * By default, the operation id is used, if `asClass` is false, the method is also suffixed with "Resource". 25 + */ 26 + methodNameBuilder?: (operation: IR.OperationObject | Operation) => string; 27 + }; 28 + 29 + export type AngularHttpRequestOptions = { 30 + /** 31 + * Whether to generate the resource as a class. 32 + * @default false 33 + */ 34 + asClass?: boolean; 35 + 36 + /** 37 + * Builds the class name for the generated resource. 38 + * By default, the class name is suffixed with "Resources". 39 + */ 40 + classNameBuilder?: (className: string) => string; 41 + 42 + /** 43 + * Builds the method name for the generated resource. 44 + * By default, the operation id is used, if `asClass` is false, the method is also suffixed with "Resource". 45 + */ 46 + methodNameBuilder?: (operation: IR.OperationObject | Operation) => string; 47 + }; 48 + 49 + export type UserConfig = Plugin.Name<'@angular/common'> & { 50 + /** 51 + * Options for generating HTTP Request instances. 52 + */ 53 + httpRequest?: AngularHttpRequestOptions; 54 + 55 + /** 56 + * Options for generating HTTP resource APIs. 57 + */ 58 + httpResource?: AngularHttpResourceOptions; 59 + }; 60 + 61 + export type HeyApiAngularCommonPlugin = DefinePlugin<UserConfig, UserConfig>;
-32
packages/openapi-ts/src/plugins/@hey-api/angular-resource/config.ts
··· 1 - import { definePluginConfig } from '../../shared/utils/config'; 2 - import { angularResourcePluginHandler } from './plugin'; 3 - import type { HeyApiAngularResourcePlugin } from './types'; 4 - 5 - export const defaultConfig: HeyApiAngularResourcePlugin['Config'] = { 6 - config: { 7 - asClass: false, 8 - classNameBuilder(className) { 9 - return className + 'Resources'; 10 - }, 11 - methodNameBuilder(operation) { 12 - if (this.asClass) { 13 - return String(operation.id); 14 - } 15 - 16 - return String(operation.id) + 'Resource'; 17 - }, 18 - }, 19 - dependencies: [ 20 - '@hey-api/client-angular', 21 - '@hey-api/sdk', 22 - '@hey-api/typescript', 23 - ], 24 - handler: angularResourcePluginHandler, 25 - name: '@hey-api/angular-resource', 26 - output: '@hey-api/angular-resource', 27 - }; 28 - 29 - /** 30 - * Type helper for `@hey-api/angular-resource` plugin, returns {@link Plugin.Config} object 31 - */ 32 - export const defineConfig = definePluginConfig(defaultConfig);
···
-2
packages/openapi-ts/src/plugins/@hey-api/angular-resource/index.ts
··· 1 - export { defaultConfig, defineConfig } from './config'; 2 - export type { HeyApiAngularResourcePlugin } from './types';
···
+113 -92
packages/openapi-ts/src/plugins/@hey-api/angular-resource/plugin.ts packages/openapi-ts/src/plugins/@angular/common/companions/angularHttpResourceCompanionPluginHandler.ts
··· 1 - import { tsc } from '../../../tsc'; 2 - import { stringCase } from '../../../utils/stringCase'; 3 import { 4 createOperationComment, 5 isOperationOptionsRequired, 6 - } from '../../shared/utils/operation'; 7 - import { sdkId } from '../sdk/constants'; 8 - import { operationClasses } from '../sdk/operation'; 9 - import { serviceFunctionIdentifier } from '../sdk/plugin-legacy'; 10 - import { typesId } from '../typescript/ref'; 11 - import type { HeyApiAngularResourcePlugin } from './types'; 12 13 - export const angularResourcePluginHandler: HeyApiAngularResourcePlugin['Handler'] = 14 ({ plugin }) => { 15 - // Check if SDK plugin exists 16 const sdkPlugin = plugin.getPlugin('@hey-api/sdk'); 17 18 - if (!sdkPlugin) { 19 - throw new Error( 20 - '@hey-api/sdk plugin is required for @hey-api/angular-resource plugin', 21 - ); 22 - } 23 - 24 - // Create the httpResource file 25 const file = plugin.createFile({ 26 - id: plugin.name, 27 - path: plugin.output, 28 }); 29 30 - // Import Angular core decorators 31 - if (plugin.config.asClass) { 32 file.import({ 33 module: '@angular/core', 34 name: 'Injectable', 35 }); 36 } 37 38 file.import({ 39 - module: '@angular/core', 40 - name: 'resource', 41 }); 42 43 file.import({ ··· 48 name: 'Options', 49 }); 50 51 - if (plugin.config.asClass) { 52 generateAngularClassServices({ file, plugin, sdkPlugin }); 53 } else { 54 generateAngularFunctionServices({ file, plugin, sdkPlugin }); ··· 69 sdkPlugin, 70 }: { 71 file: any; 72 - plugin: HeyApiAngularResourcePlugin['Instance']; 73 sdkPlugin: any; 74 }) => { 75 const serviceClasses = new Map<string, AngularServiceClassEntry>(); ··· 115 const currentClass = serviceClasses.get(currentClassName)!; 116 117 // Generate the resource method name 118 - const resourceMethodName = plugin.config.methodNameBuilder!.call( 119 - plugin.config, 120 - operation, 121 - ); 122 123 // Avoid duplicate methods 124 if (currentClass.methods.has(resourceMethodName)) { ··· 164 initializer: tsc.newExpression({ 165 argumentsArray: [], 166 expression: tsc.identifier({ 167 - text: plugin.config.classNameBuilder!(childClass.className), 168 }), 169 }), 170 name: stringCase({ ··· 188 } 189 : undefined, 190 exportClass: currentClass.root, 191 - name: plugin.config.classNameBuilder!(currentClass.className), 192 nodes: currentClass.nodes, 193 }); 194 ··· 207 sdkPlugin, 208 }: { 209 file: any; 210 - plugin: HeyApiAngularResourcePlugin['Instance']; 211 sdkPlugin: any; 212 }) => { 213 plugin.forEach('operation', ({ operation }) => { ··· 218 219 const node = generateAngularResourceFunction({ 220 file, 221 - functionName: plugin.config.methodNameBuilder!.call( 222 - plugin.config, 223 - operation, 224 - ), 225 isRequiredOptions, 226 operation, 227 plugin, ··· 234 235 const generateResourceCallExpression = ({ 236 file, 237 operation, 238 plugin, 239 sdkPlugin, 240 }: { 241 file: any; 242 operation: any; 243 plugin: any; 244 sdkPlugin: any; 245 }) => { 246 - // Import the SDK function/method instead of recreating the logic 247 - let sdkFunctionCall; 248 249 - if (sdkPlugin.config.asClass) { 250 - // For class-based SDK, use the class methods 251 const classes = operationClasses({ 252 context: plugin.context, 253 operation, 254 plugin: sdkPlugin, 255 }); 256 257 - // Get the first class entry to determine the method path 258 const firstEntry = Array.from(classes.values())[0]; 259 if (firstEntry) { 260 - // Import the root class from SDK 261 const rootClassName = firstEntry.path[0]; 262 - const sdkImport = file.import({ 263 - module: file.relativePathToFile({ 264 - context: plugin.context, 265 - id: sdkId, 266 - }), 267 - name: rootClassName, 268 - }); 269 270 - // Build the method access path 271 - let methodAccess: any = tsc.identifier({ text: sdkImport.name }); 272 273 // Navigate through the class hierarchy 274 for (let i = 1; i < firstEntry.path.length; i++) { ··· 284 } 285 } 286 287 - // Add the final method name 288 methodAccess = tsc.propertyAccessExpression({ 289 expression: methodAccess, 290 - name: firstEntry.methodName, 291 }); 292 293 - sdkFunctionCall = tsc.callExpression({ 294 functionName: methodAccess, 295 - parameters: [tsc.identifier({ text: 'request' })], 296 }); 297 } 298 } else { 299 - // For function-based SDK, import and call the function directly 300 - const functionName = serviceFunctionIdentifier({ 301 - config: plugin.context.config, 302 - handleIllegal: true, 303 - id: operation.id, 304 - operation, 305 - }); 306 307 - const sdkImport = file.import({ 308 module: file.relativePathToFile({ 309 context: plugin.context, 310 - id: sdkId, 311 }), 312 - name: functionName, 313 }); 314 315 - sdkFunctionCall = tsc.callExpression({ 316 - functionName: sdkImport.name, 317 - parameters: [tsc.identifier({ text: 'request' })], 318 }); 319 } 320 321 return tsc.callExpression({ 322 - functionName: 'resource', 323 parameters: [ 324 - tsc.objectExpression({ 325 - obj: [ 326 - { 327 - key: 'loader', 328 - value: tsc.arrowFunction({ 329 - async: true, 330 - parameters: [ 331 - { 332 - destructure: [{ name: 'request' }], 333 - type: undefined, 334 - }, 335 - ], 336 - statements: [ 337 - tsc.returnStatement({ 338 - expression: sdkFunctionCall, 339 - }), 340 - ], 341 - }), 342 - }, 343 - { 344 - key: 'request', 345 - value: tsc.identifier({ text: 'options' }), 346 - }, 347 ], 348 }), 349 ], 350 }); 351 }; 352 ··· 376 ), 377 }); 378 379 return tsc.methodDeclaration({ 380 accessLevel: 'public', 381 comment: createOperationComment({ operation }), ··· 393 tsc.returnStatement({ 394 expression: generateResourceCallExpression({ 395 file, 396 operation, 397 plugin, 398 sdkPlugin, 399 }), 400 }), ··· 434 ), 435 }); 436 437 return tsc.constVariable({ 438 comment: createOperationComment({ operation }), 439 exportConst: true, ··· 449 tsc.returnStatement({ 450 expression: generateResourceCallExpression({ 451 file, 452 operation, 453 plugin, 454 sdkPlugin, 455 }), 456 }),
··· 1 + import { tsc } from '../../../../tsc'; 2 + import { stringCase } from '../../../../utils/stringCase'; 3 + import { sdkId } from '../../../@hey-api/sdk/constants'; 4 + import { operationClasses } from '../../../@hey-api/sdk/operation'; 5 + import { typesId } from '../../../@hey-api/typescript/ref'; 6 import { 7 createOperationComment, 8 isOperationOptionsRequired, 9 + } from '../../../shared/utils/operation'; 10 + import type { HeyApiAngularCommonPlugin } from '../types'; 11 + import { REQUEST_APIS_SUFFIX } from './angularHttpRequestsCompanionPluginHandler'; 12 13 + export const RESOURCE_APIS_SUFFIX = '/http/httpResource'; 14 + 15 + export const angularHttpResourceCompanionPluginHandler: HeyApiAngularCommonPlugin['Handler'] = 16 ({ plugin }) => { 17 const sdkPlugin = plugin.getPlugin('@hey-api/sdk'); 18 19 const file = plugin.createFile({ 20 + exportFromIndex: true, 21 + id: plugin.name + RESOURCE_APIS_SUFFIX, 22 + path: plugin.output + RESOURCE_APIS_SUFFIX, 23 }); 24 25 + if (plugin.config.httpResource?.asClass) { 26 file.import({ 27 module: '@angular/core', 28 name: 'Injectable', 29 }); 30 } 31 32 + if (plugin.config.httpRequest?.asClass) { 33 + file.import({ 34 + module: '@angular/core', 35 + name: 'inject', 36 + }); 37 + } 38 + 39 file.import({ 40 + module: '@angular/common/http', 41 + name: 'httpResource', 42 }); 43 44 file.import({ ··· 49 name: 'Options', 50 }); 51 52 + if (plugin.config.httpResource!.asClass) { 53 generateAngularClassServices({ file, plugin, sdkPlugin }); 54 } else { 55 generateAngularFunctionServices({ file, plugin, sdkPlugin }); ··· 70 sdkPlugin, 71 }: { 72 file: any; 73 + plugin: HeyApiAngularCommonPlugin['Instance']; 74 sdkPlugin: any; 75 }) => { 76 const serviceClasses = new Map<string, AngularServiceClassEntry>(); ··· 116 const currentClass = serviceClasses.get(currentClassName)!; 117 118 // Generate the resource method name 119 + const resourceMethodName = 120 + plugin.config.httpResource!.methodNameBuilder!(operation); 121 122 // Avoid duplicate methods 123 if (currentClass.methods.has(resourceMethodName)) { ··· 163 initializer: tsc.newExpression({ 164 argumentsArray: [], 165 expression: tsc.identifier({ 166 + text: plugin.config.httpResource!.classNameBuilder!( 167 + childClass.className, 168 + ), 169 }), 170 }), 171 name: stringCase({ ··· 189 } 190 : undefined, 191 exportClass: currentClass.root, 192 + name: plugin.config.httpResource!.classNameBuilder!( 193 + currentClass.className, 194 + ), 195 nodes: currentClass.nodes, 196 }); 197 ··· 210 sdkPlugin, 211 }: { 212 file: any; 213 + plugin: HeyApiAngularCommonPlugin['Instance']; 214 sdkPlugin: any; 215 }) => { 216 plugin.forEach('operation', ({ operation }) => { ··· 221 222 const node = generateAngularResourceFunction({ 223 file, 224 + functionName: plugin.config.httpResource!.methodNameBuilder!(operation), 225 isRequiredOptions, 226 operation, 227 plugin, ··· 234 235 const generateResourceCallExpression = ({ 236 file, 237 + isRequiredOptions, 238 operation, 239 plugin, 240 + responseTypeName, 241 sdkPlugin, 242 }: { 243 file: any; 244 + isRequiredOptions: boolean; 245 operation: any; 246 plugin: any; 247 + responseTypeName: string; 248 sdkPlugin: any; 249 }) => { 250 + // Check if httpRequest is configured to use classes 251 + const useRequestClasses = plugin.config.httpRequest?.asClass; 252 + let requestFunctionCall; 253 + 254 + // Create the options call expression based on whether options are required 255 + const optionsCallExpression = isRequiredOptions 256 + ? tsc.callExpression({ 257 + functionName: 'options', 258 + parameters: [], 259 + }) 260 + : tsc.conditionalExpression({ 261 + condition: tsc.identifier({ text: 'options' }), 262 + whenFalse: tsc.identifier({ text: 'undefined' }), 263 + whenTrue: tsc.callExpression({ 264 + functionName: 'options', 265 + parameters: [], 266 + }), 267 + }); 268 269 + if (useRequestClasses) { 270 + // For class-based request methods, use inject and class hierarchy 271 const classes = operationClasses({ 272 context: plugin.context, 273 operation, 274 plugin: sdkPlugin, 275 }); 276 277 const firstEntry = Array.from(classes.values())[0]; 278 if (firstEntry) { 279 + // Import the root class from HTTP requests 280 const rootClassName = firstEntry.path[0]; 281 + const requestClassName = 282 + plugin.config.httpRequest!.classNameBuilder!(rootClassName); 283 284 + // Build the method access path using inject 285 + let methodAccess: any = tsc.callExpression({ 286 + functionName: 'inject', 287 + parameters: [tsc.identifier({ text: requestClassName })], 288 + }); 289 290 // Navigate through the class hierarchy 291 for (let i = 1; i < firstEntry.path.length; i++) { ··· 301 } 302 } 303 304 + // Add the final method name with "Request" suffix 305 + const requestMethodName = 306 + plugin.config.httpRequest!.methodNameBuilder!(operation); 307 methodAccess = tsc.propertyAccessExpression({ 308 expression: methodAccess, 309 + name: requestMethodName, 310 }); 311 312 + requestFunctionCall = tsc.callExpression({ 313 functionName: methodAccess, 314 + parameters: [optionsCallExpression], 315 }); 316 } 317 } else { 318 + // For function-based request methods, import and call the function directly 319 + const requestFunctionName = 320 + plugin.config.httpRequest!.methodNameBuilder!(operation); 321 322 + const requestImport = file.import({ 323 module: file.relativePathToFile({ 324 context: plugin.context, 325 + id: plugin.name + REQUEST_APIS_SUFFIX, 326 }), 327 + name: requestFunctionName, 328 }); 329 330 + requestFunctionCall = tsc.callExpression({ 331 + functionName: requestImport.name, 332 + parameters: [optionsCallExpression], 333 }); 334 } 335 336 return tsc.callExpression({ 337 + functionName: 'httpResource', 338 parameters: [ 339 + tsc.arrowFunction({ 340 + parameters: [], 341 + statements: [ 342 + tsc.returnStatement({ 343 + expression: requestFunctionCall, 344 + }), 345 ], 346 }), 347 ], 348 + types: [tsc.typeNode(responseTypeName)], 349 }); 350 }; 351 ··· 375 ), 376 }); 377 378 + // Import operation response type 379 + const responseType = file.import({ 380 + asType: true, 381 + module: file.relativePathToFile({ context: plugin.context, id: typesId }), 382 + name: fileTypeScript.getName( 383 + pluginTypeScript.api.getId({ operation, type: 'response' }), 384 + ), 385 + }); 386 + 387 return tsc.methodDeclaration({ 388 accessLevel: 'public', 389 comment: createOperationComment({ operation }), ··· 401 tsc.returnStatement({ 402 expression: generateResourceCallExpression({ 403 file, 404 + isRequiredOptions, 405 operation, 406 plugin, 407 + responseTypeName: responseType.name || 'unknown', 408 sdkPlugin, 409 }), 410 }), ··· 444 ), 445 }); 446 447 + // Import operation response type 448 + const responseType = file.import({ 449 + asType: true, 450 + module: file.relativePathToFile({ context: plugin.context, id: typesId }), 451 + name: fileTypeScript.getName( 452 + pluginTypeScript.api.getId({ operation, type: 'response' }), 453 + ), 454 + }); 455 + 456 return tsc.constVariable({ 457 comment: createOperationComment({ operation }), 458 exportConst: true, ··· 468 tsc.returnStatement({ 469 expression: generateResourceCallExpression({ 470 file, 471 + isRequiredOptions, 472 operation, 473 plugin, 474 + responseTypeName: responseType.name || 'unknown', 475 sdkPlugin, 476 }), 477 }),
-49
packages/openapi-ts/src/plugins/@hey-api/angular-resource/types.d.ts
··· 1 - import type { Operation } from '../../../types/client'; 2 - import type { DefinePlugin, Plugin } from '../../types'; 3 - 4 - export type UserConfig = Plugin.Name<'@hey-api/angular-resource'> & { 5 - /** 6 - * Whether to generate the resource as a class. 7 - * @default false 8 - */ 9 - asClass?: boolean; 10 - 11 - /** 12 - * Builds the class name for the generated resource. 13 - * By default, the class name is suffixed with "Resources". 14 - */ 15 - classNameBuilder?: (className: string) => string; 16 - 17 - /** 18 - * Builds the method name for the generated resource. 19 - * By default, the operation id is used, if `asClass` is false, the method is also suffixed with "Resource". 20 - */ 21 - methodNameBuilder?: (operation: IR.OperationObject | Operation) => string; 22 - 23 - /** 24 - * Name of the generated file. 25 - * 26 - * @default 'httpResource' 27 - */ 28 - output?: string; 29 - }; 30 - 31 - export type Config = Plugin.Name<'@hey-api/angular-resource'> & { 32 - /** 33 - * Whether to generate the resource as a class. 34 - * @default false 35 - */ 36 - asClass: boolean; 37 - 38 - classNameBuilder?: (className: string) => string; 39 - methodNameBuilder?: (operation: IR.OperationObject | Operation) => string; 40 - 41 - /** 42 - * Name of the generated file. 43 - * 44 - * @default 'httpResource' 45 - */ 46 - output?: string; 47 - }; 48 - 49 - export type HeyApiAngularResourcePlugin = DefinePlugin<UserConfig, Config>;
···
+54 -21
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/client.ts
··· 14 import { firstValueFrom } from 'rxjs'; 15 import { filter } from 'rxjs/operators'; 16 17 - import type { Client, Config, ResolvedRequestOptions } from './types'; 18 import { 19 buildUrl, 20 createConfig, ··· 48 ResolvedRequestOptions 49 >(); 50 51 - const request: Client['request'] = async (options) => { 52 const opts = { 53 ..._config, 54 ...options, 55 headers: mergeHeaders(_config.headers, options.headers), 56 httpClient: options.httpClient ?? _config.httpClient, 57 serializedBody: options.body as any, 58 }; 59 ··· 63 inject(HttpClient), 64 ); 65 } else { 66 - assertInInjectionContext(request); 67 opts.httpClient = inject(HttpClient); 68 } 69 } 70 71 - if (opts.security) { 72 - await setAuthParams({ 73 - ...opts, 74 - security: opts.security, 75 - }); 76 - } 77 - 78 - if (opts.requestValidator) { 79 - await opts.requestValidator(opts); 80 - } 81 - 82 if (opts.body && opts.bodySerializer) { 83 opts.serializedBody = opts.bodySerializer(opts.body); 84 } ··· 88 opts.headers.delete('Content-Type'); 89 } 90 91 - const url = buildUrl(opts); 92 93 - let req = new HttpRequest<unknown>( 94 opts.method, 95 url, 96 opts.serializedBody || null, ··· 100 }, 101 ); 102 103 for (const fn of interceptors.request._fns) { 104 if (fn) { 105 - req = await fn(req, opts); 106 } 107 } 108 ··· 114 115 try { 116 response = await firstValueFrom( 117 - opts.httpClient 118 - .request(req) 119 .pipe(filter((event) => event.type === HttpEventType.Response)), 120 ); 121 122 for (const fn of interceptors.response._fns) { 123 if (fn) { 124 - response = await fn(response, req, opts); 125 } 126 } 127 ··· 151 finalError, 152 response as HttpResponse<unknown>, 153 req, 154 - opts, 155 )) as string; 156 } 157 } ··· 182 post: (options) => request({ ...options, method: 'POST' }), 183 put: (options) => request({ ...options, method: 'PUT' }), 184 request, 185 setConfig, 186 trace: (options) => request({ ...options, method: 'TRACE' }), 187 };
··· 14 import { firstValueFrom } from 'rxjs'; 15 import { filter } from 'rxjs/operators'; 16 17 + import type { 18 + Client, 19 + Config, 20 + RequestOptions, 21 + ResolvedRequestOptions, 22 + ResponseStyle, 23 + } from './types'; 24 import { 25 buildUrl, 26 createConfig, ··· 54 ResolvedRequestOptions 55 >(); 56 57 + const requestOptions = < 58 + ThrowOnError extends boolean = false, 59 + TResponseStyle extends ResponseStyle = 'fields', 60 + >( 61 + options: RequestOptions<TResponseStyle, ThrowOnError>, 62 + ) => { 63 const opts = { 64 ..._config, 65 ...options, 66 headers: mergeHeaders(_config.headers, options.headers), 67 httpClient: options.httpClient ?? _config.httpClient, 68 + method: 'GET', 69 serializedBody: options.body as any, 70 }; 71 ··· 75 inject(HttpClient), 76 ); 77 } else { 78 + assertInInjectionContext(requestOptions); 79 opts.httpClient = inject(HttpClient); 80 } 81 } 82 83 if (opts.body && opts.bodySerializer) { 84 opts.serializedBody = opts.bodySerializer(opts.body); 85 } ··· 89 opts.headers.delete('Content-Type'); 90 } 91 92 + const url = buildUrl(opts as any); 93 94 + const req = new HttpRequest<unknown>( 95 opts.method, 96 url, 97 opts.serializedBody || null, ··· 101 }, 102 ); 103 104 + return { opts, req }; 105 + }; 106 + 107 + const request: Client['request'] = async (options) => { 108 + const { opts, req: initialReq } = requestOptions(options); 109 + 110 + if (opts.security) { 111 + await setAuthParams({ 112 + ...opts, 113 + security: opts.security, 114 + }); 115 + } 116 + 117 + if (opts.requestValidator) { 118 + await opts.requestValidator(opts); 119 + } 120 + 121 + let req = initialReq; 122 + 123 for (const fn of interceptors.request._fns) { 124 if (fn) { 125 + req = await fn(req, opts as any); 126 } 127 } 128 ··· 134 135 try { 136 response = await firstValueFrom( 137 + opts 138 + .httpClient!.request(req) 139 .pipe(filter((event) => event.type === HttpEventType.Response)), 140 ); 141 142 for (const fn of interceptors.response._fns) { 143 if (fn) { 144 + response = await fn(response, req, opts as any); 145 } 146 } 147 ··· 171 finalError, 172 response as HttpResponse<unknown>, 173 req, 174 + opts as any, 175 )) as string; 176 } 177 } ··· 202 post: (options) => request({ ...options, method: 'POST' }), 203 put: (options) => request({ ...options, method: 'PUT' }), 204 request, 205 + requestOptions: (options) => { 206 + if (options.security) { 207 + throw new Error('Security is not supported in requestOptions'); 208 + } 209 + 210 + if (options.requestValidator) { 211 + throw new Error( 212 + 'Request validation is not supported in requestOptions', 213 + ); 214 + } 215 + 216 + return requestOptions(options).req; 217 + }, 218 setConfig, 219 trace: (options) => request({ ...options, method: 'TRACE' }), 220 };
+9
packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/types.ts
··· 161 Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 164 type BuildUrlFn = < 165 TData extends { 166 body?: unknown; ··· 179 unknown, 180 ResolvedRequestOptions 181 >; 182 }; 183 184 /**
··· 161 Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 164 + type RequestOptionsFn = < 165 + ThrowOnError extends boolean = false, 166 + TResponseStyle extends ResponseStyle = 'fields', 167 + >( 168 + options: RequestOptions<TResponseStyle, ThrowOnError>, 169 + ) => HttpRequest<unknown>; 170 + 171 type BuildUrlFn = < 172 TData extends { 173 body?: unknown; ··· 186 unknown, 187 ResolvedRequestOptions 188 >; 189 + 190 + requestOptions: RequestOptionsFn; 191 }; 192 193 /**
+4 -4
packages/openapi-ts/src/plugins/config.ts
··· 1 - import type { HeyApiAngularResourcePlugin } from './@hey-api/angular-resource'; 2 - import { defaultConfig as heyApiAngularResource } from './@hey-api/angular-resource'; 3 import type { HeyApiClientAngularPlugin } from './@hey-api/client-angular'; 4 import { defaultConfig as heyApiClientAngular } from './@hey-api/client-angular'; 5 import type { HeyApiClientAxiosPlugin } from './@hey-api/client-axios'; ··· 47 import { defaultConfig as zod } from './zod'; 48 49 export interface PluginConfigMap { 50 - '@hey-api/angular-resource': HeyApiAngularResourcePlugin['Types']; 51 '@hey-api/client-angular': HeyApiClientAngularPlugin['Types']; 52 '@hey-api/client-axios': HeyApiClientAxiosPlugin['Types']; 53 '@hey-api/client-fetch': HeyApiClientFetchPlugin['Types']; ··· 75 export const defaultPluginConfigs: { 76 [K in PluginNames]: Plugin.Config<PluginConfigMap[K]>; 77 } = { 78 - '@hey-api/angular-resource': heyApiAngularResource, 79 '@hey-api/client-angular': heyApiClientAngular, 80 '@hey-api/client-axios': heyApiClientAxios, 81 '@hey-api/client-fetch': heyApiClientFetch,
··· 1 + import type { HeyApiAngularResourcePlugin } from './@angular/common'; 2 + import { defaultConfig as heyApiAngularResource } from './@angular/common'; 3 import type { HeyApiClientAngularPlugin } from './@hey-api/client-angular'; 4 import { defaultConfig as heyApiClientAngular } from './@hey-api/client-angular'; 5 import type { HeyApiClientAxiosPlugin } from './@hey-api/client-axios'; ··· 47 import { defaultConfig as zod } from './zod'; 48 49 export interface PluginConfigMap { 50 + '@angular/common': HeyApiAngularResourcePlugin['Types']; 51 '@hey-api/client-angular': HeyApiClientAngularPlugin['Types']; 52 '@hey-api/client-axios': HeyApiClientAxiosPlugin['Types']; 53 '@hey-api/client-fetch': HeyApiClientFetchPlugin['Types']; ··· 75 export const defaultPluginConfigs: { 76 [K in PluginNames]: Plugin.Config<PluginConfigMap[K]>; 77 } = { 78 + '@angular/common': heyApiAngularResource, 79 '@hey-api/client-angular': heyApiClientAngular, 80 '@hey-api/client-axios': heyApiClientAxios, 81 '@hey-api/client-fetch': heyApiClientFetch,
+1 -1
packages/openapi-ts/src/plugins/types.d.ts
··· 21 22 export type PluginNames = 23 | PluginClientNames 24 - | '@hey-api/angular-resource' 25 | '@hey-api/schemas' 26 | '@hey-api/sdk' 27 | '@hey-api/transformers'
··· 21 22 export type PluginNames = 23 | PluginClientNames 24 + | '@angular/common' 25 | '@hey-api/schemas' 26 | '@hey-api/sdk' 27 | '@hey-api/transformers'