@zayne-labs/callapi 1.12.4 → 1.12.6

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as getCurrentRouteSchemaKeyAndMainInitURL, B as isReadableStream, L as isArray, M as handleSchemaValidation, N as HTTPError, R as isBoolean, V as isString, a as getBody, c as getMethod, d as splitConfig, f as waitFor, h as isHTTPErrorInstance, i as createTimeoutSignal, j as handleConfigValidation, k as getFullAndNormalizedURL, l as getResolvedHeaders, o as getFetchImpl, r as createCombinedSignal, s as getHeaders, t as extraOptionDefaults, u as omitKeys, v as isValidationErrorInstance, z as isFunction } from "./defaults-WfRTxvn6.js";
1
+ import { A as getCurrentRouteSchemaKeyAndMainInitURL, B as isString, L as isArray, M as handleSchemaValidation, N as HTTPError, R as isBoolean, a as getBody, c as getMethod, d as splitConfig, f as waitFor, h as isHTTPErrorInstance, i as createTimeoutSignal, j as handleConfigValidation, k as getFullAndNormalizedURL, l as getResolvedHeaders, o as getFetchImpl, r as createCombinedSignal, s as getHeaders, t as extraOptionDefaults, u as omitKeys, v as isValidationErrorInstance, z as isFunction } from "./defaults-IsBgvt90.js";
2
2
  //#region src/result.ts
