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

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2426 from flow96/fix/nested-class-generation

fix(sdk): nested operationIds and tags. Add tests for nested class generation

authored by

Lubos and committed by
GitHub
ee1d5ec3 9d7cf27b

+10089 -4
+5
.changeset/new-scissors-jam.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + fix(sdk): handle infinite loop in nested operation IDs and tags with duplicate values
+18
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>({ 17 + baseUrl: 'https://api.example.com/v1' 18 + }));
+199
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+78
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class _HeyApiClient { 22 + protected _client: Client = _heyApiClient; 23 + 24 + constructor(args?: { 25 + client?: Client; 26 + }) { 27 + if (args?.client) { 28 + this._client = args.client; 29 + } 30 + } 31 + } 32 + 33 + class Domains extends _HeyApiClient { 34 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 35 + return (options?.client ?? this._client).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 36 + url: '/business/providers/domains', 37 + ...options 38 + }); 39 + } 40 + 41 + public post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 42 + return (options?.client ?? this._client).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 43 + url: '/business/providers/domains', 44 + ...options 45 + }); 46 + } 47 + } 48 + 49 + class Providers extends _HeyApiClient { 50 + domains = new Domains({ client: this._client }); 51 + } 52 + 53 + class Business extends _HeyApiClient { 54 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 55 + return (options?.client ?? this._client).get<BusinessGetResponses, unknown, ThrowOnError>({ 56 + url: '/locations/businesses', 57 + ...options 58 + }); 59 + } 60 + providers = new Providers({ client: this._client }); 61 + } 62 + 63 + export class NestedSdkWithInstance extends _HeyApiClient { 64 + public putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 65 + return (options?.client ?? this._client).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 66 + url: '/business/providers/domains', 67 + ...options 68 + }); 69 + } 70 + 71 + public get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 72 + return (options?.client ?? this._client).get<GetResponses, unknown, ThrowOnError>({ 73 + url: '/locations', 74 + ...options 75 + }); 76 + } 77 + business = new Business({ client: this._client }); 78 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: 'https://api.example.com/v1' | (string & {}); 85 + };
+18
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>({ 17 + baseUrl: 'https://api.example.com/v1' 18 + }));
+199
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+66
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class Domains { 22 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 23 + return (options?.client ?? _heyApiClient).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 24 + url: '/business/providers/domains', 25 + ...options 26 + }); 27 + } 28 + 29 + public static post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 30 + return (options?.client ?? _heyApiClient).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 31 + url: '/business/providers/domains', 32 + ...options 33 + }); 34 + } 35 + 36 + public static putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 37 + return (options?.client ?? _heyApiClient).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 38 + url: '/business/providers/domains', 39 + ...options 40 + }); 41 + } 42 + } 43 + 44 + class Providers { 45 + static domains = Domains; 46 + } 47 + 48 + export class Business { 49 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 50 + return (options?.client ?? _heyApiClient).get<BusinessGetResponses, unknown, ThrowOnError>({ 51 + url: '/locations/businesses', 52 + ...options 53 + }); 54 + } 55 + static providers = Providers; 56 + } 57 + 58 + export class Locations { 59 + public static get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 60 + return (options?.client ?? _heyApiClient).get<GetResponses, unknown, ThrowOnError>({ 61 + url: '/locations', 62 + ...options 63 + }); 64 + } 65 + static business = Business; 66 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: 'https://api.example.com/v1' | (string & {}); 85 + };
+18
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>({ 17 + baseUrl: 'https://api.example.com/v1' 18 + }));
+199
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+78
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class _HeyApiClient { 22 + protected _client: Client = _heyApiClient; 23 + 24 + constructor(args?: { 25 + client?: Client; 26 + }) { 27 + if (args?.client) { 28 + this._client = args.client; 29 + } 30 + } 31 + } 32 + 33 + class Domains extends _HeyApiClient { 34 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 35 + return (options?.client ?? this._client).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 36 + url: '/business/providers/domains', 37 + ...options 38 + }); 39 + } 40 + 41 + public post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 42 + return (options?.client ?? this._client).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 43 + url: '/business/providers/domains', 44 + ...options 45 + }); 46 + } 47 + } 48 + 49 + class Providers extends _HeyApiClient { 50 + domains = new Domains({ client: this._client }); 51 + } 52 + 53 + class Business extends _HeyApiClient { 54 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 55 + return (options?.client ?? this._client).get<BusinessGetResponses, unknown, ThrowOnError>({ 56 + url: '/locations/businesses', 57 + ...options 58 + }); 59 + } 60 + providers = new Providers({ client: this._client }); 61 + } 62 + 63 + export class NestedSdkWithInstance extends _HeyApiClient { 64 + public putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 65 + return (options?.client ?? this._client).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 66 + url: '/business/providers/domains', 67 + ...options 68 + }); 69 + } 70 + 71 + public get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 72 + return (options?.client ?? this._client).get<GetResponses, unknown, ThrowOnError>({ 73 + url: '/locations', 74 + ...options 75 + }); 76 + } 77 + business = new Business({ client: this._client }); 78 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: 'https://api.example.com/v1' | (string & {}); 85 + };
+18
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>({ 17 + baseUrl: 'https://api.example.com/v1' 18 + }));
+199
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+66
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class Domains { 22 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 23 + return (options?.client ?? _heyApiClient).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 24 + url: '/business/providers/domains', 25 + ...options 26 + }); 27 + } 28 + 29 + public static post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 30 + return (options?.client ?? _heyApiClient).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 31 + url: '/business/providers/domains', 32 + ...options 33 + }); 34 + } 35 + 36 + public static putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 37 + return (options?.client ?? _heyApiClient).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 38 + url: '/business/providers/domains', 39 + ...options 40 + }); 41 + } 42 + } 43 + 44 + class Providers { 45 + static domains = Domains; 46 + } 47 + 48 + export class Business { 49 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 50 + return (options?.client ?? _heyApiClient).get<BusinessGetResponses, unknown, ThrowOnError>({ 51 + url: '/locations/businesses', 52 + ...options 53 + }); 54 + } 55 + static providers = Providers; 56 + } 57 + 58 + export class Locations { 59 + public static get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 60 + return (options?.client ?? _heyApiClient).get<GetResponses, unknown, ThrowOnError>({ 61 + url: '/locations', 62 + ...options 63 + }); 64 + } 65 + static business = Business; 66 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/@hey-api/client-fetch/sdk-nested-classes/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: 'https://api.example.com/v1' | (string & {}); 85 + };
+16
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>());
+199
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+78
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class _HeyApiClient { 22 + protected _client: Client = _heyApiClient; 23 + 24 + constructor(args?: { 25 + client?: Client; 26 + }) { 27 + if (args?.client) { 28 + this._client = args.client; 29 + } 30 + } 31 + } 32 + 33 + class Domains extends _HeyApiClient { 34 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 35 + return (options?.client ?? this._client).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 36 + url: '/business/providers/domains', 37 + ...options 38 + }); 39 + } 40 + 41 + public post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 42 + return (options?.client ?? this._client).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 43 + url: '/business/providers/domains', 44 + ...options 45 + }); 46 + } 47 + } 48 + 49 + class Providers extends _HeyApiClient { 50 + domains = new Domains({ client: this._client }); 51 + } 52 + 53 + class Business extends _HeyApiClient { 54 + public get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 55 + return (options?.client ?? this._client).get<BusinessGetResponses, unknown, ThrowOnError>({ 56 + url: '/locations/businesses', 57 + ...options 58 + }); 59 + } 60 + providers = new Providers({ client: this._client }); 61 + } 62 + 63 + export class NestedSdkWithInstance extends _HeyApiClient { 64 + public putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 65 + return (options?.client ?? this._client).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 66 + url: '/business/providers/domains', 67 + ...options 68 + }); 69 + } 70 + 71 + public get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 72 + return (options?.client ?? this._client).get<GetResponses, unknown, ThrowOnError>({ 73 + url: '/locations', 74 + ...options 75 + }); 76 + } 77 + business = new Business({ client: this._client }); 78 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes-instance/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: `${string}://${string}` | (string & {}); 85 + };
+16
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ClientOptions } from './types.gen'; 4 + import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions>());
+199
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Config, ResolvedRequestOptions } from './types.gen'; 4 + import { 5 + buildUrl, 6 + createConfig, 7 + createInterceptors, 8 + getParseAs, 9 + mergeConfigs, 10 + mergeHeaders, 11 + setAuthParams, 12 + } from './utils.gen'; 13 + 14 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 15 + body?: any; 16 + headers: ReturnType<typeof mergeHeaders>; 17 + }; 18 + 19 + export const createClient = (config: Config = {}): Client => { 20 + let _config = mergeConfigs(createConfig(), config); 21 + 22 + const getConfig = (): Config => ({ ..._config }); 23 + 24 + const setConfig = (config: Config): Config => { 25 + _config = mergeConfigs(_config, config); 26 + return getConfig(); 27 + }; 28 + 29 + const interceptors = createInterceptors< 30 + Request, 31 + Response, 32 + unknown, 33 + ResolvedRequestOptions 34 + >(); 35 + 36 + const request: Client['request'] = async (options) => { 37 + const opts = { 38 + ..._config, 39 + ...options, 40 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 41 + headers: mergeHeaders(_config.headers, options.headers), 42 + serializedBody: undefined, 43 + }; 44 + 45 + if (opts.security) { 46 + await setAuthParams({ 47 + ...opts, 48 + security: opts.security, 49 + }); 50 + } 51 + 52 + if (opts.requestValidator) { 53 + await opts.requestValidator(opts); 54 + } 55 + 56 + if (opts.body && opts.bodySerializer) { 57 + opts.serializedBody = opts.bodySerializer(opts.body); 58 + } 59 + 60 + // remove Content-Type header if body is empty to avoid sending invalid requests 61 + if (opts.serializedBody === undefined || opts.serializedBody === '') { 62 + opts.headers.delete('Content-Type'); 63 + } 64 + 65 + const url = buildUrl(opts); 66 + const requestInit: ReqInit = { 67 + redirect: 'follow', 68 + ...opts, 69 + body: opts.serializedBody, 70 + }; 71 + 72 + let request = new Request(url, requestInit); 73 + 74 + for (const fn of interceptors.request._fns) { 75 + if (fn) { 76 + request = await fn(request, opts); 77 + } 78 + } 79 + 80 + // fetch must be assigned here, otherwise it would throw the error: 81 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 82 + const _fetch = opts.fetch!; 83 + let response = await _fetch(request); 84 + 85 + for (const fn of interceptors.response._fns) { 86 + if (fn) { 87 + response = await fn(response, request, opts); 88 + } 89 + } 90 + 91 + const result = { 92 + request, 93 + response, 94 + }; 95 + 96 + if (response.ok) { 97 + if ( 98 + response.status === 204 || 99 + response.headers.get('Content-Length') === '0' 100 + ) { 101 + return opts.responseStyle === 'data' 102 + ? {} 103 + : { 104 + data: {}, 105 + ...result, 106 + }; 107 + } 108 + 109 + const parseAs = 110 + (opts.parseAs === 'auto' 111 + ? getParseAs(response.headers.get('Content-Type')) 112 + : opts.parseAs) ?? 'json'; 113 + 114 + let data: any; 115 + switch (parseAs) { 116 + case 'arrayBuffer': 117 + case 'blob': 118 + case 'formData': 119 + case 'json': 120 + case 'text': 121 + data = await response[parseAs](); 122 + break; 123 + case 'stream': 124 + return opts.responseStyle === 'data' 125 + ? response.body 126 + : { 127 + data: response.body, 128 + ...result, 129 + }; 130 + } 131 + 132 + if (parseAs === 'json') { 133 + if (opts.responseValidator) { 134 + await opts.responseValidator(data); 135 + } 136 + 137 + if (opts.responseTransformer) { 138 + data = await opts.responseTransformer(data); 139 + } 140 + } 141 + 142 + return opts.responseStyle === 'data' 143 + ? data 144 + : { 145 + data, 146 + ...result, 147 + }; 148 + } 149 + 150 + const textError = await response.text(); 151 + let jsonError: unknown; 152 + 153 + try { 154 + jsonError = JSON.parse(textError); 155 + } catch { 156 + // noop 157 + } 158 + 159 + const error = jsonError ?? textError; 160 + let finalError = error; 161 + 162 + for (const fn of interceptors.error._fns) { 163 + if (fn) { 164 + finalError = (await fn(error, response, request, opts)) as string; 165 + } 166 + } 167 + 168 + finalError = finalError || ({} as string); 169 + 170 + if (opts.throwOnError) { 171 + throw finalError; 172 + } 173 + 174 + // TODO: we probably want to return error and improve types 175 + return opts.responseStyle === 'data' 176 + ? undefined 177 + : { 178 + error: finalError, 179 + ...result, 180 + }; 181 + }; 182 + 183 + return { 184 + buildUrl, 185 + connect: (options) => request({ ...options, method: 'CONNECT' }), 186 + delete: (options) => request({ ...options, method: 'DELETE' }), 187 + get: (options) => request({ ...options, method: 'GET' }), 188 + getConfig, 189 + head: (options) => request({ ...options, method: 'HEAD' }), 190 + interceptors, 191 + options: (options) => request({ ...options, method: 'OPTIONS' }), 192 + patch: (options) => request({ ...options, method: 'PATCH' }), 193 + post: (options) => request({ ...options, method: 'POST' }), 194 + put: (options) => request({ ...options, method: 'PUT' }), 195 + request, 196 + setConfig, 197 + trace: (options) => request({ ...options, method: 'TRACE' }), 198 + }; 199 + };
+25
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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';
+232
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + Client as CoreClient, 6 + Config as CoreConfig, 7 + } from '../core/types.gen'; 8 + import type { Middleware } from './utils.gen'; 9 + 10 + export type ResponseStyle = 'data' | 'fields'; 11 + 12 + export interface Config<T extends ClientOptions = ClientOptions> 13 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 14 + CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: (request: Request) => ReturnType<typeof fetch>; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 42 + | 'arrayBuffer' 43 + | 'auto' 44 + | 'blob' 45 + | 'formData' 46 + | 'json' 47 + | 'stream' 48 + | 'text'; 49 + /** 50 + * Should we return only data or multiple fields (data, error, response, etc.)? 51 + * 52 + * @default 'fields' 53 + */ 54 + responseStyle?: ResponseStyle; 55 + /** 56 + * Throw an error instead of returning it in the response? 57 + * 58 + * @default false 59 + */ 60 + throwOnError?: T['throwOnError']; 61 + } 62 + 63 + export interface RequestOptions< 64 + TResponseStyle extends ResponseStyle = 'fields', 65 + ThrowOnError extends boolean = boolean, 66 + Url extends string = string, 67 + > extends Config<{ 68 + responseStyle: TResponseStyle; 69 + throwOnError: ThrowOnError; 70 + }> { 71 + /** 72 + * Any body that you want to add to your request. 73 + * 74 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 75 + */ 76 + body?: unknown; 77 + path?: Record<string, unknown>; 78 + query?: Record<string, unknown>; 79 + /** 80 + * Security mechanism(s) to use for the request. 81 + */ 82 + security?: ReadonlyArray<Auth>; 83 + url: Url; 84 + } 85 + 86 + export interface ResolvedRequestOptions< 87 + TResponseStyle extends ResponseStyle = 'fields', 88 + ThrowOnError extends boolean = boolean, 89 + Url extends string = string, 90 + > extends RequestOptions<TResponseStyle, ThrowOnError, Url> { 91 + serializedBody?: string; 92 + } 93 + 94 + export type RequestResult< 95 + TData = unknown, 96 + TError = unknown, 97 + ThrowOnError extends boolean = boolean, 98 + TResponseStyle extends ResponseStyle = 'fields', 99 + > = ThrowOnError extends true 100 + ? Promise< 101 + TResponseStyle extends 'data' 102 + ? TData extends Record<string, unknown> 103 + ? TData[keyof TData] 104 + : TData 105 + : { 106 + data: TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData; 109 + request: Request; 110 + response: Response; 111 + } 112 + > 113 + : Promise< 114 + TResponseStyle extends 'data' 115 + ? 116 + | (TData extends Record<string, unknown> 117 + ? TData[keyof TData] 118 + : TData) 119 + | undefined 120 + : ( 121 + | { 122 + data: TData extends Record<string, unknown> 123 + ? TData[keyof TData] 124 + : TData; 125 + error: undefined; 126 + } 127 + | { 128 + data: undefined; 129 + error: TError extends Record<string, unknown> 130 + ? TError[keyof TError] 131 + : TError; 132 + } 133 + ) & { 134 + request: Request; 135 + response: Response; 136 + } 137 + >; 138 + 139 + export interface ClientOptions { 140 + baseUrl?: string; 141 + responseStyle?: ResponseStyle; 142 + throwOnError?: boolean; 143 + } 144 + 145 + type MethodFn = < 146 + TData = unknown, 147 + TError = unknown, 148 + ThrowOnError extends boolean = false, 149 + TResponseStyle extends ResponseStyle = 'fields', 150 + >( 151 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>, 152 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 153 + 154 + type RequestFn = < 155 + TData = unknown, 156 + TError = unknown, 157 + ThrowOnError extends boolean = false, 158 + TResponseStyle extends ResponseStyle = 'fields', 159 + >( 160 + options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> & 161 + Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>, 162 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 163 + 164 + type BuildUrlFn = < 165 + TData extends { 166 + body?: unknown; 167 + path?: Record<string, unknown>; 168 + query?: Record<string, unknown>; 169 + url: string; 170 + }, 171 + >( 172 + options: Pick<TData, 'url'> & Options<TData>, 173 + ) => string; 174 + 175 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & { 176 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 177 + }; 178 + 179 + /** 180 + * The `createClientConfig()` function will be called on client initialization 181 + * and the returned object will become the client's initial configuration. 182 + * 183 + * You may want to initialize your client this way instead of calling 184 + * `setConfig()`. This is useful for example if you're using Next.js 185 + * to ensure your client always has the correct values. 186 + */ 187 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 188 + override?: Config<ClientOptions & T>, 189 + ) => Config<Required<ClientOptions> & T>; 190 + 191 + export interface TDataShape { 192 + body?: unknown; 193 + headers?: unknown; 194 + path?: unknown; 195 + query?: unknown; 196 + url: string; 197 + } 198 + 199 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 200 + 201 + export type Options< 202 + TData extends TDataShape = TDataShape, 203 + ThrowOnError extends boolean = boolean, 204 + TResponseStyle extends ResponseStyle = 'fields', 205 + > = OmitKeys< 206 + RequestOptions<TResponseStyle, ThrowOnError>, 207 + 'body' | 'path' | 'query' | 'url' 208 + > & 209 + Omit<TData, 'url'>; 210 + 211 + export type OptionsLegacyParser< 212 + TData = unknown, 213 + ThrowOnError extends boolean = boolean, 214 + TResponseStyle extends ResponseStyle = 'fields', 215 + > = TData extends { body?: any } 216 + ? TData extends { headers?: any } 217 + ? OmitKeys< 218 + RequestOptions<TResponseStyle, ThrowOnError>, 219 + 'body' | 'headers' | 'url' 220 + > & 221 + TData 222 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> & 223 + TData & 224 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'> 225 + : TData extends { headers?: any } 226 + ? OmitKeys< 227 + RequestOptions<TResponseStyle, ThrowOnError>, 228 + 'headers' | 'url' 229 + > & 230 + TData & 231 + Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'> 232 + : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;
+419
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { 5 + QuerySerializer, 6 + QuerySerializerOptions, 7 + } from '../core/bodySerializer.gen'; 8 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 9 + import { 10 + serializeArrayParam, 11 + serializeObjectParam, 12 + serializePrimitiveParam, 13 + } from '../core/pathSerializer.gen'; 14 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 15 + 16 + interface PathSerializer { 17 + path: Record<string, unknown>; 18 + url: string; 19 + } 20 + 21 + const PATH_PARAM_RE = /\{[^{}]+\}/g; 22 + 23 + type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 24 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 25 + type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 26 + 27 + const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 28 + let url = _url; 29 + const matches = _url.match(PATH_PARAM_RE); 30 + if (matches) { 31 + for (const match of matches) { 32 + let explode = false; 33 + let name = match.substring(1, match.length - 1); 34 + let style: ArraySeparatorStyle = 'simple'; 35 + 36 + if (name.endsWith('*')) { 37 + explode = true; 38 + name = name.substring(0, name.length - 1); 39 + } 40 + 41 + if (name.startsWith('.')) { 42 + name = name.substring(1); 43 + style = 'label'; 44 + } else if (name.startsWith(';')) { 45 + name = name.substring(1); 46 + style = 'matrix'; 47 + } 48 + 49 + const value = path[name]; 50 + 51 + if (value === undefined || value === null) { 52 + continue; 53 + } 54 + 55 + if (Array.isArray(value)) { 56 + url = url.replace( 57 + match, 58 + serializeArrayParam({ explode, name, style, value }), 59 + ); 60 + continue; 61 + } 62 + 63 + if (typeof value === 'object') { 64 + url = url.replace( 65 + match, 66 + serializeObjectParam({ 67 + explode, 68 + name, 69 + style, 70 + value: value as Record<string, unknown>, 71 + valueOnly: true, 72 + }), 73 + ); 74 + continue; 75 + } 76 + 77 + if (style === 'matrix') { 78 + url = url.replace( 79 + match, 80 + `;${serializePrimitiveParam({ 81 + name, 82 + value: value as string, 83 + })}`, 84 + ); 85 + continue; 86 + } 87 + 88 + const replaceValue = encodeURIComponent( 89 + style === 'label' ? `.${value as string}` : (value as string), 90 + ); 91 + url = url.replace(match, replaceValue); 92 + } 93 + } 94 + return url; 95 + }; 96 + 97 + export const createQuerySerializer = <T = unknown>({ 98 + allowReserved, 99 + array, 100 + object, 101 + }: QuerySerializerOptions = {}) => { 102 + const querySerializer = (queryParams: T) => { 103 + const search: string[] = []; 104 + if (queryParams && typeof queryParams === 'object') { 105 + for (const name in queryParams) { 106 + const value = queryParams[name]; 107 + 108 + if (value === undefined || value === null) { 109 + continue; 110 + } 111 + 112 + if (Array.isArray(value)) { 113 + const serializedArray = serializeArrayParam({ 114 + allowReserved, 115 + explode: true, 116 + name, 117 + style: 'form', 118 + value, 119 + ...array, 120 + }); 121 + if (serializedArray) search.push(serializedArray); 122 + } else if (typeof value === 'object') { 123 + const serializedObject = serializeObjectParam({ 124 + allowReserved, 125 + explode: true, 126 + name, 127 + style: 'deepObject', 128 + value: value as Record<string, unknown>, 129 + ...object, 130 + }); 131 + if (serializedObject) search.push(serializedObject); 132 + } else { 133 + const serializedPrimitive = serializePrimitiveParam({ 134 + allowReserved, 135 + name, 136 + value: value as string, 137 + }); 138 + if (serializedPrimitive) search.push(serializedPrimitive); 139 + } 140 + } 141 + } 142 + return search.join('&'); 143 + }; 144 + return querySerializer; 145 + }; 146 + 147 + /** 148 + * Infers parseAs value from provided Content-Type header. 149 + */ 150 + export const getParseAs = ( 151 + contentType: string | null, 152 + ): Exclude<Config['parseAs'], 'auto'> => { 153 + if (!contentType) { 154 + // If no Content-Type header is provided, the best we can do is return the raw response body, 155 + // which is effectively the same as the 'stream' option. 156 + return 'stream'; 157 + } 158 + 159 + const cleanContent = contentType.split(';')[0]?.trim(); 160 + 161 + if (!cleanContent) { 162 + return; 163 + } 164 + 165 + if ( 166 + cleanContent.startsWith('application/json') || 167 + cleanContent.endsWith('+json') 168 + ) { 169 + return 'json'; 170 + } 171 + 172 + if (cleanContent === 'multipart/form-data') { 173 + return 'formData'; 174 + } 175 + 176 + if ( 177 + ['application/', 'audio/', 'image/', 'video/'].some((type) => 178 + cleanContent.startsWith(type), 179 + ) 180 + ) { 181 + return 'blob'; 182 + } 183 + 184 + if (cleanContent.startsWith('text/')) { 185 + return 'text'; 186 + } 187 + 188 + return; 189 + }; 190 + 191 + export const setAuthParams = async ({ 192 + security, 193 + ...options 194 + }: Pick<Required<RequestOptions>, 'security'> & 195 + Pick<RequestOptions, 'auth' | 'query'> & { 196 + headers: Headers; 197 + }) => { 198 + for (const auth of security) { 199 + const token = await getAuthToken(auth, options.auth); 200 + 201 + if (!token) { 202 + continue; 203 + } 204 + 205 + const name = auth.name ?? 'Authorization'; 206 + 207 + switch (auth.in) { 208 + case 'query': 209 + if (!options.query) { 210 + options.query = {}; 211 + } 212 + options.query[name] = token; 213 + break; 214 + case 'cookie': 215 + options.headers.append('Cookie', `${name}=${token}`); 216 + break; 217 + case 'header': 218 + default: 219 + options.headers.set(name, token); 220 + break; 221 + } 222 + 223 + return; 224 + } 225 + }; 226 + 227 + export const buildUrl: Client['buildUrl'] = (options) => { 228 + const url = getUrl({ 229 + baseUrl: options.baseUrl as string, 230 + path: options.path, 231 + query: options.query, 232 + querySerializer: 233 + typeof options.querySerializer === 'function' 234 + ? options.querySerializer 235 + : createQuerySerializer(options.querySerializer), 236 + url: options.url, 237 + }); 238 + return url; 239 + }; 240 + 241 + export const getUrl = ({ 242 + baseUrl, 243 + path, 244 + query, 245 + querySerializer, 246 + url: _url, 247 + }: { 248 + baseUrl?: string; 249 + path?: Record<string, unknown>; 250 + query?: Record<string, unknown>; 251 + querySerializer: QuerySerializer; 252 + url: string; 253 + }) => { 254 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 255 + let url = (baseUrl ?? '') + pathUrl; 256 + if (path) { 257 + url = defaultPathSerializer({ path, url }); 258 + } 259 + let search = query ? querySerializer(query) : ''; 260 + if (search.startsWith('?')) { 261 + search = search.substring(1); 262 + } 263 + if (search) { 264 + url += `?${search}`; 265 + } 266 + return url; 267 + }; 268 + 269 + export const mergeConfigs = (a: Config, b: Config): Config => { 270 + const config = { ...a, ...b }; 271 + if (config.baseUrl?.endsWith('/')) { 272 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 273 + } 274 + config.headers = mergeHeaders(a.headers, b.headers); 275 + return config; 276 + }; 277 + 278 + export const mergeHeaders = ( 279 + ...headers: Array<Required<Config>['headers'] | undefined> 280 + ): Headers => { 281 + const mergedHeaders = new Headers(); 282 + for (const header of headers) { 283 + if (!header || typeof header !== 'object') { 284 + continue; 285 + } 286 + 287 + const iterator = 288 + header instanceof Headers ? header.entries() : Object.entries(header); 289 + 290 + for (const [key, value] of iterator) { 291 + if (value === null) { 292 + mergedHeaders.delete(key); 293 + } else if (Array.isArray(value)) { 294 + for (const v of value) { 295 + mergedHeaders.append(key, v as string); 296 + } 297 + } else if (value !== undefined) { 298 + // assume object headers are meant to be JSON stringified, i.e. their 299 + // content value in OpenAPI specification is 'application/json' 300 + mergedHeaders.set( 301 + key, 302 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 303 + ); 304 + } 305 + } 306 + } 307 + return mergedHeaders; 308 + }; 309 + 310 + type ErrInterceptor<Err, Res, Req, Options> = ( 311 + error: Err, 312 + response: Res, 313 + request: Req, 314 + options: Options, 315 + ) => Err | Promise<Err>; 316 + 317 + type ReqInterceptor<Req, Options> = ( 318 + request: Req, 319 + options: Options, 320 + ) => Req | Promise<Req>; 321 + 322 + type ResInterceptor<Res, Req, Options> = ( 323 + response: Res, 324 + request: Req, 325 + options: Options, 326 + ) => Res | Promise<Res>; 327 + 328 + class Interceptors<Interceptor> { 329 + _fns: (Interceptor | null)[]; 330 + 331 + constructor() { 332 + this._fns = []; 333 + } 334 + 335 + clear() { 336 + this._fns = []; 337 + } 338 + 339 + getInterceptorIndex(id: number | Interceptor): number { 340 + if (typeof id === 'number') { 341 + return this._fns[id] ? id : -1; 342 + } else { 343 + return this._fns.indexOf(id); 344 + } 345 + } 346 + exists(id: number | Interceptor) { 347 + const index = this.getInterceptorIndex(id); 348 + return !!this._fns[index]; 349 + } 350 + 351 + eject(id: number | Interceptor) { 352 + const index = this.getInterceptorIndex(id); 353 + if (this._fns[index]) { 354 + this._fns[index] = null; 355 + } 356 + } 357 + 358 + update(id: number | Interceptor, fn: Interceptor) { 359 + const index = this.getInterceptorIndex(id); 360 + if (this._fns[index]) { 361 + this._fns[index] = fn; 362 + return id; 363 + } else { 364 + return false; 365 + } 366 + } 367 + 368 + use(fn: Interceptor) { 369 + this._fns = [...this._fns, fn]; 370 + return this._fns.length - 1; 371 + } 372 + } 373 + 374 + // `createInterceptors()` response, meant for external use as it does not 375 + // expose internals 376 + export interface Middleware<Req, Res, Err, Options> { 377 + error: Pick< 378 + Interceptors<ErrInterceptor<Err, Res, Req, Options>>, 379 + 'eject' | 'use' 380 + >; 381 + request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>; 382 + response: Pick< 383 + Interceptors<ResInterceptor<Res, Req, Options>>, 384 + 'eject' | 'use' 385 + >; 386 + } 387 + 388 + // do not add `Middleware` as return type so we can use _fns internally 389 + export const createInterceptors = <Req, Res, Err, Options>() => ({ 390 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 391 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 392 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 393 + }); 394 + 395 + const defaultQuerySerializer = createQuerySerializer({ 396 + allowReserved: false, 397 + array: { 398 + explode: true, 399 + style: 'form', 400 + }, 401 + object: { 402 + explode: true, 403 + style: 'deepObject', 404 + }, 405 + }); 406 + 407 + const defaultHeaders = { 408 + 'Content-Type': 'application/json', 409 + }; 410 + 411 + export const createConfig = <T extends ClientOptions = ClientOptions>( 412 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 413 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 414 + ...jsonBodySerializer, 415 + headers: defaultHeaders, 416 + parseAs: 'auto', 417 + querySerializer: defaultQuerySerializer, 418 + ...override, 419 + });
+42
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+90
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 { 27 + data.append(key, JSON.stringify(value)); 28 + } 29 + }; 30 + 31 + const serializeUrlSearchParamsPair = ( 32 + data: URLSearchParams, 33 + key: string, 34 + value: unknown, 35 + ): void => { 36 + if (typeof value === 'string') { 37 + data.append(key, value); 38 + } else { 39 + data.append(key, JSON.stringify(value)); 40 + } 41 + }; 42 + 43 + export const formDataBodySerializer = { 44 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 45 + body: T, 46 + ): FormData => { 47 + const data = new FormData(); 48 + 49 + Object.entries(body).forEach(([key, value]) => { 50 + if (value === undefined || value === null) { 51 + return; 52 + } 53 + if (Array.isArray(value)) { 54 + value.forEach((v) => serializeFormDataPair(data, key, v)); 55 + } else { 56 + serializeFormDataPair(data, key, value); 57 + } 58 + }); 59 + 60 + return data; 61 + }, 62 + }; 63 + 64 + export const jsonBodySerializer = { 65 + bodySerializer: <T>(body: T): string => 66 + JSON.stringify(body, (_key, value) => 67 + typeof value === 'bigint' ? value.toString() : value, 68 + ), 69 + }; 70 + 71 + export const urlSearchParamsBodySerializer = { 72 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 73 + body: T, 74 + ): string => { 75 + const data = new URLSearchParams(); 76 + 77 + Object.entries(body).forEach(([key, value]) => { 78 + if (value === undefined || value === null) { 79 + return; 80 + } 81 + if (Array.isArray(value)) { 82 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 83 + } else { 84 + serializeUrlSearchParamsPair(data, key, value); 85 + } 86 + }); 87 + 88 + return data.toString(); 89 + }, 90 + };
+153
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 + };
+120
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/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 interface Client< 11 + RequestFn = never, 12 + Config = unknown, 13 + MethodFn = never, 14 + BuildUrlFn = never, 15 + > { 16 + /** 17 + * Returns the final request URL. 18 + */ 19 + buildUrl: BuildUrlFn; 20 + connect: MethodFn; 21 + delete: MethodFn; 22 + get: MethodFn; 23 + getConfig: () => Config; 24 + head: MethodFn; 25 + options: MethodFn; 26 + patch: MethodFn; 27 + post: MethodFn; 28 + put: MethodFn; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + trace: MethodFn; 32 + } 33 + 34 + export interface Config { 35 + /** 36 + * Auth token or a function returning auth token. The resolved value will be 37 + * added to the request payload as defined by its `security` array. 38 + */ 39 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 40 + /** 41 + * A function for serializing request body parameter. By default, 42 + * {@link JSON.stringify()} will be used. 43 + */ 44 + bodySerializer?: BodySerializer | null; 45 + /** 46 + * An object containing any HTTP headers that you want to pre-populate your 47 + * `Headers` object with. 48 + * 49 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 50 + */ 51 + headers?: 52 + | RequestInit['headers'] 53 + | Record< 54 + string, 55 + | string 56 + | number 57 + | boolean 58 + | (string | number | boolean)[] 59 + | null 60 + | undefined 61 + | unknown 62 + >; 63 + /** 64 + * The request method. 65 + * 66 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 67 + */ 68 + method?: 69 + | 'CONNECT' 70 + | 'DELETE' 71 + | 'GET' 72 + | 'HEAD' 73 + | 'OPTIONS' 74 + | 'PATCH' 75 + | 'POST' 76 + | 'PUT' 77 + | 'TRACE'; 78 + /** 79 + * A function for serializing request query parameters. By default, arrays 80 + * will be exploded in form style, objects will be exploded in deepObject 81 + * style, and reserved characters are percent-encoded. 82 + * 83 + * This method will have no effect if the native `paramsSerializer()` Axios 84 + * API function is used. 85 + * 86 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 87 + */ 88 + querySerializer?: QuerySerializer | QuerySerializerOptions; 89 + /** 90 + * A function validating request data. This is useful if you want to ensure 91 + * the request conforms to the desired shape, so it can be safely sent to 92 + * the server. 93 + */ 94 + requestValidator?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function transforming response data before it's returned. This is useful 97 + * for post-processing data, e.g. converting ISO strings into Date objects. 98 + */ 99 + responseTransformer?: (data: unknown) => Promise<unknown>; 100 + /** 101 + * A function validating response data. This is useful if you want to ensure 102 + * the response conforms to the desired shape, so it can be safely passed to 103 + * the transformers and returned to the user. 104 + */ 105 + responseValidator?: (data: unknown) => Promise<unknown>; 106 + } 107 + 108 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 109 + ? true 110 + : [T] extends [never | undefined] 111 + ? [undefined] extends [T] 112 + ? false 113 + : true 114 + : false; 115 + 116 + export type OmitNever<T extends Record<string, unknown>> = { 117 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 118 + ? never 119 + : K]: T[K]; 120 + };
+3
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+66
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Options as ClientOptions, TDataShape, Client } from './client'; 4 + import type { BusinessProvidersDomainsGetData, BusinessProvidersDomainsGetResponses, BusinessProvidersDomainsPostData, BusinessProvidersDomainsPostResponses, PutBusinessProvidersDomainsData, PutBusinessProvidersDomainsResponses, BusinessGetData, BusinessGetResponses, GetData, GetResponses } from './types.gen'; 5 + import { client as _heyApiClient } from './client.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + class Domains { 22 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsGetData, ThrowOnError>) { 23 + return (options?.client ?? _heyApiClient).get<BusinessProvidersDomainsGetResponses, unknown, ThrowOnError>({ 24 + url: '/business/providers/domains', 25 + ...options 26 + }); 27 + } 28 + 29 + public static post<ThrowOnError extends boolean = false>(options?: Options<BusinessProvidersDomainsPostData, ThrowOnError>) { 30 + return (options?.client ?? _heyApiClient).post<BusinessProvidersDomainsPostResponses, unknown, ThrowOnError>({ 31 + url: '/business/providers/domains', 32 + ...options 33 + }); 34 + } 35 + 36 + public static putBusinessProvidersDomains<ThrowOnError extends boolean = false>(options?: Options<PutBusinessProvidersDomainsData, ThrowOnError>) { 37 + return (options?.client ?? _heyApiClient).put<PutBusinessProvidersDomainsResponses, unknown, ThrowOnError>({ 38 + url: '/business/providers/domains', 39 + ...options 40 + }); 41 + } 42 + } 43 + 44 + class Providers { 45 + static domains = Domains; 46 + } 47 + 48 + export class Business { 49 + public static get<ThrowOnError extends boolean = false>(options?: Options<BusinessGetData, ThrowOnError>) { 50 + return (options?.client ?? _heyApiClient).get<BusinessGetResponses, unknown, ThrowOnError>({ 51 + url: '/locations/businesses', 52 + ...options 53 + }); 54 + } 55 + static providers = Providers; 56 + } 57 + 58 + export class Locations { 59 + public static get<ThrowOnError extends boolean = false>(options?: Options<GetData, ThrowOnError>) { 60 + return (options?.client ?? _heyApiClient).get<GetResponses, unknown, ThrowOnError>({ 61 + url: '/locations', 62 + ...options 63 + }); 64 + } 65 + static business = Business; 66 + }
+85
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/@hey-api/client-fetch/sdk-nested-classes/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type BusinessProvidersDomainsGetData = { 4 + body?: never; 5 + path?: never; 6 + query?: never; 7 + url: '/business/providers/domains'; 8 + }; 9 + 10 + export type BusinessProvidersDomainsGetResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type BusinessProvidersDomainsGetResponse = BusinessProvidersDomainsGetResponses[keyof BusinessProvidersDomainsGetResponses]; 18 + 19 + export type BusinessProvidersDomainsPostData = { 20 + body?: never; 21 + path?: never; 22 + query?: never; 23 + url: '/business/providers/domains'; 24 + }; 25 + 26 + export type BusinessProvidersDomainsPostResponses = { 27 + /** 28 + * OK 29 + */ 30 + 200: string; 31 + }; 32 + 33 + export type BusinessProvidersDomainsPostResponse = BusinessProvidersDomainsPostResponses[keyof BusinessProvidersDomainsPostResponses]; 34 + 35 + export type PutBusinessProvidersDomainsData = { 36 + body?: never; 37 + path?: never; 38 + query?: never; 39 + url: '/business/providers/domains'; 40 + }; 41 + 42 + export type PutBusinessProvidersDomainsResponses = { 43 + /** 44 + * OK 45 + */ 46 + 200: string; 47 + }; 48 + 49 + export type PutBusinessProvidersDomainsResponse = PutBusinessProvidersDomainsResponses[keyof PutBusinessProvidersDomainsResponses]; 50 + 51 + export type BusinessGetData = { 52 + body?: never; 53 + path?: never; 54 + query?: never; 55 + url: '/locations/businesses'; 56 + }; 57 + 58 + export type BusinessGetResponses = { 59 + /** 60 + * OK 61 + */ 62 + 200: string; 63 + }; 64 + 65 + export type BusinessGetResponse = BusinessGetResponses[keyof BusinessGetResponses]; 66 + 67 + export type GetData = { 68 + body?: never; 69 + path?: never; 70 + query?: never; 71 + url: '/locations'; 72 + }; 73 + 74 + export type GetResponses = { 75 + /** 76 + * OK 77 + */ 78 + 200: string; 79 + }; 80 + 81 + export type GetResponse = GetResponses[keyof GetResponses]; 82 + 83 + export type ClientOptions = { 84 + baseUrl: `${string}://${string}` | (string & {}); 85 + };
+31
packages/openapi-ts-tests/main/test/plugins.test.ts
··· 481 481 }), 482 482 description: 'custom read-only and write-only naming', 483 483 }, 484 + { 485 + config: createConfig({ 486 + input: 'sdk-nested-classes.yaml', 487 + output: 'sdk-nested-classes', 488 + plugins: [ 489 + '@hey-api/client-fetch', 490 + { 491 + asClass: true, 492 + classStructure: 'auto', 493 + name: '@hey-api/sdk', 494 + }, 495 + ], 496 + }), 497 + description: 'generate nested classes with auto class structure', 498 + }, 499 + { 500 + config: createConfig({ 501 + input: 'sdk-nested-classes.yaml', 502 + output: 'sdk-nested-classes-instance', 503 + plugins: [ 504 + '@hey-api/client-fetch', 505 + { 506 + asClass: true, 507 + classStructure: 'auto', 508 + instance: 'NestedSdkWithInstance', 509 + name: '@hey-api/sdk', 510 + }, 511 + ], 512 + }), 513 + description: 'generate nested classes with auto class structure', 514 + }, 484 515 ]; 485 516 486 517 it.each(scenarios)('$description', async ({ config }) => {
+72
packages/openapi-ts-tests/specs/2.0.x/sdk-nested-classes.yaml
··· 1 + swagger: '2.0' 2 + info: 3 + title: OpenAPI 2.0 sdk nested classes example 4 + version: 1.0.0 5 + description: Test schema for nested class generation with various operationId patterns 6 + host: api.example.com 7 + basePath: /v1 8 + schemes: 9 + - https 10 + paths: 11 + /business/providers/domains: 12 + get: 13 + tags: 14 + - business 15 + - providers 16 + - domains 17 + operationId: business.providers.domains.get 18 + produces: 19 + - '*/*' 20 + responses: 21 + '200': 22 + description: OK 23 + schema: 24 + type: string 25 + post: 26 + tags: 27 + - providers 28 + - domains 29 + - business 30 + operationId: business.providers.domains.post 31 + produces: 32 + - '*/*' 33 + responses: 34 + '200': 35 + description: OK 36 + schema: 37 + type: string 38 + put: 39 + tags: 40 + - domains 41 + produces: 42 + - '*/*' 43 + responses: 44 + '200': 45 + description: OK 46 + schema: 47 + type: string 48 + /locations/businesses: 49 + get: 50 + tags: 51 + - locations 52 + - business 53 + operationId: business.get 54 + produces: 55 + - '*/*' 56 + responses: 57 + '200': 58 + description: OK 59 + schema: 60 + type: string 61 + /locations: 62 + get: 63 + tags: 64 + - locations 65 + operationId: get 66 + produces: 67 + - '*/*' 68 + responses: 69 + '200': 70 + description: OK 71 + schema: 72 + type: string
+70
packages/openapi-ts-tests/specs/3.0.x/sdk-nested-classes.yaml
··· 1 + openapi: 3.0.3 2 + info: 3 + title: OpenAPI 3.0 sdk nested classes example 4 + version: 1.0.0 5 + description: Test schema for nested class generation with various operationId patterns 6 + servers: 7 + - url: https://api.example.com/v1 8 + paths: 9 + /business/providers/domains: 10 + get: 11 + tags: 12 + - business 13 + - providers 14 + - domains 15 + operationId: business.providers.domains.get 16 + responses: 17 + '200': 18 + description: OK 19 + content: 20 + '*/*': 21 + schema: 22 + type: string 23 + post: 24 + tags: 25 + - providers 26 + - domains 27 + - business 28 + operationId: business.providers.domains.post 29 + responses: 30 + '200': 31 + description: OK 32 + content: 33 + '*/*': 34 + schema: 35 + type: string 36 + put: 37 + tags: 38 + - domains 39 + responses: 40 + '200': 41 + description: OK 42 + content: 43 + '*/*': 44 + schema: 45 + type: string 46 + /locations/businesses: 47 + get: 48 + tags: 49 + - locations 50 + - business 51 + operationId: business.get 52 + responses: 53 + '200': 54 + description: OK 55 + content: 56 + '*/*': 57 + schema: 58 + type: string 59 + /locations: 60 + get: 61 + tags: 62 + - locations 63 + operationId: get 64 + responses: 65 + '200': 66 + description: OK 67 + content: 68 + '*/*': 69 + schema: 70 + type: string
+67
packages/openapi-ts-tests/specs/3.1.x/sdk-nested-classes.yaml
··· 1 + openapi: 3.1.1 2 + info: 3 + title: OpenAPI 3.1.1 sdk nested classes example 4 + version: 1 5 + paths: 6 + /business/providers/domains: 7 + get: 8 + tags: 9 + - business 10 + - providers 11 + - domains 12 + operationId: business.providers.domains.get 13 + responses: 14 + '200': 15 + content: 16 + '*/*': 17 + schema: 18 + type: string 19 + description: OK 20 + post: 21 + tags: 22 + - providers 23 + - domains 24 + - business 25 + operationId: business.providers.domains.post 26 + responses: 27 + '200': 28 + content: 29 + '*/*': 30 + schema: 31 + type: string 32 + description: OK 33 + put: 34 + tags: 35 + - domains 36 + responses: 37 + '200': 38 + content: 39 + '*/*': 40 + schema: 41 + type: string 42 + description: OK 43 + /locations/businesses: 44 + get: 45 + tags: 46 + - locations 47 + - business 48 + operationId: business.get 49 + responses: 50 + '200': 51 + content: 52 + '*/*': 53 + schema: 54 + type: string 55 + description: OK 56 + /locations: 57 + get: 58 + tags: 59 + - locations 60 + operationId: get 61 + responses: 62 + '200': 63 + content: 64 + '*/*': 65 + schema: 66 + type: string 67 + description: OK
+14 -4
packages/openapi-ts/src/plugins/@hey-api/sdk/operation.ts
··· 124 124 context, 125 125 value: className || rootClass, 126 126 }); 127 + 128 + // Default path 129 + let path = [rootClass]; 130 + if (className) { 131 + // If root class is already within classCandidates or the same as className 132 + // do not add it again as this will cause a recursion issue. 133 + if (classCandidates.includes(rootClass) || rootClass === className) { 134 + path = [...classCandidates, className]; 135 + } else { 136 + path = [rootClass, ...classCandidates, className]; 137 + } 138 + } 139 + 127 140 classNames.set(rootClass, { 128 141 className: finalClassName, 129 142 methodName: methodName || getOperationMethodName({ operation, plugin }), 130 - path: (className 131 - ? [rootClass, ...classCandidates, className] 132 - : [rootClass] 133 - ).map((value) => 143 + path: path.map((value) => 134 144 operationClassName({ 135 145 context, 136 146 value,