@zayne-labs/callapi 1.11.12 → 1.11.15

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/dist/esm/index.js CHANGED
@@ -1,270 +1,7 @@
1
1
  import { n as requestOptionDefaults, r as defineEnum, t as extraOptionDefaults } from "./defaults-BD3B1uIH.js";
2
- import { _ as isReadableStream, b as isValidJsonString, c as HTTPError, d as isBoolean, f as isFunction, g as isQueryString, h as isPromise, i as isHTTPErrorInstance, l as ValidationError, m as isPlainObject, n as toQueryString, p as isObject, s as isValidationErrorInstance, u as isArray, v as isSerializable, y as isString } from "./body-B9WKokQt.js";
2
+ import { _ as isReadableStream, a as isValidationErrorInstance, b as isValidJsonString, d as isBoolean, f as isFunction, g as isQueryString, h as isPromise, l as toQueryString, m as isPlainObject, n as isHTTPErrorInstance, o as HTTPError, p as isObject, s as ValidationError, u as isArray, v as isSerializable, y as isString } from "./guards-ClpaRdJN.js";
3
3
  import { n as fetchSpecificKeys, t as fallBackRouteSchemaKey } from "./validation-MjkoG9bG.js";
4
4
 
5
- //#region src/result.ts
6
- const getResponseType = (response, parser) => ({
7
- arrayBuffer: () => response.arrayBuffer(),
8
- blob: () => response.blob(),
9
- formData: () => response.formData(),
10
- json: async () => {
11
- return parser(await response.text());
12
- },
13
- stream: () => response.body,
14
- text: () => response.text()
15
- });
16
- const textTypes = new Set([
17
- "image/svg",
18
- "application/xml",
19
- "application/xhtml",
20
- "application/html"
21
- ]);
22
- const JSON_REGEX = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;
23
- const detectResponseType = (response) => {
24
- const initContentType = response.headers.get("content-type");
25
- if (!initContentType) return extraOptionDefaults.responseType;
26
- const contentType = initContentType.split(";")[0] ?? "";
27
- if (JSON_REGEX.test(contentType)) return "json";
28
- if (textTypes.has(contentType) || contentType.startsWith("text/")) return "text";
29
- return "blob";
30
- };
31
- const resolveResponseData = (response, responseType, parser) => {
32
- const selectedParser = parser ?? extraOptionDefaults.responseParser;
33
- const selectedResponseType = responseType ?? detectResponseType(response);
34
- const RESPONSE_TYPE_LOOKUP = getResponseType(response, selectedParser);
35
- if (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, selectedResponseType)) throw new Error(`Invalid response type: ${responseType}`);
36
- return RESPONSE_TYPE_LOOKUP[selectedResponseType]();
37
- };
38
- const getResultModeMap = (details) => {
39
- return {
40
- all: () => details,
41
- onlyData: () => details.data,
42
- onlyResponse: () => details.response
43
- };
44
- };
45
- const resolveSuccessResult = (data, info) => {
46
- const { response, resultMode } = info;
47
- return getResultModeMap({
48
- data,
49
- error: null,
50
- response
51
- })[resultMode ?? "all"]();
52
- };
53
- const resolveErrorResult = (error, info) => {
54
- const { cloneResponse, message: customErrorMessage, resultMode } = info;
55
- let errorDetails = {
56
- data: null,
57
- error: {
58
- errorData: false,
59
- message: customErrorMessage ?? error.message,
60
- name: error.name,
61
- originalError: error
62
- },
63
- response: null
64
- };
65
- if (isValidationErrorInstance(error)) {
66
- const { errorData, message, response } = error;
67
- errorDetails = {
68
- data: null,
69
- error: {
70
- errorData,
71
- issueCause: error.issueCause,
72
- message,
73
- name: "ValidationError",
74
- originalError: error
75
- },
76
- response
77
- };
78
- }
79
- if (isHTTPErrorInstance(error)) {
80
- const { errorData, message, name, response } = error;
81
- errorDetails = {
82
- data: null,
83
- error: {
84
- errorData,
85
- message,
86
- name,
87
- originalError: error
88
- },
89
- response: cloneResponse ? response.clone() : response
90
- };
91
- }
92
- const errorResult = getResultModeMap(errorDetails)[resultMode ?? "all"]();
93
- return {
94
- errorDetails,
95
- errorResult
96
- };
97
- };
98
- const getCustomizedErrorResult = (errorResult, customErrorInfo) => {
99
- if (!errorResult) return null;
100
- const { message = errorResult.error.message } = customErrorInfo;
101
- return {
102
- ...errorResult,
103
- error: {
104
- ...errorResult.error,
105
- message
106
- }
107
- };
108
- };
109
-
110
- //#endregion
111
- //#region src/hooks.ts
112
- const getHookRegistriesAndKeys = () => {
113
- const hookRegistries = {
114
- onError: /* @__PURE__ */ new Set(),
115
- onRequest: /* @__PURE__ */ new Set(),
116
- onRequestError: /* @__PURE__ */ new Set(),
117
- onRequestReady: /* @__PURE__ */ new Set(),
118
- onRequestStream: /* @__PURE__ */ new Set(),
119
- onResponse: /* @__PURE__ */ new Set(),
120
- onResponseError: /* @__PURE__ */ new Set(),
121
- onResponseStream: /* @__PURE__ */ new Set(),
122
- onRetry: /* @__PURE__ */ new Set(),
123
- onSuccess: /* @__PURE__ */ new Set(),
124
- onValidationError: /* @__PURE__ */ new Set()
125
- };
126
- return {
127
- hookRegistries,
128
- hookRegistryKeys: Object.keys(hookRegistries)
129
- };
130
- };
131
- const composeHooksFromArray = (hooksArray, hooksExecutionMode) => {
132
- const composedHook = async (ctx) => {
133
- switch (hooksExecutionMode) {
134
- case "parallel":
135
- await Promise.all(hooksArray.map((uniqueHook) => uniqueHook?.(ctx)));
136
- break;
137
- case "sequential":
138
- for (const hook of hooksArray) await hook?.(ctx);
139
- break;
140
- default:
141
- }
142
- };
143
- return composedHook;
144
- };
145
- const executeHooks = async (...hookResultsOrPromise) => {
146
- await Promise.all(hookResultsOrPromise);
147
- };
148
- const executeHooksInCatchBlock = async (hookResultsOrPromise, hookInfo) => {
149
- const { errorInfo, shouldThrowOnError } = hookInfo;
150
- try {
151
- await Promise.all(hookResultsOrPromise);
152
- return null;
153
- } catch (hookError) {
154
- if (shouldThrowOnError) throw hookError;
155
- const { errorResult } = resolveErrorResult(hookError, errorInfo);
156
- return errorResult;
157
- }
158
- };
159
-
160
- //#endregion
161
- //#region src/stream.ts
162
- const createProgressEvent = (options) => {
163
- const { chunk, totalBytes, transferredBytes } = options;
164
- return {
165
- chunk,
166
- progress: Math.round(transferredBytes / totalBytes * 100) || 0,
167
- totalBytes,
168
- transferredBytes
169
- };
170
- };
171
- const calculateTotalBytesFromBody = async (requestBody, existingTotalBytes) => {
172
- let totalBytes = existingTotalBytes;
173
- if (!requestBody) return totalBytes;
174
- for await (const chunk of requestBody) totalBytes += chunk.byteLength;
175
- return totalBytes;
176
- };
177
- const toStreamableRequest = async (context) => {
178
- const { baseConfig, config, options, request } = context;
179
- if (!options.onRequestStream || !isReadableStream(request.body)) return request;
180
- const requestInstance = new Request(options.fullURL, {
181
- ...request,
182
- duplex: "half"
183
- });
184
- const contentLength = requestInstance.headers.get("content-length");
185
- let totalBytes = Number(contentLength ?? 0);
186
- const shouldForcefullyCalcStreamSize = isObject(options.forcefullyCalculateStreamSize) ? options.forcefullyCalculateStreamSize.request : options.forcefullyCalculateStreamSize;
187
- if (!contentLength && shouldForcefullyCalcStreamSize) totalBytes = await calculateTotalBytesFromBody(requestInstance.clone().body, totalBytes);
188
- let transferredBytes = 0;
189
- const stream = new ReadableStream({ start: async (controller) => {
190
- const body = requestInstance.body;
191
- if (!body) return;
192
- const requestStreamContext = {
193
- baseConfig,
194
- config,
195
- event: createProgressEvent({
196
- chunk: new Uint8Array(),
197
- totalBytes,
198
- transferredBytes
199
- }),
200
- options,
201
- request,
202
- requestInstance
203
- };
204
- await executeHooks(options.onRequestStream?.(requestStreamContext));
205
- for await (const chunk of body) {
206
- transferredBytes += chunk.byteLength;
207
- totalBytes = Math.max(totalBytes, transferredBytes);
208
- await executeHooks(options.onRequestStream?.({
209
- ...requestStreamContext,
210
- event: createProgressEvent({
211
- chunk,
212
- totalBytes,
213
- transferredBytes
214
- })
215
- }));
216
- controller.enqueue(chunk);
217
- }
218
- controller.close();
219
- } });
220
- return new Request(requestInstance, {
221
- body: stream,
222
- duplex: "half"
223
- });
224
- };
225
- const toStreamableResponse = async (context) => {
226
- const { baseConfig, config, options, request, response } = context;
227
- if (!options.onResponseStream || !response.body) return response;
228
- const contentLength = response.headers.get("content-length");
229
- let totalBytes = Number(contentLength ?? 0);
230
- const shouldForceContentLengthCalc = isObject(options.forcefullyCalculateStreamSize) ? options.forcefullyCalculateStreamSize.response : options.forcefullyCalculateStreamSize;
231
- if (!contentLength && shouldForceContentLengthCalc) totalBytes = await calculateTotalBytesFromBody(response.clone().body, totalBytes);
232
- let transferredBytes = 0;
233
- const stream = new ReadableStream({ start: async (controller) => {
234
- const body = response.body;
235
- if (!body) return;
236
- const responseStreamContext = {
237
- baseConfig,
238
- config,
239
- event: createProgressEvent({
240
- chunk: new Uint8Array(),
241
- totalBytes,
242
- transferredBytes
243
- }),
244
- options,
245
- request,
246
- response
247
- };
248
- await executeHooks(options.onResponseStream?.(responseStreamContext));
249
- for await (const chunk of body) {
250
- transferredBytes += chunk.byteLength;
251
- totalBytes = Math.max(totalBytes, transferredBytes);
252
- await executeHooks(options.onResponseStream?.({
253
- ...responseStreamContext,
254
- event: createProgressEvent({
255
- chunk,
256
- totalBytes,
257
- transferredBytes
258
- })
259
- }));
260
- controller.enqueue(chunk);
261
- }
262
- controller.close();
263
- } });
264
- return new Response(stream, response);
265
- };
266
-
267
- //#endregion
268
5
  //#region src/auth.ts
