@jaseeey/request-manager 2.2.2 → 2.3.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 +22 -25
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/request-manager/index.d.ts +1 -1
- package/dist/cjs/request-manager/request-manager.d.ts +68 -20
- package/dist/cjs/request-manager/request-manager.js +78 -27
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/request-manager/index.d.ts +1 -1
- package/dist/esm/request-manager/request-manager.d.ts +68 -20
- package/dist/esm/request-manager/request-manager.js +78 -27
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ It is intentionally focused: not a full HTTP client, cache layer, or request que
|
|
|
23
23
|
- [FAQ](#faq)
|
|
24
24
|
- [Known limitations](#known-limitations)
|
|
25
25
|
- [Background and scope](#background-and-scope)
|
|
26
|
+
- [Features summary](#features-summary)
|
|
26
27
|
- [Contributing](#contributing)
|
|
27
28
|
- [License](#license)
|
|
28
29
|
|
|
@@ -65,13 +66,15 @@ You can use both: a query library for cache and lifecycle, and RequestManager un
|
|
|
65
66
|
npm install @jaseeey/request-manager axios
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
**Axios is a peer dependency** (`axios
|
|
69
|
+
**Axios is a peer dependency** (`axios ^1`). The package does not bundle a client for you; you pass your own `AxiosInstance` into `call()`. Install Axios alongside this library if it is not already in your project:
|
|
69
70
|
|
|
70
71
|
```bash
|
|
71
72
|
npm install axios
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Requires **Node.js
|
|
75
|
+
Requires **Node.js 22.12+** for development tooling (running the test suite); the
|
|
76
|
+
published package remains plain ESM/CJS JavaScript that runs on Node.js 18+ in your
|
|
77
|
+
applications.
|
|
75
78
|
|
|
76
79
|
---
|
|
77
80
|
|
|
@@ -147,7 +150,7 @@ Each in-flight request is stored under a key built from:
|
|
|
147
150
|
|
|
148
151
|
1. **Axios client instance** (identity, not config equality)
|
|
149
152
|
2. **HTTP method** (compared case-insensitively, e.g. `GET` and `get` match)
|
|
150
|
-
3. **URL string** (exact string match of the `url` argument—
|
|
153
|
+
3. **URL string** (exact string match of the `url` argument—or its `baseURL`-resolved form when `resolveBaseURL` is enabled)
|
|
151
154
|
4. **`config.params`** (deterministic serialisation; missing/`undefined`/empty params are equivalent)
|
|
152
155
|
|
|
153
156
|
```text
|
|
@@ -165,13 +168,14 @@ for (const [hash, entry] of requestManager.activeRequests) {
|
|
|
165
168
|
entry.identity.method; // e.g. 'get'
|
|
166
169
|
entry.identity.url;
|
|
167
170
|
entry.identity.params; // as supplied (or null)
|
|
168
|
-
entry.identity.
|
|
171
|
+
entry.identity.paramsSerialized;
|
|
172
|
+
entry.identity.paramsSerialised; // deprecated alias of `paramsSerialized`
|
|
169
173
|
entry.original; // underlying Axios promise
|
|
170
174
|
entry.processed; // shared caller promise
|
|
171
175
|
}
|
|
172
176
|
```
|
|
173
177
|
|
|
174
|
-
|
|
178
|
+
By default the raw `url` argument is the key segment; enable [`resolveBaseURL`](#constructor) to key on the `baseURL`-resolved URL instead.
|
|
175
179
|
|
|
176
180
|
### Lifecycle
|
|
177
181
|
|
|
@@ -194,6 +198,7 @@ There is **no cache** of completed responses. De-duplication applies only to con
|
|
|
194
198
|
| Different `data` bodies | Yes (same key) | Body is **not** in the key (mutations are usually user actions) |
|
|
195
199
|
| Different headers, timeout, signal, etc. | Yes (same key) | Other config is **not** in the key |
|
|
196
200
|
| Same path, different full URL strings | No | `'/users?id=1'` and `'/users?id=2'` are different keys |
|
|
201
|
+
| Relative URL and its `baseURL`-resolved absolute form | No (Yes with `resolveBaseURL`) | Opt-in `baseURL`-aware keying |
|
|
197
202
|
| Different Axios instances | No | Separate clients never share de-duplication |
|
|
198
203
|
| Different methods | No | `GET` and `POST` to the same URL are independent |
|
|
199
204
|
|
|
@@ -330,6 +335,9 @@ await RequestManager.call(client, 'GET', 'https://example.com/health');
|
|
|
330
335
|
- `onSuccess` may return a `Promise`; it is awaited.
|
|
331
336
|
- `onError` is not awaited; keep it synchronous or fire-and-forget async work carefully.
|
|
332
337
|
- Method matching for de-duplication is case-insensitive.
|
|
338
|
+
- `call()` rejects with a `TypeError` when the client is not an Axios-like instance, the
|
|
339
|
+
`method` or `url` is invalid, or `config.params` is not serialisable (e.g. a circular
|
|
340
|
+
reference).
|
|
333
341
|
|
|
334
342
|
### Instance fields
|
|
335
343
|
|
|
@@ -345,10 +353,17 @@ Clearing the map mid-flight is not recommended except in tests. If you clear an
|
|
|
345
353
|
|
|
346
354
|
```typescript
|
|
347
355
|
const manager = new RequestManager();
|
|
356
|
+
const resolvedManager = new RequestManager({ resolveBaseURL: true });
|
|
348
357
|
```
|
|
349
358
|
|
|
350
359
|
Creates an isolated manager. The generic type parameter on the class is historical; prefer generics on `call()` for response typing.
|
|
351
360
|
|
|
361
|
+
| Option | Type | Default | Description |
|
|
362
|
+
|--------|------|---------|-------------|
|
|
363
|
+
| `resolveBaseURL` | `boolean` | `false` | Resolve relative URLs against the client's `baseURL` before keying, so a relative URL and its absolute form share one in-flight request |
|
|
364
|
+
|
|
365
|
+
With `resolveBaseURL` enabled, `'/users/me'` on a client created with `baseURL: 'https://api.example.com'` de-duplicates against `'https://api.example.com/users/me'`. Resolution mirrors Axios's own `buildFullPath` rules: absolute and protocol-relative URLs are left untouched, and joining follows the same trailing-slash rules Axios uses.
|
|
366
|
+
|
|
352
367
|
---
|
|
353
368
|
|
|
354
369
|
## Effective usage patterns
|
|
@@ -592,26 +607,7 @@ De-duplication includes `config.params` but not request bodies, headers, timeout
|
|
|
592
607
|
|
|
593
608
|
### Exact URL string matching
|
|
594
609
|
|
|
595
|
-
`/users` and `/users/` are different keys. Relative URLs are not normalised against `baseURL` for keying—the string you pass is the key segment.
|
|
596
|
-
|
|
597
|
-
### `baseURL` is not part of the key
|
|
598
|
-
|
|
599
|
-
Axios may resolve a relative `url` against the client's `baseURL` when sending the request, but RequestManager keys only on the **`url` argument string**.
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
const client = axios.create({ baseURL: 'https://api.example.com' });
|
|
603
|
-
|
|
604
|
-
// These share one in-flight request (same url string: '/users/me')
|
|
605
|
-
await Promise.all([
|
|
606
|
-
requestManager.call(client, 'GET', '/users/me'),
|
|
607
|
-
requestManager.call(client, 'GET', '/users/me')
|
|
608
|
-
]);
|
|
609
|
-
|
|
610
|
-
// This is a different key (different url string), even if it hits the same origin
|
|
611
|
-
await requestManager.call(client, 'GET', 'https://api.example.com/users/me');
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
Keep the `url` argument consistent across call sites (usually the same relative path) so de-duplication works as intended.
|
|
610
|
+
`/users` and `/users/` are different keys. Relative URLs are not normalised against `baseURL` for keying by default—the string you pass is the key segment (enable `resolveBaseURL` for `baseURL`-aware keying).
|
|
615
611
|
|
|
616
612
|
### Client identity, not configuration equality
|
|
617
613
|
|
|
@@ -645,6 +641,7 @@ Accepted trade-offs (key design, first-callback wins, in-flight-only) match that
|
|
|
645
641
|
- Shared promise for all joiners until the request settles
|
|
646
642
|
- Optional `onSuccess` / `onError` hooks (with documented join semantics)
|
|
647
643
|
- Default shared instance **and** constructible isolated managers
|
|
644
|
+
- Opt-in **`baseURL`-aware keying** (`resolveBaseURL`) so relative and absolute URLs for the same resource share one in-flight request
|
|
648
645
|
- TypeScript types, ESM + CJS builds
|
|
649
646
|
- Legacy `RequestManager.call` static helper (deprecated)
|
|
650
647
|
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { ActiveRequest, RequestIdentity } from './request-manager/index.js';
|
|
1
|
+
export type { ActiveRequest, RequestIdentity, RequestManagerOptions } from './request-manager/index.js';
|
|
2
2
|
export { RequestManager } from './request-manager/index.js';
|
|
3
3
|
export { default } from './request-manager/index.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import requestManager, { RequestManager } from './request-manager.js';
|
|
2
|
-
export type { ActiveRequest, RequestIdentity } from './request-manager.js';
|
|
2
|
+
export type { ActiveRequest, RequestIdentity, RequestManagerOptions } from './request-manager.js';
|
|
3
3
|
export { RequestManager };
|
|
4
4
|
export default requestManager;
|
|
@@ -7,10 +7,28 @@ type MaybePromise<T> = T | Promise<T>;
|
|
|
7
7
|
export interface RequestIdentity {
|
|
8
8
|
clientId: number;
|
|
9
9
|
method: string;
|
|
10
|
+
/** URL string as passed to `call`, or resolved against the client's `baseURL` when `resolveBaseURL` is enabled. */
|
|
10
11
|
url: string;
|
|
11
12
|
params: unknown;
|
|
13
|
+
paramsSerialized: string;
|
|
14
|
+
/**
|
|
15
|
+
* Backward-compatible British spelling of `paramsSerialized`.
|
|
16
|
+
*
|
|
17
|
+
* @deprecated Use `paramsSerialized` instead; to be removed in a future major release.
|
|
18
|
+
*/
|
|
12
19
|
paramsSerialised: string;
|
|
13
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Configuration options for a {@link RequestManager} instance.
|
|
23
|
+
*/
|
|
24
|
+
export interface RequestManagerOptions {
|
|
25
|
+
/**
|
|
26
|
+
* When enabled, relative URLs are resolved against the client's `baseURL` before keying,
|
|
27
|
+
* so the same logical resource de-duplicates whether callers address it relatively or
|
|
28
|
+
* absolutely. Disabled by default to preserve historical exact-string keying.
|
|
29
|
+
*/
|
|
30
|
+
resolveBaseURL?: boolean;
|
|
31
|
+
}
|
|
14
32
|
/**
|
|
15
33
|
* Active in-flight request entry stored in {@link RequestManager.activeRequests}.
|
|
16
34
|
*
|
|
@@ -38,6 +56,13 @@ export declare class RequestManager<T = any> {
|
|
|
38
56
|
activeRequests: Map<RequestKey, ActiveRequest>;
|
|
39
57
|
private clientIds;
|
|
40
58
|
private nextClientId;
|
|
59
|
+
private readonly resolveBaseURL;
|
|
60
|
+
/**
|
|
61
|
+
* Creates an isolated manager.
|
|
62
|
+
*
|
|
63
|
+
* @param {RequestManagerOptions} [options] - Optional instance configuration.
|
|
64
|
+
*/
|
|
65
|
+
constructor(options?: RequestManagerOptions);
|
|
41
66
|
/**
|
|
42
67
|
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
43
68
|
*
|
|
@@ -48,47 +73,70 @@ export declare class RequestManager<T = any> {
|
|
|
48
73
|
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
49
74
|
*
|
|
50
75
|
* Requests are de-duplicated when they target the same client instance, method, URL, and `config.params`.
|
|
76
|
+
* When constructed with `resolveBaseURL`, relative URLs are resolved against the client's `baseURL` for keying.
|
|
51
77
|
* Request bodies and other config fields (headers, timeout, signal, etc.) are not part of the key.
|
|
52
78
|
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
53
79
|
*
|
|
54
|
-
* @param client Axios client used to execute the request.
|
|
55
|
-
* @param method HTTP method.
|
|
56
|
-
* @param url Target request URL.
|
|
57
|
-
* @param data Optional request payload.
|
|
58
|
-
* @param config Optional Axios request configuration.
|
|
59
|
-
* @param
|
|
60
|
-
*
|
|
80
|
+
* @param {AxiosInstance} client - Axios client used to execute the request.
|
|
81
|
+
* @param {Method} method - HTTP method.
|
|
82
|
+
* @param {string} url - Target request URL.
|
|
83
|
+
* @param {any} [data] - Optional request payload.
|
|
84
|
+
* @param {AxiosRequestConfig|null} [config] - Optional Axios request configuration.
|
|
85
|
+
* @param {((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null} [onSuccess] -
|
|
86
|
+
* Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
87
|
+
* @param {((error: unknown) => void) | null} [onError] - Optional callback for request failures only.
|
|
88
|
+
* If it throws, the original request error is rethrown.
|
|
89
|
+
* @returns {Promise<AxiosResponse<TResponse> | TSuccess | void>} - The shared promise for
|
|
90
|
+
* the in-flight request.
|
|
91
|
+
* @throws {TypeError} - If the client is not an Axios-like instance, the method or URL is
|
|
92
|
+
* invalid, or config.params is not serializable.
|
|
61
93
|
*/
|
|
62
94
|
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>;
|
|
63
95
|
/**
|
|
64
96
|
* Builds the stable human-readable identity for an in-flight request.
|
|
65
97
|
*
|
|
66
|
-
* @param client Axios client instance used for the request.
|
|
67
|
-
* @param method HTTP method.
|
|
68
|
-
* @param url Request URL string as passed to `call`.
|
|
69
|
-
* @param params Axios `config.params` value, if any.
|
|
70
|
-
* @returns Identity fields used for hashing and inspection.
|
|
98
|
+
* @param {AxiosInstance} client - Axios client instance used for the request.
|
|
99
|
+
* @param {Method} method - HTTP method.
|
|
100
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
101
|
+
* @param {AxiosRequestConfig['params']} params - Axios `config.params` value, if any.
|
|
102
|
+
* @returns {RequestIdentity} - Identity fields used for hashing and inspection.
|
|
71
103
|
*/
|
|
72
104
|
private buildRequestIdentity;
|
|
105
|
+
/**
|
|
106
|
+
* Resolves a relative URL against the client's `baseURL` for keying, mirroring Axios's
|
|
107
|
+
* own `buildFullPath` and `combineURLs` semantics.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
110
|
+
* @param {AxiosInstance} client - Axios client whose `defaults.baseURL` is the resolution base.
|
|
111
|
+
* @returns {string} - The URL string used as the key segment.
|
|
112
|
+
*/
|
|
113
|
+
private resolveUrlAgainstBase;
|
|
114
|
+
/**
|
|
115
|
+
* Determines whether a URL is absolute, matching Axios's `isAbsoluteURL` check.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} url - URL string to test.
|
|
118
|
+
* @returns {boolean} - True when `url` has a scheme or is protocol-relative.
|
|
119
|
+
*/
|
|
120
|
+
private isAbsoluteUrl;
|
|
73
121
|
/**
|
|
74
122
|
* Hashes a request identity into a fixed-length map key.
|
|
75
123
|
*
|
|
76
|
-
* @param identity Stable request identity.
|
|
77
|
-
* @returns Lowercase hex SHA-256 digest of the identity string.
|
|
124
|
+
* @param {RequestIdentity} identity - Stable request identity.
|
|
125
|
+
* @returns {string} - Lowercase hex SHA-256 digest of the identity string.
|
|
78
126
|
*/
|
|
79
127
|
private hashIdentity;
|
|
80
128
|
/**
|
|
81
|
-
*
|
|
129
|
+
* Serializes query params into a deterministic string for request keys.
|
|
82
130
|
*
|
|
83
|
-
* @param params Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
84
|
-
* @returns A stable
|
|
131
|
+
* @param {AxiosRequestConfig['params']} params - Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
132
|
+
* @returns {string} - A stable serialization, or an empty string when params are absent.
|
|
85
133
|
*/
|
|
86
134
|
private serializeParams;
|
|
87
135
|
/**
|
|
88
|
-
* JSON-like
|
|
136
|
+
* JSON-like serialization with sorted object keys so param order does not affect identity.
|
|
89
137
|
*
|
|
90
|
-
* @param value Value to
|
|
91
|
-
* @returns Deterministic string form of `value`.
|
|
138
|
+
* @param {unknown} value - Value to serialize.
|
|
139
|
+
* @returns {string} - Deterministic string form of `value`.
|
|
92
140
|
*/
|
|
93
141
|
private stableStringify;
|
|
94
142
|
}
|
|
@@ -17,6 +17,15 @@ class RequestManager {
|
|
|
17
17
|
activeRequests = new Map();
|
|
18
18
|
clientIds = new WeakMap();
|
|
19
19
|
nextClientId = 1;
|
|
20
|
+
resolveBaseURL;
|
|
21
|
+
/**
|
|
22
|
+
* Creates an isolated manager.
|
|
23
|
+
*
|
|
24
|
+
* @param {RequestManagerOptions} [options] - Optional instance configuration.
|
|
25
|
+
*/
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.resolveBaseURL = options.resolveBaseURL === true;
|
|
28
|
+
}
|
|
20
29
|
/**
|
|
21
30
|
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
22
31
|
*
|
|
@@ -29,18 +38,28 @@ class RequestManager {
|
|
|
29
38
|
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
30
39
|
*
|
|
31
40
|
* Requests are de-duplicated when they target the same client instance, method, URL, and `config.params`.
|
|
41
|
+
* When constructed with `resolveBaseURL`, relative URLs are resolved against the client's `baseURL` for keying.
|
|
32
42
|
* Request bodies and other config fields (headers, timeout, signal, etc.) are not part of the key.
|
|
33
43
|
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
34
44
|
*
|
|
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
|
|
41
|
-
*
|
|
45
|
+
* @param {AxiosInstance} client - Axios client used to execute the request.
|
|
46
|
+
* @param {Method} method - HTTP method.
|
|
47
|
+
* @param {string} url - Target request URL.
|
|
48
|
+
* @param {any} [data] - Optional request payload.
|
|
49
|
+
* @param {AxiosRequestConfig|null} [config] - Optional Axios request configuration.
|
|
50
|
+
* @param {((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null} [onSuccess] -
|
|
51
|
+
* Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
52
|
+
* @param {((error: unknown) => void) | null} [onError] - Optional callback for request failures only.
|
|
53
|
+
* If it throws, the original request error is rethrown.
|
|
54
|
+
* @returns {Promise<AxiosResponse<TResponse> | TSuccess | void>} - The shared promise for
|
|
55
|
+
* the in-flight request.
|
|
56
|
+
* @throws {TypeError} - If the client is not an Axios-like instance, the method or URL is
|
|
57
|
+
* invalid, or config.params is not serializable.
|
|
42
58
|
*/
|
|
43
59
|
call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
60
|
+
if (client == null || typeof client.request !== 'function') {
|
|
61
|
+
return Promise.reject(new TypeError('RequestManager.call requires an Axios client instance'));
|
|
62
|
+
}
|
|
44
63
|
if (typeof method !== 'string' || method.length === 0) {
|
|
45
64
|
return Promise.reject(new TypeError('RequestManager.call requires a non-empty HTTP method string'));
|
|
46
65
|
}
|
|
@@ -49,7 +68,13 @@ class RequestManager {
|
|
|
49
68
|
}
|
|
50
69
|
data ??= {};
|
|
51
70
|
config ??= {};
|
|
52
|
-
|
|
71
|
+
let identity;
|
|
72
|
+
try {
|
|
73
|
+
identity = this.buildRequestIdentity(client, method, url, config.params);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return Promise.reject(new TypeError('RequestManager.call requires serializable config.params'));
|
|
77
|
+
}
|
|
53
78
|
const hash = this.hashIdentity(identity);
|
|
54
79
|
const existing = this.activeRequests.get(hash);
|
|
55
80
|
if (existing) {
|
|
@@ -95,11 +120,11 @@ class RequestManager {
|
|
|
95
120
|
/**
|
|
96
121
|
* Builds the stable human-readable identity for an in-flight request.
|
|
97
122
|
*
|
|
98
|
-
* @param client Axios client instance used for the request.
|
|
99
|
-
* @param method HTTP method.
|
|
100
|
-
* @param url Request URL string as passed to `call`.
|
|
101
|
-
* @param params Axios `config.params` value, if any.
|
|
102
|
-
* @returns Identity fields used for hashing and inspection.
|
|
123
|
+
* @param {AxiosInstance} client - Axios client instance used for the request.
|
|
124
|
+
* @param {Method} method - HTTP method.
|
|
125
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
126
|
+
* @param {AxiosRequestConfig['params']} params - Axios `config.params` value, if any.
|
|
127
|
+
* @returns {RequestIdentity} - Identity fields used for hashing and inspection.
|
|
103
128
|
*/
|
|
104
129
|
buildRequestIdentity(client, method, url, params) {
|
|
105
130
|
let clientId = this.clientIds.get(client);
|
|
@@ -107,31 +132,57 @@ class RequestManager {
|
|
|
107
132
|
clientId = this.nextClientId++;
|
|
108
133
|
this.clientIds.set(client, clientId);
|
|
109
134
|
}
|
|
110
|
-
const
|
|
111
|
-
const
|
|
135
|
+
const normalizedMethod = method.toLowerCase();
|
|
136
|
+
const urlKey = this.resolveBaseURL ? this.resolveUrlAgainstBase(url, client) : url;
|
|
137
|
+
const paramsSerialized = this.serializeParams(params);
|
|
112
138
|
return {
|
|
113
139
|
clientId,
|
|
114
|
-
method:
|
|
115
|
-
url,
|
|
140
|
+
method: normalizedMethod,
|
|
141
|
+
url: urlKey,
|
|
116
142
|
params: params ?? null,
|
|
117
|
-
|
|
143
|
+
paramsSerialized,
|
|
144
|
+
paramsSerialised: paramsSerialized
|
|
118
145
|
};
|
|
119
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolves a relative URL against the client's `baseURL` for keying, mirroring Axios's
|
|
149
|
+
* own `buildFullPath` and `combineURLs` semantics.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
152
|
+
* @param {AxiosInstance} client - Axios client whose `defaults.baseURL` is the resolution base.
|
|
153
|
+
* @returns {string} - The URL string used as the key segment.
|
|
154
|
+
*/
|
|
155
|
+
resolveUrlAgainstBase(url, client) {
|
|
156
|
+
const baseURL = client.defaults?.baseURL;
|
|
157
|
+
if (typeof baseURL !== 'string' || baseURL.length === 0 || this.isAbsoluteUrl(url)) {
|
|
158
|
+
return url;
|
|
159
|
+
}
|
|
160
|
+
return baseURL.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, '');
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Determines whether a URL is absolute, matching Axios's `isAbsoluteURL` check.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} url - URL string to test.
|
|
166
|
+
* @returns {boolean} - True when `url` has a scheme or is protocol-relative.
|
|
167
|
+
*/
|
|
168
|
+
isAbsoluteUrl(url) {
|
|
169
|
+
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
|
|
170
|
+
}
|
|
120
171
|
/**
|
|
121
172
|
* Hashes a request identity into a fixed-length map key.
|
|
122
173
|
*
|
|
123
|
-
* @param identity Stable request identity.
|
|
124
|
-
* @returns Lowercase hex SHA-256 digest of the identity string.
|
|
174
|
+
* @param {RequestIdentity} identity - Stable request identity.
|
|
175
|
+
* @returns {string} - Lowercase hex SHA-256 digest of the identity string.
|
|
125
176
|
*/
|
|
126
177
|
hashIdentity(identity) {
|
|
127
|
-
const material = `${identity.clientId}:${identity.method}:${identity.url}:${identity.
|
|
178
|
+
const material = `${identity.clientId}:${identity.method}:${identity.url}:${identity.paramsSerialized}`;
|
|
128
179
|
return (0, sha256_js_1.sha256Hex)(material);
|
|
129
180
|
}
|
|
130
181
|
/**
|
|
131
|
-
*
|
|
182
|
+
* Serializes query params into a deterministic string for request keys.
|
|
132
183
|
*
|
|
133
|
-
* @param params Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
134
|
-
* @returns A stable
|
|
184
|
+
* @param {AxiosRequestConfig['params']} params - Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
185
|
+
* @returns {string} - A stable serialization, or an empty string when params are absent.
|
|
135
186
|
*/
|
|
136
187
|
serializeParams(params) {
|
|
137
188
|
if (params == null) {
|
|
@@ -153,10 +204,10 @@ class RequestManager {
|
|
|
153
204
|
return this.stableStringify(params);
|
|
154
205
|
}
|
|
155
206
|
/**
|
|
156
|
-
* JSON-like
|
|
207
|
+
* JSON-like serialization with sorted object keys so param order does not affect identity.
|
|
157
208
|
*
|
|
158
|
-
* @param value Value to
|
|
159
|
-
* @returns Deterministic string form of `value`.
|
|
209
|
+
* @param {unknown} value - Value to serialize.
|
|
210
|
+
* @returns {string} - Deterministic string form of `value`.
|
|
160
211
|
*/
|
|
161
212
|
stableStringify(value) {
|
|
162
213
|
if (value === null || typeof value !== 'object') {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { ActiveRequest, RequestIdentity } from './request-manager/index.js';
|
|
1
|
+
export type { ActiveRequest, RequestIdentity, RequestManagerOptions } from './request-manager/index.js';
|
|
2
2
|
export { RequestManager } from './request-manager/index.js';
|
|
3
3
|
export { default } from './request-manager/index.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import requestManager, { RequestManager } from './request-manager.js';
|
|
2
|
-
export type { ActiveRequest, RequestIdentity } from './request-manager.js';
|
|
2
|
+
export type { ActiveRequest, RequestIdentity, RequestManagerOptions } from './request-manager.js';
|
|
3
3
|
export { RequestManager };
|
|
4
4
|
export default requestManager;
|
|
@@ -7,10 +7,28 @@ type MaybePromise<T> = T | Promise<T>;
|
|
|
7
7
|
export interface RequestIdentity {
|
|
8
8
|
clientId: number;
|
|
9
9
|
method: string;
|
|
10
|
+
/** URL string as passed to `call`, or resolved against the client's `baseURL` when `resolveBaseURL` is enabled. */
|
|
10
11
|
url: string;
|
|
11
12
|
params: unknown;
|
|
13
|
+
paramsSerialized: string;
|
|
14
|
+
/**
|
|
15
|
+
* Backward-compatible British spelling of `paramsSerialized`.
|
|
16
|
+
*
|
|
17
|
+
* @deprecated Use `paramsSerialized` instead; to be removed in a future major release.
|
|
18
|
+
*/
|
|
12
19
|
paramsSerialised: string;
|
|
13
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Configuration options for a {@link RequestManager} instance.
|
|
23
|
+
*/
|
|
24
|
+
export interface RequestManagerOptions {
|
|
25
|
+
/**
|
|
26
|
+
* When enabled, relative URLs are resolved against the client's `baseURL` before keying,
|
|
27
|
+
* so the same logical resource de-duplicates whether callers address it relatively or
|
|
28
|
+
* absolutely. Disabled by default to preserve historical exact-string keying.
|
|
29
|
+
*/
|
|
30
|
+
resolveBaseURL?: boolean;
|
|
31
|
+
}
|
|
14
32
|
/**
|
|
15
33
|
* Active in-flight request entry stored in {@link RequestManager.activeRequests}.
|
|
16
34
|
*
|
|
@@ -38,6 +56,13 @@ export declare class RequestManager<T = any> {
|
|
|
38
56
|
activeRequests: Map<RequestKey, ActiveRequest>;
|
|
39
57
|
private clientIds;
|
|
40
58
|
private nextClientId;
|
|
59
|
+
private readonly resolveBaseURL;
|
|
60
|
+
/**
|
|
61
|
+
* Creates an isolated manager.
|
|
62
|
+
*
|
|
63
|
+
* @param {RequestManagerOptions} [options] - Optional instance configuration.
|
|
64
|
+
*/
|
|
65
|
+
constructor(options?: RequestManagerOptions);
|
|
41
66
|
/**
|
|
42
67
|
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
43
68
|
*
|
|
@@ -48,47 +73,70 @@ export declare class RequestManager<T = any> {
|
|
|
48
73
|
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
49
74
|
*
|
|
50
75
|
* Requests are de-duplicated when they target the same client instance, method, URL, and `config.params`.
|
|
76
|
+
* When constructed with `resolveBaseURL`, relative URLs are resolved against the client's `baseURL` for keying.
|
|
51
77
|
* Request bodies and other config fields (headers, timeout, signal, etc.) are not part of the key.
|
|
52
78
|
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
53
79
|
*
|
|
54
|
-
* @param client Axios client used to execute the request.
|
|
55
|
-
* @param method HTTP method.
|
|
56
|
-
* @param url Target request URL.
|
|
57
|
-
* @param data Optional request payload.
|
|
58
|
-
* @param config Optional Axios request configuration.
|
|
59
|
-
* @param
|
|
60
|
-
*
|
|
80
|
+
* @param {AxiosInstance} client - Axios client used to execute the request.
|
|
81
|
+
* @param {Method} method - HTTP method.
|
|
82
|
+
* @param {string} url - Target request URL.
|
|
83
|
+
* @param {any} [data] - Optional request payload.
|
|
84
|
+
* @param {AxiosRequestConfig|null} [config] - Optional Axios request configuration.
|
|
85
|
+
* @param {((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null} [onSuccess] -
|
|
86
|
+
* Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
87
|
+
* @param {((error: unknown) => void) | null} [onError] - Optional callback for request failures only.
|
|
88
|
+
* If it throws, the original request error is rethrown.
|
|
89
|
+
* @returns {Promise<AxiosResponse<TResponse> | TSuccess | void>} - The shared promise for
|
|
90
|
+
* the in-flight request.
|
|
91
|
+
* @throws {TypeError} - If the client is not an Axios-like instance, the method or URL is
|
|
92
|
+
* invalid, or config.params is not serializable.
|
|
61
93
|
*/
|
|
62
94
|
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>;
|
|
63
95
|
/**
|
|
64
96
|
* Builds the stable human-readable identity for an in-flight request.
|
|
65
97
|
*
|
|
66
|
-
* @param client Axios client instance used for the request.
|
|
67
|
-
* @param method HTTP method.
|
|
68
|
-
* @param url Request URL string as passed to `call`.
|
|
69
|
-
* @param params Axios `config.params` value, if any.
|
|
70
|
-
* @returns Identity fields used for hashing and inspection.
|
|
98
|
+
* @param {AxiosInstance} client - Axios client instance used for the request.
|
|
99
|
+
* @param {Method} method - HTTP method.
|
|
100
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
101
|
+
* @param {AxiosRequestConfig['params']} params - Axios `config.params` value, if any.
|
|
102
|
+
* @returns {RequestIdentity} - Identity fields used for hashing and inspection.
|
|
71
103
|
*/
|
|
72
104
|
private buildRequestIdentity;
|
|
105
|
+
/**
|
|
106
|
+
* Resolves a relative URL against the client's `baseURL` for keying, mirroring Axios's
|
|
107
|
+
* own `buildFullPath` and `combineURLs` semantics.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
110
|
+
* @param {AxiosInstance} client - Axios client whose `defaults.baseURL` is the resolution base.
|
|
111
|
+
* @returns {string} - The URL string used as the key segment.
|
|
112
|
+
*/
|
|
113
|
+
private resolveUrlAgainstBase;
|
|
114
|
+
/**
|
|
115
|
+
* Determines whether a URL is absolute, matching Axios's `isAbsoluteURL` check.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} url - URL string to test.
|
|
118
|
+
* @returns {boolean} - True when `url` has a scheme or is protocol-relative.
|
|
119
|
+
*/
|
|
120
|
+
private isAbsoluteUrl;
|
|
73
121
|
/**
|
|
74
122
|
* Hashes a request identity into a fixed-length map key.
|
|
75
123
|
*
|
|
76
|
-
* @param identity Stable request identity.
|
|
77
|
-
* @returns Lowercase hex SHA-256 digest of the identity string.
|
|
124
|
+
* @param {RequestIdentity} identity - Stable request identity.
|
|
125
|
+
* @returns {string} - Lowercase hex SHA-256 digest of the identity string.
|
|
78
126
|
*/
|
|
79
127
|
private hashIdentity;
|
|
80
128
|
/**
|
|
81
|
-
*
|
|
129
|
+
* Serializes query params into a deterministic string for request keys.
|
|
82
130
|
*
|
|
83
|
-
* @param params Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
84
|
-
* @returns A stable
|
|
131
|
+
* @param {AxiosRequestConfig['params']} params - Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
132
|
+
* @returns {string} - A stable serialization, or an empty string when params are absent.
|
|
85
133
|
*/
|
|
86
134
|
private serializeParams;
|
|
87
135
|
/**
|
|
88
|
-
* JSON-like
|
|
136
|
+
* JSON-like serialization with sorted object keys so param order does not affect identity.
|
|
89
137
|
*
|
|
90
|
-
* @param value Value to
|
|
91
|
-
* @returns Deterministic string form of `value`.
|
|
138
|
+
* @param {unknown} value - Value to serialize.
|
|
139
|
+
* @returns {string} - Deterministic string form of `value`.
|
|
92
140
|
*/
|
|
93
141
|
private stableStringify;
|
|
94
142
|
}
|
|
@@ -14,6 +14,15 @@ export class RequestManager {
|
|
|
14
14
|
activeRequests = new Map();
|
|
15
15
|
clientIds = new WeakMap();
|
|
16
16
|
nextClientId = 1;
|
|
17
|
+
resolveBaseURL;
|
|
18
|
+
/**
|
|
19
|
+
* Creates an isolated manager.
|
|
20
|
+
*
|
|
21
|
+
* @param {RequestManagerOptions} [options] - Optional instance configuration.
|
|
22
|
+
*/
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.resolveBaseURL = options.resolveBaseURL === true;
|
|
25
|
+
}
|
|
17
26
|
/**
|
|
18
27
|
* Backward-compatible static API that delegates to the shared singleton instance.
|
|
19
28
|
*
|
|
@@ -26,18 +35,28 @@ export class RequestManager {
|
|
|
26
35
|
* Executes or retrieves an ongoing HTTP request based on the provided URL, method, payload, and configuration.
|
|
27
36
|
*
|
|
28
37
|
* Requests are de-duplicated when they target the same client instance, method, URL, and `config.params`.
|
|
38
|
+
* When constructed with `resolveBaseURL`, relative URLs are resolved against the client's `baseURL` for keying.
|
|
29
39
|
* Request bodies and other config fields (headers, timeout, signal, etc.) are not part of the key.
|
|
30
40
|
* If `onSuccess` returns a non-`undefined` value, that value becomes the resolved result.
|
|
31
41
|
*
|
|
32
|
-
* @param client Axios client used to execute the request.
|
|
33
|
-
* @param method HTTP method.
|
|
34
|
-
* @param url Target request URL.
|
|
35
|
-
* @param data Optional request payload.
|
|
36
|
-
* @param config Optional Axios request configuration.
|
|
37
|
-
* @param
|
|
38
|
-
*
|
|
42
|
+
* @param {AxiosInstance} client - Axios client used to execute the request.
|
|
43
|
+
* @param {Method} method - HTTP method.
|
|
44
|
+
* @param {string} url - Target request URL.
|
|
45
|
+
* @param {any} [data] - Optional request payload.
|
|
46
|
+
* @param {AxiosRequestConfig|null} [config] - Optional Axios request configuration.
|
|
47
|
+
* @param {((result: AxiosResponse<TResponse>) => MaybePromise<TSuccess | undefined>) | null} [onSuccess] -
|
|
48
|
+
* Optional callback invoked on success. Returning a value overrides the resolved response.
|
|
49
|
+
* @param {((error: unknown) => void) | null} [onError] - Optional callback for request failures only.
|
|
50
|
+
* If it throws, the original request error is rethrown.
|
|
51
|
+
* @returns {Promise<AxiosResponse<TResponse> | TSuccess | void>} - The shared promise for
|
|
52
|
+
* the in-flight request.
|
|
53
|
+
* @throws {TypeError} - If the client is not an Axios-like instance, the method or URL is
|
|
54
|
+
* invalid, or config.params is not serializable.
|
|
39
55
|
*/
|
|
40
56
|
call(client, method, url, data = {}, config = {}, onSuccess, onError) {
|
|
57
|
+
if (client == null || typeof client.request !== 'function') {
|
|
58
|
+
return Promise.reject(new TypeError('RequestManager.call requires an Axios client instance'));
|
|
59
|
+
}
|
|
41
60
|
if (typeof method !== 'string' || method.length === 0) {
|
|
42
61
|
return Promise.reject(new TypeError('RequestManager.call requires a non-empty HTTP method string'));
|
|
43
62
|
}
|
|
@@ -46,7 +65,13 @@ export class RequestManager {
|
|
|
46
65
|
}
|
|
47
66
|
data ??= {};
|
|
48
67
|
config ??= {};
|
|
49
|
-
|
|
68
|
+
let identity;
|
|
69
|
+
try {
|
|
70
|
+
identity = this.buildRequestIdentity(client, method, url, config.params);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return Promise.reject(new TypeError('RequestManager.call requires serializable config.params'));
|
|
74
|
+
}
|
|
50
75
|
const hash = this.hashIdentity(identity);
|
|
51
76
|
const existing = this.activeRequests.get(hash);
|
|
52
77
|
if (existing) {
|
|
@@ -92,11 +117,11 @@ export class RequestManager {
|
|
|
92
117
|
/**
|
|
93
118
|
* Builds the stable human-readable identity for an in-flight request.
|
|
94
119
|
*
|
|
95
|
-
* @param client Axios client instance used for the request.
|
|
96
|
-
* @param method HTTP method.
|
|
97
|
-
* @param url Request URL string as passed to `call`.
|
|
98
|
-
* @param params Axios `config.params` value, if any.
|
|
99
|
-
* @returns Identity fields used for hashing and inspection.
|
|
120
|
+
* @param {AxiosInstance} client - Axios client instance used for the request.
|
|
121
|
+
* @param {Method} method - HTTP method.
|
|
122
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
123
|
+
* @param {AxiosRequestConfig['params']} params - Axios `config.params` value, if any.
|
|
124
|
+
* @returns {RequestIdentity} - Identity fields used for hashing and inspection.
|
|
100
125
|
*/
|
|
101
126
|
buildRequestIdentity(client, method, url, params) {
|
|
102
127
|
let clientId = this.clientIds.get(client);
|
|
@@ -104,31 +129,57 @@ export class RequestManager {
|
|
|
104
129
|
clientId = this.nextClientId++;
|
|
105
130
|
this.clientIds.set(client, clientId);
|
|
106
131
|
}
|
|
107
|
-
const
|
|
108
|
-
const
|
|
132
|
+
const normalizedMethod = method.toLowerCase();
|
|
133
|
+
const urlKey = this.resolveBaseURL ? this.resolveUrlAgainstBase(url, client) : url;
|
|
134
|
+
const paramsSerialized = this.serializeParams(params);
|
|
109
135
|
return {
|
|
110
136
|
clientId,
|
|
111
|
-
method:
|
|
112
|
-
url,
|
|
137
|
+
method: normalizedMethod,
|
|
138
|
+
url: urlKey,
|
|
113
139
|
params: params ?? null,
|
|
114
|
-
|
|
140
|
+
paramsSerialized,
|
|
141
|
+
paramsSerialised: paramsSerialized
|
|
115
142
|
};
|
|
116
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Resolves a relative URL against the client's `baseURL` for keying, mirroring Axios's
|
|
146
|
+
* own `buildFullPath` and `combineURLs` semantics.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} url - Request URL string as passed to `call`.
|
|
149
|
+
* @param {AxiosInstance} client - Axios client whose `defaults.baseURL` is the resolution base.
|
|
150
|
+
* @returns {string} - The URL string used as the key segment.
|
|
151
|
+
*/
|
|
152
|
+
resolveUrlAgainstBase(url, client) {
|
|
153
|
+
const baseURL = client.defaults?.baseURL;
|
|
154
|
+
if (typeof baseURL !== 'string' || baseURL.length === 0 || this.isAbsoluteUrl(url)) {
|
|
155
|
+
return url;
|
|
156
|
+
}
|
|
157
|
+
return baseURL.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, '');
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Determines whether a URL is absolute, matching Axios's `isAbsoluteURL` check.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} url - URL string to test.
|
|
163
|
+
* @returns {boolean} - True when `url` has a scheme or is protocol-relative.
|
|
164
|
+
*/
|
|
165
|
+
isAbsoluteUrl(url) {
|
|
166
|
+
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
|
|
167
|
+
}
|
|
117
168
|
/**
|
|
118
169
|
* Hashes a request identity into a fixed-length map key.
|
|
119
170
|
*
|
|
120
|
-
* @param identity Stable request identity.
|
|
121
|
-
* @returns Lowercase hex SHA-256 digest of the identity string.
|
|
171
|
+
* @param {RequestIdentity} identity - Stable request identity.
|
|
172
|
+
* @returns {string} - Lowercase hex SHA-256 digest of the identity string.
|
|
122
173
|
*/
|
|
123
174
|
hashIdentity(identity) {
|
|
124
|
-
const material = `${identity.clientId}:${identity.method}:${identity.url}:${identity.
|
|
175
|
+
const material = `${identity.clientId}:${identity.method}:${identity.url}:${identity.paramsSerialized}`;
|
|
125
176
|
return sha256Hex(material);
|
|
126
177
|
}
|
|
127
178
|
/**
|
|
128
|
-
*
|
|
179
|
+
* Serializes query params into a deterministic string for request keys.
|
|
129
180
|
*
|
|
130
|
-
* @param params Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
131
|
-
* @returns A stable
|
|
181
|
+
* @param {AxiosRequestConfig['params']} params - Axios params value (`undefined`, plain object, array, or `URLSearchParams`).
|
|
182
|
+
* @returns {string} - A stable serialization, or an empty string when params are absent.
|
|
132
183
|
*/
|
|
133
184
|
serializeParams(params) {
|
|
134
185
|
if (params == null) {
|
|
@@ -150,10 +201,10 @@ export class RequestManager {
|
|
|
150
201
|
return this.stableStringify(params);
|
|
151
202
|
}
|
|
152
203
|
/**
|
|
153
|
-
* JSON-like
|
|
204
|
+
* JSON-like serialization with sorted object keys so param order does not affect identity.
|
|
154
205
|
*
|
|
155
|
-
* @param value Value to
|
|
156
|
-
* @returns Deterministic string form of `value`.
|
|
206
|
+
* @param {unknown} value - Value to serialize.
|
|
207
|
+
* @returns {string} - Deterministic string form of `value`.
|
|
157
208
|
*/
|
|
158
209
|
stableStringify(value) {
|
|
159
210
|
if (value === null || typeof value !== 'object') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaseeey/request-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "De-duplicate concurrent Axios HTTP requests so independent callers share one in-flight promise and response",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"axios",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"test": "vitest run --coverage"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"axios": "
|
|
56
|
+
"axios": "^1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@vitest/coverage-v8": "^
|
|
59
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
60
60
|
"axios": "^1.20.0",
|
|
61
|
-
"typescript": "^
|
|
62
|
-
"vitest": "^
|
|
61
|
+
"typescript": "^7.0.2",
|
|
62
|
+
"vitest": "^5.0.3"
|
|
63
63
|
}
|
|
64
64
|
}
|