@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.
Files changed (56) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +381 -0
  3. package/bin/run.cmd +3 -0
  4. package/bin/run.js +18 -0
  5. package/dist/clients/angular/client.ts +237 -0
  6. package/dist/clients/angular/index.ts +23 -0
  7. package/dist/clients/angular/types.ts +231 -0
  8. package/dist/clients/angular/utils.ts +408 -0
  9. package/dist/clients/axios/client.ts +154 -0
  10. package/dist/clients/axios/index.ts +21 -0
  11. package/dist/clients/axios/types.ts +158 -0
  12. package/dist/clients/axios/utils.ts +206 -0
  13. package/dist/clients/core/auth.ts +39 -0
  14. package/dist/clients/core/bodySerializer.ts +82 -0
  15. package/dist/clients/core/params.ts +167 -0
  16. package/dist/clients/core/pathSerializer.ts +169 -0
  17. package/dist/clients/core/queryKeySerializer.ts +115 -0
  18. package/dist/clients/core/serverSentEvents.ts +241 -0
  19. package/dist/clients/core/types.ts +102 -0
  20. package/dist/clients/core/utils.ts +138 -0
  21. package/dist/clients/fetch/client.ts +286 -0
  22. package/dist/clients/fetch/index.ts +23 -0
  23. package/dist/clients/fetch/types.ts +211 -0
  24. package/dist/clients/fetch/utils.ts +314 -0
  25. package/dist/clients/ky/client.ts +318 -0
  26. package/dist/clients/ky/index.ts +24 -0
  27. package/dist/clients/ky/types.ts +243 -0
  28. package/dist/clients/ky/utils.ts +312 -0
  29. package/dist/clients/next/client.ts +253 -0
  30. package/dist/clients/next/index.ts +21 -0
  31. package/dist/clients/next/types.ts +162 -0
  32. package/dist/clients/next/utils.ts +413 -0
  33. package/dist/clients/nuxt/client.ts +213 -0
  34. package/dist/clients/nuxt/index.ts +22 -0
  35. package/dist/clients/nuxt/types.ts +189 -0
  36. package/dist/clients/nuxt/utils.ts +384 -0
  37. package/dist/clients/ofetch/client.ts +259 -0
  38. package/dist/clients/ofetch/index.ts +23 -0
  39. package/dist/clients/ofetch/types.ts +275 -0
  40. package/dist/clients/ofetch/utils.ts +504 -0
  41. package/dist/index.d.mts +8634 -0
  42. package/dist/index.d.mts.map +1 -0
  43. package/dist/index.mjs +4 -0
  44. package/dist/init-DlaW5Djq.mjs +13832 -0
  45. package/dist/init-DlaW5Djq.mjs.map +1 -0
  46. package/dist/internal.d.mts +33 -0
  47. package/dist/internal.d.mts.map +1 -0
  48. package/dist/internal.mjs +4 -0
  49. package/dist/run.d.mts +1 -0
  50. package/dist/run.mjs +60 -0
  51. package/dist/run.mjs.map +1 -0
  52. package/dist/src-BYA2YioO.mjs +225 -0
  53. package/dist/src-BYA2YioO.mjs.map +1 -0
  54. package/dist/types-Ba27ofyy.d.mts +157 -0
  55. package/dist/types-Ba27ofyy.d.mts.map +1 -0
  56. package/package.json +109 -0