3
3
  const getResponseType = (response, responseParser) => ({
4
4
  arrayBuffer: () => response.arrayBuffer(),
@@ -143,7 +143,9 @@ const composeHooksFromArray = (hooksArray, hooksExecutionMode) => {
143
143
  return composedHook;
144
144
  };
145
145
  const executeHooks = async (...hookResultsOrPromise) => {
146
- await Promise.all(hookResultsOrPromise);
146
+ const validHooks = hookResultsOrPromise.filter(Boolean);
147
+ if (validHooks.length === 0) return;
148
+ await Promise.all(validHooks);
147
149
  };
148
150
  const executeHooksInCatchBlock = async (hookResultsOrPromise, hookInfo) => {
149
151
  const { errorInfo, shouldThrowOnError } = hookInfo;
@@ -159,104 +161,128 @@ const executeHooksInCatchBlock = async (hookResultsOrPromise, hookInfo) => {
159
161
  //#endregion
160
162
  //#region src/stream.ts
161
163
  const createProgressEvent = (options) => {
162
- const { chunk, totalBytes, transferredBytes } = options;
164
+ const { chunk, isDone, totalBytes, transferredBytes } = options;
165
+ let percentage = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
166
+ if (percentage >= 1) percentage = 1 - Number.EPSILON;
163
167
  return {
164
168
  chunk,
165
- progress: Math.round(transferredBytes / totalBytes * 100) || 0,
169
+ progress: (isDone ? 1 : percentage) * 100,
166
170
  totalBytes,
167
171
  transferredBytes
168
172
  };
169
173
  };
170
- const calculateTotalBytesFromBody = async (requestBody, existingTotalBytes) => {
171
- let totalBytes = existingTotalBytes;
172
- if (!requestBody) return totalBytes;
173
- for await (const chunk of requestBody) totalBytes += chunk.byteLength;
174
- return totalBytes;
174
+ const textEncoder = new TextEncoder();
175
+ const estimateBodySize = (body) => {
176
+ if (!body) return 0;
177
+ if (body instanceof ReadableStream) return 0;
178
+ if (body instanceof FormData) {
179
+ let size = 0;
180
+ for (const [key, value] of body) {
181
+ size += 40;
182
+ size += textEncoder.encode(`Content-Disposition: form-data; name="${key}"`).byteLength;
183
+ size += isString(value) ? textEncoder.encode(value).byteLength : value.size;
184
+ }
185
+ return size;
186
+ }
187
+ if (body instanceof Blob) return body.size;
188
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return body.byteLength;
189
+ if (isString(body) || body instanceof URLSearchParams) return textEncoder.encode(String(body)).byteLength;
190
+ return 0;
191
+ };
192
+ const createTrackedStream = (streamOptions) => {
193
+ const { initialTotalBytes, onStream, streamBody } = streamOptions;
194
+ let totalBytes = initialTotalBytes;
195
+ let transferredBytes = 0;
196
+ const reportProgress = async (progressOptions) => {
197
+ const { chunk, isDone } = progressOptions;
198
+ if (isDone) {
199
+ await onStream(createProgressEvent({
200
+ chunk: null,
201
+ isDone: true,
202
+ totalBytes,
203
+ transferredBytes
204
+ }));
205
+ return;
206
+ }
207
+ transferredBytes += chunk.byteLength;
208
+ totalBytes = Math.max(totalBytes, transferredBytes);
209
+ await onStream(createProgressEvent({
210
+ chunk,
211
+ totalBytes,
212
+ transferredBytes
213
+ }));
214
+ };
215
+ return streamBody.pipeThrough(new TransformStream({
216
+ flush: async () => {
217
+ await reportProgress({
218
+ chunk: null,
219
+ isDone: true
220
+ });
221
+ },
222
+ start: async () => {
223
+ await reportProgress({ chunk: new Uint8Array() });
224
+ },
225
+ transform: async (chunk, controller) => {
226
+ controller.enqueue(chunk);
227
+ await reportProgress({ chunk });
228
+ }
229
+ }));
175
230
  };
176
- const toStreamableRequest = async (context) => {
231
+ const toStreamableRequest = (context) => {
177
232
  const { baseConfig, config, options, request } = context;
178
- if (!options.onRequestStream || !isReadableStream(request.body)) return request;
233
+ if (!options.onRequestStream || !request.body) return request;
179
234
  const requestInstance = new Request(options.fullURL, {
180
235
  ...request,
181
236
  duplex: "half"
182
237
  });
183
238
  const contentLength = requestInstance.headers.get("content-length");
184
- let totalBytes = Number(contentLength ?? 0);
185
- if (!contentLength && options.forcefullyCalculateRequestStreamSize) totalBytes = await calculateTotalBytesFromBody(requestInstance.clone().body, totalBytes);
186
- let transferredBytes = 0;
187
- const stream = new ReadableStream({ start: async (controller) => {
188
- const body = requestInstance.body;
189
- if (!body) return;
190
- const requestStreamContext = {
191
- baseConfig,
192
- config,
193
- event: createProgressEvent({
194
- chunk: new Uint8Array(),
195
- totalBytes,
196
- transferredBytes
197
- }),
198
- options,
199
- request,
200
- requestInstance
201
- };
202
- await executeHooks(options.onRequestStream?.(requestStreamContext));
203
- for await (const chunk of body) {
204
- transferredBytes += chunk.byteLength;
205
- totalBytes = Math.max(totalBytes, transferredBytes);
239
+ const initialTotalBytes = contentLength ? Math.max(0, Number(contentLength) || 0) : estimateBodySize(config.body ?? request.body);
240
+ const streamBody = requestInstance.body;
241
+ if (!streamBody) return requestInstance;
242
+ const requestStreamContext = {
243
+ baseConfig,
244
+ config,
245
+ options,
246
+ request,
247
+ requestInstance
248
+ };
249
+ const trackedStream = createTrackedStream({
250
+ initialTotalBytes,
251
+ onStream: async (event) => {
206
252
  await executeHooks(options.onRequestStream?.({
207
253
  ...requestStreamContext,
208
- event: createProgressEvent({
209
- chunk,
210
- totalBytes,
211
- transferredBytes
212
- })
254
+ event
213
255
  }));
214
- controller.enqueue(chunk);
215
- }
216
- controller.close();
217
- } });
256
+ },
257
+ streamBody
258
+ });
218
259
  return new Request(requestInstance, {
219
- body: stream,
260
+ body: trackedStream,
220
261
  duplex: "half"
221
262
  });
222
263
  };
223
264
  const toStreamableResponse = (context) => {
224
265
  const { baseConfig, config, options, request, response } = context;
225
- if (!options.onResponseStream || !response.body) return response;
226
- const contentLength = response.headers.get("content-length");
227
- let totalBytes = Number(contentLength ?? 0);
228
- let transferredBytes = 0;
229
- const stream = new ReadableStream({ start: async (controller) => {
230
- const body = response.body;
231
- if (!body) return;
232
- const responseStreamContext = {
233
- baseConfig,
234
- config,
235
- event: createProgressEvent({
236
- chunk: new Uint8Array(),
237
- totalBytes,
238
- transferredBytes
239
- }),
240
- options,
241
- request,
242
- response
243
- };
244
- await executeHooks(options.onResponseStream?.(responseStreamContext));
245
- for await (const chunk of body) {
246
- transferredBytes += chunk.byteLength;
247
- totalBytes = Math.max(totalBytes, transferredBytes);
266
+ if (!options.onResponseStream || !response.body || response.status === 204) return response;
267
+ const initialTotalBytes = Math.max(0, Number(response.headers.get("content-length")) || 0);
268
+ const streamBody = response.body;
269
+ const responseStreamContext = {
270
+ baseConfig,
271
+ config,
272
+ options,
273
+ request,
274
+ response
275
+ };
276
+ const stream = createTrackedStream({
277
+ initialTotalBytes,
278
+ onStream: async (event) => {
248
279
  await executeHooks(options.onResponseStream?.({
249
280
  ...responseStreamContext,
250
- event: createProgressEvent({
251
- chunk,
252
- totalBytes,
253
- transferredBytes
254
- })
281
+ event
255
282
  }));
256
- controller.enqueue(chunk);
257
- }
258
- controller.close();
259
- } });
283
+ },
284
+ streamBody
285
+ });
260
286
  return new Response(stream, response);
261
287
  };
262
288
  //#endregion
@@ -265,21 +291,18 @@ const createDedupeStrategy = async (context) => {
265
291
  const { $GlobalRequestInfoCache, $LocalRequestInfoCache, baseConfig, config, newFetchController, options: globalOptions } = context;
266
292
  const dedupeStrategy = globalOptions.dedupeStrategy ?? extraOptionDefaults.dedupeStrategy;
267
293
  const resolvedDedupeStrategy = isFunction(dedupeStrategy) ? dedupeStrategy(context) : dedupeStrategy;
294
+ const shouldDisableDedupe = resolvedDedupeStrategy === "none";
268
295
  const getDedupeKey = () => {
269
- if (!(resolvedDedupeStrategy === "cancel" || resolvedDedupeStrategy === "defer")) return null;
270
296
  const dedupeKey = globalOptions.dedupeKey ?? extraOptionDefaults.dedupeKey;
271
297
  return isFunction(dedupeKey) ? dedupeKey(context) : dedupeKey;
272
298
  };
273
- const getDedupeCacheScopeKey = () => {
274
- const dedupeCacheScopeKey = globalOptions.dedupeCacheScopeKey ?? extraOptionDefaults.dedupeCacheScopeKey;
275
- return isFunction(dedupeCacheScopeKey) ? dedupeCacheScopeKey(context) : dedupeCacheScopeKey;
276
- };
277
- const dedupeKey = getDedupeKey();
299
+ const dedupeKey = shouldDisableDedupe ? null : getDedupeKey();
278
300
  const getRequestInfoCache = () => {
279
- if (!dedupeKey) return;
301
+ if (dedupeKey == null) return;
280
302
  const dedupeCacheScope = globalOptions.dedupeCacheScope ?? extraOptionDefaults.dedupeCacheScope;
281
- const dedupeCacheScopeKey = getDedupeCacheScopeKey();
282
- const $RequestInfoCache = dedupeCacheScope === "global" ? $GlobalRequestInfoCache.get(dedupeCacheScopeKey) ?? $GlobalRequestInfoCache.set(dedupeCacheScopeKey, /* @__PURE__ */ new Map()).get(dedupeCacheScopeKey) : $LocalRequestInfoCache;
303
+ const dedupeCacheScopeKey = globalOptions.dedupeCacheScopeKey ?? extraOptionDefaults.dedupeCacheScopeKey;
304
+ const resolvedDedupeCacheScopeKey = isFunction(dedupeCacheScopeKey) ? dedupeCacheScopeKey(context) : dedupeCacheScopeKey;
305
+ const $RequestInfoCache = dedupeCacheScope === "global" ? $GlobalRequestInfoCache.get(resolvedDedupeCacheScopeKey) ?? $GlobalRequestInfoCache.set(resolvedDedupeCacheScopeKey, /* @__PURE__ */ new Map()).get(resolvedDedupeCacheScopeKey) : $LocalRequestInfoCache;
283
306
  return {
284
307
  delete: () => $RequestInfoCache?.delete(dedupeKey),
285
308
  get: () => $RequestInfoCache?.get(dedupeKey),
@@ -311,35 +334,34 @@ const createDedupeStrategy = async (context) => {
311
334
  * simultaneously (same problem as microtasks). Any non-zero value (even 0.0000000001) forces
312
335
  * proper sequential task queue scheduling, ensuring each request gets its own task slot.
313
336
  */
314
- if (dedupeKey !== null) await waitFor(.01);
337
+ if (!shouldDisableDedupe) await waitFor(.01);
315
338
  const prevRequestInfo = $RequestInfoCache?.get();
316
339
  const getAbortErrorMessage = () => {
317
- if (globalOptions.dedupeKey) return `Duplicate request detected - Aborted previous request with key '${dedupeKey}'`;
318
- return `Duplicate request detected - Aborted previous request to '${globalOptions.fullURL}'`;
340
+ if (shouldDisableDedupe) return;
341
+ return globalOptions.dedupeKey != null ? `Duplicate request detected - Aborted previous request with key '${dedupeKey}'` : `Duplicate request detected - Aborted previous request to '${globalOptions.fullURL}'`;
319
342
  };
320
343
  const handleRequestCancelStrategy = () => {
321
- if (!(prevRequestInfo && resolvedDedupeStrategy === "cancel")) return;
344
+ if (!(!shouldDisableDedupe && prevRequestInfo && resolvedDedupeStrategy === "cancel")) return;
322
345
  const message = getAbortErrorMessage();
323
346
  const reason = new DOMException(message, "AbortError");
324
- prevRequestInfo.controller.abort(reason);
347
+ prevRequestInfo.controller?.abort(reason);
325
348
  };
326
349
  const handleRequestDeferStrategy = async (deferContext) => {
327
350
  const { fetchApi, options: localOptions, request: localRequest } = deferContext;
328
- const shouldUsePromiseFromCache = prevRequestInfo && resolvedDedupeStrategy === "defer";
329
- const streamableContext = {
351
+ const shouldDeferPromise = !shouldDisableDedupe && prevRequestInfo && resolvedDedupeStrategy === "defer";
352
+ const streamContext = {
330
353
  baseConfig,
331
354
  config,
332
355
  options: localOptions,
333
356
  request: localRequest
334
357
  };
335
- const streamableRequest = await toStreamableRequest(streamableContext);
336
- const responsePromise = shouldUsePromiseFromCache ? prevRequestInfo.responsePromise : fetchApi(localOptions.fullURL, streamableRequest);
358
+ const responsePromise = shouldDeferPromise ? prevRequestInfo.responsePromise : fetchApi(localOptions.fullURL, toStreamableRequest(streamContext));
337
359
  $RequestInfoCache?.set({
338
360
  controller: newFetchController,
339
361
  responsePromise
340
362
  });
341
363
  return toStreamableResponse({
342
- ...streamableContext,
364
+ ...streamContext,
343
365
  response: await responsePromise
344
366
  });
345
367
  };
@@ -388,7 +410,7 @@ const getResolvedPlugins = (context) => {
388
410
  const { baseConfig, options } = context;
389
411
  return isFunction(options.plugins) ? options.plugins({ basePlugins: baseConfig.plugins ?? [] }) : options.plugins ?? [];
390
412
  };
391
- const initializePlugins = async (setupContext) => {
413
+ const initializePluginsAndHooks = async (setupContext) => {
392
414
  const { baseConfig, config, currentRouteSchemaKey, mainInitURL, options, request } = setupContext;
393
415
  const { addMainHooks, addMainMiddlewares, addPluginHooks, addPluginMiddlewares, getResolvedHooks, getResolvedMiddlewares } = setupHooksAndMiddlewares({
394
416
  baseConfig,
@@ -399,35 +421,37 @@ const initializePlugins = async (setupContext) => {
399
421
  let resolvedInitURL = mainInitURL;
400
422
  const resolvedOptions = options;
401
423
  const resolvedRequest = request;
402
- const executePluginSetupFn = async (pluginSetup) => {
403
- if (!pluginSetup) return;
404
- const initResult = await pluginSetup(setupContext);
405
- if (!initResult) return;
406
- const urlString = initResult.initURL?.toString();
407
- if (isString(urlString)) {
408
- const newURLResult = getCurrentRouteSchemaKeyAndMainInitURL({
409
- baseExtraOptions: baseConfig,
410
- extraOptions: config,
411
- initURL: urlString
412
- });
413
- resolvedCurrentRouteSchemaKey = newURLResult.currentRouteSchemaKey;
414
- resolvedInitURL = newURLResult.mainInitURL;
415
- }
416
- if (initResult.request) Object.assign(resolvedRequest, initResult.request, initResult.request.extraFetchOptions);
417
- if (initResult.options) Object.assign(resolvedOptions, initResult.options);
418
- };
419
424
  const resolvedPlugins = getResolvedPlugins({
420
425
  baseConfig,
421
426
  options
422
427
  });
423
- for (const plugin of resolvedPlugins) {
424
- const [, pluginHooks, pluginMiddlewares] = await Promise.all([
425
- executePluginSetupFn(plugin.setup),
426
- isFunction(plugin.hooks) ? plugin.hooks(setupContext) : plugin.hooks,
427
- isFunction(plugin.middlewares) ? plugin.middlewares(setupContext) : plugin.middlewares
428
- ]);
429
- pluginHooks && addPluginHooks(pluginHooks);
430
- pluginMiddlewares && addPluginMiddlewares(pluginMiddlewares);
428
+ if (resolvedPlugins.length > 0) {
429
+ const executePluginSetupFn = async (pluginSetup) => {
430
+ if (!pluginSetup) return;
431
+ const initResult = await pluginSetup(setupContext);
432
+ if (!initResult) return;
433
+ const urlString = initResult.initURL?.toString();
434
+ if (isString(urlString)) {
435
+ const newURLResult = getCurrentRouteSchemaKeyAndMainInitURL({
436
+ baseExtraOptions: baseConfig,
437
+ extraOptions: config,
438
+ initURL: urlString
439
+ });
440
+ resolvedCurrentRouteSchemaKey = newURLResult.currentRouteSchemaKey;
441
+ resolvedInitURL = newURLResult.mainInitURL;
442
+ }
443
+ if (initResult.request) Object.assign(resolvedRequest, initResult.request, initResult.request.extraFetchOptions);
444
+ if (initResult.options) Object.assign(resolvedOptions, initResult.options);
445
+ };
446
+ for (const plugin of resolvedPlugins) {
447
+ const [, pluginHooks, pluginMiddlewares] = await Promise.all([
448
+ executePluginSetupFn(plugin.setup),
449
+ isFunction(plugin.hooks) ? plugin.hooks(setupContext) : plugin.hooks,
450
+ isFunction(plugin.middlewares) ? plugin.middlewares(setupContext) : plugin.middlewares
451
+ ]);
452
+ pluginHooks && addPluginHooks(pluginHooks);
453
+ pluginMiddlewares && addPluginMiddlewares(pluginMiddlewares);
454
+ }
431
455
  }
432
456
  addMainHooks();
433
457
  addMainMiddlewares();
@@ -478,17 +502,23 @@ const setupHooksAndMiddlewares = (context) => {
478
502
  };
479
503
  const getResolvedHooks = () => {
480
504
  const resolvedHooks = {};
481
- for (const [hookName, hookRegistry] of Object.entries(hookRegistries)) {
505
+ for (const hookName of hookRegistryKeys) {
506
+ const hookRegistry = hookRegistries[hookName];
482
507
  if (hookRegistry.size === 0) continue;
483
508
  const flattenedHookArray = [...hookRegistry].flat();
484
509
  if (flattenedHookArray.length === 0) continue;
510
+ if (flattenedHookArray.length === 1) {
511
+ resolvedHooks[hookName] = flattenedHookArray[0];
512
+ continue;
513
+ }
485
514
  resolvedHooks[hookName] = composeHooksFromArray(flattenedHookArray, options.hooksExecutionMode ?? extraOptionDefaults.hooksExecutionMode);
486
515
  }
487
516
  return resolvedHooks;
488
517
  };
489
518
  const getResolvedMiddlewares = () => {
490
519
  const resolvedMiddlewares = {};
491
- for (const [middlewareName, middlewareRegistry] of Object.entries(middlewareRegistries)) {
520
+ for (const middlewareName of middlewareRegistryKeys) {
521
+ const middlewareRegistry = middlewareRegistries[middlewareName];
492
522
  if (middlewareRegistry.size === 0) continue;
493
523
  const middlewareArray = [...middlewareRegistry];
494
524
  if (middlewareArray.length === 0) continue;
@@ -592,8 +622,9 @@ const createFetchClientWithContext = () => {
592
622
  const $LocalRequestInfoCache = /* @__PURE__ */ new Map();
593
623
  const callApi = async (initURL, initConfig = {}) => {
594
624
  const [fetchOptions, extraOptions] = splitConfig(initConfig);
625
+ const initURLString = initURL.toString();
595
626
  const baseConfig = isFunction(initBaseConfig) ? initBaseConfig({
596
- initURL: initURL.toString(),
627
+ initURL: initURLString,
597
628
  options: extraOptions,
598
629
  request: fetchOptions
599
630
  }) : initBaseConfig;
@@ -614,9 +645,9 @@ const createFetchClientWithContext = () => {
614
645
  const initURLResult = getCurrentRouteSchemaKeyAndMainInitURL({
615
646
  baseExtraOptions: baseConfig,
616
647
  extraOptions: config,
617
- initURL: initURL.toString()
648
+ initURL: initURLString
618
649
  });
619
- const { resolvedCurrentRouteSchemaKey, resolvedHooks, resolvedInitURL, resolvedMiddlewares, resolvedOptions, resolvedRequest } = await initializePlugins({
650
+ const { resolvedCurrentRouteSchemaKey, resolvedHooks, resolvedInitURL, resolvedMiddlewares, resolvedOptions, resolvedRequest } = await initializePluginsAndHooks({
620
651
  baseConfig,
621
652
  config,
622
653
  ...initURLResult,
@@ -635,6 +666,7 @@ const createFetchClientWithContext = () => {
635
666
  });
636
667
  const { fullURL, normalizedInitURL } = getFullAndNormalizedURL({
637
668
  baseURL: resolvedOptions.baseURL,
669
+ debugMode: resolvedOptions.debugMode,
638
670
  initURL: resolvedInitURL,
639
671
  params: resolvedOptions.params,
640
672
  query: resolvedOptions.query
@@ -656,8 +688,8 @@ const createFetchClientWithContext = () => {
656
688
  options
657
689
  });
658
690
  Object.assign(options, refetchFnResult);
659
- const newFetchController = new AbortController();
660
- const combinedSignal = createCombinedSignal(createTimeoutSignal(options.timeout), resolvedRequest.signal, newFetchController.signal);
691
+ const newFetchController = options.dedupeStrategy === "none" ? null : new AbortController();
692
+ const combinedSignal = createCombinedSignal(createTimeoutSignal(options.timeout), resolvedRequest.signal, newFetchController?.signal);
661
693
  const request = {
662
694
  ...resolvedRequest,
663
695
  signal: combinedSignal