diff --git a/examples/openapi-ts-angular/openapi-ts.config.ts b/examples/openapi-ts-angular/openapi-ts.config.ts
index e5bc5731b..63107e271 100644
--- a/examples/openapi-ts-angular/openapi-ts.config.ts
+++ b/examples/openapi-ts-angular/openapi-ts.config.ts
@@ -9,26 +9,15 @@ export default defineConfig({
path: './src/client',
},
plugins: [
- '@hey-api/client-angular',
- '@hey-api/schemas',
{
- asClass: false,
- classNameBuilder(name) {
- return `${name}Service`;
- },
- methodNameBuilder(operation) {
- return String(operation.id);
- },
- name: '@hey-api/sdk',
+ name: '@hey-api/client-angular',
+ // throwOnError: true,
},
+ '@hey-api/schemas',
+ '@hey-api/sdk',
{
enums: 'javascript',
name: '@hey-api/typescript',
},
- '@tanstack/angular-query-experimental',
- {
- asClass: true,
- name: '@hey-api/angular-resource',
- },
],
});
diff --git a/examples/openapi-ts-angular/src/app/app.component.html b/examples/openapi-ts-angular/src/app/app.component.html
index c47306d50..82637a546 100644
--- a/examples/openapi-ts-angular/src/app/app.component.html
+++ b/examples/openapi-ts-angular/src/app/app.component.html
@@ -243,8 +243,9 @@
-
@hey-api/openapi-ts 🤝 Angular
-
+ {{ title }}
+
+
diff --git a/examples/openapi-ts-angular/src/app/app.component.ts b/examples/openapi-ts-angular/src/app/app.component.ts
index 69213d755..e5dfbe536 100644
--- a/examples/openapi-ts-angular/src/app/app.component.ts
+++ b/examples/openapi-ts-angular/src/app/app.component.ts
@@ -1,48 +1,15 @@
-import { HttpClient } from '@angular/common/http';
-import { Component, inject } from '@angular/core';
+import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
-import { getPetById } from '../client';
-import { createClient } from '../client/client';
-
-const localClient = createClient({
- // set default base url for requests made by this client
- baseUrl: 'https://petstore3.swagger.io/api/v3',
- /**
- * Set default headers only for requests made by this client. This is to
- * demonstrate local clients and their configuration taking precedence over
- * internal service client.
- */
- headers: {
- Authorization: 'Bearer
',
- },
-});
+import { Demo } from './demo/demo';
@Component({
- imports: [RouterOutlet],
+ host: { ngSkipHydration: 'true' },
+ imports: [RouterOutlet, Demo],
selector: 'app-root',
styleUrl: './app.component.css',
templateUrl: './app.component.html',
})
export class AppComponent {
- title = 'angular';
-
- private http = inject(HttpClient);
-
- async onGetPetById() {
- this.http.get('', {});
- const { data, error } = await getPetById({
- client: localClient,
- path: {
- // random id 1-10
- petId: Math.floor(Math.random() * (10 - 1 + 1) + 1),
- },
- });
- if (error) {
- console.log(error);
- return;
- }
- console.log(data);
- // setPet(data!);
- }
+ title = '@hey-api/openapi-ts 🤝 Angular';
}
diff --git a/examples/openapi-ts-angular/src/app/app.config.ts b/examples/openapi-ts-angular/src/app/app.config.ts
index 117bab5e1..47397ce0d 100644
--- a/examples/openapi-ts-angular/src/app/app.config.ts
+++ b/examples/openapi-ts-angular/src/app/app.config.ts
@@ -7,6 +7,8 @@ import {
} from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
+import { client } from '../client/client.gen';
+import { provideHeyApiClient } from '../client/client/client.gen';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
@@ -15,5 +17,6 @@ export const appConfig: ApplicationConfig = {
provideRouter(routes),
provideClientHydration(withEventReplay()),
provideHttpClient(withFetch()),
+ provideHeyApiClient(client),
],
};
diff --git a/examples/openapi-ts-angular/src/app/demo/demo.css b/examples/openapi-ts-angular/src/app/demo/demo.css
new file mode 100644
index 000000000..dd26f3c80
--- /dev/null
+++ b/examples/openapi-ts-angular/src/app/demo/demo.css
@@ -0,0 +1,156 @@
+/* Pet Card Styles */
+.pet-card {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 16px;
+ padding: 1.5rem;
+ margin-top: 1.5rem;
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ transition:
+ transform 120ms ease,
+ box-shadow 120ms ease;
+}
+
+.pet-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.pet-avatar {
+ width: 64px;
+ height: 64px;
+ border-radius: 12px;
+ overflow: hidden;
+ flex-shrink: 0;
+ background: var(--red-to-pink-to-purple-horizontal-gradient);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.pet-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.pet-placeholder {
+ color: white;
+ font-size: 1.5rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.pet-details {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.pet-name {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: var(--gray-900);
+ margin: 0;
+}
+
+.pet-category {
+ font-size: 0.875rem;
+ color: var(--gray-700);
+ background: rgba(255, 65, 248, 0.1);
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ display: inline-block;
+ width: fit-content;
+}
+
+/* Error Message Styles */
+.error-message {
+ background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
+ border: 1px solid #fecaca;
+ border-radius: 12px;
+ padding: 1rem;
+ margin-top: 1.5rem;
+ box-shadow: 0 2px 4px rgba(239, 68, 68, 0.1);
+}
+
+.error-title {
+ font-size: 1rem;
+ font-weight: 600;
+ color: #dc2626;
+ margin-bottom: 0.5rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.error-title::before {
+ content: '⚠️';
+ font-size: 1.125rem;
+}
+
+.error-details {
+ font-size: 0.875rem;
+ color: #991b1b;
+ background: rgba(239, 68, 68, 0.05);
+ padding: 0.75rem;
+ border-radius: 8px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ white-space: pre-wrap;
+ word-break: break-word;
+ border-left: 3px solid #ef4444;
+}
+
+/* Button Styles Enhancement */
+button {
+ background: var(--red-to-pink-to-purple-horizontal-gradient);
+ color: white;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 120ms ease;
+ margin-top: 1rem;
+ margin-right: 0.5rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+button:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+button:active {
+ transform: translateY(0);
+}
+
+/* Responsive adjustments */
+@media screen and (max-width: 650px) {
+ .pet-card {
+ padding: 1rem;
+ margin-top: 1rem;
+ }
+
+ .pet-info {
+ flex-direction: column;
+ text-align: center;
+ gap: 1rem;
+ }
+
+ .pet-avatar {
+ width: 80px;
+ height: 80px;
+ align-self: center;
+ }
+
+ .error-message {
+ padding: 0.75rem;
+ margin-top: 1rem;
+ }
+}
diff --git a/examples/openapi-ts-angular/src/app/demo/demo.html b/examples/openapi-ts-angular/src/app/demo/demo.html
new file mode 100644
index 000000000..dadb12d97
--- /dev/null
+++ b/examples/openapi-ts-angular/src/app/demo/demo.html
@@ -0,0 +1,37 @@
+
+
+
+
+@if (error()) {
+
+
Error occurred:
+
{{ error()?.error }}
+
+}
+
+
+@if (pet()) {
+
+
+
+ @if (pet()?.photoUrls?.[0]) {
+
![]()
+ } @else {
+
{{ pet()?.name?.slice(0, 1) || 'N' }}
+ }
+
+
+
Name: {{ pet()?.name || 'N/A' }}
+
+ Category: {{ pet()?.category?.name || 'N/A' }}
+
+
+
+
+}
diff --git a/examples/openapi-ts-angular/src/app/demo/demo.ts b/examples/openapi-ts-angular/src/app/demo/demo.ts
new file mode 100644
index 000000000..7497f82e7
--- /dev/null
+++ b/examples/openapi-ts-angular/src/app/demo/demo.ts
@@ -0,0 +1,95 @@
+import { JsonPipe } from '@angular/common';
+import type { HttpErrorResponse } from '@angular/common/http';
+import { HttpClient } from '@angular/common/http';
+import { Component, inject, signal } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+import type { AddPetErrors, Pet } from '../../client';
+import { getPetById } from '../../client';
+import { createClient } from '../../client/client';
+
+const localClient = createClient({
+ // set default base url for requests made by this client
+ baseUrl: 'https://petstore3.swagger.io/api/v3',
+ /**
+ * Set default headers only for requests made by this client. This is to
+ * demonstrate local clients and their configuration taking precedence over
+ * internal service client.
+ */
+ headers: {
+ Authorization: 'Bearer ',
+ },
+});
+
+@Component({
+ host: { ngSkipHydration: 'true' },
+ imports: [RouterOutlet, JsonPipe],
+ selector: 'app-demo',
+ styleUrl: './demo.css',
+ templateUrl: './demo.html',
+})
+export class Demo {
+ pet = signal(undefined);
+ error = signal<
+ | undefined
+ | {
+ error: AddPetErrors[keyof AddPetErrors] | Error;
+ response: HttpErrorResponse;
+ }
+ >(undefined);
+
+ #http = inject(HttpClient);
+
+ // // you can set a global httpClient for this client like so, in your app.config, or on each request (like below)
+ // ngOnInit(): void {
+ // localClient.setConfig({
+ // httpClient: this.#http,
+ // });
+ // }
+
+ onGetPetByIdLocalClient = async () => {
+ const { data, error, response } = await getPetById({
+ client: localClient,
+ httpClient: this.#http,
+ path: {
+ // random id 1-10
+ petId: Math.floor(Math.random() * (10 - 1 + 1) + 1),
+ },
+ });
+
+ if (error) {
+ console.log(error);
+ this.error.set({
+ error,
+ response: response as HttpErrorResponse,
+ });
+ return;
+ }
+
+ this.pet.set(data);
+ this.error.set(undefined);
+ };
+
+ onGetPetById = async () => {
+ const { data, error, response } = await getPetById({
+ path: {
+ // random id 1-10
+ petId: Math.floor(Math.random() * (10 - 1 + 1) + 1),
+ },
+ });
+
+ if (error) {
+ console.log(error);
+ this.error.set({
+ error,
+ response: response as HttpErrorResponse,
+ });
+ return;
+ } else {
+ console.log(error);
+ console.log(response);
+ this.pet.set(data);
+ this.error.set(undefined);
+ }
+ };
+}
diff --git a/examples/openapi-ts-angular/src/client/@hey-api/angular-resource.gen.ts b/examples/openapi-ts-angular/src/client/@hey-api/angular-resource.gen.ts
deleted file mode 100644
index 5a96a8d9d..000000000
--- a/examples/openapi-ts-angular/src/client/@hey-api/angular-resource.gen.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-import { Injectable, resource } from '@angular/core';
-
-import type { Options } from '../sdk.gen';
-import {
- addPet,
- createUser,
- createUsersWithListInput,
- deleteOrder,
- deletePet,
- deleteUser,
- findPetsByStatus,
- findPetsByTags,
- getInventory,
- getOrderById,
- getPetById,
- getUserByName,
- loginUser,
- logoutUser,
- placeOrder,
- updatePet,
- updatePetWithForm,
- updateUser,
- uploadFile,
-} from '../sdk.gen';
-import type {
- AddPetData,
- CreateUserData,
- CreateUsersWithListInputData,
- DeleteOrderData,
- DeletePetData,
- DeleteUserData,
- FindPetsByStatusData,
- FindPetsByTagsData,
- GetInventoryData,
- GetOrderByIdData,
- GetPetByIdData,
- GetUserByNameData,
- LoginUserData,
- LogoutUserData,
- PlaceOrderData,
- UpdatePetData,
- UpdatePetWithFormData,
- UpdateUserData,
- UploadFileData,
-} from '../types.gen';
-
-@Injectable({
- providedIn: 'root',
-})
-export class PetServiceResources {
- /**
- * Add a new pet to the store.
- * Add a new pet to the store.
- */
- public addPetResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => addPet(params),
- params: () => options,
- });
- }
-
- /**
- * Update an existing pet.
- * Update an existing pet by Id.
- */
- public updatePetResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => updatePet(params),
- params: () => options,
- });
- }
-
- /**
- * Finds Pets by status.
- * Multiple status values can be provided with comma separated strings.
- */
- public findPetsByStatusResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => findPetsByStatus(params),
- params: () => options,
- });
- }
-
- /**
- * Finds Pets by tags.
- * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
- */
- public findPetsByTagsResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => findPetsByTags(params),
- params: () => options,
- });
- }
-
- /**
- * Deletes a pet.
- * Delete a pet.
- */
- public deletePetResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => deletePet(params),
- params: () => options,
- });
- }
-
- /**
- * Find pet by ID.
- * Returns a single pet.
- */
- public getPetByIdResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => getPetById(params),
- params: () => options,
- });
- }
-
- /**
- * Updates a pet in the store with form data.
- * Updates a pet resource based on the form data.
- */
- public updatePetWithFormResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => updatePetWithForm(params),
- params: () => options,
- });
- }
-
- /**
- * Uploads an image.
- * Upload image of the pet.
- */
- public uploadFileResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => uploadFile(params),
- params: () => options,
- });
- }
-}
-
-@Injectable({
- providedIn: 'root',
-})
-export class StoreServiceResources {
- /**
- * Returns pet inventories by status.
- * Returns a map of status codes to quantities.
- */
- public getInventoryResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => getInventory(params),
- params: () => options,
- });
- }
-
- /**
- * Place an order for a pet.
- * Place a new order in the store.
- */
- public placeOrderResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => placeOrder(params),
- params: () => options,
- });
- }
-
- /**
- * Delete purchase order by identifier.
- * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors.
- */
- public deleteOrderResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => deleteOrder(params),
- params: () => options,
- });
- }
-
- /**
- * Find purchase order by ID.
- * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
- */
- public getOrderByIdResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => getOrderById(params),
- params: () => options,
- });
- }
-}
-
-@Injectable({
- providedIn: 'root',
-})
-export class UserServiceResources {
- /**
- * Create user.
- * This can only be done by the logged in user.
- */
- public createUserResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => createUser(params),
- params: () => options,
- });
- }
-
- /**
- * Creates list of users with given input array.
- * Creates list of users with given input array.
- */
- public createUsersWithListInputResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => createUsersWithListInput(params),
- params: () => options,
- });
- }
-
- /**
- * Logs user into the system.
- * Log into the system.
- */
- public loginUserResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => loginUser(params),
- params: () => options,
- });
- }
-
- /**
- * Logs out current logged in user session.
- * Log user out of the system.
- */
- public logoutUserResource(
- options?: Options,
- ) {
- return resource({
- loader: async ({ params }) => logoutUser(params),
- params: () => options,
- });
- }
-
- /**
- * Delete user resource.
- * This can only be done by the logged in user.
- */
- public deleteUserResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => deleteUser(params),
- params: () => options,
- });
- }
-
- /**
- * Get user by user name.
- * Get user detail based on username.
- */
- public getUserByNameResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => getUserByName(params),
- params: () => options,
- });
- }
-
- /**
- * Update user resource.
- * This can only be done by the logged in user.
- */
- public updateUserResource(
- options: Options,
- ) {
- return resource({
- loader: async ({ params }) => updateUser(params),
- params: () => options,
- });
- }
-}
diff --git a/examples/openapi-ts-angular/src/client/@tanstack/angular-query-experimental.gen.ts b/examples/openapi-ts-angular/src/client/@tanstack/angular-query-experimental.gen.ts
deleted file mode 100644
index 890b38180..000000000
--- a/examples/openapi-ts-angular/src/client/@tanstack/angular-query-experimental.gen.ts
+++ /dev/null
@@ -1,687 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-import {
- type DefaultError,
- type MutationOptions,
- queryOptions,
-} from '@tanstack/angular-query-experimental';
-
-import { client as _heyApiClient } from '../client.gen';
-import {
- addPet,
- createUser,
- createUsersWithListInput,
- deleteOrder,
- deletePet,
- deleteUser,
- findPetsByStatus,
- findPetsByTags,
- getInventory,
- getOrderById,
- getPetById,
- getUserByName,
- loginUser,
- logoutUser,
- type Options,
- placeOrder,
- updatePet,
- updatePetWithForm,
- updateUser,
- uploadFile,
-} from '../sdk.gen';
-import type {
- AddPetData,
- AddPetResponse,
- CreateUserData,
- CreateUserResponse,
- CreateUsersWithListInputData,
- CreateUsersWithListInputResponse,
- DeleteOrderData,
- DeletePetData,
- DeleteUserData,
- FindPetsByStatusData,
- FindPetsByTagsData,
- GetInventoryData,
- GetOrderByIdData,
- GetPetByIdData,
- GetUserByNameData,
- LoginUserData,
- LogoutUserData,
- PlaceOrderData,
- PlaceOrderResponse,
- UpdatePetData,
- UpdatePetResponse,
- UpdatePetWithFormData,
- UpdatePetWithFormResponse,
- UpdateUserData,
- UploadFileData,
- UploadFileResponse,
-} from '../types.gen';
-
-export type QueryKey = [
- Pick & {
- _id: string;
- _infinite?: boolean;
- tags?: ReadonlyArray;
- },
-];
-
-const createQueryKey = (
- id: string,
- options?: TOptions,
- infinite?: boolean,
- tags?: ReadonlyArray,
-): [QueryKey[0]] => {
- const params: QueryKey[0] = {
- _id: id,
- baseUrl:
- options?.baseUrl ||
- (options?.client ?? _heyApiClient).getConfig().baseUrl,
- } as QueryKey[0];
- if (infinite) {
- params._infinite = infinite;
- }
- if (tags) {
- params.tags = tags;
- }
- if (options?.body) {
- params.body = options.body;
- }
- if (options?.headers) {
- params.headers = options.headers;
- }
- if (options?.path) {
- params.path = options.path;
- }
- if (options?.query) {
- params.query = options.query;
- }
- return [params];
-};
-
-export const addPetQueryKey = (options: Options) =>
- createQueryKey('addPet', options);
-
-/**
- * Add a new pet to the store.
- * Add a new pet to the store.
- */
-export const addPetOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await addPet({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: addPetQueryKey(options),
- });
-
-/**
- * Add a new pet to the store.
- * Add a new pet to the store.
- */
-export const addPetMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- AddPetResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await addPet({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-/**
- * Update an existing pet.
- * Update an existing pet by Id.
- */
-export const updatePetMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- UpdatePetResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await updatePet({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const findPetsByStatusQueryKey = (
- options: Options,
-) => createQueryKey('findPetsByStatus', options);
-
-/**
- * Finds Pets by status.
- * Multiple status values can be provided with comma separated strings.
- */
-export const findPetsByStatusOptions = (
- options: Options,
-) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await findPetsByStatus({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: findPetsByStatusQueryKey(options),
- });
-
-export const findPetsByTagsQueryKey = (options: Options) =>
- createQueryKey('findPetsByTags', options);
-
-/**
- * Finds Pets by tags.
- * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
- */
-export const findPetsByTagsOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await findPetsByTags({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: findPetsByTagsQueryKey(options),
- });
-
-/**
- * Deletes a pet.
- * Delete a pet.
- */
-export const deletePetMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- unknown,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await deletePet({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const getPetByIdQueryKey = (options: Options) =>
- createQueryKey('getPetById', options);
-
-/**
- * Find pet by ID.
- * Returns a single pet.
- */
-export const getPetByIdOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await getPetById({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: getPetByIdQueryKey(options),
- });
-
-export const updatePetWithFormQueryKey = (
- options: Options,
-) => createQueryKey('updatePetWithForm', options);
-
-/**
- * Updates a pet in the store with form data.
- * Updates a pet resource based on the form data.
- */
-export const updatePetWithFormOptions = (
- options: Options,
-) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await updatePetWithForm({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: updatePetWithFormQueryKey(options),
- });
-
-/**
- * Updates a pet in the store with form data.
- * Updates a pet resource based on the form data.
- */
-export const updatePetWithFormMutation = (
- options?: Partial>,
-): MutationOptions<
- UpdatePetWithFormResponse,
- DefaultError,
- Options
-> => {
- const mutationOptions: MutationOptions<
- UpdatePetWithFormResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await updatePetWithForm({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const uploadFileQueryKey = (options: Options) =>
- createQueryKey('uploadFile', options);
-
-/**
- * Uploads an image.
- * Upload image of the pet.
- */
-export const uploadFileOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await uploadFile({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: uploadFileQueryKey(options),
- });
-
-/**
- * Uploads an image.
- * Upload image of the pet.
- */
-export const uploadFileMutation = (
- options?: Partial>,
-): MutationOptions<
- UploadFileResponse,
- DefaultError,
- Options
-> => {
- const mutationOptions: MutationOptions<
- UploadFileResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await uploadFile({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const getInventoryQueryKey = (options?: Options) =>
- createQueryKey('getInventory', options);
-
-/**
- * Returns pet inventories by status.
- * Returns a map of status codes to quantities.
- */
-export const getInventoryOptions = (options?: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await getInventory({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: getInventoryQueryKey(options),
- });
-
-export const placeOrderQueryKey = (options?: Options) =>
- createQueryKey('placeOrder', options);
-
-/**
- * Place an order for a pet.
- * Place a new order in the store.
- */
-export const placeOrderOptions = (options?: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await placeOrder({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: placeOrderQueryKey(options),
- });
-
-/**
- * Place an order for a pet.
- * Place a new order in the store.
- */
-export const placeOrderMutation = (
- options?: Partial>,
-): MutationOptions<
- PlaceOrderResponse,
- DefaultError,
- Options
-> => {
- const mutationOptions: MutationOptions<
- PlaceOrderResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await placeOrder({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-/**
- * Delete purchase order by identifier.
- * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors.
- */
-export const deleteOrderMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- unknown,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await deleteOrder({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const getOrderByIdQueryKey = (options: Options) =>
- createQueryKey('getOrderById', options);
-
-/**
- * Find purchase order by ID.
- * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
- */
-export const getOrderByIdOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await getOrderById({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: getOrderByIdQueryKey(options),
- });
-
-export const createUserQueryKey = (options?: Options) =>
- createQueryKey('createUser', options);
-
-/**
- * Create user.
- * This can only be done by the logged in user.
- */
-export const createUserOptions = (options?: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await createUser({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: createUserQueryKey(options),
- });
-
-/**
- * Create user.
- * This can only be done by the logged in user.
- */
-export const createUserMutation = (
- options?: Partial>,
-): MutationOptions<
- CreateUserResponse,
- DefaultError,
- Options
-> => {
- const mutationOptions: MutationOptions<
- CreateUserResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await createUser({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const createUsersWithListInputQueryKey = (
- options?: Options,
-) => createQueryKey('createUsersWithListInput', options);
-
-/**
- * Creates list of users with given input array.
- * Creates list of users with given input array.
- */
-export const createUsersWithListInputOptions = (
- options?: Options,
-) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await createUsersWithListInput({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: createUsersWithListInputQueryKey(options),
- });
-
-/**
- * Creates list of users with given input array.
- * Creates list of users with given input array.
- */
-export const createUsersWithListInputMutation = (
- options?: Partial>,
-): MutationOptions<
- CreateUsersWithListInputResponse,
- DefaultError,
- Options
-> => {
- const mutationOptions: MutationOptions<
- CreateUsersWithListInputResponse,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await createUsersWithListInput({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const loginUserQueryKey = (options?: Options) =>
- createQueryKey('loginUser', options);
-
-/**
- * Logs user into the system.
- * Log into the system.
- */
-export const loginUserOptions = (options?: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await loginUser({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: loginUserQueryKey(options),
- });
-
-export const logoutUserQueryKey = (options?: Options) =>
- createQueryKey('logoutUser', options);
-
-/**
- * Logs out current logged in user session.
- * Log user out of the system.
- */
-export const logoutUserOptions = (options?: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await logoutUser({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: logoutUserQueryKey(options),
- });
-
-/**
- * Delete user resource.
- * This can only be done by the logged in user.
- */
-export const deleteUserMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- unknown,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await deleteUser({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
-
-export const getUserByNameQueryKey = (options: Options) =>
- createQueryKey('getUserByName', options);
-
-/**
- * Get user by user name.
- * Get user detail based on username.
- */
-export const getUserByNameOptions = (options: Options) =>
- queryOptions({
- queryFn: async ({ queryKey, signal }) => {
- const { data } = await getUserByName({
- ...options,
- ...queryKey[0],
- signal,
- throwOnError: true,
- });
- return data;
- },
- queryKey: getUserByNameQueryKey(options),
- });
-
-/**
- * Update user resource.
- * This can only be done by the logged in user.
- */
-export const updateUserMutation = (
- options?: Partial>,
-): MutationOptions> => {
- const mutationOptions: MutationOptions<
- unknown,
- DefaultError,
- Options
- > = {
- mutationFn: async (localOptions) => {
- const { data } = await updateUser({
- ...options,
- ...localOptions,
- throwOnError: true,
- });
- return data;
- },
- };
- return mutationOptions;
-};
diff --git a/examples/openapi-ts-angular/src/client/client/client.gen.ts b/examples/openapi-ts-angular/src/client/client/client.gen.ts
index 9575a92b7..a96dacef0 100644
--- a/examples/openapi-ts-angular/src/client/client/client.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client/client.gen.ts
@@ -1,11 +1,17 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { HttpResponse } from '@angular/common/http';
-import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http';
+import {
+ HttpClient,
+ HttpErrorResponse,
+ HttpEventType,
+ HttpRequest,
+} from '@angular/common/http';
import {
assertInInjectionContext,
inject,
provideAppInitializer,
+ runInInjectionContext,
} from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { filter } from 'rxjs/operators';
@@ -54,8 +60,14 @@ export const createClient = (config: Config = {}): Client => {
};
if (!opts.httpClient) {
- assertInInjectionContext(request);
- opts.httpClient = inject(HttpClient);
+ if (opts.injector) {
+ opts.httpClient = runInInjectionContext(opts.injector, () =>
+ inject(HttpClient),
+ );
+ } else {
+ assertInInjectionContext(request);
+ opts.httpClient = inject(HttpClient);
+ }
}
if (opts.security) {
@@ -111,35 +123,45 @@ export const createClient = (config: Config = {}): Client => {
}
}
- let bodyResponse = response.body as Record;
+ let bodyResponse: any = response.body;
if (opts.responseValidator) {
await opts.responseValidator(bodyResponse);
}
if (opts.responseTransformer) {
- bodyResponse = (await opts.responseTransformer(bodyResponse)) as Record<
- string,
- unknown
- >;
+ bodyResponse = await opts.responseTransformer(bodyResponse);
}
- return (
- opts.responseStyle === 'data'
- ? bodyResponse
- : { data: bodyResponse, ...result }
- ) as any;
+ return opts.responseStyle === 'data'
+ ? bodyResponse
+ : { data: bodyResponse, ...result };
} catch (error) {
+ if (error instanceof HttpErrorResponse) {
+ response = error;
+ }
+
+ let finalError = error instanceof HttpErrorResponse ? error.error : error;
+
for (const fn of interceptors.error._fns) {
if (fn) {
- (await fn(error, response!, req, opts)) as string;
+ finalError = (await fn(
+ finalError,
+ response as HttpResponse,
+ req,
+ opts,
+ )) as string;
}
}
+ if (opts.throwOnError) {
+ throw finalError;
+ }
+
return opts.responseStyle === 'data'
? undefined
: {
- error,
+ error: finalError,
...result,
};
}
diff --git a/examples/openapi-ts-angular/src/client/client/types.gen.ts b/examples/openapi-ts-angular/src/client/client/types.gen.ts
index b086223a6..e54d1a64e 100644
--- a/examples/openapi-ts-angular/src/client/client/types.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client/types.gen.ts
@@ -2,9 +2,12 @@
import type {
HttpClient,
+ HttpErrorResponse,
+ HttpHeaders,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
+import type { Injector } from '@angular/core';
import type { Auth } from '../core/auth.gen';
import type {
@@ -17,11 +20,29 @@ export type ResponseStyle = 'data' | 'fields';
export interface Config
extends Omit,
- CoreConfig {
+ Omit {
/**
* Base URL for all requests made by this client.
*/
baseUrl?: T['baseUrl'];
+ /**
+ * An object containing any HTTP headers that you want to pre-populate your
+ * `HttpHeaders` object with.
+ *
+ * {@link https://angular.dev/api/common/http/HttpHeaders#constructor See more}
+ */
+ headers?:
+ | HttpHeaders
+ | Record<
+ string,
+ | string
+ | number
+ | boolean
+ | (string | number | boolean)[]
+ | null
+ | undefined
+ | unknown
+ >;
/**
* The HTTP client to use for making requests.
*/
@@ -32,6 +53,13 @@ export interface Config
* @default 'fields'
*/
responseStyle?: ResponseStyle;
+
+ /**
+ * Throw an error instead of returning it in the response?
+ *
+ * @default false
+ */
+ throwOnError?: T['throwOnError'];
}
export interface RequestOptions<
@@ -48,6 +76,10 @@ export interface RequestOptions<
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
*/
body?: unknown;
+ /**
+ * Optional custom injector for dependency resolution if you don't implicitly or explicitly provide one.
+ */
+ injector?: Injector;
path?: Record;
query?: Record;
/**
@@ -80,8 +112,8 @@ export type RequestResult<
data: TData extends Record
? TData[keyof TData]
: TData;
- request: Request;
- response: Response;
+ request: HttpRequest;
+ response: HttpResponse;
}
>
: Promise<
@@ -91,23 +123,23 @@ export type RequestResult<
? TData[keyof TData]
: TData)
| undefined
- : (
+ :
| {
data: TData extends Record
? TData[keyof TData]
: TData;
error: undefined;
+ request: HttpRequest;
+ response: HttpResponse;
}
| {
data: undefined;
- error: TError extends Record
- ? TError[keyof TError]
- : TError;
+ error: TError[keyof TError];
+ request: HttpRequest;
+ response: HttpErrorResponse & {
+ error: TError[keyof TError] | null;
+ };
}
- ) & {
- request: Request;
- response: Response;
- }
>;
export interface ClientOptions {
diff --git a/examples/openapi-ts-angular/src/client/client/utils.gen.ts b/examples/openapi-ts-angular/src/client/client/utils.gen.ts
index d5bf0a1de..9cd7b1faa 100644
--- a/examples/openapi-ts-angular/src/client/client/utils.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client/utils.gen.ts
@@ -1,5 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts
+import { HttpHeaders } from '@angular/common/http';
+
import { getAuthToken } from '../core/auth.gen';
import type {
QuerySerializer,
@@ -198,7 +200,7 @@ export const setAuthParams = async ({
...options
}: Pick, 'security'> &
Pick & {
- headers: Headers;
+ headers: HttpHeaders;
}) => {
for (const auth of security) {
const token = await getAuthToken(auth, options.auth);
@@ -282,33 +284,47 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
export const mergeHeaders = (
...headers: Array['headers'] | undefined>
-): Headers => {
- const mergedHeaders = new Headers();
+): HttpHeaders => {
+ let mergedHeaders = new HttpHeaders();
+
for (const header of headers) {
if (!header || typeof header !== 'object') {
continue;
}
- const iterator =
- header instanceof Headers ? header.entries() : Object.entries(header);
-
- for (const [key, value] of iterator) {
- if (value === null) {
- mergedHeaders.delete(key);
- } else if (Array.isArray(value)) {
- for (const v of value) {
- mergedHeaders.append(key, v as string);
+ if (header instanceof HttpHeaders) {
+ // Merge HttpHeaders instance
+ header.keys().forEach((key) => {
+ const values = header.getAll(key);
+ if (values) {
+ values.forEach((value) => {
+ mergedHeaders = mergedHeaders.append(key, value);
+ });
+ }
+ });
+ } else {
+ // Merge plain object headers
+ for (const [key, value] of Object.entries(header)) {
+ if (value === null) {
+ mergedHeaders = mergedHeaders.delete(key);
+ } else if (Array.isArray(value)) {
+ for (const v of value) {
+ mergedHeaders = mergedHeaders.append(key, v as string);
+ }
+ } else if (value !== undefined) {
+ // assume object headers are meant to be JSON stringified, i.e. their
+ // content value in OpenAPI specification is 'application/json'
+ mergedHeaders = mergedHeaders.set(
+ key,
+ typeof value === 'object'
+ ? JSON.stringify(value)
+ : (value as string),
+ );
}
- } else if (value !== undefined) {
- // assume object headers are meant to be JSON stringified, i.e. their
- // content value in OpenAPI specification is 'application/json'
- mergedHeaders.set(
- key,
- typeof value === 'object' ? JSON.stringify(value) : (value as string),
- );
}
}
}
+
return mergedHeaders;
};
diff --git a/examples/openapi-ts-tanstack-angular-query-experimental/openapi-ts.config.ts b/examples/openapi-ts-tanstack-angular-query-experimental/openapi-ts.config.ts
index 1475ba634..67386b15d 100644
--- a/examples/openapi-ts-tanstack-angular-query-experimental/openapi-ts.config.ts
+++ b/examples/openapi-ts-tanstack-angular-query-experimental/openapi-ts.config.ts
@@ -9,7 +9,10 @@ export default defineConfig({
path: './src/client',
},
plugins: [
- '@hey-api/client-fetch',
+ {
+ name: '@hey-api/client-angular',
+ // throwOnError: true,
+ },
'@hey-api/schemas',
'@hey-api/sdk',
{
diff --git a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/app.config.ts b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/app.config.ts
index 36fe57eee..99ebd5ac2 100644
--- a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/app.config.ts
+++ b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/app.config.ts
@@ -1,47 +1,24 @@
+import { provideHttpClient, withFetch } from '@angular/common/http';
import type { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router';
import {
- provideAngularQuery,
+ provideTanStackQuery,
QueryClient,
} from '@tanstack/angular-query-experimental';
import { client } from '../client/client.gen';
+import { provideHeyApiClient } from '../client/client/client.gen';
import { routes } from './app.routes';
-client.setConfig({
- // set default base url for requests made by this client
- baseUrl: 'https://petstore3.swagger.io/api/v3',
- /**
- * Set default headers only for requests made by this client. This is to
- * demonstrate local clients and their configuration taking precedence over
- * internal service client.
- */
- headers: {
- Authorization: 'Bearer ',
- },
-});
-
-client.interceptors.request.use((request, options) => {
- // Middleware is great for adding authorization tokens to requests made to
- // protected paths. Headers are set randomly here to allow surfacing the
- // default headers, too.
- if (
- options.url === '/pet/{petId}' &&
- options.method === 'GET' &&
- Math.random() < 0.5
- ) {
- request.headers.set('Authorization', 'Bearer ');
- }
- return request;
-});
-
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
- provideAngularQuery(
+ provideHttpClient(withFetch()),
+ provideHeyApiClient(client),
+ provideTanStackQuery(
new QueryClient({
defaultOptions: {
queries: {
diff --git a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.css b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.css
index 8ea9543c6..13b45619a 100644
--- a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.css
+++ b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.css
@@ -1,16 +1,64 @@
:host {
display: grid;
gap: 20px;
-
max-width: 400px;
margin: auto;
}
+.pet-store-header {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-start;
+ flex-wrap: wrap;
+}
+
mat-card {
margin-bottom: 20px;
}
+.pet-card {
+ width: 100%;
+ margin-top: 1rem;
+}
+
+.pet-card-title {
+ font-size: 1.4rem;
+ font-weight: 600;
+}
+
+.pet-card-subtitle {
+ font-size: 1rem;
+ color: #666;
+}
+
+.pet-card-image {
+ object-fit: cover;
+ min-height: 180px;
+ background: #fafafa;
+}
+
+.pet-status {
+ margin-top: 0.5rem;
+ color: #888;
+}
+
+.pet-form-card {
+ margin-top: 2rem;
+ max-width: 400px;
+}
+
+.pet-form-title {
+ font-size: 1.2rem;
+ font-weight: 500;
+}
+
+.pet-form-content {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
.actions {
display: flex;
- gap: 10px;
+ gap: 0.5rem;
}
diff --git a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.html b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.html
index fa2a15a7a..506f36640 100644
--- a/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.html
+++ b/examples/openapi-ts-tanstack-angular-query-experimental/src/app/pet-store/pet-store.component.html
@@ -1,48 +1,63 @@
-
-
- {{ pet.data()!.name }}
- {{ pet.data()!.category }}
-
-
-
+@let pet = petState.data();
+
+
+@if (addPet.data()) {
+
+ {{addPet.data()|json}}
+
+}
-
+@if (updatePet.data()) {
+
+ {{updatePet.data()|json}}
+
+}