@zayne-labs/callapi 1.8.21 → 1.9.0

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,4 +1,4 @@
1
- import { HTTPError, ValidationError, createCombinedSignal, createTimeoutSignal, dedupeDefaults, defineEnum, deterministicHashFn, getBody, getFetchImpl, getHeaders, hookDefaults, isArray, isFunction, isHTTPErrorInstance, isObject, isPlainObject, isReadableStream, isString, isValidationErrorInstance, requestOptionDefaults, responseDefaults, retryDefaults, splitBaseConfig, splitConfig, toQueryString, waitFor } from "./utils-DZe23qYR.js";
1
+ import { HTTPError, ValidationError, createCombinedSignal, createTimeoutSignal, defineEnum, deterministicHashFn, extraOptionDefaults, getBody, getFetchImpl, getHeaders, isArray, isFunction, isHTTPErrorInstance, isObject, isPlainObject, isReadableStream, isString, isValidationErrorInstance, requestOptionDefaults, splitBaseConfig, splitConfig, toQueryString, waitFor } from "./utils-DUbIqsYh.js";
2
2
 
3
3
  //#region src/result.ts
4
4
  const getResponseType = (response, parser) => ({
@@ -13,8 +13,8 @@ const getResponseType = (response, parser) => ({
13
13
  text: () => response.text()
14
14
  });
15
15
  const resolveResponseData = (response, responseType, parser) => {
16
- const selectedParser = parser ?? responseDefaults.responseParser;
17
- const selectedResponseType = responseType ?? responseDefaults.responseType;
16
+ const selectedParser = parser ?? extraOptionDefaults().responseParser;
17
+ const selectedResponseType = responseType ?? extraOptionDefaults().responseType;
18
18
  const RESPONSE_TYPE_LOOKUP = getResponseType(response, selectedParser);
19
19
  if (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, selectedResponseType)) throw new Error(`Invalid response type: ${responseType}`);
20
20
  return RESPONSE_TYPE_LOOKUP[selectedResponseType]();
@@ -241,71 +241,25 @@ const toStreamableResponse = async (context) => {
241
241
 
242
242
  //#endregion
243
243
  //#region src/dedupe.ts
244
- const resolveDedupeKey = (dedupeKey, context) => {
245
- if (isFunction(dedupeKey)) return dedupeKey(context);
246
- return dedupeKey ?? null;
247
- };
248
- const getAbortErrorMessage = (dedupeKey, context) => {
249
- if (dedupeKey) return `Duplicate request detected - Aborted previous request with key '${resolveDedupeKey(dedupeKey, context)}' as a new request was initiated`;
250
- return `Duplicate request detected - Aborted previous request to '${context.options.fullURL}' as a new request with identical options was initiated`;
251
- };
252
- /**
253
- * @description Creates and manages the deduplication strategy for a request.
254
- *
255
- * This is the core function that implements request deduplication logic. It handles
256
- * cache management, key generation, and provides strategy-specific handlers for
257
- * cancel and defer operations.
258
- *
259
- * **Key Responsibilities:**
260
- * - Generates or resolves deduplication keys
261
- * - Manages cache scope (local vs global)
262
- * - Provides strategy handlers (cancel, defer)
263
- * - Handles cache cleanup operations
264
- *
265
- * **Performance Optimizations:**
266
- * - Includes a small delay (0.1ms) for simultaneous request handling
267
- * - Lazy cache creation for global scopes
268
- * - Efficient cache key resolution
269
- *
270
- * **Internal Usage:**
271
- * This function is used internally by the CallApi system and is not intended
272
- * for direct use in application code. It's called automatically during request
273
- * processing when deduplication is enabled.
274
- *
275
- * @param context - Extended request context with deduplication-specific properties
276
- * @returns Promise resolving to deduplication strategy handlers and metadata
277
- *
278
- * @example
279
- * ```ts
280
- * // This is used internally, but conceptually:
281
- * const strategy = await createDedupeStrategy({
282
- * ...requestContext,
283
- * $GlobalRequestInfoCache: globalCache,
284
- * $LocalRequestInfoCache: localCache,
285
- * newFetchController: new AbortController()
286
- * });
287
- *
288
- * // Use the returned strategy
289
- * await strategy.handleRequestCancelStrategy();
290
- * const response = await strategy.handleRequestDeferStrategy({ options, request });
291
- * strategy.removeDedupeKeyFromCache();
292
- * ```
293
- */
294
244
  const createDedupeStrategy = async (context) => {
295
245
  const { $GlobalRequestInfoCache: $GlobalRequestInfoCache$1, $LocalRequestInfoCache, baseConfig, config, newFetchController, options: globalOptions, request: globalRequest } = context;
296
- const dedupeStrategy = globalOptions.dedupeStrategy ?? dedupeDefaults.dedupeStrategy;
246
+ const dedupeStrategy = globalOptions.dedupeStrategy ?? extraOptionDefaults().dedupeStrategy;
247
+ const resolvedDedupeStrategy = isFunction(dedupeStrategy) ? dedupeStrategy(context) : dedupeStrategy;
297
248
  const getDedupeKey = () => {
298
- const shouldHaveDedupeKey = dedupeStrategy === "cancel" || dedupeStrategy === "defer";
249
+ const shouldHaveDedupeKey = resolvedDedupeStrategy === "cancel" || resolvedDedupeStrategy === "defer";
299
250
  if (!shouldHaveDedupeKey) return null;
300
- if (globalOptions.dedupeKey) return resolveDedupeKey(globalOptions.dedupeKey, context);
251
+ if (globalOptions.dedupeKey) {
252
+ const resolvedDedupeKey = isFunction(globalOptions.dedupeKey) ? globalOptions.dedupeKey(context) : globalOptions.dedupeKey;
253
+ return resolvedDedupeKey;
254
+ }
301
255
  return `${globalOptions.fullURL}-${deterministicHashFn({
302
256
  options: globalOptions,
303
257
  request: globalRequest
304
258
  })}`;
305
259
  };
306
260
  const dedupeKey = getDedupeKey();
307
- const dedupeCacheScope = globalOptions.dedupeCacheScope ?? dedupeDefaults.dedupeCacheScope;
308
- const dedupeCacheScopeKey = globalOptions.dedupeCacheScopeKey ?? dedupeDefaults.dedupeCacheScopeKey;
261
+ const dedupeCacheScope = globalOptions.dedupeCacheScope ?? extraOptionDefaults().dedupeCacheScope;
262
+ const dedupeCacheScopeKey = globalOptions.dedupeCacheScopeKey ?? extraOptionDefaults().dedupeCacheScopeKey;
309
263
  if (dedupeCacheScope === "global" && !$GlobalRequestInfoCache$1.has(dedupeCacheScopeKey)) $GlobalRequestInfoCache$1.set(dedupeCacheScopeKey, /* @__PURE__ */ new Map());
310
264
  const $RequestInfoCache = dedupeCacheScope === "global" ? $GlobalRequestInfoCache$1.get(dedupeCacheScopeKey) : $LocalRequestInfoCache;
311
265
  const $RequestInfoCacheOrNull = dedupeKey !== null ? $RequestInfoCache : null;
@@ -315,10 +269,14 @@ const createDedupeStrategy = async (context) => {
315
269
  ******/
316
270
  if (dedupeKey !== null) await waitFor(.1);
317
271
  const prevRequestInfo = $RequestInfoCacheOrNull?.get(dedupeKey);
272
+ const getAbortErrorMessage = () => {
273
+ if (globalOptions.dedupeKey) return `Duplicate request detected - Aborted previous request with key '${dedupeKey}' as a new request was initiated`;
274
+ return `Duplicate request detected - Aborted previous request to '${globalOptions.fullURL}' as a new request with identical options was initiated`;
275
+ };
318
276
  const handleRequestCancelStrategy = () => {
319
- const shouldCancelRequest = prevRequestInfo && dedupeStrategy === "cancel";
277
+ const shouldCancelRequest = prevRequestInfo && resolvedDedupeStrategy === "cancel";
320
278
  if (!shouldCancelRequest) return;
321
- const message = getAbortErrorMessage(globalOptions.dedupeKey, context);
279
+ const message = getAbortErrorMessage();
322
280
  const reason = new DOMException(message, "AbortError");
323
281
  prevRequestInfo.controller.abort(reason);
324
282
  return Promise.resolve();
@@ -326,7 +284,7 @@ const createDedupeStrategy = async (context) => {
326
284
  const handleRequestDeferStrategy = async (deferContext) => {
327
285
  const { options: localOptions, request: localRequest } = deferContext;
328
286
  const fetchApi = getFetchImpl(localOptions.customFetchImpl);
329
- const shouldUsePromiseFromCache = prevRequestInfo && dedupeStrategy === "defer";
287
+ const shouldUsePromiseFromCache = prevRequestInfo && resolvedDedupeStrategy === "defer";
330
288
  const streamableContext = {
331
289
  baseConfig,
332
290
  config,
@@ -349,10 +307,11 @@ const createDedupeStrategy = async (context) => {
349
307
  $RequestInfoCacheOrNull?.delete(dedupeKey);
350
308
  };
351
309
  return {
352
- dedupeStrategy,
310
+ getAbortErrorMessage,
353
311
  handleRequestCancelStrategy,
354
312
  handleRequestDeferStrategy,
355
- removeDedupeKeyFromCache
313
+ removeDedupeKeyFromCache,
314
+ resolvedDedupeStrategy
356
315
  };
357
316
  };
358
317
 
@@ -534,7 +493,7 @@ const initializePlugins = async (context) => {
534
493
  clonedHookRegistries[key].add(pluginHook);
535
494
  }
536
495
  };
537
- const hookRegistrationOrder = options.hooksRegistrationOrder ?? hookDefaults.hooksRegistrationOrder;
496
+ const hookRegistrationOrder = options.hooksRegistrationOrder ?? extraOptionDefaults().hooksRegistrationOrder;
538
497
  if (hookRegistrationOrder === "mainFirst") addMainHooks();
539
498
  const { currentRouteSchemaKey, mainInitURL } = getCurrentRouteSchemaKeyAndMainInitURL({
540
499
  baseExtraOptions: baseConfig,
@@ -545,9 +504,9 @@ const initializePlugins = async (context) => {
545
504
  let resolvedInitURL = mainInitURL;
546
505
  let resolvedOptions = options;
547
506
  let resolvedRequestOptions = request;
548
- const executePluginInit = async (pluginInit) => {
549
- if (!pluginInit) return;
550
- const initResult = await pluginInit({
507
+ const executePluginSetupFn = async (pluginSetupFn) => {
508
+ if (!pluginSetupFn) return;
509
+ const initResult = await pluginSetupFn({
551
510
  baseConfig,
552
511
  config,
553
512
  initURL,
@@ -573,7 +532,7 @@ const initializePlugins = async (context) => {
573
532
  options
574
533
  });
575
534
  for (const plugin of resolvedPlugins) {
576
- await executePluginInit(plugin.init);
535
+ await executePluginSetupFn(plugin.setup);
577
536
  if (!plugin.hooks) continue;
578
537
  addPluginHooks(plugin.hooks);
579
538
  }
@@ -583,7 +542,7 @@ const initializePlugins = async (context) => {
583
542
  if (hookRegistry.size === 0) continue;
584
543
  const flattenedHookArray = [...hookRegistry].flat();
585
544
  if (flattenedHookArray.length === 0) continue;
586
- const hooksExecutionMode = options.hooksExecutionMode ?? hookDefaults.hooksExecutionMode;
545
+ const hooksExecutionMode = options.hooksExecutionMode ?? extraOptionDefaults().hooksExecutionMode;
587
546
  const composedHook = composeAllHooks(flattenedHookArray, hooksExecutionMode);
588
547
  resolvedHooks[key] = composedHook;
589
548
  }
@@ -600,20 +559,20 @@ const initializePlugins = async (context) => {
600
559
  //#region src/retry.ts
601
560
  const getLinearDelay = (currentAttemptCount, options) => {
602
561
  const retryDelay = options.retryDelay ?? options.retry?.delay;
603
- const resolveRetryDelay = (isFunction(retryDelay) ? retryDelay(currentAttemptCount) : retryDelay) ?? retryDefaults.delay;
562
+ const resolveRetryDelay = (isFunction(retryDelay) ? retryDelay(currentAttemptCount) : retryDelay) ?? extraOptionDefaults().retryDelay;
604
563
  return resolveRetryDelay;
605
564
  };
606
565
  const getExponentialDelay = (currentAttemptCount, options) => {
607
- const retryDelay = options.retryDelay ?? options.retry?.delay ?? retryDefaults.delay;
566
+ const retryDelay = options.retryDelay ?? options.retry?.delay ?? extraOptionDefaults().retryDelay;
608
567
  const resolvedRetryDelay = isFunction(retryDelay) ? retryDelay(currentAttemptCount) : retryDelay;
609
- const maxDelay = options.retryMaxDelay ?? options.retry?.maxDelay ?? retryDefaults.maxDelay;
568
+ const maxDelay = options.retryMaxDelay ?? options.retry?.maxDelay ?? extraOptionDefaults().retryMaxDelay;
610
569
  const exponentialDelay = resolvedRetryDelay * 2 ** currentAttemptCount;
611
570
  return Math.min(exponentialDelay, maxDelay);
612
571
  };
613
572
  const createRetryStrategy = (ctx) => {
614
573
  const { options } = ctx;
615
574
  const currentAttemptCount = options["~retryAttemptCount"] ?? 1;
616
- const retryStrategy = options.retryStrategy ?? options.retry?.strategy ?? retryDefaults.strategy;
575
+ const retryStrategy = options.retryStrategy ?? options.retry?.strategy ?? extraOptionDefaults().retryStrategy;
617
576
  const getDelay = () => {
618
577
  switch (retryStrategy) {
619
578
  case "exponential": return getExponentialDelay(currentAttemptCount, options);
@@ -622,14 +581,13 @@ const createRetryStrategy = (ctx) => {
622
581
  }
623
582
  };
624
583
  const shouldAttemptRetry = async () => {
625
- const retryCondition = options.retryCondition ?? options.retry?.condition ?? retryDefaults.condition;
626
- const maximumRetryAttempts = options.retryAttempts ?? options.retry?.attempts ?? retryDefaults.attempts;
584
+ const retryCondition = options.retryCondition ?? options.retry?.condition ?? extraOptionDefaults().retryCondition;
585
+ const maximumRetryAttempts = options.retryAttempts ?? options.retry?.attempts ?? extraOptionDefaults().retryAttempts;
627
586
  const customRetryCondition = await retryCondition(ctx);
628
587
  const baseShouldRetry = maximumRetryAttempts >= currentAttemptCount && customRetryCondition;
629
588
  if (!baseShouldRetry) return false;
630
- const retryMethods = new Set(options.retryMethods ?? options.retry?.methods ?? retryDefaults.methods);
631
- const resolvedMethod = ctx.request.method ?? requestOptionDefaults.method;
632
- const includesMethod = retryMethods.has(resolvedMethod);
589
+ const retryMethods = new Set(options.retryMethods ?? options.retry?.methods ?? extraOptionDefaults().retryMethods);
590
+ const includesMethod = retryMethods.has(ctx.request.method);
633
591
  const retryStatusCodes = new Set(options.retryStatusCodes ?? options.retry?.statusCodes ?? []);
634
592
  const includesStatusCodes = Boolean(ctx.response?.status) && (retryStatusCodes.size > 0 ? retryStatusCodes.has(ctx.response.status) : true);
635
593
  const shouldRetry = includesMethod && includesStatusCodes;
@@ -645,19 +603,28 @@ const createRetryStrategy = (ctx) => {
645
603
  //#endregion
646
604
  //#region src/url.ts
647
605
  const slash = "/";
648
- const column = ":";
606
+ const colon = ":";
607
+ const openBrace = "{";
608
+ const closeBrace = "}";
649
609
  const mergeUrlWithParams = (url, params) => {
650
610
  if (!params) return url;
651
611
  let newUrl = url;
652
612
  if (isArray(params)) {
653
- const matchedParamArray = newUrl.split(slash).filter((param) => param.startsWith(column));
654
- for (const [index, matchedParam] of matchedParamArray.entries()) {
613
+ const urlParts = newUrl.split(slash);
614
+ const matchedParamsArray = urlParts.filter((part) => part.startsWith(colon) || part.startsWith(openBrace) && part.endsWith(closeBrace));
615
+ for (const [index, matchedParam] of matchedParamsArray.entries()) {
655
616
  const realParam = params[index];
656
617
  newUrl = newUrl.replace(matchedParam, realParam);
657
618
  }
658
619
  return newUrl;
659
620
  }
660
- for (const [key, value] of Object.entries(params)) newUrl = newUrl.replace(`${column}${key}`, String(value));
621
+ for (const [key, value] of Object.entries(params)) {
622
+ const stringValue = String(value);
623
+ const colonPattern = `${colon}${key}`;
624
+ const bracePattern = `${openBrace}${key}${closeBrace}`;
625
+ if (newUrl.includes(colonPattern)) newUrl = newUrl.replace(colonPattern, stringValue);
626
+ else if (newUrl.includes(bracePattern)) newUrl = newUrl.replace(bracePattern, stringValue);
627
+ }
661
628
  return newUrl;
662
629
  };
663
630
  const questionMark = "?";
@@ -708,10 +675,9 @@ const extractMethodFromURL = (initURL) => {
708
675
  if (!method || !routeKeyMethods.includes(method)) return;
709
676
  return method;
710
677
  };
711
- const getMethod = (options) => {
712
- const { initURL, method, schemaConfig } = options;
713
- if (schemaConfig?.requireMethodProvision === true) return method?.toUpperCase() ?? requestOptionDefaults.method;
714
- return method?.toUpperCase() ?? extractMethodFromURL(initURL)?.toUpperCase() ?? requestOptionDefaults.method;
678
+ const getMethod = (ctx) => {
679
+ const { initURL, method } = ctx;
680
+ return method?.toUpperCase() ?? extractMethodFromURL(initURL)?.toUpperCase() ?? requestOptionDefaults().method;
715
681
  };
716
682
  const normalizeURL = (initURL) => {
717
683
  const methodFromURL = extractMethodFromURL(initURL);
@@ -724,8 +690,8 @@ const getFullAndNormalizedURL = (options) => {
724
690
  const normalizedInitURL = normalizeURL(initURL);
725
691
  const urlWithMergedParams = mergeUrlWithParams(normalizedInitURL, params);
726
692
  const urlWithMergedQueryAndParams = mergeUrlWithQuery(urlWithMergedParams, query);
727
- const shouldNotPrependBaseURL = urlWithMergedQueryAndParams.startsWith("http") || !baseURL;
728
- const fullURL = shouldNotPrependBaseURL ? urlWithMergedQueryAndParams : `${baseURL}${urlWithMergedQueryAndParams}`;
693
+ const shouldPrependBaseURL = !urlWithMergedQueryAndParams.startsWith("http") && baseURL;
694
+ const fullURL = shouldPrependBaseURL ? `${baseURL}${urlWithMergedQueryAndParams}` : urlWithMergedQueryAndParams;
729
695
  return {
730
696
  fullURL,
731
697
  normalizedInitURL
@@ -786,7 +752,7 @@ const createFetchClient = (initBaseConfig = {}) => {
786
752
  ...resolvedRequestOptions,
787
753
  signal: combinedSignal
788
754
  };
789
- const { dedupeStrategy, handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache } = await createDedupeStrategy({
755
+ const { getAbortErrorMessage, handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache, resolvedDedupeStrategy } = await createDedupeStrategy({
790
756
  $GlobalRequestInfoCache,
791
757
  $LocalRequestInfoCache,
792
758
  baseConfig,
@@ -839,7 +805,7 @@ const createFetchClient = (initBaseConfig = {}) => {
839
805
  options,
840
806
  request
841
807
  });
842
- const shouldCloneResponse = dedupeStrategy === "defer" || options.cloneResponse;
808
+ const shouldCloneResponse = resolvedDedupeStrategy === "defer" || options.cloneResponse;
843
809
  if (!response.ok) {
844
810
  const errorData = await resolveResponseData(shouldCloneResponse ? response.clone() : response, options.responseType, options.responseParser);
845
811
  const validErrorData = await handleValidation(resolvedSchema?.errorData, {
@@ -937,7 +903,7 @@ const createFetchClient = (initBaseConfig = {}) => {
937
903
  }
938
904
  let message = error?.message;
939
905
  if (error instanceof DOMException && error.name === "AbortError") {
940
- message = getAbortErrorMessage(options.dedupeKey, errorContext);
906
+ message = getAbortErrorMessage();
941
907
  !shouldThrowOnError && console.error(`${error.name}:`, message);
942
908
  }
943
909
  if (error instanceof DOMException && error.name === "TimeoutError") {
@@ -958,8 +924,8 @@ const callApi = createFetchClient();
958
924
  //#region src/defineHelpers.ts
959
925
  const defineSchema = (routes, config) => {
960
926
  return {
961
- config,
962
- routes
927
+ config: defineSchemaConfig(config),
928
+ routes: defineSchemaRoutes(routes)
963
929
  };
964
930
  };
965
931
  const defineSchemaConfig = (config) => {