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

feat: add Next.js client

Lubos 2dc380ea c928e800

+2175 -471
+9
.changeset/dry-eggs-begin.md
··· 1 + --- 2 + '@hey-api/client-axios': patch 3 + '@hey-api/client-fetch': patch 4 + '@hey-api/client-next': patch 5 + '@hey-api/client-nuxt': patch 6 + '@hey-api/openapi-ts': patch 7 + --- 8 + 9 + fix: update keywords in package.json
+5
.changeset/light-jokes-grin.md
··· 1 + --- 2 + '@hey-api/client-next': minor 3 + --- 4 + 5 + feat: initial release
+5
.changeset/spotty-suns-build.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix: add Next.js client
+1 -1
docs/.vitepress/config/en.ts
··· 73 73 }, 74 74 { 75 75 link: '/openapi-ts/clients/next-js', 76 - text: 'Next.js <span data-soon>soon</span>', 76 + text: 'Next.js', 77 77 }, 78 78 { 79 79 link: '/openapi-ts/clients/nuxt',
+1 -1
docs/openapi-ts/clients.md
··· 27 27 28 28 - [Fetch API](/openapi-ts/clients/fetch) 29 29 - [Axios](/openapi-ts/clients/axios) 30 - - [Next.js](/openapi-ts/clients/next-js) <span data-soon>Soon</span> 30 + - [Next.js](/openapi-ts/clients/next-js) 31 31 - [Nuxt](/openapi-ts/clients/nuxt) 32 32 - [Legacy](/openapi-ts/clients/legacy) 33 33
+256 -2
docs/openapi-ts/clients/next-js.md
··· 3 3 description: Next.js client for Hey API. Compatible with all our features. 4 4 --- 5 5 6 - # Next.js <span data-soon>soon</span> 6 + # Next.js 7 7 8 8 ::: warning 9 - This feature isn't in development yet. Help us prioritize it by voting on [GitHub](https://github.com/hey-api/openapi-ts/issues/1515). 9 + Next.js client is currently in beta. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues). 10 10 ::: 11 11 12 12 [Next.js](https://nextjs.org/) is the React framework for the web. Used by some of the world's largest companies, Next.js enables you to create high-quality web applications with the power of React components. 13 13 14 + <!-- <button class="buttonLink" @click="(event) => embedProject('hey-api-client-next-example')(event)"> 15 + Live demo 16 + </button> --> 17 + 18 + ## Installation 19 + 20 + Start by adding `@hey-api/client-next` to your dependencies. 21 + 22 + ::: code-group 23 + 24 + ```sh [npm] 25 + npm install @hey-api/client-next 26 + ``` 27 + 28 + ```sh [pnpm] 29 + pnpm add @hey-api/client-next 30 + ``` 31 + 32 + ```sh [yarn] 33 + yarn add @hey-api/client-next 34 + ``` 35 + 36 + ```sh [bun] 37 + bun add @hey-api/client-next 38 + ``` 39 + 40 + ::: 41 + 42 + In your [configuration](/openapi-ts/get-started), add `@hey-api/client-next` to your plugins and you'll be ready to generate client artifacts. :tada: 43 + 44 + ::: code-group 45 + 46 + ```js [config] 47 + export default { 48 + input: 'path/to/openapi.json', 49 + output: 'src/client', 50 + plugins: ['@hey-api/client-next'], // [!code ++] 51 + }; 52 + ``` 53 + 54 + ```sh [cli] 55 + npx @hey-api/openapi-ts \ 56 + -i path/to/openapi.json \ 57 + -o src/client \ 58 + -c @hey-api/client-next # [!code ++] 59 + ``` 60 + 61 + ::: 62 + 63 + ## Configuration 64 + 65 + The Next.js client is built as a thin wrapper on top of [fetch](https://nextjs.org/docs/app/api-reference/functions/fetch), extending its functionality to work with Hey API. If you're already familiar with Fetch, configuring your client will feel like working directly with Fetch API. 66 + 67 + When we installed the client above, it created a [`client.gen.ts`](/openapi-ts/output#client) file. You will most likely want to configure the exported `client` instance. There are two ways to do that. 68 + 69 + ### `setConfig()` 70 + 71 + This is the simpler approach. You can call the `setConfig()` method at the beginning of your application or anytime you need to update the client configuration. You can pass any Fetch API configuration option to `setConfig()`, and even your own Fetch implementation. 72 + 73 + ```js 74 + import { client } from 'client/client.gen'; 75 + 76 + client.setConfig({ 77 + baseUrl: 'https://example.com', 78 + }); 79 + ``` 80 + 81 + The disadvantage of this approach is that your code may call the `client` instance before it's configured for the first time. Depending on your use case, you might need to use the second approach. 82 + 83 + ### Runtime API 84 + 85 + Since `client.gen.ts` is a generated file, we can't directly modify it. Instead, we can tell our configuration to use a custom file implementing the Runtime API. We do that by specifying the `runtimeConfigPath` option. 86 + 87 + ```js 88 + export default { 89 + input: 'path/to/openapi.json', 90 + output: 'src/client', 91 + plugins: [ 92 + { 93 + name: '@hey-api/client-next', 94 + runtimeConfigPath: './src/hey-api.ts', // [!code ++] 95 + }, 96 + ], 97 + }; 98 + ``` 99 + 100 + In our custom file, we need to export a `createClientConfig()` method. This function is a simple wrapper allowing us to override configuration values. 101 + 102 + ::: code-group 103 + 104 + ```ts [hey-api.ts] 105 + import type { CreateClientConfig } from '@hey-api/client-next'; 106 + 107 + export const createClientConfig: CreateClientConfig = (config) => ({ 108 + ...config, 109 + baseUrl: 'https://example.com', 110 + }); 111 + ``` 112 + 113 + ::: 114 + 115 + With this approach, `client.gen.ts` will call `createClientConfig()` before initializing the `client` instance. If needed, you can still use `setConfig()` to update the client configuration later. 116 + 117 + ### `createClient()` 118 + 119 + You can also create your own client instance. You can use it to manually send requests or point it to a different domain. 120 + 121 + ```js 122 + import { createClient } from '@hey-api/client-next'; 123 + 124 + const myClient = createClient({ 125 + baseUrl: 'https://example.com', 126 + }); 127 + ``` 128 + 129 + You can also pass this instance to any SDK function through the `client` option. This will override the default instance from `client.gen.ts`. 130 + 131 + ```js 132 + const response = await getFoo({ 133 + client: myClient, 134 + }); 135 + ``` 136 + 137 + ### SDKs 138 + 139 + Alternatively, you can pass the client configuration options to each SDK function. This is useful if you don't want to create a client instance for one-off use cases. 140 + 141 + ```js 142 + const response = await getFoo({ 143 + baseUrl: 'https://example.com', // <-- override default configuration 144 + }); 145 + ``` 146 + 147 + ## Interceptors 148 + 149 + Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. They can be added with `use` and removed with `eject`. Fetch API does not have the interceptor functionality, so we implement our own. Below is an example request interceptor 150 + 151 + ::: code-group 152 + 153 + ```js [use] 154 + import { client } from 'client/client.gen'; 155 + 156 + // Supports async functions 157 + client.interceptors.request.use(async (options) => { 158 + // do something 159 + }); 160 + ``` 161 + 162 + ```js [eject] 163 + import { client } from 'client/client.gen'; 164 + 165 + client.interceptors.request.eject((options) => { 166 + // do something 167 + }); 168 + ``` 169 + 170 + ::: 171 + 172 + and an example response interceptor 173 + 174 + ::: code-group 175 + 176 + ```js [use] 177 + import { client } from 'client/client.gen'; 178 + 179 + client.interceptors.response.use((response) => { 180 + // do something 181 + return response; 182 + }); 183 + ``` 184 + 185 + ```js [eject] 186 + import { client } from 'client/client.gen'; 187 + 188 + client.interceptors.response.eject((response) => { 189 + // do something 190 + return response; 191 + }); 192 + ``` 193 + 194 + ::: 195 + 196 + ::: tip 197 + To eject, you must provide a reference to the function that was passed to `use()`. 198 + ::: 199 + 200 + ## Auth 201 + 202 + The SDKs include auth mechanisms for every endpoint. You will want to configure the `auth` field to pass the right token for each request. The `auth` field can be a string or a function returning a string representing the token. The returned value will be attached only to requests that require auth. 203 + 204 + ```js 205 + import { client } from 'client/client.gen'; 206 + 207 + client.setConfig({ 208 + auth: () => '<my_token>', // [!code ++] 209 + baseUrl: 'https://example.com', 210 + }); 211 + ``` 212 + 213 + If you're not using SDKs or generating auth, using interceptors is a common approach to configuring auth for each request. 214 + 215 + ```js 216 + import { client } from 'client/client.gen'; 217 + 218 + client.interceptors.request.use((options) => { 219 + options.headers.set('Authorization', 'Bearer <my_token>'); // [!code ++] 220 + }); 221 + ``` 222 + 223 + ## Build URL 224 + 225 + If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint. 226 + 227 + ```ts 228 + type FooData = { 229 + path: { 230 + fooId: number; 231 + }; 232 + query?: { 233 + bar?: string; 234 + }; 235 + url: '/foo/{fooId}'; 236 + }; 237 + 238 + const url = client.buildUrl<FooData>({ 239 + path: { 240 + fooId: 1, 241 + }, 242 + query: { 243 + bar: 'baz', 244 + }, 245 + url: '/foo/{fooId}', 246 + }); 247 + console.log(url); // prints '/foo/1?bar=baz' 248 + ``` 249 + 250 + ## Bundling 251 + 252 + Sometimes, you may not want to declare client packages as a dependency. This scenario is common if you're using Hey API to generate output that is repackaged and published for other consumers under your own brand. For such cases, our clients support bundling through the `client.bundle` configuration option. 253 + 254 + ```js 255 + export default { 256 + input: 'path/to/openapi.json', 257 + output: 'src/client', 258 + plugins: [ 259 + { 260 + bundle: true, // [!code ++] 261 + name: '@hey-api/client-next', 262 + }, 263 + ], 264 + }; 265 + ``` 266 + 267 + <!--@include: ../../examples.md--> 14 268 <!--@include: ../../sponsors.md-->
+4
docs/openapi-ts/clients/nuxt.md
··· 11 11 12 12 [Nuxt](https://nuxt.com/) is an open source framework that makes web development intuitive and powerful. 13 13 14 + <!-- <button class="buttonLink" @click="(event) => embedProject('hey-api-client-fetch-example')(event)"> 15 + Live demo 16 + </button> --> 17 + 14 18 ## Installation 15 19 16 20 Start by adding `@hey-api/client-nuxt` to your dependencies.
+1 -1
examples/openapi-ts-next/app/client/client.gen.ts examples/openapi-ts-next/src/client/client.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - import { createClient, createConfig } from '@hey-api/client-fetch'; 3 + import { createClient, createConfig } from '@hey-api/client-next'; 4 4 5 5 import { createClientConfig } from '../hey-api'; 6 6
examples/openapi-ts-next/app/client/index.ts examples/openapi-ts-next/src/client/index.ts
+1 -1
examples/openapi-ts-next/app/client/sdk.gen.ts examples/openapi-ts-next/src/client/sdk.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - import type { Options } from '@hey-api/client-fetch'; 3 + import type { Options } from '@hey-api/client-next'; 4 4 5 5 import { client as _heyApiClient } from './client.gen'; 6 6 import type {
examples/openapi-ts-next/app/client/types.gen.ts examples/openapi-ts-next/src/client/types.gen.ts
+1 -1
examples/openapi-ts-next/app/hey-api.ts examples/openapi-ts-next/src/hey-api.ts
··· 1 - import type { CreateClientConfig } from '@hey-api/client-fetch'; 1 + import type { CreateClientConfig } from '@hey-api/client-next'; 2 2 3 3 export const createClientConfig: CreateClientConfig = (config) => ({ 4 4 ...config,
+11
examples/openapi-ts-next/app/layout.tsx
··· 3 3 import type { Metadata } from 'next'; 4 4 import { Geist, Geist_Mono } from 'next/font/google'; 5 5 6 + import { client } from '@/src/client/client.gen'; 7 + 8 + client.interceptors.request.use((options) => { 9 + console.log(options); 10 + }); 11 + 12 + client.interceptors.response.use((response, options) => { 13 + console.log(response, options); 14 + return response; 15 + }); 16 + 6 17 const geistSans = Geist({ 7 18 subsets: ['latin'], 8 19 variable: '--font-geist-sans',
+12 -8
examples/openapi-ts-next/app/page.tsx
··· 1 1 'use client'; 2 2 3 3 import Image from 'next/image'; 4 + import Link from 'next/link'; 4 5 import { useEffect, useState } from 'react'; 5 6 6 - import { getPetById } from './client/sdk.gen'; 7 - import type { Pet } from './client/types.gen'; 7 + import { getPetById } from '@/src/client/sdk.gen'; 8 + import type { Pet } from '@/src/client/types.gen'; 8 9 9 10 export default function Home() { 10 11 const [pet, setPet] = useState<Pet>(); ··· 13 14 useEffect(() => { 14 15 const fetchPet = async () => { 15 16 const { data } = await getPetById({ 17 + cache: 'force-cache', 18 + next: { 19 + revalidate: 10, 20 + tags: ['pet'], 21 + }, 16 22 path: { 17 23 petId, 18 24 }, ··· 64 70 /> 65 71 Random pet 66 72 </button> 67 - <a 73 + <Link 68 74 className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" 69 - href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 70 - target="_blank" 71 - rel="noopener noreferrer" 75 + href="/pet/8" 72 76 > 73 - Read our docs 74 - </a> 77 + Server component 78 + </Link> 75 79 </div> 76 80 </main> 77 81 <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
+124
examples/openapi-ts-next/app/pet/[id]/page.tsx
··· 1 + import Image from 'next/image'; 2 + import Link from 'next/link'; 3 + import { notFound } from 'next/navigation'; 4 + 5 + import { getPetById } from '@/src/client/sdk.gen'; 6 + 7 + async function getPet(id: string) { 8 + const petId = Number.parseInt(id, 10); 9 + const { data: pet } = await getPetById({ 10 + cache: 'force-cache', 11 + next: { 12 + revalidate: 10, 13 + tags: ['pet'], 14 + }, 15 + path: { 16 + petId, 17 + }, 18 + throwOnError: true, 19 + }); 20 + if (!pet) { 21 + notFound(); 22 + } 23 + return pet; 24 + } 25 + 26 + export async function generateMetadata({ 27 + params, 28 + }: { 29 + params: Promise<{ id: string }>; 30 + }) { 31 + const { id } = await params; 32 + const pet = await getPet(id); 33 + return { 34 + name: pet.name, 35 + }; 36 + } 37 + 38 + export default async function Blog({ 39 + params, 40 + }: { 41 + params: Promise<{ id: string }>; 42 + }) { 43 + const { id } = await params; 44 + const pet = await getPet(id); 45 + 46 + return ( 47 + <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> 48 + <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> 49 + <Image 50 + className="dark:invert" 51 + src="/next.svg" 52 + alt="Next.js logo" 53 + width={180} 54 + height={38} 55 + priority 56 + /> 57 + <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> 58 + <li className="mb-2"> 59 + Static pet name:{' '} 60 + <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> 61 + {pet.name} 62 + </code> 63 + </li> 64 + </ol> 65 + 66 + <div className="flex gap-4 items-center flex-col sm:flex-row"> 67 + <Link 68 + className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" 69 + href="/" 70 + > 71 + Client component 72 + </Link> 73 + </div> 74 + </main> 75 + <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> 76 + <a 77 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 78 + href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 79 + target="_blank" 80 + rel="noopener noreferrer" 81 + > 82 + <Image 83 + aria-hidden 84 + src="/file.svg" 85 + alt="File icon" 86 + width={16} 87 + height={16} 88 + /> 89 + Learn 90 + </a> 91 + <a 92 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 93 + href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 94 + target="_blank" 95 + rel="noopener noreferrer" 96 + > 97 + <Image 98 + aria-hidden 99 + src="/window.svg" 100 + alt="Window icon" 101 + width={16} 102 + height={16} 103 + /> 104 + Examples 105 + </a> 106 + <a 107 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 108 + href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 109 + target="_blank" 110 + rel="noopener noreferrer" 111 + > 112 + <Image 113 + aria-hidden 114 + src="/globe.svg" 115 + alt="Globe icon" 116 + width={16} 117 + height={16} 118 + /> 119 + Go to nextjs.org → 120 + </a> 121 + </footer> 122 + </div> 123 + ); 124 + }
+3 -3
examples/openapi-ts-next/openapi-ts.config.ts
··· 6 6 output: { 7 7 format: 'prettier', 8 8 lint: 'eslint', 9 - path: './app/client', 9 + path: './src/client', 10 10 }, 11 11 plugins: [ 12 12 { 13 - name: '@hey-api/client-fetch', 14 - runtimeConfigPath: './app/hey-api.ts', 13 + name: '@hey-api/client-next', 14 + runtimeConfigPath: './src/hey-api.ts', 15 15 }, 16 16 '@hey-api/sdk', 17 17 {
+1 -1
examples/openapi-ts-next/package.json
··· 10 10 "start": "next start" 11 11 }, 12 12 "dependencies": { 13 - "@hey-api/client-fetch": "workspace:*", 13 + "@hey-api/client-next": "workspace:*", 14 14 "next": "15.1.6", 15 15 "react": "19.0.0", 16 16 "react-dom": "19.0.0"
+1
packages/client-axios/package.json
··· 20 20 "keywords": [ 21 21 "axios", 22 22 "client", 23 + "codegen", 23 24 "http", 24 25 "javascript", 25 26 "openapi",
+50
packages/client-axios/src/__tests__/client.test.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { createClient } from '../client'; 4 + 5 + describe('buildUrl', () => { 6 + const client = createClient(); 7 + 8 + const scenarios: { 9 + options: Parameters<typeof client.buildUrl>[0]; 10 + url: string; 11 + }[] = [ 12 + { 13 + options: { 14 + url: '', 15 + }, 16 + url: '/', 17 + }, 18 + { 19 + options: { 20 + url: '/foo', 21 + }, 22 + url: '/foo', 23 + }, 24 + { 25 + options: { 26 + path: { 27 + fooId: 1, 28 + }, 29 + url: '/foo/{fooId}', 30 + }, 31 + url: '/foo/1', 32 + }, 33 + { 34 + options: { 35 + path: { 36 + fooId: 1, 37 + }, 38 + query: { 39 + bar: 'baz', 40 + }, 41 + url: '/foo/{fooId}', 42 + }, 43 + url: '/foo/1?bar=baz', 44 + }, 45 + ]; 46 + 47 + it.each(scenarios)('returns $url', ({ options, url }) => { 48 + expect(client.buildUrl(options)).toBe(url); 49 + }); 50 + });
+110
packages/client-axios/src/client.ts
··· 1 + import type { AxiosError, RawAxiosRequestHeaders } from 'axios'; 2 + import axios from 'axios'; 3 + 4 + import type { Client, Config } from './types'; 5 + import { 6 + buildUrl, 7 + createConfig, 8 + mergeConfigs, 9 + mergeHeaders, 10 + setAuthParams, 11 + } from './utils'; 12 + 13 + export const createClient = (config: Config = {}): Client => { 14 + let _config = mergeConfigs(createConfig(), config); 15 + 16 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 + const { auth, ...configWithoutAuth } = _config; 18 + const instance = axios.create(configWithoutAuth); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + instance.defaults = { 25 + ...instance.defaults, 26 + ..._config, 27 + // @ts-expect-error 28 + headers: mergeHeaders(instance.defaults.headers, _config.headers), 29 + }; 30 + return getConfig(); 31 + }; 32 + 33 + // @ts-expect-error 34 + const request: Client['request'] = async (options) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + axios: options.axios ?? _config.axios ?? instance, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + }; 41 + 42 + if (opts.security) { 43 + await setAuthParams({ 44 + ...opts, 45 + security: opts.security, 46 + }); 47 + } 48 + 49 + if (opts.body && opts.bodySerializer) { 50 + opts.body = opts.bodySerializer(opts.body); 51 + } 52 + 53 + const url = buildUrl(opts); 54 + 55 + try { 56 + // assign Axios here for consistency with fetch 57 + const _axios = opts.axios!; 58 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 59 + const { auth, ...optsWithoutAuth } = opts; 60 + const response = await _axios({ 61 + ...optsWithoutAuth, 62 + data: opts.body, 63 + headers: opts.headers as RawAxiosRequestHeaders, 64 + // let `paramsSerializer()` handle query params if it exists 65 + params: opts.paramsSerializer ? opts.query : undefined, 66 + url, 67 + }); 68 + 69 + let { data } = response; 70 + 71 + if (opts.responseType === 'json') { 72 + if (opts.responseValidator) { 73 + await opts.responseValidator(data); 74 + } 75 + 76 + if (opts.responseTransformer) { 77 + data = await opts.responseTransformer(data); 78 + } 79 + } 80 + 81 + return { 82 + ...response, 83 + data: data ?? {}, 84 + }; 85 + } catch (error) { 86 + const e = error as AxiosError; 87 + if (opts.throwOnError) { 88 + throw e; 89 + } 90 + // @ts-expect-error 91 + e.error = e.response?.data ?? {}; 92 + return e; 93 + } 94 + }; 95 + 96 + return { 97 + buildUrl, 98 + delete: (options) => request({ ...options, method: 'DELETE' }), 99 + get: (options) => request({ ...options, method: 'GET' }), 100 + getConfig, 101 + head: (options) => request({ ...options, method: 'HEAD' }), 102 + instance, 103 + options: (options) => request({ ...options, method: 'OPTIONS' }), 104 + patch: (options) => request({ ...options, method: 'PATCH' }), 105 + post: (options) => request({ ...options, method: 'POST' }), 106 + put: (options) => request({ ...options, method: 'PUT' }), 107 + request, 108 + setConfig, 109 + } as Client; 110 + };
+1 -111
packages/client-axios/src/index.ts
··· 1 - import type { AxiosError, RawAxiosRequestHeaders } from 'axios'; 2 - import axios from 'axios'; 3 - 4 - import type { Client, Config } from './types'; 5 - import { 6 - buildUrl, 7 - createConfig, 8 - mergeConfigs, 9 - mergeHeaders, 10 - setAuthParams, 11 - } from './utils'; 12 - 13 - export const createClient = (config: Config): Client => { 14 - let _config = mergeConfigs(createConfig(), config); 15 - 16 - // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 - const { auth, ...configWithoutAuth } = _config; 18 - const instance = axios.create(configWithoutAuth); 19 - 20 - const getConfig = (): Config => ({ ..._config }); 21 - 22 - const setConfig = (config: Config): Config => { 23 - _config = mergeConfigs(_config, config); 24 - instance.defaults = { 25 - ...instance.defaults, 26 - ..._config, 27 - // @ts-expect-error 28 - headers: mergeHeaders(instance.defaults.headers, _config.headers), 29 - }; 30 - return getConfig(); 31 - }; 32 - 33 - // @ts-expect-error 34 - const request: Client['request'] = async (options) => { 35 - const opts = { 36 - ..._config, 37 - ...options, 38 - axios: options.axios ?? _config.axios ?? instance, 39 - headers: mergeHeaders(_config.headers, options.headers), 40 - }; 41 - 42 - if (opts.security) { 43 - await setAuthParams({ 44 - ...opts, 45 - security: opts.security, 46 - }); 47 - } 48 - 49 - if (opts.body && opts.bodySerializer) { 50 - opts.body = opts.bodySerializer(opts.body); 51 - } 52 - 53 - const url = buildUrl(opts); 54 - 55 - try { 56 - // assign Axios here for consistency with fetch 57 - const _axios = opts.axios; 58 - // eslint-disable-next-line @typescript-eslint/no-unused-vars 59 - const { auth, ...optsWithoutAuth } = opts; 60 - const response = await _axios({ 61 - ...optsWithoutAuth, 62 - data: opts.body, 63 - headers: opts.headers as RawAxiosRequestHeaders, 64 - // let `paramsSerializer()` handle query params if it exists 65 - params: opts.paramsSerializer ? opts.query : undefined, 66 - url, 67 - }); 68 - 69 - let { data } = response; 70 - 71 - if (opts.responseType === 'json') { 72 - if (opts.responseValidator) { 73 - await opts.responseValidator(data); 74 - } 75 - 76 - if (opts.responseTransformer) { 77 - data = await opts.responseTransformer(data); 78 - } 79 - } 80 - 81 - return { 82 - ...response, 83 - data: data ?? {}, 84 - }; 85 - } catch (error) { 86 - const e = error as AxiosError; 87 - if (opts.throwOnError) { 88 - throw e; 89 - } 90 - // @ts-expect-error 91 - e.error = e.response?.data ?? {}; 92 - return e; 93 - } 94 - }; 95 - 96 - return { 97 - buildUrl, 98 - delete: (options) => request({ ...options, method: 'DELETE' }), 99 - get: (options) => request({ ...options, method: 'GET' }), 100 - getConfig, 101 - head: (options) => request({ ...options, method: 'HEAD' }), 102 - instance, 103 - options: (options) => request({ ...options, method: 'OPTIONS' }), 104 - patch: (options) => request({ ...options, method: 'PATCH' }), 105 - post: (options) => request({ ...options, method: 'POST' }), 106 - put: (options) => request({ ...options, method: 'PUT' }), 107 - request, 108 - setConfig, 109 - } as Client; 110 - }; 111 - 1 + export { createClient } from './client'; 112 2 export type { 113 3 Client, 114 4 Config,
+1
packages/client-fetch/package.json
··· 19 19 "funding": "https://github.com/sponsors/hey-api", 20 20 "keywords": [ 21 21 "client", 22 + "codegen", 22 23 "fetch", 23 24 "http", 24 25 "javascript",
+50
packages/client-fetch/src/__tests__/client.test.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { createClient } from '../client'; 4 + 5 + describe('buildUrl', () => { 6 + const client = createClient(); 7 + 8 + const scenarios: { 9 + options: Parameters<typeof client.buildUrl>[0]; 10 + url: string; 11 + }[] = [ 12 + { 13 + options: { 14 + url: '', 15 + }, 16 + url: '/', 17 + }, 18 + { 19 + options: { 20 + url: '/foo', 21 + }, 22 + url: '/foo', 23 + }, 24 + { 25 + options: { 26 + path: { 27 + fooId: 1, 28 + }, 29 + url: '/foo/{fooId}', 30 + }, 31 + url: '/foo/1', 32 + }, 33 + { 34 + options: { 35 + path: { 36 + fooId: 1, 37 + }, 38 + query: { 39 + bar: 'baz', 40 + }, 41 + url: '/foo/{fooId}', 42 + }, 43 + url: '/foo/1?bar=baz', 44 + }, 45 + ]; 46 + 47 + it.each(scenarios)('returns $url', ({ options, url }) => { 48 + expect(client.buildUrl(options)).toBe(url); 49 + }); 50 + });
+1 -1
packages/client-fetch/src/__tests__/index.test.ts packages/client-next/src/__tests__/client.test.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { createClient } from '../index'; 3 + import { createClient } from '../client'; 4 4 5 5 describe('buildUrl', () => { 6 6 const client = createClient();
+167
packages/client-fetch/src/client.ts
··· 1 + import type { Client, Config, RequestOptions } from './types'; 2 + import { 3 + buildUrl, 4 + createConfig, 5 + createInterceptors, 6 + getParseAs, 7 + mergeConfigs, 8 + mergeHeaders, 9 + setAuthParams, 10 + } from './utils'; 11 + 12 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 + body?: any; 14 + headers: ReturnType<typeof mergeHeaders>; 15 + }; 16 + 17 + export const createClient = (config: Config = {}): Client => { 18 + let _config = mergeConfigs(createConfig(), config); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + return getConfig(); 25 + }; 26 + 27 + const interceptors = createInterceptors< 28 + Request, 29 + Response, 30 + unknown, 31 + RequestOptions 32 + >(); 33 + 34 + // @ts-expect-error 35 + const request: Client['request'] = async (options) => { 36 + const opts = { 37 + ..._config, 38 + ...options, 39 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 40 + headers: mergeHeaders(_config.headers, options.headers), 41 + }; 42 + 43 + if (opts.security) { 44 + await setAuthParams({ 45 + ...opts, 46 + security: opts.security, 47 + }); 48 + } 49 + 50 + if (opts.body && opts.bodySerializer) { 51 + opts.body = opts.bodySerializer(opts.body); 52 + } 53 + 54 + // remove Content-Type header if body is empty to avoid sending invalid requests 55 + if (!opts.body) { 56 + opts.headers.delete('Content-Type'); 57 + } 58 + 59 + const url = buildUrl(opts); 60 + const requestInit: ReqInit = { 61 + redirect: 'follow', 62 + ...opts, 63 + }; 64 + 65 + let request = new Request(url, requestInit); 66 + 67 + for (const fn of interceptors.request._fns) { 68 + request = await fn(request, opts); 69 + } 70 + 71 + // fetch must be assigned here, otherwise it would throw the error: 72 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 73 + const _fetch = opts.fetch!; 74 + let response = await _fetch(request); 75 + 76 + for (const fn of interceptors.response._fns) { 77 + response = await fn(response, request, opts); 78 + } 79 + 80 + const result = { 81 + request, 82 + response, 83 + }; 84 + 85 + if (response.ok) { 86 + if ( 87 + response.status === 204 || 88 + response.headers.get('Content-Length') === '0' 89 + ) { 90 + return { 91 + data: {}, 92 + ...result, 93 + }; 94 + } 95 + 96 + const parseAs = 97 + (opts.parseAs === 'auto' 98 + ? getParseAs(response.headers.get('Content-Type')) 99 + : opts.parseAs) ?? 'json'; 100 + 101 + if (parseAs === 'stream') { 102 + return { 103 + data: response.body, 104 + ...result, 105 + }; 106 + } 107 + 108 + let data = await response[parseAs](); 109 + if (parseAs === 'json') { 110 + if (opts.responseValidator) { 111 + await opts.responseValidator(data); 112 + } 113 + 114 + if (opts.responseTransformer) { 115 + data = await opts.responseTransformer(data); 116 + } 117 + } 118 + 119 + return { 120 + data, 121 + ...result, 122 + }; 123 + } 124 + 125 + let error = await response.text(); 126 + 127 + try { 128 + error = JSON.parse(error); 129 + } catch { 130 + // noop 131 + } 132 + 133 + let finalError = error; 134 + 135 + for (const fn of interceptors.error._fns) { 136 + finalError = (await fn(error, response, request, opts)) as string; 137 + } 138 + 139 + finalError = finalError || ({} as string); 140 + 141 + if (opts.throwOnError) { 142 + throw finalError; 143 + } 144 + 145 + return { 146 + error: finalError, 147 + ...result, 148 + }; 149 + }; 150 + 151 + return { 152 + buildUrl, 153 + connect: (options) => request({ ...options, method: 'CONNECT' }), 154 + delete: (options) => request({ ...options, method: 'DELETE' }), 155 + get: (options) => request({ ...options, method: 'GET' }), 156 + getConfig, 157 + head: (options) => request({ ...options, method: 'HEAD' }), 158 + interceptors, 159 + options: (options) => request({ ...options, method: 'OPTIONS' }), 160 + patch: (options) => request({ ...options, method: 'PATCH' }), 161 + post: (options) => request({ ...options, method: 'POST' }), 162 + put: (options) => request({ ...options, method: 'PUT' }), 163 + request, 164 + setConfig, 165 + trace: (options) => request({ ...options, method: 'TRACE' }), 166 + }; 167 + };
+1 -168
packages/client-fetch/src/index.ts
··· 1 - import type { Client, Config, RequestOptions } from './types'; 2 - import { 3 - buildUrl, 4 - createConfig, 5 - createInterceptors, 6 - getParseAs, 7 - mergeConfigs, 8 - mergeHeaders, 9 - setAuthParams, 10 - } from './utils'; 11 - 12 - type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 - body?: any; 14 - headers: ReturnType<typeof mergeHeaders>; 15 - }; 16 - 17 - export const createClient = (config: Config = {}): Client => { 18 - let _config = mergeConfigs(createConfig(), config); 19 - 20 - const getConfig = (): Config => ({ ..._config }); 21 - 22 - const setConfig = (config: Config): Config => { 23 - _config = mergeConfigs(_config, config); 24 - return getConfig(); 25 - }; 26 - 27 - const interceptors = createInterceptors< 28 - Request, 29 - Response, 30 - unknown, 31 - RequestOptions 32 - >(); 33 - 34 - // @ts-expect-error 35 - const request: Client['request'] = async (options) => { 36 - const opts = { 37 - ..._config, 38 - ...options, 39 - fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 40 - headers: mergeHeaders(_config.headers, options.headers), 41 - }; 42 - 43 - if (opts.security) { 44 - await setAuthParams({ 45 - ...opts, 46 - security: opts.security, 47 - }); 48 - } 49 - 50 - if (opts.body && opts.bodySerializer) { 51 - opts.body = opts.bodySerializer(opts.body); 52 - } 53 - 54 - // remove Content-Type header if body is empty to avoid sending invalid requests 55 - if (!opts.body) { 56 - opts.headers.delete('Content-Type'); 57 - } 58 - 59 - const url = buildUrl(opts); 60 - const requestInit: ReqInit = { 61 - redirect: 'follow', 62 - ...opts, 63 - }; 64 - 65 - let request = new Request(url, requestInit); 66 - 67 - for (const fn of interceptors.request._fns) { 68 - request = await fn(request, opts); 69 - } 70 - 71 - // fetch must be assigned here, otherwise it would throw the error: 72 - // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 73 - const _fetch = opts.fetch!; 74 - let response = await _fetch(request); 75 - 76 - for (const fn of interceptors.response._fns) { 77 - response = await fn(response, request, opts); 78 - } 79 - 80 - const result = { 81 - request, 82 - response, 83 - }; 84 - 85 - if (response.ok) { 86 - if ( 87 - response.status === 204 || 88 - response.headers.get('Content-Length') === '0' 89 - ) { 90 - return { 91 - data: {}, 92 - ...result, 93 - }; 94 - } 95 - 96 - const parseAs = 97 - (opts.parseAs === 'auto' 98 - ? getParseAs(response.headers.get('Content-Type')) 99 - : opts.parseAs) ?? 'json'; 100 - 101 - if (parseAs === 'stream') { 102 - return { 103 - data: response.body, 104 - ...result, 105 - }; 106 - } 107 - 108 - let data = await response[parseAs](); 109 - if (parseAs === 'json') { 110 - if (opts.responseValidator) { 111 - await opts.responseValidator(data); 112 - } 113 - 114 - if (opts.responseTransformer) { 115 - data = await opts.responseTransformer(data); 116 - } 117 - } 118 - 119 - return { 120 - data, 121 - ...result, 122 - }; 123 - } 124 - 125 - let error = await response.text(); 126 - 127 - try { 128 - error = JSON.parse(error); 129 - } catch { 130 - // noop 131 - } 132 - 133 - let finalError = error; 134 - 135 - for (const fn of interceptors.error._fns) { 136 - finalError = (await fn(error, response, request, opts)) as string; 137 - } 138 - 139 - finalError = finalError || ({} as string); 140 - 141 - if (opts.throwOnError) { 142 - throw finalError; 143 - } 144 - 145 - return { 146 - error: finalError, 147 - ...result, 148 - }; 149 - }; 150 - 151 - return { 152 - buildUrl, 153 - connect: (options) => request({ ...options, method: 'CONNECT' }), 154 - delete: (options) => request({ ...options, method: 'DELETE' }), 155 - get: (options) => request({ ...options, method: 'GET' }), 156 - getConfig, 157 - head: (options) => request({ ...options, method: 'HEAD' }), 158 - interceptors, 159 - options: (options) => request({ ...options, method: 'OPTIONS' }), 160 - patch: (options) => request({ ...options, method: 'PATCH' }), 161 - post: (options) => request({ ...options, method: 'POST' }), 162 - put: (options) => request({ ...options, method: 'PUT' }), 163 - request, 164 - setConfig, 165 - trace: (options) => request({ ...options, method: 'TRACE' }), 166 - }; 167 - }; 168 - 1 + export { createClient } from './client'; 169 2 export type { 170 3 Client, 171 4 Config,
+21
packages/client-next/LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) Hey API 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+40
packages/client-next/README.md
··· 1 + <div align="center"> 2 + <img alt="Hey API logo" height="150" src="https://heyapi.dev/images/logo-300w.png" width="150"> 3 + <h1 align="center"><b>Next.js Client</b></h1> 4 + <p align="center">🚀 Next.js client for `@hey-api/openapi-ts` codegen.</p> 5 + </div> 6 + 7 + <!-- TODO: Add working Next.js example --> 8 + <!-- [Live demo](https://stackblitz.com/edit/hey-api-client-fetch-example?file=openapi-ts.config.ts,src%2Fclient%2Fschemas.gen.ts,src%2Fclient%2Fsdk.gen.ts,src%2Fclient%2Ftypes.gen.ts,src%2FApp.tsx) --> 9 + 10 + ## Features 11 + 12 + - seamless integration with `@hey-api/openapi-ts` ecosystem 13 + - type-safe response data and errors 14 + - response data validation and transformation 15 + - access to the original request and response 16 + - granular request and response customization options 17 + - minimal learning curve thanks to extending the underlying technology 18 + - support bundling inside the generated output 19 + 20 + ## Documentation 21 + 22 + Please visit our [website](https://heyapi.dev/) for documentation, guides, migrating, and more. 23 + 24 + ## Sponsors 25 + 26 + Love Hey API? Become our [sponsor](https://github.com/sponsors/hey-api). 27 + 28 + <p> 29 + <a href="https://kutt.it/pkEZyc" target="_blank"> 30 + <img alt="Stainless logo" height="50" src="https://heyapi.dev/images/stainless-logo-wordmark-480w.jpeg" /> 31 + </a> 32 + </p> 33 + 34 + ## GitHub Integration (coming soon) 35 + 36 + Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). 37 + 38 + ## Migration Guides 39 + 40 + [OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen)
+71
packages/client-next/package.json
··· 1 + { 2 + "name": "@hey-api/client-next", 3 + "version": "0.1.0", 4 + "description": "🚀 Next.js client for `@hey-api/openapi-ts` codegen.", 5 + "homepage": "https://heyapi.dev/", 6 + "repository": { 7 + "type": "git", 8 + "url": "git+https://github.com/hey-api/openapi-ts.git" 9 + }, 10 + "bugs": { 11 + "url": "https://github.com/hey-api/openapi-ts/issues" 12 + }, 13 + "license": "MIT", 14 + "author": { 15 + "email": "lubos@heyapi.dev", 16 + "name": "Hey API", 17 + "url": "https://heyapi.dev" 18 + }, 19 + "funding": "https://github.com/sponsors/hey-api", 20 + "keywords": [ 21 + "client", 22 + "codegen", 23 + "fetch", 24 + "http", 25 + "javascript", 26 + "next", 27 + "next.js", 28 + "openapi", 29 + "react", 30 + "rest", 31 + "swagger", 32 + "typescript" 33 + ], 34 + "type": "module", 35 + "main": "./dist/index.cjs", 36 + "module": "./dist/index.js", 37 + "types": "./dist/index.d.ts", 38 + "exports": { 39 + ".": { 40 + "import": { 41 + "types": "./dist/index.d.ts", 42 + "default": "./dist/index.js" 43 + }, 44 + "require": { 45 + "types": "./dist/index.d.cts", 46 + "default": "./dist/index.cjs" 47 + } 48 + }, 49 + "./package.json": "./package.json" 50 + }, 51 + "sideEffects": false, 52 + "files": [ 53 + "dist", 54 + "LICENSE.md", 55 + "src" 56 + ], 57 + "scripts": { 58 + "build": "tsup && rollup -c && pnpm check-exports", 59 + "check-exports": "attw --pack .", 60 + "dev": "tsup --watch", 61 + "prepublishOnly": "pnpm build", 62 + "test:coverage": "vitest run --coverage", 63 + "test:update": "vitest watch --update", 64 + "test:watch": "vitest watch", 65 + "test": "vitest run", 66 + "typecheck": "vitest --typecheck --watch=false" 67 + }, 68 + "devDependencies": { 69 + "@hey-api/client-core": "workspace:*" 70 + } 71 + }
+30
packages/client-next/rollup.config.mjs
··· 1 + import path from 'node:path'; 2 + 3 + import { defineConfig } from 'rollup'; 4 + import dts from 'rollup-plugin-dts'; 5 + 6 + const files = ['index.d.ts', 'index.d.cts']; 7 + 8 + export default files.map((file) => 9 + defineConfig({ 10 + external: (id) => { 11 + const normalizedId = id.split(path.sep).join('/'); 12 + if (normalizedId === '@hey-api/client-core') { 13 + return false; 14 + } 15 + return ( 16 + !normalizedId.startsWith('/') && !/^[a-zA-Z]:\//.test(normalizedId) 17 + ); 18 + }, 19 + input: `./dist/${file}`, 20 + output: { 21 + file: `./dist/${file}`, 22 + format: 'es', 23 + }, 24 + plugins: [ 25 + dts({ 26 + respectExternal: true, 27 + }), 28 + ], 29 + }), 30 + );
+189
packages/client-next/src/__tests__/utils.test.ts
··· 1 + import type { Auth } from '@hey-api/client-core'; 2 + import { describe, expect, it, vi } from 'vitest'; 3 + 4 + import { getParseAs, setAuthParams } from '../utils'; 5 + 6 + describe('getParseAs', () => { 7 + const scenarios: Array<{ 8 + content: Parameters<typeof getParseAs>[0]; 9 + parseAs: ReturnType<typeof getParseAs>; 10 + }> = [ 11 + { 12 + content: null, 13 + parseAs: 'stream', 14 + }, 15 + { 16 + content: 'application/json', 17 + parseAs: 'json', 18 + }, 19 + { 20 + content: 'application/ld+json', 21 + parseAs: 'json', 22 + }, 23 + { 24 + content: 'application/ld+json;charset=utf-8', 25 + parseAs: 'json', 26 + }, 27 + { 28 + content: 'application/ld+json; charset=utf-8', 29 + parseAs: 'json', 30 + }, 31 + { 32 + content: 'multipart/form-data', 33 + parseAs: 'formData', 34 + }, 35 + { 36 + content: 'application/*', 37 + parseAs: 'blob', 38 + }, 39 + { 40 + content: 'audio/*', 41 + parseAs: 'blob', 42 + }, 43 + { 44 + content: 'image/*', 45 + parseAs: 'blob', 46 + }, 47 + { 48 + content: 'video/*', 49 + parseAs: 'blob', 50 + }, 51 + { 52 + content: 'text/*', 53 + parseAs: 'text', 54 + }, 55 + { 56 + content: 'unsupported', 57 + parseAs: undefined, 58 + }, 59 + ]; 60 + 61 + it.each(scenarios)( 62 + 'detects $content as $parseAs', 63 + async ({ content, parseAs }) => { 64 + expect(getParseAs(content)).toEqual(parseAs); 65 + }, 66 + ); 67 + }); 68 + 69 + describe('setAuthParams', () => { 70 + it('sets bearer token in headers', async () => { 71 + const auth = vi.fn().mockReturnValue('foo'); 72 + const headers = new Headers(); 73 + const query: Record<any, unknown> = {}; 74 + await setAuthParams({ 75 + auth, 76 + headers, 77 + query, 78 + security: [ 79 + { 80 + name: 'baz', 81 + scheme: 'bearer', 82 + type: 'http', 83 + }, 84 + ], 85 + }); 86 + expect(auth).toHaveBeenCalled(); 87 + expect(headers.get('baz')).toBe('Bearer foo'); 88 + expect(Object.keys(query).length).toBe(0); 89 + }); 90 + 91 + it('sets access token in query', async () => { 92 + const auth = vi.fn().mockReturnValue('foo'); 93 + const headers = new Headers(); 94 + const query: Record<any, unknown> = {}; 95 + await setAuthParams({ 96 + auth, 97 + headers, 98 + query, 99 + security: [ 100 + { 101 + in: 'query', 102 + name: 'baz', 103 + scheme: 'bearer', 104 + type: 'http', 105 + }, 106 + ], 107 + }); 108 + expect(auth).toHaveBeenCalled(); 109 + expect(headers.get('baz')).toBeNull(); 110 + expect(query.baz).toBe('Bearer foo'); 111 + }); 112 + 113 + it('sets Authorization header when `in` and `name` are undefined', async () => { 114 + const auth = vi.fn().mockReturnValue('foo'); 115 + const headers = new Headers(); 116 + const query: Record<any, unknown> = {}; 117 + await setAuthParams({ 118 + auth, 119 + headers, 120 + query, 121 + security: [ 122 + { 123 + type: 'http', 124 + }, 125 + ], 126 + }); 127 + expect(auth).toHaveBeenCalled(); 128 + expect(headers.get('Authorization')).toBe('foo'); 129 + expect(query).toEqual({}); 130 + }); 131 + 132 + it('sets first scheme only', async () => { 133 + const auth = vi.fn().mockReturnValue('foo'); 134 + const headers = new Headers(); 135 + const query: Record<any, unknown> = {}; 136 + await setAuthParams({ 137 + auth, 138 + headers, 139 + query, 140 + security: [ 141 + { 142 + name: 'baz', 143 + scheme: 'bearer', 144 + type: 'http', 145 + }, 146 + { 147 + in: 'query', 148 + name: 'baz', 149 + scheme: 'bearer', 150 + type: 'http', 151 + }, 152 + ], 153 + }); 154 + expect(auth).toHaveBeenCalled(); 155 + expect(headers.get('baz')).toBe('Bearer foo'); 156 + expect(Object.keys(query).length).toBe(0); 157 + }); 158 + 159 + it('sets first scheme with token', async () => { 160 + const auth = vi.fn().mockImplementation((auth: Auth) => { 161 + if (auth.type === 'apiKey') { 162 + return; 163 + } 164 + return 'foo'; 165 + }); 166 + const headers = new Headers(); 167 + const query: Record<any, unknown> = {}; 168 + await setAuthParams({ 169 + auth, 170 + headers, 171 + query, 172 + security: [ 173 + { 174 + name: 'baz', 175 + type: 'apiKey', 176 + }, 177 + { 178 + in: 'query', 179 + name: 'baz', 180 + scheme: 'bearer', 181 + type: 'http', 182 + }, 183 + ], 184 + }); 185 + expect(auth).toHaveBeenCalled(); 186 + expect(headers.get('baz')).toBeNull(); 187 + expect(query.baz).toBe('Bearer foo'); 188 + }); 189 + });
+157
packages/client-next/src/client.ts
··· 1 + import type { Client, Config, RequestOptions } from './types'; 2 + import { 3 + buildUrl, 4 + createConfig, 5 + createInterceptors, 6 + getParseAs, 7 + mergeConfigs, 8 + mergeHeaders, 9 + setAuthParams, 10 + } from './utils'; 11 + 12 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 13 + body?: any; 14 + headers: ReturnType<typeof mergeHeaders>; 15 + }; 16 + 17 + export const createClient = (config: Config = {}): Client => { 18 + let _config = mergeConfigs(createConfig(), config); 19 + 20 + const getConfig = (): Config => ({ ..._config }); 21 + 22 + const setConfig = (config: Config): Config => { 23 + _config = mergeConfigs(_config, config); 24 + return getConfig(); 25 + }; 26 + 27 + const interceptors = createInterceptors<Response, unknown, RequestOptions>(); 28 + 29 + // @ts-expect-error 30 + const request: Client['request'] = async (options) => { 31 + const opts = { 32 + ..._config, 33 + ...options, 34 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 35 + headers: mergeHeaders(_config.headers, options.headers), 36 + }; 37 + 38 + if (opts.security) { 39 + await setAuthParams({ 40 + ...opts, 41 + security: opts.security, 42 + }); 43 + } 44 + 45 + if (opts.body && opts.bodySerializer) { 46 + opts.body = opts.bodySerializer(opts.body); 47 + } 48 + 49 + // remove Content-Type header if body is empty to avoid sending invalid requests 50 + if (!opts.body) { 51 + opts.headers.delete('Content-Type'); 52 + } 53 + 54 + for (const fn of interceptors.request._fns) { 55 + await fn(opts); 56 + } 57 + 58 + const url = buildUrl(opts); 59 + // fetch must be assigned here, otherwise it would throw the error: 60 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 61 + const _fetch = opts.fetch!; 62 + let response = await _fetch(url, { 63 + ...opts, 64 + body: opts.body as ReqInit['body'], 65 + }); 66 + 67 + for (const fn of interceptors.response._fns) { 68 + response = await fn(response, opts); 69 + } 70 + 71 + const result = { 72 + response, 73 + }; 74 + 75 + if (response.ok) { 76 + if ( 77 + response.status === 204 || 78 + response.headers.get('Content-Length') === '0' 79 + ) { 80 + return { 81 + data: {}, 82 + ...result, 83 + }; 84 + } 85 + 86 + const parseAs = 87 + (opts.parseAs === 'auto' 88 + ? getParseAs(response.headers.get('Content-Type')) 89 + : opts.parseAs) ?? 'json'; 90 + 91 + if (parseAs === 'stream') { 92 + return { 93 + data: response.body, 94 + ...result, 95 + }; 96 + } 97 + 98 + let data = await response[parseAs](); 99 + if (parseAs === 'json') { 100 + if (opts.responseValidator) { 101 + await opts.responseValidator(data); 102 + } 103 + 104 + if (opts.responseTransformer) { 105 + data = await opts.responseTransformer(data); 106 + } 107 + } 108 + 109 + return { 110 + data, 111 + ...result, 112 + }; 113 + } 114 + 115 + let error = await response.text(); 116 + 117 + try { 118 + error = JSON.parse(error); 119 + } catch { 120 + // noop 121 + } 122 + 123 + let finalError = error; 124 + 125 + for (const fn of interceptors.error._fns) { 126 + finalError = (await fn(error, response, opts)) as string; 127 + } 128 + 129 + finalError = finalError || ({} as string); 130 + 131 + if (opts.throwOnError) { 132 + throw finalError; 133 + } 134 + 135 + return { 136 + error: finalError, 137 + ...result, 138 + }; 139 + }; 140 + 141 + return { 142 + buildUrl, 143 + connect: (options) => request({ ...options, method: 'CONNECT' }), 144 + delete: (options) => request({ ...options, method: 'DELETE' }), 145 + get: (options) => request({ ...options, method: 'GET' }), 146 + getConfig, 147 + head: (options) => request({ ...options, method: 'HEAD' }), 148 + interceptors, 149 + options: (options) => request({ ...options, method: 'OPTIONS' }), 150 + patch: (options) => request({ ...options, method: 'PATCH' }), 151 + post: (options) => request({ ...options, method: 'POST' }), 152 + put: (options) => request({ ...options, method: 'PUT' }), 153 + request, 154 + setConfig, 155 + trace: (options) => request({ ...options, method: 'TRACE' }), 156 + }; 157 + };
+17
packages/client-next/src/index.ts
··· 1 + export { createClient } from './client'; 2 + export type { 3 + Client, 4 + Config, 5 + CreateClientConfig, 6 + Options, 7 + OptionsLegacyParser, 8 + RequestOptions, 9 + RequestResult, 10 + } from './types'; 11 + export { createConfig } from './utils'; 12 + export type { Auth, QuerySerializerOptions } from '@hey-api/client-core'; 13 + export { 14 + formDataBodySerializer, 15 + jsonBodySerializer, 16 + urlSearchParamsBodySerializer, 17 + } from '@hey-api/client-core';
+161
packages/client-next/src/types.ts
··· 1 + import type { 2 + Auth, 3 + Client as CoreClient, 4 + Config as CoreConfig, 5 + } from '@hey-api/client-core'; 6 + 7 + import type { Middleware } from './utils'; 8 + 9 + export interface Config<ThrowOnError extends boolean = boolean> 10 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 11 + CoreConfig { 12 + /** 13 + * Base URL for all requests made by this client. 14 + * 15 + * @default '' 16 + */ 17 + baseUrl?: string; 18 + /** 19 + * Fetch API implementation. You can use this option to provide a custom 20 + * fetch instance. 21 + * 22 + * @default globalThis.fetch 23 + */ 24 + fetch?: typeof fetch; 25 + /** 26 + * Return the response data parsed in a specified format. By default, `auto` 27 + * will infer the appropriate method from the `Content-Type` response header. 28 + * You can override this behavior with any of the {@link Body} methods. 29 + * Select `stream` if you don't want to parse response data at all. 30 + * 31 + * @default 'auto' 32 + */ 33 + parseAs?: Exclude<keyof Body, 'body' | 'bodyUsed'> | 'auto' | 'stream'; 34 + /** 35 + * Throw an error instead of returning it in the response? 36 + * 37 + * @default false 38 + */ 39 + throwOnError?: ThrowOnError; 40 + } 41 + 42 + export interface RequestOptions< 43 + ThrowOnError extends boolean = boolean, 44 + Url extends string = string, 45 + > extends Config<ThrowOnError> { 46 + /** 47 + * Any body that you want to add to your request. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 50 + */ 51 + body?: 52 + | RequestInit['body'] 53 + | Record<string, unknown> 54 + | Array<Record<string, unknown>> 55 + | Array<unknown> 56 + | number; 57 + /** 58 + * You can provide a client instance returned by `createClient()` instead of 59 + * individual options. This might be also useful if you want to implement a 60 + * custom client. 61 + */ 62 + client?: Client; 63 + path?: Record<string, unknown>; 64 + query?: Record<string, unknown>; 65 + /** 66 + * Security mechanism(s) to use for the request. 67 + */ 68 + security?: ReadonlyArray<Auth>; 69 + url: Url; 70 + } 71 + 72 + export type RequestResult< 73 + TData = unknown, 74 + TError = unknown, 75 + ThrowOnError extends boolean = boolean, 76 + > = ThrowOnError extends true 77 + ? Promise<{ 78 + data: TData; 79 + response: Response; 80 + }> 81 + : Promise< 82 + ( 83 + | { data: TData; error: undefined } 84 + | { data: undefined; error: TError } 85 + ) & { 86 + response: Response; 87 + } 88 + >; 89 + 90 + type MethodFn = < 91 + TData = unknown, 92 + TError = unknown, 93 + ThrowOnError extends boolean = false, 94 + >( 95 + options: Omit<RequestOptions<ThrowOnError>, 'method'>, 96 + ) => RequestResult<TData, TError, ThrowOnError>; 97 + 98 + type RequestFn = < 99 + TData = unknown, 100 + TError = unknown, 101 + ThrowOnError extends boolean = false, 102 + >( 103 + options: Omit<RequestOptions<ThrowOnError>, 'method'> & 104 + Pick<Required<RequestOptions<ThrowOnError>>, 'method'>, 105 + ) => RequestResult<TData, TError, ThrowOnError>; 106 + 107 + type BuildUrlFn = < 108 + TData extends { 109 + body?: unknown; 110 + path?: Record<string, unknown>; 111 + query?: Record<string, unknown>; 112 + url: string; 113 + }, 114 + >( 115 + options: Pick<TData, 'url'> & Options<TData>, 116 + ) => string; 117 + 118 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 119 + interceptors: Middleware<Response, unknown, RequestOptions>; 120 + }; 121 + 122 + /** 123 + * The `createClientConfig()` function will be called on client initialization 124 + * and the returned object will become the client's initial configuration. 125 + * 126 + * You may want to initialize your client this way instead of calling 127 + * `setConfig()`. This is useful for example if you're using Next.js 128 + * to ensure your client always has the correct values. 129 + */ 130 + export type CreateClientConfig = (override?: Config) => Config; 131 + 132 + interface DataShape { 133 + body?: unknown; 134 + headers?: unknown; 135 + path?: unknown; 136 + query?: unknown; 137 + url: string; 138 + } 139 + 140 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 141 + 142 + export type Options< 143 + TData extends DataShape = DataShape, 144 + ThrowOnError extends boolean = boolean, 145 + > = OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'path' | 'query' | 'url'> & 146 + Omit<TData, 'url'>; 147 + 148 + export type OptionsLegacyParser< 149 + TData = unknown, 150 + ThrowOnError extends boolean = boolean, 151 + > = TData extends { body?: any } 152 + ? TData extends { headers?: any } 153 + ? OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'headers' | 'url'> & TData 154 + : OmitKeys<RequestOptions<ThrowOnError>, 'body' | 'url'> & 155 + TData & 156 + Pick<RequestOptions<ThrowOnError>, 'headers'> 157 + : TData extends { headers?: any } 158 + ? OmitKeys<RequestOptions<ThrowOnError>, 'headers' | 'url'> & 159 + TData & 160 + Pick<RequestOptions<ThrowOnError>, 'body'> 161 + : OmitKeys<RequestOptions<ThrowOnError>, 'url'> & TData;
+396
packages/client-next/src/utils.ts
··· 1 + import type { 2 + QuerySerializer, 3 + QuerySerializerOptions, 4 + } from '@hey-api/client-core'; 5 + import { 6 + getAuthToken, 7 + jsonBodySerializer, 8 + serializeArrayParam, 9 + serializeObjectParam, 10 + serializePrimitiveParam, 11 + } from '@hey-api/client-core'; 12 + 13 + import type { 14 + Client, 15 + Config, 16 + CreateClientConfig, 17 + RequestOptions, 18 + } from './types'; 19 + 20 + interface PathSerializer { 21 + path: Record<string, unknown>; 22 + url: string; 23 + } 24 + 25 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 26 + 27 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 28 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 29 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 30 + 31 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 32 + let url = _url; 33 + const matches = _url.match(PATH_PARAM_RE); 34 + if (matches) { 35 + for (const match of matches) { 36 + let explode = false; 37 + let name = match.substring(1, match.length - 1); 38 + let style: ArraySeparatorStyle = 'simple'; 39 + 40 + if (name.endsWith('*')) { 41 + explode = true; 42 + name = name.substring(0, name.length - 1); 43 + } 44 + 45 + if (name.startsWith('.')) { 46 + name = name.substring(1); 47 + style = 'label'; 48 + } else if (name.startsWith(';')) { 49 + name = name.substring(1); 50 + style = 'matrix'; 51 + } 52 + 53 + const value = path[name]; 54 + 55 + if (value === undefined || value === null) { 56 + continue; 57 + } 58 + 59 + if (Array.isArray(value)) { 60 + url = url.replace( 61 + match, 62 + serializeArrayParam({ explode, name, style, value }), 63 + ); 64 + continue; 65 + } 66 + 67 + if (typeof value === 'object') { 68 + url = url.replace( 69 + match, 70 + serializeObjectParam({ 71 + explode, 72 + name, 73 + style, 74 + value: value as Record<string, unknown>, 75 + }), 76 + ); 77 + continue; 78 + } 79 + 80 + if (style === 'matrix') { 81 + url = url.replace( 82 + match, 83 + `;${serializePrimitiveParam({ 84 + name, 85 + value: value as string, 86 + })}`, 87 + ); 88 + continue; 89 + } 90 + 91 + const replaceValue = encodeURIComponent( 92 + style === 'label' ? `.${value as string}` : (value as string), 93 + ); 94 + url = url.replace(match, replaceValue); 95 + } 96 + } 97 + return url; 98 + }; 99 + 100 + export const createQuerySerializer = <T = unknown>({ 101 + allowReserved, 102 + array, 103 + object, 104 + }: QuerySerializerOptions = {}) => { 105 + const querySerializer = (queryParams: T) => { 106 + let search: string[] = []; 107 + if (queryParams && typeof queryParams === 'object') { 108 + for (const name in queryParams) { 109 + const value = queryParams[name]; 110 + 111 + if (value === undefined || value === null) { 112 + continue; 113 + } 114 + 115 + if (Array.isArray(value)) { 116 + search = [ 117 + ...search, 118 + serializeArrayParam({ 119 + allowReserved, 120 + explode: true, 121 + name, 122 + style: 'form', 123 + value, 124 + ...array, 125 + }), 126 + ]; 127 + continue; 128 + } 129 + 130 + if (typeof value === 'object') { 131 + search = [ 132 + ...search, 133 + serializeObjectParam({ 134 + allowReserved, 135 + explode: true, 136 + name, 137 + style: 'deepObject', 138 + value: value as Record<string, unknown>, 139 + ...object, 140 + }), 141 + ]; 142 + continue; 143 + } 144 + 145 + search = [ 146 + ...search, 147 + serializePrimitiveParam({ 148 + allowReserved, 149 + name, 150 + value: value as string, 151 + }), 152 + ]; 153 + } 154 + } 155 + return search.join('&'); 156 + }; 157 + return querySerializer; 158 + }; 159 + 160 + /** 161 + * Infers parseAs value from provided Content-Type header. 162 + */ 163 + export const getParseAs = ( 164 + contentType: string | null, 165 + ): Exclude<Config['parseAs'], 'auto'> => { 166 + if (!contentType) { 167 + // If no Content-Type header is provided, the best we can do is return the raw response body, 168 + // which is effectively the same as the 'stream' option. 169 + return 'stream'; 170 + } 171 + 172 + const cleanContent = contentType.split(';')[0]?.trim(); 173 + 174 + if (!cleanContent) { 175 + return; 176 + } 177 + 178 + if ( 179 + cleanContent.startsWith('application/json') || 180 + cleanContent.endsWith('+json') 181 + ) { 182 + return 'json'; 183 + } 184 + 185 + if (cleanContent === 'multipart/form-data') { 186 + return 'formData'; 187 + } 188 + 189 + if ( 190 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 191 + cleanContent.startsWith(type), 192 + ) 193 + ) { 194 + return 'blob'; 195 + } 196 + 197 + if (cleanContent.startsWith('text/')) { 198 + return 'text'; 199 + } 200 + }; 201 + 202 + export const setAuthParams = async ({ 203 + security, 204 + ...options 205 + }: Pick<Required<RequestOptions>, 'security'> & 206 + Pick<RequestOptions, 'auth' | 'query'> & { 207 + headers: Headers; 208 + }) => { 209 + for (const auth of security) { 210 + const token = await getAuthToken(auth, options.auth); 211 + 212 + if (!token) { 213 + continue; 214 + } 215 + 216 + const name = auth.name ?? 'Authorization'; 217 + 218 + switch (auth.in) { 219 + case 'query': 220 + if (!options.query) { 221 + options.query = {}; 222 + } 223 + options.query[name] = token; 224 + break; 225 + case 'header': 226 + default: 227 + options.headers.set(name, token); 228 + break; 229 + } 230 + 231 + return; 232 + } 233 + }; 234 + 235 + export const buildUrl: Client['buildUrl'] = (options) => { 236 + const url = getUrl({ 237 + baseUrl: options.baseUrl ?? '', 238 + path: options.path, 239 + query: options.query, 240 + querySerializer: 241 + typeof options.querySerializer === 'function' 242 + ? options.querySerializer 243 + : createQuerySerializer(options.querySerializer), 244 + url: options.url, 245 + }); 246 + return url; 247 + }; 248 + 249 + export const getUrl = ({ 250 + baseUrl, 251 + path, 252 + query, 253 + querySerializer, 254 + url: _url, 255 + }: { 256 + baseUrl: string; 257 + path?: Record<string, unknown>; 258 + query?: Record<string, unknown>; 259 + querySerializer: QuerySerializer; 260 + url: string; 261 + }) => { 262 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 263 + let url = baseUrl + pathUrl; 264 + if (path) { 265 + url = defaultPathSerializer({ path, url }); 266 + } 267 + let search = query ? querySerializer(query) : ''; 268 + if (search.startsWith('?')) { 269 + search = search.substring(1); 270 + } 271 + if (search) { 272 + url += `?${search}`; 273 + } 274 + return url; 275 + }; 276 + 277 + export const mergeConfigs = (a: Config, b: Config): Config => { 278 + const config = { ...a, ...b }; 279 + if (config.baseUrl?.endsWith('/')) { 280 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 281 + } 282 + config.headers = mergeHeaders(a.headers, b.headers); 283 + return config; 284 + }; 285 + 286 + export const mergeHeaders = ( 287 + ...headers: Array<Required<Config>['headers'] | undefined> 288 + ): Headers => { 289 + const mergedHeaders = new Headers(); 290 + for (const header of headers) { 291 + if (!header || typeof header !== 'object') { 292 + continue; 293 + } 294 + 295 + const iterator = 296 + header instanceof Headers ? header.entries() : Object.entries(header); 297 + 298 + for (const [key, value] of iterator) { 299 + if (value === null) { 300 + mergedHeaders.delete(key); 301 + } else if (Array.isArray(value)) { 302 + for (const v of value) { 303 + mergedHeaders.append(key, v as string); 304 + } 305 + } else if (value !== undefined) { 306 + // assume object headers are meant to be JSON stringified, i.e. their 307 + // content value in OpenAPI specification is 'application/json' 308 + mergedHeaders.set( 309 + key, 310 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 311 + ); 312 + } 313 + } 314 + } 315 + return mergedHeaders; 316 + }; 317 + 318 + type ErrInterceptor<Err, Res, Options> = ( 319 + error: Err, 320 + response: Res, 321 + options: Options, 322 + ) => Err | Promise<Err>; 323 + 324 + type ReqInterceptor<Options> = (options: Options) => void | Promise<void>; 325 + 326 + type ResInterceptor<Res, Options> = ( 327 + response: Res, 328 + options: Options, 329 + ) => Res | Promise<Res>; 330 + 331 + class Interceptors<Interceptor> { 332 + _fns: Interceptor[]; 333 + 334 + constructor() { 335 + this._fns = []; 336 + } 337 + 338 + clear() { 339 + this._fns = []; 340 + } 341 + 342 + exists(fn: Interceptor) { 343 + return this._fns.indexOf(fn) !== -1; 344 + } 345 + 346 + eject(fn: Interceptor) { 347 + const index = this._fns.indexOf(fn); 348 + if (index !== -1) { 349 + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; 350 + } 351 + } 352 + 353 + use(fn: Interceptor) { 354 + this._fns = [...this._fns, fn]; 355 + } 356 + } 357 + 358 + // `createInterceptors()` response, meant for external use as it does not 359 + // expose internals 360 + export interface Middleware<Res, Err, Options> { 361 + error: Pick<Interceptors<ErrInterceptor<Err, Res, Options>>, 'eject' | 'use'>; 362 + request: Pick<Interceptors<ReqInterceptor<Options>>, 'eject' | 'use'>; 363 + response: Pick<Interceptors<ResInterceptor<Res, Options>>, 'eject' | 'use'>; 364 + } 365 + 366 + // do not add `Middleware` as return type so we can use _fns internally 367 + export const createInterceptors = <Res, Err, Options>() => ({ 368 + error: new Interceptors<ErrInterceptor<Err, Res, Options>>(), 369 + request: new Interceptors<ReqInterceptor<Options>>(), 370 + response: new Interceptors<ResInterceptor<Res, Options>>(), 371 + }); 372 + 373 + const defaultQuerySerializer = createQuerySerializer({ 374 + allowReserved: false, 375 + array: { 376 + explode: true, 377 + style: 'form', 378 + }, 379 + object: { 380 + explode: true, 381 + style: 'deepObject', 382 + }, 383 + }); 384 + 385 + const defaultHeaders = { 386 + 'Content-Type': 'application/json', 387 + }; 388 + 389 + export const createConfig: CreateClientConfig = (override = {}) => ({ 390 + ...jsonBodySerializer, 391 + baseUrl: '', 392 + headers: defaultHeaders, 393 + parseAs: 'auto', 394 + querySerializer: defaultQuerySerializer, 395 + ...override, 396 + });
+14
packages/client-next/tsconfig.base.json
··· 1 + { 2 + "compilerOptions": { 3 + "declaration": true, 4 + "esModuleInterop": true, 5 + "module": "ESNext", 6 + "moduleResolution": "Bundler", 7 + "noImplicitOverride": true, 8 + "noUncheckedIndexedAccess": true, 9 + "noUnusedLocals": true, 10 + "strict": true, 11 + "target": "ES2022", 12 + "useUnknownInCatchVariables": false 13 + } 14 + }
+7
packages/client-next/tsconfig.json
··· 1 + { 2 + "extends": "./tsconfig.base.json", 3 + "compilerOptions": { 4 + "declaration": false, 5 + "esModuleInterop": true 6 + } 7 + }
+12
packages/client-next/tsup.config.ts
··· 1 + import { defineConfig } from 'tsup'; 2 + 3 + export default defineConfig((options) => ({ 4 + clean: true, 5 + dts: true, 6 + entry: ['src/index.ts'], 7 + format: ['cjs', 'esm'], 8 + minify: !options.watch, 9 + shims: false, 10 + sourcemap: true, 11 + treeshake: true, 12 + }));
+14
packages/client-next/vitest.config.ts
··· 1 + import { fileURLToPath } from 'node:url'; 2 + 3 + import { defineConfig } from 'vitest/config'; 4 + 5 + export default defineConfig({ 6 + test: { 7 + coverage: { 8 + exclude: ['dist', 'src/**/*.d.ts'], 9 + include: ['src/**/*.ts'], 10 + provider: 'v8', 11 + }, 12 + root: fileURLToPath(new URL('./', import.meta.url)), 13 + }, 14 + });
+2 -1
packages/client-nuxt/README.md
··· 4 4 <p align="center">🚀 Nuxt client for `@hey-api/openapi-ts` codegen.</p> 5 5 </div> 6 6 7 - [Live demo](https://stackblitz.com/edit/hey-api-client-nuxt-example?file=openapi-ts.config.ts,src%2Fclient%2Fschemas.gen.ts,src%2Fclient%2Fsdk.gen.ts,src%2Fclient%2Ftypes.gen.ts,src%2Fcomponents%home.vue) 7 + <!-- TODO: add working example once StackBlitz updates their Node version --> 8 + <!-- [Live demo](https://stackblitz.com/edit/hey-api-client-nuxt-example?file=openapi-ts.config.ts,src%2Fclient%2Fschemas.gen.ts,src%2Fclient%2Fsdk.gen.ts,src%2Fclient%2Ftypes.gen.ts,src%2Fcomponents%home.vue) --> 8 9 9 10 ## Features 10 11
+1 -2
packages/client-nuxt/package.json
··· 19 19 "funding": "https://github.com/sponsors/hey-api", 20 20 "keywords": [ 21 21 "client", 22 + "codegen", 22 23 "fetch", 23 24 "http", 24 25 "javascript", 25 26 "nuxt", 26 27 "openapi", 27 - "react", 28 28 "rest", 29 - "svelte", 30 29 "swagger", 31 30 "typescript", 32 31 "vue"
+1 -1
packages/client-nuxt/src/__tests__/index.test.ts packages/client-nuxt/src/__tests__/client.test.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { createClient } from '../index'; 3 + import { createClient } from '../client'; 4 4 5 5 describe('buildUrl', () => { 6 6 const client = createClient();
+151
packages/client-nuxt/src/client.ts
··· 1 + import type { NuxtApp } from 'nuxt/app'; 2 + import { 3 + useAsyncData, 4 + useFetch, 5 + useLazyAsyncData, 6 + useLazyFetch, 7 + } from 'nuxt/app'; 8 + 9 + import type { Client, Config } from './types'; 10 + import { 11 + buildUrl, 12 + createConfig, 13 + mergeConfigs, 14 + mergeHeaders, 15 + mergeInterceptors, 16 + setAuthParams, 17 + unwrapRefs, 18 + } from './utils'; 19 + 20 + export const createClient = (config: Config = {}): Client => { 21 + let _config = mergeConfigs(createConfig(), config); 22 + 23 + const getConfig = (): Config => ({ ..._config }); 24 + 25 + const setConfig = (config: Config): Config => { 26 + _config = mergeConfigs(_config, config); 27 + return getConfig(); 28 + }; 29 + 30 + const request: Client['request'] = ({ 31 + asyncDataOptions, 32 + composable, 33 + key, 34 + ...options 35 + }) => { 36 + const opts = { 37 + ..._config, 38 + ...options, 39 + $fetch: options.$fetch ?? _config.$fetch ?? $fetch, 40 + headers: mergeHeaders(_config.headers, options.headers), 41 + onRequest: mergeInterceptors(_config.onRequest, options.onRequest), 42 + onResponse: mergeInterceptors(_config.onResponse, options.onResponse), 43 + }; 44 + 45 + const { responseTransformer, responseValidator, security } = opts; 46 + if (security) { 47 + // auth must happen in interceptors otherwise we'd need to require 48 + // asyncContext enabled 49 + // https://nuxt.com/docs/guide/going-further/experimental-features#asynccontext 50 + opts.onRequest = [ 51 + async ({ options }) => { 52 + await setAuthParams({ 53 + auth: opts.auth, 54 + headers: options.headers, 55 + query: options.query, 56 + security, 57 + }); 58 + }, 59 + ...opts.onRequest, 60 + ]; 61 + } 62 + 63 + if (responseTransformer || responseValidator) { 64 + opts.onResponse = [ 65 + ...opts.onResponse, 66 + async ({ options, response }) => { 67 + if (options.responseType && options.responseType !== 'json') { 68 + return; 69 + } 70 + 71 + if (responseValidator) { 72 + await responseValidator(response._data); 73 + } 74 + 75 + if (responseTransformer) { 76 + response._data = await responseTransformer(response._data); 77 + } 78 + }, 79 + ]; 80 + } 81 + 82 + if (opts.body && opts.bodySerializer) { 83 + opts.body = opts.bodySerializer(unwrapRefs(opts.body)); 84 + } 85 + 86 + // remove Content-Type header if body is empty to avoid sending invalid requests 87 + if (!opts.body) { 88 + opts.headers.delete('Content-Type'); 89 + } 90 + 91 + const fetchFn = opts.$fetch; 92 + 93 + if (composable === '$fetch') { 94 + const url = buildUrl(opts); 95 + return fetchFn( 96 + url, 97 + // @ts-expect-error 98 + unwrapRefs(opts), 99 + ); 100 + } 101 + 102 + if (composable === 'useFetch') { 103 + const url = buildUrl(opts); 104 + return useFetch(url, opts); 105 + } 106 + 107 + if (composable === 'useLazyFetch') { 108 + const url = buildUrl(opts); 109 + return useLazyFetch(url, opts); 110 + } 111 + 112 + const handler: (ctx?: NuxtApp) => Promise<any> = () => { 113 + const url = buildUrl(opts); 114 + return fetchFn( 115 + url, 116 + // @ts-expect-error 117 + unwrapRefs(opts), 118 + ); 119 + }; 120 + 121 + if (composable === 'useAsyncData') { 122 + return key 123 + ? useAsyncData(key, handler, asyncDataOptions) 124 + : useAsyncData(handler, asyncDataOptions); 125 + } 126 + 127 + if (composable === 'useLazyAsyncData') { 128 + return key 129 + ? useLazyAsyncData(key, handler, asyncDataOptions) 130 + : useLazyAsyncData(handler, asyncDataOptions); 131 + } 132 + 133 + return undefined as any; 134 + }; 135 + 136 + return { 137 + buildUrl, 138 + connect: (options) => request({ ...options, method: 'CONNECT' }), 139 + delete: (options) => request({ ...options, method: 'DELETE' }), 140 + get: (options) => request({ ...options, method: 'GET' }), 141 + getConfig, 142 + head: (options) => request({ ...options, method: 'HEAD' }), 143 + options: (options) => request({ ...options, method: 'OPTIONS' }), 144 + patch: (options) => request({ ...options, method: 'PATCH' }), 145 + post: (options) => request({ ...options, method: 'POST' }), 146 + put: (options) => request({ ...options, method: 'PUT' }), 147 + request, 148 + setConfig, 149 + trace: (options) => request({ ...options, method: 'TRACE' }), 150 + }; 151 + };
+1 -152
packages/client-nuxt/src/index.ts
··· 1 - import type { NuxtApp } from 'nuxt/app'; 2 - import { 3 - useAsyncData, 4 - useFetch, 5 - useLazyAsyncData, 6 - useLazyFetch, 7 - } from 'nuxt/app'; 8 - 9 - import type { Client, Config } from './types'; 10 - import { 11 - buildUrl, 12 - createConfig, 13 - mergeConfigs, 14 - mergeHeaders, 15 - mergeInterceptors, 16 - setAuthParams, 17 - unwrapRefs, 18 - } from './utils'; 19 - 20 - export const createClient = (config: Config = {}): Client => { 21 - let _config = mergeConfigs(createConfig(), config); 22 - 23 - const getConfig = (): Config => ({ ..._config }); 24 - 25 - const setConfig = (config: Config): Config => { 26 - _config = mergeConfigs(_config, config); 27 - return getConfig(); 28 - }; 29 - 30 - const request: Client['request'] = ({ 31 - asyncDataOptions, 32 - composable, 33 - key, 34 - ...options 35 - }) => { 36 - const opts = { 37 - ..._config, 38 - ...options, 39 - $fetch: options.$fetch ?? _config.$fetch ?? $fetch, 40 - headers: mergeHeaders(_config.headers, options.headers), 41 - onRequest: mergeInterceptors(_config.onRequest, options.onRequest), 42 - onResponse: mergeInterceptors(_config.onResponse, options.onResponse), 43 - }; 44 - 45 - const { responseTransformer, responseValidator, security } = opts; 46 - if (security) { 47 - // auth must happen in interceptors otherwise we'd need to require 48 - // asyncContext enabled 49 - // https://nuxt.com/docs/guide/going-further/experimental-features#asynccontext 50 - opts.onRequest = [ 51 - async ({ options }) => { 52 - await setAuthParams({ 53 - auth: opts.auth, 54 - headers: options.headers, 55 - query: options.query, 56 - security, 57 - }); 58 - }, 59 - ...opts.onRequest, 60 - ]; 61 - } 62 - 63 - if (responseTransformer || responseValidator) { 64 - opts.onResponse = [ 65 - ...opts.onResponse, 66 - async ({ options, response }) => { 67 - if (options.responseType && options.responseType !== 'json') { 68 - return; 69 - } 70 - 71 - if (responseValidator) { 72 - await responseValidator(response._data); 73 - } 74 - 75 - if (responseTransformer) { 76 - response._data = await responseTransformer(response._data); 77 - } 78 - }, 79 - ]; 80 - } 81 - 82 - if (opts.body && opts.bodySerializer) { 83 - opts.body = opts.bodySerializer(unwrapRefs(opts.body)); 84 - } 85 - 86 - // remove Content-Type header if body is empty to avoid sending invalid requests 87 - if (!opts.body) { 88 - opts.headers.delete('Content-Type'); 89 - } 90 - 91 - const fetchFn = opts.$fetch; 92 - 93 - if (composable === '$fetch') { 94 - const url = buildUrl(opts); 95 - return fetchFn( 96 - url, 97 - // @ts-expect-error 98 - unwrapRefs(opts), 99 - ); 100 - } 101 - 102 - if (composable === 'useFetch') { 103 - const url = buildUrl(opts); 104 - return useFetch(url, opts); 105 - } 106 - 107 - if (composable === 'useLazyFetch') { 108 - const url = buildUrl(opts); 109 - return useLazyFetch(url, opts); 110 - } 111 - 112 - const handler: (ctx?: NuxtApp) => Promise<any> = () => { 113 - const url = buildUrl(opts); 114 - return fetchFn( 115 - url, 116 - // @ts-expect-error 117 - unwrapRefs(opts), 118 - ); 119 - }; 120 - 121 - if (composable === 'useAsyncData') { 122 - return key 123 - ? useAsyncData(key, handler, asyncDataOptions) 124 - : useAsyncData(handler, asyncDataOptions); 125 - } 126 - 127 - if (composable === 'useLazyAsyncData') { 128 - return key 129 - ? useLazyAsyncData(key, handler, asyncDataOptions) 130 - : useLazyAsyncData(handler, asyncDataOptions); 131 - } 132 - 133 - return undefined as any; 134 - }; 135 - 136 - return { 137 - buildUrl, 138 - connect: (options) => request({ ...options, method: 'CONNECT' }), 139 - delete: (options) => request({ ...options, method: 'DELETE' }), 140 - get: (options) => request({ ...options, method: 'GET' }), 141 - getConfig, 142 - head: (options) => request({ ...options, method: 'HEAD' }), 143 - options: (options) => request({ ...options, method: 'OPTIONS' }), 144 - patch: (options) => request({ ...options, method: 'PATCH' }), 145 - post: (options) => request({ ...options, method: 'POST' }), 146 - put: (options) => request({ ...options, method: 'PUT' }), 147 - request, 148 - setConfig, 149 - trace: (options) => request({ ...options, method: 'TRACE' }), 150 - }; 151 - }; 152 - 1 + export { createClient } from './client'; 153 2 export type { 154 3 Client, 155 4 Composable,
+1 -1
packages/openapi-ts/bin/index.cjs
··· 13 13 .version(pkg.version) 14 14 .option( 15 15 '-c, --client <value>', 16 - 'HTTP client to generate [@hey-api/client-axios, @hey-api/client-fetch, @hey-api/client-nuxt, legacy/angular, legacy/axios, legacy/fetch, legacy/node, legacy/xhr]', 16 + 'HTTP client to generate [@hey-api/client-axios, @hey-api/client-fetch, @hey-api/client-next, @hey-api/client-nuxt, legacy/angular, legacy/axios, legacy/fetch, legacy/node, legacy/xhr]', 17 17 ) 18 18 .option('-d, --debug', 'Set log level to debug') 19 19 .option('--dry-run [value]', 'Skip writing files to disk?')
+4
packages/openapi-ts/package.json
··· 26 26 "http", 27 27 "javascript", 28 28 "json", 29 + "next", 30 + "next.js", 29 31 "node", 32 + "nuxt", 30 33 "openapi", 31 34 "rest", 32 35 "swagger", ··· 98 101 "@angular/router": "19.0.5", 99 102 "@hey-api/client-axios": "workspace:*", 100 103 "@hey-api/client-fetch": "workspace:*", 104 + "@hey-api/client-next": "workspace:*", 101 105 "@hey-api/client-nuxt": "workspace:*", 102 106 "@tanstack/angular-query-experimental": "5.62.13", 103 107 "@tanstack/react-query": "5.62.15",
+3 -2
packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts
··· 1 1 import type { Plugin } from '../../types'; 2 2 import type { Config as ClientAxiosConfig } from '../client-axios'; 3 3 import type { Config as ClientFetchConfig } from '../client-fetch'; 4 + import type { Config as ClientNextConfig } from '../client-next'; 4 5 import type { Config as ClientNuxtConfig } from '../client-nuxt'; 5 6 6 7 export type PluginHandler = Plugin.Handler< 7 - ClientAxiosConfig | ClientFetchConfig | ClientNuxtConfig 8 + ClientAxiosConfig | ClientFetchConfig | ClientNextConfig | ClientNuxtConfig 8 9 >; 9 10 10 11 export type PluginInstance = Plugin.Instance< 11 - ClientAxiosConfig | ClientFetchConfig | ClientNuxtConfig 12 + ClientAxiosConfig | ClientFetchConfig | ClientNextConfig | ClientNuxtConfig 12 13 >;
+21
packages/openapi-ts/src/plugins/@hey-api/client-next/config.ts
··· 1 + import type { Plugin } from '../../types'; 2 + import { handler } from '../client-core/plugin'; 3 + import type { Config } from './types'; 4 + 5 + export const defaultConfig: Plugin.Config<Config> = { 6 + _handler: handler, 7 + _handlerLegacy: () => {}, 8 + _tags: ['client'], 9 + bundle: false, 10 + name: '@hey-api/client-next', 11 + output: 'client', 12 + throwOnError: false, 13 + }; 14 + 15 + /** 16 + * Type helper for `@hey-api/client-next` plugin, returns {@link Plugin.Config} object 17 + */ 18 + export const defineConfig: Plugin.DefineConfig<Config> = (config) => ({ 19 + ...defaultConfig, 20 + ...config, 21 + });
+2
packages/openapi-ts/src/plugins/@hey-api/client-next/index.ts
··· 1 + export { defaultConfig, defineConfig } from './config'; 2 + export type { Config } from './types';
+12
packages/openapi-ts/src/plugins/@hey-api/client-next/types.d.ts
··· 1 + import type { Client, Plugin } from '../../types'; 2 + 3 + export interface Config 4 + extends Plugin.Name<'@hey-api/client-next'>, 5 + Client.Config { 6 + /** 7 + * Throw an error instead of returning it in the response? 8 + * 9 + * @default false 10 + */ 11 + throwOnError?: boolean; 12 + }
+7
packages/openapi-ts/src/plugins/index.ts
··· 7 7 defaultConfig as heyApiClientFetch, 8 8 } from './@hey-api/client-fetch'; 9 9 import { 10 + type Config as HeyApiClientNext, 11 + defaultConfig as heyApiClientNext, 12 + } from './@hey-api/client-next'; 13 + import { 10 14 type Config as HeyApiClientNuxt, 11 15 defaultConfig as heyApiClientNuxt, 12 16 } from './@hey-api/client-nuxt'; ··· 76 80 export type UserPlugins = 77 81 | Plugin.UserConfig<HeyApiClientAxios> 78 82 | Plugin.UserConfig<HeyApiClientFetch> 83 + | Plugin.UserConfig<HeyApiClientNext> 79 84 | Plugin.UserConfig<HeyApiClientNuxt> 80 85 | Plugin.UserConfig<HeyApiLegacyAngular> 81 86 | Plugin.UserConfig<HeyApiLegacyAxios> ··· 100 105 export type ClientPlugins = 101 106 | Plugin.Config<HeyApiClientAxios> 102 107 | Plugin.Config<HeyApiClientFetch> 108 + | Plugin.Config<HeyApiClientNext> 103 109 | Plugin.Config<HeyApiClientNuxt> 104 110 | Plugin.Config<HeyApiLegacyAngular> 105 111 | Plugin.Config<HeyApiLegacyAxios> ··· 121 127 export const defaultPluginConfigs: DefaultPluginConfigs<ClientPlugins> = { 122 128 '@hey-api/client-axios': heyApiClientAxios, 123 129 '@hey-api/client-fetch': heyApiClientFetch, 130 + '@hey-api/client-next': heyApiClientNext, 124 131 '@hey-api/client-nuxt': heyApiClientNuxt, 125 132 '@hey-api/schemas': heyApiSchemas, 126 133 '@hey-api/sdk': heyApiSdk,
+1
packages/openapi-ts/src/plugins/types.d.ts
··· 11 11 export type PluginClientNames = 12 12 | '@hey-api/client-axios' 13 13 | '@hey-api/client-fetch' 14 + | '@hey-api/client-next' 14 15 | '@hey-api/client-nuxt' 15 16 | 'legacy/angular' 16 17 | 'legacy/axios'
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/index.cjs
··· 1 - 'use strict';var D=require('axios');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var D__default=/*#__PURE__*/_interopDefault(D);var A=async(t,e)=>{let r=typeof e=="function"?await e(t):e;if(r)return t.scheme==="bearer"?`Bearer ${r}`:t.scheme==="basic"?`Basic ${btoa(r)}`:r},z=(t,e,r)=>{typeof r=="string"||r instanceof Blob?t.append(e,r):t.append(e,JSON.stringify(r));},w=(t,e,r)=>{typeof r=="string"?t.append(e,r):t.append(e,JSON.stringify(r));},j={bodySerializer:t=>{let e=new FormData;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(e,r,a)):z(e,r,i));}),e}},q={bodySerializer:t=>JSON.stringify(t)},P={bodySerializer:t=>{let e=new URLSearchParams;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>w(e,r,a)):w(e,r,i));}),e}},v=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},$=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},k=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},h=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(!e){let n=(t?a:a.map(o=>encodeURIComponent(o))).join($(i));switch(i){case "label":return `.${n}`;case "matrix":return `;${r}=${n}`;case "simple":return n;default:return `${r}=${n}`}}let s=v(i),l=a.map(n=>i==="label"||i==="simple"?t?n:encodeURIComponent(n):f({allowReserved:t,name:r,value:n})).join(s);return i==="label"||i==="matrix"?s+l:l},f=({allowReserved:t,name:e,value:r})=>{if(r==null)return "";if(typeof r=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${e}=${t?r:encodeURIComponent(r)}`},g=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(a instanceof Date)return `${r}=${a.toISOString()}`;if(i!=="deepObject"&&!e){let n=[];Object.entries(a).forEach(([u,d])=>{n=[...n,u,t?d:encodeURIComponent(d)];});let o=n.join(",");switch(i){case "form":return `${r}=${o}`;case "label":return `.${o}`;case "matrix":return `;${r}=${o}`;default:return o}}let s=k(i),l=Object.entries(a).map(([n,o])=>f({allowReserved:t,name:i==="deepObject"?`${r}[${n}]`:n,value:o})).join(s);return i==="label"||i==="matrix"?s+l:l};var E=/\{[^{}]+\}/g,T=({path:t,url:e})=>{let r=e,i=e.match(E);if(i)for(let a of i){let s=false,l=a.substring(1,a.length-1),n="simple";l.endsWith("*")&&(s=true,l=l.substring(0,l.length-1)),l.startsWith(".")?(l=l.substring(1),n="label"):l.startsWith(";")&&(l=l.substring(1),n="matrix");let o=t[l];if(o==null)continue;if(Array.isArray(o)){r=r.replace(a,h({explode:s,name:l,style:n,value:o}));continue}if(typeof o=="object"){r=r.replace(a,g({explode:s,name:l,style:n,value:o}));continue}if(n==="matrix"){r=r.replace(a,`;${f({name:l,value:o})}`);continue}let u=encodeURIComponent(n==="label"?`.${o}`:o);r=r.replace(a,u);}return r},U=({allowReserved:t,array:e,object:r}={})=>a=>{let s=[];if(a&&typeof a=="object")for(let l in a){let n=a[l];if(n!=null){if(Array.isArray(n)){s=[...s,h({allowReserved:t,explode:true,name:l,style:"form",value:n,...e})];continue}if(typeof n=="object"){s=[...s,g({allowReserved:t,explode:true,name:l,style:"deepObject",value:n,...r})];continue}s=[...s,f({allowReserved:t,name:l,value:n})];}}return s.join("&")},R=async({security:t,...e})=>{for(let r of t){let i=await A(r,e.auth);if(!i)continue;let a=r.name??"Authorization";switch(r.in){case "query":e.query||(e.query={}),e.query[a]=i;break;case "header":default:e.headers[a]=i;break}return}},b=t=>H({path:t.path,query:t.paramsSerializer?undefined:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:U(t.querySerializer),url:t.url}),H=({path:t,query:e,querySerializer:r,url:i})=>{let s=i.startsWith("/")?i:`/${i}`;t&&(s=T({path:t,url:s}));let l=e?r(e):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(s+=`?${l}`),s},S=(t,e)=>{let r={...t,...e};return r.headers=y(t.headers,e.headers),r},B=["common","delete","get","head","patch","post","put"],y=(...t)=>{let e={};for(let r of t){if(!r||typeof r!="object")continue;let i=Object.entries(r);for(let[a,s]of i)if(B.includes(a)&&typeof s=="object")e[a]={...e[a],...s};else if(s===null)delete e[a];else if(Array.isArray(s))for(let l of s)e[a]=[...e[a]??[],l];else s!==undefined&&(e[a]=typeof s=="object"?JSON.stringify(s):s);}return e},x=(t={})=>({baseURL:"",...t});var L=t=>{let e=S(x(),t),{auth:r,...i}=e,a=D__default.default.create(i),s=()=>({...e}),l=o=>(e=S(e,o),a.defaults={...a.defaults,...e,headers:y(a.defaults.headers,e.headers)},s()),n=async o=>{let u={...e,...o,axios:o.axios??e.axios??a,headers:y(e.headers,o.headers)};u.security&&await R({...u,security:u.security}),u.body&&u.bodySerializer&&(u.body=u.bodySerializer(u.body));let d=b(u);try{let m=u.axios,{auth:c,...O}=u,C=await m({...O,data:u.body,headers:u.headers,params:u.paramsSerializer?u.query:void 0,url:d}),{data:p}=C;return u.responseType==="json"&&(u.responseValidator&&await u.responseValidator(p),u.responseTransformer&&(p=await u.responseTransformer(p))),{...C,data:p??{}}}catch(m){let c=m;if(u.throwOnError)throw c;return c.error=c.response?.data??{},c}};return {buildUrl:b,delete:o=>n({...o,method:"DELETE"}),get:o=>n({...o,method:"GET"}),getConfig:s,head:o=>n({...o,method:"HEAD"}),instance:a,options:o=>n({...o,method:"OPTIONS"}),patch:o=>n({...o,method:"PATCH"}),post:o=>n({...o,method:"POST"}),put:o=>n({...o,method:"PUT"}),request:n,setConfig:l}};exports.createClient=L;exports.createConfig=x;exports.formDataBodySerializer=j;exports.jsonBodySerializer=q;exports.urlSearchParamsBodySerializer=P;//# sourceMappingURL=index.cjs.map 1 + 'use strict';var D=require('axios');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var D__default=/*#__PURE__*/_interopDefault(D);var A=async(t,e)=>{let r=typeof e=="function"?await e(t):e;if(r)return t.scheme==="bearer"?`Bearer ${r}`:t.scheme==="basic"?`Basic ${btoa(r)}`:r},z=(t,e,r)=>{typeof r=="string"||r instanceof Blob?t.append(e,r):t.append(e,JSON.stringify(r));},w=(t,e,r)=>{typeof r=="string"?t.append(e,r):t.append(e,JSON.stringify(r));},j={bodySerializer:t=>{let e=new FormData;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(e,r,a)):z(e,r,i));}),e}},q={bodySerializer:t=>JSON.stringify(t)},P={bodySerializer:t=>{let e=new URLSearchParams;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>w(e,r,a)):w(e,r,i));}),e}},v=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},$=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},k=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},h=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(!e){let n=(t?a:a.map(o=>encodeURIComponent(o))).join($(i));switch(i){case "label":return `.${n}`;case "matrix":return `;${r}=${n}`;case "simple":return n;default:return `${r}=${n}`}}let s=v(i),l=a.map(n=>i==="label"||i==="simple"?t?n:encodeURIComponent(n):f({allowReserved:t,name:r,value:n})).join(s);return i==="label"||i==="matrix"?s+l:l},f=({allowReserved:t,name:e,value:r})=>{if(r==null)return "";if(typeof r=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${e}=${t?r:encodeURIComponent(r)}`},g=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(a instanceof Date)return `${r}=${a.toISOString()}`;if(i!=="deepObject"&&!e){let n=[];Object.entries(a).forEach(([u,d])=>{n=[...n,u,t?d:encodeURIComponent(d)];});let o=n.join(",");switch(i){case "form":return `${r}=${o}`;case "label":return `.${o}`;case "matrix":return `;${r}=${o}`;default:return o}}let s=k(i),l=Object.entries(a).map(([n,o])=>f({allowReserved:t,name:i==="deepObject"?`${r}[${n}]`:n,value:o})).join(s);return i==="label"||i==="matrix"?s+l:l};var E=/\{[^{}]+\}/g,T=({path:t,url:e})=>{let r=e,i=e.match(E);if(i)for(let a of i){let s=false,l=a.substring(1,a.length-1),n="simple";l.endsWith("*")&&(s=true,l=l.substring(0,l.length-1)),l.startsWith(".")?(l=l.substring(1),n="label"):l.startsWith(";")&&(l=l.substring(1),n="matrix");let o=t[l];if(o==null)continue;if(Array.isArray(o)){r=r.replace(a,h({explode:s,name:l,style:n,value:o}));continue}if(typeof o=="object"){r=r.replace(a,g({explode:s,name:l,style:n,value:o}));continue}if(n==="matrix"){r=r.replace(a,`;${f({name:l,value:o})}`);continue}let u=encodeURIComponent(n==="label"?`.${o}`:o);r=r.replace(a,u);}return r},U=({allowReserved:t,array:e,object:r}={})=>a=>{let s=[];if(a&&typeof a=="object")for(let l in a){let n=a[l];if(n!=null){if(Array.isArray(n)){s=[...s,h({allowReserved:t,explode:true,name:l,style:"form",value:n,...e})];continue}if(typeof n=="object"){s=[...s,g({allowReserved:t,explode:true,name:l,style:"deepObject",value:n,...r})];continue}s=[...s,f({allowReserved:t,name:l,value:n})];}}return s.join("&")},R=async({security:t,...e})=>{for(let r of t){let i=await A(r,e.auth);if(!i)continue;let a=r.name??"Authorization";switch(r.in){case "query":e.query||(e.query={}),e.query[a]=i;break;case "header":default:e.headers[a]=i;break}return}},b=t=>H({path:t.path,query:t.paramsSerializer?undefined:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:U(t.querySerializer),url:t.url}),H=({path:t,query:e,querySerializer:r,url:i})=>{let s=i.startsWith("/")?i:`/${i}`;t&&(s=T({path:t,url:s}));let l=e?r(e):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(s+=`?${l}`),s},S=(t,e)=>{let r={...t,...e};return r.headers=y(t.headers,e.headers),r},B=["common","delete","get","head","patch","post","put"],y=(...t)=>{let e={};for(let r of t){if(!r||typeof r!="object")continue;let i=Object.entries(r);for(let[a,s]of i)if(B.includes(a)&&typeof s=="object")e[a]={...e[a],...s};else if(s===null)delete e[a];else if(Array.isArray(s))for(let l of s)e[a]=[...e[a]??[],l];else s!==undefined&&(e[a]=typeof s=="object"?JSON.stringify(s):s);}return e},x=(t={})=>({baseURL:"",...t});var I=(t={})=>{let e=S(x(),t),{auth:r,...i}=e,a=D__default.default.create(i),s=()=>({...e}),l=o=>(e=S(e,o),a.defaults={...a.defaults,...e,headers:y(a.defaults.headers,e.headers)},s()),n=async o=>{let u={...e,...o,axios:o.axios??e.axios??a,headers:y(e.headers,o.headers)};u.security&&await R({...u,security:u.security}),u.body&&u.bodySerializer&&(u.body=u.bodySerializer(u.body));let d=b(u);try{let m=u.axios,{auth:c,...O}=u,C=await m({...O,data:u.body,headers:u.headers,params:u.paramsSerializer?u.query:void 0,url:d}),{data:p}=C;return u.responseType==="json"&&(u.responseValidator&&await u.responseValidator(p),u.responseTransformer&&(p=await u.responseTransformer(p))),{...C,data:p??{}}}catch(m){let c=m;if(u.throwOnError)throw c;return c.error=c.response?.data??{},c}};return {buildUrl:b,delete:o=>n({...o,method:"DELETE"}),get:o=>n({...o,method:"GET"}),getConfig:s,head:o=>n({...o,method:"HEAD"}),instance:a,options:o=>n({...o,method:"OPTIONS"}),patch:o=>n({...o,method:"PATCH"}),post:o=>n({...o,method:"POST"}),put:o=>n({...o,method:"PUT"}),request:n,setConfig:l}};exports.createClient=I;exports.createConfig=x;exports.formDataBodySerializer=j;exports.jsonBodySerializer=q;exports.urlSearchParamsBodySerializer=P;//# sourceMappingURL=index.cjs.map 2 2 //# sourceMappingURL=index.cjs.map
+2 -2
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/index.d.cts
··· 186 186 headers?: any; 187 187 } ? OmitKeys<RequestOptions<ThrowOnError>, 'headers' | 'url'> & TData & Pick<RequestOptions<ThrowOnError>, 'body'> : OmitKeys<RequestOptions<ThrowOnError>, 'url'> & TData; 188 188 189 - declare const createConfig: CreateClientConfig; 189 + declare const createClient: (config?: Config) => Client; 190 190 191 - declare const createClient: (config: Config) => Client; 191 + declare const createConfig: CreateClientConfig; 192 192 193 193 export { type Auth, type Client, type Config, type CreateClientConfig, type Options, type OptionsLegacyParser, type QuerySerializerOptions, type RequestOptions, type RequestResult, createClient, createConfig, formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer };
+2 -2
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/index.d.ts
··· 186 186 headers?: any; 187 187 } ? OmitKeys<RequestOptions<ThrowOnError>, 'headers' | 'url'> & TData & Pick<RequestOptions<ThrowOnError>, 'body'> : OmitKeys<RequestOptions<ThrowOnError>, 'url'> & TData; 188 188 189 - declare const createConfig: CreateClientConfig; 189 + declare const createClient: (config?: Config) => Client; 190 190 191 - declare const createClient: (config: Config) => Client; 191 + declare const createConfig: CreateClientConfig; 192 192 193 193 export { type Auth, type Client, type Config, type CreateClientConfig, type Options, type OptionsLegacyParser, type QuerySerializerOptions, type RequestOptions, type RequestResult, createClient, createConfig, formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer };
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-fetch/bundle/client/index.cjs
··· 1 - 'use strict';var A=async(t,r)=>{let e=typeof r=="function"?await r(t):r;if(e)return t.scheme==="bearer"?`Bearer ${e}`:t.scheme==="basic"?`Basic ${btoa(e)}`:e},j=(t,r,e)=>{typeof e=="string"||e instanceof Blob?t.append(r,e):t.append(r,JSON.stringify(e));},z=(t,r,e)=>{typeof e=="string"?t.append(r,e):t.append(r,JSON.stringify(e));},U={bodySerializer:t=>{let r=new FormData;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>j(r,e,a)):j(r,e,i));}),r}},b={bodySerializer:t=>JSON.stringify(t)},k={bodySerializer:t=>{let r=new URLSearchParams;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(r,e,a)):z(r,e,i));}),r}},T=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},_=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},D=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},q=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(!r){let s=(t?a:a.map(l=>encodeURIComponent(l))).join(_(i));switch(i){case "label":return `.${s}`;case "matrix":return `;${e}=${s}`;case "simple":return s;default:return `${e}=${s}`}}let o=T(i),n=a.map(s=>i==="label"||i==="simple"?t?s:encodeURIComponent(s):y({allowReserved:t,name:e,value:s})).join(o);return i==="label"||i==="matrix"?o+n:n},y=({allowReserved:t,name:r,value:e})=>{if(e==null)return "";if(typeof e=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${r}=${t?e:encodeURIComponent(e)}`},O=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(a instanceof Date)return `${e}=${a.toISOString()}`;if(i!=="deepObject"&&!r){let s=[];Object.entries(a).forEach(([f,u])=>{s=[...s,f,t?u:encodeURIComponent(u)];});let l=s.join(",");switch(i){case "form":return `${e}=${l}`;case "label":return `.${l}`;case "matrix":return `;${e}=${l}`;default:return l}}let o=D(i),n=Object.entries(a).map(([s,l])=>y({allowReserved:t,name:i==="deepObject"?`${e}[${s}]`:s,value:l})).join(o);return i==="label"||i==="matrix"?o+n:n};var H=/\{[^{}]+\}/g,B=({path:t,url:r})=>{let e=r,i=r.match(H);if(i)for(let a of i){let o=false,n=a.substring(1,a.length-1),s="simple";n.endsWith("*")&&(o=true,n=n.substring(0,n.length-1)),n.startsWith(".")?(n=n.substring(1),s="label"):n.startsWith(";")&&(n=n.substring(1),s="matrix");let l=t[n];if(l==null)continue;if(Array.isArray(l)){e=e.replace(a,q({explode:o,name:n,style:s,value:l}));continue}if(typeof l=="object"){e=e.replace(a,O({explode:o,name:n,style:s,value:l}));continue}if(s==="matrix"){e=e.replace(a,`;${y({name:n,value:l})}`);continue}let f=encodeURIComponent(s==="label"?`.${l}`:l);e=e.replace(a,f);}return e},E=({allowReserved:t,array:r,object:e}={})=>a=>{let o=[];if(a&&typeof a=="object")for(let n in a){let s=a[n];if(s!=null){if(Array.isArray(s)){o=[...o,q({allowReserved:t,explode:true,name:n,style:"form",value:s,...r})];continue}if(typeof s=="object"){o=[...o,O({allowReserved:t,explode:true,name:n,style:"deepObject",value:s,...e})];continue}o=[...o,y({allowReserved:t,name:n,value:s})];}}return o.join("&")},P=t=>{if(!t)return "stream";let r=t.split(";")[0]?.trim();if(r){if(r.startsWith("application/json")||r.endsWith("+json"))return "json";if(r==="multipart/form-data")return "formData";if(["application/","audio/","image/","video/"].some(e=>r.startsWith(e)))return "blob";if(r.startsWith("text/"))return "text"}},I=async({security:t,...r})=>{for(let e of t){let i=await A(e,r.auth);if(!i)continue;let a=e.name??"Authorization";switch(e.in){case "query":r.query||(r.query={}),r.query[a]=i;break;case "header":default:r.headers.set(a,i);break}return}},S=t=>W({baseUrl:t.baseUrl??"",path:t.path,query:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:E(t.querySerializer),url:t.url}),W=({baseUrl:t,path:r,query:e,querySerializer:i,url:a})=>{let o=a.startsWith("/")?a:`/${a}`,n=t+o;r&&(n=B({path:r,url:n}));let s=e?i(e):"";return s.startsWith("?")&&(s=s.substring(1)),s&&(n+=`?${s}`),n},C=(t,r)=>{let e={...t,...r};return e.baseUrl?.endsWith("/")&&(e.baseUrl=e.baseUrl.substring(0,e.baseUrl.length-1)),e.headers=x(t.headers,r.headers),e},x=(...t)=>{let r=new Headers;for(let e of t){if(!e||typeof e!="object")continue;let i=e instanceof Headers?e.entries():Object.entries(e);for(let[a,o]of i)if(o===null)r.delete(a);else if(Array.isArray(o))for(let n of o)r.append(a,n);else o!==undefined&&r.set(a,typeof o=="object"?JSON.stringify(o):o);}return r},h=class{_fns;constructor(){this._fns=[];}clear(){this._fns=[];}exists(r){return this._fns.indexOf(r)!==-1}eject(r){let e=this._fns.indexOf(r);e!==-1&&(this._fns=[...this._fns.slice(0,e),...this._fns.slice(e+1)]);}use(r){this._fns=[...this._fns,r];}},v=()=>({error:new h,request:new h,response:new h}),N=E({allowReserved:false,array:{explode:true,style:"form"},object:{explode:true,style:"deepObject"}}),Q={"Content-Type":"application/json"},w=(t={})=>({...b,baseUrl:"",headers:Q,parseAs:"auto",querySerializer:N,...t});var F=(t={})=>{let r=C(w(),t),e=()=>({...r}),i=n=>(r=C(r,n),e()),a=v(),o=async n=>{let s={...r,...n,fetch:n.fetch??r.fetch??globalThis.fetch,headers:x(r.headers,n.headers)};s.security&&await I({...s,security:s.security}),s.body&&s.bodySerializer&&(s.body=s.bodySerializer(s.body)),s.body||s.headers.delete("Content-Type");let l=S(s),f={redirect:"follow",...s},u=new Request(l,f);for(let p of a.request._fns)u=await p(u,s);let $=s.fetch,c=await $(u);for(let p of a.response._fns)c=await p(c,u,s);let m={request:u,response:c};if(c.ok){if(c.status===204||c.headers.get("Content-Length")==="0")return {data:{},...m};let p=(s.parseAs==="auto"?P(c.headers.get("Content-Type")):s.parseAs)??"json";if(p==="stream")return {data:c.body,...m};let R=await c[p]();return p==="json"&&(s.responseValidator&&await s.responseValidator(R),s.responseTransformer&&(R=await s.responseTransformer(R))),{data:R,...m}}let g=await c.text();try{g=JSON.parse(g);}catch{}let d=g;for(let p of a.error._fns)d=await p(g,c,u,s);if(d=d||{},s.throwOnError)throw d;return {error:d,...m}};return {buildUrl:S,connect:n=>o({...n,method:"CONNECT"}),delete:n=>o({...n,method:"DELETE"}),get:n=>o({...n,method:"GET"}),getConfig:e,head:n=>o({...n,method:"HEAD"}),interceptors:a,options:n=>o({...n,method:"OPTIONS"}),patch:n=>o({...n,method:"PATCH"}),post:n=>o({...n,method:"POST"}),put:n=>o({...n,method:"PUT"}),request:o,setConfig:i,trace:n=>o({...n,method:"TRACE"})}};exports.createClient=F;exports.createConfig=w;exports.formDataBodySerializer=U;exports.jsonBodySerializer=b;exports.urlSearchParamsBodySerializer=k;//# sourceMappingURL=index.cjs.map 1 + 'use strict';var A=async(t,r)=>{let e=typeof r=="function"?await r(t):r;if(e)return t.scheme==="bearer"?`Bearer ${e}`:t.scheme==="basic"?`Basic ${btoa(e)}`:e},j=(t,r,e)=>{typeof e=="string"||e instanceof Blob?t.append(r,e):t.append(r,JSON.stringify(e));},z=(t,r,e)=>{typeof e=="string"?t.append(r,e):t.append(r,JSON.stringify(e));},U={bodySerializer:t=>{let r=new FormData;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>j(r,e,a)):j(r,e,i));}),r}},b={bodySerializer:t=>JSON.stringify(t)},k={bodySerializer:t=>{let r=new URLSearchParams;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(r,e,a)):z(r,e,i));}),r}},T=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},_=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},D=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},q=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(!r){let s=(t?a:a.map(l=>encodeURIComponent(l))).join(_(i));switch(i){case "label":return `.${s}`;case "matrix":return `;${e}=${s}`;case "simple":return s;default:return `${e}=${s}`}}let o=T(i),n=a.map(s=>i==="label"||i==="simple"?t?s:encodeURIComponent(s):y({allowReserved:t,name:e,value:s})).join(o);return i==="label"||i==="matrix"?o+n:n},y=({allowReserved:t,name:r,value:e})=>{if(e==null)return "";if(typeof e=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${r}=${t?e:encodeURIComponent(e)}`},O=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(a instanceof Date)return `${e}=${a.toISOString()}`;if(i!=="deepObject"&&!r){let s=[];Object.entries(a).forEach(([f,u])=>{s=[...s,f,t?u:encodeURIComponent(u)];});let l=s.join(",");switch(i){case "form":return `${e}=${l}`;case "label":return `.${l}`;case "matrix":return `;${e}=${l}`;default:return l}}let o=D(i),n=Object.entries(a).map(([s,l])=>y({allowReserved:t,name:i==="deepObject"?`${e}[${s}]`:s,value:l})).join(o);return i==="label"||i==="matrix"?o+n:n};var H=/\{[^{}]+\}/g,B=({path:t,url:r})=>{let e=r,i=r.match(H);if(i)for(let a of i){let o=false,n=a.substring(1,a.length-1),s="simple";n.endsWith("*")&&(o=true,n=n.substring(0,n.length-1)),n.startsWith(".")?(n=n.substring(1),s="label"):n.startsWith(";")&&(n=n.substring(1),s="matrix");let l=t[n];if(l==null)continue;if(Array.isArray(l)){e=e.replace(a,q({explode:o,name:n,style:s,value:l}));continue}if(typeof l=="object"){e=e.replace(a,O({explode:o,name:n,style:s,value:l}));continue}if(s==="matrix"){e=e.replace(a,`;${y({name:n,value:l})}`);continue}let f=encodeURIComponent(s==="label"?`.${l}`:l);e=e.replace(a,f);}return e},E=({allowReserved:t,array:r,object:e}={})=>a=>{let o=[];if(a&&typeof a=="object")for(let n in a){let s=a[n];if(s!=null){if(Array.isArray(s)){o=[...o,q({allowReserved:t,explode:true,name:n,style:"form",value:s,...r})];continue}if(typeof s=="object"){o=[...o,O({allowReserved:t,explode:true,name:n,style:"deepObject",value:s,...e})];continue}o=[...o,y({allowReserved:t,name:n,value:s})];}}return o.join("&")},P=t=>{if(!t)return "stream";let r=t.split(";")[0]?.trim();if(r){if(r.startsWith("application/json")||r.endsWith("+json"))return "json";if(r==="multipart/form-data")return "formData";if(["application/","audio/","image/","video/"].some(e=>r.startsWith(e)))return "blob";if(r.startsWith("text/"))return "text"}},I=async({security:t,...r})=>{for(let e of t){let i=await A(e,r.auth);if(!i)continue;let a=e.name??"Authorization";switch(e.in){case "query":r.query||(r.query={}),r.query[a]=i;break;case "header":default:r.headers.set(a,i);break}return}},S=t=>W({baseUrl:t.baseUrl??"",path:t.path,query:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:E(t.querySerializer),url:t.url}),W=({baseUrl:t,path:r,query:e,querySerializer:i,url:a})=>{let o=a.startsWith("/")?a:`/${a}`,n=t+o;r&&(n=B({path:r,url:n}));let s=e?i(e):"";return s.startsWith("?")&&(s=s.substring(1)),s&&(n+=`?${s}`),n},C=(t,r)=>{let e={...t,...r};return e.baseUrl?.endsWith("/")&&(e.baseUrl=e.baseUrl.substring(0,e.baseUrl.length-1)),e.headers=x(t.headers,r.headers),e},x=(...t)=>{let r=new Headers;for(let e of t){if(!e||typeof e!="object")continue;let i=e instanceof Headers?e.entries():Object.entries(e);for(let[a,o]of i)if(o===null)r.delete(a);else if(Array.isArray(o))for(let n of o)r.append(a,n);else o!==undefined&&r.set(a,typeof o=="object"?JSON.stringify(o):o);}return r},h=class{_fns;constructor(){this._fns=[];}clear(){this._fns=[];}exists(r){return this._fns.indexOf(r)!==-1}eject(r){let e=this._fns.indexOf(r);e!==-1&&(this._fns=[...this._fns.slice(0,e),...this._fns.slice(e+1)]);}use(r){this._fns=[...this._fns,r];}},v=()=>({error:new h,request:new h,response:new h}),N=E({allowReserved:false,array:{explode:true,style:"form"},object:{explode:true,style:"deepObject"}}),Q={"Content-Type":"application/json"},w=(t={})=>({...b,baseUrl:"",headers:Q,parseAs:"auto",querySerializer:N,...t});var J=(t={})=>{let r=C(w(),t),e=()=>({...r}),i=n=>(r=C(r,n),e()),a=v(),o=async n=>{let s={...r,...n,fetch:n.fetch??r.fetch??globalThis.fetch,headers:x(r.headers,n.headers)};s.security&&await I({...s,security:s.security}),s.body&&s.bodySerializer&&(s.body=s.bodySerializer(s.body)),s.body||s.headers.delete("Content-Type");let l=S(s),f={redirect:"follow",...s},u=new Request(l,f);for(let p of a.request._fns)u=await p(u,s);let $=s.fetch,c=await $(u);for(let p of a.response._fns)c=await p(c,u,s);let m={request:u,response:c};if(c.ok){if(c.status===204||c.headers.get("Content-Length")==="0")return {data:{},...m};let p=(s.parseAs==="auto"?P(c.headers.get("Content-Type")):s.parseAs)??"json";if(p==="stream")return {data:c.body,...m};let R=await c[p]();return p==="json"&&(s.responseValidator&&await s.responseValidator(R),s.responseTransformer&&(R=await s.responseTransformer(R))),{data:R,...m}}let g=await c.text();try{g=JSON.parse(g);}catch{}let d=g;for(let p of a.error._fns)d=await p(g,c,u,s);if(d=d||{},s.throwOnError)throw d;return {error:d,...m}};return {buildUrl:S,connect:n=>o({...n,method:"CONNECT"}),delete:n=>o({...n,method:"DELETE"}),get:n=>o({...n,method:"GET"}),getConfig:e,head:n=>o({...n,method:"HEAD"}),interceptors:a,options:n=>o({...n,method:"OPTIONS"}),patch:n=>o({...n,method:"PATCH"}),post:n=>o({...n,method:"POST"}),put:n=>o({...n,method:"PUT"}),request:o,setConfig:i,trace:n=>o({...n,method:"TRACE"})}};exports.createClient=J;exports.createConfig=w;exports.formDataBodySerializer=U;exports.jsonBodySerializer=b;exports.urlSearchParamsBodySerializer=k;//# sourceMappingURL=index.cjs.map 2 2 //# sourceMappingURL=index.cjs.map
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-nuxt/bundle/client/index.cjs
··· 1 - 'use strict';var app=require('nuxt/app'),vue=require('vue');var $=async(r,t)=>{let e=typeof t=="function"?await t(r):t;if(e)return r.scheme==="bearer"?`Bearer ${e}`:r.scheme==="basic"?`Basic ${btoa(e)}`:e},P=(r,t,e)=>{typeof e=="string"||e instanceof Blob?r.append(t,e):r.append(t,JSON.stringify(e));},U=(r,t,e)=>{typeof e=="string"?r.append(t,e):r.append(t,JSON.stringify(e));},L={bodySerializer:r=>{let t=new FormData;return Object.entries(r).forEach(([e,s])=>{s!=null&&(Array.isArray(s)?s.forEach(o=>P(t,e,o)):P(t,e,s));}),t}},R={bodySerializer:r=>JSON.stringify(r)},H={bodySerializer:r=>{let t=new URLSearchParams;return Object.entries(r).forEach(([e,s])=>{s!=null&&(Array.isArray(s)?s.forEach(o=>U(t,e,o)):U(t,e,s));}),t}},D=r=>{switch(r){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},N=r=>{switch(r){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},I=r=>{switch(r){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},C=({allowReserved:r,explode:t,name:e,style:s,value:o})=>{if(!t){let n=(r?o:o.map(u=>encodeURIComponent(u))).join(N(s));switch(s){case "label":return `.${n}`;case "matrix":return `;${e}=${n}`;case "simple":return n;default:return `${e}=${n}`}}let a=D(s),i=o.map(n=>s==="label"||s==="simple"?r?n:encodeURIComponent(n):y({allowReserved:r,name:e,value:n})).join(a);return s==="label"||s==="matrix"?a+i:i},y=({allowReserved:r,name:t,value:e})=>{if(e==null)return "";if(typeof e=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${t}=${r?e:encodeURIComponent(e)}`},S=({allowReserved:r,explode:t,name:e,style:s,value:o})=>{if(o instanceof Date)return `${e}=${o.toISOString()}`;if(s!=="deepObject"&&!t){let n=[];Object.entries(o).forEach(([l,p])=>{n=[...n,l,r?p:encodeURIComponent(p)];});let u=n.join(",");switch(s){case "form":return `${e}=${u}`;case "label":return `.${u}`;case "matrix":return `;${e}=${u}`;default:return u}}let a=I(s),i=Object.entries(o).map(([n,u])=>y({allowReserved:r,name:s==="deepObject"?`${e}[${n}]`:n,value:u})).join(a);return s==="label"||s==="matrix"?a+i:i};var Q=/\{[^{}]+\}/g,V=({path:r,url:t})=>{let e=t,s=t.match(Q);if(s)for(let o of s){let a=false,i=o.substring(1,o.length-1),n="simple";i.endsWith("*")&&(a=true,i=i.substring(0,i.length-1)),i.startsWith(".")?(i=i.substring(1),n="label"):i.startsWith(";")&&(i=i.substring(1),n="matrix");let u=vue.toValue(vue.toValue(r)[i]);if(u==null)continue;if(Array.isArray(u)){e=e.replace(o,C({explode:a,name:i,style:n,value:u}));continue}if(typeof u=="object"){e=e.replace(o,S({explode:a,name:i,style:n,value:u}));continue}if(n==="matrix"){e=e.replace(o,`;${y({name:i,value:u})}`);continue}let l=encodeURIComponent(n==="label"?`.${u}`:u);e=e.replace(o,l);}return e},E=({allowReserved:r,array:t,object:e}={})=>o=>{let a=[],i=vue.toValue(o);if(i&&typeof i=="object")for(let n in i){let u=vue.toValue(i[n]);if(u!=null){if(Array.isArray(u)){a=[...a,C({allowReserved:r,explode:true,name:n,style:"form",value:u,...t})];continue}if(typeof u=="object"){a=[...a,S({allowReserved:r,explode:true,name:n,style:"deepObject",value:u,...e})];continue}a=[...a,y({allowReserved:r,name:n,value:u})];}}return a.join("&")},k=async({security:r,...t})=>{for(let e of r){let s=await $(e,t.auth);if(!s)continue;let o=e.name??"Authorization";switch(e.in){case "query":t.query||(t.query={}),vue.toValue(t.query)[o]=s;break;case "header":default:t.headers.set(o,s);break}return}},d=r=>F({baseUrl:r.baseURL??"",path:r.path,query:r.query,querySerializer:typeof r.querySerializer=="function"?r.querySerializer:E(r.querySerializer),url:r.url}),F=({baseUrl:r,path:t,query:e,querySerializer:s,url:o})=>{let a=o.startsWith("/")?o:`/${o}`,i=r+a;t&&(i=V({path:t,url:i}));let n=e?s(e):"";return n.startsWith("?")&&(n=n.substring(1)),n&&(i+=`?${n}`),i},x=(r,t)=>{let e={...r,...t};return e.baseURL?.endsWith("/")&&(e.baseURL=e.baseURL.substring(0,e.baseURL.length-1)),e.headers=T(r.headers,t.headers),e},T=(...r)=>{let t=new Headers;for(let e of r){if(!e||typeof e!="object")continue;let s=e;vue.isRef(s)&&(s=vue.unref(s));let o=s instanceof Headers?s.entries():Object.entries(s);for(let[a,i]of o)if(i===null)t.delete(a);else if(Array.isArray(i))for(let n of i)t.append(a,f(n));else if(i!==undefined){let n=f(i);t.set(a,typeof n=="object"?JSON.stringify(n):n);}}return t},w=(...r)=>r.reduce((t,e)=>{if(typeof e=="function")t.push(e);else if(Array.isArray(e))return t.concat(e);return t},[]),W=E({allowReserved:false,array:{explode:true,style:"form"},object:{explode:true,style:"deepObject"}}),J={"Content-Type":"application/json"},q=(r={})=>({...R,baseURL:"",headers:J,querySerializer:W,...r}),f=r=>{if(r===null||typeof r!="object"||r instanceof Headers)return vue.isRef(r)?vue.unref(r):r;if(Array.isArray(r))return r.map(e=>f(e));if(vue.isRef(r))return f(vue.unref(r));let t={};for(let e in r)t[e]=f(r[e]);return t};var re=(r={})=>{let t=x(q(),r),e=()=>({...t}),s=a=>(t=x(t,a),e()),o=({asyncDataOptions:a,composable:i,key:n,...u})=>{let l={...t,...u,$fetch:u.$fetch??t.$fetch??$fetch,headers:T(t.headers,u.headers),onRequest:w(t.onRequest,u.onRequest),onResponse:w(t.onResponse,u.onResponse)},{responseTransformer:p,responseValidator:g,security:j}=l;j&&(l.onRequest=[async({options:c})=>{await k({auth:l.auth,headers:c.headers,query:c.query,security:j});},...l.onRequest]),(p||g)&&(l.onResponse=[...l.onResponse,async({options:c,response:b})=>{c.responseType&&c.responseType!=="json"||(g&&await g(b._data),p&&(b._data=await p(b._data)));}]),l.body&&l.bodySerializer&&(l.body=l.bodySerializer(f(l.body))),l.body||l.headers.delete("Content-Type");let O=l.$fetch;if(i==="$fetch"){let c=d(l);return O(c,f(l))}if(i==="useFetch"){let c=d(l);return app.useFetch(c,l)}if(i==="useLazyFetch"){let c=d(l);return app.useLazyFetch(c,l)}let h=()=>{let c=d(l);return O(c,f(l))};if(i==="useAsyncData")return n?app.useAsyncData(n,h,a):app.useAsyncData(h,a);if(i==="useLazyAsyncData")return n?app.useLazyAsyncData(n,h,a):app.useLazyAsyncData(h,a)};return {buildUrl:d,connect:a=>o({...a,method:"CONNECT"}),delete:a=>o({...a,method:"DELETE"}),get:a=>o({...a,method:"GET"}),getConfig:e,head:a=>o({...a,method:"HEAD"}),options:a=>o({...a,method:"OPTIONS"}),patch:a=>o({...a,method:"PATCH"}),post:a=>o({...a,method:"POST"}),put:a=>o({...a,method:"PUT"}),request:o,setConfig:s,trace:a=>o({...a,method:"TRACE"})}};exports.createClient=re;exports.createConfig=q;exports.formDataBodySerializer=L;exports.jsonBodySerializer=R;exports.urlSearchParamsBodySerializer=H;//# sourceMappingURL=index.cjs.map 1 + 'use strict';var app=require('nuxt/app'),vue=require('vue');var $=async(r,t)=>{let e=typeof t=="function"?await t(r):t;if(e)return r.scheme==="bearer"?`Bearer ${e}`:r.scheme==="basic"?`Basic ${btoa(e)}`:e},P=(r,t,e)=>{typeof e=="string"||e instanceof Blob?r.append(t,e):r.append(t,JSON.stringify(e));},U=(r,t,e)=>{typeof e=="string"?r.append(t,e):r.append(t,JSON.stringify(e));},L={bodySerializer:r=>{let t=new FormData;return Object.entries(r).forEach(([e,s])=>{s!=null&&(Array.isArray(s)?s.forEach(o=>P(t,e,o)):P(t,e,s));}),t}},R={bodySerializer:r=>JSON.stringify(r)},H={bodySerializer:r=>{let t=new URLSearchParams;return Object.entries(r).forEach(([e,s])=>{s!=null&&(Array.isArray(s)?s.forEach(o=>U(t,e,o)):U(t,e,s));}),t}},D=r=>{switch(r){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},N=r=>{switch(r){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},I=r=>{switch(r){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},C=({allowReserved:r,explode:t,name:e,style:s,value:o})=>{if(!t){let n=(r?o:o.map(u=>encodeURIComponent(u))).join(N(s));switch(s){case "label":return `.${n}`;case "matrix":return `;${e}=${n}`;case "simple":return n;default:return `${e}=${n}`}}let a=D(s),i=o.map(n=>s==="label"||s==="simple"?r?n:encodeURIComponent(n):y({allowReserved:r,name:e,value:n})).join(a);return s==="label"||s==="matrix"?a+i:i},y=({allowReserved:r,name:t,value:e})=>{if(e==null)return "";if(typeof e=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${t}=${r?e:encodeURIComponent(e)}`},S=({allowReserved:r,explode:t,name:e,style:s,value:o})=>{if(o instanceof Date)return `${e}=${o.toISOString()}`;if(s!=="deepObject"&&!t){let n=[];Object.entries(o).forEach(([l,p])=>{n=[...n,l,r?p:encodeURIComponent(p)];});let u=n.join(",");switch(s){case "form":return `${e}=${u}`;case "label":return `.${u}`;case "matrix":return `;${e}=${u}`;default:return u}}let a=I(s),i=Object.entries(o).map(([n,u])=>y({allowReserved:r,name:s==="deepObject"?`${e}[${n}]`:n,value:u})).join(a);return s==="label"||s==="matrix"?a+i:i};var Q=/\{[^{}]+\}/g,V=({path:r,url:t})=>{let e=t,s=t.match(Q);if(s)for(let o of s){let a=false,i=o.substring(1,o.length-1),n="simple";i.endsWith("*")&&(a=true,i=i.substring(0,i.length-1)),i.startsWith(".")?(i=i.substring(1),n="label"):i.startsWith(";")&&(i=i.substring(1),n="matrix");let u=vue.toValue(vue.toValue(r)[i]);if(u==null)continue;if(Array.isArray(u)){e=e.replace(o,C({explode:a,name:i,style:n,value:u}));continue}if(typeof u=="object"){e=e.replace(o,S({explode:a,name:i,style:n,value:u}));continue}if(n==="matrix"){e=e.replace(o,`;${y({name:i,value:u})}`);continue}let l=encodeURIComponent(n==="label"?`.${u}`:u);e=e.replace(o,l);}return e},E=({allowReserved:r,array:t,object:e}={})=>o=>{let a=[],i=vue.toValue(o);if(i&&typeof i=="object")for(let n in i){let u=vue.toValue(i[n]);if(u!=null){if(Array.isArray(u)){a=[...a,C({allowReserved:r,explode:true,name:n,style:"form",value:u,...t})];continue}if(typeof u=="object"){a=[...a,S({allowReserved:r,explode:true,name:n,style:"deepObject",value:u,...e})];continue}a=[...a,y({allowReserved:r,name:n,value:u})];}}return a.join("&")},k=async({security:r,...t})=>{for(let e of r){let s=await $(e,t.auth);if(!s)continue;let o=e.name??"Authorization";switch(e.in){case "query":t.query||(t.query={}),vue.toValue(t.query)[o]=s;break;case "header":default:t.headers.set(o,s);break}return}},d=r=>F({baseUrl:r.baseURL??"",path:r.path,query:r.query,querySerializer:typeof r.querySerializer=="function"?r.querySerializer:E(r.querySerializer),url:r.url}),F=({baseUrl:r,path:t,query:e,querySerializer:s,url:o})=>{let a=o.startsWith("/")?o:`/${o}`,i=r+a;t&&(i=V({path:t,url:i}));let n=e?s(e):"";return n.startsWith("?")&&(n=n.substring(1)),n&&(i+=`?${n}`),i},x=(r,t)=>{let e={...r,...t};return e.baseURL?.endsWith("/")&&(e.baseURL=e.baseURL.substring(0,e.baseURL.length-1)),e.headers=T(r.headers,t.headers),e},T=(...r)=>{let t=new Headers;for(let e of r){if(!e||typeof e!="object")continue;let s=e;vue.isRef(s)&&(s=vue.unref(s));let o=s instanceof Headers?s.entries():Object.entries(s);for(let[a,i]of o)if(i===null)t.delete(a);else if(Array.isArray(i))for(let n of i)t.append(a,f(n));else if(i!==undefined){let n=f(i);t.set(a,typeof n=="object"?JSON.stringify(n):n);}}return t},w=(...r)=>r.reduce((t,e)=>{if(typeof e=="function")t.push(e);else if(Array.isArray(e))return t.concat(e);return t},[]),W=E({allowReserved:false,array:{explode:true,style:"form"},object:{explode:true,style:"deepObject"}}),J={"Content-Type":"application/json"},q=(r={})=>({...R,baseURL:"",headers:J,querySerializer:W,...r}),f=r=>{if(r===null||typeof r!="object"||r instanceof Headers)return vue.isRef(r)?vue.unref(r):r;if(Array.isArray(r))return r.map(e=>f(e));if(vue.isRef(r))return f(vue.unref(r));let t={};for(let e in r)t[e]=f(r[e]);return t};var K=(r={})=>{let t=x(q(),r),e=()=>({...t}),s=a=>(t=x(t,a),e()),o=({asyncDataOptions:a,composable:i,key:n,...u})=>{let l={...t,...u,$fetch:u.$fetch??t.$fetch??$fetch,headers:T(t.headers,u.headers),onRequest:w(t.onRequest,u.onRequest),onResponse:w(t.onResponse,u.onResponse)},{responseTransformer:p,responseValidator:g,security:j}=l;j&&(l.onRequest=[async({options:c})=>{await k({auth:l.auth,headers:c.headers,query:c.query,security:j});},...l.onRequest]),(p||g)&&(l.onResponse=[...l.onResponse,async({options:c,response:b})=>{c.responseType&&c.responseType!=="json"||(g&&await g(b._data),p&&(b._data=await p(b._data)));}]),l.body&&l.bodySerializer&&(l.body=l.bodySerializer(f(l.body))),l.body||l.headers.delete("Content-Type");let O=l.$fetch;if(i==="$fetch"){let c=d(l);return O(c,f(l))}if(i==="useFetch"){let c=d(l);return app.useFetch(c,l)}if(i==="useLazyFetch"){let c=d(l);return app.useLazyFetch(c,l)}let h=()=>{let c=d(l);return O(c,f(l))};if(i==="useAsyncData")return n?app.useAsyncData(n,h,a):app.useAsyncData(h,a);if(i==="useLazyAsyncData")return n?app.useLazyAsyncData(n,h,a):app.useLazyAsyncData(h,a)};return {buildUrl:d,connect:a=>o({...a,method:"CONNECT"}),delete:a=>o({...a,method:"DELETE"}),get:a=>o({...a,method:"GET"}),getConfig:e,head:a=>o({...a,method:"HEAD"}),options:a=>o({...a,method:"OPTIONS"}),patch:a=>o({...a,method:"PATCH"}),post:a=>o({...a,method:"POST"}),put:a=>o({...a,method:"PUT"}),request:o,setConfig:s,trace:a=>o({...a,method:"TRACE"})}};exports.createClient=K;exports.createConfig=q;exports.formDataBodySerializer=L;exports.jsonBodySerializer=R;exports.urlSearchParamsBodySerializer=H;//# sourceMappingURL=index.cjs.map 2 2 //# sourceMappingURL=index.cjs.map
+2 -2
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-nuxt/bundle/client/index.d.cts
··· 180 180 type FetchOptions<TData> = Omit<UseFetchOptions<TData, TData>, keyof AsyncDataOptions<TData>>; 181 181 type Composable = '$fetch' | 'useAsyncData' | 'useFetch' | 'useLazyAsyncData' | 'useLazyFetch'; 182 182 183 - declare const createConfig: CreateClientConfig; 184 - 185 183 declare const createClient: (config?: Config) => Client; 184 + 185 + declare const createConfig: CreateClientConfig; 186 186 187 187 export { type Auth, type Client, type Composable, type Config, type CreateClientConfig, type Options, type OptionsLegacyParser, type QuerySerializerOptions, type RequestOptions, type RequestResult, createClient, createConfig, formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer };
+2 -2
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-nuxt/bundle/client/index.d.ts
··· 180 180 type FetchOptions<TData> = Omit<UseFetchOptions<TData, TData>, keyof AsyncDataOptions<TData>>; 181 181 type Composable = '$fetch' | 'useAsyncData' | 'useFetch' | 'useLazyAsyncData' | 'useLazyFetch'; 182 182 183 - declare const createConfig: CreateClientConfig; 184 - 185 183 declare const createClient: (config?: Config) => Client; 184 + 185 + declare const createConfig: CreateClientConfig; 186 186 187 187 export { type Auth, type Client, type Composable, type Config, type CreateClientConfig, type Options, type OptionsLegacyParser, type QuerySerializerOptions, type RequestOptions, type RequestResult, createClient, createConfig, formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer };
+11 -2
pnpm-lock.yaml
··· 261 261 262 262 examples/openapi-ts-next: 263 263 dependencies: 264 - '@hey-api/client-fetch': 264 + '@hey-api/client-next': 265 265 specifier: workspace:* 266 - version: link:../../packages/client-fetch 266 + version: link:../../packages/client-next 267 267 next: 268 268 specifier: 15.1.6 269 269 version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) ··· 730 730 specifier: workspace:* 731 731 version: link:../client-core 732 732 733 + packages/client-next: 734 + devDependencies: 735 + '@hey-api/client-core': 736 + specifier: workspace:* 737 + version: link:../client-core 738 + 733 739 packages/client-nuxt: 734 740 dependencies: 735 741 nuxt: ··· 800 806 '@hey-api/client-fetch': 801 807 specifier: workspace:* 802 808 version: link:../client-fetch 809 + '@hey-api/client-next': 810 + specifier: workspace:* 811 + version: link:../client-next 803 812 '@hey-api/client-nuxt': 804 813 specifier: workspace:* 805 814 version: link:../client-nuxt