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

feat: initialize `ofetch` example project

authored by

Dmitriy Brolnickij and committed by
Lubos
a1807fe4 5305752a

+3838
+1
examples/openapi-ts-ofetch/env.d.ts
··· 1 + /// <reference types="vite/client" />
+12
examples/openapi-ts-ofetch/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Hey API — ofetch example</title> 7 + </head> 8 + <body> 9 + <div id="app"></div> 10 + <script type="module" src="/src/main.ts"></script> 11 + </body> 12 + </html>
+20
examples/openapi-ts-ofetch/openapi-ts.config.ts
··· 1 + import { defineConfig } from '@hey-api/openapi-ts'; 2 + 3 + export default defineConfig({ 4 + input: 5 + 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 6 + output: { 7 + format: 'prettier', 8 + lint: 'eslint', 9 + path: './src/client', 10 + }, 11 + plugins: [ 12 + '@hey-api/client-ofetch', 13 + '@hey-api/schemas', 14 + '@hey-api/sdk', 15 + { 16 + enums: 'javascript', 17 + name: '@hey-api/typescript', 18 + }, 19 + ], 20 + });
+45
examples/openapi-ts-ofetch/package.json
··· 1 + { 2 + "name": "@example/openapi-ts-ofetch", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "build": "vite build", 8 + "dev": "vite", 9 + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx --fix --ignore-path .gitignore", 10 + "openapi-ts": "openapi-ts", 11 + "preview": "vite preview", 12 + "typecheck": "vue-tsc --build --force" 13 + }, 14 + "dependencies": { 15 + "ofetch": "1.4.1", 16 + "vue": "3.5.21" 17 + }, 18 + "devDependencies": { 19 + "@config/vite-base": "workspace:*", 20 + "@hey-api/openapi-ts": "workspace:*", 21 + "@rushstack/eslint-patch": "1.10.5", 22 + "@tsconfig/node20": "20.1.4", 23 + "@types/jsdom": "21.1.7", 24 + "@types/node": "22.10.5", 25 + "@vitejs/plugin-vue": "5.2.1", 26 + "@vitejs/plugin-vue-jsx": "4.1.1", 27 + "@vue/eslint-config-prettier": "10.1.0", 28 + "@vue/eslint-config-typescript": "14.2.0", 29 + "@vue/test-utils": "2.4.6", 30 + "@vue/tsconfig": "0.7.0", 31 + "autoprefixer": "10.4.20", 32 + "eslint": "9.17.0", 33 + "eslint-plugin-vue": "9.32.0", 34 + "jsdom": "23.0.0", 35 + "npm-run-all2": "6.2.0", 36 + "postcss": "8.4.41", 37 + "prettier": "3.4.2", 38 + "tailwindcss": "3.4.9", 39 + "typescript": "5.8.3", 40 + "vite": "7.1.2", 41 + "vite-plugin-vue-devtools": "7.7.0", 42 + "vitest": "3.1.1", 43 + "vue-tsc": "2.2.0" 44 + } 45 + }
+6
examples/openapi-ts-ofetch/postcss.config.js
··· 1 + export default { 2 + plugins: { 3 + autoprefixer: {}, 4 + tailwindcss: {}, 5 + }, 6 + };
+190
examples/openapi-ts-ofetch/src/App.vue
··· 1 + <script setup lang="ts"> 2 + import { ref } from 'vue'; 3 + 4 + import { createClient } from './client/client'; 5 + import { PetSchema } from './client/schemas.gen'; 6 + import { addPet, getPetById, updatePet } from './client/sdk.gen'; 7 + import type { Pet } from './client/types.gen'; 8 + 9 + const pet = ref<Pet | undefined>(); 10 + const petInput = ref({ name: '', category: '' }); 11 + const isPetNameRequired = PetSchema.required.includes('name'); 12 + 13 + const localClient = createClient({ 14 + baseUrl: 'https://petstore3.swagger.io/api/v3', 15 + headers: { 16 + Authorization: 'Bearer <token_from_local_client>', 17 + }, 18 + }); 19 + 20 + localClient.interceptors.request.use((request, options) => { 21 + if ( 22 + options.url === '/pet/{petId}' && 23 + options.method === 'GET' && 24 + Math.random() < 0.5 25 + ) { 26 + request.headers.set('Authorization', 'Bearer <token_from_interceptor>'); 27 + } 28 + return request; 29 + }); 30 + 31 + localClient.interceptors.error.use((error) => { 32 + console.error(error); 33 + return error; 34 + }); 35 + 36 + function randomInt(min: number, max: number) { 37 + return Math.floor(Math.random() * (max - min + 1) + min); 38 + } 39 + 40 + async function setRandomPetId() { 41 + const id = randomInt(1, 10); 42 + const { data, error } = await getPetById({ 43 + client: localClient, 44 + path: { petId: id }, 45 + }); 46 + if (error) { 47 + console.error(error); 48 + return; 49 + } 50 + pet.value = data!; 51 + } 52 + 53 + function buildPetBody(base?: Partial<Pet>) { 54 + return { 55 + category: { 56 + id: base?.category?.id ?? 0, 57 + name: petInput.value.category, 58 + }, 59 + id: base?.id ?? 0, 60 + name: petInput.value.name, 61 + photoUrls: ['string'], 62 + status: 'available' as const, 63 + tags: [ 64 + { 65 + id: 0, 66 + name: 'string', 67 + }, 68 + ], 69 + }; 70 + } 71 + 72 + async function handleAddPet() { 73 + if (isPetNameRequired && !petInput.value.name) return; 74 + const { data, error } = await addPet({ body: buildPetBody() }); 75 + if (error) { 76 + console.error(error); 77 + return; 78 + } 79 + pet.value = data!; 80 + } 81 + 82 + async function handleUpdatePet() { 83 + if (!pet.value) return; 84 + const { data, error } = await updatePet({ 85 + body: buildPetBody(pet.value), 86 + headers: { Authorization: 'Bearer <token_from_method>' }, 87 + }); 88 + if (error) { 89 + console.error(error); 90 + return; 91 + } 92 + pet.value = data!; 93 + } 94 + </script> 95 + 96 + <template> 97 + <div class="bg-[#18191b] py-12"> 98 + <div class="mx-auto flex max-w-md flex-col gap-12"> 99 + <div class="flex items-center"> 100 + <a class="shrink-0" href="https://heyapi.dev/" target="_blank"> 101 + <img 102 + alt="Hey API logo" 103 + class="size-16 transition duration-300 will-change-auto" 104 + src="https://heyapi.dev/logo.png" 105 + /> 106 + </a> 107 + 108 + <h1 class="text-2xl font-bold text-white"> 109 + @hey-api/openapi-ts 🤝 ofetch 110 + </h1> 111 + </div> 112 + 113 + <div class="flex flex-col gap-2"> 114 + <div 115 + class="flex max-w-60 items-center gap-3 rounded border border-[#575e64] bg-[#1f2123] p-4" 116 + > 117 + <div 118 + class="flex size-10 place-content-center place-items-center rounded-full bg-[#233057] text-lg font-medium text-[#9eb1ff]" 119 + > 120 + <span> 121 + {{ pet?.name?.slice(0, 1) || 'N' }} 122 + </span> 123 + </div> 124 + 125 + <div> 126 + <p class="text-sm font-bold text-white"> 127 + Name: {{ pet?.name || 'N/A' }} 128 + </p> 129 + 130 + <p class="text-sm text-[#f1f7feb5]"> 131 + Category: {{ pet?.category?.name || 'N/A' }} 132 + </p> 133 + </div> 134 + </div> 135 + 136 + <button 137 + class="rounded bg-[#3e63dd] p-1 text-sm font-medium text-white" 138 + type="button" 139 + @click="setRandomPetId" 140 + > 141 + Get Random Pet 142 + </button> 143 + </div> 144 + 145 + <form class="flex flex-col gap-3" @submit.prevent="handleAddPet"> 146 + <div class="flex w-64 flex-col gap-1"> 147 + <label class="font-medium text-white" for="name">Name</label> 148 + 149 + <input 150 + v-model="petInput.name" 151 + class="rounded border border-[#575e64] bg-[#121314] p-1 text-sm text-white placeholder:text-[#575e64]" 152 + name="name" 153 + placeholder="Kitty" 154 + :required="isPetNameRequired" 155 + /> 156 + </div> 157 + 158 + <div class="flex w-64 flex-col gap-1"> 159 + <label class="font-medium text-white" for="category">Category</label> 160 + 161 + <input 162 + v-model="petInput.category" 163 + class="rounded border border-[#575e64] bg-[#121314] p-1 text-sm text-white placeholder:text-[#575e64]" 164 + name="category" 165 + placeholder="Cats" 166 + required 167 + /> 168 + </div> 169 + 170 + <div class="flex gap-2"> 171 + <button 172 + class="rounded bg-[#3e63dd] p-2 text-sm font-medium text-white" 173 + type="submit" 174 + > 175 + Add Pet 176 + </button> 177 + 178 + <button 179 + class="rounded bg-[#3e63dd] p-2 text-sm font-medium text-white disabled:cursor-not-allowed" 180 + :disabled="!pet" 181 + type="button" 182 + @click="handleUpdatePet" 183 + > 184 + Update Pet 185 + </button> 186 + </div> 187 + </form> 188 + </div> 189 + </div> 190 + </template>
+7
examples/openapi-ts-ofetch/src/assets/main.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities; 4 + 5 + body { 6 + @apply bg-[#111113]; 7 + }
+28
examples/openapi-ts-ofetch/src/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { 4 + type ClientOptions as DefaultClientOptions, 5 + type Config, 6 + createClient, 7 + createConfig, 8 + } from './client'; 9 + import type { ClientOptions } from './types.gen'; 10 + 11 + /** 12 + * The `createClientConfig()` function will be called on client initialization 13 + * and the returned object will become the client's initial configuration. 14 + * 15 + * You may want to initialize your client this way instead of calling 16 + * `setConfig()`. This is useful for example if you're using Next.js 17 + * to ensure your client always has the correct values. 18 + */ 19 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = 20 + ( 21 + override?: Config<DefaultClientOptions & T>, 22 + ) => Config<Required<DefaultClientOptions> & T>; 23 + 24 + export const client = createClient( 25 + createConfig<ClientOptions>({ 26 + baseUrl: 'https://petstore3.swagger.io/api/v3', 27 + }), 28 + );
+236
examples/openapi-ts-ofetch/src/client/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; 4 + 5 + import { createSseClient } from '../core/serverSentEvents.gen'; 6 + import type { HttpMethod } from '../core/types.gen'; 7 + import { getValidRequestBody } from '../core/utils.gen'; 8 + import type { 9 + Client, 10 + Config, 11 + RequestOptions, 12 + ResolvedRequestOptions, 13 + } from './types.gen'; 14 + import { 15 + buildOfetchOptions, 16 + buildUrl, 17 + createConfig, 18 + createInterceptors, 19 + isRepeatableBody, 20 + mapParseAsToResponseType, 21 + mergeConfigs, 22 + mergeHeaders, 23 + parseError, 24 + parseSuccess, 25 + setAuthParams, 26 + wrapDataReturn, 27 + wrapErrorReturn, 28 + } from './utils.gen'; 29 + 30 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 31 + body?: any; 32 + headers: ReturnType<typeof mergeHeaders>; 33 + }; 34 + 35 + export const createClient = (config: Config = {}): Client => { 36 + let _config = mergeConfigs(createConfig(), config); 37 + 38 + const getConfig = (): Config => ({ ..._config }); 39 + 40 + const setConfig = (config: Config): Config => { 41 + _config = mergeConfigs(_config, config); 42 + return getConfig(); 43 + }; 44 + 45 + const interceptors = createInterceptors< 46 + Request, 47 + Response, 48 + unknown, 49 + ResolvedRequestOptions 50 + >(); 51 + 52 + const beforeRequest = async (options: RequestOptions) => { 53 + const opts = { 54 + ..._config, 55 + ...options, 56 + headers: mergeHeaders(_config.headers, options.headers), 57 + serializedBody: undefined, 58 + }; 59 + 60 + if (opts.security) { 61 + await setAuthParams({ 62 + ...opts, 63 + security: opts.security, 64 + }); 65 + } 66 + 67 + if (opts.requestValidator) { 68 + await opts.requestValidator(opts); 69 + } 70 + 71 + if (opts.body !== undefined && opts.bodySerializer) { 72 + opts.serializedBody = opts.bodySerializer(opts.body); 73 + } 74 + 75 + // remove Content-Type header if body is empty to avoid sending invalid requests 76 + if (opts.body === undefined || opts.serializedBody === '') { 77 + opts.headers.delete('Content-Type'); 78 + } 79 + 80 + // Precompute network body for retries and consistent handling 81 + const networkBody = getValidRequestBody(opts) as 82 + | RequestInit['body'] 83 + | null 84 + | undefined; 85 + 86 + const url = buildUrl(opts); 87 + 88 + return { networkBody, opts, url }; 89 + }; 90 + 91 + const request: Client['request'] = async (options) => { 92 + const { 93 + networkBody: initialNetworkBody, 94 + opts, 95 + url, 96 + } = await beforeRequest(options as any); 97 + // Compute response type mapping once 98 + const ofetchResponseType: OfetchResponseType | undefined = 99 + mapParseAsToResponseType(opts.parseAs, opts.responseType); 100 + 101 + const $ofetch = opts.ofetch ?? ofetch; 102 + 103 + // Always create Request pre-network (align with client-fetch) 104 + let networkBody = initialNetworkBody; 105 + const requestInit: ReqInit = { 106 + body: networkBody, 107 + headers: opts.headers as Headers, 108 + method: opts.method, 109 + redirect: 'follow', 110 + signal: opts.signal, 111 + }; 112 + let request = new Request(url, requestInit); 113 + 114 + for (const fn of interceptors.request.fns) { 115 + if (fn) { 116 + request = await fn(request, opts); 117 + } 118 + } 119 + 120 + // Reflect any interceptor changes into opts used for network and downstream 121 + opts.headers = request.headers; 122 + opts.method = request.method as Uppercase<HttpMethod>; 123 + // Attempt to reflect possible signal/body changes (safely) 124 + 125 + const reqBody = (request as any).body as unknown; 126 + let effectiveRetry = opts.retry; 127 + if (reqBody !== undefined && reqBody !== null) { 128 + if (isRepeatableBody(reqBody)) { 129 + networkBody = reqBody as BodyInit; 130 + } else { 131 + networkBody = reqBody as BodyInit; 132 + // Disable retries for non-repeatable bodies 133 + effectiveRetry = 0 as any; 134 + } 135 + } 136 + 137 + opts.signal = (request as any).signal as AbortSignal | undefined; 138 + const finalUrl = request.url; 139 + 140 + // Build ofetch options and perform the request 141 + const responseOptions = buildOfetchOptions( 142 + opts as ResolvedRequestOptions, 143 + networkBody ?? undefined, 144 + effectiveRetry as any, 145 + ); 146 + 147 + let response = await $ofetch.raw(finalUrl, responseOptions); 148 + 149 + for (const fn of interceptors.response.fns) { 150 + if (fn) { 151 + response = await fn(response, request, opts); 152 + } 153 + } 154 + 155 + const result = { request, response }; 156 + 157 + if (response.ok) { 158 + const data = await parseSuccess(response, opts, ofetchResponseType); 159 + return wrapDataReturn(data, result, opts.responseStyle); 160 + } 161 + 162 + let finalError = await parseError(response); 163 + 164 + for (const fn of interceptors.error.fns) { 165 + if (fn) { 166 + finalError = await fn(finalError, response, request, opts); 167 + } 168 + } 169 + 170 + // Ensure error is never undefined after interceptors 171 + finalError = (finalError as any) || ({} as string); 172 + 173 + if (opts.throwOnError) { 174 + throw finalError; 175 + } 176 + 177 + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; 178 + }; 179 + 180 + const makeMethodFn = 181 + (method: Uppercase<HttpMethod>) => (options: RequestOptions) => 182 + request({ ...options, method } as any); 183 + 184 + const makeSseFn = 185 + (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => { 186 + const { networkBody, opts, url } = await beforeRequest(options); 187 + const optsForSse: any = { ...opts }; 188 + delete optsForSse.body; 189 + return createSseClient({ 190 + ...optsForSse, 191 + fetch: opts.fetch, 192 + headers: opts.headers as Headers, 193 + method, 194 + onRequest: async (url, init) => { 195 + let request = new Request(url, init); 196 + for (const fn of interceptors.request.fns) { 197 + if (fn) { 198 + request = await fn(request, opts); 199 + } 200 + } 201 + return request; 202 + }, 203 + serializedBody: networkBody as BodyInit | null | undefined, 204 + signal: opts.signal, 205 + url, 206 + }); 207 + }; 208 + 209 + return { 210 + buildUrl, 211 + connect: makeMethodFn('CONNECT'), 212 + delete: makeMethodFn('DELETE'), 213 + get: makeMethodFn('GET'), 214 + getConfig, 215 + head: makeMethodFn('HEAD'), 216 + interceptors, 217 + options: makeMethodFn('OPTIONS'), 218 + patch: makeMethodFn('PATCH'), 219 + post: makeMethodFn('POST'), 220 + put: makeMethodFn('PUT'), 221 + request, 222 + setConfig, 223 + sse: { 224 + connect: makeSseFn('CONNECT'), 225 + delete: makeSseFn('DELETE'), 226 + get: makeSseFn('GET'), 227 + head: makeSseFn('HEAD'), 228 + options: makeSseFn('OPTIONS'), 229 + patch: makeSseFn('PATCH'), 230 + post: makeSseFn('POST'), 231 + put: makeSseFn('PUT'), 232 + trace: makeSseFn('TRACE'), 233 + }, 234 + trace: makeMethodFn('TRACE'), 235 + } as Client; 236 + };
+25
examples/openapi-ts-ofetch/src/client/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type { Auth } from '../core/auth.gen'; 4 + export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 5 + export { 6 + formDataBodySerializer, 7 + jsonBodySerializer, 8 + urlSearchParamsBodySerializer, 9 + } from '../core/bodySerializer.gen'; 10 + export { buildClientParams } from '../core/params.gen'; 11 + export { createClient } from './client.gen'; 12 + export type { 13 + Client, 14 + ClientOptions, 15 + Config, 16 + CreateClientConfig, 17 + Options, 18 + OptionsLegacyParser, 19 + RequestOptions, 20 + RequestResult, 21 + ResolvedRequestOptions, 22 + ResponseStyle, 23 + TDataShape, 24 + } from './types.gen'; 25 + export { createConfig, mergeHeaders } from './utils.gen';
+300
examples/openapi-ts-ofetch/src/client/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { 4 + FetchOptions as OfetchOptions, 5 + ResponseType as OfetchResponseType, 6 + } from 'ofetch'; 7 + import type { ofetch } from 'ofetch'; 8 + 9 + import type { Auth } from '../core/auth.gen'; 10 + import type { 11 + ServerSentEventsOptions, 12 + ServerSentEventsResult, 13 + } from '../core/serverSentEvents.gen'; 14 + import type { 15 + Client as CoreClient, 16 + Config as CoreConfig, 17 + } from '../core/types.gen'; 18 + import type { Middleware } from './utils.gen'; 19 + 20 + export type ResponseStyle = 'data' | 'fields'; 21 + 22 + export interface Config<T extends ClientOptions = ClientOptions> 23 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 24 + CoreConfig { 25 + agent?: OfetchOptions['agent']; 26 + /** 27 + * Base URL for all requests made by this client. 28 + */ 29 + baseUrl?: T['baseUrl']; 30 + /** Node-only proxy/agent options */ 31 + dispatcher?: OfetchOptions['dispatcher']; 32 + /** Optional fetch instance used for SSE streaming */ 33 + fetch?: typeof fetch; 34 + // No custom fetch option: provide custom instance via `ofetch` instead 35 + /** 36 + * Please don't use the Fetch client for Next.js applications. The `next` 37 + * options won't have any effect. 38 + * 39 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 40 + */ 41 + next?: never; 42 + /** 43 + * Custom ofetch instance created via `ofetch.create()`. If provided, it will 44 + * be used for requests instead of the default `ofetch` export. 45 + */ 46 + ofetch?: typeof ofetch; 47 + /** ofetch interceptors and runtime options */ 48 + onRequest?: OfetchOptions['onRequest']; 49 + onRequestError?: OfetchOptions['onRequestError']; 50 + onResponse?: OfetchOptions['onResponse']; 51 + onResponseError?: OfetchOptions['onResponseError']; 52 + /** 53 + * Return the response data parsed in a specified format. By default, `auto` 54 + * will infer the appropriate method from the `Content-Type` response header. 55 + * You can override this behavior with any of the {@link Body} methods. 56 + * Select `stream` if you don't want to parse response data at all. 57 + * 58 + * @default 'auto' 59 + */ 60 + parseAs?: 61 + | 'arrayBuffer' 62 + | 'auto' 63 + | 'blob' 64 + | 'formData' 65 + | 'json' 66 + | 'stream' 67 + | 'text'; 68 + /** Custom response parser (ofetch). */ 69 + parseResponse?: OfetchOptions['parseResponse']; 70 + /** 71 + * Should we return only data or multiple fields (data, error, response, etc.)? 72 + * 73 + * @default 'fields' 74 + */ 75 + responseStyle?: ResponseStyle; 76 + /** 77 + * ofetch responseType override. If provided, it will be passed directly to 78 + * ofetch and take precedence over `parseAs`. 79 + */ 80 + responseType?: OfetchResponseType; 81 + /** 82 + * Automatically retry failed requests. 83 + */ 84 + retry?: OfetchOptions['retry']; 85 + retryDelay?: OfetchOptions['retryDelay']; 86 + retryStatusCodes?: OfetchOptions['retryStatusCodes']; 87 + /** 88 + * Throw an error instead of returning it in the response? 89 + * 90 + * @default false 91 + */ 92 + throwOnError?: T['throwOnError']; 93 + /** 94 + * Abort the request after the given milliseconds. 95 + */ 96 + timeout?: number; 97 + } 98 + 99 + export interface RequestOptions< 100 + TData = unknown, 101 + TResponseStyle extends ResponseStyle = 'fields', 102 + ThrowOnError extends boolean = boolean, 103 + Url extends string = string, 104 + > extends Config<{ 105 + responseStyle: TResponseStyle; 106 + throwOnError: ThrowOnError; 107 + }>, 108 + Pick< 109 + ServerSentEventsOptions<TData>, 110 + | 'onSseError' 111 + | 'onSseEvent' 112 + | 'sseDefaultRetryDelay' 113 + | 'sseMaxRetryAttempts' 114 + | 'sseMaxRetryDelay' 115 + > { 116 + /** 117 + * Any body that you want to add to your request. 118 + * 119 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 120 + */ 121 + body?: unknown; 122 + path?: Record<string, unknown>; 123 + query?: Record<string, unknown>; 124 + /** 125 + * Security mechanism(s) to use for the request. 126 + */ 127 + security?: ReadonlyArray<Auth>; 128 + url: Url; 129 + } 130 + 131 + export interface ResolvedRequestOptions< 132 + TResponseStyle extends ResponseStyle = 'fields', 133 + ThrowOnError extends boolean = boolean, 134 + Url extends string = string, 135 + > extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> { 136 + serializedBody?: string; 137 + } 138 + 139 + export type RequestResult< 140 + TData = unknown, 141 + TError = unknown, 142 + ThrowOnError extends boolean = boolean, 143 + TResponseStyle extends ResponseStyle = 'fields', 144 + > = ThrowOnError extends true 145 + ? Promise< 146 + TResponseStyle extends 'data' 147 + ? TData extends Record<string, unknown> 148 + ? TData[keyof TData] 149 + : TData 150 + : { 151 + data: TData extends Record<string, unknown> 152 + ? TData[keyof TData] 153 + : TData; 154 + request: Request; 155 + response: Response; 156 + } 157 + > 158 + : Promise< 159 + TResponseStyle extends 'data' 160 + ? 161 + | (TData extends Record<string, unknown> 162 + ? TData[keyof TData] 163 + : TData) 164 + | undefined 165 + : ( 166 + | { 167 + data: TData extends Record<string, unknown> 168 + ? TData[keyof TData] 169 + : TData; 170 + error: undefined; 171 + } 172 + | { 173 + data: undefined; 174 + error: TError extends Record<string, unknown> 175 + ? TError[keyof TError] 176 + : TError; 177 + } 178 + ) & { 179 + request: Request; 180 + response: Response; 181 + } 182 + >; 183 + 184 + export interface ClientOptions { 185 + baseUrl?: string; 186 + responseStyle?: ResponseStyle; 187 + throwOnError?: boolean; 188 + } 189 + 190 + type MethodFn = < 191 + TData = unknown, 192 + TError = unknown, 193 + ThrowOnError extends boolean = false, 194 + TResponseStyle extends ResponseStyle = 'fields', 195 + >( 196 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>, 197 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 198 + 199 + type SseFn = < 200 + TData = unknown, 201 + TError = unknown, 202 + ThrowOnError extends boolean = false, 203 + TResponseStyle extends ResponseStyle = 'fields', 204 + >( 205 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>, 206 + ) => Promise<ServerSentEventsResult<TData, TError>>; 207 + 208 + type RequestFn = < 209 + TData = unknown, 210 + TError = unknown, 211 + ThrowOnError extends boolean = false, 212 + TResponseStyle extends ResponseStyle = 'fields', 213 + >( 214 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> & 215 + Pick< 216 + Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 217 + 'method' 218 + >, 219 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 220 + 221 + type BuildUrlFn = < 222 + TData extends { 223 + body?: unknown; 224 + path?: Record<string, unknown>; 225 + query?: Record<string, unknown>; 226 + url: string; 227 + }, 228 + >( 229 + options: Pick<TData, 'url'> & Options<TData>, 230 + ) => string; 231 + 232 + export type Client = CoreClient< 233 + RequestFn, 234 + Config, 235 + MethodFn, 236 + BuildUrlFn, 237 + SseFn 238 + > & { 239 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 240 + }; 241 + 242 + /** 243 + * The `createClientConfig()` function will be called on client initialization 244 + * and the returned object will become the client's initial configuration. 245 + * 246 + * You may want to initialize your client this way instead of calling 247 + * `setConfig()`. This is useful for example if you're using Next.js 248 + * to ensure your client always has the correct values. 249 + */ 250 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 251 + override?: Config<ClientOptions & T>, 252 + ) => Config<Required<ClientOptions> & T>; 253 + 254 + export interface TDataShape { 255 + body?: unknown; 256 + headers?: unknown; 257 + path?: unknown; 258 + query?: unknown; 259 + url: string; 260 + } 261 + 262 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 263 + 264 + export type Options< 265 + TData extends TDataShape = TDataShape, 266 + ThrowOnError extends boolean = boolean, 267 + TResponse = unknown, 268 + TResponseStyle extends ResponseStyle = 'fields', 269 + > = OmitKeys< 270 + RequestOptions<TResponse, TResponseStyle, ThrowOnError>, 271 + 'body' | 'path' | 'query' | 'url' 272 + > & 273 + Omit<TData, 'url'>; 274 + 275 + export type OptionsLegacyParser< 276 + TData = unknown, 277 + ThrowOnError extends boolean = boolean, 278 + TResponseStyle extends ResponseStyle = 'fields', 279 + > = TData extends { body?: any } 280 + ? TData extends { headers?: any } 281 + ? OmitKeys< 282 + RequestOptions<unknown, TResponseStyle, ThrowOnError>, 283 + 'body' | 'headers' | 'url' 284 + > & 285 + TData 286 + : OmitKeys< 287 + RequestOptions<unknown, TResponseStyle, ThrowOnError>, 288 + 'body' | 'url' 289 + > & 290 + TData & 291 + Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'headers'> 292 + : TData extends { headers?: any } 293 + ? OmitKeys< 294 + RequestOptions<unknown, TResponseStyle, ThrowOnError>, 295 + 'headers' | 'url' 296 + > & 297 + TData & 298 + Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'body'> 299 + : OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'url'> & 300 + TData;
+532
examples/openapi-ts-ofetch/src/client/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { 4 + FetchOptions as OfetchOptions, 5 + ResponseType as OfetchResponseType, 6 + } from 'ofetch'; 7 + 8 + import { getAuthToken } from '../core/auth.gen'; 9 + import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 10 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 11 + import { 12 + serializeArrayParam, 13 + serializeObjectParam, 14 + serializePrimitiveParam, 15 + } from '../core/pathSerializer.gen'; 16 + import { getUrl } from '../core/utils.gen'; 17 + import type { 18 + Client, 19 + ClientOptions, 20 + Config, 21 + RequestOptions, 22 + ResolvedRequestOptions, 23 + ResponseStyle, 24 + } from './types.gen'; 25 + 26 + export const createQuerySerializer = <T = unknown>({ 27 + allowReserved, 28 + array, 29 + object, 30 + }: QuerySerializerOptions = {}) => { 31 + const querySerializer = (queryParams: T) => { 32 + const search: string[] = []; 33 + if (queryParams && typeof queryParams === 'object') { 34 + for (const name in queryParams) { 35 + const value = queryParams[name]; 36 + 37 + if (value === undefined || value === null) { 38 + continue; 39 + } 40 + 41 + if (Array.isArray(value)) { 42 + const serializedArray = serializeArrayParam({ 43 + allowReserved, 44 + explode: true, 45 + name, 46 + style: 'form', 47 + value, 48 + ...array, 49 + }); 50 + if (serializedArray) search.push(serializedArray); 51 + } else if (typeof value === 'object') { 52 + const serializedObject = serializeObjectParam({ 53 + allowReserved, 54 + explode: true, 55 + name, 56 + style: 'deepObject', 57 + value: value as Record<string, unknown>, 58 + ...object, 59 + }); 60 + if (serializedObject) search.push(serializedObject); 61 + } else { 62 + const serializedPrimitive = serializePrimitiveParam({ 63 + allowReserved, 64 + name, 65 + value: value as string, 66 + }); 67 + if (serializedPrimitive) search.push(serializedPrimitive); 68 + } 69 + } 70 + } 71 + return search.join('&'); 72 + }; 73 + return querySerializer; 74 + }; 75 + 76 + /** 77 + * Infers parseAs value from provided Content-Type header. 78 + */ 79 + export const getParseAs = ( 80 + contentType: string | null, 81 + ): Exclude<Config['parseAs'], 'auto'> => { 82 + if (!contentType) { 83 + // If no Content-Type header is provided, the best we can do is return the raw response body, 84 + // which is effectively the same as the 'stream' option. 85 + return 'stream'; 86 + } 87 + 88 + const cleanContent = contentType.split(';')[0]?.trim(); 89 + 90 + if (!cleanContent) { 91 + return; 92 + } 93 + 94 + if ( 95 + cleanContent.startsWith('application/json') || 96 + cleanContent.endsWith('+json') 97 + ) { 98 + return 'json'; 99 + } 100 + 101 + if (cleanContent === 'multipart/form-data') { 102 + return 'formData'; 103 + } 104 + 105 + if ( 106 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 107 + cleanContent.startsWith(type), 108 + ) 109 + ) { 110 + return 'blob'; 111 + } 112 + 113 + if (cleanContent.startsWith('text/')) { 114 + return 'text'; 115 + } 116 + 117 + return; 118 + }; 119 + 120 + /** 121 + * Map our parseAs value to ofetch responseType when not explicitly provided. 122 + */ 123 + export const mapParseAsToResponseType = ( 124 + parseAs: Config['parseAs'] | undefined, 125 + explicit?: OfetchResponseType, 126 + ): OfetchResponseType | undefined => { 127 + if (explicit) return explicit; 128 + switch (parseAs) { 129 + case 'arrayBuffer': 130 + case 'blob': 131 + case 'json': 132 + case 'text': 133 + case 'stream': 134 + return parseAs; 135 + case 'formData': 136 + case 'auto': 137 + default: 138 + return undefined; // let ofetch auto-detect 139 + } 140 + }; 141 + 142 + const checkForExistence = ( 143 + options: Pick<RequestOptions, 'auth' | 'query'> & { 144 + headers: Headers; 145 + }, 146 + name?: string, 147 + ): boolean => { 148 + if (!name) { 149 + return false; 150 + } 151 + if ( 152 + options.headers.has(name) || 153 + options.query?.[name] || 154 + options.headers.get('Cookie')?.includes(`${name}=`) 155 + ) { 156 + return true; 157 + } 158 + return false; 159 + }; 160 + 161 + export const setAuthParams = async ({ 162 + security, 163 + ...options 164 + }: Pick<Required<RequestOptions>, 'security'> & 165 + Pick<RequestOptions, 'auth' | 'query'> & { 166 + headers: Headers; 167 + }) => { 168 + for (const auth of security) { 169 + if (checkForExistence(options, auth.name)) { 170 + continue; 171 + } 172 + 173 + const token = await getAuthToken(auth, options.auth); 174 + 175 + if (!token) { 176 + continue; 177 + } 178 + 179 + const name = auth.name ?? 'Authorization'; 180 + 181 + switch (auth.in) { 182 + case 'query': 183 + if (!options.query) { 184 + options.query = {}; 185 + } 186 + options.query[name] = token; 187 + break; 188 + case 'cookie': 189 + options.headers.append('Cookie', `${name}=${token}`); 190 + break; 191 + case 'header': 192 + default: 193 + options.headers.set(name, token); 194 + break; 195 + } 196 + } 197 + }; 198 + 199 + export const buildUrl: Client['buildUrl'] = (options) => 200 + getUrl({ 201 + baseUrl: options.baseUrl as string, 202 + path: options.path, 203 + query: options.query, 204 + querySerializer: 205 + typeof options.querySerializer === 'function' 206 + ? options.querySerializer 207 + : createQuerySerializer(options.querySerializer), 208 + url: options.url, 209 + }); 210 + 211 + export const mergeConfigs = (a: Config, b: Config): Config => { 212 + const config = { ...a, ...b }; 213 + if (config.baseUrl?.endsWith('/')) { 214 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 215 + } 216 + config.headers = mergeHeaders(a.headers, b.headers); 217 + return config; 218 + }; 219 + 220 + const headersEntries = (headers: Headers): Array<[string, string]> => { 221 + const entries: Array<[string, string]> = []; 222 + headers.forEach((value, key) => { 223 + entries.push([key, value]); 224 + }); 225 + return entries; 226 + }; 227 + 228 + export const mergeHeaders = ( 229 + ...headers: Array<Required<Config>['headers'] | undefined> 230 + ): Headers => { 231 + const mergedHeaders = new Headers(); 232 + for (const header of headers) { 233 + if (!header) { 234 + continue; 235 + } 236 + 237 + const iterator = 238 + header instanceof Headers 239 + ? headersEntries(header) 240 + : Object.entries(header); 241 + 242 + for (const [key, value] of iterator) { 243 + if (value === null) { 244 + mergedHeaders.delete(key); 245 + } else if (Array.isArray(value)) { 246 + for (const v of value) { 247 + mergedHeaders.append(key, v as string); 248 + } 249 + } else if (value !== undefined) { 250 + // assume object headers are meant to be JSON stringified, i.e. their 251 + // content value in OpenAPI specification is 'application/json' 252 + mergedHeaders.set( 253 + key, 254 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 255 + ); 256 + } 257 + } 258 + } 259 + return mergedHeaders; 260 + }; 261 + 262 + /** 263 + * Heuristic to detect whether a request body can be safely retried. 264 + */ 265 + export const isRepeatableBody = (body: unknown): boolean => { 266 + if (body == null) return true; // undefined/null treated as no-body 267 + if (typeof body === 'string') return true; 268 + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) 269 + return true; 270 + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) 271 + return true; 272 + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) 273 + return true; 274 + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; 275 + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; 276 + // Streams are not repeatable 277 + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) 278 + return false; 279 + // Default: assume non-repeatable for unknown structured bodies 280 + return false; 281 + }; 282 + 283 + /** 284 + * Small helper to unify data vs fields return style. 285 + */ 286 + export const wrapDataReturn = <T>( 287 + data: T, 288 + result: { request: Request; response: Response }, 289 + responseStyle: ResponseStyle | undefined, 290 + ): 291 + | T 292 + | ((T extends Record<string, unknown> ? { data: T } : { data: T }) & 293 + typeof result) => 294 + (responseStyle ?? 'fields') === 'data' 295 + ? (data as any) 296 + : ({ data, ...result } as any); 297 + 298 + /** 299 + * Small helper to unify error vs fields return style. 300 + */ 301 + export const wrapErrorReturn = <E>( 302 + error: E, 303 + result: { request: Request; response: Response }, 304 + responseStyle: ResponseStyle | undefined, 305 + ): 306 + | undefined 307 + | ((E extends Record<string, unknown> ? { error: E } : { error: E }) & 308 + typeof result) => 309 + (responseStyle ?? 'fields') === 'data' 310 + ? undefined 311 + : ({ error, ...result } as any); 312 + 313 + /** 314 + * Build options for $ofetch.raw from our resolved opts and body. 315 + */ 316 + export const buildOfetchOptions = ( 317 + opts: ResolvedRequestOptions, 318 + body: BodyInit | null | undefined, 319 + retryOverride?: OfetchOptions['retry'], 320 + ): OfetchOptions => { 321 + const responseType = mapParseAsToResponseType( 322 + opts.parseAs, 323 + opts.responseType, 324 + ); 325 + return { 326 + agent: opts.agent as OfetchOptions['agent'], 327 + body: body as any, 328 + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], 329 + headers: opts.headers as Headers, 330 + method: opts.method, 331 + onRequest: opts.onRequest as OfetchOptions['onRequest'], 332 + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], 333 + onResponse: opts.onResponse as OfetchOptions['onResponse'], 334 + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], 335 + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], 336 + query: undefined, // URL already includes query 337 + responseType, 338 + retry: (retryOverride ?? 339 + (opts.retry as OfetchOptions['retry'])) as OfetchOptions['retry'], 340 + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], 341 + retryStatusCodes: 342 + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], 343 + signal: opts.signal, 344 + timeout: opts.timeout as number | undefined, 345 + } as OfetchOptions; 346 + }; 347 + 348 + /** 349 + * Parse a successful response, handling empty bodies and stream cases. 350 + */ 351 + export const parseSuccess = async ( 352 + response: Response, 353 + opts: ResolvedRequestOptions, 354 + ofetchResponseType?: OfetchResponseType, 355 + ): Promise<unknown> => { 356 + // Stream requested: return stream body 357 + if (ofetchResponseType === 'stream') { 358 + return response.body; 359 + } 360 + 361 + const inferredParseAs = 362 + (opts.parseAs === 'auto' 363 + ? getParseAs(response.headers.get('Content-Type')) 364 + : opts.parseAs) ?? 'json'; 365 + 366 + // Handle empty responses 367 + if ( 368 + response.status === 204 || 369 + response.headers.get('Content-Length') === '0' 370 + ) { 371 + switch (inferredParseAs) { 372 + case 'arrayBuffer': 373 + case 'blob': 374 + case 'text': 375 + return await (response as any)[inferredParseAs](); 376 + case 'formData': 377 + return new FormData(); 378 + case 'stream': 379 + return response.body; 380 + default: 381 + return {}; 382 + } 383 + } 384 + 385 + // Prefer ofetch-populated data 386 + let data: unknown = (response as any)._data; 387 + if (typeof data === 'undefined') { 388 + switch (inferredParseAs) { 389 + case 'arrayBuffer': 390 + case 'blob': 391 + case 'formData': 392 + case 'json': 393 + case 'text': 394 + data = await (response as any)[inferredParseAs](); 395 + break; 396 + case 'stream': 397 + return response.body; 398 + } 399 + } 400 + 401 + if (inferredParseAs === 'json') { 402 + if (opts.responseValidator) { 403 + await opts.responseValidator(data); 404 + } 405 + if (opts.responseTransformer) { 406 + data = await opts.responseTransformer(data); 407 + } 408 + } 409 + 410 + return data; 411 + }; 412 + 413 + /** 414 + * Parse an error response payload. 415 + */ 416 + export const parseError = async (response: Response): Promise<unknown> => { 417 + let error: unknown = (response as any)._data; 418 + if (typeof error === 'undefined') { 419 + const textError = await response.text(); 420 + try { 421 + error = JSON.parse(textError); 422 + } catch { 423 + error = textError; 424 + } 425 + } 426 + return error ?? ({} as string); 427 + }; 428 + 429 + type ErrInterceptor<Err, Res, Req, Options> = ( 430 + error: Err, 431 + response: Res, 432 + request: Req, 433 + options: Options, 434 + ) => Err | Promise<Err>; 435 + 436 + type ReqInterceptor<Req, Options> = ( 437 + request: Req, 438 + options: Options, 439 + ) => Req | Promise<Req>; 440 + 441 + type ResInterceptor<Res, Req, Options> = ( 442 + response: Res, 443 + request: Req, 444 + options: Options, 445 + ) => Res | Promise<Res>; 446 + 447 + class Interceptors<Interceptor> { 448 + fns: Array<Interceptor | null> = []; 449 + 450 + clear(): void { 451 + this.fns = []; 452 + } 453 + 454 + eject(id: number | Interceptor): void { 455 + const index = this.getInterceptorIndex(id); 456 + if (this.fns[index]) { 457 + this.fns[index] = null; 458 + } 459 + } 460 + 461 + exists(id: number | Interceptor): boolean { 462 + const index = this.getInterceptorIndex(id); 463 + return Boolean(this.fns[index]); 464 + } 465 + 466 + getInterceptorIndex(id: number | Interceptor): number { 467 + if (typeof id === 'number') { 468 + return this.fns[id] ? id : -1; 469 + } 470 + return this.fns.indexOf(id); 471 + } 472 + 473 + update( 474 + id: number | Interceptor, 475 + fn: Interceptor, 476 + ): number | Interceptor | false { 477 + const index = this.getInterceptorIndex(id); 478 + if (this.fns[index]) { 479 + this.fns[index] = fn; 480 + return id; 481 + } 482 + return false; 483 + } 484 + 485 + use(fn: Interceptor): number { 486 + this.fns.push(fn); 487 + return this.fns.length - 1; 488 + } 489 + } 490 + 491 + export interface Middleware<Req, Res, Err, Options> { 492 + error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>; 493 + request: Interceptors<ReqInterceptor<Req, Options>>; 494 + response: Interceptors<ResInterceptor<Res, Req, Options>>; 495 + } 496 + 497 + export const createInterceptors = <Req, Res, Err, Options>(): Middleware< 498 + Req, 499 + Res, 500 + Err, 501 + Options 502 + > => ({ 503 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 504 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 505 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 506 + }); 507 + 508 + const defaultQuerySerializer = createQuerySerializer({ 509 + allowReserved: false, 510 + array: { 511 + explode: true, 512 + style: 'form', 513 + }, 514 + object: { 515 + explode: true, 516 + style: 'deepObject', 517 + }, 518 + }); 519 + 520 + const defaultHeaders = { 521 + 'Content-Type': 'application/json', 522 + }; 523 + 524 + export const createConfig = <T extends ClientOptions = ClientOptions>( 525 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 526 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 527 + ...jsonBodySerializer, 528 + headers: defaultHeaders, 529 + parseAs: 'auto', 530 + querySerializer: defaultQuerySerializer, 531 + ...override, 532 + });
+42
examples/openapi-ts-ofetch/src/client/core/auth.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type AuthToken = string | undefined; 4 + 5 + export interface Auth { 6 + /** 7 + * Which part of the request do we use to send the auth? 8 + * 9 + * @default 'header' 10 + */ 11 + in?: 'header' | 'query' | 'cookie'; 12 + /** 13 + * Header or query parameter name. 14 + * 15 + * @default 'Authorization' 16 + */ 17 + name?: string; 18 + scheme?: 'basic' | 'bearer'; 19 + type: 'apiKey' | 'http'; 20 + } 21 + 22 + export const getAuthToken = async ( 23 + auth: Auth, 24 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 25 + ): Promise<string | undefined> => { 26 + const token = 27 + typeof callback === 'function' ? await callback(auth) : callback; 28 + 29 + if (!token) { 30 + return; 31 + } 32 + 33 + if (auth.scheme === 'bearer') { 34 + return `Bearer ${token}`; 35 + } 36 + 37 + if (auth.scheme === 'basic') { 38 + return `Basic ${btoa(token)}`; 39 + } 40 + 41 + return token; 42 + };
+92
examples/openapi-ts-ofetch/src/client/core/bodySerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { 4 + ArrayStyle, 5 + ObjectStyle, 6 + SerializerOptions, 7 + } from './pathSerializer.gen'; 8 + 9 + export type QuerySerializer = (query: Record<string, unknown>) => string; 10 + 11 + export type BodySerializer = (body: any) => any; 12 + 13 + export interface QuerySerializerOptions { 14 + allowReserved?: boolean; 15 + array?: SerializerOptions<ArrayStyle>; 16 + object?: SerializerOptions<ObjectStyle>; 17 + } 18 + 19 + const serializeFormDataPair = ( 20 + data: FormData, 21 + key: string, 22 + value: unknown, 23 + ): void => { 24 + if (typeof value === 'string' || value instanceof Blob) { 25 + data.append(key, value); 26 + } else if (value instanceof Date) { 27 + data.append(key, value.toISOString()); 28 + } else { 29 + data.append(key, JSON.stringify(value)); 30 + } 31 + }; 32 + 33 + const serializeUrlSearchParamsPair = ( 34 + data: URLSearchParams, 35 + key: string, 36 + value: unknown, 37 + ): void => { 38 + if (typeof value === 'string') { 39 + data.append(key, value); 40 + } else { 41 + data.append(key, JSON.stringify(value)); 42 + } 43 + }; 44 + 45 + export const formDataBodySerializer = { 46 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 47 + body: T, 48 + ): FormData => { 49 + const data = new FormData(); 50 + 51 + Object.entries(body).forEach(([key, value]) => { 52 + if (value === undefined || value === null) { 53 + return; 54 + } 55 + if (Array.isArray(value)) { 56 + value.forEach((v) => serializeFormDataPair(data, key, v)); 57 + } else { 58 + serializeFormDataPair(data, key, value); 59 + } 60 + }); 61 + 62 + return data; 63 + }, 64 + }; 65 + 66 + export const jsonBodySerializer = { 67 + bodySerializer: <T>(body: T): string => 68 + JSON.stringify(body, (_key, value) => 69 + typeof value === 'bigint' ? value.toString() : value, 70 + ), 71 + }; 72 + 73 + export const urlSearchParamsBodySerializer = { 74 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 75 + body: T, 76 + ): string => { 77 + const data = new URLSearchParams(); 78 + 79 + Object.entries(body).forEach(([key, value]) => { 80 + if (value === undefined || value === null) { 81 + return; 82 + } 83 + if (Array.isArray(value)) { 84 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 85 + } else { 86 + serializeUrlSearchParamsPair(data, key, value); 87 + } 88 + }); 89 + 90 + return data.toString(); 91 + }, 92 + };
+153
examples/openapi-ts-ofetch/src/client/core/params.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + type Slot = 'body' | 'headers' | 'path' | 'query'; 4 + 5 + export type Field = 6 + | { 7 + in: Exclude<Slot, 'body'>; 8 + /** 9 + * Field name. This is the name we want the user to see and use. 10 + */ 11 + key: string; 12 + /** 13 + * Field mapped name. This is the name we want to use in the request. 14 + * If omitted, we use the same value as `key`. 15 + */ 16 + map?: string; 17 + } 18 + | { 19 + in: Extract<Slot, 'body'>; 20 + /** 21 + * Key isn't required for bodies. 22 + */ 23 + key?: string; 24 + map?: string; 25 + }; 26 + 27 + export interface Fields { 28 + allowExtra?: Partial<Record<Slot, boolean>>; 29 + args?: ReadonlyArray<Field>; 30 + } 31 + 32 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 33 + 34 + const extraPrefixesMap: Record<string, Slot> = { 35 + $body_: 'body', 36 + $headers_: 'headers', 37 + $path_: 'path', 38 + $query_: 'query', 39 + }; 40 + const extraPrefixes = Object.entries(extraPrefixesMap); 41 + 42 + type KeyMap = Map< 43 + string, 44 + { 45 + in: Slot; 46 + map?: string; 47 + } 48 + >; 49 + 50 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 51 + if (!map) { 52 + map = new Map(); 53 + } 54 + 55 + for (const config of fields) { 56 + if ('in' in config) { 57 + if (config.key) { 58 + map.set(config.key, { 59 + in: config.in, 60 + map: config.map, 61 + }); 62 + } 63 + } else if (config.args) { 64 + buildKeyMap(config.args, map); 65 + } 66 + } 67 + 68 + return map; 69 + }; 70 + 71 + interface Params { 72 + body: unknown; 73 + headers: Record<string, unknown>; 74 + path: Record<string, unknown>; 75 + query: Record<string, unknown>; 76 + } 77 + 78 + const stripEmptySlots = (params: Params) => { 79 + for (const [slot, value] of Object.entries(params)) { 80 + if (value && typeof value === 'object' && !Object.keys(value).length) { 81 + delete params[slot as Slot]; 82 + } 83 + } 84 + }; 85 + 86 + export const buildClientParams = ( 87 + args: ReadonlyArray<unknown>, 88 + fields: FieldsConfig, 89 + ) => { 90 + const params: Params = { 91 + body: {}, 92 + headers: {}, 93 + path: {}, 94 + query: {}, 95 + }; 96 + 97 + const map = buildKeyMap(fields); 98 + 99 + let config: FieldsConfig[number] | undefined; 100 + 101 + for (const [index, arg] of args.entries()) { 102 + if (fields[index]) { 103 + config = fields[index]; 104 + } 105 + 106 + if (!config) { 107 + continue; 108 + } 109 + 110 + if ('in' in config) { 111 + if (config.key) { 112 + const field = map.get(config.key)!; 113 + const name = field.map || config.key; 114 + (params[field.in] as Record<string, unknown>)[name] = arg; 115 + } else { 116 + params.body = arg; 117 + } 118 + } else { 119 + for (const [key, value] of Object.entries(arg ?? {})) { 120 + const field = map.get(key); 121 + 122 + if (field) { 123 + const name = field.map || key; 124 + (params[field.in] as Record<string, unknown>)[name] = value; 125 + } else { 126 + const extra = extraPrefixes.find(([prefix]) => 127 + key.startsWith(prefix), 128 + ); 129 + 130 + if (extra) { 131 + const [prefix, slot] = extra; 132 + (params[slot] as Record<string, unknown>)[ 133 + key.slice(prefix.length) 134 + ] = value; 135 + } else { 136 + for (const [slot, allowed] of Object.entries( 137 + config.allowExtra ?? {}, 138 + )) { 139 + if (allowed) { 140 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 141 + break; 142 + } 143 + } 144 + } 145 + } 146 + } 147 + } 148 + } 149 + 150 + stripEmptySlots(params); 151 + 152 + return params; 153 + };
+181
examples/openapi-ts-ofetch/src/client/core/pathSerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + interface SerializeOptions<T> 4 + extends SerializePrimitiveOptions, 5 + SerializerOptions<T> {} 6 + 7 + interface SerializePrimitiveOptions { 8 + allowReserved?: boolean; 9 + name: string; 10 + } 11 + 12 + export interface SerializerOptions<T> { 13 + /** 14 + * @default true 15 + */ 16 + explode: boolean; 17 + style: T; 18 + } 19 + 20 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 21 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 22 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 23 + export type ObjectStyle = 'form' | 'deepObject'; 24 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 25 + 26 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 27 + value: string; 28 + } 29 + 30 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 31 + switch (style) { 32 + case 'label': 33 + return '.'; 34 + case 'matrix': 35 + return ';'; 36 + case 'simple': 37 + return ','; 38 + default: 39 + return '&'; 40 + } 41 + }; 42 + 43 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 44 + switch (style) { 45 + case 'form': 46 + return ','; 47 + case 'pipeDelimited': 48 + return '|'; 49 + case 'spaceDelimited': 50 + return '%20'; 51 + default: 52 + return ','; 53 + } 54 + }; 55 + 56 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 57 + switch (style) { 58 + case 'label': 59 + return '.'; 60 + case 'matrix': 61 + return ';'; 62 + case 'simple': 63 + return ','; 64 + default: 65 + return '&'; 66 + } 67 + }; 68 + 69 + export const serializeArrayParam = ({ 70 + allowReserved, 71 + explode, 72 + name, 73 + style, 74 + value, 75 + }: SerializeOptions<ArraySeparatorStyle> & { 76 + value: unknown[]; 77 + }) => { 78 + if (!explode) { 79 + const joinedValues = ( 80 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 81 + ).join(separatorArrayNoExplode(style)); 82 + switch (style) { 83 + case 'label': 84 + return `.${joinedValues}`; 85 + case 'matrix': 86 + return `;${name}=${joinedValues}`; 87 + case 'simple': 88 + return joinedValues; 89 + default: 90 + return `${name}=${joinedValues}`; 91 + } 92 + } 93 + 94 + const separator = separatorArrayExplode(style); 95 + const joinedValues = value 96 + .map((v) => { 97 + if (style === 'label' || style === 'simple') { 98 + return allowReserved ? v : encodeURIComponent(v as string); 99 + } 100 + 101 + return serializePrimitiveParam({ 102 + allowReserved, 103 + name, 104 + value: v as string, 105 + }); 106 + }) 107 + .join(separator); 108 + return style === 'label' || style === 'matrix' 109 + ? separator + joinedValues 110 + : joinedValues; 111 + }; 112 + 113 + export const serializePrimitiveParam = ({ 114 + allowReserved, 115 + name, 116 + value, 117 + }: SerializePrimitiveParam) => { 118 + if (value === undefined || value === null) { 119 + return ''; 120 + } 121 + 122 + if (typeof value === 'object') { 123 + throw new Error( 124 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 125 + ); 126 + } 127 + 128 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 129 + }; 130 + 131 + export const serializeObjectParam = ({ 132 + allowReserved, 133 + explode, 134 + name, 135 + style, 136 + value, 137 + valueOnly, 138 + }: SerializeOptions<ObjectSeparatorStyle> & { 139 + value: Record<string, unknown> | Date; 140 + valueOnly?: boolean; 141 + }) => { 142 + if (value instanceof Date) { 143 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 144 + } 145 + 146 + if (style !== 'deepObject' && !explode) { 147 + let values: string[] = []; 148 + Object.entries(value).forEach(([key, v]) => { 149 + values = [ 150 + ...values, 151 + key, 152 + allowReserved ? (v as string) : encodeURIComponent(v as string), 153 + ]; 154 + }); 155 + const joinedValues = values.join(','); 156 + switch (style) { 157 + case 'form': 158 + return `${name}=${joinedValues}`; 159 + case 'label': 160 + return `.${joinedValues}`; 161 + case 'matrix': 162 + return `;${name}=${joinedValues}`; 163 + default: 164 + return joinedValues; 165 + } 166 + } 167 + 168 + const separator = separatorObjectExplode(style); 169 + const joinedValues = Object.entries(value) 170 + .map(([key, v]) => 171 + serializePrimitiveParam({ 172 + allowReserved, 173 + name: style === 'deepObject' ? `${name}[${key}]` : key, 174 + value: v as string, 175 + }), 176 + ) 177 + .join(separator); 178 + return style === 'label' || style === 'matrix' 179 + ? separator + joinedValues 180 + : joinedValues; 181 + };
+264
examples/openapi-ts-ofetch/src/client/core/serverSentEvents.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Config } from './types.gen'; 4 + 5 + export type ServerSentEventsOptions<TData = unknown> = Omit< 6 + RequestInit, 7 + 'method' 8 + > & 9 + Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & { 10 + /** 11 + * Fetch API implementation. You can use this option to provide a custom 12 + * fetch instance. 13 + * 14 + * @default globalThis.fetch 15 + */ 16 + fetch?: typeof fetch; 17 + /** 18 + * Implementing clients can call request interceptors inside this hook. 19 + */ 20 + onRequest?: (url: string, init: RequestInit) => Promise<Request>; 21 + /** 22 + * Callback invoked when a network or parsing error occurs during streaming. 23 + * 24 + * This option applies only if the endpoint returns a stream of events. 25 + * 26 + * @param error The error that occurred. 27 + */ 28 + onSseError?: (error: unknown) => void; 29 + /** 30 + * Callback invoked when an event is streamed from the server. 31 + * 32 + * This option applies only if the endpoint returns a stream of events. 33 + * 34 + * @param event Event streamed from the server. 35 + * @returns Nothing (void). 36 + */ 37 + onSseEvent?: (event: StreamEvent<TData>) => void; 38 + serializedBody?: RequestInit['body']; 39 + /** 40 + * Default retry delay in milliseconds. 41 + * 42 + * This option applies only if the endpoint returns a stream of events. 43 + * 44 + * @default 3000 45 + */ 46 + sseDefaultRetryDelay?: number; 47 + /** 48 + * Maximum number of retry attempts before giving up. 49 + */ 50 + sseMaxRetryAttempts?: number; 51 + /** 52 + * Maximum retry delay in milliseconds. 53 + * 54 + * Applies only when exponential backoff is used. 55 + * 56 + * This option applies only if the endpoint returns a stream of events. 57 + * 58 + * @default 30000 59 + */ 60 + sseMaxRetryDelay?: number; 61 + /** 62 + * Optional sleep function for retry backoff. 63 + * 64 + * Defaults to using `setTimeout`. 65 + */ 66 + sseSleepFn?: (ms: number) => Promise<void>; 67 + url: string; 68 + }; 69 + 70 + export interface StreamEvent<TData = unknown> { 71 + data: TData; 72 + event?: string; 73 + id?: string; 74 + retry?: number; 75 + } 76 + 77 + export type ServerSentEventsResult< 78 + TData = unknown, 79 + TReturn = void, 80 + TNext = unknown, 81 + > = { 82 + stream: AsyncGenerator< 83 + TData extends Record<string, unknown> ? TData[keyof TData] : TData, 84 + TReturn, 85 + TNext 86 + >; 87 + }; 88 + 89 + export const createSseClient = <TData = unknown>({ 90 + onRequest, 91 + onSseError, 92 + onSseEvent, 93 + responseTransformer, 94 + responseValidator, 95 + sseDefaultRetryDelay, 96 + sseMaxRetryAttempts, 97 + sseMaxRetryDelay, 98 + sseSleepFn, 99 + url, 100 + ...options 101 + }: ServerSentEventsOptions): ServerSentEventsResult<TData> => { 102 + let lastEventId: string | undefined; 103 + 104 + const sleep = 105 + sseSleepFn ?? 106 + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); 107 + 108 + const createStream = async function* () { 109 + let retryDelay: number = sseDefaultRetryDelay ?? 3000; 110 + let attempt = 0; 111 + const signal = options.signal ?? new AbortController().signal; 112 + 113 + while (true) { 114 + if (signal.aborted) break; 115 + 116 + attempt++; 117 + 118 + const headers = 119 + options.headers instanceof Headers 120 + ? options.headers 121 + : new Headers(options.headers as Record<string, string> | undefined); 122 + 123 + if (lastEventId !== undefined) { 124 + headers.set('Last-Event-ID', lastEventId); 125 + } 126 + 127 + try { 128 + const requestInit: RequestInit = { 129 + redirect: 'follow', 130 + ...options, 131 + body: options.serializedBody, 132 + headers, 133 + signal, 134 + }; 135 + let request = new Request(url, requestInit); 136 + if (onRequest) { 137 + request = await onRequest(url, requestInit); 138 + } 139 + // fetch must be assigned here, otherwise it would throw the error: 140 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 141 + const _fetch = options.fetch ?? globalThis.fetch; 142 + const response = await _fetch(request); 143 + 144 + if (!response.ok) 145 + throw new Error( 146 + `SSE failed: ${response.status} ${response.statusText}`, 147 + ); 148 + 149 + if (!response.body) throw new Error('No body in SSE response'); 150 + 151 + const reader = response.body 152 + .pipeThrough(new TextDecoderStream()) 153 + .getReader(); 154 + 155 + let buffer = ''; 156 + 157 + const abortHandler = () => { 158 + try { 159 + reader.cancel(); 160 + } catch { 161 + // noop 162 + } 163 + }; 164 + 165 + signal.addEventListener('abort', abortHandler); 166 + 167 + try { 168 + while (true) { 169 + const { done, value } = await reader.read(); 170 + if (done) break; 171 + buffer += value; 172 + 173 + const chunks = buffer.split('\n\n'); 174 + buffer = chunks.pop() ?? ''; 175 + 176 + for (const chunk of chunks) { 177 + const lines = chunk.split('\n'); 178 + const dataLines: Array<string> = []; 179 + let eventName: string | undefined; 180 + 181 + for (const line of lines) { 182 + if (line.startsWith('data:')) { 183 + dataLines.push(line.replace(/^data:\s*/, '')); 184 + } else if (line.startsWith('event:')) { 185 + eventName = line.replace(/^event:\s*/, ''); 186 + } else if (line.startsWith('id:')) { 187 + lastEventId = line.replace(/^id:\s*/, ''); 188 + } else if (line.startsWith('retry:')) { 189 + const parsed = Number.parseInt( 190 + line.replace(/^retry:\s*/, ''), 191 + 10, 192 + ); 193 + if (!Number.isNaN(parsed)) { 194 + retryDelay = parsed; 195 + } 196 + } 197 + } 198 + 199 + let data: unknown; 200 + let parsedJson = false; 201 + 202 + if (dataLines.length) { 203 + const rawData = dataLines.join('\n'); 204 + try { 205 + data = JSON.parse(rawData); 206 + parsedJson = true; 207 + } catch { 208 + data = rawData; 209 + } 210 + } 211 + 212 + if (parsedJson) { 213 + if (responseValidator) { 214 + await responseValidator(data); 215 + } 216 + 217 + if (responseTransformer) { 218 + data = await responseTransformer(data); 219 + } 220 + } 221 + 222 + onSseEvent?.({ 223 + data, 224 + event: eventName, 225 + id: lastEventId, 226 + retry: retryDelay, 227 + }); 228 + 229 + if (dataLines.length) { 230 + yield data as any; 231 + } 232 + } 233 + } 234 + } finally { 235 + signal.removeEventListener('abort', abortHandler); 236 + reader.releaseLock(); 237 + } 238 + 239 + break; // exit loop on normal completion 240 + } catch (error) { 241 + // connection failed or aborted; retry after delay 242 + onSseError?.(error); 243 + 244 + if ( 245 + sseMaxRetryAttempts !== undefined && 246 + attempt >= sseMaxRetryAttempts 247 + ) { 248 + break; // stop after firing error 249 + } 250 + 251 + // exponential backoff: double retry each attempt, cap at 30s 252 + const backoff = Math.min( 253 + retryDelay * 2 ** (attempt - 1), 254 + sseMaxRetryDelay ?? 30000, 255 + ); 256 + await sleep(backoff); 257 + } 258 + } 259 + }; 260 + 261 + const stream = createStream(); 262 + 263 + return { stream }; 264 + };
+118
examples/openapi-ts-ofetch/src/client/core/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth, AuthToken } from './auth.gen'; 4 + import type { 5 + BodySerializer, 6 + QuerySerializer, 7 + QuerySerializerOptions, 8 + } from './bodySerializer.gen'; 9 + 10 + export type HttpMethod = 11 + | 'connect' 12 + | 'delete' 13 + | 'get' 14 + | 'head' 15 + | 'options' 16 + | 'patch' 17 + | 'post' 18 + | 'put' 19 + | 'trace'; 20 + 21 + export type Client< 22 + RequestFn = never, 23 + Config = unknown, 24 + MethodFn = never, 25 + BuildUrlFn = never, 26 + SseFn = never, 27 + > = { 28 + /** 29 + * Returns the final request URL. 30 + */ 31 + buildUrl: BuildUrlFn; 32 + getConfig: () => Config; 33 + request: RequestFn; 34 + setConfig: (config: Config) => Config; 35 + } & { 36 + [K in HttpMethod]: MethodFn; 37 + } & ([SseFn] extends [never] 38 + ? { sse?: never } 39 + : { sse: { [K in HttpMethod]: SseFn } }); 40 + 41 + export interface Config { 42 + /** 43 + * Auth token or a function returning auth token. The resolved value will be 44 + * added to the request payload as defined by its `security` array. 45 + */ 46 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 47 + /** 48 + * A function for serializing request body parameter. By default, 49 + * {@link JSON.stringify()} will be used. 50 + */ 51 + bodySerializer?: BodySerializer | null; 52 + /** 53 + * An object containing any HTTP headers that you want to pre-populate your 54 + * `Headers` object with. 55 + * 56 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 57 + */ 58 + headers?: 59 + | RequestInit['headers'] 60 + | Record< 61 + string, 62 + | string 63 + | number 64 + | boolean 65 + | (string | number | boolean)[] 66 + | null 67 + | undefined 68 + | unknown 69 + >; 70 + /** 71 + * The request method. 72 + * 73 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 74 + */ 75 + method?: Uppercase<HttpMethod>; 76 + /** 77 + * A function for serializing request query parameters. By default, arrays 78 + * will be exploded in form style, objects will be exploded in deepObject 79 + * style, and reserved characters are percent-encoded. 80 + * 81 + * This method will have no effect if the native `paramsSerializer()` Axios 82 + * API function is used. 83 + * 84 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 85 + */ 86 + querySerializer?: QuerySerializer | QuerySerializerOptions; 87 + /** 88 + * A function validating request data. This is useful if you want to ensure 89 + * the request conforms to the desired shape, so it can be safely sent to 90 + * the server. 91 + */ 92 + requestValidator?: (data: unknown) => Promise<unknown>; 93 + /** 94 + * A function transforming response data before it's returned. This is useful 95 + * for post-processing data, e.g. converting ISO strings into Date objects. 96 + */ 97 + responseTransformer?: (data: unknown) => Promise<unknown>; 98 + /** 99 + * A function validating response data. This is useful if you want to ensure 100 + * the response conforms to the desired shape, so it can be safely passed to 101 + * the transformers and returned to the user. 102 + */ 103 + responseValidator?: (data: unknown) => Promise<unknown>; 104 + } 105 + 106 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 107 + ? true 108 + : [T] extends [never | undefined] 109 + ? [undefined] extends [T] 110 + ? false 111 + : true 112 + : false; 113 + 114 + export type OmitNever<T extends Record<string, unknown>> = { 115 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 116 + ? never 117 + : K]: T[K]; 118 + };
+143
examples/openapi-ts-ofetch/src/client/core/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; 4 + import { 5 + type ArraySeparatorStyle, 6 + serializeArrayParam, 7 + serializeObjectParam, 8 + serializePrimitiveParam, 9 + } from './pathSerializer.gen'; 10 + 11 + export interface PathSerializer { 12 + path: Record<string, unknown>; 13 + url: string; 14 + } 15 + 16 + export const PATH_PARAM_RE = /\{[^{}]+\}/g; 17 + 18 + export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 19 + let url = _url; 20 + const matches = _url.match(PATH_PARAM_RE); 21 + if (matches) { 22 + for (const match of matches) { 23 + let explode = false; 24 + let name = match.substring(1, match.length - 1); 25 + let style: ArraySeparatorStyle = 'simple'; 26 + 27 + if (name.endsWith('*')) { 28 + explode = true; 29 + name = name.substring(0, name.length - 1); 30 + } 31 + 32 + if (name.startsWith('.')) { 33 + name = name.substring(1); 34 + style = 'label'; 35 + } else if (name.startsWith(';')) { 36 + name = name.substring(1); 37 + style = 'matrix'; 38 + } 39 + 40 + const value = path[name]; 41 + 42 + if (value === undefined || value === null) { 43 + continue; 44 + } 45 + 46 + if (Array.isArray(value)) { 47 + url = url.replace( 48 + match, 49 + serializeArrayParam({ explode, name, style, value }), 50 + ); 51 + continue; 52 + } 53 + 54 + if (typeof value === 'object') { 55 + url = url.replace( 56 + match, 57 + serializeObjectParam({ 58 + explode, 59 + name, 60 + style, 61 + value: value as Record<string, unknown>, 62 + valueOnly: true, 63 + }), 64 + ); 65 + continue; 66 + } 67 + 68 + if (style === 'matrix') { 69 + url = url.replace( 70 + match, 71 + `;${serializePrimitiveParam({ 72 + name, 73 + value: value as string, 74 + })}`, 75 + ); 76 + continue; 77 + } 78 + 79 + const replaceValue = encodeURIComponent( 80 + style === 'label' ? `.${value as string}` : (value as string), 81 + ); 82 + url = url.replace(match, replaceValue); 83 + } 84 + } 85 + return url; 86 + }; 87 + 88 + export const getUrl = ({ 89 + baseUrl, 90 + path, 91 + query, 92 + querySerializer, 93 + url: _url, 94 + }: { 95 + baseUrl?: string; 96 + path?: Record<string, unknown>; 97 + query?: Record<string, unknown>; 98 + querySerializer: QuerySerializer; 99 + url: string; 100 + }) => { 101 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 102 + let url = (baseUrl ?? '') + pathUrl; 103 + if (path) { 104 + url = defaultPathSerializer({ path, url }); 105 + } 106 + let search = query ? querySerializer(query) : ''; 107 + if (search.startsWith('?')) { 108 + search = search.substring(1); 109 + } 110 + if (search) { 111 + url += `?${search}`; 112 + } 113 + return url; 114 + }; 115 + 116 + export function getValidRequestBody(options: { 117 + body?: unknown; 118 + bodySerializer?: BodySerializer | null; 119 + serializedBody?: unknown; 120 + }) { 121 + const hasBody = options.body !== undefined; 122 + const isSerializedBody = hasBody && options.bodySerializer; 123 + 124 + if (isSerializedBody) { 125 + if ('serializedBody' in options) { 126 + const hasSerializedBody = 127 + options.serializedBody !== undefined && options.serializedBody !== ''; 128 + 129 + return hasSerializedBody ? options.serializedBody : null; 130 + } 131 + 132 + // not all clients implement a serializedBody property (i.e. client-axios) 133 + return options.body !== '' ? options.body : null; 134 + } 135 + 136 + // plain/text body 137 + if (hasBody) { 138 + return options.body; 139 + } 140 + 141 + // no body was provided 142 + return undefined; 143 + }
+4
examples/openapi-ts-ofetch/src/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export * from './sdk.gen'; 4 + export * from './types.gen';
+188
examples/openapi-ts-ofetch/src/client/schemas.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export const OrderSchema = { 4 + properties: { 5 + complete: { 6 + type: 'boolean', 7 + }, 8 + id: { 9 + example: 10, 10 + format: 'int64', 11 + type: 'integer', 12 + }, 13 + petId: { 14 + example: 198772, 15 + format: 'int64', 16 + type: 'integer', 17 + }, 18 + quantity: { 19 + example: 7, 20 + format: 'int32', 21 + type: 'integer', 22 + }, 23 + shipDate: { 24 + format: 'date-time', 25 + type: 'string', 26 + }, 27 + status: { 28 + description: 'Order Status', 29 + enum: ['placed', 'approved', 'delivered'], 30 + example: 'approved', 31 + type: 'string', 32 + }, 33 + }, 34 + type: 'object', 35 + 'x-swagger-router-model': 'io.swagger.petstore.model.Order', 36 + xml: { 37 + name: 'order', 38 + }, 39 + } as const; 40 + 41 + export const CategorySchema = { 42 + properties: { 43 + id: { 44 + example: 1, 45 + format: 'int64', 46 + type: 'integer', 47 + }, 48 + name: { 49 + example: 'Dogs', 50 + type: 'string', 51 + }, 52 + }, 53 + type: 'object', 54 + 'x-swagger-router-model': 'io.swagger.petstore.model.Category', 55 + xml: { 56 + name: 'category', 57 + }, 58 + } as const; 59 + 60 + export const UserSchema = { 61 + properties: { 62 + email: { 63 + example: 'john@email.com', 64 + type: 'string', 65 + }, 66 + firstName: { 67 + example: 'John', 68 + type: 'string', 69 + }, 70 + id: { 71 + example: 10, 72 + format: 'int64', 73 + type: 'integer', 74 + }, 75 + lastName: { 76 + example: 'James', 77 + type: 'string', 78 + }, 79 + password: { 80 + example: '12345', 81 + type: 'string', 82 + }, 83 + phone: { 84 + example: '12345', 85 + type: 'string', 86 + }, 87 + userStatus: { 88 + description: 'User Status', 89 + example: 1, 90 + format: 'int32', 91 + type: 'integer', 92 + }, 93 + username: { 94 + example: 'theUser', 95 + type: 'string', 96 + }, 97 + }, 98 + type: 'object', 99 + 'x-swagger-router-model': 'io.swagger.petstore.model.User', 100 + xml: { 101 + name: 'user', 102 + }, 103 + } as const; 104 + 105 + export const TagSchema = { 106 + properties: { 107 + id: { 108 + format: 'int64', 109 + type: 'integer', 110 + }, 111 + name: { 112 + type: 'string', 113 + }, 114 + }, 115 + type: 'object', 116 + 'x-swagger-router-model': 'io.swagger.petstore.model.Tag', 117 + xml: { 118 + name: 'tag', 119 + }, 120 + } as const; 121 + 122 + export const PetSchema = { 123 + properties: { 124 + category: { 125 + $ref: '#/components/schemas/Category', 126 + }, 127 + id: { 128 + example: 10, 129 + format: 'int64', 130 + type: 'integer', 131 + }, 132 + name: { 133 + example: 'doggie', 134 + type: 'string', 135 + }, 136 + photoUrls: { 137 + items: { 138 + type: 'string', 139 + xml: { 140 + name: 'photoUrl', 141 + }, 142 + }, 143 + type: 'array', 144 + xml: { 145 + wrapped: true, 146 + }, 147 + }, 148 + status: { 149 + description: 'pet status in the store', 150 + enum: ['available', 'pending', 'sold'], 151 + type: 'string', 152 + }, 153 + tags: { 154 + items: { 155 + $ref: '#/components/schemas/Tag', 156 + }, 157 + type: 'array', 158 + xml: { 159 + wrapped: true, 160 + }, 161 + }, 162 + }, 163 + required: ['name', 'photoUrls'], 164 + type: 'object', 165 + 'x-swagger-router-model': 'io.swagger.petstore.model.Pet', 166 + xml: { 167 + name: 'pet', 168 + }, 169 + } as const; 170 + 171 + export const ApiResponseSchema = { 172 + properties: { 173 + code: { 174 + format: 'int32', 175 + type: 'integer', 176 + }, 177 + message: { 178 + type: 'string', 179 + }, 180 + type: { 181 + type: 'string', 182 + }, 183 + }, 184 + type: 'object', 185 + xml: { 186 + name: '##default', 187 + }, 188 + } as const;
+467
examples/openapi-ts-ofetch/src/client/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Options as ClientOptions, TDataShape } from './client'; 4 + import { client } from './client.gen'; 5 + import type { 6 + AddPetData, 7 + AddPetErrors, 8 + AddPetResponses, 9 + CreateUserData, 10 + CreateUserErrors, 11 + CreateUserResponses, 12 + CreateUsersWithListInputData, 13 + CreateUsersWithListInputErrors, 14 + CreateUsersWithListInputResponses, 15 + DeleteOrderData, 16 + DeleteOrderErrors, 17 + DeleteOrderResponses, 18 + DeletePetData, 19 + DeletePetErrors, 20 + DeletePetResponses, 21 + DeleteUserData, 22 + DeleteUserErrors, 23 + DeleteUserResponses, 24 + FindPetsByStatusData, 25 + FindPetsByStatusErrors, 26 + FindPetsByStatusResponses, 27 + FindPetsByTagsData, 28 + FindPetsByTagsErrors, 29 + FindPetsByTagsResponses, 30 + GetInventoryData, 31 + GetInventoryErrors, 32 + GetInventoryResponses, 33 + GetOrderByIdData, 34 + GetOrderByIdErrors, 35 + GetOrderByIdResponses, 36 + GetPetByIdData, 37 + GetPetByIdErrors, 38 + GetPetByIdResponses, 39 + GetUserByNameData, 40 + GetUserByNameErrors, 41 + GetUserByNameResponses, 42 + LoginUserData, 43 + LoginUserErrors, 44 + LoginUserResponses, 45 + LogoutUserData, 46 + LogoutUserErrors, 47 + LogoutUserResponses, 48 + PlaceOrderData, 49 + PlaceOrderErrors, 50 + PlaceOrderResponses, 51 + UpdatePetData, 52 + UpdatePetErrors, 53 + UpdatePetResponses, 54 + UpdatePetWithFormData, 55 + UpdatePetWithFormErrors, 56 + UpdatePetWithFormResponses, 57 + UpdateUserData, 58 + UpdateUserErrors, 59 + UpdateUserResponses, 60 + UploadFileData, 61 + UploadFileErrors, 62 + UploadFileResponses, 63 + } from './types.gen'; 64 + 65 + export type Options< 66 + TData extends TDataShape = TDataShape, 67 + ThrowOnError extends boolean = boolean, 68 + > = ClientOptions<TData, ThrowOnError> & { 69 + /** 70 + * You can provide a client instance returned by `createClient()` instead of 71 + * individual options. This might be also useful if you want to implement a 72 + * custom client. 73 + */ 74 + client?: Client; 75 + /** 76 + * You can pass arbitrary values through the `meta` object. This can be 77 + * used to access values that aren't defined as part of the SDK function. 78 + */ 79 + meta?: Record<string, unknown>; 80 + }; 81 + 82 + /** 83 + * Add a new pet to the store. 84 + * Add a new pet to the store. 85 + */ 86 + export const addPet = <ThrowOnError extends boolean = false>( 87 + options: Options<AddPetData, ThrowOnError>, 88 + ) => 89 + (options.client ?? client).post<AddPetResponses, AddPetErrors, ThrowOnError>({ 90 + security: [ 91 + { 92 + scheme: 'bearer', 93 + type: 'http', 94 + }, 95 + ], 96 + url: '/pet', 97 + ...options, 98 + headers: { 99 + 'Content-Type': 'application/json', 100 + ...options.headers, 101 + }, 102 + }); 103 + 104 + /** 105 + * Update an existing pet. 106 + * Update an existing pet by Id. 107 + */ 108 + export const updatePet = <ThrowOnError extends boolean = false>( 109 + options: Options<UpdatePetData, ThrowOnError>, 110 + ) => 111 + (options.client ?? client).put< 112 + UpdatePetResponses, 113 + UpdatePetErrors, 114 + ThrowOnError 115 + >({ 116 + security: [ 117 + { 118 + scheme: 'bearer', 119 + type: 'http', 120 + }, 121 + ], 122 + url: '/pet', 123 + ...options, 124 + headers: { 125 + 'Content-Type': 'application/json', 126 + ...options.headers, 127 + }, 128 + }); 129 + 130 + /** 131 + * Finds Pets by status. 132 + * Multiple status values can be provided with comma separated strings. 133 + */ 134 + export const findPetsByStatus = <ThrowOnError extends boolean = false>( 135 + options: Options<FindPetsByStatusData, ThrowOnError>, 136 + ) => 137 + (options.client ?? client).get< 138 + FindPetsByStatusResponses, 139 + FindPetsByStatusErrors, 140 + ThrowOnError 141 + >({ 142 + security: [ 143 + { 144 + scheme: 'bearer', 145 + type: 'http', 146 + }, 147 + ], 148 + url: '/pet/findByStatus', 149 + ...options, 150 + }); 151 + 152 + /** 153 + * Finds Pets by tags. 154 + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 155 + */ 156 + export const findPetsByTags = <ThrowOnError extends boolean = false>( 157 + options: Options<FindPetsByTagsData, ThrowOnError>, 158 + ) => 159 + (options.client ?? client).get< 160 + FindPetsByTagsResponses, 161 + FindPetsByTagsErrors, 162 + ThrowOnError 163 + >({ 164 + security: [ 165 + { 166 + scheme: 'bearer', 167 + type: 'http', 168 + }, 169 + ], 170 + url: '/pet/findByTags', 171 + ...options, 172 + }); 173 + 174 + /** 175 + * Deletes a pet. 176 + * Delete a pet. 177 + */ 178 + export const deletePet = <ThrowOnError extends boolean = false>( 179 + options: Options<DeletePetData, ThrowOnError>, 180 + ) => 181 + (options.client ?? client).delete< 182 + DeletePetResponses, 183 + DeletePetErrors, 184 + ThrowOnError 185 + >({ 186 + security: [ 187 + { 188 + scheme: 'bearer', 189 + type: 'http', 190 + }, 191 + ], 192 + url: '/pet/{petId}', 193 + ...options, 194 + }); 195 + 196 + /** 197 + * Find pet by ID. 198 + * Returns a single pet. 199 + */ 200 + export const getPetById = <ThrowOnError extends boolean = false>( 201 + options: Options<GetPetByIdData, ThrowOnError>, 202 + ) => 203 + (options.client ?? client).get< 204 + GetPetByIdResponses, 205 + GetPetByIdErrors, 206 + ThrowOnError 207 + >({ 208 + security: [ 209 + { 210 + name: 'api_key', 211 + type: 'apiKey', 212 + }, 213 + { 214 + scheme: 'bearer', 215 + type: 'http', 216 + }, 217 + ], 218 + url: '/pet/{petId}', 219 + ...options, 220 + }); 221 + 222 + /** 223 + * Updates a pet in the store with form data. 224 + * Updates a pet resource based on the form data. 225 + */ 226 + export const updatePetWithForm = <ThrowOnError extends boolean = false>( 227 + options: Options<UpdatePetWithFormData, ThrowOnError>, 228 + ) => 229 + (options.client ?? client).post< 230 + UpdatePetWithFormResponses, 231 + UpdatePetWithFormErrors, 232 + ThrowOnError 233 + >({ 234 + security: [ 235 + { 236 + scheme: 'bearer', 237 + type: 'http', 238 + }, 239 + ], 240 + url: '/pet/{petId}', 241 + ...options, 242 + }); 243 + 244 + /** 245 + * Uploads an image. 246 + * Upload image of the pet. 247 + */ 248 + export const uploadFile = <ThrowOnError extends boolean = false>( 249 + options: Options<UploadFileData, ThrowOnError>, 250 + ) => 251 + (options.client ?? client).post< 252 + UploadFileResponses, 253 + UploadFileErrors, 254 + ThrowOnError 255 + >({ 256 + bodySerializer: null, 257 + security: [ 258 + { 259 + scheme: 'bearer', 260 + type: 'http', 261 + }, 262 + ], 263 + url: '/pet/{petId}/uploadImage', 264 + ...options, 265 + headers: { 266 + 'Content-Type': 'application/octet-stream', 267 + ...options.headers, 268 + }, 269 + }); 270 + 271 + /** 272 + * Returns pet inventories by status. 273 + * Returns a map of status codes to quantities. 274 + */ 275 + export const getInventory = <ThrowOnError extends boolean = false>( 276 + options?: Options<GetInventoryData, ThrowOnError>, 277 + ) => 278 + (options?.client ?? client).get< 279 + GetInventoryResponses, 280 + GetInventoryErrors, 281 + ThrowOnError 282 + >({ 283 + security: [ 284 + { 285 + name: 'api_key', 286 + type: 'apiKey', 287 + }, 288 + ], 289 + url: '/store/inventory', 290 + ...options, 291 + }); 292 + 293 + /** 294 + * Place an order for a pet. 295 + * Place a new order in the store. 296 + */ 297 + export const placeOrder = <ThrowOnError extends boolean = false>( 298 + options?: Options<PlaceOrderData, ThrowOnError>, 299 + ) => 300 + (options?.client ?? client).post< 301 + PlaceOrderResponses, 302 + PlaceOrderErrors, 303 + ThrowOnError 304 + >({ 305 + url: '/store/order', 306 + ...options, 307 + headers: { 308 + 'Content-Type': 'application/json', 309 + ...options?.headers, 310 + }, 311 + }); 312 + 313 + /** 314 + * Delete purchase order by identifier. 315 + * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors. 316 + */ 317 + export const deleteOrder = <ThrowOnError extends boolean = false>( 318 + options: Options<DeleteOrderData, ThrowOnError>, 319 + ) => 320 + (options.client ?? client).delete< 321 + DeleteOrderResponses, 322 + DeleteOrderErrors, 323 + ThrowOnError 324 + >({ 325 + url: '/store/order/{orderId}', 326 + ...options, 327 + }); 328 + 329 + /** 330 + * Find purchase order by ID. 331 + * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. 332 + */ 333 + export const getOrderById = <ThrowOnError extends boolean = false>( 334 + options: Options<GetOrderByIdData, ThrowOnError>, 335 + ) => 336 + (options.client ?? client).get< 337 + GetOrderByIdResponses, 338 + GetOrderByIdErrors, 339 + ThrowOnError 340 + >({ 341 + url: '/store/order/{orderId}', 342 + ...options, 343 + }); 344 + 345 + /** 346 + * Create user. 347 + * This can only be done by the logged in user. 348 + */ 349 + export const createUser = <ThrowOnError extends boolean = false>( 350 + options?: Options<CreateUserData, ThrowOnError>, 351 + ) => 352 + (options?.client ?? client).post< 353 + CreateUserResponses, 354 + CreateUserErrors, 355 + ThrowOnError 356 + >({ 357 + url: '/user', 358 + ...options, 359 + headers: { 360 + 'Content-Type': 'application/json', 361 + ...options?.headers, 362 + }, 363 + }); 364 + 365 + /** 366 + * Creates list of users with given input array. 367 + * Creates list of users with given input array. 368 + */ 369 + export const createUsersWithListInput = <ThrowOnError extends boolean = false>( 370 + options?: Options<CreateUsersWithListInputData, ThrowOnError>, 371 + ) => 372 + (options?.client ?? client).post< 373 + CreateUsersWithListInputResponses, 374 + CreateUsersWithListInputErrors, 375 + ThrowOnError 376 + >({ 377 + url: '/user/createWithList', 378 + ...options, 379 + headers: { 380 + 'Content-Type': 'application/json', 381 + ...options?.headers, 382 + }, 383 + }); 384 + 385 + /** 386 + * Logs user into the system. 387 + * Log into the system. 388 + */ 389 + export const loginUser = <ThrowOnError extends boolean = false>( 390 + options?: Options<LoginUserData, ThrowOnError>, 391 + ) => 392 + (options?.client ?? client).get< 393 + LoginUserResponses, 394 + LoginUserErrors, 395 + ThrowOnError 396 + >({ 397 + url: '/user/login', 398 + ...options, 399 + }); 400 + 401 + /** 402 + * Logs out current logged in user session. 403 + * Log user out of the system. 404 + */ 405 + export const logoutUser = <ThrowOnError extends boolean = false>( 406 + options?: Options<LogoutUserData, ThrowOnError>, 407 + ) => 408 + (options?.client ?? client).get< 409 + LogoutUserResponses, 410 + LogoutUserErrors, 411 + ThrowOnError 412 + >({ 413 + url: '/user/logout', 414 + ...options, 415 + }); 416 + 417 + /** 418 + * Delete user resource. 419 + * This can only be done by the logged in user. 420 + */ 421 + export const deleteUser = <ThrowOnError extends boolean = false>( 422 + options: Options<DeleteUserData, ThrowOnError>, 423 + ) => 424 + (options.client ?? client).delete< 425 + DeleteUserResponses, 426 + DeleteUserErrors, 427 + ThrowOnError 428 + >({ 429 + url: '/user/{username}', 430 + ...options, 431 + }); 432 + 433 + /** 434 + * Get user by user name. 435 + * Get user detail based on username. 436 + */ 437 + export const getUserByName = <ThrowOnError extends boolean = false>( 438 + options: Options<GetUserByNameData, ThrowOnError>, 439 + ) => 440 + (options.client ?? client).get< 441 + GetUserByNameResponses, 442 + GetUserByNameErrors, 443 + ThrowOnError 444 + >({ 445 + url: '/user/{username}', 446 + ...options, 447 + }); 448 + 449 + /** 450 + * Update user resource. 451 + * This can only be done by the logged in user. 452 + */ 453 + export const updateUser = <ThrowOnError extends boolean = false>( 454 + options: Options<UpdateUserData, ThrowOnError>, 455 + ) => 456 + (options.client ?? client).put< 457 + UpdateUserResponses, 458 + UpdateUserErrors, 459 + ThrowOnError 460 + >({ 461 + url: '/user/{username}', 462 + ...options, 463 + headers: { 464 + 'Content-Type': 'application/json', 465 + ...options.headers, 466 + }, 467 + });
+699
examples/openapi-ts-ofetch/src/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type Order = { 4 + complete?: boolean; 5 + id?: number; 6 + petId?: number; 7 + quantity?: number; 8 + shipDate?: string; 9 + /** 10 + * Order Status 11 + */ 12 + status?: 'placed' | 'approved' | 'delivered'; 13 + }; 14 + 15 + export type Category = { 16 + id?: number; 17 + name?: string; 18 + }; 19 + 20 + export type User = { 21 + email?: string; 22 + firstName?: string; 23 + id?: number; 24 + lastName?: string; 25 + password?: string; 26 + phone?: string; 27 + /** 28 + * User Status 29 + */ 30 + userStatus?: number; 31 + username?: string; 32 + }; 33 + 34 + export type Tag = { 35 + id?: number; 36 + name?: string; 37 + }; 38 + 39 + export type Pet = { 40 + category?: Category; 41 + id?: number; 42 + name: string; 43 + photoUrls: Array<string>; 44 + /** 45 + * pet status in the store 46 + */ 47 + status?: 'available' | 'pending' | 'sold'; 48 + tags?: Array<Tag>; 49 + }; 50 + 51 + export type ApiResponse = { 52 + code?: number; 53 + message?: string; 54 + type?: string; 55 + }; 56 + 57 + export type Pet2 = Pet; 58 + 59 + /** 60 + * List of user object 61 + */ 62 + export type UserArray = Array<User>; 63 + 64 + export type AddPetData = { 65 + /** 66 + * Create a new pet in the store 67 + */ 68 + body: Pet; 69 + path?: never; 70 + query?: never; 71 + url: '/pet'; 72 + }; 73 + 74 + export type AddPetErrors = { 75 + /** 76 + * Invalid input 77 + */ 78 + 400: unknown; 79 + /** 80 + * Validation exception 81 + */ 82 + 422: unknown; 83 + /** 84 + * Unexpected error 85 + */ 86 + default: unknown; 87 + }; 88 + 89 + export type AddPetResponses = { 90 + /** 91 + * Successful operation 92 + */ 93 + 200: Pet; 94 + }; 95 + 96 + export type AddPetResponse = AddPetResponses[keyof AddPetResponses]; 97 + 98 + export type UpdatePetData = { 99 + /** 100 + * Update an existent pet in the store 101 + */ 102 + body: Pet; 103 + path?: never; 104 + query?: never; 105 + url: '/pet'; 106 + }; 107 + 108 + export type UpdatePetErrors = { 109 + /** 110 + * Invalid ID supplied 111 + */ 112 + 400: unknown; 113 + /** 114 + * Pet not found 115 + */ 116 + 404: unknown; 117 + /** 118 + * Validation exception 119 + */ 120 + 422: unknown; 121 + /** 122 + * Unexpected error 123 + */ 124 + default: unknown; 125 + }; 126 + 127 + export type UpdatePetResponses = { 128 + /** 129 + * Successful operation 130 + */ 131 + 200: Pet; 132 + }; 133 + 134 + export type UpdatePetResponse = UpdatePetResponses[keyof UpdatePetResponses]; 135 + 136 + export type FindPetsByStatusData = { 137 + body?: never; 138 + path?: never; 139 + query: { 140 + /** 141 + * Status values that need to be considered for filter 142 + */ 143 + status: 'available' | 'pending' | 'sold'; 144 + }; 145 + url: '/pet/findByStatus'; 146 + }; 147 + 148 + export type FindPetsByStatusErrors = { 149 + /** 150 + * Invalid status value 151 + */ 152 + 400: unknown; 153 + /** 154 + * Unexpected error 155 + */ 156 + default: unknown; 157 + }; 158 + 159 + export type FindPetsByStatusResponses = { 160 + /** 161 + * successful operation 162 + */ 163 + 200: Array<Pet>; 164 + }; 165 + 166 + export type FindPetsByStatusResponse = 167 + FindPetsByStatusResponses[keyof FindPetsByStatusResponses]; 168 + 169 + export type FindPetsByTagsData = { 170 + body?: never; 171 + path?: never; 172 + query: { 173 + /** 174 + * Tags to filter by 175 + */ 176 + tags: Array<string>; 177 + }; 178 + url: '/pet/findByTags'; 179 + }; 180 + 181 + export type FindPetsByTagsErrors = { 182 + /** 183 + * Invalid tag value 184 + */ 185 + 400: unknown; 186 + /** 187 + * Unexpected error 188 + */ 189 + default: unknown; 190 + }; 191 + 192 + export type FindPetsByTagsResponses = { 193 + /** 194 + * successful operation 195 + */ 196 + 200: Array<Pet>; 197 + }; 198 + 199 + export type FindPetsByTagsResponse = 200 + FindPetsByTagsResponses[keyof FindPetsByTagsResponses]; 201 + 202 + export type DeletePetData = { 203 + body?: never; 204 + headers?: { 205 + api_key?: string; 206 + }; 207 + path: { 208 + /** 209 + * Pet id to delete 210 + */ 211 + petId: number; 212 + }; 213 + query?: never; 214 + url: '/pet/{petId}'; 215 + }; 216 + 217 + export type DeletePetErrors = { 218 + /** 219 + * Invalid pet value 220 + */ 221 + 400: unknown; 222 + /** 223 + * Unexpected error 224 + */ 225 + default: unknown; 226 + }; 227 + 228 + export type DeletePetResponses = { 229 + /** 230 + * Pet deleted 231 + */ 232 + 200: unknown; 233 + }; 234 + 235 + export type GetPetByIdData = { 236 + body?: never; 237 + path: { 238 + /** 239 + * ID of pet to return 240 + */ 241 + petId: number; 242 + }; 243 + query?: never; 244 + url: '/pet/{petId}'; 245 + }; 246 + 247 + export type GetPetByIdErrors = { 248 + /** 249 + * Invalid ID supplied 250 + */ 251 + 400: unknown; 252 + /** 253 + * Pet not found 254 + */ 255 + 404: unknown; 256 + /** 257 + * Unexpected error 258 + */ 259 + default: unknown; 260 + }; 261 + 262 + export type GetPetByIdResponses = { 263 + /** 264 + * successful operation 265 + */ 266 + 200: Pet; 267 + }; 268 + 269 + export type GetPetByIdResponse = GetPetByIdResponses[keyof GetPetByIdResponses]; 270 + 271 + export type UpdatePetWithFormData = { 272 + body?: never; 273 + path: { 274 + /** 275 + * ID of pet that needs to be updated 276 + */ 277 + petId: number; 278 + }; 279 + query?: { 280 + /** 281 + * Name of pet that needs to be updated 282 + */ 283 + name?: string; 284 + /** 285 + * Status of pet that needs to be updated 286 + */ 287 + status?: string; 288 + }; 289 + url: '/pet/{petId}'; 290 + }; 291 + 292 + export type UpdatePetWithFormErrors = { 293 + /** 294 + * Invalid input 295 + */ 296 + 400: unknown; 297 + /** 298 + * Unexpected error 299 + */ 300 + default: unknown; 301 + }; 302 + 303 + export type UpdatePetWithFormResponses = { 304 + /** 305 + * successful operation 306 + */ 307 + 200: Pet; 308 + }; 309 + 310 + export type UpdatePetWithFormResponse = 311 + UpdatePetWithFormResponses[keyof UpdatePetWithFormResponses]; 312 + 313 + export type UploadFileData = { 314 + body?: Blob | File; 315 + path: { 316 + /** 317 + * ID of pet to update 318 + */ 319 + petId: number; 320 + }; 321 + query?: { 322 + /** 323 + * Additional Metadata 324 + */ 325 + additionalMetadata?: string; 326 + }; 327 + url: '/pet/{petId}/uploadImage'; 328 + }; 329 + 330 + export type UploadFileErrors = { 331 + /** 332 + * No file uploaded 333 + */ 334 + 400: unknown; 335 + /** 336 + * Pet not found 337 + */ 338 + 404: unknown; 339 + /** 340 + * Unexpected error 341 + */ 342 + default: unknown; 343 + }; 344 + 345 + export type UploadFileResponses = { 346 + /** 347 + * successful operation 348 + */ 349 + 200: ApiResponse; 350 + }; 351 + 352 + export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; 353 + 354 + export type GetInventoryData = { 355 + body?: never; 356 + path?: never; 357 + query?: never; 358 + url: '/store/inventory'; 359 + }; 360 + 361 + export type GetInventoryErrors = { 362 + /** 363 + * Unexpected error 364 + */ 365 + default: unknown; 366 + }; 367 + 368 + export type GetInventoryResponses = { 369 + /** 370 + * successful operation 371 + */ 372 + 200: { 373 + [key: string]: number; 374 + }; 375 + }; 376 + 377 + export type GetInventoryResponse = 378 + GetInventoryResponses[keyof GetInventoryResponses]; 379 + 380 + export type PlaceOrderData = { 381 + body?: Order; 382 + path?: never; 383 + query?: never; 384 + url: '/store/order'; 385 + }; 386 + 387 + export type PlaceOrderErrors = { 388 + /** 389 + * Invalid input 390 + */ 391 + 400: unknown; 392 + /** 393 + * Validation exception 394 + */ 395 + 422: unknown; 396 + /** 397 + * Unexpected error 398 + */ 399 + default: unknown; 400 + }; 401 + 402 + export type PlaceOrderResponses = { 403 + /** 404 + * successful operation 405 + */ 406 + 200: Order; 407 + }; 408 + 409 + export type PlaceOrderResponse = PlaceOrderResponses[keyof PlaceOrderResponses]; 410 + 411 + export type DeleteOrderData = { 412 + body?: never; 413 + path: { 414 + /** 415 + * ID of the order that needs to be deleted 416 + */ 417 + orderId: number; 418 + }; 419 + query?: never; 420 + url: '/store/order/{orderId}'; 421 + }; 422 + 423 + export type DeleteOrderErrors = { 424 + /** 425 + * Invalid ID supplied 426 + */ 427 + 400: unknown; 428 + /** 429 + * Order not found 430 + */ 431 + 404: unknown; 432 + /** 433 + * Unexpected error 434 + */ 435 + default: unknown; 436 + }; 437 + 438 + export type DeleteOrderResponses = { 439 + /** 440 + * order deleted 441 + */ 442 + 200: unknown; 443 + }; 444 + 445 + export type GetOrderByIdData = { 446 + body?: never; 447 + path: { 448 + /** 449 + * ID of order that needs to be fetched 450 + */ 451 + orderId: number; 452 + }; 453 + query?: never; 454 + url: '/store/order/{orderId}'; 455 + }; 456 + 457 + export type GetOrderByIdErrors = { 458 + /** 459 + * Invalid ID supplied 460 + */ 461 + 400: unknown; 462 + /** 463 + * Order not found 464 + */ 465 + 404: unknown; 466 + /** 467 + * Unexpected error 468 + */ 469 + default: unknown; 470 + }; 471 + 472 + export type GetOrderByIdResponses = { 473 + /** 474 + * successful operation 475 + */ 476 + 200: Order; 477 + }; 478 + 479 + export type GetOrderByIdResponse = 480 + GetOrderByIdResponses[keyof GetOrderByIdResponses]; 481 + 482 + export type CreateUserData = { 483 + /** 484 + * Created user object 485 + */ 486 + body?: User; 487 + path?: never; 488 + query?: never; 489 + url: '/user'; 490 + }; 491 + 492 + export type CreateUserErrors = { 493 + /** 494 + * Unexpected error 495 + */ 496 + default: unknown; 497 + }; 498 + 499 + export type CreateUserResponses = { 500 + /** 501 + * successful operation 502 + */ 503 + 200: User; 504 + }; 505 + 506 + export type CreateUserResponse = CreateUserResponses[keyof CreateUserResponses]; 507 + 508 + export type CreateUsersWithListInputData = { 509 + body?: Array<User>; 510 + path?: never; 511 + query?: never; 512 + url: '/user/createWithList'; 513 + }; 514 + 515 + export type CreateUsersWithListInputErrors = { 516 + /** 517 + * Unexpected error 518 + */ 519 + default: unknown; 520 + }; 521 + 522 + export type CreateUsersWithListInputResponses = { 523 + /** 524 + * Successful operation 525 + */ 526 + 200: User; 527 + }; 528 + 529 + export type CreateUsersWithListInputResponse = 530 + CreateUsersWithListInputResponses[keyof CreateUsersWithListInputResponses]; 531 + 532 + export type LoginUserData = { 533 + body?: never; 534 + path?: never; 535 + query?: { 536 + /** 537 + * The password for login in clear text 538 + */ 539 + password?: string; 540 + /** 541 + * The user name for login 542 + */ 543 + username?: string; 544 + }; 545 + url: '/user/login'; 546 + }; 547 + 548 + export type LoginUserErrors = { 549 + /** 550 + * Invalid username/password supplied 551 + */ 552 + 400: unknown; 553 + /** 554 + * Unexpected error 555 + */ 556 + default: unknown; 557 + }; 558 + 559 + export type LoginUserResponses = { 560 + /** 561 + * successful operation 562 + */ 563 + 200: string; 564 + }; 565 + 566 + export type LoginUserResponse = LoginUserResponses[keyof LoginUserResponses]; 567 + 568 + export type LogoutUserData = { 569 + body?: never; 570 + path?: never; 571 + query?: never; 572 + url: '/user/logout'; 573 + }; 574 + 575 + export type LogoutUserErrors = { 576 + /** 577 + * Unexpected error 578 + */ 579 + default: unknown; 580 + }; 581 + 582 + export type LogoutUserResponses = { 583 + /** 584 + * successful operation 585 + */ 586 + 200: unknown; 587 + }; 588 + 589 + export type DeleteUserData = { 590 + body?: never; 591 + path: { 592 + /** 593 + * The name that needs to be deleted 594 + */ 595 + username: string; 596 + }; 597 + query?: never; 598 + url: '/user/{username}'; 599 + }; 600 + 601 + export type DeleteUserErrors = { 602 + /** 603 + * Invalid username supplied 604 + */ 605 + 400: unknown; 606 + /** 607 + * User not found 608 + */ 609 + 404: unknown; 610 + /** 611 + * Unexpected error 612 + */ 613 + default: unknown; 614 + }; 615 + 616 + export type DeleteUserResponses = { 617 + /** 618 + * User deleted 619 + */ 620 + 200: unknown; 621 + }; 622 + 623 + export type GetUserByNameData = { 624 + body?: never; 625 + path: { 626 + /** 627 + * The name that needs to be fetched. Use user1 for testing 628 + */ 629 + username: string; 630 + }; 631 + query?: never; 632 + url: '/user/{username}'; 633 + }; 634 + 635 + export type GetUserByNameErrors = { 636 + /** 637 + * Invalid username supplied 638 + */ 639 + 400: unknown; 640 + /** 641 + * User not found 642 + */ 643 + 404: unknown; 644 + /** 645 + * Unexpected error 646 + */ 647 + default: unknown; 648 + }; 649 + 650 + export type GetUserByNameResponses = { 651 + /** 652 + * successful operation 653 + */ 654 + 200: User; 655 + }; 656 + 657 + export type GetUserByNameResponse = 658 + GetUserByNameResponses[keyof GetUserByNameResponses]; 659 + 660 + export type UpdateUserData = { 661 + /** 662 + * Update an existent user in the store 663 + */ 664 + body?: User; 665 + path: { 666 + /** 667 + * name that need to be deleted 668 + */ 669 + username: string; 670 + }; 671 + query?: never; 672 + url: '/user/{username}'; 673 + }; 674 + 675 + export type UpdateUserErrors = { 676 + /** 677 + * bad request 678 + */ 679 + 400: unknown; 680 + /** 681 + * user not found 682 + */ 683 + 404: unknown; 684 + /** 685 + * Unexpected error 686 + */ 687 + default: unknown; 688 + }; 689 + 690 + export type UpdateUserResponses = { 691 + /** 692 + * successful operation 693 + */ 694 + 200: unknown; 695 + }; 696 + 697 + export type ClientOptions = { 698 + baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {}); 699 + };
+18
examples/openapi-ts-ofetch/src/main.ts
··· 1 + import './assets/main.css'; 2 + 3 + import { createApp } from 'vue'; 4 + 5 + import App from './App.vue'; 6 + import { client } from './client/client.gen'; 7 + 8 + // configure internal service client 9 + client.setConfig({ 10 + // set default base url for requests 11 + baseUrl: 'https://petstore3.swagger.io/api/v3', 12 + // set default headers for requests 13 + headers: { 14 + Authorization: 'Bearer <token_from_service_client>', 15 + }, 16 + }); 17 + 18 + createApp(App).mount('#app');
+9
examples/openapi-ts-ofetch/tailwind.config.ts
··· 1 + import type { Config } from 'tailwindcss'; 2 + 3 + export default { 4 + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 5 + plugins: [], 6 + theme: { 7 + extend: {}, 8 + }, 9 + } satisfies Config;
+14
examples/openapi-ts-ofetch/tsconfig.app.json
··· 1 + { 2 + "extends": "@vue/tsconfig/tsconfig.dom.json", 3 + "include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"], 4 + "exclude": ["./src/**/__tests__/*"], 5 + "compilerOptions": { 6 + "composite": true, 7 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 8 + 9 + "baseUrl": ".", 10 + "paths": { 11 + "@/*": ["./src/*"] 12 + } 13 + } 14 + }
+11
examples/openapi-ts-ofetch/tsconfig.json
··· 1 + { 2 + "files": [], 3 + "references": [ 4 + { 5 + "path": "./tsconfig.node.json" 6 + }, 7 + { 8 + "path": "./tsconfig.app.json" 9 + } 10 + ] 11 + }
+19
examples/openapi-ts-ofetch/tsconfig.node.json
··· 1 + { 2 + "extends": "@tsconfig/node20/tsconfig.json", 3 + "include": [ 4 + "vite.config.*", 5 + "vitest.config.*", 6 + "cypress.config.*", 7 + "nightwatch.conf.*", 8 + "playwright.config.*" 9 + ], 10 + "compilerOptions": { 11 + "composite": true, 12 + "noEmit": true, 13 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 + 15 + "module": "ESNext", 16 + "moduleResolution": "Bundler", 17 + "types": ["node"] 18 + } 19 + }
+14
examples/openapi-ts-ofetch/vite.config.ts
··· 1 + import { fileURLToPath, URL } from 'node:url'; 2 + 3 + import { createViteConfig } from '@config/vite-base'; 4 + import vue from '@vitejs/plugin-vue'; 5 + 6 + // https://vitejs.dev/config/ 7 + export default createViteConfig({ 8 + plugins: [vue()], 9 + resolve: { 10 + alias: { 11 + '@': fileURLToPath(new URL('./src', import.meta.url)), 12 + }, 13 + }, 14 + });