diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c50f87ce3..20d66513b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,6 +41,10 @@ jobs:
if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest'
run: pnpm build --filter="@example/**"
+ - name: Check examples generated code
+ if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest'
+ run: pnpm examples:check
+
- name: Run linter
run: pnpm lint
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 6df8dbfe3..c4595f000 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,11 +3,12 @@
"source.fixAll.eslint": "explicit"
},
"editor.quickSuggestions": {
- "strings": true
+ "strings": "on"
},
"eslint.format.enable": true,
"eslint.nodePath": "./node_modules",
"eslint.workingDirectories": [{ "pattern": "./packages/*/" }],
"typescript.preferences.autoImportFileExcludePatterns": ["dist/**"],
+ "typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"],
"typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/docs/openapi-ts/community/contributing/developing.md b/docs/openapi-ts/community/contributing/developing.md
index 8b1aa38c3..2f7445d83 100644
--- a/docs/openapi-ts/community/contributing/developing.md
+++ b/docs/openapi-ts/community/contributing/developing.md
@@ -8,3 +8,49 @@ description: Learn how to contribute to Hey API.
::: warning
This page is under construction. We appreciate your patience.
:::
+
+## Working with Examples
+
+The `examples` folder contains various integration examples that demonstrate how to use `@hey-api/openapi-ts` with different frameworks and libraries. These examples are kept in sync with the codebase through automated checks.
+
+### Generating Example Code
+
+When you make changes to the core packages that affect code generation, you need to regenerate the client code in all examples:
+
+```bash
+pnpm examples:generate
+```
+
+This command will:
+
+- Find all examples with an `openapi-ts` script
+- Run the OpenAPI code generator for each example
+- Update the generated client code in each example
+
+### Checking Example Code
+
+Before committing changes, ensure that all generated example code is up-to-date:
+
+```bash
+pnpm examples:check
+```
+
+This command will:
+
+- Regenerate all example code
+- Check if any files were modified
+- Exit with an error if generated code is out of sync
+
+This check is also run automatically in CI to ensure examples stay in sync with the main codebase.
+
+### Example Workflow
+
+1. Make changes to core packages
+2. Build the packages: `pnpm build --filter="@hey-api/**"`
+3. Regenerate examples: `pnpm examples:generate`
+4. Commit all changes including the updated generated code
+5. The CI will verify that examples are in sync
+
+::: tip
+Think of generated example code as snapshot tests - they should always reflect the current state of the code generator.
+:::
diff --git a/docs/partials/contributors-list.md b/docs/partials/contributors-list.md
index bf465d047..b7aa7ab8f 100644
--- a/docs/partials/contributors-list.md
+++ b/docs/partials/contributors-list.md
@@ -51,6 +51,7 @@
- [Marcel Richter](https://github.com/mrclrchtr)
- [Marek Lukáš](https://github.com/tajnymag)
- [Matsu](https://github.com/Matsuuu)
+- [Maurici Abad Gutierrez](https://github.com/mauriciabad)
- [Max Scopp](https://github.com/max-scopp)
- [Maximilian Dewald](https://github.com/maxdewald)
- [Michał Grezel](https://github.com/dracomithril)
diff --git a/eslint-rules/local-paths.js b/eslint-rules/local-paths.js
new file mode 100644
index 000000000..10fedd255
--- /dev/null
+++ b/eslint-rules/local-paths.js
@@ -0,0 +1,233 @@
+import path from 'node:path';
+
+function normalize(p) {
+ return p.split(path.sep).join('/');
+}
+
+function stripExtAndIndex(p) {
+ return p
+ .replace(/(\/index)?\.(ts|tsx|js|cjs|mjs|d\.ts)$/, '')
+ .replace(/\/index$/, '');
+}
+
+/**
+ * Single consolidated rule implementing the previous three behaviors:
+ * - plugins: allow relative within same plugin (plugin may be @scope/name), else require `~`
+ * - openApi: allow relative only within same first-level openApi folder, else require `~`
+ * - first-level folders: allow relative only within same first-level folder, else require `~`
+ */
+const enforceLocalPaths = {
+ create(context) {
+ const filename = context.getFilename();
+ if (!filename || filename === '') return {};
+
+ const normalizedFile = normalize(filename);
+ const srcMarker = '/packages/openapi-ts/src/';
+ const srcIdx = normalizedFile.indexOf(srcMarker);
+ if (srcIdx === -1) return {};
+
+ const after = normalizedFile.slice(srcIdx + srcMarker.length);
+ const parts = after.split('/');
+ const firstLevel = parts[0];
+
+ // helpers and mode-specific roots
+ // compute the absolute normalized bundle root for accurate comparisons
+ let bundleAbsRoot =
+ normalizedFile.slice(0, srcIdx + srcMarker.length) +
+ 'plugins/@hey-api/client-core/bundle/';
+ bundleAbsRoot = normalize(bundleAbsRoot);
+
+ let mode = 'first-level';
+ let pluginAbsRoot = null;
+ let openApiFirstRoot = null;
+ let firstRoot = null;
+
+ if (firstLevel === 'plugins') {
+ mode = 'plugins';
+ // derive plugin folder (support scoped plugin names)
+ let pluginFolder = parts[1] || '';
+ if (pluginFolder.startsWith('@') && parts.length > 2) {
+ pluginFolder = `${pluginFolder}/${parts[2]}`;
+ }
+ pluginAbsRoot =
+ normalizedFile.slice(0, srcIdx + srcMarker.length) +
+ `plugins/${pluginFolder}/`;
+ pluginAbsRoot = normalize(pluginAbsRoot);
+ } else if (firstLevel === 'openApi') {
+ mode = 'openApi';
+ const apiFirst = parts[1] || '';
+ openApiFirstRoot =
+ normalizedFile.slice(0, srcIdx + srcMarker.length) +
+ `openApi/${apiFirst}/`;
+ openApiFirstRoot = normalize(openApiFirstRoot);
+ } else {
+ firstRoot =
+ normalizedFile.slice(0, srcIdx + srcMarker.length) + `${firstLevel}/`;
+ firstRoot = normalize(firstRoot);
+ }
+
+ function resolveFromBasedir(basedir, sourceValue) {
+ try {
+ return path.resolve(basedir, sourceValue);
+ } catch {
+ return null;
+ }
+ }
+
+ function resolveTildeToAbs(sourceValue) {
+ const rest = sourceValue.replace(/^~\/?/, '');
+ return path.resolve(process.cwd(), 'packages/openapi-ts/src', rest);
+ }
+
+ function toTilde(resolvedAbsolutePath) {
+ const normalized = normalize(resolvedAbsolutePath);
+ const marker = '/packages/openapi-ts/src/';
+ const i = normalized.indexOf(marker);
+ if (i === -1) return null;
+ let rest = normalized.slice(i + marker.length);
+ rest = stripExtAndIndex(rest);
+ return `~/${rest}`;
+ }
+
+ function reportReplaceWithTilde(node, sourceValue, message) {
+ const basedir = path.dirname(filename);
+ const resolved = resolveFromBasedir(basedir, sourceValue);
+ if (!resolved) return;
+ const newImport = toTilde(resolved);
+ if (!newImport) return;
+ context.report({
+ fix(fixer) {
+ return fixer.replaceText(node.source, `'${newImport}'`);
+ },
+ message,
+ node: node.source,
+ });
+ }
+
+ function reportReplaceWithRelative(node, sourceValue, message) {
+ const resolved = resolveTildeToAbs(sourceValue);
+ if (!resolved) return;
+ let relativePath = path
+ .relative(path.dirname(filename), resolved)
+ .split(path.sep)
+ .join('/');
+ relativePath = stripExtAndIndex(relativePath);
+ if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
+ context.report({
+ fix(fixer) {
+ return fixer.replaceText(node.source, `'${relativePath}'`);
+ },
+ message,
+ node: node.source,
+ });
+ }
+
+ function shouldRewriteRelative(sourceValue) {
+ if (typeof sourceValue !== 'string') return false;
+ if (!sourceValue.startsWith('.')) return false;
+ const basedir = path.dirname(filename);
+ const resolved = resolveFromBasedir(basedir, sourceValue);
+ if (!resolved) return false;
+ const nr = normalize(resolved);
+ if (!nr.includes('/packages/openapi-ts/src/')) return false;
+
+ if (mode === 'plugins') {
+ if (nr.startsWith(pluginAbsRoot)) return false; // inside same plugin -> keep relative
+ if (nr.startsWith(bundleAbsRoot)) return false; // client-core bundle: keep relative
+ return true;
+ }
+
+ if (mode === 'openApi') {
+ // if target is inside same first-level openApi folder -> don't rewrite
+ if (nr.startsWith(openApiFirstRoot)) return false;
+ // only rewrite if target is inside openApi at all
+ return nr.includes('/packages/openapi-ts/src/openApi/');
+ }
+
+ // generic first-level folder rule
+ if (nr.startsWith(firstRoot)) return false;
+ return true;
+ }
+
+ function shouldRewriteTilde(sourceValue) {
+ if (typeof sourceValue !== 'string') return false;
+ if (!sourceValue.startsWith('~')) return false;
+ const resolved = resolveTildeToAbs(sourceValue);
+ if (!resolved) return false;
+ const nr = normalize(resolved);
+
+ if (mode === 'plugins') {
+ // prefer relative imports for same-plugin targets or for the shared client-core bundle
+ return nr.startsWith(pluginAbsRoot) || nr.startsWith(bundleAbsRoot);
+ }
+ if (mode === 'openApi') {
+ return nr.startsWith(openApiFirstRoot);
+ }
+ return nr.startsWith(firstRoot);
+ }
+
+ return {
+ ExportAllDeclaration(node) {
+ const sourceValue = node.source && node.source.value;
+ if (shouldRewriteRelative(sourceValue)) {
+ reportReplaceWithTilde(
+ node,
+ sourceValue,
+ 'Prefer `~` export for cross-boundary exports (autofixable).',
+ );
+ return;
+ }
+ if (shouldRewriteTilde(sourceValue)) {
+ reportReplaceWithRelative(
+ node,
+ sourceValue,
+ 'Prefer relative export for intra-boundary targets (autofixable).',
+ );
+ return;
+ }
+ },
+ ImportDeclaration(node) {
+ const sourceValue = node.source && node.source.value;
+ if (shouldRewriteRelative(sourceValue)) {
+ reportReplaceWithTilde(
+ node,
+ sourceValue,
+ 'Prefer `~` import for cross-boundary imports (autofixable).',
+ );
+ return;
+ }
+ if (shouldRewriteTilde(sourceValue)) {
+ reportReplaceWithRelative(
+ node,
+ sourceValue,
+ 'Prefer relative import for intra-boundary targets (autofixable).',
+ );
+ return;
+ }
+ },
+ };
+ },
+ meta: {
+ docs: {
+ description:
+ 'Enforce local import path boundaries for openapi-ts sources',
+ recommended: false,
+ },
+ fixable: 'code',
+ schema: [],
+ type: 'suggestion',
+ },
+};
+
+export default {
+ configs: {
+ recommended: {
+ rules: {
+ 'local-paths/enforce-local-paths': 'error',
+ },
+ },
+ },
+ rules: {
+ 'enforce-local-paths': enforceLocalPaths,
+ },
+};
diff --git a/eslint.config.js b/eslint.config.js
index 42826d4b0..93fb9b916 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -8,9 +8,12 @@ import pluginTypeScriptSortKeys from 'eslint-plugin-typescript-sort-keys';
import globals from 'globals';
import tseslint from 'typescript-eslint';
+import pluginLocalPaths from './eslint-rules/local-paths.js';
+
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
+ pluginLocalPaths.configs.recommended,
{
languageOptions: {
ecmaVersion: 'latest',
@@ -19,6 +22,7 @@ export default tseslint.config(
},
},
plugins: {
+ 'local-paths': pluginLocalPaths,
'simple-import-sort': pluginSimpleImportSort,
'sort-destructure-keys': pluginSortDestructureKeys,
'sort-keys-fix': pluginSortKeysFix,
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 000000000..6359053b1
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,84 @@
+# Examples
+
+This directory contains integration examples demonstrating how to use `@hey-api/openapi-ts` with various frameworks, libraries, and client implementations.
+
+## Available Examples
+
+- **openapi-ts-angular** - Angular integration with common HTTP client
+- **openapi-ts-angular-common** - Angular with @angular/common/http
+- **openapi-ts-axios** - Using Axios client
+- **openapi-ts-fastify** - Fastify server integration
+- **openapi-ts-fetch** - Native Fetch API client
+- **openapi-ts-next** - Next.js integration
+- **openapi-ts-nuxt** - Nuxt.js integration with plugin
+- **openapi-ts-ofetch** - Using ofetch client
+- **openapi-ts-openai** - OpenAI API integration
+- **openapi-ts-pinia-colada** - Vue with Pinia Colada state management
+- **openapi-ts-sample** - Sample/template example (excluded from CI)
+- **openapi-ts-tanstack-angular-query-experimental** - Angular with TanStack Query
+- **openapi-ts-tanstack-react-query** - React with TanStack Query
+- **openapi-ts-tanstack-svelte-query** - Svelte with TanStack Query
+- **openapi-ts-tanstack-vue-query** - Vue with TanStack Query
+
+## Generated Code
+
+All examples (except `openapi-ts-sample`) contain generated client code that is **committed to the repository**. This ensures:
+
+1. Examples always reflect the current state of the code generator
+2. Changes to the code generator are visible in pull requests
+3. CI can verify that examples are kept up-to-date
+
+## Regenerating Examples
+
+After making changes to the core packages, regenerate all example code:
+
+```bash
+pnpm examples:generate
+```
+
+This command will run `openapi-ts` for each example that has an `openapi-ts` script in its `package.json`.
+
+## Verifying Examples
+
+To check if all examples are up-to-date with the current codebase:
+
+```bash
+pnpm examples:check
+```
+
+This check is also run automatically in CI. If it fails, run `pnpm examples:generate` and commit the changes.
+
+## Running Examples
+
+Each example can be run individually using the `example` script:
+
+```bash
+# Run dev server for fetch example
+pnpm example fetch dev
+
+# Build fetch example
+pnpm example fetch build
+```
+
+Or directly using pnpm filters:
+
+```bash
+pnpm --filter @example/openapi-ts-fetch dev
+```
+
+## Creating New Examples
+
+When creating a new example:
+
+1. Create a new directory in `examples/`
+2. Add an `openapi-ts` script to `package.json`
+3. Run `pnpm examples:generate` to create initial generated code
+4. Commit both the source and generated code
+5. The example will automatically be included in CI checks
+
+## Excluding Examples
+
+To exclude an example from CI (like `openapi-ts-sample`):
+
+1. Remove the `openapi-ts` script from `package.json`, or
+2. Update the exclusion filters in `package.json` scripts and `.github/workflows/ci.yml`
diff --git a/examples/openapi-ts-angular-common/src/client/@angular/common/http/resources.gen.ts b/examples/openapi-ts-angular-common/src/client/@angular/common.gen.ts
similarity index 53%
rename from examples/openapi-ts-angular-common/src/client/@angular/common/http/resources.gen.ts
rename to examples/openapi-ts-angular-common/src/client/@angular/common.gen.ts
index f4be36afb..e063c5925 100644
--- a/examples/openapi-ts-angular-common/src/client/@angular/common/http/resources.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/@angular/common.gen.ts
@@ -1,9 +1,10 @@
// This file is auto-generated by @hey-api/openapi-ts
-import { httpResource } from '@angular/common/http';
+import { type HttpRequest, httpResource } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import type { Options } from '../../../sdk.gen';
+import { client } from '../client.gen';
+import type { Options } from '../sdk.gen';
import type {
AddPetData,
AddPetResponse,
@@ -38,28 +39,294 @@ import type {
UpdateUserData,
UploadFileData,
UploadFileResponse,
-} from '../../../types.gen';
-import {
- addPetRequest,
- createUserRequest,
- createUsersWithListInputRequest,
- deleteOrderRequest,
- deletePetRequest,
- deleteUserRequest,
- findPetsByStatusRequest,
- findPetsByTagsRequest,
- getInventoryRequest,
- getOrderByIdRequest,
- getPetByIdRequest,
- getUserByNameRequest,
- loginUserRequest,
- logoutUserRequest,
- placeOrderRequest,
- updatePetRequest,
- updatePetWithFormRequest,
- updateUserRequest,
- uploadFileRequest,
-} from './requests.gen';
+} from '../types.gen';
+
+/**
+ * Add a new pet to the store.
+ *
+ * Add a new pet to the store.
+ */
+export const addPetRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/pet',
+ ...options,
+ });
+
+/**
+ * Update an existing pet.
+ *
+ * Update an existing pet by Id.
+ */
+export const updatePetRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'PUT',
+ responseStyle: 'data',
+ url: '/pet',
+ ...options,
+ });
+
+/**
+ * Finds Pets by status.
+ *
+ * Multiple status values can be provided with comma separated strings.
+ */
+export const findPetsByStatusRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/pet/findByStatus',
+ ...options,
+ });
+
+/**
+ * Finds Pets by tags.
+ *
+ * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
+ */
+export const findPetsByTagsRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/pet/findByTags',
+ ...options,
+ });
+
+/**
+ * Deletes a pet.
+ *
+ * Delete a pet.
+ */
+export const deletePetRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'DELETE',
+ responseStyle: 'data',
+ url: '/pet/{petId}',
+ ...options,
+ });
+
+/**
+ * Find pet by ID.
+ *
+ * Returns a single pet.
+ */
+export const getPetByIdRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/pet/{petId}',
+ ...options,
+ });
+
+/**
+ * Updates a pet in the store with form data.
+ *
+ * Updates a pet resource based on the form data.
+ */
+export const updatePetWithFormRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/pet/{petId}',
+ ...options,
+ });
+
+/**
+ * Uploads an image.
+ *
+ * Upload image of the pet.
+ */
+export const uploadFileRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/pet/{petId}/uploadImage',
+ ...options,
+ });
+
+/**
+ * Returns pet inventories by status.
+ *
+ * Returns a map of status codes to quantities.
+ */
+export const getInventoryRequest = (
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/store/inventory',
+ ...options,
+ });
+
+/**
+ * Place an order for a pet.
+ *
+ * Place a new order in the store.
+ */
+export const placeOrderRequest = (
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/store/order',
+ ...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.
+ */
+export const deleteOrderRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'DELETE',
+ responseStyle: 'data',
+ url: '/store/order/{orderId}',
+ ...options,
+ });
+
+/**
+ * Find purchase order by ID.
+ *
+ * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
+ */
+export const getOrderByIdRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/store/order/{orderId}',
+ ...options,
+ });
+
+/**
+ * Create user.
+ *
+ * This can only be done by the logged in user.
+ */
+export const createUserRequest = (
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/user',
+ ...options,
+ });
+
+/**
+ * Creates list of users with given input array.
+ *
+ * Creates list of users with given input array.
+ */
+export const createUsersWithListInputRequest = <
+ ThrowOnError extends boolean = false,
+>(
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'POST',
+ responseStyle: 'data',
+ url: '/user/createWithList',
+ ...options,
+ });
+
+/**
+ * Logs user into the system.
+ *
+ * Log into the system.
+ */
+export const loginUserRequest = (
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/user/login',
+ ...options,
+ });
+
+/**
+ * Logs out current logged in user session.
+ *
+ * Log user out of the system.
+ */
+export const logoutUserRequest = (
+ options?: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/user/logout',
+ ...options,
+ });
+
+/**
+ * Delete user resource.
+ *
+ * This can only be done by the logged in user.
+ */
+export const deleteUserRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'DELETE',
+ responseStyle: 'data',
+ url: '/user/{username}',
+ ...options,
+ });
+
+/**
+ * Get user by user name.
+ *
+ * Get user detail based on username.
+ */
+export const getUserByNameRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'GET',
+ responseStyle: 'data',
+ url: '/user/{username}',
+ ...options,
+ });
+
+/**
+ * Update user resource.
+ *
+ * This can only be done by the logged in user.
+ */
+export const updateUserRequest = (
+ options: Options,
+): HttpRequest =>
+ (options?.client ?? client).requestOptions({
+ method: 'PUT',
+ responseStyle: 'data',
+ url: '/user/{username}',
+ ...options,
+ });
@Injectable({
providedIn: 'root',
@@ -67,6 +334,7 @@ import {
export class PetServiceResources {
/**
* Add a new pet to the store.
+ *
* Add a new pet to the store.
*/
public addPet(
@@ -80,6 +348,7 @@ export class PetServiceResources {
/**
* Update an existing pet.
+ *
* Update an existing pet by Id.
*/
public updatePet(
@@ -93,6 +362,7 @@ export class PetServiceResources {
/**
* Finds Pets by status.
+ *
* Multiple status values can be provided with comma separated strings.
*/
public findPetsByStatus(
@@ -106,6 +376,7 @@ export class PetServiceResources {
/**
* Finds Pets by tags.
+ *
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*/
public findPetsByTags(
@@ -119,6 +390,7 @@ export class PetServiceResources {
/**
* Deletes a pet.
+ *
* Delete a pet.
*/
public deletePet(
@@ -132,6 +404,7 @@ export class PetServiceResources {
/**
* Find pet by ID.
+ *
* Returns a single pet.
*/
public getPetById(
@@ -145,6 +418,7 @@ export class PetServiceResources {
/**
* Updates a pet in the store with form data.
+ *
* Updates a pet resource based on the form data.
*/
public updatePetWithForm(
@@ -158,6 +432,7 @@ export class PetServiceResources {
/**
* Uploads an image.
+ *
* Upload image of the pet.
*/
public uploadFile(
@@ -176,6 +451,7 @@ export class PetServiceResources {
export class StoreServiceResources {
/**
* Returns pet inventories by status.
+ *
* Returns a map of status codes to quantities.
*/
public getInventory(
@@ -189,6 +465,7 @@ export class StoreServiceResources {
/**
* Place an order for a pet.
+ *
* Place a new order in the store.
*/
public placeOrder(
@@ -202,6 +479,7 @@ export class StoreServiceResources {
/**
* 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 deleteOrder(
@@ -215,6 +493,7 @@ export class StoreServiceResources {
/**
* Find purchase order by ID.
+ *
* For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
*/
public getOrderById(
@@ -233,6 +512,7 @@ export class StoreServiceResources {
export class UserServiceResources {
/**
* Create user.
+ *
* This can only be done by the logged in user.
*/
public createUser(
@@ -246,6 +526,7 @@ export class UserServiceResources {
/**
* Creates list of users with given input array.
+ *
* Creates list of users with given input array.
*/
public createUsersWithListInput(
@@ -261,6 +542,7 @@ export class UserServiceResources {
/**
* Logs user into the system.
+ *
* Log into the system.
*/
public loginUser(
@@ -274,6 +556,7 @@ export class UserServiceResources {
/**
* Logs out current logged in user session.
+ *
* Log user out of the system.
*/
public logoutUser(
@@ -287,6 +570,7 @@ export class UserServiceResources {
/**
* Delete user resource.
+ *
* This can only be done by the logged in user.
*/
public deleteUser(
@@ -300,6 +584,7 @@ export class UserServiceResources {
/**
* Get user by user name.
+ *
* Get user detail based on username.
*/
public getUserByName(
@@ -313,6 +598,7 @@ export class UserServiceResources {
/**
* Update user resource.
+ *
* This can only be done by the logged in user.
*/
public updateUser(
diff --git a/examples/openapi-ts-angular-common/src/client/@angular/common/http/requests.gen.ts b/examples/openapi-ts-angular-common/src/client/@angular/common/http/requests.gen.ts
deleted file mode 100644
index e66f2fbc8..000000000
--- a/examples/openapi-ts-angular-common/src/client/@angular/common/http/requests.gen.ts
+++ /dev/null
@@ -1,295 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-import type { HttpRequest } from '@angular/common/http';
-
-import { client as _heyApiClient } from '../../../client.gen';
-import type { Options } 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';
-
-/**
- * Add a new pet to the store.
- * Add a new pet to the store.
- */
-export const addPetRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/pet',
- ...options,
- });
-
-/**
- * Update an existing pet.
- * Update an existing pet by Id.
- */
-export const updatePetRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'PUT',
- responseStyle: 'data',
- url: '/pet',
- ...options,
- });
-
-/**
- * Finds Pets by status.
- * Multiple status values can be provided with comma separated strings.
- */
-export const findPetsByStatusRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/pet/findByStatus',
- ...options,
- });
-
-/**
- * Finds Pets by tags.
- * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
- */
-export const findPetsByTagsRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/pet/findByTags',
- ...options,
- });
-
-/**
- * Deletes a pet.
- * Delete a pet.
- */
-export const deletePetRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'DELETE',
- responseStyle: 'data',
- url: '/pet/{petId}',
- ...options,
- });
-
-/**
- * Find pet by ID.
- * Returns a single pet.
- */
-export const getPetByIdRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/pet/{petId}',
- ...options,
- });
-
-/**
- * Updates a pet in the store with form data.
- * Updates a pet resource based on the form data.
- */
-export const updatePetWithFormRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/pet/{petId}',
- ...options,
- });
-
-/**
- * Uploads an image.
- * Upload image of the pet.
- */
-export const uploadFileRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/pet/{petId}/uploadImage',
- ...options,
- });
-
-/**
- * Returns pet inventories by status.
- * Returns a map of status codes to quantities.
- */
-export const getInventoryRequest = (
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/store/inventory',
- ...options,
- });
-
-/**
- * Place an order for a pet.
- * Place a new order in the store.
- */
-export const placeOrderRequest = (
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/store/order',
- ...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.
- */
-export const deleteOrderRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'DELETE',
- responseStyle: 'data',
- url: '/store/order/{orderId}',
- ...options,
- });
-
-/**
- * Find purchase order by ID.
- * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
- */
-export const getOrderByIdRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/store/order/{orderId}',
- ...options,
- });
-
-/**
- * Create user.
- * This can only be done by the logged in user.
- */
-export const createUserRequest = (
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/user',
- ...options,
- });
-
-/**
- * Creates list of users with given input array.
- * Creates list of users with given input array.
- */
-export const createUsersWithListInputRequest = <
- ThrowOnError extends boolean = false,
->(
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'POST',
- responseStyle: 'data',
- url: '/user/createWithList',
- ...options,
- });
-
-/**
- * Logs user into the system.
- * Log into the system.
- */
-export const loginUserRequest = (
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/user/login',
- ...options,
- });
-
-/**
- * Logs out current logged in user session.
- * Log user out of the system.
- */
-export const logoutUserRequest = (
- options?: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/user/logout',
- ...options,
- });
-
-/**
- * Delete user resource.
- * This can only be done by the logged in user.
- */
-export const deleteUserRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'DELETE',
- responseStyle: 'data',
- url: '/user/{username}',
- ...options,
- });
-
-/**
- * Get user by user name.
- * Get user detail based on username.
- */
-export const getUserByNameRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'GET',
- responseStyle: 'data',
- url: '/user/{username}',
- ...options,
- });
-
-/**
- * Update user resource.
- * This can only be done by the logged in user.
- */
-export const updateUserRequest = (
- options: Options,
-): HttpRequest =>
- (options?.client ?? _heyApiClient).requestOptions({
- method: 'PUT',
- responseStyle: 'data',
- url: '/user/{username}',
- ...options,
- });
diff --git a/examples/openapi-ts-angular-common/src/client/client.gen.ts b/examples/openapi-ts-angular-common/src/client/client.gen.ts
index 37fae577c..5bd9edd4b 100644
--- a/examples/openapi-ts-angular-common/src/client/client.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/client.gen.ts
@@ -1,12 +1,12 @@
// This file is auto-generated by @hey-api/openapi-ts
import {
- type ClientOptions as DefaultClientOptions,
+ type ClientOptions,
type Config,
createClient,
createConfig,
} from './client';
-import type { ClientOptions } from './types.gen';
+import type { ClientOptions as ClientOptions2 } from './types.gen';
/**
* The `createClientConfig()` function will be called on client initialization
@@ -16,13 +16,12 @@ import type { ClientOptions } from './types.gen';
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
-export type CreateClientConfig =
- (
- override?: Config,
- ) => Config & T>;
+export type CreateClientConfig = (
+ override?: Config,
+) => Config & T>;
export const client = createClient(
- createConfig({
+ createConfig({
baseUrl: 'https://petstore3.swagger.io/api/v3',
throwOnError: true,
}),
diff --git a/examples/openapi-ts-angular-common/src/client/client/client.gen.ts b/examples/openapi-ts-angular-common/src/client/client/client.gen.ts
index 93b77ace4..1555ca230 100644
--- a/examples/openapi-ts-angular-common/src/client/client/client.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/client/client.gen.ts
@@ -18,6 +18,7 @@ import { filter } from 'rxjs/operators';
import { createSseClient } from '../core/serverSentEvents.gen';
import type { HttpMethod } from '../core/types.gen';
+import { getValidRequestBody } from '../core/utils.gen';
import type {
Client,
Config,
@@ -69,7 +70,7 @@ export const createClient = (config: Config = {}): Client => {
...options,
headers: mergeHeaders(_config.headers, options.headers),
httpClient: options.httpClient ?? _config.httpClient,
- serializedBody: options.body as any,
+ serializedBody: undefined,
};
if (!opts.httpClient) {
@@ -83,12 +84,12 @@ export const createClient = (config: Config = {}): Client => {
}
}
- if (opts.body && opts.bodySerializer) {
+ if (opts.body !== undefined && opts.bodySerializer) {
opts.serializedBody = opts.bodySerializer(opts.body);
}
// remove Content-Type header if body is empty to avoid sending invalid requests
- if (opts.serializedBody === undefined || opts.serializedBody === '') {
+ if (opts.body === undefined || opts.serializedBody === '') {
opts.headers.delete('Content-Type');
}
@@ -97,7 +98,7 @@ export const createClient = (config: Config = {}): Client => {
const req = new HttpRequest(
opts.method ?? 'GET',
url,
- opts.serializedBody || null,
+ getValidRequestBody(opts),
{
redirect: 'follow',
...opts,
@@ -130,7 +131,7 @@ export const createClient = (config: Config = {}): Client => {
let req = initialReq;
- for (const fn of interceptors.request._fns) {
+ for (const fn of interceptors.request.fns) {
if (fn) {
req = await fn(req, opts as any);
}
@@ -151,7 +152,7 @@ export const createClient = (config: Config = {}): Client => {
.pipe(filter((event) => event.type === HttpEventType.Response)),
)) as HttpResponse;
- for (const fn of interceptors.response._fns) {
+ for (const fn of interceptors.response.fns) {
if (fn) {
result.response = await fn(result.response, req, opts as any);
}
@@ -177,7 +178,7 @@ export const createClient = (config: Config = {}): Client => {
let finalError = error instanceof HttpErrorResponse ? error.error : error;
- for (const fn of interceptors.error._fns) {
+ for (const fn of interceptors.error.fns) {
if (fn) {
finalError = (await fn(
finalError,
diff --git a/examples/openapi-ts-angular-common/src/client/client/index.ts b/examples/openapi-ts-angular-common/src/client/client/index.ts
index 318a84b6a..cbf8dfeed 100644
--- a/examples/openapi-ts-angular-common/src/client/client/index.ts
+++ b/examples/openapi-ts-angular-common/src/client/client/index.ts
@@ -8,6 +8,7 @@ export {
urlSearchParamsBodySerializer,
} from '../core/bodySerializer.gen';
export { buildClientParams } from '../core/params.gen';
+export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
export { createClient } from './client.gen';
export type {
Client,
diff --git a/examples/openapi-ts-angular-common/src/client/client/utils.gen.ts b/examples/openapi-ts-angular-common/src/client/client/utils.gen.ts
index b90ad7e5e..64a5d8b09 100644
--- a/examples/openapi-ts-angular-common/src/client/client/utils.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/client/utils.gen.ts
@@ -345,67 +345,61 @@ type ResInterceptor = (
) => Res | Promise;
class Interceptors {
- _fns: (Interceptor | null)[];
+ fns: Array = [];
- constructor() {
- this._fns = [];
+ clear(): void {
+ this.fns = [];
}
- clear() {
- this._fns = [];
- }
-
- getInterceptorIndex(id: number | Interceptor): number {
- if (typeof id === 'number') {
- return this._fns[id] ? id : -1;
- } else {
- return this._fns.indexOf(id);
+ eject(id: number | Interceptor): void {
+ const index = this.getInterceptorIndex(id);
+ if (this.fns[index]) {
+ this.fns[index] = null;
}
}
- exists(id: number | Interceptor) {
+
+ exists(id: number | Interceptor): boolean {
const index = this.getInterceptorIndex(id);
- return !!this._fns[index];
+ return Boolean(this.fns[index]);
}
- eject(id: number | Interceptor) {
- const index = this.getInterceptorIndex(id);
- if (this._fns[index]) {
- this._fns[index] = null;
+ getInterceptorIndex(id: number | Interceptor): number {
+ if (typeof id === 'number') {
+ return this.fns[id] ? id : -1;
}
+ return this.fns.indexOf(id);
}
- update(id: number | Interceptor, fn: Interceptor) {
+ update(
+ id: number | Interceptor,
+ fn: Interceptor,
+ ): number | Interceptor | false {
const index = this.getInterceptorIndex(id);
- if (this._fns[index]) {
- this._fns[index] = fn;
+ if (this.fns[index]) {
+ this.fns[index] = fn;
return id;
- } else {
- return false;
}
+ return false;
}
- use(fn: Interceptor) {
- this._fns = [...this._fns, fn];
- return this._fns.length - 1;
+ use(fn: Interceptor): number {
+ this.fns.push(fn);
+ return this.fns.length - 1;
}
}
-// `createInterceptors()` response, meant for external use as it does not
-// expose internals
export interface Middleware {
- error: Pick<
- Interceptors>,
- 'eject' | 'use'
- >;
- request: Pick>, 'eject' | 'use'>;
- response: Pick<
- Interceptors>,
- 'eject' | 'use'
- >;
+ error: Interceptors>;
+ request: Interceptors>;
+ response: Interceptors>;
}
-// do not add `Middleware` as return type so we can use _fns internally
-export const createInterceptors = () => ({
+export const createInterceptors = (): Middleware<
+ Req,
+ Res,
+ Err,
+ Options
+> => ({
error: new Interceptors>(),
request: new Interceptors>(),
response: new Interceptors>(),
diff --git a/examples/openapi-ts-angular-common/src/client/core/queryKeySerializer.gen.ts b/examples/openapi-ts-angular-common/src/client/core/queryKeySerializer.gen.ts
new file mode 100644
index 000000000..d3bb68396
--- /dev/null
+++ b/examples/openapi-ts-angular-common/src/client/core/queryKeySerializer.gen.ts
@@ -0,0 +1,136 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+/**
+ * JSON-friendly union that mirrors what Pinia Colada can hash.
+ */
+export type JsonValue =
+ | null
+ | string
+ | number
+ | boolean
+ | JsonValue[]
+ | { [key: string]: JsonValue };
+
+/**
+ * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
+ */
+export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
+ if (
+ value === undefined ||
+ typeof value === 'function' ||
+ typeof value === 'symbol'
+ ) {
+ return undefined;
+ }
+ if (typeof value === 'bigint') {
+ return value.toString();
+ }
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+ return value;
+};
+
+/**
+ * Safely stringifies a value and parses it back into a JsonValue.
+ */
+export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
+ try {
+ const json = JSON.stringify(input, queryKeyJsonReplacer);
+ if (json === undefined) {
+ return undefined;
+ }
+ return JSON.parse(json) as JsonValue;
+ } catch {
+ return undefined;
+ }
+};
+
+/**
+ * Detects plain objects (including objects with a null prototype).
+ */
+const isPlainObject = (value: unknown): value is Record => {
+ if (value === null || typeof value !== 'object') {
+ return false;
+ }
+ const prototype = Object.getPrototypeOf(value as object);
+ return prototype === Object.prototype || prototype === null;
+};
+
+/**
+ * Turns URLSearchParams into a sorted JSON object for deterministic keys.
+ */
+const serializeSearchParams = (params: URLSearchParams): JsonValue => {
+ const entries = Array.from(params.entries()).sort(([a], [b]) =>
+ a.localeCompare(b),
+ );
+ const result: Record = {};
+
+ for (const [key, value] of entries) {
+ const existing = result[key];
+ if (existing === undefined) {
+ result[key] = value;
+ continue;
+ }
+
+ if (Array.isArray(existing)) {
+ (existing as string[]).push(value);
+ } else {
+ result[key] = [existing, value];
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Normalizes any accepted value into a JSON-friendly shape for query keys.
+ */
+export const serializeQueryKeyValue = (
+ value: unknown,
+): JsonValue | undefined => {
+ if (value === null) {
+ return null;
+ }
+
+ if (
+ typeof value === 'string' ||
+ typeof value === 'number' ||
+ typeof value === 'boolean'
+ ) {
+ return value;
+ }
+
+ if (
+ value === undefined ||
+ typeof value === 'function' ||
+ typeof value === 'symbol'
+ ) {
+ return undefined;
+ }
+
+ if (typeof value === 'bigint') {
+ return value.toString();
+ }
+
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+
+ if (Array.isArray(value)) {
+ return stringifyToJsonValue(value);
+ }
+
+ if (
+ typeof URLSearchParams !== 'undefined' &&
+ value instanceof URLSearchParams
+ ) {
+ return serializeSearchParams(value);
+ }
+
+ if (isPlainObject(value)) {
+ return stringifyToJsonValue(value);
+ }
+
+ return undefined;
+};
diff --git a/examples/openapi-ts-angular-common/src/client/core/utils.gen.ts b/examples/openapi-ts-angular-common/src/client/core/utils.gen.ts
index ac31396fe..0b5389d08 100644
--- a/examples/openapi-ts-angular-common/src/client/core/utils.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/core/utils.gen.ts
@@ -1,6 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { QuerySerializer } from './bodySerializer.gen';
+import type { BodySerializer, QuerySerializer } from './bodySerializer.gen';
import {
type ArraySeparatorStyle,
serializeArrayParam,
@@ -112,3 +112,32 @@ export const getUrl = ({
}
return url;
};
+
+export function getValidRequestBody(options: {
+ body?: unknown;
+ bodySerializer?: BodySerializer | null;
+ serializedBody?: unknown;
+}) {
+ const hasBody = options.body !== undefined;
+ const isSerializedBody = hasBody && options.bodySerializer;
+
+ if (isSerializedBody) {
+ if ('serializedBody' in options) {
+ const hasSerializedBody =
+ options.serializedBody !== undefined && options.serializedBody !== '';
+
+ return hasSerializedBody ? options.serializedBody : null;
+ }
+
+ // not all clients implement a serializedBody property (i.e. client-axios)
+ return options.body !== '' ? options.body : null;
+ }
+
+ // plain/text body
+ if (hasBody) {
+ return options.body;
+ }
+
+ // no body was provided
+ return undefined;
+}
diff --git a/examples/openapi-ts-angular-common/src/client/index.ts b/examples/openapi-ts-angular-common/src/client/index.ts
index 6921f209d..89fcd5868 100644
--- a/examples/openapi-ts-angular-common/src/client/index.ts
+++ b/examples/openapi-ts-angular-common/src/client/index.ts
@@ -1,5 +1,5 @@
// This file is auto-generated by @hey-api/openapi-ts
-export * from './@angular/common/http/requests.gen';
-export * from './@angular/common/http/resources.gen';
+
+export * from './@angular/common.gen';
export * from './sdk.gen';
-export * from './types.gen';
+export type * from './types.gen';
diff --git a/examples/openapi-ts-angular-common/src/client/sdk.gen.ts b/examples/openapi-ts-angular-common/src/client/sdk.gen.ts
index 072abae41..d1c943a6c 100644
--- a/examples/openapi-ts-angular-common/src/client/sdk.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/sdk.gen.ts
@@ -1,7 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { Client, Options as ClientOptions, TDataShape } from './client';
-import { client as _heyApiClient } from './client.gen';
+import type { Client, Options as Options2, TDataShape } from './client';
+import { client } from './client.gen';
import type {
AddPetData,
AddPetErrors,
@@ -65,7 +65,7 @@ import type {
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
-> = ClientOptions & {
+> = Options2 & {
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
@@ -81,12 +81,13 @@ export type Options<
/**
* Add a new pet to the store.
+ *
* Add a new pet to the store.
*/
export const addPet = (
options: Options,
) =>
- (options.client ?? _heyApiClient).post<
+ (options.client ?? client).post<
AddPetResponses,
AddPetErrors,
ThrowOnError,
@@ -109,12 +110,13 @@ export const addPet = (
/**
* Update an existing pet.
+ *
* Update an existing pet by Id.
*/
export const updatePet = (
options: Options,
) =>
- (options.client ?? _heyApiClient).put<
+ (options.client ?? client).put<
UpdatePetResponses,
UpdatePetErrors,
ThrowOnError,
@@ -137,12 +139,13 @@ export const updatePet = (
/**
* Finds Pets by status.
+ *
* Multiple status values can be provided with comma separated strings.
*/
export const findPetsByStatus = (
options: Options,
) =>
- (options.client ?? _heyApiClient).get<
+ (options.client ?? client).get<
FindPetsByStatusResponses,
FindPetsByStatusErrors,
ThrowOnError,
@@ -161,12 +164,13 @@ export const findPetsByStatus = (
/**
* Finds Pets by tags.
+ *
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*/
export const findPetsByTags = (
options: Options,
) =>
- (options.client ?? _heyApiClient).get<
+ (options.client ?? client).get<
FindPetsByTagsResponses,
FindPetsByTagsErrors,
ThrowOnError,
@@ -185,12 +189,13 @@ export const findPetsByTags = (
/**
* Deletes a pet.
+ *
* Delete a pet.
*/
export const deletePet = (
options: Options,
) =>
- (options.client ?? _heyApiClient).delete<
+ (options.client ?? client).delete<
DeletePetResponses,
DeletePetErrors,
ThrowOnError,
@@ -209,12 +214,13 @@ export const deletePet = (
/**
* Find pet by ID.
+ *
* Returns a single pet.
*/
export const getPetById = (
options: Options,
) =>
- (options.client ?? _heyApiClient).get<
+ (options.client ?? client).get<
GetPetByIdResponses,
GetPetByIdErrors,
ThrowOnError,
@@ -237,12 +243,13 @@ export const getPetById = (
/**
* Updates a pet in the store with form data.
+ *
* Updates a pet resource based on the form data.
*/
export const updatePetWithForm = (
options: Options,
) =>
- (options.client ?? _heyApiClient).post<
+ (options.client ?? client).post<
UpdatePetWithFormResponses,
UpdatePetWithFormErrors,
ThrowOnError,
@@ -261,12 +268,13 @@ export const updatePetWithForm = (
/**
* Uploads an image.
+ *
* Upload image of the pet.
*/
export const uploadFile = (
options: Options,
) =>
- (options.client ?? _heyApiClient).post<
+ (options.client ?? client).post<
UploadFileResponses,
UploadFileErrors,
ThrowOnError,
@@ -290,12 +298,13 @@ export const uploadFile = (
/**
* Returns pet inventories by status.
+ *
* Returns a map of status codes to quantities.
*/
export const getInventory = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).get<
+ (options?.client ?? client).get<
GetInventoryResponses,
GetInventoryErrors,
ThrowOnError,
@@ -314,12 +323,13 @@ export const getInventory = (
/**
* Place an order for a pet.
+ *
* Place a new order in the store.
*/
export const placeOrder = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).post<
+ (options?.client ?? client).post<
PlaceOrderResponses,
PlaceOrderErrors,
ThrowOnError,
@@ -336,12 +346,13 @@ export const placeOrder = (
/**
* 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 deleteOrder = (
options: Options,
) =>
- (options.client ?? _heyApiClient).delete<
+ (options.client ?? client).delete<
DeleteOrderResponses,
DeleteOrderErrors,
ThrowOnError,
@@ -354,12 +365,13 @@ export const deleteOrder = (
/**
* Find purchase order by ID.
+ *
* For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
*/
export const getOrderById = (
options: Options,
) =>
- (options.client ?? _heyApiClient).get<
+ (options.client ?? client).get<
GetOrderByIdResponses,
GetOrderByIdErrors,
ThrowOnError,
@@ -372,12 +384,13 @@ export const getOrderById = (
/**
* Create user.
+ *
* This can only be done by the logged in user.
*/
export const createUser = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).post<
+ (options?.client ?? client).post<
CreateUserResponses,
CreateUserErrors,
ThrowOnError,
@@ -394,12 +407,13 @@ export const createUser = (
/**
* Creates list of users with given input array.
+ *
* Creates list of users with given input array.
*/
export const createUsersWithListInput = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).post<
+ (options?.client ?? client).post<
CreateUsersWithListInputResponses,
CreateUsersWithListInputErrors,
ThrowOnError,
@@ -416,12 +430,13 @@ export const createUsersWithListInput = (
/**
* Logs user into the system.
+ *
* Log into the system.
*/
export const loginUser = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).get<
+ (options?.client ?? client).get<
LoginUserResponses,
LoginUserErrors,
ThrowOnError,
@@ -434,12 +449,13 @@ export const loginUser = (
/**
* Logs out current logged in user session.
+ *
* Log user out of the system.
*/
export const logoutUser = (
options?: Options,
) =>
- (options?.client ?? _heyApiClient).get<
+ (options?.client ?? client).get<
LogoutUserResponses,
LogoutUserErrors,
ThrowOnError,
@@ -452,12 +468,13 @@ export const logoutUser = (
/**
* Delete user resource.
+ *
* This can only be done by the logged in user.
*/
export const deleteUser = (
options: Options,
) =>
- (options.client ?? _heyApiClient).delete<
+ (options.client ?? client).delete<
DeleteUserResponses,
DeleteUserErrors,
ThrowOnError,
@@ -470,12 +487,13 @@ export const deleteUser = (
/**
* Get user by user name.
+ *
* Get user detail based on username.
*/
export const getUserByName = (
options: Options,
) =>
- (options.client ?? _heyApiClient).get<
+ (options.client ?? client).get<
GetUserByNameResponses,
GetUserByNameErrors,
ThrowOnError,
@@ -488,12 +506,13 @@ export const getUserByName = (
/**
* Update user resource.
+ *
* This can only be done by the logged in user.
*/
export const updateUser = (
options: Options,
) =>
- (options.client ?? _heyApiClient).put<
+ (options.client ?? client).put<
UpdateUserResponses,
UpdateUserErrors,
ThrowOnError,
diff --git a/examples/openapi-ts-angular-common/src/client/types.gen.ts b/examples/openapi-ts-angular-common/src/client/types.gen.ts
index 992c17fb2..a2e6be0fa 100644
--- a/examples/openapi-ts-angular-common/src/client/types.gen.ts
+++ b/examples/openapi-ts-angular-common/src/client/types.gen.ts
@@ -1,5 +1,9 @@
// This file is auto-generated by @hey-api/openapi-ts
+export type ClientOptions = {
+ baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {});
+};
+
export type Order = {
complete?: boolean;
id?: number;
@@ -693,7 +697,3 @@ export type UpdateUserResponses = {
*/
200: unknown;
};
-
-export type ClientOptions = {
- baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {});
-};
diff --git a/examples/openapi-ts-angular/src/client/client.gen.ts b/examples/openapi-ts-angular/src/client/client.gen.ts
index f1e680045..069f4daba 100644
--- a/examples/openapi-ts-angular/src/client/client.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client.gen.ts
@@ -1,12 +1,12 @@
// This file is auto-generated by @hey-api/openapi-ts
import {
- type ClientOptions as DefaultClientOptions,
+ type ClientOptions,
type Config,
createClient,
createConfig,
} from './client';
-import type { ClientOptions } from './types.gen';
+import type { ClientOptions as ClientOptions2 } from './types.gen';
/**
* The `createClientConfig()` function will be called on client initialization
@@ -16,13 +16,12 @@ import type { ClientOptions } from './types.gen';
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
-export type CreateClientConfig =
- (
- override?: Config,
- ) => Config & T>;
+export type CreateClientConfig = (
+ override?: Config,
+) => Config & T>;
export const client = createClient(
- createConfig({
+ createConfig({
baseUrl: 'https://petstore3.swagger.io/api/v3',
}),
);
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 93b77ace4..1555ca230 100644
--- a/examples/openapi-ts-angular/src/client/client/client.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client/client.gen.ts
@@ -18,6 +18,7 @@ import { filter } from 'rxjs/operators';
import { createSseClient } from '../core/serverSentEvents.gen';
import type { HttpMethod } from '../core/types.gen';
+import { getValidRequestBody } from '../core/utils.gen';
import type {
Client,
Config,
@@ -69,7 +70,7 @@ export const createClient = (config: Config = {}): Client => {
...options,
headers: mergeHeaders(_config.headers, options.headers),
httpClient: options.httpClient ?? _config.httpClient,
- serializedBody: options.body as any,
+ serializedBody: undefined,
};
if (!opts.httpClient) {
@@ -83,12 +84,12 @@ export const createClient = (config: Config = {}): Client => {
}
}
- if (opts.body && opts.bodySerializer) {
+ if (opts.body !== undefined && opts.bodySerializer) {
opts.serializedBody = opts.bodySerializer(opts.body);
}
// remove Content-Type header if body is empty to avoid sending invalid requests
- if (opts.serializedBody === undefined || opts.serializedBody === '') {
+ if (opts.body === undefined || opts.serializedBody === '') {
opts.headers.delete('Content-Type');
}
@@ -97,7 +98,7 @@ export const createClient = (config: Config = {}): Client => {
const req = new HttpRequest(
opts.method ?? 'GET',
url,
- opts.serializedBody || null,
+ getValidRequestBody(opts),
{
redirect: 'follow',
...opts,
@@ -130,7 +131,7 @@ export const createClient = (config: Config = {}): Client => {
let req = initialReq;
- for (const fn of interceptors.request._fns) {
+ for (const fn of interceptors.request.fns) {
if (fn) {
req = await fn(req, opts as any);
}
@@ -151,7 +152,7 @@ export const createClient = (config: Config = {}): Client => {
.pipe(filter((event) => event.type === HttpEventType.Response)),
)) as HttpResponse;
- for (const fn of interceptors.response._fns) {
+ for (const fn of interceptors.response.fns) {
if (fn) {
result.response = await fn(result.response, req, opts as any);
}
@@ -177,7 +178,7 @@ export const createClient = (config: Config = {}): Client => {
let finalError = error instanceof HttpErrorResponse ? error.error : error;
- for (const fn of interceptors.error._fns) {
+ for (const fn of interceptors.error.fns) {
if (fn) {
finalError = (await fn(
finalError,
diff --git a/examples/openapi-ts-angular/src/client/client/index.ts b/examples/openapi-ts-angular/src/client/client/index.ts
index 318a84b6a..cbf8dfeed 100644
--- a/examples/openapi-ts-angular/src/client/client/index.ts
+++ b/examples/openapi-ts-angular/src/client/client/index.ts
@@ -8,6 +8,7 @@ export {
urlSearchParamsBodySerializer,
} from '../core/bodySerializer.gen';
export { buildClientParams } from '../core/params.gen';
+export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
export { createClient } from './client.gen';
export type {
Client,
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 b90ad7e5e..64a5d8b09 100644
--- a/examples/openapi-ts-angular/src/client/client/utils.gen.ts
+++ b/examples/openapi-ts-angular/src/client/client/utils.gen.ts
@@ -345,67 +345,61 @@ type ResInterceptor = (
) => Res | Promise;
class Interceptors {
- _fns: (Interceptor | null)[];
+ fns: Array = [];
- constructor() {
- this._fns = [];
+ clear(): void {
+ this.fns = [];
}
- clear() {
- this._fns = [];
- }
-
- getInterceptorIndex(id: number | Interceptor): number {
- if (typeof id === 'number') {
- return this._fns[id] ? id : -1;
- } else {
- return this._fns.indexOf(id);
+ eject(id: number | Interceptor): void {
+ const index = this.getInterceptorIndex(id);
+ if (this.fns[index]) {
+ this.fns[index] = null;
}
}
- exists(id: number | Interceptor) {
+
+ exists(id: number | Interceptor): boolean {
const index = this.getInterceptorIndex(id);
- return !!this._fns[index];
+ return Boolean(this.fns[index]);
}
- eject(id: number | Interceptor) {
- const index = this.getInterceptorIndex(id);
- if (this._fns[index]) {
- this._fns[index] = null;
+ getInterceptorIndex(id: number | Interceptor): number {
+ if (typeof id === 'number') {
+ return this.fns[id] ? id : -1;
}
+ return this.fns.indexOf(id);
}
- update(id: number | Interceptor, fn: Interceptor) {
+ update(
+ id: number | Interceptor,
+ fn: Interceptor,
+ ): number | Interceptor | false {
const index = this.getInterceptorIndex(id);
- if (this._fns[index]) {
- this._fns[index] = fn;
+ if (this.fns[index]) {
+ this.fns[index] = fn;
return id;
- } else {
- return false;
}
+ return false;
}
- use(fn: Interceptor) {
- this._fns = [...this._fns, fn];
- return this._fns.length - 1;
+ use(fn: Interceptor): number {
+ this.fns.push(fn);
+ return this.fns.length - 1;
}
}
-// `createInterceptors()` response, meant for external use as it does not
-// expose internals
export interface Middleware {
- error: Pick<
- Interceptors>,
- 'eject' | 'use'
- >;
- request: Pick>, 'eject' | 'use'>;
- response: Pick<
- Interceptors>,
- 'eject' | 'use'
- >;
+ error: Interceptors>;
+ request: Interceptors>;
+ response: Interceptors>;
}
-// do not add `Middleware` as return type so we can use _fns internally
-export const createInterceptors = () => ({
+export const createInterceptors = (): Middleware<
+ Req,
+ Res,
+ Err,
+ Options
+> => ({
error: new Interceptors>(),
request: new Interceptors>(),
response: new Interceptors>(),
diff --git a/examples/openapi-ts-angular/src/client/core/queryKeySerializer.gen.ts b/examples/openapi-ts-angular/src/client/core/queryKeySerializer.gen.ts
new file mode 100644
index 000000000..d3bb68396
--- /dev/null
+++ b/examples/openapi-ts-angular/src/client/core/queryKeySerializer.gen.ts
@@ -0,0 +1,136 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+/**
+ * JSON-friendly union that mirrors what Pinia Colada can hash.
+ */
+export type JsonValue =
+ | null
+ | string
+ | number
+ | boolean
+ | JsonValue[]
+ | { [key: string]: JsonValue };
+
+/**
+ * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
+ */
+export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
+ if (
+ value === undefined ||
+ typeof value === 'function' ||
+ typeof value === 'symbol'
+ ) {
+ return undefined;
+ }
+ if (typeof value === 'bigint') {
+ return value.toString();
+ }
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+ return value;
+};
+
+/**
+ * Safely stringifies a value and parses it back into a JsonValue.
+ */
+export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
+ try {
+ const json = JSON.stringify(input, queryKeyJsonReplacer);
+ if (json === undefined) {
+ return undefined;
+ }
+ return JSON.parse(json) as JsonValue;
+ } catch {
+ return undefined;
+ }
+};
+
+/**
+ * Detects plain objects (including objects with a null prototype).
+ */
+const isPlainObject = (value: unknown): value is Record => {
+ if (value === null || typeof value !== 'object') {
+ return false;
+ }
+ const prototype = Object.getPrototypeOf(value as object);
+ return prototype === Object.prototype || prototype === null;
+};
+
+/**
+ * Turns URLSearchParams into a sorted JSON object for deterministic keys.
+ */
+const serializeSearchParams = (params: URLSearchParams): JsonValue => {
+ const entries = Array.from(params.entries()).sort(([a], [b]) =>
+ a.localeCompare(b),
+ );
+ const result: Record = {};
+
+ for (const [key, value] of entries) {
+ const existing = result[key];
+ if (existing === undefined) {
+ result[key] = value;
+ continue;
+ }
+
+ if (Array.isArray(existing)) {
+ (existing as string[]).push(value);
+ } else {
+ result[key] = [existing, value];
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Normalizes any accepted value into a JSON-friendly shape for query keys.
+ */
+export const serializeQueryKeyValue = (
+ value: unknown,
+): JsonValue | undefined => {
+ if (value === null) {
+ return null;
+ }
+
+ if (
+ typeof value === 'string' ||
+ typeof value === 'number' ||
+ typeof value === 'boolean'
+ ) {
+ return value;
+ }
+
+ if (
+ value === undefined ||
+ typeof value === 'function' ||
+ typeof value === 'symbol'
+ ) {
+ return undefined;
+ }
+
+ if (typeof value === 'bigint') {
+ return value.toString();
+ }
+
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+
+ if (Array.isArray(value)) {
+ return stringifyToJsonValue(value);
+ }
+
+ if (
+ typeof URLSearchParams !== 'undefined' &&
+ value instanceof URLSearchParams
+ ) {
+ return serializeSearchParams(value);
+ }
+
+ if (isPlainObject(value)) {
+ return stringifyToJsonValue(value);
+ }
+
+ return undefined;
+};
diff --git a/examples/openapi-ts-angular/src/client/core/utils.gen.ts b/examples/openapi-ts-angular/src/client/core/utils.gen.ts
index ac31396fe..0b5389d08 100644
--- a/examples/openapi-ts-angular/src/client/core/utils.gen.ts
+++ b/examples/openapi-ts-angular/src/client/core/utils.gen.ts
@@ -1,6 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { QuerySerializer } from './bodySerializer.gen';
+import type { BodySerializer, QuerySerializer } from './bodySerializer.gen';
import {
type ArraySeparatorStyle,
serializeArrayParam,
@@ -112,3 +112,32 @@ export const getUrl = ({
}
return url;
};
+
+export function getValidRequestBody(options: {
+ body?: unknown;
+ bodySerializer?: BodySerializer | null;
+ serializedBody?: unknown;
+}) {
+ const hasBody = options.body !== undefined;
+ const isSerializedBody = hasBody && options.bodySerializer;
+
+ if (isSerializedBody) {
+ if ('serializedBody' in options) {
+ const hasSerializedBody =
+ options.serializedBody !== undefined && options.serializedBody !== '';
+
+ return hasSerializedBody ? options.serializedBody : null;
+ }
+
+ // not all clients implement a serializedBody property (i.e. client-axios)
+ return options.body !== '' ? options.body : null;
+ }
+
+ // plain/text body
+ if (hasBody) {
+ return options.body;
+ }
+
+ // no body was provided
+ return undefined;
+}
diff --git a/examples/openapi-ts-angular/src/client/index.ts b/examples/openapi-ts-angular/src/client/index.ts
index 688e3c912..57ed02bf5 100644
--- a/examples/openapi-ts-angular/src/client/index.ts
+++ b/examples/openapi-ts-angular/src/client/index.ts
@@ -1,3 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
+
export * from './sdk.gen';
-export * from './types.gen';
+export type * from './types.gen';
diff --git a/examples/openapi-ts-angular/src/client/sdk.gen.ts b/examples/openapi-ts-angular/src/client/sdk.gen.ts
index 2c40f5893..9d7d0b4e5 100644
--- a/examples/openapi-ts-angular/src/client/sdk.gen.ts
+++ b/examples/openapi-ts-angular/src/client/sdk.gen.ts
@@ -2,8 +2,8 @@
import { Injectable } from '@angular/core';
-import type { Client, Options as ClientOptions, TDataShape } from './client';
-import { client as _heyApiClient } from './client.gen';
+import type { Client, Options as Options2, TDataShape } from './client';
+import { client } from './client.gen';
import type {
AddPetData,
AddPetErrors,
@@ -67,7 +67,7 @@ import type {
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
-> = ClientOptions & {
+> = Options2 & {
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
@@ -87,12 +87,13 @@ export type Options<
export class PetService {
/**
* Add a new pet to the store.
+ *
* Add a new pet to the store.
*/
public addPet(
options: Options,
) {
- return (options.client ?? _heyApiClient).post<
+ return (options.client ?? client).post<
AddPetResponses,
AddPetErrors,
ThrowOnError
@@ -114,12 +115,13 @@ export class PetService {
/**
* Update an existing pet.
+ *
* Update an existing pet by Id.
*/
public updatePet(
options: Options,
) {
- return (options.client ?? _heyApiClient).put<
+ return (options.client ?? client).put<
UpdatePetResponses,
UpdatePetErrors,
ThrowOnError
@@ -141,12 +143,13 @@ export class PetService {
/**
* Finds Pets by status.
+ *
* Multiple status values can be provided with comma separated strings.
*/
public findPetsByStatus(
options: Options,
) {
- return (options.client ?? _heyApiClient).get<
+ return (options.client ?? client).get<
FindPetsByStatusResponses,
FindPetsByStatusErrors,
ThrowOnError
@@ -164,12 +167,13 @@ export class PetService {
/**
* Finds Pets by tags.
+ *
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*/
public findPetsByTags(
options: Options