UNPKG

@hyper-fetch/core

Version:

Cache, Queue and Persist your requests no matter if you are online or offline!

223 lines 17.1 kB
import { RequestSendType, PayloadType, RequestJSON, RequestOptionsType, RequestConfigurationType, PayloadMapperType, RequestInstance, RequestMapper, ResponseMapper, ExtractUrlParams, RetryOnErrorCallbackType, OptimisticCallback } from './request.types'; import { RequestHooks } from './request.hooks'; import { ClientInstance } from '../client'; import { ResponseErrorType, ResponseSuccessType, ResponseType } from '../adapter'; import { ExtractClientAdapterType, ExtractClientGlobalError, ExtractParamsType, EmptyTypes, ExtractAdapterMethodType, ExtractAdapterOptionsType, HydrateDataType, SyncOrAsync } from '../types'; import { MockerConfigType, MockResponseType } from '../mocker'; type ClientAdapterOptions<C extends ClientInstance> = ExtractAdapterOptionsType<ExtractClientAdapterType<C>>; type ClientAdapterMethod<C extends ClientInstance> = ExtractAdapterMethodType<ExtractClientAdapterType<C>>; type ClientRequestOptions<E, C extends ClientInstance> = RequestOptionsType<E, ClientAdapterOptions<C>, ClientAdapterMethod<C>>; /** * Request is a class that represents a request sent to the server. It contains all the necessary information to make a request, like endpoint, method, headers, data, and much more. * It is executed at any time via methods like `send` or `exec`. * * We can set it up with options like endpoint, method, headers and more. * We can choose some of advanced settings like cache, invalidation patterns, concurrency, retries and much, much more. * * @info We should not use this class directly in the standard development flow. * We can initialize it using the `createRequest` method on the **Client** class. * * @attention The most important thing about the request is that it keeps data in the format that can be dumped. * This is necessary for the persistence and different dispatcher storage types. * This class doesn't have any callback methods by design and communicate with dispatcher and cache by events. * * It should be serializable to JSON and deserializable back to the class. * Serialization should not affect the result of the request, so it's methods and functional part should be only syntax sugar for given runtime. */ export declare class Request<Response, Payload, QueryParams, LocalError, Endpoint extends string, Client extends ClientInstance, HasPayload extends true | false = false, HasParams extends true | false = false, HasQuery extends true | false = false, MutationContext = undefined> { readonly client: Client; readonly requestOptions: ClientRequestOptions<Endpoint, Client>; readonly initialRequestConfiguration?: RequestConfigurationType<Payload, Endpoint extends string ? ExtractUrlParams<Endpoint> : never, QueryParams, Endpoint, ClientAdapterOptions<Client>, ClientAdapterMethod<Client>> | undefined; endpoint: Endpoint; headers?: HeadersInit; auth: boolean; method: ClientAdapterMethod<Client>; params: ExtractUrlParams<Endpoint> | EmptyTypes; payload: PayloadType<Payload>; queryParams: QueryParams | EmptyTypes; options?: ClientAdapterOptions<Client> | undefined; cancelable: boolean; retry: number; retryTime: number; cacheTime: number; cache: boolean; staleTime: number; queued: boolean; offline: boolean; abortKey: string; cacheKey: string; queryKey: string; used: boolean; deduplicate: boolean; deduplicateTime: number | null; scope: string | null; /** * Instance-level lifecycle hooks. These callbacks fire for every `send()` / `exec()` call * made on this request instance (and its clones), without needing to pass them to `send()` each time. * Useful for cross-cutting concerns like logging, analytics, or toast notifications. * * Each method registers a callback and returns an unsubscribe function. * Multiple listeners per hook are supported. */ $hooks: RequestHooks<RequestInstance>; isMockerEnabled: boolean; unstable_mock?: { fn: (options: { request: RequestInstance; requestId: string; }) => MockResponseType<Response, LocalError | ExtractClientGlobalError<Client>, ExtractClientAdapterType<Client>>; config: MockerConfigType; }; /** @internal */ unstable_payloadMapper?: PayloadMapperType<Payload>; /** @internal */ unstable_requestMapper?: RequestMapper<any, any>; /** @internal */ unstable_responseMapper?: ResponseMapper<this, ResponseSuccessType<any, any> | ResponseErrorType<any, any>>; /** @internal */ retryOnError?: RetryOnErrorCallbackType<RequestInstance>; /** @internal */ optimistic?: OptimisticCallback<RequestInstance, any>; unstable_hasParams: HasParams; unstable_hasPayload: HasPayload; unstable_hasQuery: HasQuery; unstable_hasMutationContext: MutationContext; private updatedAbortKey; private updatedCacheKey; private updatedQueryKey; constructor(client: Client, requestOptions: ClientRequestOptions<Endpoint, Client>, initialRequestConfiguration?: RequestConfigurationType<Payload, Endpoint extends string ? ExtractUrlParams<Endpoint> : never, QueryParams, Endpoint, ClientAdapterOptions<Client>, ClientAdapterMethod<Client>> | undefined); /** Set the request headers. Returns a cloned request with the new headers applied. */ setHeaders: (headers: HeadersInit) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set whether authentication interceptors should run for this request. */ setAuth: (auth: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set the URL path parameters (e.g., `:userId`). */ setParams: <P extends ExtractParamsType<this>>(params: P) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, P extends null ? false : true, HasQuery, MutationContext>; /** Set the request body payload. */ setPayload: <P extends Payload>(payload: P) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, P extends null ? false : true, HasParams, HasQuery, MutationContext>; /** Set the query parameters appended to the URL. */ setQueryParams: (queryParams: QueryParams) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, true, MutationContext>; /** Set adapter-specific options (e.g., axios config, fetch init). */ setOptions: (options: ClientAdapterOptions<Client>) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, true, MutationContext>; /** * Set a scope identifier for this request. * All keys (cache, queue, abort) are prefixed with this scope, isolating * the request from other scopes. In "server" client mode, setting a scope * also enables caching (which is otherwise disabled to prevent cross-request leaks). */ setScope: (scopeId: string) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set whether in-flight requests with the same abort key are cancelled when a new one is dispatched. */ setCancelable: (cancelable: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set the number of retry attempts on failure. */ setRetry: (retry: ClientRequestOptions<Endpoint, Client>["retry"]) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set the delay in milliseconds between retry attempts. */ setRetryTime: (retryTime: ClientRequestOptions<Endpoint, Client>["retryTime"]) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** * Set a callback that controls whether a failed request should be retried. * Called on each failed attempt before scheduling the next retry. * Return `true` to allow the retry, `false` to stop retrying immediately. */ setRetryOnError: (callback: RetryOnErrorCallbackType<Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery>>) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** * Configure optimistic update behavior for this request. * The callback runs before the request is sent (in React's `useSubmit`) and receives * the request, client, and payload. Return `context` (available in submit callbacks), * `rollback` (called automatically on failure/abort), and `invalidate` (cache keys * invalidated on success). */ setOptimistic: <Ctx>(callback: OptimisticCallback<this, Ctx>) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, Ctx>; /** Set how long (in ms) the response remains in cache before being garbage collected. */ setCacheTime: (cacheTime: ClientRequestOptions<Endpoint, Client>["cacheTime"]) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set whether this request should use the cache. */ setCache: (cache: ClientRequestOptions<Endpoint, Client>["cache"]) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set how long (in ms) cached data is considered fresh before triggering a revalidation. */ setStaleTime: (staleTime: ClientRequestOptions<Endpoint, Client>["staleTime"]) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set whether the request should be queued and executed sequentially rather than immediately. */ setQueued: (queued: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Override the abort key used to cancel this request. */ setAbortKey: (abortKey: string) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Override the cache key used to store/retrieve the response. */ setCacheKey: (cacheKey: string) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Override the query key that identifies this request in the dispatcher queue. */ setQueryKey: (queryKey: string) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Enable or disable request deduplication. When enabled, concurrent identical requests share a single response. */ setDeduplicate: (deduplicate: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set the time window (in ms) during which identical requests are deduplicated. */ setDeduplicateTime: (deduplicateTime: number) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Mark the request as used (prevents automatic refetching in hooks). */ setUsed: (used: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Set whether the request should be stored in the offline queue when there is no network connection. */ setOffline: (offline: boolean) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Attach a mock handler to this request. When mocking is enabled, the handler is called instead of the real adapter. */ setMock: (fn: (options: { request: Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery>; requestId: string; }) => SyncOrAsync<MockResponseType<Response, LocalError | ExtractClientGlobalError<Client>, ExtractClientAdapterType<Client>>>, config?: MockerConfigType) => this; /** Remove any attached mock handler and disable mocking for this request. */ clearMock: () => this; /** Enable or disable the mocker for this request without removing the mock handler. */ setMockingEnabled: (isMockerEnabled: boolean) => this; /** * Map data before it gets send to the server * @param payloadMapper * @returns */ setPayloadMapper: <MappedPayload extends any | Promise<any>>(payloadMapper: (data: Payload) => MappedPayload) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** * Map request before it gets send to the server * @param requestMapper mapper of the request * @returns new request */ setRequestMapper: <NewRequest extends RequestInstance>(requestMapper: RequestMapper<this, NewRequest>) => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** * Map the response to the new interface * @param responseMapper our mapping callback * @returns new response */ setResponseMapper: <MappedResponse extends ResponseSuccessType<any, any> | ResponseErrorType<any, any>>(responseMapper?: ResponseMapper<this, MappedResponse>) => Request<MappedResponse extends ResponseType<infer R, any, any> ? R : Response, Payload, QueryParams, MappedResponse extends ResponseType<any, infer E, any> ? E : LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; private paramsMapper; /** Serialize the request configuration into a plain JSON object for persistence or transfer. */ toJSON(): RequestJSON<this>; /** Create a new request instance with optional configuration overrides. Used internally by all setter methods. */ clone<NewData extends true | false = HasPayload, NewParams extends true | false = HasParams, NewQueryParams extends true | false = HasQuery>(configuration?: RequestConfigurationType<Payload, (typeof this)["params"], QueryParams, Endpoint, ClientAdapterOptions<Client>, ClientAdapterMethod<Client>>): Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, NewData, NewParams, NewQueryParams, MutationContext>; /** Abort all in-flight requests sharing this request's abort key. */ abort: () => Request<Response, Payload, QueryParams, LocalError, Endpoint, Client, HasPayload, HasParams, HasQuery, MutationContext>; /** Extract the current cache data for this request into a portable hydration payload (useful for SSR). */ dehydrate: (config?: { /** in case of using adapter without cache we can provide response to dehydrate */ response?: ResponseType<Response, LocalError | ExtractClientGlobalError<Client>, ExtractClientAdapterType<Client>>; /** override cache data */ override?: boolean; }) => HydrateDataType<Response, LocalError | ExtractClientGlobalError<Client>, ExtractClientAdapterType<Client>> | undefined; /** * Read the response from cache data * * If it returns error and data at the same time, it means that latest request was failed * and we show previous data from cache together with error received from actual request */ read(): ResponseType<Response, LocalError | ExtractClientGlobalError<Client>, ExtractClientAdapterType<Client>> | undefined; /** * Method to use the request WITHOUT adding it to cache and queues. This mean it will make simple request without queue side effects. * @param options * @disableReturns * @returns * ```tsx * Promise<[Data | null, Error | null, HttpStatus]> * ``` */ exec: RequestSendType<this>; /** * Method used to perform requests with usage of cache and queues * @param options * @param requestCallback * @disableReturns * @returns * ```tsx * Promise<{ data: Data | null, error: Error | null, status: HttpStatus, ... }> * ``` */ send: RequestSendType<this>; /** Reconstruct a Request instance from a previously serialized JSON representation. */ static fromJSON: <NewResponse, NewPayload, NewQueryParams, NewLocalError, NewEndpoint extends string, NewClient extends ClientInstance, NewHasPayload extends true | false = false, NewHasParams extends true | false = false, NewHasQuery extends true | false = false>(client: NewClient, json: RequestJSON<Request<NewResponse, NewPayload, NewQueryParams, NewLocalError, NewEndpoint, NewClient, NewHasPayload, NewHasParams, NewHasQuery>>) => Request<NewResponse, NewPayload, NewQueryParams, NewLocalError, NewEndpoint, NewClient, NewHasPayload, NewHasParams, NewHasQuery, undefined>; } export {}; //# sourceMappingURL=request.d.ts.map