269
6
  const resolveAuthValue = (value) => isFunction(value) ? value() : value;
270
7
  const getAuthHeader = async (auth) => {
@@ -533,122 +270,386 @@ const getFullAndNormalizedURL = (options) => {
533
270
  const normalizedInitURL = normalizeURL(initURL);
534
271
  const urlWithMergedQueryAndParams = mergeUrlWithQuery(mergeUrlWithParams(normalizedInitURL, params), query);
535
272
  return {
536
- fullURL: !urlWithMergedQueryAndParams.startsWith("http") && baseURL ? `${baseURL}${urlWithMergedQueryAndParams}` : urlWithMergedQueryAndParams,
537
- normalizedInitURL
273
+ fullURL: !urlWithMergedQueryAndParams.startsWith("http") && baseURL ? `${baseURL}${urlWithMergedQueryAndParams}` : urlWithMergedQueryAndParams,
274
+ normalizedInitURL
275
+ };
276
+ };
277
+
278
+ //#endregion
279
+ //#region src/utils/polyfills/combinedSignal.ts
280
+ const createCombinedSignalPolyfill = (signals) => {
281
+ const controller = new AbortController();
282
+ const handleAbort = (actualSignal) => {
283
+ if (controller.signal.aborted) return;
284
+ controller.abort(actualSignal.reason);
285
+ };
286
+ for (const actualSignal of signals) {
287
+ if (actualSignal.aborted) {
288
+ handleAbort(actualSignal);
289
+ break;
290
+ }
291
+ actualSignal.addEventListener("abort", () => handleAbort(actualSignal), { signal: controller.signal });
292
+ }
293
+ return controller.signal;
294
+ };
295
+
296
+ //#endregion
297
+ //#region src/utils/polyfills/timeoutSignal.ts
298
+ const createTimeoutSignalPolyfill = (milliseconds) => {
299
+ const controller = new AbortController();
300
+ const reason = new DOMException("Request timed out", "TimeoutError");
301
+ const timeout = setTimeout(() => controller.abort(reason), milliseconds);
302
+ controller.signal.addEventListener("abort", () => clearTimeout(timeout));
303
+ return controller.signal;
304
+ };
305
+
306
+ //#endregion
307
+ //#region src/utils/common.ts
308
+ const omitKeys = (initialObject, keysToOmit) => {
309
+ const updatedObject = {};
310
+ const keysToOmitSet = new Set(keysToOmit);
311
+ for (const [key, value] of Object.entries(initialObject)) if (!keysToOmitSet.has(key)) updatedObject[key] = value;
312
+ return updatedObject;
313
+ };
314
+ const pickKeys = (initialObject, keysToPick) => {
315
+ const updatedObject = {};
316
+ const keysToPickSet = new Set(keysToPick);
317
+ for (const [key, value] of Object.entries(initialObject)) if (keysToPickSet.has(key)) updatedObject[key] = value;
318
+ return updatedObject;
319
+ };
320
+ const splitBaseConfig = (baseConfig) => [pickKeys(baseConfig, fetchSpecificKeys), omitKeys(baseConfig, fetchSpecificKeys)];
321
+ const splitConfig = (config) => [pickKeys(config, fetchSpecificKeys), omitKeys(config, fetchSpecificKeys)];
322
+ const objectifyHeaders = (headers) => {
323
+ if (!headers || isPlainObject(headers)) return headers;
324
+ return Object.fromEntries(headers);
325
+ };
326
+ const getHeaders = async (options) => {
327
+ const { auth, body, headers } = options;
328
+ if (!(Boolean(headers) || Boolean(body) || Boolean(auth))) return;
329
+ const headersObject = {
330
+ ...await getAuthHeader(auth),
331
+ ...objectifyHeaders(headers)
332
+ };
333
+ if (isQueryString(body)) {
334
+ headersObject["Content-Type"] = "application/x-www-form-urlencoded";
335
+ return headersObject;
336
+ }
337
+ if (isSerializable(body) || isValidJsonString(body)) {
338
+ headersObject["Content-Type"] = "application/json";
339
+ headersObject.Accept = "application/json";
340
+ }
341
+ return headersObject;
342
+ };
343
+ const getMethod = (ctx) => {
344
+ const { initURL, method } = ctx;
345
+ return method?.toUpperCase() ?? extractMethodFromURL(initURL)?.toUpperCase() ?? requestOptionDefaults.method;
346
+ };
347
+ const getBody = (options) => {
348
+ const { body, bodySerializer } = options;
349
+ if (isSerializable(body)) return (bodySerializer ?? extraOptionDefaults.bodySerializer)(body);
350
+ return body;
351
+ };
352
+ const getInitFetchImpl = (customFetchImpl) => {
353
+ if (customFetchImpl) return customFetchImpl;
354
+ if (typeof globalThis !== "undefined" && isFunction(globalThis.fetch)) return globalThis.fetch;
355
+ throw new Error("No fetch implementation found");
356
+ };
357
+ const getFetchImpl = (context) => {
358
+ const { customFetchImpl, fetchMiddleware, requestContext } = context;
359
+ const initFetchImpl = getInitFetchImpl(customFetchImpl);
360
+ return fetchMiddleware ? fetchMiddleware({
361
+ ...requestContext,
362
+ fetchImpl: initFetchImpl
363
+ }) : initFetchImpl;
364
+ };
365
+ const waitFor = (delay) => {
366
+ if (delay === 0) return;
367
+ return new Promise((resolve) => setTimeout(resolve, delay));
368
+ };
369
+ const createCombinedSignal = (...signals) => {
370
+ const cleanedSignals = signals.filter((signal) => signal != null);
371
+ if (!("any" in AbortSignal)) return createCombinedSignalPolyfill(cleanedSignals);
372
+ return AbortSignal.any(cleanedSignals);
373
+ };
374
+ const createTimeoutSignal = (milliseconds) => {
375
+ if (milliseconds == null) return null;
376
+ if (!("timeout" in AbortSignal)) return createTimeoutSignalPolyfill(milliseconds);
377
+ return AbortSignal.timeout(milliseconds);
378
+ };
379
+ const deterministicHashFn = (value) => {
380
+ return JSON.stringify(value, (_, val) => {
381
+ if (!isPlainObject(val)) return val;
382
+ const sortedKeys = Object.keys(val).toSorted();
383
+ const result = {};
384
+ for (const key of sortedKeys) result[key] = val[key];
385
+ return result;
386
+ });
387
+ };
388
+ const toArray = (value) => isArray(value) ? value : [value];
389
+
390
+ //#endregion
391
+ //#region src/result.ts
392
+ const getResponseType = (response, parser) => ({
393
+ arrayBuffer: () => response.arrayBuffer(),
394
+ blob: () => response.blob(),
395
+ formData: () => response.formData(),
396
+ json: async () => {
397
+ return parser(await response.text());
398
+ },
399
+ stream: () => response.body,
400
+ text: () => response.text()
401
+ });
402
+ const textTypes = new Set([
403
+ "image/svg",
404
+ "application/xml",
405
+ "application/xhtml",
406
+ "application/html"
407
+ ]);
408
+ const JSON_REGEX = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;
409
+ const detectResponseType = (response) => {
410
+ const initContentType = response.headers.get("content-type");
411
+ if (!initContentType) return extraOptionDefaults.responseType;
412
+ const contentType = initContentType.split(";")[0] ?? "";
413
+ if (JSON_REGEX.test(contentType)) return "json";
414
+ if (textTypes.has(contentType) || contentType.startsWith("text/")) return "text";
415
+ return "blob";
416
+ };
417
+ const resolveResponseData = (response, responseType, parser) => {
418
+ const selectedParser = parser ?? extraOptionDefaults.responseParser;
419
+ const selectedResponseType = responseType ?? detectResponseType(response);
420
+ const RESPONSE_TYPE_LOOKUP = getResponseType(response, selectedParser);
421
+ if (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, selectedResponseType)) throw new Error(`Invalid response type: ${responseType}`);
422
+ return RESPONSE_TYPE_LOOKUP[selectedResponseType]();
423
+ };
424
+ const getResultModeMap = (details) => {
425
+ return {
426
+ all: () => details,
427
+ onlyData: () => details.data,
428
+ onlyResponse: () => details.response,
429
+ withoutResponse: () => omitKeys(details, ["response"])
430
+ };
431
+ };
432
+ const resolveSuccessResult = (data, info) => {
433
+ const { response, resultMode } = info;
434
+ return getResultModeMap({
435
+ data,
436
+ error: null,
437
+ response
438
+ })[resultMode ?? "all"]();
439
+ };
440
+ const resolveErrorResult = (error, info) => {
441
+ const { cloneResponse, message: customErrorMessage, resultMode } = info;
442
+ let errorDetails = {
443
+ data: null,
444
+ error: {
445
+ errorData: false,
446
+ message: customErrorMessage ?? error.message,
447
+ name: error.name,
448
+ originalError: error
449
+ },
450
+ response: null
451
+ };
452
+ if (isValidationErrorInstance(error)) {
453
+ const { errorData, message, response } = error;
454
+ errorDetails = {
455
+ data: null,
456
+ error: {
457
+ errorData,
458
+ issueCause: error.issueCause,
459
+ message,
460
+ name: "ValidationError",
461
+ originalError: error
462
+ },
463
+ response
464
+ };
465
+ }
466
+ if (isHTTPErrorInstance(error)) {
467
+ const { errorData, message, name, response } = error;
468
+ errorDetails = {
469
+ data: null,
470
+ error: {
471
+ errorData,
472
+ message,
473
+ name,
474
+ originalError: error
475
+ },
476
+ response: cloneResponse ? response.clone() : response
477
+ };
478
+ }
479
+ const errorResult = getResultModeMap(errorDetails)[resultMode ?? "all"]();
480
+ return {
481
+ errorDetails,
482
+ errorResult
483
+ };
484
+ };
485
+ const getCustomizedErrorResult = (errorResult, customErrorInfo) => {
486
+ if (!errorResult) return null;
487
+ const { message = errorResult.error.message } = customErrorInfo;
488
+ return {
489
+ ...errorResult,
490
+ error: {
491
+ ...errorResult.error,
492
+ message
493
+ }
538
494
  };
539
495
  };
