@jaseeey/request-manager 2.0.4-alpha.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/request-manager/request-manager.d.ts +33 -13
- package/dist/cjs/request-manager/request-manager.js +37 -9
- package/dist/esm/package.json +1 -0
- package/dist/esm/request-manager/request-manager.d.ts +33 -13
- package/dist/esm/request-manager/request-manager.js +38 -12
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ catch (err) {
|
|
|
74
74
|
- `url`: The URL to which the request is sent.
|
|
75
75
|
- `data` (optional): The data to be sent as the request body.
|
|
76
76
|
- `config` (optional): The configuration options for the request.
|
|
77
|
-
- `onSuccess` (optional): A callback function that is called when the request is successful.
|
|
77
|
+
- `onSuccess` (optional): A callback function that is called when the request is successful. Returning a non-`undefined` value from this callback changes the resolved value of the `call()` promise.
|
|
78
78
|
- `onError` (optional): A callback function that is called when the request fails.
|
|
79
79
|
|
|
80
80
|
## Known Limitations
|
|
@@ -89,7 +89,7 @@ Given that only the first set of callbacks are executed, managing state updates
|
|
|
89
89
|
|
|
90
90
|
### Request Differentiation
|
|
91
91
|
|
|
92
|
-
The system identifies duplicate requests based
|
|
92
|
+
The system identifies duplicate requests based on client instance, URL, and method, but still overlooks differences in headers, query parameters, or POST bodies for otherwise matching requests. Applications requiring differentiation based on those fields may need to extend the key generation logic within the RequestManager.
|
|
93
93
|
|
|
94
94
|
### Lifecycle Management
|
|
95
95
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
|
|
2
2
|
type RequestKey = string;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
interface RequestPromise {
|
|
5
|
+
original: Promise<AxiosResponse<unknown>>;
|
|
6
|
+
processed: Promise<unknown>;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* RequestManager
|
|
@@ -11,20 +12,39 @@ interface RequestPromise<T = any> {
|
|
|
11
12
|
* concurrently. When a request to a specific URL and method is in progress, subsequent requests to the same URL and
|
|
12
13
|
* method will return the same promise, preventing duplicate requests.
|
|
13
14
|
*/
|
|
14
|
-
declare class RequestManager<T = any> {
|
|
15
|
+
export declare class RequestManager<T = any> {
|
|
15
16
|
/**
|
|
16
|
-
* A Map to store active requests, with each key being a combination of URL and HTTP method and
|
|
17
|
-
* value being the Promise of the ongoing HTTP request.
|
|
17
|
+
* A Map to store active requests, with each key being a combination of client instance, URL, and HTTP method and
|
|
18
|
+
* its corresponding value being the Promise of the ongoing HTTP request.
|
|
18
19
|
*/
|
|
19
|
-
activeRequests: Map<RequestKey, RequestPromise
|
|
20
|
+
activeRequests: Map<RequestKey, RequestPromise>;
|
|
21
|
+
private clientIds;
|
|
22
|
+
private nextClientId;
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* onFailure callback if the request fails.
|
|
24
|
+
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
25
|
+
*
|
|
26
|
+
* @deprecated Prefer the default exported instance (`requestManager.call(...)`).
|
|
25
27
|
*/
|
|
26
|
-
call(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse) =>
|
|
28
|
+
static call<TResponse = any, TSuccess = never>(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null, onError?: ((error: unknown) => void) | null): Promise<AxiosResponse<TResponse> | TSuccess | void>;
|
|
29
|
+
/**
|
|
30
|
+
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
31
|
+
*
|
|
32
|
+
* Requests are de-duplicated only when they target the same client instance, method, and URL.
|
|
33
|
+
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
34
|
+
*
|
|
35
|
+
* @param client Axios client used to execute the request.
|
|
36
|
+
* @param method HTTP method.
|
|
37
|
+
* @param url Target request URL.
|
|
38
|
+
* @param data Optional request payload.
|
|
39
|
+
* @param config Optional Axios request configuration.
|
|
40
|
+
* @param onSuccess Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
41
|
+
* @param onError Optional callback invoked on error. If provided, the promise resolves to `void` after handling.
|
|
42
|
+
*/
|
|
43
|
+
call<TResponse = T, TSuccess = never>(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null, onError?: ((error: unknown) => void) | null): Promise<AxiosResponse<TResponse> | TSuccess | void>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a stable in-memory key for de-duplication within this manager instance.
|
|
46
|
+
*/
|
|
47
|
+
private buildRequestKey;
|
|
27
48
|
}
|
|
28
49
|
declare const requestManager: RequestManager<any>;
|
|
29
|
-
export { RequestManager };
|
|
30
50
|
export default requestManager;
|
|
@@ -10,20 +10,38 @@ exports.RequestManager = void 0;
|
|
|
10
10
|
*/
|
|
11
11
|
class RequestManager {
|
|
12
12
|
/**
|
|
13
|
-
* A Map to store active requests, with each key being a combination of URL and HTTP method and
|
|
14
|
-
* value being the Promise of the ongoing HTTP request.
|
|
13
|
+
* A Map to store active requests, with each key being a combination of client instance, URL, and HTTP method and
|
|
14
|
+
* its corresponding value being the Promise of the ongoing HTTP request.
|
|
15
15
|
*/
|
|
16
16
|
activeRequests = new Map();
|
|
17
|
+
clientIds = new WeakMap();
|
|
18
|
+
nextClientId = 1;
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
20
|
+
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Prefer the default exported instance (`requestManager.call(...)`).
|
|
23
|
+
*/
|
|
24
|
+
static call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
25
|
+
return requestManager.call(client, method, url, data, config, onSuccess, onError);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
29
|
+
*
|
|
30
|
+
* Requests are de-duplicated only when they target the same client instance, method, and URL.
|
|
31
|
+
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
32
|
+
*
|
|
33
|
+
* @param client Axios client used to execute the request.
|
|
34
|
+
* @param method HTTP method.
|
|
35
|
+
* @param url Target request URL.
|
|
36
|
+
* @param data Optional request payload.
|
|
37
|
+
* @param config Optional Axios request configuration.
|
|
38
|
+
* @param onSuccess Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
39
|
+
* @param onError Optional callback invoked on error. If provided, the promise resolves to `void` after handling.
|
|
22
40
|
*/
|
|
23
41
|
async call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
24
42
|
data ??= {};
|
|
25
43
|
config ??= {};
|
|
26
|
-
const key =
|
|
44
|
+
const key = this.buildRequestKey(client, method, url);
|
|
27
45
|
if (this.activeRequests.has(key)) {
|
|
28
46
|
return this.activeRequests.get(key).processed;
|
|
29
47
|
}
|
|
@@ -31,7 +49,7 @@ class RequestManager {
|
|
|
31
49
|
try {
|
|
32
50
|
const requestResult = await responsePromise;
|
|
33
51
|
const callbackResult = onSuccess && typeof onSuccess === 'function'
|
|
34
|
-
? onSuccess(requestResult)
|
|
52
|
+
? await onSuccess(requestResult)
|
|
35
53
|
: undefined;
|
|
36
54
|
return callbackResult !== undefined
|
|
37
55
|
? callbackResult
|
|
@@ -52,8 +70,18 @@ class RequestManager {
|
|
|
52
70
|
this.activeRequests.set(key, { original: requestPromise, processed: processedPromise });
|
|
53
71
|
return processedPromise;
|
|
54
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Creates a stable in-memory key for de-duplication within this manager instance.
|
|
75
|
+
*/
|
|
76
|
+
buildRequestKey(client, method, url) {
|
|
77
|
+
let clientId = this.clientIds.get(client);
|
|
78
|
+
if (!clientId) {
|
|
79
|
+
clientId = this.nextClientId++;
|
|
80
|
+
this.clientIds.set(client, clientId);
|
|
81
|
+
}
|
|
82
|
+
return `${clientId}:${method.toLowerCase()}:${url}`;
|
|
83
|
+
}
|
|
55
84
|
}
|
|
56
85
|
exports.RequestManager = RequestManager;
|
|
57
|
-
// Create default instance
|
|
58
86
|
const requestManager = new RequestManager();
|
|
59
87
|
exports.default = requestManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "module"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
|
|
2
2
|
type RequestKey = string;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
interface RequestPromise {
|
|
5
|
+
original: Promise<AxiosResponse<unknown>>;
|
|
6
|
+
processed: Promise<unknown>;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* RequestManager
|
|
@@ -11,20 +12,39 @@ interface RequestPromise<T = any> {
|
|
|
11
12
|
* concurrently. When a request to a specific URL and method is in progress, subsequent requests to the same URL and
|
|
12
13
|
* method will return the same promise, preventing duplicate requests.
|
|
13
14
|
*/
|
|
14
|
-
declare class RequestManager<T = any> {
|
|
15
|
+
export declare class RequestManager<T = any> {
|
|
15
16
|
/**
|
|
16
|
-
* A Map to store active requests, with each key being a combination of URL and HTTP method and
|
|
17
|
-
* value being the Promise of the ongoing HTTP request.
|
|
17
|
+
* A Map to store active requests, with each key being a combination of client instance, URL, and HTTP method and
|
|
18
|
+
* its corresponding value being the Promise of the ongoing HTTP request.
|
|
18
19
|
*/
|
|
19
|
-
activeRequests: Map<RequestKey, RequestPromise
|
|
20
|
+
activeRequests: Map<RequestKey, RequestPromise>;
|
|
21
|
+
private clientIds;
|
|
22
|
+
private nextClientId;
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* onFailure callback if the request fails.
|
|
24
|
+
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
25
|
+
*
|
|
26
|
+
* @deprecated Prefer the default exported instance (`requestManager.call(...)`).
|
|
25
27
|
*/
|
|
26
|
-
call(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse) =>
|
|
28
|
+
static call<TResponse = any, TSuccess = never>(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null, onError?: ((error: unknown) => void) | null): Promise<AxiosResponse<TResponse> | TSuccess | void>;
|
|
29
|
+
/**
|
|
30
|
+
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
31
|
+
*
|
|
32
|
+
* Requests are de-duplicated only when they target the same client instance, method, and URL.
|
|
33
|
+
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
34
|
+
*
|
|
35
|
+
* @param client Axios client used to execute the request.
|
|
36
|
+
* @param method HTTP method.
|
|
37
|
+
* @param url Target request URL.
|
|
38
|
+
* @param data Optional request payload.
|
|
39
|
+
* @param config Optional Axios request configuration.
|
|
40
|
+
* @param onSuccess Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
41
|
+
* @param onError Optional callback invoked on error. If provided, the promise resolves to `void` after handling.
|
|
42
|
+
*/
|
|
43
|
+
call<TResponse = T, TSuccess = never>(client: AxiosInstance, method: Method, url: string, data?: any, config?: AxiosRequestConfig | null, onSuccess?: ((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null, onError?: ((error: unknown) => void) | null): Promise<AxiosResponse<TResponse> | TSuccess | void>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a stable in-memory key for de-duplication within this manager instance.
|
|
46
|
+
*/
|
|
47
|
+
private buildRequestKey;
|
|
27
48
|
}
|
|
28
49
|
declare const requestManager: RequestManager<any>;
|
|
29
|
-
export { RequestManager };
|
|
30
50
|
export default requestManager;
|
|
@@ -5,22 +5,40 @@
|
|
|
5
5
|
* concurrently. When a request to a specific URL and method is in progress, subsequent requests to the same URL and
|
|
6
6
|
* method will return the same promise, preventing duplicate requests.
|
|
7
7
|
*/
|
|
8
|
-
class RequestManager {
|
|
8
|
+
export class RequestManager {
|
|
9
9
|
/**
|
|
10
|
-
* A Map to store active requests, with each key being a combination of URL and HTTP method and
|
|
11
|
-
* value being the Promise of the ongoing HTTP request.
|
|
10
|
+
* A Map to store active requests, with each key being a combination of client instance, URL, and HTTP method and
|
|
11
|
+
* its corresponding value being the Promise of the ongoing HTTP request.
|
|
12
12
|
*/
|
|
13
13
|
activeRequests = new Map();
|
|
14
|
+
clientIds = new WeakMap();
|
|
15
|
+
nextClientId = 1;
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
17
|
+
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Prefer the default exported instance (`requestManager.call(...)`).
|
|
20
|
+
*/
|
|
21
|
+
static call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
22
|
+
return requestManager.call(client, method, url, data, config, onSuccess, onError);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
26
|
+
*
|
|
27
|
+
* Requests are de-duplicated only when they target the same client instance, method, and URL.
|
|
28
|
+
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
29
|
+
*
|
|
30
|
+
* @param client Axios client used to execute the request.
|
|
31
|
+
* @param method HTTP method.
|
|
32
|
+
* @param url Target request URL.
|
|
33
|
+
* @param data Optional request payload.
|
|
34
|
+
* @param config Optional Axios request configuration.
|
|
35
|
+
* @param onSuccess Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
36
|
+
* @param onError Optional callback invoked on error. If provided, the promise resolves to `void` after handling.
|
|
19
37
|
*/
|
|
20
38
|
async call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
21
39
|
data ??= {};
|
|
22
40
|
config ??= {};
|
|
23
|
-
const key =
|
|
41
|
+
const key = this.buildRequestKey(client, method, url);
|
|
24
42
|
if (this.activeRequests.has(key)) {
|
|
25
43
|
return this.activeRequests.get(key).processed;
|
|
26
44
|
}
|
|
@@ -28,7 +46,7 @@ class RequestManager {
|
|
|
28
46
|
try {
|
|
29
47
|
const requestResult = await responsePromise;
|
|
30
48
|
const callbackResult = onSuccess && typeof onSuccess === 'function'
|
|
31
|
-
? onSuccess(requestResult)
|
|
49
|
+
? await onSuccess(requestResult)
|
|
32
50
|
: undefined;
|
|
33
51
|
return callbackResult !== undefined
|
|
34
52
|
? callbackResult
|
|
@@ -49,9 +67,17 @@ class RequestManager {
|
|
|
49
67
|
this.activeRequests.set(key, { original: requestPromise, processed: processedPromise });
|
|
50
68
|
return processedPromise;
|
|
51
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a stable in-memory key for de-duplication within this manager instance.
|
|
72
|
+
*/
|
|
73
|
+
buildRequestKey(client, method, url) {
|
|
74
|
+
let clientId = this.clientIds.get(client);
|
|
75
|
+
if (!clientId) {
|
|
76
|
+
clientId = this.nextClientId++;
|
|
77
|
+
this.clientIds.set(client, clientId);
|
|
78
|
+
}
|
|
79
|
+
return `${clientId}:${method.toLowerCase()}:${url}`;
|
|
80
|
+
}
|
|
52
81
|
}
|
|
53
|
-
// Create default instance
|
|
54
82
|
const requestManager = new RequestManager();
|
|
55
|
-
// Export both the class and the default instance
|
|
56
|
-
export { RequestManager };
|
|
57
83
|
export default requestManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaseeey/request-manager",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A library to prevent multiple API requests when called simultaneously",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jason Ilicic <me@jasey.io>",
|
|
@@ -16,32 +16,33 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"
|
|
20
|
-
"build:
|
|
19
|
+
"prebuild": "rm -rf dist",
|
|
20
|
+
"build:esm": "tsc -p tsconfig.esm.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
|
|
21
|
+
"build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json",
|
|
21
22
|
"build": "npm run build:esm && npm run build:cjs",
|
|
22
|
-
"
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"test": "vitest run --coverage"
|
|
23
25
|
},
|
|
24
26
|
"dependencies": {
|
|
25
|
-
"axios": "^1.
|
|
27
|
+
"axios": "^1.13.6"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@types/axios": "^0.14.0",
|
|
30
|
-
"@types/jest": "^29.5.14",
|
|
30
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
31
31
|
"braces": "^3.0.3",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"typescript": "^5.7.2"
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "^4.0.18"
|
|
35
34
|
},
|
|
36
35
|
"main": "./dist/cjs/index.js",
|
|
37
36
|
"module": "./dist/esm/index.js",
|
|
38
37
|
"types": "./dist/esm/index.d.ts",
|
|
39
38
|
"exports": {
|
|
40
39
|
".": {
|
|
40
|
+
"types": "./dist/esm/index.d.ts",
|
|
41
41
|
"require": "./dist/cjs/index.js",
|
|
42
42
|
"import": "./dist/esm/index.js"
|
|
43
43
|
},
|
|
44
44
|
"./request-manager": {
|
|
45
|
+
"types": "./dist/esm/request-manager/index.d.ts",
|
|
45
46
|
"require": "./dist/cjs/request-manager/index.js",
|
|
46
47
|
"import": "./dist/esm/request-manager/index.js"
|
|
47
48
|
}
|