@hey-api/openapi-ts 0.84.0 → 0.84.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,272 @@
1
+ import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch';
2
+
3
+ import { createSseClient } from '../core/serverSentEvents';
4
+ import type { HttpMethod } from '../core/types';
5
+ import { getValidRequestBody } from '../core/utils';
6
+ import type {
7
+ Client,
8
+ Config,
9
+ RequestOptions,
10
+ ResolvedRequestOptions,
11
+ } from './types';
12
+ import {
13
+ buildOfetchOptions,
14
+ buildUrl,
15
+ createConfig,
16
+ createInterceptors,
17
+ isRepeatableBody,
18
+ mapParseAsToResponseType,
19
+ mergeConfigs,
20
+ mergeHeaders,
21
+ parseError,
22
+ parseSuccess,
23
+ setAuthParams,
24
+ wrapDataReturn,
25
+ wrapErrorReturn,
26
+ } from './utils';
27
+
28
+ type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
29
+ body?: BodyInit | null | undefined;
30
+ headers: ReturnType<typeof mergeHeaders>;
31
+ };
32
+
33
+ export const createClient = (config: Config = {}): Client => {
34
+ let _config = mergeConfigs(createConfig(), config);
35
+
36
+ const getConfig = (): Config => ({ ..._config });
37
+
38
+ const setConfig = (config: Config): Config => {
39
+ _config = mergeConfigs(_config, config);
40
+ return getConfig();
41
+ };
42
+
43
+ const interceptors = createInterceptors<
44
+ Request,
45
+ Response,
46
+ unknown,
47
+ ResolvedRequestOptions
48
+ >();
49
+
50
+ // precompute serialized / network body
51
+ const resolveOptions = async (options: RequestOptions) => {
52
+ const opts = {
53
+ ..._config,
54
+ ...options,
55
+ headers: mergeHeaders(_config.headers, options.headers),
56
+ serializedBody: undefined,
57
+ };
58
+
59
+ if (opts.security) {
60
+ await setAuthParams({
61
+ ...opts,
62
+ security: opts.security,
63
+ });
64
+ }
65
+
66
+ if (opts.requestValidator) {
67
+ await opts.requestValidator(opts);
68
+ }
69
+
70
+ if (opts.body !== undefined && opts.bodySerializer) {
71
+ opts.serializedBody = opts.bodySerializer(opts.body);
72
+ }
73
+
74
+ // remove Content-Type if body is empty to avoid invalid requests
75
+ if (opts.body === undefined || opts.serializedBody === '') {
76
+ opts.headers.delete('Content-Type');
77
+ }
78
+
79
+ // if a raw body is provided (no serializer), adjust Content-Type only when it
80
+ // equals the default JSON value to better match the concrete body type
81
+ if (
82
+ opts.body !== undefined &&
83
+ opts.bodySerializer === null &&
84
+ (opts.headers.get('Content-Type') || '').toLowerCase() ===
85
+ 'application/json'
86
+ ) {
87
+ const b: unknown = opts.body;
88
+ if (typeof FormData !== 'undefined' && b instanceof FormData) {
89
+ // let the runtime set the multipart boundary
90
+ opts.headers.delete('Content-Type');
91
+ } else if (
92
+ typeof URLSearchParams !== 'undefined' &&
93
+ b instanceof URLSearchParams
94
+ ) {
95
+ // standard urlencoded content type (+ charset)
96
+ opts.headers.set(
97
+ 'Content-Type',
98
+ 'application/x-www-form-urlencoded;charset=UTF-8',
99
+ );
100
+ } else if (typeof Blob !== 'undefined' && b instanceof Blob) {
101
+ const t = b.type?.trim();
102
+ if (t) {
103
+ opts.headers.set('Content-Type', t);
104
+ } else {
105
+ // unknown blob type: avoid sending a misleading JSON header
106
+ opts.headers.delete('Content-Type');
107
+ }
108
+ }
109
+ }
110
+
111
+ // precompute network body (stability for retries and interceptors)
112
+ const networkBody = getValidRequestBody(opts) as
113
+ | RequestInit['body']
114
+ | null
115
+ | undefined;
116
+
117
+ const url = buildUrl(opts);
118
+
119
+ return { networkBody, opts, url };
120
+ };
121
+
122
+ // apply request interceptors and mirror header/method/signal back to opts
123
+ const applyRequestInterceptors = async (
124
+ request: Request,
125
+ opts: ResolvedRequestOptions,
126
+ ) => {
127
+ for (const fn of interceptors.request.fns) {
128
+ if (fn) {
129
+ request = await fn(request, opts);
130
+ }
131
+ }
132
+ // reflect interceptor changes into opts used by the network layer
133
+ opts.headers = request.headers;
134
+ opts.method = request.method as Uppercase<HttpMethod>;
135
+ // ignore request.body changes to avoid turning serialized bodies into streams
136
+ // body comes only from getValidRequestBody(options)
137
+ // reflect signal if present
138
+ opts.signal = (request as any).signal as AbortSignal | undefined;
139
+ return request;
140
+ };
141
+
142
+ // build ofetch options with stable retry logic based on body repeatability
143
+ const buildNetworkOptions = (
144
+ opts: ResolvedRequestOptions,
145
+ body: BodyInit | null | undefined,
146
+ responseType: OfetchResponseType | undefined,
147
+ ) => {
148
+ const effectiveRetry = isRepeatableBody(body)
149
+ ? (opts.retry as any)
150
+ : (0 as any);
151
+ return buildOfetchOptions(opts, body, responseType, effectiveRetry);
152
+ };
153
+
154
+ const request: Client['request'] = async (options) => {
155
+ const {
156
+ networkBody: initialNetworkBody,
157
+ opts,
158
+ url,
159
+ } = await resolveOptions(options as any);
160
+ // map parseAs -> ofetch responseType once per request
161
+ const ofetchResponseType: OfetchResponseType | undefined =
162
+ mapParseAsToResponseType(opts.parseAs, opts.responseType);
163
+
164
+ const $ofetch = opts.ofetch ?? ofetch;
165
+
166
+ // create Request before network to run middleware consistently
167
+ const networkBody = initialNetworkBody;
168
+ const requestInit: ReqInit = {
169
+ body: networkBody,
170
+ headers: opts.headers as Headers,
171
+ method: opts.method,
172
+ redirect: 'follow',
173
+ signal: opts.signal,
174
+ };
175
+ let request = new Request(url, requestInit);
176
+
177
+ request = await applyRequestInterceptors(request, opts);
178
+ const finalUrl = request.url;
179
+
180
+ // build ofetch options and perform the request (.raw keeps the Response)
181
+ const responseOptions = buildNetworkOptions(
182
+ opts as ResolvedRequestOptions,
183
+ networkBody,
184
+ ofetchResponseType,
185
+ );
186
+
187
+ let response = await $ofetch.raw(finalUrl, responseOptions);
188
+
189
+ for (const fn of interceptors.response.fns) {
190
+ if (fn) {
191
+ response = await fn(response, request, opts);
192
+ }
193
+ }
194
+
195
+ const result = { request, response };
196
+
197
+ if (response.ok) {
198
+ const data = await parseSuccess(response, opts, ofetchResponseType);
199
+ return wrapDataReturn(data, result, opts.responseStyle);
200
+ }
201
+
202
+ let finalError = await parseError(response);
203
+
204
+ for (const fn of interceptors.error.fns) {
205
+ if (fn) {
206
+ finalError = await fn(finalError, response, request, opts);
207
+ }
208
+ }
209
+
210
+ // ensure error is never undefined after interceptors
211
+ finalError = (finalError as any) || ({} as string);
212
+
213
+ if (opts.throwOnError) {
214
+ throw finalError;
215
+ }
216
+
217
+ return wrapErrorReturn(finalError, result, opts.responseStyle) as any;
218
+ };
219
+
220
+ const makeMethodFn =
221
+ (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
222
+ request({ ...options, method } as any);
223
+
224
+ const makeSseFn =
225
+ (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
226
+ const { networkBody, opts, url } = await resolveOptions(options);
227
+ const optsForSse: any = { ...opts };
228
+ delete optsForSse.body; // body is provided via serializedBody below
229
+ return createSseClient({
230
+ ...optsForSse,
231
+ fetch: opts.fetch,
232
+ headers: opts.headers as Headers,
233
+ method,
234
+ onRequest: async (url, init) => {
235
+ let request = new Request(url, init);
236
+ request = await applyRequestInterceptors(request, opts);
237
+ return request;
238
+ },
239
+ serializedBody: networkBody as BodyInit | null | undefined,
240
+ signal: opts.signal,
241
+ url,
242
+ });
243
+ };
244
+
245
+ return {
246
+ buildUrl,
247
+ connect: makeMethodFn('CONNECT'),
248
+ delete: makeMethodFn('DELETE'),
249
+ get: makeMethodFn('GET'),
250
+ getConfig,
251
+ head: makeMethodFn('HEAD'),
252
+ interceptors,
253
+ options: makeMethodFn('OPTIONS'),
254
+ patch: makeMethodFn('PATCH'),
255
+ post: makeMethodFn('POST'),
256
+ put: makeMethodFn('PUT'),
257
+ request,
258
+ setConfig,
259
+ sse: {
260
+ connect: makeSseFn('CONNECT'),
261
+ delete: makeSseFn('DELETE'),
262
+ get: makeSseFn('GET'),
263
+ head: makeSseFn('HEAD'),
264
+ options: makeSseFn('OPTIONS'),
265
+ patch: makeSseFn('PATCH'),
266
+ post: makeSseFn('POST'),
267
+ put: makeSseFn('PUT'),
268
+ trace: makeSseFn('TRACE'),
269
+ },
270
+ trace: makeMethodFn('TRACE'),
271
+ } as Client;
272
+ };
@@ -0,0 +1,23 @@
1
+ export type { Auth } from '../core/auth';
2
+ export type { QuerySerializerOptions } from '../core/bodySerializer';
3
+ export {
4
+ formDataBodySerializer,
5
+ jsonBodySerializer,
6
+ urlSearchParamsBodySerializer,
7
+ } from '../core/bodySerializer';
8
+ export { buildClientParams } from '../core/params';
9
+ export { createClient } from './client';
10
+ export type {
11
+ Client,
12
+ ClientOptions,
13
+ Config,
14
+ CreateClientConfig,
15
+ Options,
16
+ OptionsLegacyParser,
17
+ RequestOptions,
18
+ RequestResult,
19
+ ResolvedRequestOptions,
20
+ ResponseStyle,
21
+ TDataShape,
22
+ } from './types';
23
+ export { createConfig, mergeHeaders } from './utils';
@@ -0,0 +1,333 @@
1
+ import type {
2
+ FetchOptions as OfetchOptions,
3
+ ResponseType as OfetchResponseType,
4
+ } from 'ofetch';
5
+ import type { ofetch } from 'ofetch';
6
+
7
+ import type { Auth } from '../core/auth';
8
+ import type {
9
+ ServerSentEventsOptions,
10
+ ServerSentEventsResult,
11
+ } from '../core/serverSentEvents';
12
+ import type {
13
+ Client as CoreClient,
14
+ Config as CoreConfig,
15
+ } from '../core/types';
16
+ import type { Middleware } from './utils';
17
+
18
+ export type ResponseStyle = 'data' | 'fields';
19
+
20
+ export interface Config<T extends ClientOptions = ClientOptions>
21
+ extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
22
+ CoreConfig {
23
+ /**
24
+ * HTTP(S) agent configuration (Node.js only). Passed through to ofetch.
25
+ */
26
+ agent?: OfetchOptions['agent'];
27
+ /**
28
+ * Base URL for all requests made by this client.
29
+ */
30
+ baseUrl?: T['baseUrl'];
31
+ /**
32
+ * Node-only proxy/agent options.
33
+ */
34
+ dispatcher?: OfetchOptions['dispatcher'];
35
+ /**
36
+ * Fetch API implementation. Used for SSE streaming. You can use this option
37
+ * to provide a custom fetch instance.
38
+ *
39
+ * @default globalThis.fetch
40
+ */
41
+ fetch?: typeof fetch;
42
+ /**
43
+ * Controls the native ofetch behaviour that throws `FetchError` when
44
+ * `response.ok === false`. We default to suppressing it to match the fetch
45
+ * client semantics and let `throwOnError` drive the outcome.
46
+ */
47
+ ignoreResponseError?: OfetchOptions['ignoreResponseError'];
48
+ // No custom fetch option: provide custom instance via `ofetch` instead
49
+ /**
50
+ * Please don't use the Fetch client for Next.js applications. The `next`
51
+ * options won't have any effect.
52
+ *
53
+ * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
54
+ */
55
+ next?: never;
56
+ /**
57
+ * Custom ofetch instance created via `ofetch.create()`. If provided, it will
58
+ * be used for requests instead of the default `ofetch` export.
59
+ */
60
+ ofetch?: typeof ofetch;
61
+ /**
62
+ * ofetch hook called before a request is sent.
63
+ */
64
+ onRequest?: OfetchOptions['onRequest'];
65
+ /**
66
+ * ofetch hook called when a request fails before receiving a response
67
+ * (e.g., network errors or aborted requests).
68
+ */
69
+ onRequestError?: OfetchOptions['onRequestError'];
70
+ /**
71
+ * ofetch hook called after a successful response is received and parsed.
72
+ */
73
+ onResponse?: OfetchOptions['onResponse'];
74
+ /**
75
+ * ofetch hook called when the response indicates an error (non-ok status)
76
+ * or when response parsing fails.
77
+ */
78
+ onResponseError?: OfetchOptions['onResponseError'];
79
+ /**
80
+ * Return the response data parsed in a specified format. By default, `auto`
81
+ * will infer the appropriate method from the `Content-Type` response header.
82
+ * You can override this behavior with any of the {@link Body} methods.
83
+ * Select `stream` if you don't want to parse response data at all.
84
+ *
85
+ * @default 'auto'
86
+ */
87
+ parseAs?:
88
+ | 'arrayBuffer'
89
+ | 'auto'
90
+ | 'blob'
91
+ | 'formData'
92
+ | 'json'
93
+ | 'stream'
94
+ | 'text';
95
+ /** Custom response parser (ofetch). */
96
+ parseResponse?: OfetchOptions['parseResponse'];
97
+ /**
98
+ * Should we return only data or multiple fields (data, error, response, etc.)?
99
+ *
100
+ * @default 'fields'
101
+ */
102
+ responseStyle?: ResponseStyle;
103
+ /**
104
+ * ofetch responseType override. If provided, it will be passed directly to
105
+ * ofetch and take precedence over `parseAs`.
106
+ */
107
+ responseType?: OfetchResponseType;
108
+ /**
109
+ * Automatically retry failed requests.
110
+ */
111
+ retry?: OfetchOptions['retry'];
112
+ /**
113
+ * Delay (in ms) between retry attempts.
114
+ */
115
+ retryDelay?: OfetchOptions['retryDelay'];
116
+ /**
117
+ * HTTP status codes that should trigger a retry.
118
+ */
119
+ retryStatusCodes?: OfetchOptions['retryStatusCodes'];
120
+ /**
121
+ * Throw an error instead of returning it in the response?
122
+ *
123
+ * @default false
124
+ */
125
+ throwOnError?: T['throwOnError'];
126
+ /**
127
+ * Abort the request after the given milliseconds.
128
+ */
129
+ timeout?: number;
130
+ }
131
+
132
+ export interface RequestOptions<
133
+ TData = unknown,
134
+ TResponseStyle extends ResponseStyle = 'fields',
135
+ ThrowOnError extends boolean = boolean,
136
+ Url extends string = string,
137
+ > extends Config<{
138
+ responseStyle: TResponseStyle;
139
+ throwOnError: ThrowOnError;
140
+ }>,
141
+ Pick<
142
+ ServerSentEventsOptions<TData>,
143
+ | 'onSseError'
144
+ | 'onSseEvent'
145
+ | 'sseDefaultRetryDelay'
146
+ | 'sseMaxRetryAttempts'
147
+ | 'sseMaxRetryDelay'
148
+ > {
149
+ /**
150
+ * Any body that you want to add to your request.
151
+ *
152
+ * {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
153
+ */
154
+ body?: unknown;
155
+ path?: Record<string, unknown>;
156
+ query?: Record<string, unknown>;
157
+ /**
158
+ * Security mechanism(s) to use for the request.
159
+ */
160
+ security?: ReadonlyArray<Auth>;
161
+ url: Url;
162
+ }
163
+
164
+ export interface ResolvedRequestOptions<
165
+ TResponseStyle extends ResponseStyle = 'fields',
166
+ ThrowOnError extends boolean = boolean,
167
+ Url extends string = string,
168
+ > extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
169
+ serializedBody?: string;
170
+ }
171
+
172
+ export type RequestResult<
173
+ TData = unknown,
174
+ TError = unknown,
175
+ ThrowOnError extends boolean = boolean,
176
+ TResponseStyle extends ResponseStyle = 'fields',
177
+ > = ThrowOnError extends true
178
+ ? Promise<
179
+ TResponseStyle extends 'data'
180
+ ? TData extends Record<string, unknown>
181
+ ? TData[keyof TData]
182
+ : TData
183
+ : {
184
+ data: TData extends Record<string, unknown>
185
+ ? TData[keyof TData]
186
+ : TData;
187
+ request: Request;
188
+ response: Response;
189
+ }
190
+ >
191
+ : Promise<
192
+ TResponseStyle extends 'data'
193
+ ?
194
+ | (TData extends Record<string, unknown>
195
+ ? TData[keyof TData]
196
+ : TData)
197
+ | undefined
198
+ : (
199
+ | {
200
+ data: TData extends Record<string, unknown>
201
+ ? TData[keyof TData]
202
+ : TData;
203
+ error: undefined;
204
+ }
205
+ | {
206
+ data: undefined;
207
+ error: TError extends Record<string, unknown>
208
+ ? TError[keyof TError]
209
+ : TError;
210
+ }
211
+ ) & {
212
+ request: Request;
213
+ response: Response;
214
+ }
215
+ >;
216
+
217
+ export interface ClientOptions {
218
+ baseUrl?: string;
219
+ responseStyle?: ResponseStyle;
220
+ throwOnError?: boolean;
221
+ }
222
+
223
+ type MethodFn = <
224
+ TData = unknown,
225
+ TError = unknown,
226
+ ThrowOnError extends boolean = false,
227
+ TResponseStyle extends ResponseStyle = 'fields',
228
+ >(
229
+ options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
230
+ ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
231
+
232
+ type SseFn = <
233
+ TData = unknown,
234
+ TError = unknown,
235
+ ThrowOnError extends boolean = false,
236
+ TResponseStyle extends ResponseStyle = 'fields',
237
+ >(
238
+ options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
239
+ ) => Promise<ServerSentEventsResult<TData, TError>>;
240
+
241
+ type RequestFn = <
242
+ TData = unknown,
243
+ TError = unknown,
244
+ ThrowOnError extends boolean = false,
245
+ TResponseStyle extends ResponseStyle = 'fields',
246
+ >(
247
+ options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
248
+ Pick<
249
+ Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>,
250
+ 'method'
251
+ >,
252
+ ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
253
+
254
+ type BuildUrlFn = <
255
+ TData extends {
256
+ body?: unknown;
257
+ path?: Record<string, unknown>;
258
+ query?: Record<string, unknown>;
259
+ url: string;
260
+ },
261
+ >(
262
+ options: Pick<TData, 'url'> & Options<TData>,
263
+ ) => string;
264
+
265
+ export type Client = CoreClient<
266
+ RequestFn,
267
+ Config,
268
+ MethodFn,
269
+ BuildUrlFn,
270
+ SseFn
271
+ > & {
272
+ interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
273
+ };
274
+
275
+ /**
276
+ * The `createClientConfig()` function will be called on client initialization
277
+ * and the returned object will become the client's initial configuration.
278
+ *
279
+ * You may want to initialize your client this way instead of calling
280
+ * `setConfig()`. This is useful for example if you're using Next.js
281
+ * to ensure your client always has the correct values.
282
+ */
283
+ export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
284
+ override?: Config<ClientOptions & T>,
285
+ ) => Config<Required<ClientOptions> & T>;
286
+
287
+ export interface TDataShape {
288
+ body?: unknown;
289
+ headers?: unknown;
290
+ path?: unknown;
291
+ query?: unknown;
292
+ url: string;
293
+ }
294
+
295
+ type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
296
+
297
+ export type Options<
298
+ TData extends TDataShape = TDataShape,
299
+ ThrowOnError extends boolean = boolean,
300
+ TResponse = unknown,
301
+ TResponseStyle extends ResponseStyle = 'fields',
302
+ > = OmitKeys<
303
+ RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
304
+ 'body' | 'path' | 'query' | 'url'
305
+ > &
306
+ Omit<TData, 'url'>;
307
+
308
+ export type OptionsLegacyParser<
309
+ TData = unknown,
310
+ ThrowOnError extends boolean = boolean,
311
+ TResponseStyle extends ResponseStyle = 'fields',
312
+ > = TData extends { body?: any }
313
+ ? TData extends { headers?: any }
314
+ ? OmitKeys<
315
+ RequestOptions<unknown, TResponseStyle, ThrowOnError>,
316
+ 'body' | 'headers' | 'url'
317
+ > &
318
+ TData
319
+ : OmitKeys<
320
+ RequestOptions<unknown, TResponseStyle, ThrowOnError>,
321
+ 'body' | 'url'
322
+ > &
323
+ TData &
324
+ Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'headers'>
325
+ : TData extends { headers?: any }
326
+ ? OmitKeys<
327
+ RequestOptions<unknown, TResponseStyle, ThrowOnError>,
328
+ 'headers' | 'url'
329
+ > &
330
+ TData &
331
+ Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'body'>
332
+ : OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'url'> &
333
+ TData;