540
496
 
541
497
  //#endregion
542
- //#region src/utils/polyfills/combinedSignal.ts
543
- const createCombinedSignalPolyfill = (signals) => {
544
- const controller = new AbortController();
545
- const handleAbort = (actualSignal) => {
546
- if (controller.signal.aborted) return;
547
- controller.abort(actualSignal.reason);
498
+ //#region src/hooks.ts
499
+ const getHookRegistriesAndKeys = () => {
500
+ const hookRegistries = {
501
+ onError: /* @__PURE__ */ new Set(),
502
+ onRequest: /* @__PURE__ */ new Set(),
503
+ onRequestError: /* @__PURE__ */ new Set(),
504
+ onRequestReady: /* @__PURE__ */ new Set(),
505
+ onRequestStream: /* @__PURE__ */ new Set(),
506
+ onResponse: /* @__PURE__ */ new Set(),
507
+ onResponseError: /* @__PURE__ */ new Set(),
508
+ onResponseStream: /* @__PURE__ */ new Set(),
509
+ onRetry: /* @__PURE__ */ new Set(),
510
+ onSuccess: /* @__PURE__ */ new Set(),
511
+ onValidationError: /* @__PURE__ */ new Set()
548
512
  };
549
- for (const actualSignal of signals) {
550
- if (actualSignal.aborted) {
551
- handleAbort(actualSignal);
552
- break;
513
+ return {
514
+ hookRegistries,
515
+ hookRegistryKeys: Object.keys(hookRegistries)
516
+ };
517
+ };
518
+ const composeHooksFromArray = (hooksArray, hooksExecutionMode) => {
519
+ const composedHook = async (ctx) => {
520
+ switch (hooksExecutionMode) {
521
+ case "parallel":
522
+ await Promise.all(hooksArray.map((uniqueHook) => uniqueHook?.(ctx)));
523
+ break;
524
+ case "sequential":
525
+ for (const hook of hooksArray) await hook?.(ctx);
526
+ break;
527
+ default:
553
528
  }
554
- actualSignal.addEventListener("abort", () => handleAbort(actualSignal), { signal: controller.signal });
555
- }
556
- return controller.signal;
529
+ };
530
+ return composedHook;
557
531
  };
558
-
559
- //#endregion
560
- //#region src/utils/polyfills/timeoutSignal.ts
561
- const createTimeoutSignalPolyfill = (milliseconds) => {
562
- const controller = new AbortController();
563
- const reason = new DOMException("Request timed out", "TimeoutError");
564
- const timeout = setTimeout(() => controller.abort(reason), milliseconds);
565
- controller.signal.addEventListener("abort", () => clearTimeout(timeout));
566
- return controller.signal;
532
+ const executeHooks = async (...hookResultsOrPromise) => {
533
+ await Promise.all(hookResultsOrPromise);
534
+ };
535
+ const executeHooksInCatchBlock = async (hookResultsOrPromise, hookInfo) => {
536
+ const { errorInfo, shouldThrowOnError } = hookInfo;
537
+ try {
538
+ await Promise.all(hookResultsOrPromise);
539
+ return null;
540
+ } catch (hookError) {
541
+ if (shouldThrowOnError) throw hookError;
542
+ const { errorResult } = resolveErrorResult(hookError, errorInfo);
543
+ return errorResult;
544
+ }
567
545
  };
568
546
 
569
547
  //#endregion
570
- //#region src/utils/common.ts
571
- const omitKeys = (initialObject, keysToOmit) => {
572
- const updatedObject = {};
573
- const keysToOmitSet = new Set(keysToOmit);
574
- for (const [key, value] of Object.entries(initialObject)) if (!keysToOmitSet.has(key)) updatedObject[key] = value;
575
- return updatedObject;
576
- };
577
- const pickKeys = (initialObject, keysToPick) => {
578
- const updatedObject = {};
579
- const keysToPickSet = new Set(keysToPick);
580
- for (const [key, value] of Object.entries(initialObject)) if (keysToPickSet.has(key)) updatedObject[key] = value;
581
- return updatedObject;
582
- };
583
- const splitBaseConfig = (baseConfig) => [pickKeys(baseConfig, fetchSpecificKeys), omitKeys(baseConfig, fetchSpecificKeys)];
584
- const splitConfig = (config) => [pickKeys(config, fetchSpecificKeys), omitKeys(config, fetchSpecificKeys)];
585
- const objectifyHeaders = (headers) => {
586
- if (!headers || isPlainObject(headers)) return headers;
587
- return Object.fromEntries(headers);
588
- };
589
- const getHeaders = async (options) => {
590
- const { auth, body, headers } = options;
591
- if (!(Boolean(headers) || Boolean(body) || Boolean(auth))) return;
592
- const headersObject = {
593
- ...await getAuthHeader(auth),
594
- ...objectifyHeaders(headers)
548
+ //#region src/stream.ts
549
+ const createProgressEvent = (options) => {
550
+ const { chunk, totalBytes, transferredBytes } = options;
551
+ return {
552
+ chunk,
553
+ progress: Math.round(transferredBytes / totalBytes * 100) || 0,
554
+ totalBytes,
555
+ transferredBytes
595
556
  };
596
- if (isQueryString(body)) {
597
- headersObject["Content-Type"] = "application/x-www-form-urlencoded";
598
- return headersObject;
599
- }
600
- if (isSerializable(body) || isValidJsonString(body)) {
601
- headersObject["Content-Type"] = "application/json";
602
- headersObject.Accept = "application/json";
603
- }
604
- return headersObject;
605
- };
606
- const getMethod = (ctx) => {
607
- const { initURL, method } = ctx;
608
- return method?.toUpperCase() ?? extractMethodFromURL(initURL)?.toUpperCase() ?? requestOptionDefaults.method;
609
- };
610
- const getBody = (options) => {
611
- const { body, bodySerializer } = options;
612
- if (isSerializable(body)) return (bodySerializer ?? extraOptionDefaults.bodySerializer)(body);
613
- return body;
614
- };
615
- const getInitFetchImpl = (customFetchImpl) => {
616
- if (customFetchImpl) return customFetchImpl;
617
- if (typeof globalThis !== "undefined" && isFunction(globalThis.fetch)) return globalThis.fetch;
618
- throw new Error("No fetch implementation found");
619
- };
620
- const getFetchImpl = (context) => {
621
- const { customFetchImpl, fetchMiddleware, requestContext } = context;
622
- const initFetchImpl = getInitFetchImpl(customFetchImpl);
623
- return fetchMiddleware ? fetchMiddleware({
624
- ...requestContext,
625
- fetchImpl: initFetchImpl
626
- }) : initFetchImpl;
627
- };
628
- const waitFor = (delay) => {
629
- if (delay === 0) return;
630
- return new Promise((resolve) => setTimeout(resolve, delay));
631
- };
632
- const createCombinedSignal = (...signals) => {
633
- const cleanedSignals = signals.filter((signal) => signal != null);
634
- if (!("any" in AbortSignal)) return createCombinedSignalPolyfill(cleanedSignals);
635
- return AbortSignal.any(cleanedSignals);
636
557
  };
637
- const createTimeoutSignal = (milliseconds) => {
638
- if (milliseconds == null) return null;
639
- if (!("timeout" in AbortSignal)) return createTimeoutSignalPolyfill(milliseconds);
640
- return AbortSignal.timeout(milliseconds);
558
+ const calculateTotalBytesFromBody = async (requestBody, existingTotalBytes) => {
559
+ let totalBytes = existingTotalBytes;
560
+ if (!requestBody) return totalBytes;
561
+ for await (const chunk of requestBody) totalBytes += chunk.byteLength;
562
+ return totalBytes;
641
563
  };
642
- const deterministicHashFn = (value) => {
643
- return JSON.stringify(value, (_, val) => {
644
- if (!isPlainObject(val)) return val;
645
- const sortedKeys = Object.keys(val).toSorted();
646
- const result = {};
647
- for (const key of sortedKeys) result[key] = val[key];
648
- return result;
564
+ const toStreamableRequest = async (context) => {
565
+ const { baseConfig, config, options, request } = context;
566
+ if (!options.onRequestStream || !isReadableStream(request.body)) return request;
567
+ const requestInstance = new Request(options.fullURL, {
568
+ ...request,
569
+ duplex: "half"
570
+ });
571
+ const contentLength = requestInstance.headers.get("content-length");
572
+ let totalBytes = Number(contentLength ?? 0);
573
+ const shouldForcefullyCalcStreamSize = isObject(options.forcefullyCalculateStreamSize) ? options.forcefullyCalculateStreamSize.request : options.forcefullyCalculateStreamSize;
574
+ if (!contentLength && shouldForcefullyCalcStreamSize) totalBytes = await calculateTotalBytesFromBody(requestInstance.clone().body, totalBytes);
575
+ let transferredBytes = 0;
576
+ const stream = new ReadableStream({ start: async (controller) => {
577
+ const body = requestInstance.body;
578
+ if (!body) return;
579
+ const requestStreamContext = {
580
+ baseConfig,
581
+ config,
582
+ event: createProgressEvent({
583
+ chunk: new Uint8Array(),
584
+ totalBytes,
585
+ transferredBytes
586
+ }),
587
+ options,
588
+ request,
589
+ requestInstance
590
+ };
591
+ await executeHooks(options.onRequestStream?.(requestStreamContext));
592
+ for await (const chunk of body) {
593
+ transferredBytes += chunk.byteLength;
594
+ totalBytes = Math.max(totalBytes, transferredBytes);
595
+ await executeHooks(options.onRequestStream?.({
596
+ ...requestStreamContext,
597
+ event: createProgressEvent({
598
+ chunk,
599
+ totalBytes,
600
+ transferredBytes
601
+ })
602
+ }));
603
+ controller.enqueue(chunk);
604
+ }
605
+ controller.close();
606
+ } });
607
+ return new Request(requestInstance, {
608
+ body: stream,
609
+ duplex: "half"
649
610
  });
650
611
  };
651
- const toArray = (value) => isArray(value) ? value : [value];
612
+ const toStreamableResponse = async (context) => {
613
+ const { baseConfig, config, options, request, response } = context;
614
+ if (!options.onResponseStream || !response.body) return response;
615
+ const contentLength = response.headers.get("content-length");
616
+ let totalBytes = Number(contentLength ?? 0);
617
+ const shouldForceContentLengthCalc = isObject(options.forcefullyCalculateStreamSize) ? options.forcefullyCalculateStreamSize.response : options.forcefullyCalculateStreamSize;
618
+ if (!contentLength && shouldForceContentLengthCalc) totalBytes = await calculateTotalBytesFromBody(response.clone().body, totalBytes);
619
+ let transferredBytes = 0;
620
+ const stream = new ReadableStream({ start: async (controller) => {
621
+ const body = response.body;
622
+ if (!body) return;
623
+ const responseStreamContext = {
624
+ baseConfig,
625
+ config,
626
+ event: createProgressEvent({
627
+ chunk: new Uint8Array(),
628
+ totalBytes,
629
+ transferredBytes
630
+ }),
631
+ options,
632
+ request,
633
+ response
634
+ };
635
+ await executeHooks(options.onResponseStream?.(responseStreamContext));
636
+ for await (const chunk of body) {
637
+ transferredBytes += chunk.byteLength;
638
+ totalBytes = Math.max(totalBytes, transferredBytes);
639
+ await executeHooks(options.onResponseStream?.({
640
+ ...responseStreamContext,
641
+ event: createProgressEvent({
642
+ chunk,
643
+ totalBytes,
644
+ transferredBytes
645
+ })
646
+ }));
647
+ controller.enqueue(chunk);
648
+ }
649
+ controller.close();
650
+ } });
651
+ return new Response(stream, response);
652
+ };
652
653
 
653
654
  //#endregion
654
655
  //#region src/dedupe.ts