diff --git a/README.md b/README.md
index 2406397..747655f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[npm-url]: https://npmjs.org/package/fetchff
[npm-image]: http://img.shields.io/npm/v/fetchff.svg
-[![NPM version][npm-image]][npm-url] [![Blazing Fast](https://badgen.now.sh/badge/speed/blazing%20%F0%9F%94%A5/green)](https://github.com/MattCCC/fetchff) [![Code Coverage](https://badgen.now.sh/badge/coverage/97.68%/blue)](https://github.com/MattCCC/fetchff) [![npm downloads](https://img.shields.io/npm/dm/fetchff.svg?style=flat-square)](http://npm-stat.com/charts.html?package=fetchff) [![gzip size](https://img.shields.io/bundlephobia/minzip/fetchff)](https://bundlephobia.com/result?p=fetchff)
+[![NPM version][npm-image]][npm-url] [![Blazing Fast](https://badgen.now.sh/badge/speed/blazing%20%F0%9F%94%A5/green)](https://github.com/MattCCC/fetchff) [![Code Coverage](https://img.shields.io/badge/coverage-97.39-green)](https://github.com/MattCCC/fetchff) [![npm downloads](https://img.shields.io/npm/dm/fetchff.svg?color=lightblue)](http://npm-stat.com/charts.html?package=fetchff) [![gzip size](https://img.shields.io/bundlephobia/minzip/fetchff)](https://bundlephobia.com/result?p=fetchff)
## Why?
@@ -22,9 +22,9 @@ Managing multiple API endpoints can be complex and time-consuming. `fetchff` sim
## Features
- **100% Performance-Oriented**: Optimized for speed and efficiency, ensuring fast and reliable API interactions.
-- **Fully TypeScript Compatible**: Enjoy full TypeScript support for better development experience and type safety.
-- **Smart Error Retry**: Features exponential backoff for intelligent error handling and retry mechanisms.
+- **Smart Retry Mechanism**: Features exponential backoff for intelligent error handling and retry mechanisms.
- **Automatic Request Deduplication**: Set the time during which requests are deduplicated (treated as same request).
+- **Smart Cache Management**: Dynamically manage cache with configurable expiration, custom keys, and selective invalidation.
- **Dynamic URLs Support**: Easily manage routes with dynamic parameters, such as `/user/:userId`.
- **Native `fetch()` Support**: Uses the modern `fetch()` API by default, eliminating the need for libraries like Axios.
- **Global and Per Request Error Handling**: Flexible error management at both global and individual request levels.
@@ -35,7 +35,8 @@ Managing multiple API endpoints can be complex and time-consuming. `fetchff` sim
- **Supports All Axios Options**: Fully compatible with all Axios configuration options for seamless integration.
- **Lightweight**: Minimal footprint, only a few KBs when gzipped, ensuring quick load times.
- **Framework Independent**: Pure JavaScript solution, compatible with any framework or library.
-- **Browser and Node 18+ Compatible**: Works flawlessly in both modern browsers and Node.js environments.
+- **Browser and Node.js 18+ Compatible**: Works flawlessly in both modern browsers and Node.js environments.
+- **Fully TypeScript Compatible**: Enjoy full TypeScript support for better development experience and type safety.
- **Custom Interceptors**: Includes `onRequest`, `onResponse`, and `onError` interceptors for flexible request and response handling.
Please open an issue for future requests.
@@ -68,8 +69,7 @@ yarn add fetchff
import { fetchf } from 'fetchff';
const { data, error, status } = await fetchf(
- 'https://example.com/api/v1/getBooks',
- { bookId: 1 },
+ 'https://example.com/api/v1/books',
{
timeout: 2000,
// Specify some other settings here...
@@ -437,36 +437,42 @@ You can also use all native `fetch()` settings.
Settings that are global only are marked with star `*`.
-| | Type | Default | Description |
-| ----------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| baseURL \* (alias: apiUrl) | string | | Your API base url. |
-| endpoints \* | object | | List of your endpoints. Each endpoint accepts all these settings. They can be set globally or per-endpoint when they are called. |
-| fetcher \* | Function | fetch | The native `fetch()` is used by default. A custom instance that exposes create() and request() can be used otherwise. |
-| url | string | | URL path e.g. /user-details/get |
-| method | string | get | Default request method e.g. GET, POST, DELETE, PUT etc. |
-| params | object URLSearchParams | {} | A key-value pairs added to the URL to send extra information with a request. If you pass an object, it will be automatically converted. It works with nested objects, arrays and custom data structures. If you use `createApiFetcher()` then it is the first argument of your api.endpoint() function. You can still pass configuration in 3rd argument if necessary. |
-| body (alias: data) | object string FormData URLSearchParams Blob ArrayBuffer ReadableStream | {} | The body is the data sent with the request, such as JSON, text, or form data, included in the request payload for POST, PUT, or PATCH requests. |
-| urlPathParams | object | {} | An object with URL path parameters so to dynamically replace placeholders in the URL path. For example, if URL contains a placeholder like `/users/:userId`, you can provide an object with the `userId` key to replace that placeholder with an actual value. The keys in the `urlPathParams` object should match the placeholders in the URL. This allows for dynamic URL construction based on runtime values. |
-| strategy | string | reject | Error handling strategies - basically what to return when an error occurs. It can be a default data, promise can be hanged (nothing would be returned) or rejected so to use try/catch.
`reject` - Promises are rejected, and global error handling is triggered. Requires try/catch for handling.
`softFail` - returns a response object with additional properties such as `data`, `error`, `config`, `request`, and `headers` when an error occurs. This approach avoids throwing errors, allowing you to handle error information directly within the response object without the need for try/catch blocks.
`defaultResponse` - returns default response specified in case of an error. Promise will not be rejected. It could be used in conjuction with `flattenResponse` and as `defaultResponse: {}` so to provide a sensible defaults.
`silent` - hangs the promise silently on error, useful for fire-and-forget requests without the need for try/catch. In case of an error, the promise will never be resolved or rejected, and any code after will never be executed. The requests could be dispatched within an asynchronous wrapper functions that do not need to be awaited. If used properly, it prevents excessive usage of try/catch or additional response data checks everywhere. You can use it in combination with `onError` to handle errors separately. |
-| cancellable | boolean | false | If `true`, any ongoing previous requests to same API endpoint will be cancelled, if a subsequent request is made meanwhile. This helps you avoid unnecessary requests to the backend. |
-| rejectCancelled | boolean | false | If `true` and request is set to `cancellable`, a cancelled requests' promise will be rejected. By default, instead of rejecting the promise, `defaultResponse` is returned. |
-| flattenResponse | boolean | false | Flatten nested response data, so you can avoid writing `response.data.data` and obtain response directly. Response is flattened when there is a "data" within response "data", and no other object properties set. |
-| defaultResponse | any | null | Default response when there is no data or when endpoint fails depending on the chosen `strategy` |
-| withCredentials | boolean | false | Indicates whether credentials (such as cookies) should be included with the request. |
-| timeout | int | 30000 | You can set a request timeout for all requests or particular in milliseconds. |
-| dedupeTime | int | 1000 | Time window, in milliseconds, during which identical requests are deduplicated (treated as single request). |
-| onRequest | function(config) | | You can specify a function that will be triggered before the request is sent. The request configuration object will be sent as the first argument of the function. This is useful for modifying request parameters, headers, etc. |
-| onResponse | function(response) | | You can specify a function that will be triggered when the endpoint successfully responds. The full Response Object is sent as the first argument of the function. This is useful for handling the response data, parsing, and error handling based on status codes. |
-| onError | function(error) | | You can specify a function or class that will be triggered when endpoint fails. If it's a class it should expose a `process` method. When using native fetch(), the full Response Object is sent as a first argument of the function. |
-| logger | object | null | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. |
-| retry | object | | The object with retry settings available below. |
-| retry.retries | number | 0 | The number of times to retry the request in case of failure. If set to `0` (default), no retries will be attempted. |
-| retry.delay | number | 1000 | The initial delay (in milliseconds) between retry attempts. |
-| retry.backoff | number | 1.5 | The backoff factor to apply to the delay between retries. For example, if the delay is 100ms and the backoff is 1.5, the next delay will be 150ms, then 225ms, and so on. |
-| retry.maxDelay | number | 30000 | The maximum delay (in milliseconds) between retry attempts. |
-| retry.resetTimeout | boolean | true | Reset timeout when retrying requests. |
-| retry.retryOn | array | [408, 409, 425, 429, 500, 502, 503, 504] | An array of HTTP status codes on which to retry the request. Default values include: 408 (Request Timeout), 409 (Conflict), 425 (Too Early), 429 (Too Many Requests), 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). |
-| retry.shouldRetry | async function | | A custom asynchronous function to determine whether to retry the request. It receives two arguments: `error` (the error object) and `attempts` (the number of attempts made so far). |
+| | Type | Default | Description |
+| -------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| endpoints \* | `object` | | List of your endpoints. Each endpoint accepts all the settings below. They can be set globally, per-endpoint or per-request. |
+| fetcher \* | `FetcherInstance` | | A custom adapter (an instance / object) that exposes `create()` function so to create instance of API Fetcher. The `create()` should return `request()` function that would be used when making the requests. The native `fetch()` is used if the fetcher is not provided. |
+| baseURL (alias: apiUrl) | `string` | | Your API base url. |
+| url | `string` | | URL path e.g. /user-details/get |
+| method | `string` | `GET` | Default request method e.g. GET, POST, DELETE, PUT etc. All methods are supported. |
+| params | `object` `URLSearchParams` `NameValuePair[]` | `{}` | Query Parameters - a key-value pairs added to the URL to send extra information with a request. If you pass an object, it will be automatically converted. It works with nested objects, arrays and custom data structures similarly to what `jQuery` used to do in the past. If you use `createApiFetcher()` then it is the first argument of your `api.myEndpoint()` function. You can still pass configuration in 3rd argument if want to. |
+| body (alias: data) | `object` `string` `FormData` `URLSearchParams` `Blob` `ArrayBuffer` `ReadableStream` | `{}` | The body is the data sent with the request, such as JSON, text, or form data, included in the request payload for POST, PUT, or PATCH requests. |
+| urlPathParams | `object` | `{}` | An object with URL path parameters so to dynamically replace placeholders in the URL path. For example, if URL contains a placeholder like `/users/:userId`, you can provide an object with the `userId` key to replace that placeholder with an actual value. The keys in the `urlPathParams` object should match the placeholders in the URL. This allows for dynamic URL construction based on runtime values. |
+| strategy | `string` | `reject` | Error handling strategies - basically what to return when an error occurs. It can be a default data, promise can be hanged (nothing would be returned) or rejected so to use try/catch.
`reject` - Promises are rejected, and global error handling is triggered. Requires try/catch for handling.
`softFail` - returns a response object with additional properties such as `data`, `error`, `config`, `request`, and `headers` when an error occurs. This approach avoids throwing errors, allowing you to handle error information directly within the response object without the need for try/catch blocks.
`defaultResponse` - returns default response specified in case of an error. Promise will not be rejected. It could be used in conjuction with `flattenResponse` and as `defaultResponse: {}` so to provide a sensible defaults.
`silent` - hangs the promise silently on error, useful for fire-and-forget requests without the need for try/catch. In case of an error, the promise will never be resolved or rejected, and any code after will never be executed. The requests could be dispatched within an asynchronous wrapper functions that do not need to be awaited. If used properly, it prevents excessive usage of try/catch or additional response data checks everywhere. You can use it in combination with `onError` to handle errors separately. |
+| cancellable | `boolean` | `false` | If `true`, any ongoing previous requests to same API endpoint will be cancelled, if a subsequent request is made meanwhile. This helps you avoid unnecessary requests to the backend. |
+| rejectCancelled | `boolean` | `false` | If `true` and request is set to `cancellable`, a cancelled requests' promise will be rejected. By default, instead of rejecting the promise, `defaultResponse` is returned. |
+| flattenResponse | `boolean` | `false` | Flatten nested response data, so you can avoid writing `response.data.data` and obtain response directly. Response is flattened when there is a "data" within response "data", and no other object properties set. |
+| defaultResponse | `any` | `null` | Default response when there is no data or when endpoint fails depending on the chosen `strategy` |
+| withCredentials | `boolean` | `false` | Indicates whether credentials (such as cookies) should be included with the request. |
+| timeout | `number` | `30000` | You can set a request timeout for all requests or particular in milliseconds. |
+| dedupeTime | `number` | `1000` | Time window, in milliseconds, during which identical requests are deduplicated (treated as single request). |
+| pollingInterval | `number` | `0` | Interval in milliseconds between polling attempts. Setting `0` disables polling. |
+| shouldStopPolling | `PollingFunction` | `(response, error, attempt) => false` | Function to determine if polling should stop based on the response. Returns `true` to stop polling, `false` to continue. |
+| cacheTime | `number` | `0` | Maximum time, in seconds, a cache entry is considered fresh. After this time, the entry may be considered stale. |
+| cacheKey | `CacheKeyFunction` | | Function to customize the cache key. If not provided, it automatically generates a unique cache key based on `Method`, `URL`, `Query Params`, `Dynamic Path Params`, `mode`, `credentials`, `cache`, `redirect`, `referrer`, `integrity`, `headers` and `body`, selectively using a very fast hashing variant of djb2 authored by Daniel J. Bernstein. |
+| cacheBuster | `CacheBusterFunction` | `(config) => false` | Function to invalidate or refresh cache under certain conditions. Defaults to no cache busting. |
+| skipCache | `CacheSkipFunction` | `(response, config) => false` | Function to determine whether to set or skip setting caching based on the response. Defaults to no skipping. |
+| onRequest | `RequestInterceptor` `RequestInterceptor[]` | `(config) => config` | A function or an array of functions that are invoked before sending a request. Each function receives the request configuration object as its argument, allowing you to modify request parameters, headers, or other settings. The function should return the updated configuration. |
+| onResponse | `ResponseInterceptor` `ResponseInterceptor[]` | `(response) => response` | A function or an array of functions that are invoked when a response is received. Each function receives the full response object, enabling you to process the response, handle status codes, or parse data as needed. The function should return the processed response. |
+| onError | `ErrorInterceptor` | `(error) => void` | You can specify a function or class that will be triggered when endpoint fails. If it's a class it should expose a `process` method. When using native fetch(), the full Response Object is sent as a first argument of the function. |
+| logger | `object` | `null` | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. |
+| retry | `object` | | The object with retry settings available below. |
+| retry.retries | `number` | `0` | The number of times to retry the request in case of failure. If set to `0` (default), no retries will be attempted. |
+| retry.delay | `number` | `1000` | The initial delay (in milliseconds) between retry attempts. |
+| retry.backoff | `number` | `1.5` | The backoff factor to apply to the delay between retries. For example, if the delay is 100ms and the backoff is 1.5, the next delay will be 150ms, then 225ms, and so on. |
+| retry.maxDelay | `number` | `30000` | The maximum delay (in milliseconds) between retry attempts. Default is equal to timeout. |
+| retry.resetTimeout | `boolean` | `true` | Reset timeout when retrying requests. |
+| retry.retryOn | `number[]` | `[408, 409, 425, 429, 500, 502, 503, 504]` | An array of HTTP status codes on which to retry the request. Default values include: 408 (Request Timeout), 409 (Conflict), 425 (Too Early), 429 (Too Many Requests), 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). |
+| retry.shouldRetry | `RetryFunction` | `(error, attempt) => true` | A custom asynchronous function to determine whether to retry the request. It receives two arguments: `error` - the error object, and `attempts` - the current attempts made, starting from `0`. |
## ✔️ Retry Mechanism
@@ -691,14 +697,10 @@ try {
```typescript
import { fetchf } from 'fetchff';
-const books = await fetchf(
- 'https://example.com/api/v1/getBooks',
- { bookId: 1 },
- {
- timeout: 2000,
- // Specify some other settings here...
- },
-);
+const books = await fetchf('https://example.com/api/v1/books', {
+ timeout: 2000,
+ // Specify some other settings here...
+});
```
### Multiple APIs Handler from different API sources
diff --git a/src/cache-manager.ts b/src/cache-manager.ts
new file mode 100644
index 0000000..a6e0436
--- /dev/null
+++ b/src/cache-manager.ts
@@ -0,0 +1,216 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { hash } from './hash';
+import { fetchf } from './index';
+import type { FetcherConfig } from './types/request-handler';
+import type { CacheEntry } from './types/cache-manager';
+import { GET, OBJECT, UNDEFINED } from './const';
+import { shallowSerialize, sortObject } from './utils';
+
+const cache = new Map>();
+
+/**
+ * Generates a cache key for a given URL and fetch options, ensuring that key factors
+ * like method, headers, body, and other options are included in the cache key.
+ * Headers and other objects are sorted by key to ensure consistent cache keys.
+ *
+ * @param options - The fetch options that may affect the request. The most important are:
+ * @property {string} [method="GET"] - The HTTP method (GET, POST, etc.).
+ * @property {HeadersInit} [headers={}] - The request headers.
+ * @property {BodyInit | null} [body=""] - The body of the request (only for methods like POST, PUT).
+ * @property {RequestMode} [mode="cors"] - The mode for the request (e.g., cors, no-cors, include).
+ * @property {RequestCredentials} [credentials="include"] - Whether to include credentials like cookies.
+ * @property {RequestCache} [cache="default"] - The cache mode (e.g., default, no-store, reload).
+ * @property {RequestRedirect} [redirect="follow"] - How to handle redirects (e.g., follow, error, manual).
+ * @property {string} [referrer=""] - The referrer URL to send with the request.
+ * @property {string} [integrity=""] - Subresource integrity value (a cryptographic hash for resource validation).
+ * @returns {string} - A unique cache key based on the URL and request options. Empty if cache is to be burst.
+ *
+ * @example
+ * const cacheKey = generateCacheKey({
+ * url: 'https://api.example.com/data',
+ * method: 'POST',
+ * headers: { 'Content-Type': 'application/json' },
+ * body: JSON.stringify({ name: 'Alice' }),
+ * mode: 'cors',
+ * credentials: 'include',
+ * });
+ * console.log(cacheKey);
+ */
+export function generateCacheKey(options: FetcherConfig): string {
+ const {
+ url = '',
+ method = GET,
+ headers = {},
+ body = '',
+ mode = 'cors',
+ credentials = 'include',
+ cache = 'default',
+ redirect = 'follow',
+ referrer = '',
+ integrity = '',
+ } = options;
+
+ // Bail early if cache should be burst
+ if (cache === 'reload') {
+ return '';
+ }
+
+ // Sort headers and body + convert sorted to strings for hashing purposes
+ // Native serializer is on avg. 3.5x faster than a Fast Hash or FNV-1a
+ const headersString = shallowSerialize(sortObject(headers));
+
+ let bodyString = '';
+
+ // In majority of cases we do not cache body
+ if (body !== null) {
+ if (typeof body === 'string') {
+ bodyString = hash(body);
+ } else if (body instanceof FormData) {
+ body.forEach((value, key) => {
+ // Append key=value and '&' directly to the result
+ bodyString += key + '=' + value + '&';
+ });
+ bodyString = hash(bodyString);
+ } else if (
+ (typeof Blob !== UNDEFINED && body instanceof Blob) ||
+ (typeof File !== UNDEFINED && body instanceof File)
+ ) {
+ bodyString = 'BF' + body.size + body.type;
+ } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
+ bodyString = 'AB' + body.byteLength;
+ } else {
+ const o = typeof body === OBJECT ? sortObject(body) : String(body);
+ bodyString = hash(JSON.stringify(o));
+ }
+ }
+
+ // Concatenate all key parts into a cache key string
+ // Template literals are apparently slower
+ return (
+ method +
+ url +
+ mode +
+ credentials +
+ cache +
+ redirect +
+ referrer +
+ integrity +
+ headersString +
+ bodyString
+ );
+}
+
+/**
+ * Checks if the cache entry is expired based on its timestamp and the maximum stale time.
+ *
+ * @param {number} timestamp - The timestamp of the cache entry.
+ * @param {number} maxStaleTime - The maximum stale time in seconds.
+ * @returns {boolean} - Returns true if the cache entry is expired, false otherwise.
+ */
+function isCacheExpired(timestamp: number, maxStaleTime: number): boolean {
+ if (!maxStaleTime) {
+ return false;
+ }
+
+ return Date.now() - timestamp > maxStaleTime * 1000;
+}
+
+/**
+ * Retrieves a cache entry if it exists and is not expired.
+ *
+ * @param {string} key Cache key to utilize
+ * @param {FetcherConfig} cacheTime - Maximum time to cache entry.
+ * @returns {CacheEntry | null} - The cache entry if it exists and is not expired, null otherwise.
+ */
+export function getCache(
+ key: string,
+ cacheTime: number,
+): CacheEntry | null {
+ const entry = cache.get(key);
+
+ if (entry) {
+ if (!isCacheExpired(entry.timestamp, cacheTime)) {
+ return entry;
+ }
+
+ cache.delete(key);
+ }
+
+ return null;
+}
+
+/**
+ * Sets a new cache entry or updates an existing one.
+ *
+ * @param {string} key Cache key to utilize
+ * @param {T} data - The data to be cached.
+ * @param {boolean} isLoading - Indicates if the data is currently being fetched.
+ */
+export function setCache(
+ key: string,
+ data: T,
+ isLoading: boolean = false,
+): void {
+ cache.set(key, {
+ data,
+ isLoading,
+ timestamp: Date.now(),
+ });
+}
+
+/**
+ * Revalidates a cache entry by fetching fresh data and updating the cache.
+ *
+ * @param {string} key Cache key to utilize
+ * @param {FetcherConfig} config - The request configuration object.
+ * @returns {Promise} - A promise that resolves when the revalidation is complete.
+ */
+export async function revalidate(
+ key: string,
+ config: FetcherConfig,
+): Promise {
+ try {
+ // Fetch fresh data
+ const newData = await fetchf(config.url, {
+ ...config,
+ cache: 'reload',
+ });
+
+ setCache(key, newData);
+ } catch (error) {
+ console.error(`Error revalidating ${config.url}:`, error);
+
+ // Rethrow the error to forward it
+ throw error;
+ }
+}
+
+/**
+ * Invalidates (deletes) a cache entry.
+ *
+ * @param {string} key Cache key to utilize
+ */
+export function deleteCache(key: string): void {
+ cache.delete(key);
+}
+
+/**
+ * Mutates a cache entry with new data and optionally revalidates it.
+ *
+ * @param {string} key Cache key to utilize
+ * @param {FetcherConfig} config - The request configuration object.
+ * @param {T} newData - The new data to be cached.
+ * @param {boolean} revalidateAfter - If true, triggers revalidation after mutation.
+ */
+export function mutate(
+ key: string,
+ config: FetcherConfig,
+ newData: T,
+ revalidateAfter: boolean = false,
+): void {
+ setCache(key, newData);
+
+ if (revalidateAfter) {
+ revalidate(key, config);
+ }
+}
diff --git a/src/const.ts b/src/const.ts
index f4e8f9f..41913df 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -1,9 +1,13 @@
export const APPLICATION_JSON = 'application/json';
export const CONTENT_TYPE = 'Content-Type';
+
export const UNDEFINED = 'undefined';
export const OBJECT = 'object';
+export const STRING = 'string';
+
export const ABORT_ERROR = 'AbortError';
export const TIMEOUT_ERROR = 'TimeoutError';
export const CANCELLED_ERROR = 'CanceledError';
+
export const GET = 'GET';
export const HEAD = 'HEAD';
diff --git a/src/hash.ts b/src/hash.ts
index 0719759..a79c9ad 100644
--- a/src/hash.ts
+++ b/src/hash.ts
@@ -1,68 +1,20 @@
-import { UNDEFINED } from './const';
-import { RequestConfig } from './types';
-
-// Garbage collected hash cache table
-// Since we use hashtable, it is really fast
-const hashCache = new WeakMap