@@ -0,0 +1,504 @@
1
+ import type { FetchOptions as OfetchOptions, ResponseType as OfetchResponseType } from 'ofetch';
2
+
3
+ import { getAuthToken } from '../core/auth';
4
+ import type { QuerySerializerOptions } from '../core/bodySerializer';
5
+ import { jsonBodySerializer } from '../core/bodySerializer';
6
+ import {
7
+ serializeArrayParam,
8
+ serializeObjectParam,
9
+ serializePrimitiveParam,
10
+ } from '../core/pathSerializer';
11
+ import { getUrl } from '../core/utils';
12
+ import type {
13
+ Client,
14
+ ClientOptions,
15
+ Config,
16
+ RequestOptions,
17
+ ResolvedRequestOptions,
18
+ ResponseStyle,
19
+ } from './types';
20
+
21
+ export const createQuerySerializer = <T = unknown>({
22
+ parameters = {},
23
+ ...args
24
+ }: QuerySerializerOptions = {}) => {
25
+ const querySerializer = (queryParams: T) => {
26
+ const search: string[] = [];
27
+ if (queryParams && typeof queryParams === 'object') {
28
+ for (const name in queryParams) {
29
+ const value = queryParams[name];
30
+
31
+ if (value === undefined || value === null) {
32
+ continue;
33
+ }
34
+
35
+ const options = parameters[name] || args;
36
+
37
+ if (Array.isArray(value)) {
38
+ const serializedArray = serializeArrayParam({
39
+ allowReserved: options.allowReserved,
40
+ explode: true,
41
+ name,
42
+ style: 'form',
43
+ value,
44
+ ...options.array,
45
+ });
46
+ if (serializedArray) search.push(serializedArray);
47
+ } else if (typeof value === 'object') {
48
+ const serializedObject = serializeObjectParam({
49
+ allowReserved: options.allowReserved,
50
+ explode: true,
51
+ name,
52
+ style: 'deepObject',
53
+ value: value as Record<string, unknown>,
54
+ ...options.object,
55
+ });
56
+ if (serializedObject) search.push(serializedObject);
57
+ } else {
58
+ const serializedPrimitive = serializePrimitiveParam({
59
+ allowReserved: options.allowReserved,
60
+ name,
61
+ value: value as string,
62
+ });
63
+ if (serializedPrimitive) search.push(serializedPrimitive);
64
+ }
65
+ }
66
+ }
67
+ return search.join('&');
68
+ };
69
+ return querySerializer;
70
+ };
71
+
72
+ /**
73
+ * Infers parseAs value from provided Content-Type header.
74
+ */
75
+ export const getParseAs = (contentType: string | null): Exclude<Config['parseAs'], 'auto'> => {
76
+ if (!contentType) {
77
+ // If no Content-Type header is provided, the best we can do is return the raw response body,
78
+ // which is effectively the same as the 'stream' option.
79
+ return 'stream';
80
+ }
81
+
82
+ const cleanContent = contentType.split(';')[0]?.trim();
83
+
84
+ if (!cleanContent) {
85
+ return;
86
+ }
87
+
88
+ if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) {
89
+ return 'json';
90
+ }
91
+
92
+ if (cleanContent === 'multipart/form-data') {
93
+ return 'formData';
94
+ }
95
+
96
+ if (
97
+ ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type))
98
+ ) {
99
+ return 'blob';
100
+ }
101
+
102
+ if (cleanContent.startsWith('text/')) {
103
+ return 'text';
104
+ }
105
+
106
+ return;
107
+ };
108
+
109
+ /**
110
+ * Map our parseAs value to ofetch responseType when not explicitly provided.
111
+ */
112
+ export const mapParseAsToResponseType = (
113
+ parseAs: Config['parseAs'] | undefined,
114
+ explicit?: OfetchResponseType,
115
+ ): OfetchResponseType | undefined => {
116
+ if (explicit) return explicit;
117
+ switch (parseAs) {
118
+ case 'arrayBuffer':
119
+ case 'blob':
120
+ case 'json':
121
+ case 'text':
122
+ case 'stream':
123
+ return parseAs;
124
+ case 'formData':
125
+ case 'auto':
126
+ default:
127
+ return undefined; // let ofetch auto-detect
128
+ }
129
+ };
130
+
131
+ const checkForExistence = (
132
+ options: Pick<RequestOptions, 'auth' | 'query'> & {
133
+ headers: Headers;
134
+ },
135
+ name?: string,
136
+ ): boolean => {
137
+ if (!name) {
138
+ return false;
139
+ }
140
+ if (
141
+ options.headers.has(name) ||
142
+ options.query?.[name] ||
143
+ options.headers.get('Cookie')?.includes(`${name}=`)
144
+ ) {
145
+ return true;
146
+ }
147
+ return false;
148
+ };
149
+
150
+ export const setAuthParams = async ({
151
+ security,
152
+ ...options
153
+ }: Pick<Required<RequestOptions>, 'security'> &
154
+ Pick<RequestOptions, 'auth' | 'query'> & {
155
+ headers: Headers;
156
+ }) => {
157
+ for (const auth of security) {
158
+ if (checkForExistence(options, auth.name)) {
159
+ continue;
160
+ }
161
+
162
+ const token = await getAuthToken(auth, options.auth);
163
+
164
+ if (!token) {
165
+ continue;
166
+ }
167
+
168
+ const name = auth.name ?? 'Authorization';
169
+
170
+ switch (auth.in) {
171
+ case 'query':
172
+ if (!options.query) {
173
+ options.query = {};
174
+ }
175
+ options.query[name] = token;
176
+ break;
177
+ case 'cookie':
178
+ options.headers.append('Cookie', `${name}=${token}`);
179
+ break;
180
+ case 'header':
181
+ default:
182
+ options.headers.set(name, token);
183
+ break;
184
+ }
185
+ }
186
+ };
187
+
188
+ export const buildUrl: Client['buildUrl'] = (options) =>
189
+ getUrl({
190
+ baseUrl: options.baseUrl as string,
191
+ path: options.path,
192
+ query: options.query,
193
+ querySerializer:
194
+ typeof options.querySerializer === 'function'
195
+ ? options.querySerializer
196
+ : createQuerySerializer(options.querySerializer),
197
+ url: options.url,
198
+ });
199
+
200
+ export const mergeConfigs = (a: Config, b: Config): Config => {
201
+ const config = { ...a, ...b };
202
+ if (config.baseUrl?.endsWith('/')) {
203
+ config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
204
+ }
205
+ config.headers = mergeHeaders(a.headers, b.headers);
206
+ return config;
207
+ };
208
+
209
+ const headersEntries = (headers: Headers): Array<[string, string]> => {
210
+ const entries: Array<[string, string]> = [];
211
+ headers.forEach((value, key) => {
212
+ entries.push([key, value]);
213
+ });
214
+ return entries;
215
+ };
216
+
217
+ export const mergeHeaders = (
218
+ ...headers: Array<Required<Config>['headers'] | undefined>
219
+ ): Headers => {
220
+ const mergedHeaders = new Headers();
221
+ for (const header of headers) {
222
+ if (!header) {
223
+ continue;
224
+ }
225
+
226
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
227
+
228
+ for (const [key, value] of iterator) {
229
+ if (value === null) {
230
+ mergedHeaders.delete(key);
231
+ } else if (Array.isArray(value)) {
232
+ for (const v of value) {
233
+ mergedHeaders.append(key, v as string);
234
+ }
235
+ } else if (value !== undefined) {
236
+ // assume object headers are meant to be JSON stringified, i.e. their
237
+ // content value in OpenAPI specification is 'application/json'
238
+ mergedHeaders.set(
239
+ key,
240
+ typeof value === 'object' ? JSON.stringify(value) : (value as string),
241
+ );
242
+ }
243
+ }
244
+ }
245
+ return mergedHeaders;
246
+ };
247
+
248
+ /**
249
+ * Heuristic to detect whether a request body can be safely retried.
250
+ */
251
+ export const isRepeatableBody = (body: unknown): boolean => {
252
+ if (body == null) return true; // undefined/null treated as no-body
253
+ if (typeof body === 'string') return true;
254
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) return true;
255
+ if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) return true;
256
+ if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) return true;
257
+ if (typeof Blob !== 'undefined' && body instanceof Blob) return true;
258
+ if (typeof FormData !== 'undefined' && body instanceof FormData) return true;
259
+ // Streams are not repeatable
260
+ if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) return false;
261
+ // Default: assume non-repeatable for unknown structured bodies
262
+ return false;
263
+ };
264
+
265
+ /**
266
+ * Small helper to unify data vs fields return style.
267
+ */
268
+ export const wrapDataReturn = <T>(
269
+ data: T,
270
+ result: { request: Request; response: Response },
271
+ responseStyle: ResponseStyle | undefined,
272
+ ): T | ((T extends Record<string, unknown> ? { data: T } : { data: T }) & typeof result) =>
273
+ (responseStyle ?? 'fields') === 'data' ? (data as any) : ({ data, ...result } as any);
274
+
275
+ /**
276
+ * Small helper to unify error vs fields return style.
277
+ */
278
+ export const wrapErrorReturn = <E>(
279
+ error: E,
280
+ result: { request: Request; response: Response },
281
+ responseStyle: ResponseStyle | undefined,
282
+ ):
283
+ | undefined
284
+ | ((E extends Record<string, unknown> ? { error: E } : { error: E }) & typeof result) =>
285
+ (responseStyle ?? 'fields') === 'data' ? undefined : ({ error, ...result } as any);
286
+
287
+ /**
288
+ * Build options for $ofetch.raw from our resolved opts and body.
289
+ */
290
+ export const buildOfetchOptions = (
291
+ opts: ResolvedRequestOptions,
292
+ body: BodyInit | null | undefined,
293
+ responseType: OfetchResponseType | undefined,
294
+ retryOverride?: OfetchOptions['retry'],
295
+ ): OfetchOptions =>
296
+ ({
297
+ agent: opts.agent as OfetchOptions['agent'],
298
+ body,
299
+ credentials: opts.credentials as OfetchOptions['credentials'],
300
+ dispatcher: opts.dispatcher as OfetchOptions['dispatcher'],
301
+ headers: opts.headers as Headers,
302
+ ignoreResponseError: (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? true,
303
+ method: opts.method,
304
+ onRequest: opts.onRequest as OfetchOptions['onRequest'],
305
+ onRequestError: opts.onRequestError as OfetchOptions['onRequestError'],
306
+ onResponse: opts.onResponse as OfetchOptions['onResponse'],
307
+ onResponseError: opts.onResponseError as OfetchOptions['onResponseError'],
308
+ parseResponse: opts.parseResponse as OfetchOptions['parseResponse'],
309
+ // URL already includes query
310
+ query: undefined,
311
+ responseType,
312
+ retry: retryOverride ?? (opts.retry as OfetchOptions['retry']),
313
+ retryDelay: opts.retryDelay as OfetchOptions['retryDelay'],
314
+ retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'],
315
+ signal: opts.signal,
316
+ timeout: opts.timeout as number | undefined,
317
+ }) as OfetchOptions;
318
+
319
+ /**
320
+ * Parse a successful response, handling empty bodies and stream cases.
321
+ */
322
+ export const parseSuccess = async (
323
+ response: Response,
324
+ opts: ResolvedRequestOptions,
325
+ ofetchResponseType?: OfetchResponseType,
326
+ ): Promise<unknown> => {
327
+ // Stream requested: return stream body
328
+ if (ofetchResponseType === 'stream') {
329
+ return response.body;
330
+ }
331
+
332
+ const inferredParseAs =
333
+ (opts.parseAs === 'auto' ? getParseAs(response.headers.get('Content-Type')) : opts.parseAs) ??
334
+ 'json';
335
+
336
+ // Handle empty responses
337
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
338
+ switch (inferredParseAs) {
339
+ case 'arrayBuffer':
340
+ case 'blob':
341
+ case 'text':
342
+ return await (response as any)[inferredParseAs]();
343
+ case 'formData':
344
+ return new FormData();
345
+ case 'stream':
346
+ return response.body;
347
+ default:
348
+ return {};
349
+ }
350
+ }
351
+
352
+ // Prefer ofetch-populated data unless we explicitly need raw `formData`
353
+ let data: unknown = (response as any)._data;
354
+ if (inferredParseAs === 'formData' || typeof data === 'undefined') {
355
+ switch (inferredParseAs) {
356
+ case 'arrayBuffer':
357
+ case 'blob':
358
+ case 'formData':
359
+ case 'text':
360
+ data = await (response as any)[inferredParseAs]();
361
+ break;
362
+ case 'json': {
363
+ // Some servers return 200 with no Content-Length and empty body.
364
+ // response.json() would throw; detect empty via clone().text() first.
365
+ const txt = await response.clone().text();
366
+ if (!txt) {
367
+ data = {};
368
+ } else {
369
+ data = await (response as any).json();
370
+ }
371
+ break;
372
+ }
373
+ case 'stream':
374
+ return response.body;
375
+ }
376
+ }
377
+
378
+ if (inferredParseAs === 'json') {
379
+ if (opts.responseValidator) {
380
+ await opts.responseValidator(data);
381
+ }
382
+ if (opts.responseTransformer) {
383
+ data = await opts.responseTransformer(data);
384
+ }
385
+ }
386
+
387
+ return data;
388
+ };
389
+
390
+ /**
391
+ * Parse an error response payload.
392
+ */
393
+ export const parseError = async (response: Response): Promise<unknown> => {
394
+ let error: unknown = (response as any)._data;
395
+ if (typeof error === 'undefined') {
396
+ const textError = await response.text();
397
+ try {
398
+ error = JSON.parse(textError);
399
+ } catch {
400
+ error = textError;
401
+ }
402
+ }
403
+ return error ?? ({} as string);
404
+ };
405
+
406
+ type ErrInterceptor<Err, Res, Req, Options> = (
407
+ error: Err,
408
+ response: Res,
409
+ request: Req,
410
+ options: Options,
411
+ ) => Err | Promise<Err>;
412
+
413
+ type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>;
414
+
415
+ type ResInterceptor<Res, Req, Options> = (
416
+ response: Res,
417
+ request: Req,
418
+ options: Options,
419
+ ) => Res | Promise<Res>;
420
+
421
+ class Interceptors<Interceptor> {
422
+ fns: Array<Interceptor | null> = [];
423
+
424
+ clear(): void {
425
+ this.fns = [];
426
+ }
427
+
428
+ eject(id: number | Interceptor): void {
429
+ const index = this.getInterceptorIndex(id);
430
+ if (this.fns[index]) {
431
+ this.fns[index] = null;
432
+ }
433
+ }
434
+
435
+ exists(id: number | Interceptor): boolean {
436
+ const index = this.getInterceptorIndex(id);
437
+ return Boolean(this.fns[index]);
438
+ }
439
+
440
+ getInterceptorIndex(id: number | Interceptor): number {
441
+ if (typeof id === 'number') {
442
+ return this.fns[id] ? id : -1;
443
+ }
444
+ return this.fns.indexOf(id);
445
+ }
446
+
447
+ update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false {
448
+ const index = this.getInterceptorIndex(id);
449
+ if (this.fns[index]) {
450
+ this.fns[index] = fn;
451
+ return id;
452
+ }
453
+ return false;
454
+ }
455
+
456
+ use(fn: Interceptor): number {
457
+ this.fns.push(fn);
458
+ return this.fns.length - 1;
459
+ }
460
+ }
461
+
462
+ export interface Middleware<Req, Res, Err, Options> {
463
+ error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;
464
+ request: Interceptors<ReqInterceptor<Req, Options>>;
465
+ response: Interceptors<ResInterceptor<Res, Req, Options>>;
466
+ }
467
+
468
+ export const createInterceptors = <Req, Res, Err, Options>(): Middleware<
469
+ Req,
470
+ Res,
471
+ Err,
472
+ Options
473
+ > => ({
474
+ error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
475
+ request: new Interceptors<ReqInterceptor<Req, Options>>(),
476
+ response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
477
+ });
478
+
479
+ const defaultQuerySerializer = createQuerySerializer({
480
+ allowReserved: false,
481
+ array: {
482
+ explode: true,
483
+ style: 'form',
484
+ },
485
+ object: {
486
+ explode: true,
487
+ style: 'deepObject',
488
+ },
489
+ });
490
+
491
+ const defaultHeaders = {
492
+ 'Content-Type': 'application/json',
493
+ };
494
+
495
+ export const createConfig = <T extends ClientOptions = ClientOptions>(
496
+ override: Config<Omit<ClientOptions, keyof T> & T> = {},
497
+ ): Config<Omit<ClientOptions, keyof T> & T> => ({
498
+ ...jsonBodySerializer,
499
+ headers: defaultHeaders,
500
+ ignoreResponseError: true,
501
+ parseAs: 'auto',
502
+ querySerializer: defaultQuerySerializer,
503
+ ...override,
504
+ });