@enegalan/request-manager 1.0.3 → 1.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 CHANGED
@@ -3,15 +3,15 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@enegalan/request-manager.svg)](https://www.npmjs.com/package/@enegalan/request-manager)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- RequestManager is a JavaScript library designed to manage and regulate HTTP requests efficiently. It allows you to use HTTP calls from any library (ajax, Ext.Ajax, axios, fetch, etc.) by accepting Promises as parameters.
6
+ RequestManager is a JavaScript library designed to manage and regulate HTTP requests efficiently. It cancels duplicate in-flight calls and works with fetch, axios, jQuery.ajax, Ext.Ajax, raw XHR, and custom clients.
7
7
 
8
8
  ## Key Features
9
9
 
10
- - **Universal Compatibility**: Works with any HTTP library that returns a Promise (fetch, axios, Ext.Ajax, etc.)
11
- - **Automatic Cancellation**: When a request is repeated with the same `requestKey`, the previous request is automatically cancelled. This identifier is generated with `url` parameter or can be manually specified in `options`.
12
- - **Prioritizes Recent Requests**: The library ensures that only the most recent request is processed, cancelling older ones
13
- - **Simple API**: Easy to use and integrate into existing projects
14
- - **Adapt to your requirements**: The library supports `options` for custom request management
10
+ - **Universal Compatibility**: Dedicated helpers for fetch, axios, ajax-style clients (jQuery / Ext.Ajax), and XMLHttpRequest — plus a low-level `request()` escape hatch
11
+ - **Automatic Cancellation**: When a request is repeated with the same identifier, the previous request is automatically cancelled. The ID comes from the cleaned URL, or from `options.requestKey`
12
+ - **Prioritizes Recent Requests**: Only the most recent request for a given ID is kept; older ones are aborted
13
+ - **Simple API**: Prefer the helper that matches your HTTP client; wire cancel yourself only with `request()`
14
+ - **Adapt to your requirements**: Shared options (`requestKey`, `noCancel`, `includeQuery`, ...) across helpers
15
15
  - **TypeScript Support**: Full TypeScript type definitions included
16
16
  - **Multiple Module Formats**: ESM, CommonJS, and UMD builds available
17
17
 
@@ -24,16 +24,19 @@ npm install @enegalan/request-manager
24
24
  ### Usage in Different Environments
25
25
 
26
26
  **ES Modules (recommended):**
27
+
27
28
  ```javascript
28
29
  import RequestManager from '@enegalan/request-manager';
29
30
  ```
30
31
 
31
32
  **CommonJS:**
33
+
32
34
  ```javascript
33
35
  const { RequestManager } = require('@enegalan/request-manager');
34
36
  ```
35
37
 
36
38
  **Browser (CDN):**
39
+
37
40
  ```html
38
41
  <!-- Using unpkg -->
39
42
  <script src="https://unpkg.com/@enegalan/request-manager/dist/request-manager.min.js"></script>
@@ -42,7 +45,7 @@ const { RequestManager } = require('@enegalan/request-manager');
42
45
  <script src="https://cdn.jsdelivr.net/npm/@enegalan/request-manager/dist/request-manager.min.js"></script>
43
46
 
44
47
  <script>
45
- const requestManager = new RequestManager();
48
+ const requestManager = new RequestManager();
46
49
  </script>
47
50
  ```
48
51
 
@@ -61,66 +64,98 @@ const xhrResult: XhrResponse<{ name: string }> = await requestManager.xhr('/api/
61
64
 
62
65
  ## Usage
63
66
 
64
- ### Basic Example with fetch()
67
+ ### Which method should I use?
65
68
 
66
- ```javascript
67
- import RequestManager from '@enegalan/request-manager';
69
+ Pick the **dedicated helper** for your HTTP client. Use `request()` only when none of the helpers fit.
68
70
 
69
- const requestManager = new RequestManager();
71
+ | Client | Use this | Why |
72
+ | --------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
73
+ | `fetch` | **`fetch(url, options)`** | Creates the AbortSignal and passes it to `fetch` for you |
74
+ | `axios` | **`axios(url, options, axiosInstance?)`** | Creates an `AbortSignal` and wires cancel for you (axios ≥ 0.22) |
75
+ | jQuery `.ajax`, Ext.Ajax, similar | **`ajax(ajaxFunction, url, options)`** | Runs your ajax function, then wires abort for you (`req.abort`, `Ext.Ajax.abort(req)`, or `xhr.abort`) |
76
+ | Raw `XMLHttpRequest` | **`xhr(url, options)`** | Owns open/send and abort lifecycle |
77
+ | Custom / already-started Promise | **`request(url, promiseOrFn, options)`** | Escape hatch — **you** must pass `signal` / `cancelToken` / `addAbortListener` |
78
+
79
+ **Rule of thumb**
80
+
81
+ 1. Known client → use its helper (`fetch` / `axios` / `ajax` / `xhr`).
82
+ 2. Helper already cancels the real network call — no manual abort wiring.
83
+ 3. `request()` is for edge cases (wrapping an existing Promise, exotic clients). Same cancellation _map_ as the helpers, but abort plumbing is your job.
70
84
 
71
- requestManager.fetch('/api/users')
72
- .then(response => response.json())
73
- .then(data => console.log(data))
74
- .catch(error => {
75
- if (error.message === 'Request was cancelled') {
76
- console.log('Request was cancelled');
77
- } else {
78
- console.error('Request failed:', error);
79
- }
80
- });
85
+ ```javascript
86
+ // Preferred
87
+ requestManager.fetch('/api/users');
88
+ requestManager.axios('/api/users');
89
+ requestManager.ajax(({ url, ...opts }) => $.ajax({ url, ...opts }), '/api/users');
90
+ requestManager.ajax(({ url, ...opts }) => Ext.Ajax.request({ url, ...opts }), '/api/users');
91
+ requestManager.xhr('/api/users');
92
+
93
+ // Escape hatch — you wire cancel yourself (see sandbox / API notes below)
94
+ requestManager.request('/api/users', ({ options }) => fetch('/api/users', { signal: options.signal, ...options }));
81
95
  ```
82
96
 
83
- ### POST Request with Options
97
+ > [!IMPORTANT]
98
+ > Calling `request(url, Ext.Ajax.request(...))` or `request(url, $.ajax(...))` **without** linking abort (via `addAbortListener` / `cancelToken` / `signal`) does **not** abort the browser request when a duplicate starts. Use **`ajax()`** for those clients.
99
+
100
+ ### Basic Example with fetch()
84
101
 
85
102
  ```javascript
86
103
  import RequestManager from '@enegalan/request-manager';
87
104
 
88
- const requestManager = new RequestManager();
105
+ const requestManager = new RequestManager({ verbose: true });
89
106
 
90
- requestManager.fetch('/api/users', {
91
- method: 'POST',
92
- headers: { 'Content-Type': 'application/json' },
93
- body: JSON.stringify({ name: 'John' })
94
- })
95
- .then(response => response.json())
96
- .then(data => console.log(data));
107
+ requestManager
108
+ .fetch('/api/users')
109
+ .then((response) => response.json())
110
+ .then((data) => console.log(data))
111
+ .catch((error) => {
112
+ if (error.message.includes('was cancelled')) {
113
+ console.log('Request was cancelled');
114
+ } else {
115
+ console.error('Request failed:', error);
116
+ }
117
+ });
97
118
  ```
98
119
 
99
- ### Using request() with Promise
120
+ ### POST Request with Options
100
121
 
101
122
  ```javascript
102
123
  import RequestManager from '@enegalan/request-manager';
103
124
 
104
125
  const requestManager = new RequestManager();
105
126
 
106
- requestManager.request('/api/users', fetch('/api/users'))
107
- .then(response => response.json())
108
- .then(data => console.log(data));
127
+ requestManager
128
+ .fetch('/api/users', {
129
+ method: 'POST',
130
+ headers: { 'Content-Type': 'application/json' },
131
+ body: JSON.stringify({ name: 'John' }),
132
+ })
133
+ .then((response) => response.json())
134
+ .then((data) => console.log(data));
109
135
  ```
110
136
 
111
- ### Using request() with Function
137
+ ### Using request()
138
+
139
+ `request()` is the low-level API when you already have a Promise or need custom wiring.
112
140
 
113
141
  ```javascript
114
142
  import RequestManager from '@enegalan/request-manager';
115
143
 
116
144
  const requestManager = new RequestManager();
117
145
 
118
- // Custom logic
119
- requestManager.request('/api/users', ({ options }) => {
120
- return fetch('/api/users', options);
121
- })
122
- .then(response => response.json())
123
- .then(data => console.log(data));
146
+ // Function form — options include signal; pass it into fetch (or prefer requestManager.fetch)
147
+ requestManager
148
+ .request('/api/users', ({ options }) => {
149
+ return fetch('/api/users', { signal: options.signal, ...options });
150
+ })
151
+ .then((response) => response.json())
152
+ .then((data) => console.log(data));
153
+
154
+ // Pre-created Promise — must also pass abortController / cancelToken or cancel is incomplete
155
+ const abortController = requestManager.getAbortController();
156
+ requestManager.request('/api/users', fetch('/api/users', { signal: abortController.signal }), {
157
+ abortController,
158
+ });
124
159
  ```
125
160
 
126
161
  ### Automatic Cancellation with Same URL
@@ -132,17 +167,17 @@ const requestManager = new RequestManager();
132
167
 
133
168
  // By default, requests with the same URL (cleaned) will cancel previous ones
134
169
  // The URL is automatically cleaned (protocol and query params removed) to generate the request ID
135
- requestManager.fetch('/api/search?q=test')
136
- .catch(error => {
170
+ requestManager.fetch('/api/search?q=test').catch((error) => {
137
171
  console.log('First request cancelled:', error.message);
138
- });
172
+ });
139
173
 
140
174
  // This second request will automatically cancel the first one
141
175
  // because they share the same cleaned URL
142
176
  setTimeout(() => {
143
- requestManager.fetch('/api/search?q=updated')
144
- .then(response => response.json())
145
- .then(data => console.log('Second request completed:', data));
177
+ requestManager
178
+ .fetch('/api/search?q=updated')
179
+ .then((response) => response.json())
180
+ .then((data) => console.log('Second request completed:', data));
146
181
  }, 100);
147
182
  ```
148
183
 
@@ -154,20 +189,22 @@ import RequestManager from '@enegalan/request-manager';
154
189
  const requestManager = new RequestManager();
155
190
 
156
191
  // You can use requestKey to override the default URL-based ID generation
157
- requestManager.fetch('/api/search?q=test', {
158
- requestKey: 'search-users' // Custom key instead of cleaned URL
159
- })
160
- .catch(error => {
161
- console.log('First request cancelled:', error.message);
162
- });
192
+ requestManager
193
+ .fetch('/api/search?q=test', {
194
+ requestKey: 'search-users', // Custom key instead of cleaned URL
195
+ })
196
+ .catch((error) => {
197
+ console.log('First request cancelled:', error.message);
198
+ });
163
199
 
164
200
  // This second request will cancel the first one because they share the same requestKey
165
201
  setTimeout(() => {
166
- requestManager.fetch('/api/search?q=updated', {
167
- requestKey: 'search-users' // Same key = same request ID = cancellation
168
- })
169
- .then(response => response.json())
170
- .then(data => console.log('Second request completed:', data));
202
+ requestManager
203
+ .fetch('/api/search?q=updated', {
204
+ requestKey: 'search-users', // Same key = same request ID = cancellation
205
+ })
206
+ .then((response) => response.json())
207
+ .then((data) => console.log('Second request completed:', data));
171
208
  }, 100);
172
209
  ```
173
210
 
@@ -180,9 +217,9 @@ const requestManager = new RequestManager();
180
217
 
181
218
  // You can use a function to generate the requestKey dynamically
182
219
  function searchUsers(query) {
183
- return requestManager.fetch(`/api/search?q=${query}`, {
184
- requestKey: () => `search-${query}` // Function that returns the key
185
- });
220
+ return requestManager.fetch(`/api/search?q=${query}`, {
221
+ requestKey: () => `search-${query}`, // Function that returns the key
222
+ });
186
223
  }
187
224
 
188
225
  // Both calls will share the same requestKey and cancel each other
@@ -199,49 +236,46 @@ const requestManager = new RequestManager();
199
236
 
200
237
  // Use noCancel: true to allow multiple requests to execute concurrently
201
238
  // This is useful for lazy loading scenarios where you want all requests to complete
202
- requestManager.fetch('/api/lazy?load=1', { noCancel: true })
203
- .then(response => response.json())
204
- .then(data => console.log('Load 1:', data));
239
+ requestManager
240
+ .fetch('/api/lazy?load=1', { noCancel: true })
241
+ .then((response) => response.json())
242
+ .then((data) => console.log('Load 1:', data));
205
243
 
206
- requestManager.fetch('/api/lazy?load=2', { noCancel: true })
207
- .then(response => response.json())
208
- .then(data => console.log('Load 2:', data));
244
+ requestManager
245
+ .fetch('/api/lazy?load=2', { noCancel: true })
246
+ .then((response) => response.json())
247
+ .then((data) => console.log('Load 2:', data));
209
248
 
210
- requestManager.fetch('/api/lazy?load=3', { noCancel: true })
211
- .then(response => response.json())
212
- .then(data => console.log('Load 3:', data));
249
+ requestManager
250
+ .fetch('/api/lazy?load=3', { noCancel: true })
251
+ .then((response) => response.json())
252
+ .then((data) => console.log('Load 3:', data));
213
253
 
214
254
  // All three requests will execute concurrently without canceling each other
215
255
  // Even though they share the same cleaned URL (without query params)
216
256
  ```
217
257
 
218
- ### Using with Axios
258
+ ### Using includeQuery to Distinguish Query Strings
219
259
 
220
260
  ```javascript
221
- import axios from 'axios';
222
261
  import RequestManager from '@enegalan/request-manager';
223
262
 
224
263
  const requestManager = new RequestManager();
225
264
 
226
- const CancelToken = axios.CancelToken;
227
- const source = CancelToken.source();
228
-
229
- requestManager.request('/api/users', axios.get('/api/users', {
230
- cancelToken: source.token
231
- }), {
232
- cancelToken: () => source.cancel()
233
- })
234
- .then(response => console.log(response.data))
235
- .catch(error => {
236
- if (axios.isCancel(error)) {
237
- console.log('Request was cancelled');
238
- } else {
239
- console.error('Request failed:', error);
240
- }
241
- });
265
+ // By default, query params are stripped from the ID:
266
+ // /api/users?page=1 and /api/users?page=2 share the same ID and cancel each other.
267
+
268
+ // With includeQuery: true, the query string is part of the ID
269
+ requestManager.fetch('/api/users?page=1', { includeQuery: true });
270
+ requestManager.fetch('/api/users?page=2', { includeQuery: true });
271
+ // Both run — different query = different ID
272
+
273
+ // Same full URL still cancels the previous one
274
+ requestManager.fetch('/api/users?page=1', { includeQuery: true });
275
+ requestManager.fetch('/api/users?page=1', { includeQuery: true }); // cancels the previous page=1
242
276
  ```
243
277
 
244
- ### Using with Axios and Function
278
+ ### Using with Axios
245
279
 
246
280
  ```javascript
247
281
  import axios from 'axios';
@@ -249,64 +283,73 @@ import RequestManager from '@enegalan/request-manager';
249
283
 
250
284
  const requestManager = new RequestManager();
251
285
 
252
- requestManager.request('/api/users', ({ options }) => {
253
- const CancelToken = axios.CancelToken;
254
- const source = CancelToken.source();
255
- return axios.get('/api/users', { cancelToken: source.token });
256
- })
257
- .then(response => console.log(response.data))
258
- .catch(error => {
259
- if (axios.isCancel(error)) {
260
- console.log('Request was cancelled');
261
- } else {
262
- console.error('Request failed:', error);
263
- }
264
- });
286
+ requestManager
287
+ .axios('/api/users')
288
+ .then((response) => console.log(response.data))
289
+ .catch((error) => {
290
+ if (axios.isCancel(error)) {
291
+ console.log('Request was cancelled');
292
+ } else {
293
+ console.error('Request failed:', error);
294
+ }
295
+ });
265
296
  ```
266
297
 
267
- ### Using with Other Libraries
298
+ ### Using with jQuery / Ext.Ajax (`ajax()`)
299
+
300
+ `ajax()` invokes your function, inspects the returned request object, and registers abort automatically (`req.abort`, `Ext.Ajax.abort(req)`, or `xhr.abort`).
268
301
 
269
302
  ```javascript
270
303
  import RequestManager from '@enegalan/request-manager';
271
304
 
272
305
  const requestManager = new RequestManager();
273
306
 
274
- requestManager.request('/api/data', ({ options }) => {
275
- return new Promise((resolve, reject) => {
276
- const xhr = new XMLHttpRequest();
277
- xhr.open('GET', '/api/data');
278
- xhr.onload = () => resolve(xhr.responseText);
279
- xhr.onerror = () => reject(new Error('Request failed'));
280
- xhr.send();
281
-
282
- // Use signal to cancel if needed
283
- options.signal.addEventListener('abort', () => {
284
- xhr.abort();
285
- reject(new Error('Request was cancelled'));
286
- });
287
- });
288
- })
289
- .then(data => console.log(data))
290
- .catch(error => console.error(error));
307
+ // jQuery
308
+ requestManager.ajax($.ajax.bind($), '/api/users', { method: 'GET' });
309
+
310
+ // Ext.Ajax — return the Ext request object (not a Promise)
311
+ requestManager.ajax(({ url, ...options }) => Ext.Ajax.request({ url, ...options }), '/api/users');
312
+
313
+ // Or bind Ext.Ajax.request directly when options shape matches
314
+ requestManager.ajax(Ext.Ajax.request.bind(Ext.Ajax), '/api/users');
315
+ ```
316
+
317
+ Equivalent with `request()` (more boilerplate — not recommended):
318
+
319
+ ```javascript
320
+ requestManager.request('/api/users', ({ options }) => {
321
+ const req = Ext.Ajax.request({ url: '/api/users', ...options });
322
+ requestManager.addAbortListener(() => Ext.Ajax.abort(req), options.signal);
323
+ return req;
324
+ });
291
325
  ```
292
326
 
293
- ### Using with Pre-created Promises
327
+ ### Using with Other Libraries
328
+
329
+ If there is no dedicated helper, use `request()` and **must** abort on `options.signal` (or pass `cancelToken` / `addAbortListener`).
294
330
 
295
331
  ```javascript
296
332
  import RequestManager from '@enegalan/request-manager';
297
333
 
298
334
  const requestManager = new RequestManager();
299
335
 
300
- const existingPromise = fetch('/api/data');
301
-
302
- requestManager.request('/api/data', existingPromise, {
303
- // You can still provide cancelToken if your library supports it
304
- cancelToken: () => {
305
- // Custom cancellation logic
306
- }
307
- })
308
- .then(response => response.json())
309
- .then(data => console.log(data));
336
+ requestManager
337
+ .request('/api/data', ({ options }) => {
338
+ return new Promise((resolve, reject) => {
339
+ const xhr = new XMLHttpRequest();
340
+ xhr.open('GET', '/api/data');
341
+ xhr.onload = () => resolve(xhr.responseText);
342
+ xhr.onerror = () => reject(new Error('Request failed'));
343
+ xhr.send();
344
+
345
+ options.signal.addEventListener('abort', () => {
346
+ xhr.abort();
347
+ reject(new Error('Request was cancelled'));
348
+ });
349
+ });
350
+ })
351
+ .then((data) => console.log(data))
352
+ .catch((error) => console.error(error));
310
353
  ```
311
354
 
312
355
  ## API Reference
@@ -316,10 +359,12 @@ requestManager.request('/api/data', existingPromise, {
316
359
  Creates a new RequestManager instance.
317
360
 
318
361
  **Parameters:**
362
+
319
363
  - `options` (Object, optional): Configuration options
320
- - `verbose` (boolean, optional): If true, cancellation errors will include messages globally for all requests.
364
+ - `verbose` (boolean, optional): If true, cancellation rejects with a message that includes the request id. If false (default), cancellation is silent (wrapper promise does not settle; nothing is logged).
321
365
 
322
366
  **Example:**
367
+
323
368
  ```javascript
324
369
  // Create with verbose mode enabled
325
370
  const requestManager = new RequestManager({ verbose: true });
@@ -327,36 +372,40 @@ const requestManager = new RequestManager({ verbose: true });
327
372
 
328
373
  ### `request(url, requestPromise, options)`
329
374
 
330
- Executes an HTTP request, cancelling any previous request with the same identifier.
375
+ Low-level entry point. Tracks the call by ID and cancels the previous one with the same ID — but **you** must connect abort to the underlying client (`signal`, `cancelToken`, or `addAbortListener`). Otherwise the manager drops the tracked entry while the HTTP request may keep running.
331
376
 
332
377
  **Parameters:**
378
+
333
379
  - `url` (string): The URL of the request (used to generate request ID from cleaned URL)
334
- - `requestPromise` (Promise|Function|string): The Promise returned by any HTTP library (fetch, axios, etc.), a Function that can receive `{ options }` and returns a Promise, or a URL string (which will be used with fetch internally)
380
+ - `requestPromise` (Promise|Function|string): A Promise from any HTTP library, a Function that receives `{ options }` and returns a Promise/request object, or a URL string (fetch internally)
335
381
  - `options` (Object, optional): Configuration options
336
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
337
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
338
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will share the same ID and cancel previous ones. If not provided, the cleaned URL is used as the key. Can be a string, number, or function that returns a key.
339
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests. Useful for lazy loading scenarios where multiple requests should execute in parallel.
382
+ - `abortController` (AbortController): AbortController instance (created automatically if not provided)
383
+ - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
384
+ - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will share the same ID and cancel previous ones. If not provided, the cleaned URL is used as the key. Can be a string, number, or function that returns a key.
385
+ - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests. Useful for lazy loading scenarios where multiple requests should execute in parallel.
386
+ - `includeQuery` (boolean): If true, keeps the query string when generating the request ID from the URL.
340
387
 
341
388
  > [!TIP]
342
389
  > When `requestPromise` is a Function, you can pass custom properties in `options`. These will be accessible inside the callback via the `{ options }` parameter.
343
390
 
344
391
  **Returns:** Promise that resolves/rejects based on the most recent request
345
392
 
346
- **Note:** The request ID is automatically generated from the cleaned URL (protocol and query params removed) unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request to prevent cancellation. When `requestPromise` is a Function, it receives `{ options }` where `options` contains the `signal` (AbortSignal) and any other fetch options.
393
+ **Note:** The request ID is automatically generated from the cleaned URL (protocol and hash removed; query params removed unless `includeQuery` is true) unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request to prevent cancellation. When `requestPromise` is a Function, it receives `{ options }` where `options` contains the `signal` (AbortSignal) and any other fetch options.
347
394
 
348
395
  ### `fetch(url, options)`
349
396
 
350
397
  Executes an HTTP request using fetch, cancelling any previous request with the same identifier.
351
398
 
352
399
  **Parameters:**
400
+
353
401
  - `url` (string): The URL to fetch
354
402
  - `options` (Object, optional): Configuration options (same as `request()` method)
355
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If not provided, the cleaned URL is used as the key.
356
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
357
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
358
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
359
- - Any other properties are passed as fetch options (method, headers, body, etc.)
403
+ - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If not provided, the cleaned URL is used as the key.
404
+ - `abortController` (AbortController): AbortController instance (created automatically if not provided)
405
+ - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
406
+ - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
407
+ - `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
408
+ - Any other properties are passed as fetch options (method, headers, body, etc.)
360
409
 
361
410
  **Returns:** Promise that resolves/rejects based on the most recent request
362
411
 
@@ -367,18 +416,21 @@ Executes an HTTP request using fetch, cancelling any previous request with the s
367
416
  Executes an HTTP request using axios, cancelling any previous request with the same identifier.
368
417
 
369
418
  **Parameters:**
419
+
370
420
  - `url` (string): The URL to request
371
421
  - `options` (Object, optional): Configuration options
372
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
373
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
374
- - Any other properties are passed as axios options (method, headers, params, data, etc.)
422
+ - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
423
+ - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
424
+ - `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
425
+ - Any other properties are passed as axios options (method, headers, params, data, etc.)
375
426
  - `axiosInstance` (Object, optional): Custom axios instance to use. If not provided, uses the global `axios` object.
376
427
 
377
428
  **Returns:** Promise that resolves/rejects based on the most recent request
378
429
 
379
- **Note:** This method automatically creates a CancelToken for axios cancellation. The request ID is automatically generated from the cleaned URL unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request.
430
+ **Note:** This method automatically creates an AbortController and passes its `signal` in the axios config, so cancellation requires axios ≥ 0.22. The request ID is automatically generated from the cleaned URL unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request.
380
431
 
381
432
  **Example:**
433
+
382
434
  ```javascript
383
435
  import axios from 'axios';
384
436
  import RequestManager from '@enegalan/request-manager';
@@ -386,71 +438,69 @@ import RequestManager from '@enegalan/request-manager';
386
438
  const requestManager = new RequestManager();
387
439
 
388
440
  // Simple GET request (uses global axios)
389
- requestManager.axios('/api/users')
390
- .then(response => console.log(response.data))
391
- .catch(error => console.error(error));
441
+ requestManager
442
+ .axios('/api/users')
443
+ .then((response) => console.log(response.data))
444
+ .catch((error) => console.error(error));
392
445
 
393
446
  // With custom axios instance
394
447
  const apiClient = axios.create({
395
- baseURL: 'https://api.example.com',
396
- timeout: 5000
448
+ baseURL: 'https://api.example.com',
449
+ timeout: 5000,
397
450
  });
398
451
 
399
- requestManager.axios('/users', {}, apiClient)
400
- .then(response => console.log(response.data));
452
+ requestManager.axios('/users', {}, apiClient).then((response) => console.log(response.data));
401
453
 
402
454
  // POST request with options
403
- requestManager.axios('/api/users', {
404
- method: 'POST',
405
- data: { name: 'John' },
406
- headers: { 'Content-Type': 'application/json' }
407
- })
408
- .then(response => console.log(response.data));
455
+ requestManager
456
+ .axios('/api/users', {
457
+ method: 'POST',
458
+ data: { name: 'John' },
459
+ headers: { 'Content-Type': 'application/json' },
460
+ })
461
+ .then((response) => console.log(response.data));
409
462
  ```
410
463
 
411
464
  ### `ajax(ajaxFunction, url, options)`
412
465
 
413
- Executes an HTTP request using a custom ajax method function, cancelling any previous request with the same identifier.
466
+ Helper for **ajax-style clients** (jQuery.ajax, Ext.Ajax, etc.) that return a request object rather than (or in addition to) a Promise.
467
+
468
+ Calls `ajaxFunction({ url, ...options })`, then auto-wires cancel by inspecting the returned object:
469
+
470
+ 1. `req.abort` if present (jQuery)
471
+ 2. else `Ext.Ajax.abort(req)` when Ext is available and `req.xhr` exists
472
+ 3. else `req.xhr.abort` / raw `XMLHttpRequest.abort`
414
473
 
415
474
  **Parameters:**
416
- - `ajaxFunction` (Function): A function that receives `{ url, ...options }` and returns a Promise. The function should accept an object with `url` and other options including the `signal` (AbortSignal).
475
+
476
+ - `ajaxFunction` (Function): Receives `{ url, ...options }` and returns the library request object (or a Promise).
417
477
  - `url` (string): The URL to request
418
478
  - `options` (Object, optional): Configuration options
419
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
420
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
421
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
422
- - `verbose` (boolean): If true, cancellation errors will include messages
423
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
424
- - Any other properties are passed to the ajax method function
479
+ - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
480
+ - `abortController` (AbortController): AbortController instance (created automatically if not provided)
481
+ - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
482
+ - `verbose` (boolean): If true, cancellation rejects with a message that includes the request id
483
+ - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
484
+ - `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
485
+ - Any other properties are passed to the ajax method function
425
486
 
426
487
  **Returns:** Promise that resolves/rejects based on the most recent request
427
488
 
428
489
  **Example:**
490
+
429
491
  ```javascript
430
492
  import RequestManager from '@enegalan/request-manager';
431
493
 
432
494
  const requestManager = new RequestManager();
433
495
 
434
- // Using with jQuery.ajax
435
- requestManager.ajax(
436
- ({ url, ...options }) => {
437
- return new Promise((resolve, reject) => {
438
- $.ajax({
439
- url: url,
440
- ...options,
441
- success: resolve,
442
- error: reject
443
- });
444
- });
445
- },
446
- '/api/users',
447
- {
448
- method: 'GET',
449
- headers: { 'Content-Type': 'application/json' }
450
- }
451
- )
452
- .then(data => console.log(data))
453
- .catch(error => console.error(error));
496
+ // jQuery
497
+ requestManager
498
+ .ajax($.ajax.bind($), '/api/users', { method: 'GET' })
499
+ .then((data) => console.log(data))
500
+ .catch((error) => console.error(error));
501
+
502
+ // Ext.Ajax
503
+ requestManager.ajax(({ url, ...options }) => Ext.Ajax.request({ url, ...options }), '/api/users');
454
504
  ```
455
505
 
456
506
  ### `xhr(url, options)`
@@ -458,45 +508,80 @@ requestManager.ajax(
458
508
  Executes an HTTP request using XMLHttpRequest, cancelling any previous request with the same identifier.
459
509
 
460
510
  **Parameters:**
511
+
461
512
  - `url` (string): The URL to request
462
513
  - `options` (Object, optional): Configuration options
463
- - `method` (string): HTTP method (GET, POST, PUT, DELETE, etc.). Defaults to 'GET'.
464
- - `headers` (Object): Headers object to set on the request
465
- - `body` (string|FormData|Blob|ArrayBuffer): Request body
466
- - `responseType` (string): Response type ('text', 'json', 'blob', 'arraybuffer', 'document'). Defaults to 'text'.
467
- - `withCredentials` (boolean): Whether to send credentials with the request
468
- - `timeout` (number): Request timeout in milliseconds
469
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
470
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
471
- - `verbose` (boolean): If true, cancellation errors will include messages
472
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
514
+ - `method` (string): HTTP method (GET, POST, PUT, DELETE, etc.). Defaults to 'GET'.
515
+ - `headers` (Object): Headers object to set on the request
516
+ - `body` (string|FormData|Blob|ArrayBuffer): Request body
517
+ - `responseType` (string): Response type ('text', 'json', 'blob', 'arraybuffer', 'document'). Defaults to 'text'.
518
+ - `withCredentials` (boolean): Whether to send credentials with the request
519
+ - `timeout` (number): Request timeout in milliseconds
520
+ - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
521
+ - `abortController` (AbortController): AbortController instance (created automatically if not provided)
522
+ - `verbose` (boolean): If true, cancellation rejects with a message that includes the request id
523
+ - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
524
+ - `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
473
525
 
474
526
  **Returns:** Promise that resolves/rejects based on the most recent request. The resolved value is an object with:
475
- - `data`: The response data (automatically parsed as JSON if Content-Type is application/json)
476
- - `status`: HTTP status code
477
- - `statusText`: HTTP status text
478
- - `headers`: Response headers string
479
- - `xhr`: The XMLHttpRequest instance
527
+
528
+ - `data`: The response data (automatically parsed as JSON if Content-Type is application/json)
529
+ - `status`: HTTP status code
530
+ - `statusText`: HTTP status text
531
+ - `headers`: Response headers string
532
+ - `xhr`: The XMLHttpRequest instance
480
533
 
481
534
  **Example:**
535
+
482
536
  ```javascript
483
537
  import RequestManager from '@enegalan/request-manager';
484
538
 
485
539
  const requestManager = new RequestManager();
486
540
 
487
541
  // Simple GET request
488
- requestManager.xhr('/api/users')
489
- .then(response => console.log(response.data))
490
- .catch(error => console.error(error));
542
+ requestManager
543
+ .xhr('/api/users')
544
+ .then((response) => console.log(response.data))
545
+ .catch((error) => console.error(error));
491
546
 
492
547
  // POST request with options
493
- requestManager.xhr('/api/users', {
494
- method: 'POST',
495
- headers: { 'Content-Type': 'application/json' },
496
- body: JSON.stringify({ name: 'John' }),
497
- responseType: 'json'
498
- })
499
- .then(response => console.log(response.data));
548
+ requestManager
549
+ .xhr('/api/users', {
550
+ method: 'POST',
551
+ headers: { 'Content-Type': 'application/json' },
552
+ body: JSON.stringify({ name: 'John' }),
553
+ responseType: 'json',
554
+ })
555
+ .then((response) => console.log(response.data));
556
+ ```
557
+
558
+ ### `getRequestId(url, options)`
559
+
560
+ Returns the request ID that RequestManager assigns for a URL and options.
561
+
562
+ **Parameters:**
563
+
564
+ - `url` (string): The URL used when starting the request
565
+ - `options` (Object, optional): Same options used for the request
566
+ - `requestKey` (string|number|Function, optional): Key override
567
+ - `includeQuery` (boolean, optional): Keep query string in the URL-based ID
568
+ - `noCancel` (boolean, optional): If true, returns a **new** unique ID (will not match an already in-flight `noCancel` request)
569
+
570
+ **Returns:** `string` — the request identifier
571
+
572
+ **Example:**
573
+
574
+ ```javascript
575
+ requestManager.fetch('/api/users');
576
+
577
+ const id = requestManager.getRequestId('/api/users');
578
+ if (requestManager.isActive(id)) {
579
+ requestManager.cancel(id);
580
+ }
581
+
582
+ // With the same options used for the request:
583
+ const searchId = requestManager.getRequestId('/api/search?q=test', { requestKey: 'search-users' });
584
+ requestManager.cancel(searchId);
500
585
  ```
501
586
 
502
587
  ### `cancel(requestId)`
@@ -504,6 +589,7 @@ requestManager.xhr('/api/users', {
504
589
  Cancels a specific request by its identifier.
505
590
 
506
591
  **Parameters:**
592
+
507
593
  - `requestId` (string): The unique identifier of the request to cancel
508
594
 
509
595
  **Returns:** `true` if the request was found and cancelled, `false` otherwise
@@ -519,6 +605,7 @@ Cancels all active requests.
519
605
  Checks if a request with the given identifier is currently active.
520
606
 
521
607
  **Parameters:**
608
+
522
609
  - `requestId` (string): The unique identifier to check
523
610
 
524
611
  **Returns:** `true` if the request is active, `false` otherwise
@@ -535,11 +622,12 @@ Clears all active requests without cancelling them. Use with caution - this will
535
622
 
536
623
  ### `getSignal()`
537
624
 
538
- Gets the AbortSignal from the current AbortController. Creates a new AbortController if one doesn't exist or if the current one is aborted.
625
+ Creates a new AbortController and returns its signal for the next `request()` (one `getSignal` → one request). Do not use for parallel requests; use `fetch()`, `axios()`, or `request(url, ({ options }) => ...)` instead — they create their own signal.
539
626
 
540
- **Returns:** AbortSignal from the current AbortController
627
+ **Returns:** AbortSignal from a new AbortController
541
628
 
542
629
  **Example:**
630
+
543
631
  ```javascript
544
632
  const signal = requestManager.getSignal();
545
633
  requestManager.request('/api/users', fetch('/api/users', { signal }));
@@ -547,11 +635,12 @@ requestManager.request('/api/users', fetch('/api/users', { signal }));
547
635
 
548
636
  ### `getAbortController()`
549
637
 
550
- Gets the current AbortController instance. Creates a new AbortController if one doesn't exist or if the current one is aborted.
638
+ Creates a new AbortController for the next request handoff. Always returns a fresh controller (never reuses one from another in-flight request).
551
639
 
552
640
  **Returns:** AbortController instance
553
641
 
554
642
  **Example:**
643
+
555
644
  ```javascript
556
645
  const abortController = requestManager.getAbortController();
557
646
  requestManager.request('/api/users', fetch('/api/users', { signal: abortController.signal }));
@@ -568,17 +657,19 @@ Gets the manager options that were passed to the constructor or set via `setOpti
568
657
  Sets the manager options.
569
658
 
570
659
  **Parameters:**
660
+
571
661
  - `options` (Object): Configuration options
572
- - `verbose` (boolean, optional): If true, cancellation errors will include messages
662
+ - `verbose` (boolean, optional): If true, cancellation rejects with a message that includes the request id. If false (default), cancellation is silent.
573
663
 
574
664
  **Example:**
665
+
575
666
  ```javascript
576
667
  const requestManager = new RequestManager();
577
668
 
578
- // Enable verbose mode at runtime
669
+ // Enable verbose cancellation messages at runtime
579
670
  requestManager.setOptions({ verbose: true });
580
671
 
581
- // Disable verbose mode
672
+ // Silent cancellation (default) — no rejection / no console noise
582
673
  requestManager.setOptions({ verbose: false });
583
674
  ```
584
675
 
@@ -587,10 +678,12 @@ requestManager.setOptions({ verbose: false });
587
678
  Links an abort signal with an HTTP client abort method. Useful for custom HTTP clients that only support the abort method to cancel requests.
588
679
 
589
680
  **Parameters:**
681
+
590
682
  - `abortMethod` (Function): The abort method to call when the signal is aborted
591
683
  - `signal` (AbortSignal): The signal to listen to
592
684
 
593
685
  **Example:**
686
+
594
687
  ```javascript
595
688
  const abortController = new AbortController();
596
689
  const req = $.ajax({ url });