@hey-api/openapi-ts 0.0.0-next-20260205083026
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.
- package/LICENSE.md +21 -0
- package/README.md +381 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +18 -0
- package/dist/clients/angular/client.ts +237 -0
- package/dist/clients/angular/index.ts +23 -0
- package/dist/clients/angular/types.ts +231 -0
- package/dist/clients/angular/utils.ts +408 -0
- package/dist/clients/axios/client.ts +154 -0
- package/dist/clients/axios/index.ts +21 -0
- package/dist/clients/axios/types.ts +158 -0
- package/dist/clients/axios/utils.ts +206 -0
- package/dist/clients/core/auth.ts +39 -0
- package/dist/clients/core/bodySerializer.ts +82 -0
- package/dist/clients/core/params.ts +167 -0
- package/dist/clients/core/pathSerializer.ts +169 -0
- package/dist/clients/core/queryKeySerializer.ts +115 -0
- package/dist/clients/core/serverSentEvents.ts +241 -0
- package/dist/clients/core/types.ts +102 -0
- package/dist/clients/core/utils.ts +138 -0
- package/dist/clients/fetch/client.ts +286 -0
- package/dist/clients/fetch/index.ts +23 -0
- package/dist/clients/fetch/types.ts +211 -0
- package/dist/clients/fetch/utils.ts +314 -0
- package/dist/clients/ky/client.ts +318 -0
- package/dist/clients/ky/index.ts +24 -0
- package/dist/clients/ky/types.ts +243 -0
- package/dist/clients/ky/utils.ts +312 -0
- package/dist/clients/next/client.ts +253 -0
- package/dist/clients/next/index.ts +21 -0
- package/dist/clients/next/types.ts +162 -0
- package/dist/clients/next/utils.ts +413 -0
- package/dist/clients/nuxt/client.ts +213 -0
- package/dist/clients/nuxt/index.ts +22 -0
- package/dist/clients/nuxt/types.ts +189 -0
- package/dist/clients/nuxt/utils.ts +384 -0
- package/dist/clients/ofetch/client.ts +259 -0
- package/dist/clients/ofetch/index.ts +23 -0
- package/dist/clients/ofetch/types.ts +275 -0
- package/dist/clients/ofetch/utils.ts +504 -0
- package/dist/index.d.mts +8634 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/init-DlaW5Djq.mjs +13832 -0
- package/dist/init-DlaW5Djq.mjs.map +1 -0
- package/dist/internal.d.mts +33 -0
- package/dist/internal.d.mts.map +1 -0
- package/dist/internal.mjs +4 -0
- package/dist/run.d.mts +1 -0
- package/dist/run.mjs +60 -0
- package/dist/run.mjs.map +1 -0
- package/dist/src-BYA2YioO.mjs +225 -0
- package/dist/src-BYA2YioO.mjs.map +1 -0
- package/dist/types-Ba27ofyy.d.mts +157 -0
- package/dist/types-Ba27ofyy.d.mts.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
|
+
import { isRef, toValue, unref } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { getAuthToken } from '../core/auth';
|
|
5
|
+
import type { QuerySerializerOptions } from '../core/bodySerializer';
|
|
6
|
+
import { jsonBodySerializer } from '../core/bodySerializer';
|
|
7
|
+
import {
|
|
8
|
+
serializeArrayParam,
|
|
9
|
+
serializeObjectParam,
|
|
10
|
+
serializePrimitiveParam,
|
|
11
|
+
} from '../core/pathSerializer';
|
|
12
|
+
import type {
|
|
13
|
+
ArraySeparatorStyle,
|
|
14
|
+
BuildUrlOptions,
|
|
15
|
+
Client,
|
|
16
|
+
ClientOptions,
|
|
17
|
+
Config,
|
|
18
|
+
QuerySerializer,
|
|
19
|
+
RequestOptions,
|
|
20
|
+
} from './types';
|
|
21
|
+
|
|
22
|
+
type PathSerializer = Pick<Required<BuildUrlOptions>, 'path' | 'url'>;
|
|
23
|
+
|
|
24
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
25
|
+
|
|
26
|
+
type MaybeArray<T> = T | T[];
|
|
27
|
+
|
|
28
|
+
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
|
29
|
+
let url = _url;
|
|
30
|
+
const matches = _url.match(PATH_PARAM_RE);
|
|
31
|
+
if (matches) {
|
|
32
|
+
for (const match of matches) {
|
|
33
|
+
let explode = false;
|
|
34
|
+
let name = match.substring(1, match.length - 1);
|
|
35
|
+
let style: ArraySeparatorStyle = 'simple';
|
|
36
|
+
|
|
37
|
+
if (name.endsWith('*')) {
|
|
38
|
+
explode = true;
|
|
39
|
+
name = name.substring(0, name.length - 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (name.startsWith('.')) {
|
|
43
|
+
name = name.substring(1);
|
|
44
|
+
style = 'label';
|
|
45
|
+
} else if (name.startsWith(';')) {
|
|
46
|
+
name = name.substring(1);
|
|
47
|
+
style = 'matrix';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const value = toValue((toValue(path) as Record<string, unknown> | undefined)?.[name]);
|
|
51
|
+
|
|
52
|
+
if (value === undefined || value === null) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof value === 'object') {
|
|
62
|
+
url = url.replace(
|
|
63
|
+
match,
|
|
64
|
+
serializeObjectParam({
|
|
65
|
+
explode,
|
|
66
|
+
name,
|
|
67
|
+
style,
|
|
68
|
+
value: value as Record<string, unknown>,
|
|
69
|
+
valueOnly: true,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (style === 'matrix') {
|
|
76
|
+
url = url.replace(
|
|
77
|
+
match,
|
|
78
|
+
`;${serializePrimitiveParam({
|
|
79
|
+
name,
|
|
80
|
+
value: value as string,
|
|
81
|
+
})}`,
|
|
82
|
+
);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const replaceValue = encodeURIComponent(
|
|
87
|
+
style === 'label' ? `.${value as string}` : (value as string),
|
|
88
|
+
);
|
|
89
|
+
url = url.replace(match, replaceValue);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return url;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const createQuerySerializer = <T = unknown>({
|
|
96
|
+
parameters = {},
|
|
97
|
+
...args
|
|
98
|
+
}: QuerySerializerOptions = {}) => {
|
|
99
|
+
const querySerializer = (queryParams: T) => {
|
|
100
|
+
const search: string[] = [];
|
|
101
|
+
const qParams = toValue(queryParams);
|
|
102
|
+
if (qParams && typeof qParams === 'object') {
|
|
103
|
+
for (const name in qParams) {
|
|
104
|
+
const value = toValue(qParams[name]);
|
|
105
|
+
|
|
106
|
+
if (value === undefined || value === null) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const options = parameters[name] || args;
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
const serializedArray = serializeArrayParam({
|
|
114
|
+
allowReserved: options.allowReserved,
|
|
115
|
+
explode: true,
|
|
116
|
+
name,
|
|
117
|
+
style: 'form',
|
|
118
|
+
value,
|
|
119
|
+
...options.array,
|
|
120
|
+
});
|
|
121
|
+
if (serializedArray) search.push(serializedArray);
|
|
122
|
+
} else if (typeof value === 'object') {
|
|
123
|
+
const serializedObject = serializeObjectParam({
|
|
124
|
+
allowReserved: options.allowReserved,
|
|
125
|
+
explode: true,
|
|
126
|
+
name,
|
|
127
|
+
style: 'deepObject',
|
|
128
|
+
value: value as Record<string, unknown>,
|
|
129
|
+
...options.object,
|
|
130
|
+
});
|
|
131
|
+
if (serializedObject) search.push(serializedObject);
|
|
132
|
+
} else {
|
|
133
|
+
const serializedPrimitive = serializePrimitiveParam({
|
|
134
|
+
allowReserved: options.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
|
+
const checkForExistence = (
|
|
148
|
+
options: Pick<RequestOptions, 'auth' | 'query'> & {
|
|
149
|
+
headers: Headers;
|
|
150
|
+
},
|
|
151
|
+
name?: string,
|
|
152
|
+
): boolean => {
|
|
153
|
+
if (!name) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
options.headers.has(name) ||
|
|
158
|
+
(toValue(options.query) as Record<string, unknown> | undefined)?.[name] ||
|
|
159
|
+
options.headers.get('Cookie')?.includes(`${name}=`)
|
|
160
|
+
) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const setAuthParams = async ({
|
|
167
|
+
security,
|
|
168
|
+
...options
|
|
169
|
+
}: Pick<Required<RequestOptions>, 'security'> &
|
|
170
|
+
Pick<RequestOptions, 'auth' | 'query'> & {
|
|
171
|
+
headers: Headers;
|
|
172
|
+
}) => {
|
|
173
|
+
for (const auth of security) {
|
|
174
|
+
if (checkForExistence(options, auth.name)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const token = await getAuthToken(auth, options.auth);
|
|
178
|
+
|
|
179
|
+
if (!token) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const name = auth.name ?? 'Authorization';
|
|
184
|
+
|
|
185
|
+
switch (auth.in) {
|
|
186
|
+
case 'query': {
|
|
187
|
+
if (!options.query) {
|
|
188
|
+
options.query = {};
|
|
189
|
+
}
|
|
190
|
+
const queryValue = toValue(options.query) as Record<string, unknown> | undefined;
|
|
191
|
+
if (queryValue) {
|
|
192
|
+
queryValue[name] = token;
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case 'cookie':
|
|
197
|
+
options.headers.append('Cookie', `${name}=${token}`);
|
|
198
|
+
break;
|
|
199
|
+
case 'header':
|
|
200
|
+
default:
|
|
201
|
+
options.headers.set(name, token);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const buildUrl: Client['buildUrl'] = (options) => {
|
|
208
|
+
const url = getUrl({
|
|
209
|
+
baseUrl: options.baseURL as string,
|
|
210
|
+
path: options.path,
|
|
211
|
+
query: options.query,
|
|
212
|
+
querySerializer:
|
|
213
|
+
typeof options.querySerializer === 'function'
|
|
214
|
+
? options.querySerializer
|
|
215
|
+
: createQuerySerializer(options.querySerializer),
|
|
216
|
+
url: options.url,
|
|
217
|
+
});
|
|
218
|
+
return url;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const getUrl = ({
|
|
222
|
+
baseUrl,
|
|
223
|
+
path,
|
|
224
|
+
query,
|
|
225
|
+
querySerializer,
|
|
226
|
+
url: _url,
|
|
227
|
+
}: Pick<BuildUrlOptions, 'path' | 'query' | 'url'> & {
|
|
228
|
+
baseUrl?: string;
|
|
229
|
+
querySerializer: QuerySerializer;
|
|
230
|
+
}) => {
|
|
231
|
+
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
|
|
232
|
+
let url = (baseUrl ?? '') + pathUrl;
|
|
233
|
+
if (path) {
|
|
234
|
+
url = defaultPathSerializer({ path, url });
|
|
235
|
+
}
|
|
236
|
+
let search = query ? querySerializer(query) : '';
|
|
237
|
+
if (search.startsWith('?')) {
|
|
238
|
+
search = search.substring(1);
|
|
239
|
+
}
|
|
240
|
+
if (search) {
|
|
241
|
+
url += `?${search}`;
|
|
242
|
+
}
|
|
243
|
+
return url;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export const mergeConfigs = (a: Config, b: Config): Config => {
|
|
247
|
+
const config = { ...a, ...b };
|
|
248
|
+
if (config.baseURL?.endsWith('/')) {
|
|
249
|
+
config.baseURL = config.baseURL.substring(0, config.baseURL.length - 1);
|
|
250
|
+
}
|
|
251
|
+
config.headers = mergeHeaders(a.headers, b.headers);
|
|
252
|
+
return config;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const headersEntries = (headers: Headers): Array<[string, string]> => {
|
|
256
|
+
const entries: Array<[string, string]> = [];
|
|
257
|
+
headers.forEach((value, key) => {
|
|
258
|
+
entries.push([key, value]);
|
|
259
|
+
});
|
|
260
|
+
return entries;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const mergeHeaders = (
|
|
264
|
+
...headers: Array<Required<Config>['headers'] | undefined>
|
|
265
|
+
): Headers => {
|
|
266
|
+
const mergedHeaders = new Headers();
|
|
267
|
+
for (const header of headers) {
|
|
268
|
+
if (!header || typeof header !== 'object') {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let h: unknown = header;
|
|
273
|
+
if (isRef(h)) {
|
|
274
|
+
h = unref(h);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const iterator =
|
|
278
|
+
h instanceof Headers ? headersEntries(h) : Object.entries(h as Record<string, unknown>);
|
|
279
|
+
|
|
280
|
+
for (const [key, value] of iterator) {
|
|
281
|
+
if (value === null) {
|
|
282
|
+
mergedHeaders.delete(key);
|
|
283
|
+
} else if (Array.isArray(value)) {
|
|
284
|
+
for (const v of value) {
|
|
285
|
+
mergedHeaders.append(key, unwrapRefs(v) as string);
|
|
286
|
+
}
|
|
287
|
+
} else if (value !== undefined) {
|
|
288
|
+
const v = unwrapRefs(value);
|
|
289
|
+
// assume object headers are meant to be JSON stringified, i.e. their
|
|
290
|
+
// content value in OpenAPI specification is 'application/json'
|
|
291
|
+
mergedHeaders.set(key, typeof v === 'object' ? JSON.stringify(v) : (v as string));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return mergedHeaders;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const mergeInterceptors = <T>(...args: Array<MaybeArray<T>>): Array<T> =>
|
|
299
|
+
args.reduce<Array<T>>((acc, item) => {
|
|
300
|
+
if (typeof item === 'function') {
|
|
301
|
+
acc.push(item);
|
|
302
|
+
} else if (Array.isArray(item)) {
|
|
303
|
+
return acc.concat(item);
|
|
304
|
+
}
|
|
305
|
+
return acc;
|
|
306
|
+
}, []);
|
|
307
|
+
|
|
308
|
+
const defaultQuerySerializer = createQuerySerializer({
|
|
309
|
+
allowReserved: false,
|
|
310
|
+
array: {
|
|
311
|
+
explode: true,
|
|
312
|
+
style: 'form',
|
|
313
|
+
},
|
|
314
|
+
object: {
|
|
315
|
+
explode: true,
|
|
316
|
+
style: 'deepObject',
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const defaultHeaders = {
|
|
321
|
+
'Content-Type': 'application/json',
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const createConfig = <T extends ClientOptions = ClientOptions>(
|
|
325
|
+
override: Config<Omit<ClientOptions, keyof T> & T> = {},
|
|
326
|
+
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
|
327
|
+
...jsonBodySerializer,
|
|
328
|
+
headers: defaultHeaders,
|
|
329
|
+
querySerializer: defaultQuerySerializer,
|
|
330
|
+
...override,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
type UnwrapRefs<T> =
|
|
334
|
+
T extends Ref<infer V>
|
|
335
|
+
? V
|
|
336
|
+
: T extends ComputedRef<infer V>
|
|
337
|
+
? V
|
|
338
|
+
: T extends Record<string, unknown> // this doesn't handle functions well
|
|
339
|
+
? { [K in keyof T]: UnwrapRefs<T[K]> }
|
|
340
|
+
: T;
|
|
341
|
+
|
|
342
|
+
export const unwrapRefs = <T>(value: T): UnwrapRefs<T> => {
|
|
343
|
+
if (value === null || typeof value !== 'object' || value instanceof Headers) {
|
|
344
|
+
return (isRef(value) ? unref(value) : value) as UnwrapRefs<T>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (Array.isArray(value)) {
|
|
348
|
+
return value.map((item) => unwrapRefs(item)) as UnwrapRefs<T>;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (isRef(value)) {
|
|
352
|
+
return unwrapRefs(unref(value) as T);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// unwrap into new object to avoid modifying the source
|
|
356
|
+
const result: Record<string, unknown> = {};
|
|
357
|
+
for (const key in value) {
|
|
358
|
+
result[key] = unwrapRefs(value[key] as T);
|
|
359
|
+
}
|
|
360
|
+
return result as UnwrapRefs<T>;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export const serializeBody = (
|
|
364
|
+
opts: Pick<Parameters<Client['request']>[0], 'body' | 'bodySerializer'>,
|
|
365
|
+
) => {
|
|
366
|
+
if (opts.body && opts.bodySerializer) {
|
|
367
|
+
return opts.bodySerializer(opts.body);
|
|
368
|
+
}
|
|
369
|
+
return opts.body;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export const executeFetchFn = (
|
|
373
|
+
opts: Omit<Parameters<Client['request']>[0], 'composable'>,
|
|
374
|
+
fetchFn: Required<Config>['$fetch'],
|
|
375
|
+
) => {
|
|
376
|
+
const unwrappedOpts = unwrapRefs(opts);
|
|
377
|
+
unwrappedOpts.rawBody = unwrappedOpts.body;
|
|
378
|
+
unwrappedOpts.body = serializeBody(unwrappedOpts);
|
|
379
|
+
return fetchFn(
|
|
380
|
+
buildUrl(opts),
|
|
381
|
+
// @ts-expect-error
|
|
382
|
+
unwrappedOpts,
|
|
383
|
+
);
|
|
384
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
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 { Client, Config, RequestOptions, ResolvedRequestOptions } from './types';
|
|
7
|
+
import {
|
|
8
|
+
buildOfetchOptions,
|
|
9
|
+
buildUrl,
|
|
10
|
+
createConfig,
|
|
11
|
+
createInterceptors,
|
|
12
|
+
isRepeatableBody,
|
|
13
|
+
mapParseAsToResponseType,
|
|
14
|
+
mergeConfigs,
|
|
15
|
+
mergeHeaders,
|
|
16
|
+
parseError,
|
|
17
|
+
parseSuccess,
|
|
18
|
+
setAuthParams,
|
|
19
|
+
wrapDataReturn,
|
|
20
|
+
wrapErrorReturn,
|
|
21
|
+
} from './utils';
|
|
22
|
+
|
|
23
|
+
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
|
|
24
|
+
body?: BodyInit | null | undefined;
|
|
25
|
+
headers: ReturnType<typeof mergeHeaders>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const createClient = (config: Config = {}): Client => {
|
|
29
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
30
|
+
|
|
31
|
+
const getConfig = (): Config => ({ ..._config });
|
|
32
|
+
|
|
33
|
+
const setConfig = (config: Config): Config => {
|
|
34
|
+
_config = mergeConfigs(_config, config);
|
|
35
|
+
return getConfig();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
|
|
39
|
+
|
|
40
|
+
// precompute serialized / network body
|
|
41
|
+
const resolveOptions = async (options: RequestOptions) => {
|
|
42
|
+
const opts = {
|
|
43
|
+
..._config,
|
|
44
|
+
...options,
|
|
45
|
+
headers: mergeHeaders(_config.headers, options.headers),
|
|
46
|
+
serializedBody: undefined,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (opts.security) {
|
|
50
|
+
await setAuthParams({
|
|
51
|
+
...opts,
|
|
52
|
+
security: opts.security,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (opts.requestValidator) {
|
|
57
|
+
await opts.requestValidator(opts);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (opts.body !== undefined && opts.bodySerializer) {
|
|
61
|
+
opts.serializedBody = opts.bodySerializer(opts.body);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// remove Content-Type if body is empty to avoid invalid requests
|
|
65
|
+
if (opts.body === undefined || opts.serializedBody === '') {
|
|
66
|
+
opts.headers.delete('Content-Type');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// if a raw body is provided (no serializer), adjust Content-Type only when it
|
|
70
|
+
// equals the default JSON value to better match the concrete body type
|
|
71
|
+
if (
|
|
72
|
+
opts.body !== undefined &&
|
|
73
|
+
opts.bodySerializer === null &&
|
|
74
|
+
(opts.headers.get('Content-Type') || '').toLowerCase() === 'application/json'
|
|
75
|
+
) {
|
|
76
|
+
const b: unknown = opts.body;
|
|
77
|
+
if (typeof FormData !== 'undefined' && b instanceof FormData) {
|
|
78
|
+
// let the runtime set the multipart boundary
|
|
79
|
+
opts.headers.delete('Content-Type');
|
|
80
|
+
} else if (typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams) {
|
|
81
|
+
// standard urlencoded content type (+ charset)
|
|
82
|
+
opts.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
83
|
+
} else if (typeof Blob !== 'undefined' && b instanceof Blob) {
|
|
84
|
+
const t = b.type?.trim();
|
|
85
|
+
if (t) {
|
|
86
|
+
opts.headers.set('Content-Type', t);
|
|
87
|
+
} else {
|
|
88
|
+
// unknown blob type: avoid sending a misleading JSON header
|
|
89
|
+
opts.headers.delete('Content-Type');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// precompute network body (stability for retries and interceptors)
|
|
95
|
+
const networkBody = getValidRequestBody(opts) as RequestInit['body'] | null | undefined;
|
|
96
|
+
|
|
97
|
+
const url = buildUrl(opts);
|
|
98
|
+
|
|
99
|
+
return { networkBody, opts, url };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// apply request interceptors and mirror header/method/signal back to opts
|
|
103
|
+
const applyRequestInterceptors = async (
|
|
104
|
+
request: Request,
|
|
105
|
+
opts: ResolvedRequestOptions,
|
|
106
|
+
body: BodyInit | null | undefined,
|
|
107
|
+
) => {
|
|
108
|
+
for (const fn of interceptors.request.fns) {
|
|
109
|
+
if (fn) {
|
|
110
|
+
request = await fn(request, opts);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// reflect interceptor changes into opts used by the network layer
|
|
114
|
+
opts.headers = request.headers;
|
|
115
|
+
opts.method = request.method as Uppercase<HttpMethod>;
|
|
116
|
+
// ignore request.body changes to avoid turning serialized bodies into streams
|
|
117
|
+
// body comes only from getValidRequestBody(options)
|
|
118
|
+
// reflect signal if present
|
|
119
|
+
opts.signal = (request as any).signal as AbortSignal | undefined;
|
|
120
|
+
|
|
121
|
+
// When body is FormData, remove Content-Type header to avoid boundary mismatch.
|
|
122
|
+
// Note: We already delete Content-Type in resolveOptions for FormData, but the
|
|
123
|
+
// Request constructor (line 175) re-adds it with an auto-generated boundary.
|
|
124
|
+
// Since we pass the original FormData (not the Request's body) to ofetch, and
|
|
125
|
+
// ofetch will generate its own boundary, we must remove the Request's Content-Type
|
|
126
|
+
// to let ofetch set the correct one. Otherwise the boundary in the header won't
|
|
127
|
+
// match the boundary in the actual multipart body sent by ofetch.
|
|
128
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) {
|
|
129
|
+
opts.headers.delete('Content-Type');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return request;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// build ofetch options with stable retry logic based on body repeatability
|
|
136
|
+
const buildNetworkOptions = (
|
|
137
|
+
opts: ResolvedRequestOptions,
|
|
138
|
+
body: BodyInit | null | undefined,
|
|
139
|
+
responseType: OfetchResponseType | undefined,
|
|
140
|
+
) => {
|
|
141
|
+
const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any);
|
|
142
|
+
return buildOfetchOptions(opts, body, responseType, effectiveRetry);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const request: Client['request'] = async (options) => {
|
|
146
|
+
const { networkBody: initialNetworkBody, opts, url } = await resolveOptions(options as any);
|
|
147
|
+
// map parseAs -> ofetch responseType once per request
|
|
148
|
+
const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(
|
|
149
|
+
opts.parseAs,
|
|
150
|
+
opts.responseType,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const $ofetch = opts.ofetch ?? ofetch;
|
|
154
|
+
|
|
155
|
+
// create Request before network to run middleware consistently
|
|
156
|
+
const networkBody = initialNetworkBody;
|
|
157
|
+
const requestInit: ReqInit = {
|
|
158
|
+
body: networkBody,
|
|
159
|
+
headers: opts.headers as Headers,
|
|
160
|
+
method: opts.method,
|
|
161
|
+
redirect: 'follow',
|
|
162
|
+
signal: opts.signal,
|
|
163
|
+
};
|
|
164
|
+
let request = new Request(url, requestInit);
|
|
165
|
+
|
|
166
|
+
request = await applyRequestInterceptors(request, opts, networkBody);
|
|
167
|
+
const finalUrl = request.url;
|
|
168
|
+
|
|
169
|
+
// build ofetch options and perform the request (.raw keeps the Response)
|
|
170
|
+
const responseOptions = buildNetworkOptions(
|
|
171
|
+
opts as ResolvedRequestOptions,
|
|
172
|
+
networkBody,
|
|
173
|
+
ofetchResponseType,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
let response = await $ofetch.raw(finalUrl, responseOptions);
|
|
177
|
+
|
|
178
|
+
for (const fn of interceptors.response.fns) {
|
|
179
|
+
if (fn) {
|
|
180
|
+
response = await fn(response, request, opts);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const result = { request, response };
|
|
185
|
+
|
|
186
|
+
if (response.ok) {
|
|
187
|
+
const data = await parseSuccess(response, opts, ofetchResponseType);
|
|
188
|
+
return wrapDataReturn(data, result, opts.responseStyle);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let finalError = await parseError(response);
|
|
192
|
+
|
|
193
|
+
for (const fn of interceptors.error.fns) {
|
|
194
|
+
if (fn) {
|
|
195
|
+
finalError = await fn(finalError, response, request, opts);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ensure error is never undefined after interceptors
|
|
200
|
+
finalError = (finalError as any) || ({} as string);
|
|
201
|
+
|
|
202
|
+
if (opts.throwOnError) {
|
|
203
|
+
throw finalError;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return wrapErrorReturn(finalError, result, opts.responseStyle) as any;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
|
|
210
|
+
request({ ...options, method } as any);
|
|
211
|
+
|
|
212
|
+
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
|
|
213
|
+
const { networkBody, opts, url } = await resolveOptions(options);
|
|
214
|
+
const optsForSse: any = { ...opts };
|
|
215
|
+
delete optsForSse.body; // body is provided via serializedBody below
|
|
216
|
+
return createSseClient({
|
|
217
|
+
...optsForSse,
|
|
218
|
+
fetch: opts.fetch,
|
|
219
|
+
headers: opts.headers as Headers,
|
|
220
|
+
method,
|
|
221
|
+
onRequest: async (url, init) => {
|
|
222
|
+
let request = new Request(url, init);
|
|
223
|
+
request = await applyRequestInterceptors(request, opts, networkBody);
|
|
224
|
+
return request;
|
|
225
|
+
},
|
|
226
|
+
serializedBody: networkBody as BodyInit | null | undefined,
|
|
227
|
+
signal: opts.signal,
|
|
228
|
+
url,
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
buildUrl,
|
|
234
|
+
connect: makeMethodFn('CONNECT'),
|
|
235
|
+
delete: makeMethodFn('DELETE'),
|
|
236
|
+
get: makeMethodFn('GET'),
|
|
237
|
+
getConfig,
|
|
238
|
+
head: makeMethodFn('HEAD'),
|
|
239
|
+
interceptors,
|
|
240
|
+
options: makeMethodFn('OPTIONS'),
|
|
241
|
+
patch: makeMethodFn('PATCH'),
|
|
242
|
+
post: makeMethodFn('POST'),
|
|
243
|
+
put: makeMethodFn('PUT'),
|
|
244
|
+
request,
|
|
245
|
+
setConfig,
|
|
246
|
+
sse: {
|
|
247
|
+
connect: makeSseFn('CONNECT'),
|
|
248
|
+
delete: makeSseFn('DELETE'),
|
|
249
|
+
get: makeSseFn('GET'),
|
|
250
|
+
head: makeSseFn('HEAD'),
|
|
251
|
+
options: makeSseFn('OPTIONS'),
|
|
252
|
+
patch: makeSseFn('PATCH'),
|
|
253
|
+
post: makeSseFn('POST'),
|
|
254
|
+
put: makeSseFn('PUT'),
|
|
255
|
+
trace: makeSseFn('TRACE'),
|
|
256
|
+
},
|
|
257
|
+
trace: makeMethodFn('TRACE'),
|
|
258
|
+
} as Client;
|
|
259
|
+
};
|
|
@@ -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 { serializeQueryKeyValue } from '../core/queryKeySerializer';
|
|
10
|
+
export { createClient } from './client';
|
|
11
|
+
export type {
|
|
12
|
+
Client,
|
|
13
|
+
ClientOptions,
|
|
14
|
+
Config,
|
|
15
|
+
CreateClientConfig,
|
|
16
|
+
Options,
|
|
17
|
+
RequestOptions,
|
|
18
|
+
RequestResult,
|
|
19
|
+
ResolvedRequestOptions,
|
|
20
|
+
ResponseStyle,
|
|
21
|
+
TDataShape,
|
|
22
|
+
} from './types';
|
|
23
|
+
export { createConfig, mergeHeaders } from './utils';
|