@zayne-labs/callapi 1.8.20 → 1.8.21

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.
@@ -215,32 +215,107 @@ declare class ValidationError extends Error {
215
215
  type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
216
216
  type Params = UnmaskType<Record<string, AllowedQueryParamValues> | AllowedQueryParamValues[]>;
217
217
  type Query = UnmaskType<Record<string, AllowedQueryParamValues>>;
218
- type InitURLOrURLObject = AnyString | URL;
218
+ type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
219
219
  interface URLOptions {
220
220
  /**
221
- * Base URL to be prepended to all request URLs
221
+ * Base URL to be prepended to all request URLs.
222
+ *
223
+ * When provided, this will be prepended to relative URLs. Absolute URLs (starting with http/https) will not be prepended by the baseURL.
224
+ *
222
225
  */
223
226
  baseURL?: string;
224
227
  /**
225
- * Resolved request URL
228
+ * Resolved request URL after processing baseURL, parameters, and query strings.
229
+ *
230
+ * This is the final URL that will be used for the HTTP request, computed from
231
+ * baseURL, initURL, params, and query parameters.
232
+ *
233
+ * @readonly
226
234
  */
227
235
  readonly fullURL?: string;
228
236
  /**
229
- * The url string passed to the callApi instance
237
+ * The original URL string passed to the callApi instance.
238
+ *
239
+ * This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
240
+ *
241
+ * @readonly
230
242
  */
231
243
  readonly initURL?: string;
232
244
  /**
233
- * The URL string passed to the callApi instance, but normalized (removed any method modifiers etc)
245
+ * The URL string after normalization, with method modifiers removed.
246
+ *
247
+ * Method modifiers like "@get/", "@post/" are stripped to create a clean URL
248
+ * for parameter substitution and final URL construction.
249
+ *
250
+ * @readonly
251
+ *
252
+ *
234
253
  */
235
254
  readonly initURLNormalized?: string;
236
255
  /**
237
- * Parameters to be appended to the URL (i.e: /:id)
256
+ * Parameters to be substituted into URL path segments.
257
+ *
258
+ * Supports both object-style (named parameters) and array-style (positional parameters)
259
+ * for flexible URL parameter substitution.
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * // Object-style parameters (recommended)
264
+ * const namedParams: URLOptions = {
265
+ * initURL: "/users/:userId/posts/:postId",
266
+ * params: { userId: "123", postId: "456" }
267
+ * };
268
+ * // Results in: /users/123/posts/456
238
269
  *
239
- * If url is defined as `/path/:id`, params will be `{ id: string }`
270
+ * // Array-style parameters (positional)
271
+ * const positionalParams: URLOptions = {
272
+ * initURL: "/users/:userId/posts/:postId",
273
+ * params: ["123", "456"] // Maps in order: userId=123, postId=456
274
+ * };
275
+ * // Results in: /users/123/posts/456
276
+ *
277
+ * // Single parameter
278
+ * const singleParam: URLOptions = {
279
+ * initURL: "/users/:id",
280
+ * params: { id: "user-123" }
281
+ * };
282
+ * // Results in: /users/user-123
283
+ * ```
240
284
  */
241
285
  params?: Params;
242
286
  /**
243
- * Query parameters to append to the URL.
287
+ * Query parameters to append to the URL as search parameters.
288
+ *
289
+ * These will be serialized into the URL query string using standard
290
+ * URL encoding practices.
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Basic query parameters
295
+ * const queryOptions: URLOptions = {
296
+ * initURL: "/users",
297
+ * query: {
298
+ * page: 1,
299
+ * limit: 10,
300
+ * search: "john doe",
301
+ * active: true
302
+ * }
303
+ * };
304
+ * // Results in: /users?page=1&limit=10&search=john%20doe&active=true
305
+ *
306
+ * // Filtering and sorting
307
+ * const filterOptions: URLOptions = {
308
+ * initURL: "/products",
309
+ * query: {
310
+ * category: "electronics",
311
+ * minPrice: 100,
312
+ * maxPrice: 500,
313
+ * sortBy: "price",
314
+ * order: "asc"
315
+ * }
316
+ * };
317
+ * // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
318
+ * ```
244
319
  */
245
320
  query?: Query;
246
321
  }
@@ -327,9 +402,8 @@ interface CallApiSchema {
327
402
  }
328
403
  declare const routeKeyMethods: ["delete", "get", "patch", "post", "put"];
329
404
  type RouteKeyMethods = (typeof routeKeyMethods)[number];
330
- type RouteKeyMethodsURLUnion = `${RouteKeyMethods}/`;
331
- type PossibleRouteKey = AnyString | RouteKeyMethodsURLUnion;
332
- type BaseCallApiSchemaRoutes = Partial<Record<PossibleRouteKey, CallApiSchema>>;
405
+ type RouteKeyMethodsURLUnion = `@${RouteKeyMethods}/`;
406
+ type BaseCallApiSchemaRoutes = Partial<Record<AnyString | RouteKeyMethodsURLUnion, CallApiSchema>>;
333
407
  type BaseCallApiSchemaAndConfig = {
334
408
  config?: CallApiSchemaConfig;
335
409
  routes: BaseCallApiSchemaRoutes;
@@ -468,134 +542,566 @@ declare global {
468
542
  //#endregion
469
543
  //#region src/hooks.d.ts
470
544
  type PluginExtraOptions<TPluginOptions = unknown> = {
545
+ /** Plugin-specific options passed to the plugin configuration */
471
546
  options: Partial<TPluginOptions>;
472
547
  };
473
548
  interface Hooks<TData = DefaultDataType, TErrorData = DefaultDataType, TPluginOptions = unknown> {
474
549
  /**
475
- * Hook that will be called when any error occurs within the request/response lifecycle, regardless of whether the error is from the api or not.
476
- * It is basically a combination of `onRequestError` and `onResponseError` hooks
550
+ * Hook called when any error occurs within the request/response lifecycle.
551
+ *
552
+ * This is a unified error handler that catches both request errors (network failures,
553
+ * timeouts, etc.) and response errors (HTTP error status codes). It's essentially
554
+ * a combination of `onRequestError` and `onResponseError` hooks.
555
+ *
556
+ * @param context - Error context containing error details, request info, and response (if available)
557
+ * @returns Promise or void - Hook can be async or sync
477
558
  */
478
559
  onError?: (context: ErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
479
560
  /**
480
- * Hook that will be called just before the request is being made.
561
+ * Hook called just before the HTTP request is sent.
562
+ *
563
+ * This is the ideal place to modify request headers, add authentication,
564
+ * implement request logging, or perform any setup before the network call.
565
+ *
566
+ * @param context - Request context with mutable request object and configuration
567
+ * @returns Promise or void - Hook can be async or sync
568
+ *
481
569
  */
482
570
  onRequest?: (context: RequestContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
483
571
  /**
484
- * Hook that will be called when an error occurs during the fetch request.
572
+ * Hook called when an error occurs during the fetch request itself.
573
+ *
574
+ * This handles network-level errors like connection failures, timeouts,
575
+ * DNS resolution errors, or other issues that prevent getting an HTTP response.
576
+ * Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
577
+ *
578
+ * @param context - Request error context with error details and null response
579
+ * @returns Promise or void - Hook can be async or sync
485
580
  */
486
581
  onRequestError?: (context: RequestErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
487
582
  /**
488
- * Hook that will be called when upload stream progress is tracked
583
+ * Hook called during upload stream progress tracking.
584
+ *
585
+ * This hook is triggered when uploading data (like file uploads) and provides
586
+ * progress information about the upload. Useful for implementing progress bars
587
+ * or upload status indicators.
588
+ *
589
+ * @param context - Request stream context with progress event and request instance
590
+ * @returns Promise or void - Hook can be async or sync
591
+ *
489
592
  */
490
593
  onRequestStream?: (context: RequestStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
491
594
  /**
492
- * Hook that will be called when any response is received from the api, whether successful or not
595
+ * Hook called when any HTTP response is received from the API.
596
+ *
597
+ * This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
598
+ * It's useful for response logging, metrics collection, or any processing that
599
+ * should happen regardless of response status.
600
+ *
601
+ * @param context - Response context with either success data or error information
602
+ * @returns Promise or void - Hook can be async or sync
603
+ *
493
604
  */
494
605
  onResponse?: (context: ResponseContext<TData, TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
495
606
  /**
496
- * Hook that will be called when an error response is received from the api.
607
+ * Hook called when an HTTP error response (4xx, 5xx) is received from the API.
608
+ *
609
+ * This handles server-side errors where an HTTP response was successfully received
610
+ * but indicates an error condition. Different from `onRequestError` which handles
611
+ * network-level failures.
612
+ *
613
+ * @param context - Response error context with HTTP error details and response
614
+ * @returns Promise or void - Hook can be async or sync
497
615
  */
498
616
  onResponseError?: (context: ResponseErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
499
617
  /**
500
- * Hook that will be called when download stream progress is tracked
618
+ * Hook called during download stream progress tracking.
619
+ *
620
+ * This hook is triggered when downloading data (like file downloads) and provides
621
+ * progress information about the download. Useful for implementing progress bars
622
+ * or download status indicators.
623
+ *
624
+ * @param context - Response stream context with progress event and response
625
+ * @returns Promise or void - Hook can be async or sync
626
+ *
501
627
  */
502
628
  onResponseStream?: (context: ResponseStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
503
629
  /**
504
- * Hook that will be called when a request is retried.
630
+ * Hook called when a request is being retried.
631
+ *
632
+ * This hook is triggered before each retry attempt, providing information about
633
+ * the previous failure and the current retry attempt number. Useful for implementing
634
+ * custom retry logic, exponential backoff, or retry logging.
635
+ *
636
+ * @param context - Retry context with error details and retry attempt count
637
+ * @returns Promise or void - Hook can be async or sync
638
+ *
505
639
  */
506
640
  onRetry?: (response: RetryContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
507
641
  /**
508
- * Hook that will be called when a successful response is received from the api.
642
+ * Hook called when a successful response (2xx status) is received from the API.
643
+ *
644
+ * This hook is triggered only for successful responses and provides access to
645
+ * the parsed response data. Ideal for success logging, caching, or post-processing
646
+ * of successful API responses.
647
+ *
648
+ * @param context - Success context with parsed response data and response object
649
+ * @returns Promise or void - Hook can be async or sync
650
+ *
509
651
  */
510
652
  onSuccess?: (context: SuccessContext<TData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
511
653
  /**
512
- * Hook that will be called when a validation error occurs.
654
+ * Hook called when a validation error occurs.
655
+ *
656
+ * This hook is triggered when request or response data fails validation against
657
+ * a defined schema. It provides access to the validation error details and can
658
+ * be used for custom error handling, logging, or fallback behavior.
659
+ *
660
+ * @param context - Validation error context with error details and response (if available)
661
+ * @returns Promise or void - Hook can be async or sync
662
+ *
513
663
  */
514
664
  onValidationError?: (context: ValidationErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
515
665
  }
516
666
  type HooksOrHooksArray<TData = DefaultDataType, TErrorData = DefaultDataType, TMoreOptions = unknown> = { [Key in keyof Hooks<TData, TErrorData, TMoreOptions>]: Hooks<TData, TErrorData, TMoreOptions>[Key] | Array<Hooks<TData, TErrorData, TMoreOptions>[Key]> };
667
+ interface HookConfigOptions {
668
+ /**
669
+ * Controls the execution mode of all composed hooks (main + plugin hooks).
670
+ *
671
+ * - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
672
+ * - **"sequential"**: All hooks execute one by one in registration order via await in a loop
673
+ *
674
+ * This affects how ALL hooks execute together, regardless of their source (main or plugin).
675
+ *
676
+ * Use `hookRegistrationOrder` to control the registration order of main vs plugin hooks.
677
+ *
678
+ * @default "parallel"
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * // Parallel execution (default) - all hooks run simultaneously
683
+ * hooksExecutionMode: "parallel"
684
+ *
685
+ * // Sequential execution - hooks run one after another
686
+ * hooksExecutionMode: "sequential"
687
+ *
688
+ * // Use case: Hooks have dependencies and must run in order
689
+ * const client = callApi.create({
690
+ * hooksExecutionMode: "sequential",
691
+ * hookRegistrationOrder: "mainFirst",
692
+ * plugins: [transformPlugin],
693
+ * onRequest: (ctx) => {
694
+ * // This runs first, then transform plugin runs
695
+ * ctx.request.headers["x-request-id"] = generateId();
696
+ * }
697
+ * });
698
+ *
699
+ * // Use case: Independent operations can run in parallel for speed
700
+ * const client = callApi.create({
701
+ * hooksExecutionMode: "parallel", // Default
702
+ * plugins: [metricsPlugin, cachePlugin, loggingPlugin],
703
+ * onRequest: (ctx) => {
704
+ * // All hooks (main + plugins) run simultaneously
705
+ * addRequestTimestamp(ctx.request);
706
+ * }
707
+ * });
708
+ *
709
+ * // Use case: Error handling hooks that need sequential processing
710
+ * const client = callApi.create({
711
+ * hooksExecutionMode: "sequential",
712
+ * onError: [
713
+ * (ctx) => logError(ctx.error), // Log first
714
+ * (ctx) => reportError(ctx.error), // Then report
715
+ * (ctx) => cleanupResources(ctx) // Finally cleanup
716
+ * ]
717
+ * });
718
+ * ```
719
+ */
720
+ hooksExecutionMode?: "parallel" | "sequential";
721
+ /**
722
+ * Controls the registration order of main hooks relative to plugin hooks.
723
+ *
724
+ * - **"pluginsFirst"**: Plugin hooks register first, then main hooks (default)
725
+ * - **"mainFirst"**: Main hooks register first, then plugin hooks
726
+ *
727
+ * This determines the order hooks are added to the registry, which affects
728
+ * their execution sequence when using sequential execution mode.
729
+ *
730
+ * @default "pluginsFirst"
731
+ *
732
+ * @example
733
+ * ```ts
734
+ * // Plugin hooks register first (default behavior)
735
+ * hookRegistrationOrder: "pluginsFirst"
736
+ *
737
+ * // Main hooks register first
738
+ * hookRegistrationOrder: "mainFirst"
739
+ *
740
+ * // Use case: Main validation before plugin processing
741
+ * const client = callApi.create({
742
+ * hookRegistrationOrder: "mainFirst",
743
+ * hooksExecutionMode: "sequential",
744
+ * plugins: [transformPlugin],
745
+ * onRequest: (ctx) => {
746
+ * // This main hook runs first in sequential mode
747
+ * if (!ctx.request.headers.authorization) {
748
+ * throw new Error("Authorization required");
749
+ * }
750
+ * }
751
+ * });
752
+ *
753
+ * // Use case: Plugin setup before main logic (default)
754
+ * const client = callApi.create({
755
+ * hookRegistrationOrder: "pluginsFirst", // Default
756
+ * hooksExecutionMode: "sequential",
757
+ * plugins: [setupPlugin],
758
+ * onRequest: (ctx) => {
759
+ * // Plugin runs first, then this main hook
760
+ * console.log("Request prepared:", ctx.request.url);
761
+ * }
762
+ * });
763
+ *
764
+ * // Use case: Parallel mode (registration order less important)
765
+ * const client = callApi.create({
766
+ * hookRegistrationOrder: "pluginsFirst",
767
+ * hooksExecutionMode: "parallel", // All run simultaneously
768
+ * plugins: [metricsPlugin, cachePlugin],
769
+ * onRequest: (ctx) => {
770
+ * // All hooks run in parallel regardless of registration order
771
+ * addRequestId(ctx.request);
772
+ * }
773
+ * });
774
+ * ```
775
+ */
776
+ hooksRegistrationOrder?: "mainFirst" | "pluginsFirst";
777
+ }
517
778
  type RequestContext = {
518
779
  /**
519
- * Config object passed to createFetchClient
780
+ * Base configuration object passed to createFetchClient.
781
+ *
782
+ * Contains the foundational configuration that applies to all requests
783
+ * made by this client instance, such as baseURL, default headers, and
784
+ * global options.
520
785
  */
521
786
  baseConfig: BaseCallApiExtraOptions & CallApiRequestOptions;
522
787
  /**
523
- * Config object passed to the callApi instance
788
+ * Instance-specific configuration object passed to the callApi instance.
789
+ *
790
+ * Contains configuration specific to this particular API call, which
791
+ * can override or extend the base configuration.
524
792
  */
525
793
  config: CallApiExtraOptions & CallApiRequestOptions;
526
794
  /**
527
- * Merged options consisting of extra options from createFetchClient, the callApi instance and default options.
795
+ * Merged options combining base config, instance config, and default options.
528
796
  *
797
+ * This is the final resolved configuration that will be used for the request,
798
+ * with proper precedence applied (instance > base > defaults).
529
799
  */
530
800
  options: CallApiExtraOptionsForHooks;
531
801
  /**
532
- * Merged request consisting of request options from createFetchClient, the callApi instance and default request options.
802
+ * Merged request object ready to be sent.
803
+ *
804
+ * Contains the final request configuration including URL, method, headers,
805
+ * body, and other fetch options. This object can be modified in onRequest
806
+ * hooks to customize the outgoing request.
533
807
  */
534
808
  request: CallApiRequestOptionsForHooks;
535
809
  };
536
810
  type ValidationErrorContext = UnmaskType<RequestContext & {
811
+ /** Validation error containing details about what failed validation */
537
812
  error: ValidationError;
813
+ /** HTTP response object if validation failed on response, null if on request */
538
814
  response: Response | null;
539
815
  }>;
540
816
  type SuccessContext<TData> = UnmaskType<RequestContext & {
817
+ /** Parsed response data with the expected success type */
541
818
  data: TData;
819
+ /** HTTP response object for the successful request */
542
820
  response: Response;
543
821
  }>;
544
822
  type ResponseContext<TData, TErrorData> = UnmaskType<RequestContext & (Prettify<CallApiResultSuccessVariant<TData>> | Prettify<Extract<CallApiResultErrorVariant<TErrorData>, {
545
823
  error: PossibleHTTPError<TErrorData>;
546
824
  }>>)>;
547
825
  type RequestErrorContext = RequestContext & {
826
+ /** Error that occurred during the request (network, timeout, etc.) */
548
827
  error: PossibleJavaScriptOrValidationError;
828
+ /** Always null for request errors since no response was received */
549
829
  response: null;
550
830
  };
551
831
  type ErrorContext<TErrorData> = UnmaskType<RequestContext & ({
832
+ /** HTTP error with response data */
552
833
  error: PossibleHTTPError<TErrorData>;
834
+ /** HTTP response object containing error status */
553
835
  response: Response;
554
836
  } | {
837
+ /** Request-level error (network, timeout, validation, etc.) */
555
838
  error: PossibleJavaScriptOrValidationError;
839
+ /** Response object if available, null for request errors */
556
840
  response: Response | null;
557
841
  })>;
558
842
  type ResponseErrorContext<TErrorData> = UnmaskType<Extract<ErrorContext<TErrorData>, {
559
843
  error: PossibleHTTPError<TErrorData>;
560
844
  }> & RequestContext>;
561
845
  type RetryContext<TErrorData> = UnmaskType<ErrorContext<TErrorData> & {
846
+ /** Current retry attempt number (1-based, so 1 = first retry) */
562
847
  retryAttemptCount: number;
563
848
  }>;
564
849
  type RequestStreamContext = UnmaskType<RequestContext & {
850
+ /** Progress event containing loaded/total bytes information */
565
851
  event: StreamProgressEvent;
852
+ /** The actual Request instance being uploaded */
566
853
  requestInstance: Request;
567
854
  }>;
568
855
  type ResponseStreamContext = UnmaskType<RequestContext & {
856
+ /** Progress event containing loaded/total bytes information */
569
857
  event: StreamProgressEvent;
858
+ /** HTTP response object being downloaded */
570
859
  response: Response;
571
860
  }>;
572
861
  //#endregion
573
862
  //#region src/dedupe.d.ts
574
863
  type DedupeOptions = {
575
864
  /**
576
- * Defines the scope of the deduplication cache, can be set to "global" | "local".
577
- * - If set to "global", the deduplication cache will be shared across all requests, regardless of whether they shared the same `createFetchClient` or not.
578
- * - If set to "local", the deduplication cache will be scoped to the current request.
865
+ * Controls the scope of request deduplication caching.
866
+ *
867
+ * - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
868
+ * Useful for applications with multiple API clients that should share deduplication state.
869
+ * - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
870
+ * Provides better isolation and is recommended for most use cases.
871
+ *
872
+ *
873
+ * **Real-world Scenarios:**
874
+ * - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
875
+ * - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
876
+ *
877
+ * @example
878
+ * ```ts
879
+ * // Local scope - each client has its own deduplication cache
880
+ * const userClient = createFetchClient({ baseURL: "/api/users" });
881
+ * const postClient = createFetchClient({ baseURL: "/api/posts" });
882
+ * // These clients won't share deduplication state
883
+ *
884
+ * // Global scope - share cache across related clients
885
+ * const userClient = createFetchClient({
886
+ * baseURL: "/api/users",
887
+ * dedupeCacheScope: "global",
888
+ * });
889
+ * const postClient = createFetchClient({
890
+ * baseURL: "/api/posts",
891
+ * dedupeCacheScope: "global",
892
+ * });
893
+ * // These clients will share deduplication state
894
+ * ```
895
+ *
579
896
  * @default "local"
580
897
  */
581
898
  dedupeCacheScope?: "global" | "local";
582
899
  /**
583
- * Unique key to namespace the deduplication cache when `dedupeCacheScope` is set to `"global"`.
900
+ * Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
901
+ *
902
+ * This creates logical groupings of deduplication caches. All instances with the same key
903
+ * will share the same cache namespace, allowing fine-grained control over which clients
904
+ * share deduplication state.
905
+ *
906
+ * **Best Practices:**
907
+ * - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
908
+ * - Keep scope keys consistent across related API clients
909
+ * - Consider using different scope keys for different environments (dev, staging, prod)
910
+ * - Avoid overly broad scope keys that might cause unintended cache sharing
911
+ *
912
+ * **Cache Management:**
913
+ * - Each scope key maintains its own independent cache
914
+ * - Caches are automatically cleaned up when no references remain
915
+ * - Consider the memory implications of multiple global scopes
916
+ *
917
+ * @example
918
+ * ```ts
919
+ * // Group related API clients together
920
+ * const userClient = createFetchClient({
921
+ * baseURL: "/api/users",
922
+ * dedupeCacheScope: "global",
923
+ * dedupeCacheScopeKey: "user-service"
924
+ * });
925
+ * const profileClient = createFetchClient({
926
+ * baseURL: "/api/profiles",
927
+ * dedupeCacheScope: "global",
928
+ * dedupeCacheScopeKey: "user-service" // Same scope - will share cache
929
+ * });
930
+ *
931
+ * // Separate analytics client with its own cache
932
+ * const analyticsClient = createFetchClient({
933
+ * baseURL: "/api/analytics",
934
+ * dedupeCacheScope: "global",
935
+ * dedupeCacheScopeKey: "analytics-service" // Different scope
936
+ * });
937
+ *
938
+ * // Environment-specific scoping
939
+ * const apiClient = createFetchClient({
940
+ * dedupeCacheScope: "global",
941
+ * dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
942
+ * });
943
+ * ```
584
944
  *
585
- * CallApi instances sharing this key will use the same cache for deduplication.
586
945
  * @default "default"
587
946
  */
588
947
  dedupeCacheScopeKey?: "default" | AnyString;
589
948
  /**
590
- * Custom request key to be used to identify a request within the selected deduplication cache.
591
- * @default the full request url + string formed from the request options
949
+ * Custom key generator for request deduplication.
950
+ *
951
+ * Override the default key generation strategy to control exactly which requests
952
+ * are considered duplicates. The default key combines URL, method, body, and
953
+ * relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
954
+ *
955
+ * **Default Key Generation:**
956
+ * The auto-generated key includes:
957
+ * - Full request URL (including query parameters)
958
+ * - HTTP method (GET, POST, etc.)
959
+ * - Request body (for POST/PUT/PATCH requests)
960
+ * - Stable headers (excludes Date, Authorization, User-Agent, etc.)
961
+ *
962
+ * **Custom Key Best Practices:**
963
+ * - Include only the parts of the request that should affect deduplication
964
+ * - Avoid including volatile data (timestamps, random IDs, etc.)
965
+ * - Consider performance - simpler keys are faster to compute and compare
966
+ * - Ensure keys are deterministic for the same logical request
967
+ * - Use consistent key formats across your application
968
+ *
969
+ * **Performance Considerations:**
970
+ * - Function-based keys are computed on every request - keep them lightweight
971
+ * - String keys are fastest but least flexible
972
+ * - Consider caching expensive key computations if needed
973
+ *
974
+ * @example
975
+ * ```ts
976
+ * import { callApi } from "@zayne-labs/callapi";
977
+ *
978
+ * // Simple static key - useful for singleton requests
979
+ * const config = callApi("/api/config", {
980
+ * dedupeKey: "app-config",
981
+ * dedupeStrategy: "defer" // Share the same config across all requests
982
+ * });
983
+ *
984
+ * // URL and method only - ignore headers and body
985
+ * const userData = callApi("/api/user/123", {
986
+ * dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
987
+ * });
988
+ *
989
+ * // Include specific headers in deduplication
990
+ * const apiCall = callApi("/api/data", {
991
+ * dedupeKey: (context) => {
992
+ * const authHeader = context.request.headers.get("Authorization");
993
+ * return `${context.options.fullURL}-${authHeader}`;
994
+ * }
995
+ * });
996
+ *
997
+ * // User-specific deduplication
998
+ * const userSpecificCall = callApi("/api/dashboard", {
999
+ * dedupeKey: (context) => {
1000
+ * const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
1001
+ * return `dashboard-${userId}`;
1002
+ * }
1003
+ * });
1004
+ *
1005
+ * // Ignore certain query parameters
1006
+ * const searchCall = callApi("/api/search?q=test&timestamp=123456", {
1007
+ * dedupeKey: (context) => {
1008
+ * const url = new URL(context.options.fullURL);
1009
+ * url.searchParams.delete("timestamp"); // Remove volatile param
1010
+ * return `search:${url.toString()}`;
1011
+ * }
1012
+ * });
1013
+ * ```
1014
+ *
1015
+ * @default Auto-generated from request details
592
1016
  */
593
- dedupeKey?: string;
1017
+ dedupeKey?: string | ((context: RequestContext) => string);
594
1018
  /**
595
- * Defines the deduplication strategy for the request, can be set to "none" | "defer" | "cancel".
596
- * - If set to "cancel", the previous pending request with the same request key will be cancelled and lets the new request through.
597
- * - If set to "defer", all new request with the same request key will be share the same response, until the previous one is completed.
598
- * - If set to "none", deduplication is disabled.
1019
+ * Strategy for handling duplicate requests.
1020
+ *
1021
+ * **Strategy Details:**
1022
+ * - `"cancel"`: Aborts any in-flight request with the same key when a new request starts.
1023
+ * The previous request will throw an AbortError, and the new request proceeds normally.
1024
+ * Best for scenarios where only the latest request matters.
1025
+ *
1026
+ * - `"defer"`: Returns the existing promise for duplicate requests, effectively sharing
1027
+ * the same response across multiple callers. All callers receive the same result.
1028
+ * Ideal for expensive operations that shouldn't be repeated.
1029
+ *
1030
+ * - `"none"`: Disables request deduplication entirely. Every request executes independently
1031
+ * regardless of similarity. Use when you need guaranteed request execution.
1032
+ *
1033
+ * **Real-world Use Cases:**
1034
+ *
1035
+ * **Cancel Strategy:**
1036
+ * - Search-as-you-type functionality (cancel previous searches)
1037
+ * - Real-time data updates (only latest request matters)
1038
+ * - User navigation (cancel previous page loads)
1039
+ * - Form submissions where rapid clicks should cancel previous attempts
1040
+ *
1041
+ * **Defer Strategy:**
1042
+ * - Configuration/settings loading (share across components)
1043
+ * - User profile data (multiple components need same data)
1044
+ * - Expensive computations or reports
1045
+ * - Authentication token refresh (prevent multiple refresh attempts)
1046
+ *
1047
+ * **None Strategy:**
1048
+ * - Analytics events (every event should be sent)
1049
+ * - Logging requests (each log entry is unique)
1050
+ * - File uploads (each upload is independent)
1051
+ * - Critical business operations that must not be deduplicated
1052
+ *
1053
+ *
1054
+ * @example
1055
+ * ```ts
1056
+ * // Cancel strategy - search functionality
1057
+ * const searchClient = createFetchClient({
1058
+ * baseURL: "/api/search",
1059
+ * dedupeStrategy: "cancel" // Cancel previous searches
1060
+ * });
1061
+ *
1062
+ * // Defer strategy - shared configuration
1063
+ * const configClient = createFetchClient({
1064
+ * dedupeStrategy: "defer" // Share config across components
1065
+ * });
1066
+ *
1067
+ * // Multiple components requesting config simultaneously
1068
+ * const config1 = configClient("/api/config"); // Makes actual request
1069
+ * const config2 = configClient("/api/config"); // Returns same promise as config1
1070
+ * const config3 = configClient("/api/config"); // Returns same promise as config1
1071
+ * // All three will resolve with the same response
1072
+ *
1073
+ * // None strategy - analytics
1074
+ * const analyticsClient = createFetchClient({
1075
+ * baseURL: "/api/analytics",
1076
+ * dedupeStrategy: "none" // Every event must be sent
1077
+ * });
1078
+ *
1079
+ * // Real-world search example with cancel
1080
+ * const handleSearch = async (query: string) => {
1081
+ * try {
1082
+ * const results = await callApi("/api/search", {
1083
+ * method: "POST",
1084
+ * body: { query },
1085
+ * dedupeStrategy: "cancel",
1086
+ * dedupeKey: "search" // All searches share the same key
1087
+ * });
1088
+ * updateUI(results);
1089
+ * } catch (error) {
1090
+ * if (error.name === "AbortError") {
1091
+ * // Previous search was cancelled - this is expected
1092
+ * return;
1093
+ * }
1094
+ * handleError(error);
1095
+ * }
1096
+ * };
1097
+ *
1098
+ * // Authentication token refresh with defer
1099
+ * const refreshToken = () => callApi("/api/auth/refresh", {
1100
+ * dedupeStrategy: "defer",
1101
+ * dedupeKey: "token-refresh" // Ensure only one refresh happens
1102
+ * });
1103
+ * ```
1104
+ *
599
1105
  * @default "cancel"
600
1106
  */
601
1107
  dedupeStrategy?: "cancel" | "defer" | "none";
@@ -659,7 +1165,9 @@ interface RetryOptions<TErrorData> {
659
1165
  */
660
1166
  type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaResult<TSchemaOption, undefined> ? TObject : Required<TObject>;
661
1167
  type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? `${TSchemaConfig["prefix"]}${TSchemaRouteKeys}` : TSchemaConfig["baseURL"] extends string ? `${TSchemaConfig["baseURL"]}${TSchemaRouteKeys}` : TSchemaRouteKeys;
662
- type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys : TSchemaRouteKeys | InitURLOrURLObject;
1168
+ type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys :
1169
+ // eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
1170
+ TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
663
1171
  type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
664
1172
  type InferAllRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Extract<keyof TBaseSchemaRoutes, string>>;
665
1173
  type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
@@ -785,59 +1293,183 @@ type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
785
1293
  headers: Record<string, string | undefined>;
786
1294
  };
787
1295
  type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
788
- type SharedExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = DedupeOptions & HooksOrHooksArray<TData, TErrorData, Partial<InferPluginOptions<TPluginArray>>> & Partial<InferPluginOptions<TPluginArray>> & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
1296
+ type SharedExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = DedupeOptions & HookConfigOptions & HooksOrHooksArray<TData, TErrorData, Partial<InferPluginOptions<TPluginArray>>> & Partial<InferPluginOptions<TPluginArray>> & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
789
1297
  /**
790
- * Authorization header value.
1298
+ * Automatically add an Authorization header value.
1299
+ *
1300
+ * Supports multiple authentication patterns:
1301
+ * - String: Direct authorization header value
1302
+ * - Auth object: Structured authentication configuration
1303
+ * - null: Explicitly removes authorization
1304
+ *
1305
+ * @example
1306
+ * ```ts
1307
+ * // Bearer token authentication
1308
+ * auth: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
1309
+ *
1310
+ * // Basic authentication
1311
+ * auth: "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
1312
+ *
1313
+ * // Using Auth object for dynamic authentication
1314
+ * auth: {
1315
+ * type: "bearer",
1316
+ * token: () => getAccessToken()
1317
+ * }
1318
+ *
1319
+ * // Remove inherited auth from base config
1320
+ * auth: null
1321
+ * ```
791
1322
  */
792
1323
  auth?: string | Auth | null;
793
1324
  /**
794
- * Base URL for the request.
1325
+ * Base URL for all API requests. Will be prepended to relative URLs.
1326
+ *
1327
+ * @example
1328
+ * ```ts
1329
+ * // Set base URL for all requests
1330
+ * baseURL: "https://api.example.com/v1"
1331
+ *
1332
+ * // Then use relative URLs in requests
1333
+ * callApi("/users") // → https://api.example.com/v1/users
1334
+ * callApi("/posts/123") // → https://api.example.com/v1/posts/123
1335
+ *
1336
+ * // Environment-specific base URLs
1337
+ * baseURL: process.env.NODE_ENV === "production"
1338
+ * ? "https://api.example.com"
1339
+ * : "http://localhost:3000/api"
1340
+ * ```
795
1341
  */
796
1342
  baseURL?: string;
797
1343
  /**
798
- * Custom function to serialize the body object into a string.
1344
+ * Custom function to serialize request body objects into strings.
1345
+ *
1346
+ * Useful for custom serialization formats or when the default JSON
1347
+ * serialization doesn't meet your needs.
1348
+ *
1349
+ * @example
1350
+ * ```ts
1351
+ * // Custom form data serialization
1352
+ * bodySerializer: (data) => {
1353
+ * const formData = new URLSearchParams();
1354
+ * Object.entries(data).forEach(([key, value]) => {
1355
+ * formData.append(key, String(value));
1356
+ * });
1357
+ * return formData.toString();
1358
+ * }
1359
+ *
1360
+ * // XML serialization
1361
+ * bodySerializer: (data) => {
1362
+ * return `<request>${Object.entries(data)
1363
+ * .map(([key, value]) => `<${key}>${value}</${key}>`)
1364
+ * .join('')}</request>`;
1365
+ * }
1366
+ *
1367
+ * // Custom JSON with specific formatting
1368
+ * bodySerializer: (data) => JSON.stringify(data, null, 2)
1369
+ * ```
799
1370
  */
800
1371
  bodySerializer?: (bodyData: Record<string, unknown>) => string;
801
1372
  /**
802
- * Whether or not to clone the response, so response.json() and the like can be read again else where.
1373
+ * Whether to clone the response so it can be read multiple times.
1374
+ *
1375
+ * By default, response streams can only be consumed once. Enable this when you need
1376
+ * to read the response in multiple places (e.g., in hooks and main code).
1377
+ *
803
1378
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Response/clone
804
1379
  * @default false
805
1380
  */
806
1381
  cloneResponse?: boolean;
807
1382
  /**
808
- * Custom fetch implementation
1383
+ * Custom fetch implementation to replace the default fetch function.
1384
+ *
1385
+ * Useful for testing, adding custom behavior, or using alternative HTTP clients
1386
+ * that implement the fetch API interface.
1387
+ *
1388
+ * @example
1389
+ * ```ts
1390
+ * // Use node-fetch in Node.js environments
1391
+ * import fetch from 'node-fetch';
1392
+ *
1393
+ * // Mock fetch for testing
1394
+ * customFetchImpl: async (url, init) => {
1395
+ * return new Response(JSON.stringify({ mocked: true }), {
1396
+ * status: 200,
1397
+ * headers: { 'Content-Type': 'application/json' }
1398
+ * });
1399
+ * }
1400
+ *
1401
+ * // Add custom logging to all requests
1402
+ * customFetchImpl: async (url, init) => {
1403
+ * console.log(`Fetching: ${url}`);
1404
+ * const response = await fetch(url, init);
1405
+ * console.log(`Response: ${response.status}`);
1406
+ * return response;
1407
+ * }
1408
+ *
1409
+ * // Use with custom HTTP client
1410
+ * customFetchImpl: async (url, init) => {
1411
+ * // Convert to your preferred HTTP client format
1412
+ * return await customHttpClient.request({
1413
+ * url: url.toString(),
1414
+ * method: init?.method || 'GET',
1415
+ * headers: init?.headers,
1416
+ * body: init?.body
1417
+ * });
1418
+ * }
1419
+ * ```
809
1420
  */
810
1421
  customFetchImpl?: FetchImpl;
811
1422
  /**
812
- * Default HTTP error message to use if none is provided from a response.
1423
+ * Default HTTP error message when server doesn't provide one.
1424
+ *
1425
+ * Can be a static string or a function that receives error context
1426
+ * to generate dynamic error messages based on the response.
1427
+ *
813
1428
  * @default "Failed to fetch data from server!"
1429
+ *
1430
+ * @example
1431
+ * ```ts
1432
+ * // Static error message
1433
+ * defaultHTTPErrorMessage: "API request failed. Please try again."
1434
+ *
1435
+ * // Dynamic error message based on status code
1436
+ * defaultHTTPErrorMessage: ({ response }) => {
1437
+ * switch (response.status) {
1438
+ * case 401: return "Authentication required. Please log in.";
1439
+ * case 403: return "Access denied. Insufficient permissions.";
1440
+ * case 404: return "Resource not found.";
1441
+ * case 429: return "Too many requests. Please wait and try again.";
1442
+ * case 500: return "Server error. Please contact support.";
1443
+ * default: return `Request failed with status ${response.status}`;
1444
+ * }
1445
+ * }
1446
+ *
1447
+ * // Include error data in message
1448
+ * defaultHTTPErrorMessage: ({ errorData, response }) => {
1449
+ * const userMessage = errorData?.message || "Unknown error occurred";
1450
+ * return `${userMessage} (Status: ${response.status})`;
1451
+ * }
1452
+ * ```
814
1453
  */
815
1454
  defaultHTTPErrorMessage?: string | ((context: Pick<HTTPError<TErrorData>, "errorData" | "response">) => string);
816
1455
  /**
817
- * If true, forces the calculation of the total byte size from the request or response body, in case the content-length header is not present or is incorrect.
1456
+ * Forces calculation of total byte size from request/response body streams.
1457
+ *
1458
+ * Useful when the Content-Length header is missing or incorrect, and you need
1459
+ * accurate size information for progress tracking or bandwidth monitoring.
1460
+ *
818
1461
  * @default false
1462
+ *
819
1463
  */
820
1464
  forcefullyCalculateStreamSize?: boolean | {
821
1465
  request?: boolean;
822
1466
  response?: boolean;
823
1467
  };
824
1468
  /**
825
- * Defines the mode in which the composed hooks are executed".
826
- * - If set to "parallel", main and plugin hooks will be executed in parallel.
827
- * - If set to "sequential", the plugin hooks will be executed first, followed by the main hook.
828
- * @default "parallel"
829
- */
830
- mergedHooksExecutionMode?: "parallel" | "sequential";
831
- /**
832
- * - Controls what order in which the composed hooks execute
833
- * @default "mainHooksAfterPlugins"
834
- */
835
- mergedHooksExecutionOrder?: "mainHooksAfterPlugins" | "mainHooksBeforePlugins";
836
- /**
837
- * - An optional field you can fill with additional information,
838
- * to associate with the request, typically used for logging or tracing.
1469
+ * Optional metadata field for associating additional information with requests.
839
1470
  *
840
- * - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
1471
+ * Useful for logging, tracing, or handling specific cases in shared interceptors.
1472
+ * The meta object is passed through to all hooks and can be accessed in error handlers.
841
1473
  *
842
1474
  * @example
843
1475
  * ```ts
@@ -854,61 +1486,321 @@ type SharedExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, T
854
1486
  * url: "https://example.com/api/data",
855
1487
  * meta: { userId: "123" },
856
1488
  * });
1489
+ *
1490
+ * // Use case: Request tracking
1491
+ * const result = await callMainApi({
1492
+ * url: "https://example.com/api/data",
1493
+ * meta: {
1494
+ * requestId: generateId(),
1495
+ * source: "user-dashboard",
1496
+ * priority: "high"
1497
+ * }
1498
+ * });
1499
+ *
1500
+ * // Use case: Feature flags
1501
+ * const client = callApi.create({
1502
+ * baseURL: "https://api.example.com",
1503
+ * meta: {
1504
+ * features: ["newUI", "betaFeature"],
1505
+ * experiment: "variantA"
1506
+ * }
1507
+ * });
857
1508
  * ```
858
1509
  */
859
1510
  meta?: GlobalMeta;
860
1511
  /**
861
- * Custom function to parse the response string
1512
+ * Custom function to parse response strings into objects.
1513
+ *
1514
+ * Useful when the API returns non-JSON responses or when you need
1515
+ * custom parsing logic for specific response formats.
1516
+ *
1517
+ * @example
1518
+ * ```ts
1519
+ * // Parse XML responses
1520
+ * responseParser: (responseString) => {
1521
+ * const parser = new DOMParser();
1522
+ * const doc = parser.parseFromString(responseString, "text/xml");
1523
+ * return xmlToObject(doc);
1524
+ * }
1525
+ *
1526
+ * // Parse CSV responses
1527
+ * responseParser: (responseString) => {
1528
+ * const lines = responseString.split('\n');
1529
+ * const headers = lines[0].split(',');
1530
+ * const data = lines.slice(1).map(line => {
1531
+ * const values = line.split(',');
1532
+ * return headers.reduce((obj, header, index) => {
1533
+ * obj[header] = values[index];
1534
+ * return obj;
1535
+ * }, {});
1536
+ * });
1537
+ * return { data };
1538
+ * }
1539
+ *
1540
+ * // Parse custom format with error handling
1541
+ * responseParser: async (responseString) => {
1542
+ * try {
1543
+ * // Custom parsing logic
1544
+ * const parsed = customFormat.parse(responseString);
1545
+ * return { success: true, data: parsed };
1546
+ * } catch (error) {
1547
+ * return { success: false, error: error.message };
1548
+ * }
1549
+ * }
1550
+ * ```
862
1551
  */
863
1552
  responseParser?: (responseString: string) => Awaitable<Record<string, unknown>>;
864
1553
  /**
865
- * Expected response type, affects how response is parsed
1554
+ * Expected response type, determines how the response body is parsed.
1555
+ *
1556
+ * Different response types trigger different parsing methods:
1557
+ * - **"json"**: Parses as JSON using response.json()
1558
+ * - **"text"**: Returns as plain text using response.text()
1559
+ * - **"blob"**: Returns as Blob using response.blob()
1560
+ * - **"arrayBuffer"**: Returns as ArrayBuffer using response.arrayBuffer()
1561
+ * - **"stream"**: Returns the response body stream directly
1562
+ *
866
1563
  * @default "json"
1564
+ *
1565
+ * @example
1566
+ * ```ts
1567
+ * // JSON API responses (default)
1568
+ * responseType: "json"
1569
+ *
1570
+ * // Plain text responses
1571
+ * responseType: "text"
1572
+ * // Usage: const csvData = await callApi("/export.csv", { responseType: "text" });
1573
+ *
1574
+ * // File downloads
1575
+ * responseType: "blob"
1576
+ * // Usage: const file = await callApi("/download/file.pdf", { responseType: "blob" });
1577
+ *
1578
+ * // Binary data
1579
+ * responseType: "arrayBuffer"
1580
+ * // Usage: const buffer = await callApi("/binary-data", { responseType: "arrayBuffer" });
1581
+ *
1582
+ * // Streaming responses
1583
+ * responseType: "stream"
1584
+ * // Usage: const stream = await callApi("/large-dataset", { responseType: "stream" });
1585
+ * ```
867
1586
  */
868
1587
  responseType?: TResponseType;
869
1588
  /**
870
- * Mode of the result, can influence how results are handled or returned.
871
- * Can be set to "all" | "onlySuccess" | "onlyError" | "onlyResponse".
1589
+ * Controls what data is included in the returned result object.
1590
+ *
1591
+ * Different modes return different combinations of data, error, and response:
1592
+ * - **"all"**: Returns { data, error, response } - complete result information
1593
+ * - **"allWithException"**: Returns { data, error, response } but throws on errors
1594
+ * - **"onlySuccess"**: Returns only data (null for errors), never throws
1595
+ * - **"onlySuccessWithException"**: Returns only data but throws on errors
1596
+ *
872
1597
  * @default "all"
1598
+ *
1599
+ * @example
1600
+ * ```ts
1601
+ * // Complete result with all information (default)
1602
+ * resultMode: "all"
1603
+ * const { data, error, response } = await callApi("/users");
1604
+ * if (error) {
1605
+ * console.error("Request failed:", error);
1606
+ * } else {
1607
+ * console.log("Users:", data);
1608
+ * }
1609
+ *
1610
+ * // Complete result but throws on errors
1611
+ * resultMode: "allWithException"
1612
+ * try {
1613
+ * const { data, response } = await callApi("/users", { resultMode: "allWithException" });
1614
+ * console.log("Users:", data);
1615
+ * } catch (error) {
1616
+ * console.error("Request failed:", error);
1617
+ * }
1618
+ *
1619
+ * // Only data, returns null on errors
1620
+ * resultMode: "onlySuccess"
1621
+ * const users = await callApi("/users", { resultMode: "onlySuccess" });
1622
+ * if (users) {
1623
+ * console.log("Users:", users);
1624
+ * } else {
1625
+ * console.log("Request failed");
1626
+ * }
1627
+ *
1628
+ * // Only data with null, throws on errors
1629
+ * resultMode: "onlySuccessWithException"
1630
+ * try {
1631
+ * const users = await callApi("/users", { resultMode: "onlySuccessWithException" });
1632
+ * console.log("Users:", users);
1633
+ * } catch (error) {
1634
+ * console.error("Request failed:", error);
1635
+ * }
1636
+ * ```
873
1637
  */
874
1638
  resultMode?: TResultMode;
875
1639
  /**
876
- * If true or the function returns true, throws errors instead of returning them
877
- * The function is passed the error object and can be used to conditionally throw the error
1640
+ * Controls whether errors are thrown as exceptions or returned in the result.
1641
+ *
1642
+ * Can be a boolean or a function that receives the error and decides whether to throw.
1643
+ * When true, errors are thrown as exceptions instead of being returned in the result object.
1644
+ *
878
1645
  * @default false
1646
+ *
1647
+ * @example
1648
+ * ```ts
1649
+ * // Always throw errors
1650
+ * throwOnError: true
1651
+ * try {
1652
+ * const data = await callApi("/users");
1653
+ * console.log("Users:", data);
1654
+ * } catch (error) {
1655
+ * console.error("Request failed:", error);
1656
+ * }
1657
+ *
1658
+ * // Never throw errors (default)
1659
+ * throwOnError: false
1660
+ * const { data, error } = await callApi("/users");
1661
+ * if (error) {
1662
+ * console.error("Request failed:", error);
1663
+ * }
1664
+ *
1665
+ * // Conditionally throw based on error type
1666
+ * throwOnError: (error) => {
1667
+ * // Throw on client errors (4xx) but not server errors (5xx)
1668
+ * return error.response?.status >= 400 && error.response?.status < 500;
1669
+ * }
1670
+ *
1671
+ * // Throw only on specific status codes
1672
+ * throwOnError: (error) => {
1673
+ * const criticalErrors = [401, 403, 404];
1674
+ * return criticalErrors.includes(error.response?.status);
1675
+ * }
1676
+ *
1677
+ * // Throw on validation errors but not network errors
1678
+ * throwOnError: (error) => {
1679
+ * return error.type === "validation";
1680
+ * }
1681
+ * ```
879
1682
  */
880
1683
  throwOnError?: TThrowOnError;
881
1684
  /**
882
- * Request timeout in milliseconds
1685
+ * Request timeout in milliseconds. Request will be aborted if it takes longer.
1686
+ *
1687
+ * Useful for preventing requests from hanging indefinitely and providing
1688
+ * better user experience with predictable response times.
1689
+ *
1690
+ * @example
1691
+ * ```ts
1692
+ * // 5 second timeout
1693
+ * timeout: 5000
1694
+ *
1695
+ * // Different timeouts for different endpoints
1696
+ * const quickApi = createFetchClient({ timeout: 3000 }); // 3s for fast endpoints
1697
+ * const slowApi = createFetchClient({ timeout: 30000 }); // 30s for slow operations
1698
+ *
1699
+ * // Per-request timeout override
1700
+ * await callApi("/quick-data", { timeout: 1000 });
1701
+ * await callApi("/slow-report", { timeout: 60000 });
1702
+ *
1703
+ * // No timeout (use with caution)
1704
+ * timeout: 0
1705
+ * ```
883
1706
  */
884
1707
  timeout?: number;
885
1708
  };
886
1709
  type BaseCallApiExtraOptions<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig> = SharedExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray> & {
887
1710
  /**
888
- * An array of base callApi plugins. It allows you to extend the behavior of the library.
1711
+ * Array of base CallApi plugins to extend library functionality.
1712
+ *
1713
+ * Base plugins are applied to all instances created from this base configuration
1714
+ * and provide foundational functionality like authentication, logging, or caching.
1715
+ *
1716
+ * @example
1717
+ * ```ts
1718
+ * // Add logging plugin
1719
+ *
1720
+ * // Create base client with common plugins
1721
+ * const callApi = createFetchClient({
1722
+ * baseURL: "https://api.example.com",
1723
+ * plugins: [loggerPlugin({ enabled: true })]
1724
+ * });
1725
+ *
1726
+ * // All requests inherit base plugins
1727
+ * await callApi("/users");
1728
+ * await callApi("/posts");
1729
+ *
1730
+ * ```
889
1731
  */
890
1732
  plugins?: TBasePluginArray;
891
1733
  /**
892
- * Base schemas for the client.
1734
+ * Base validation schemas for the client configuration.
1735
+ *
1736
+ * Defines validation rules for requests and responses that apply to all
1737
+ * instances created from this base configuration. Provides type safety
1738
+ * and runtime validation for API interactions.
893
1739
  */
894
1740
  schema?: TBaseSchemaAndConfig;
895
1741
  /**
896
- * Specifies which configuration parts should skip automatic merging between base and main configs.
897
- * Use this when you need manual control over how configs are combined.
1742
+ * Controls which configuration parts skip automatic merging between base and instance configs.
898
1743
  *
899
- * @enum
900
- * - `"all"` - Disables automatic merging for both request and options
901
- * - `"options"` - Disables automatic merging of options only
902
- * - `"request"` - Disables automatic merging of request only
1744
+ * By default, CallApi automatically merges base configuration with instance configuration.
1745
+ * This option allows you to disable automatic merging for specific parts when you need
1746
+ * manual control over how configurations are combined.
903
1747
  *
904
- * **Example**
1748
+ * @enum
1749
+ * - **"all"**: Disables automatic merging for both request options and extra options
1750
+ * - **"options"**: Disables automatic merging of extra options only (hooks, plugins, etc.)
1751
+ * - **"request"**: Disables automatic merging of request options only (headers, body, etc.)
905
1752
  *
1753
+ * @example
906
1754
  * ```ts
1755
+ * // Skip all automatic merging - full manual control
1756
+ * const client = callApi.create((ctx) => ({
1757
+ * skipAutoMergeFor: "all",
1758
+ *
1759
+ * // Manually decide what to merge
1760
+ * baseURL: ctx.options.baseURL, // Keep base URL
1761
+ * timeout: 5000, // Override timeout
1762
+ * headers: {
1763
+ * ...ctx.request.headers, // Merge headers manually
1764
+ * "X-Custom": "value" // Add custom header
1765
+ * }
1766
+ * }));
1767
+ *
1768
+ * // Skip options merging - manual plugin/hook control
1769
+ * const client = callApi.create((ctx) => ({
1770
+ * skipAutoMergeFor: "options",
1771
+ *
1772
+ * // Manually control which plugins to use
1773
+ * plugins: [
1774
+ * ...ctx.options.plugins?.filter(p => p.name !== "unwanted") || [],
1775
+ * customPlugin
1776
+ * ],
1777
+ *
1778
+ * // Request options still auto-merge
1779
+ * method: "POST"
1780
+ * }));
1781
+ *
1782
+ * // Skip request merging - manual request control
1783
+ * const client = callApi.create((ctx) => ({
1784
+ * skipAutoMergeFor: "request",
1785
+ *
1786
+ * // Extra options still auto-merge (plugins, hooks, etc.)
1787
+ *
1788
+ * // Manually control request options
1789
+ * headers: {
1790
+ * "Content-Type": "application/json",
1791
+ * // Don't merge base headers
1792
+ * },
1793
+ * method: ctx.request.method || "GET"
1794
+ * }));
1795
+ *
1796
+ * // Use case: Conditional merging based on request
907
1797
  * const client = createFetchClient((ctx) => ({
908
1798
  * skipAutoMergeFor: "options",
909
1799
  *
910
- * // Now you can manually merge options in your config function
911
- * ...ctx.options,
1800
+ * // Only use auth plugin for protected routes
1801
+ * plugins: ctx.initURL.includes("/protected/")
1802
+ * ? [...(ctx.options.plugins || []), authPlugin]
1803
+ * : ctx.options.plugins?.filter(p => p.name !== "auth") || []
912
1804
  * }));
913
1805
  * ```
914
1806
  */
@@ -916,20 +1808,35 @@ type BaseCallApiExtraOptions<TBaseData = DefaultDataType, TBaseErrorData = Defau
916
1808
  };
917
1809
  type CallApiExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TCurrentRouteSchemaKey extends string = string> = SharedExtraOptions<TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TPluginArray> & {
918
1810
  /**
919
- * An array of instance CallApi plugins. It allows you to extend the behavior of the library.
1811
+ * Array of instance-specific CallApi plugins or a function to configure plugins.
1812
+ *
1813
+ * Instance plugins are added to the base plugins and provide functionality
1814
+ * specific to this particular API instance. Can be a static array or a function
1815
+ * that receives base plugins and returns the instance plugins.
1816
+ *
920
1817
  */
921
1818
  plugins?: TPluginArray | ((context: {
922
1819
  basePlugins: Writeable<TBasePluginArray, "deep">;
923
1820
  }) => TPluginArray);
924
1821
  /**
925
- * Schemas for the callapi instance
1822
+ * Instance-specific validation schemas or a function to configure schemas.
1823
+ *
1824
+ * Defines validation rules specific to this API instance, extending or
1825
+ * overriding base schemas. Can be a static schema object or a function
1826
+ * that receives base schema context and returns instance schemas.
1827
+ *
926
1828
  */
927
1829
  schema?: TSchema | ((context: {
928
1830
  baseSchema: Writeable<TBaseSchemaRoutes, "deep">;
929
1831
  currentRouteSchema: GetCurrentRouteSchema<TBaseSchemaRoutes, TCurrentRouteSchemaKey>;
930
1832
  }) => TSchema);
931
1833
  /**
932
- * Schema config for the callapi instance
1834
+ * Instance-specific schema configuration or a function to configure schema behavior.
1835
+ *
1836
+ * Controls how validation schemas are applied and behave for this specific
1837
+ * API instance. Can override base schema configuration or extend it with
1838
+ * instance-specific validation rules.
1839
+ *
933
1840
  */
934
1841
  schemaConfig?: TSchemaConfig | ((context: {
935
1842
  baseSchemaConfig: Writeable<TBaseSchemaConfig, "deep">;
@@ -946,4 +1853,4 @@ type CallApiParameters<TData = DefaultDataType, TErrorData = DefaultDataType, TR
946
1853
  type CallApiResult<TData, TErrorData, TResultMode extends ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion, TResponseType extends ResponseTypeUnion> = Promise<GetCallApiResult<TData, TErrorData, TResultMode, TThrowOnError, TResponseType>>;
947
1854
  //#endregion
948
1855
  export { AnyFunction, AnyString, ApplyStrictConfig, ApplyURLBasedConfig, BaseCallApiConfig, BaseCallApiExtraOptions, BaseCallApiSchemaAndConfig, BaseCallApiSchemaRoutes, CallApiConfig, CallApiExtraOptions, CallApiExtraOptionsForHooks, CallApiParameters, CallApiPlugin, CallApiRequestOptions, CallApiRequestOptionsForHooks, CallApiResult, CallApiResultErrorVariant, CallApiResultSuccessVariant, CallApiSchema, CallApiSchemaConfig, DedupeOptions, DefaultDataType, DefaultPluginArray, DefaultThrowOnError, ErrorContext, GetCurrentRouteSchema, GetCurrentRouteSchemaKey, HTTPError, Hooks, HooksOrHooksArray, InferInitURL, InferParamsFromRoute, InferSchemaResult, PluginExtraOptions, PluginHooks, PluginHooksWithMoreOptions, PluginInitContext, PossibleHTTPError, PossibleJavaScriptError, PossibleJavaScriptOrValidationError, PossibleValidationError, Register, RequestContext, RequestStreamContext, ResponseContext, ResponseErrorContext, ResponseStreamContext, ResponseTypeUnion, ResultModeUnion, RetryOptions, SuccessContext, ThrowOnErrorUnion, URLOptions, ValidationError, Writeable };
949
- //# sourceMappingURL=common-Vd9i_nPc.d.ts.map
1856
+ //# sourceMappingURL=common-C-kIzPcz.d.ts.map