@enegalan/request-manager 1.1.0 → 1.1.2

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.
@@ -0,0 +1,720 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function _arrayLikeToArray(r, a) {
6
+ (null == a || a > r.length) && (a = r.length);
7
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
8
+ return n;
9
+ }
10
+ function _arrayWithHoles(r) {
11
+ if (Array.isArray(r)) return r;
12
+ }
13
+ function _assertClassBrand(e, t, n) {
14
+ if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
15
+ throw new TypeError("Private element is not present on this object");
16
+ }
17
+ function _checkPrivateRedeclaration(e, t) {
18
+ if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
19
+ }
20
+ function _classPrivateMethodInitSpec(e, a) {
21
+ _checkPrivateRedeclaration(e, a), a.add(e);
22
+ }
23
+ function _defineProperty(e, r, t) {
24
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
25
+ value: t,
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true
29
+ }) : e[r] = t, e;
30
+ }
31
+ function _iterableToArrayLimit(r, l) {
32
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
33
+ if (null != t) {
34
+ var e,
35
+ n,
36
+ i,
37
+ u,
38
+ a = [],
39
+ f = true,
40
+ o = false;
41
+ try {
42
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
43
+ } catch (r) {
44
+ o = true, n = r;
45
+ } finally {
46
+ try {
47
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
48
+ } finally {
49
+ if (o) throw n;
50
+ }
51
+ }
52
+ return a;
53
+ }
54
+ }
55
+ function _nonIterableRest() {
56
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57
+ }
58
+ function ownKeys(e, r) {
59
+ var t = Object.keys(e);
60
+ if (Object.getOwnPropertySymbols) {
61
+ var o = Object.getOwnPropertySymbols(e);
62
+ r && (o = o.filter(function (r) {
63
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
64
+ })), t.push.apply(t, o);
65
+ }
66
+ return t;
67
+ }
68
+ function _objectSpread2(e) {
69
+ for (var r = 1; r < arguments.length; r++) {
70
+ var t = null != arguments[r] ? arguments[r] : {};
71
+ r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
72
+ _defineProperty(e, r, t[r]);
73
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
74
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
75
+ });
76
+ }
77
+ return e;
78
+ }
79
+ function _slicedToArray(r, e) {
80
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
81
+ }
82
+ function _toPrimitive(t, r) {
83
+ if ("object" != typeof t || !t) return t;
84
+ var e = t[Symbol.toPrimitive];
85
+ if (void 0 !== e) {
86
+ var i = e.call(t, r);
87
+ if ("object" != typeof i) return i;
88
+ throw new TypeError("@@toPrimitive must return a primitive value.");
89
+ }
90
+ return ("string" === r ? String : Number)(t);
91
+ }
92
+ function _toPropertyKey(t) {
93
+ var i = _toPrimitive(t, "string");
94
+ return "symbol" == typeof i ? i : i + "";
95
+ }
96
+ function _unsupportedIterableToArray(r, a) {
97
+ if (r) {
98
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
99
+ var t = {}.toString.call(r).slice(8, -1);
100
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
101
+ }
102
+ }
103
+
104
+ var _RequestManager_brand = /*#__PURE__*/new WeakSet();
105
+ /**
106
+ * RequestManager - A library for managing and regulating HTTP requests efficiently.
107
+ * @license MIT
108
+ * @author Eneko Galan <enekogalanelorza@gmail.com>
109
+ * This library allows you to manage HTTP requests from any library (ajax, Ext.Ajax, axios, fetch, etc.)
110
+ * by accepting Promises as parameters. When a request is repeated with the same identifier,
111
+ * the previous request is automatically cancelled and the new one is executed, giving priority to the most recent requests.
112
+ */
113
+ class RequestManager {
114
+ constructor() {
115
+ var _options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
116
+ /**
117
+ * Resolves the AbortController for a request: explicit option, pending handoff, or new.
118
+ * Clears the pending handoff so concurrent requests do not share it.
119
+ * @param {AbortController|undefined} provided - Optional AbortController from options
120
+ * @returns {AbortController}
121
+ * @private
122
+ */
123
+ _classPrivateMethodInitSpec(this, _RequestManager_brand);
124
+ /**
125
+ * @type {Map<string, import('./index.d.ts').ActiveRequest>}
126
+ */
127
+ this.activeRequests = new Map();
128
+ /**
129
+ * @type {import('./index.d.ts').Options}
130
+ */
131
+ this.options = _options;
132
+ /**
133
+ * One-shot AbortController for getSignal()/getAbortController() handoff.
134
+ * Consumed by the next request that does not pass options.abortController.
135
+ * @type {AbortController|null}
136
+ */
137
+ this.abortController = null;
138
+ }
139
+
140
+ /**
141
+ * Gets the manager options
142
+ * @returns {import('./index.d.ts').Options} Manager options
143
+ */
144
+ getOptions() {
145
+ return this.options;
146
+ }
147
+
148
+ /**
149
+ * Sets the manager options
150
+ * @param {import('./index.d.ts').Options} options - The options to set
151
+ */
152
+ setOptions(options) {
153
+ this.options = options;
154
+ }
155
+
156
+ /**
157
+ * Creates a new AbortController and returns its signal for the next request()
158
+ * (one getSignal → one request). Do not use for parallel requests; use fetch(),
159
+ * axios(), or request(url, ({ options }) => ...) instead — they create their own signal.
160
+ * @returns {AbortSignal} The signal from a new AbortController
161
+ * @example
162
+ * const signal = requestManager.getSignal();
163
+ * requestManager.request('/api/users', fetch('/api/users', { signal }));
164
+ */
165
+ getSignal() {
166
+ return this.getAbortController().signal;
167
+ }
168
+
169
+ /**
170
+ * Creates a new AbortController for the next request handoff.
171
+ * Always returns a fresh controller (never reuses one from another in-flight request).
172
+ * @returns {AbortController} A new AbortController instance
173
+ */
174
+ getAbortController() {
175
+ this.abortController = new AbortController();
176
+ return this.abortController;
177
+ }
178
+
179
+ /**
180
+ * Checks if a request with the given identifier is currently active.
181
+ * @param {string} requestId - The unique identifier to check
182
+ * @returns {boolean} True if the request is active, false otherwise
183
+ */
184
+ isActive(requestId) {
185
+ return this.activeRequests.has(requestId);
186
+ }
187
+
188
+ /**
189
+ * Gets the number of active requests.
190
+ * @returns {number} The number of currently active requests
191
+ */
192
+ getActiveCount() {
193
+ return this.activeRequests.size;
194
+ }
195
+
196
+ /**
197
+ * Clears all active requests without cancelling them.
198
+ * Use with caution - this will not cancel the underlying HTTP requests.
199
+ */
200
+ clear() {
201
+ this.activeRequests.clear();
202
+ }
203
+
204
+ /**
205
+ * Executes an HTTP request, cancelling any previous request with the same identifier.
206
+ * @param {string} url - The URL to request
207
+ * @param {Promise|import('./index.d.ts').RequestFunction|string} requestPromise - The request promise or function that returns a promise
208
+ * @param {import('./index.d.ts').RequestOptions} options - Optional configuration
209
+ * @returns {Promise} A Promise that resolves/rejects based on the most recent request
210
+ * @example
211
+ * // Request with Promise
212
+ * requestManager.request('/api/users', axios.get('/api/users'));
213
+ * @example
214
+ * // Request with Function
215
+ * requestManager.request('/api/users', ({ options }) => fetch('/api/users', { signal: options.signal, ...options }));
216
+ * @example
217
+ * // Request with Promise and custom cancellation grouping with requestKey
218
+ * const options = {
219
+ * requestKey: 'get-users'
220
+ * }
221
+ * requestManager.request('/api/users', axios.get('/api/users', options), options);
222
+ */
223
+ request(url, requestPromise) {
224
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
225
+ return _assertClassBrand(_RequestManager_brand, this, _request).call(this, this.getRequestId(url, options), requestPromise, options);
226
+ }
227
+
228
+ /**
229
+ * Executes an HTTP request using fetch, cancelling any previous request with the same identifier.
230
+ * @param {string} url - The URL to fetch
231
+ * @param {import('./index.d.ts').FetchOptions} options - Optional configuration
232
+ * @returns {Promise} A Promise that resolves/rejects based on the most recent request
233
+ * @example
234
+ * // Simple GET request
235
+ * requestManager.fetch('/api/users');
236
+ * @example
237
+ * // POST request with options
238
+ * requestManager.fetch('/api/users', {
239
+ * method: 'POST',
240
+ * headers: { 'Content-Type': 'application/json' },
241
+ * body: JSON.stringify({ name: 'John' })
242
+ * });
243
+ * @example
244
+ * // Request with requestKey for custom cancellation grouping with requestKey
245
+ * requestManager.fetch('/api/users', {
246
+ * requestKey: 'get-users'
247
+ * });
248
+ */
249
+ fetch(url) {
250
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
251
+ return _assertClassBrand(_RequestManager_brand, this, _request).call(this, this.getRequestId(url, options), url, options);
252
+ }
253
+
254
+ /**
255
+ * Executes an HTTP request using axios, cancelling any previous request with the same identifier.
256
+ * @param {string} url - The URL to request
257
+ * @param {import('./index.d.ts').AxiosRequestOptions} options - Optional configuration
258
+ * @param {import('./index.d.ts').AxiosInstance|import('./index.d.ts').AxiosStatic|null} axiosInstance - Optional axios instance to use. If not provided, uses global axios.
259
+ * @returns {Promise} A Promise that resolves/rejects based on the most recent request
260
+ * @example
261
+ * // Simple GET request (uses global axios)
262
+ * requestManager.axios('/api/users');
263
+ * @example
264
+ * // With custom axios instance
265
+ * const myAxios = axios.create({ baseURL: 'https://api.example.com' });
266
+ * requestManager.axios('/users', {}, myAxios);
267
+ * @example
268
+ * // POST request with options
269
+ * requestManager.axios('/api/users', {
270
+ * method: 'POST',
271
+ * headers: { 'Content-Type': 'application/json' },
272
+ * body: JSON.stringify({ name: 'John' })
273
+ * });
274
+ * @example
275
+ * // Request with requestKey for custom cancellation grouping with requestKey
276
+ * requestManager.axios('/api/users', {
277
+ * requestKey: 'get-users'
278
+ * });
279
+ */
280
+ axios(url) {
281
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
282
+ var axiosInstance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
283
+ var axiosLib = axiosInstance || (typeof axios !== 'undefined' ? axios : null);
284
+ if (!axiosLib) {
285
+ throw new Error('axios was not found: pass an axios instance as the third argument of requestManager.axios() or make sure axios is available globally');
286
+ }
287
+ _assertClassBrand(_RequestManager_brand, this, _checkAxiosVersion).call(this, axiosLib);
288
+ return _assertClassBrand(_RequestManager_brand, this, _request).call(this, this.getRequestId(url, options), _ref => {
289
+ var requestOptions = _ref.options;
290
+ return axiosLib(_objectSpread2({
291
+ url
292
+ }, requestOptions));
293
+ }, options);
294
+ }
295
+
296
+ /**
297
+ * Executes an HTTP request using jQuery.ajax, cancelling any previous request with the same identifier.
298
+ * @param {import('./index.d.ts').AjaxFunction} ajaxFunction - A function that receives { url, ...options } and returns a Promise
299
+ * @param {string} url - The URL to request
300
+ * @param {import('./index.d.ts').BaseRequestOptions & Record<string, any>} options - Optional configuration
301
+ * @returns {Promise} A Promise that resolves/rejects based on the most recent request
302
+ * @example
303
+ * // Simple GET request
304
+ * requestManager.ajax(ajaxFunction, '/api/users');
305
+ * @example
306
+ * // POST request with options
307
+ * requestManager.ajax(ajaxFunction, '/api/users', {
308
+ * method: 'POST',
309
+ * headers: { 'Content-Type': 'application/json' },
310
+ * body: JSON.stringify({ name: 'John' })
311
+ * });
312
+ * @example
313
+ * // Request with requestKey for custom cancellation grouping with requestKey
314
+ * requestManager.ajax(ajaxFunction, '/api/users', {
315
+ * requestKey: 'get-users'
316
+ * });
317
+ */
318
+ ajax(ajaxFunction, url) {
319
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
320
+ if (typeof ajaxFunction !== 'function') throw new Error('ajaxFunction parameter must be a function');
321
+ return _assertClassBrand(_RequestManager_brand, this, _request).call(this, this.getRequestId(url, options), _ref2 => {
322
+ var requestOptions = _ref2.options;
323
+ return ajaxFunction(_objectSpread2({
324
+ url
325
+ }, requestOptions));
326
+ }, options);
327
+ }
328
+
329
+ /**
330
+ * Executes an HTTP request using XMLHttpRequest, cancelling any previous request with the same identifier.
331
+ * @param {string} url - The URL to request
332
+ * @param {import('./index.d.ts').XhrOptions} options - Optional configuration
333
+ * @returns {Promise<import('./index.d.ts').XhrResponse>} A Promise that resolves/rejects based on the most recent request
334
+ * @example
335
+ * // Simple GET request
336
+ * requestManager.xhr('/api/users');
337
+ * @example
338
+ * // POST request with options
339
+ * requestManager.xhr('/api/users', {
340
+ * method: 'POST',
341
+ * headers: { 'Content-Type': 'application/json' },
342
+ * body: JSON.stringify({ name: 'John' })
343
+ * });
344
+ * @example
345
+ * // Request with requestKey for custom cancellation grouping
346
+ * requestManager.xhr('/api/users', {
347
+ * requestKey: 'get-users'
348
+ * });
349
+ */
350
+ xhr(url) {
351
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
352
+ /** @type {import('./index.d.ts').RequestFunction<import('./index.d.ts').XhrResponse>} */
353
+ var xhrFunction = _ref3 => {
354
+ var fetchOptions = _ref3.options;
355
+ // Create XMLHttpRequest
356
+ var xhr = new XMLHttpRequest();
357
+ var method = (options.method || 'GET').toUpperCase();
358
+ // Create a promise that wraps the XHR request
359
+ var xhrPromise = new Promise((resolve, reject) => {
360
+ xhr.onload = function () {
361
+ detachAbortListener();
362
+ if (xhr.status >= 200 && xhr.status < 300) {
363
+ var _xhr$getResponseHeade;
364
+ var response = xhr.response;
365
+ if (options.responseType === 'json' || (!options.responseType || options.responseType === 'text') && (_xhr$getResponseHeade = xhr.getResponseHeader('Content-Type')) !== null && _xhr$getResponseHeade !== void 0 && _xhr$getResponseHeade.includes('application/json') && typeof response === 'string') {
366
+ try {
367
+ response = JSON.parse(response);
368
+ } catch (_unused) {}
369
+ }
370
+ resolve({
371
+ data: response,
372
+ status: xhr.status,
373
+ statusText: xhr.statusText,
374
+ headers: xhr.getAllResponseHeaders(),
375
+ xhr: xhr
376
+ });
377
+ } else {
378
+ reject({
379
+ message: "Request failed with status ".concat(xhr.status),
380
+ status: xhr.status,
381
+ statusText: xhr.statusText,
382
+ xhr: xhr
383
+ });
384
+ }
385
+ };
386
+ xhr.onerror = function () {
387
+ detachAbortListener();
388
+ reject({
389
+ message: 'Network error',
390
+ xhr: xhr
391
+ });
392
+ };
393
+ xhr.ontimeout = function () {
394
+ detachAbortListener();
395
+ reject({
396
+ message: 'Request timeout',
397
+ xhr: xhr
398
+ });
399
+ };
400
+
401
+ // Open the request
402
+ xhr.open(method, url, true);
403
+
404
+ // Set response type
405
+ if (options.responseType) xhr.responseType = options.responseType;
406
+ // Set withCredentials
407
+ if (options.withCredentials !== undefined) xhr.withCredentials = options.withCredentials;
408
+ // Set timeout
409
+ if (options.timeout !== undefined) xhr.timeout = options.timeout;
410
+ // Set headers
411
+ if (options.headers) Object.keys(options.headers).forEach(key => {
412
+ xhr.setRequestHeader(key, options.headers[key]);
413
+ });
414
+
415
+ // Connect abort signal to xhr.abort()
416
+ var abortListener = () => xhr.abort();
417
+ if (fetchOptions.signal) fetchOptions.signal.addEventListener('abort', abortListener);
418
+ // Detach the listener once the request settles so completed requests do not keep it alive
419
+ var detachAbortListener = () => {
420
+ if (fetchOptions.signal) fetchOptions.signal.removeEventListener('abort', abortListener);
421
+ };
422
+ xhr.onabort = function () {
423
+ reject({
424
+ message: 'Request was cancelled',
425
+ xhr: xhr
426
+ });
427
+ };
428
+
429
+ // Send the request
430
+ xhr.send(options.body || null);
431
+ });
432
+ return xhrPromise;
433
+ };
434
+ return _assertClassBrand(_RequestManager_brand, this, _request).call(this, this.getRequestId(url, options), xhrFunction, options);
435
+ }
436
+
437
+ /**
438
+ * Returns the request identifier for a URL and options.
439
+ * @param {string} url - The URL used when starting the request
440
+ * @param {import('./index.d.ts').BaseRequestOptions} options - Same options used for the request
441
+ * @returns {string} The request identifier
442
+ * @example
443
+ * requestManager.fetch('/api/users');
444
+ * const id = requestManager.getRequestId('/api/users');
445
+ * requestManager.cancel(id);
446
+ */
447
+ getRequestId(url) {
448
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
449
+ var requestKey = options.requestKey;
450
+ var prefix = 'request_';
451
+
452
+ // Generate a unique identifier to prevent cancellation for non cancelable requests
453
+ if (options.noCancel) {
454
+ return "".concat(prefix).concat(Date.now(), "_").concat(Math.random().toString(36).slice(2, 11));
455
+ }
456
+
457
+ // Handle function requestKey
458
+ if (typeof requestKey === 'function') {
459
+ try {
460
+ requestKey = requestKey();
461
+ } catch (_unused2) {
462
+ requestKey = null;
463
+ }
464
+ }
465
+ if (requestKey !== null && requestKey !== undefined) return "".concat(prefix).concat(String(requestKey));
466
+
467
+ // Use cleaned URL as key as fallback
468
+ var cleanedUrl = url || '';
469
+ if (cleanedUrl.includes('://')) cleanedUrl = cleanedUrl.split('://')[1];
470
+ if (cleanedUrl.includes('#')) cleanedUrl = cleanedUrl.split('#')[0];
471
+ if (!options.includeQuery && cleanedUrl.includes('?')) cleanedUrl = cleanedUrl.split('?')[0];
472
+ var methodPrefix = options.includeMethod === false ? '' : "".concat((options.method || options.type || 'GET').toUpperCase(), "_");
473
+ return "".concat(prefix).concat(methodPrefix).concat(cleanedUrl);
474
+ }
475
+
476
+ /**
477
+ * Cancels a specific request by its identifier.
478
+ * @param {string} requestId - The unique identifier of the request to cancel
479
+ * @returns {boolean} True if the request was found and cancelled, false otherwise
480
+ */
481
+ cancel(requestId) {
482
+ /** @type {import('./index.d.ts').ActiveRequest|undefined} */
483
+ var requestInfo = this.activeRequests.get(requestId);
484
+ if (!requestInfo) return false;
485
+ requestInfo.isCancelled = true; // Mark as cancelled
486
+
487
+ // Try to abort using AbortController (for fetch)
488
+ if (requestInfo.abortController && !requestInfo.abortController.signal.aborted) {
489
+ try {
490
+ requestInfo.abortController.abort('Request was cancelled');
491
+ } catch (error) {}
492
+ }
493
+
494
+ // Try to cancel using cancel token/function (for axios and others)
495
+ if (requestInfo.cancelToken) {
496
+ try {
497
+ if (typeof requestInfo.cancelToken === 'function') requestInfo.cancelToken();else if (requestInfo.cancelToken.cancel) requestInfo.cancelToken.cancel();
498
+ } catch (error) {}
499
+ }
500
+
501
+ // Reject the wrapper promise
502
+ _assertClassBrand(_RequestManager_brand, this, _deleteRequest).call(this, requestId, requestInfo.rejectWrapper, this.getOptions().verbose ? new Error("Request ".concat(requestId, " was cancelled")) : null);
503
+ return true;
504
+ }
505
+
506
+ /**
507
+ * Link abort signal with HTTP client abort method.
508
+ * Useful for custom HTTP clients that only support the abort method to cancel requests.
509
+ * @param {Function} abortMethod - The abort method to call when the signal is aborted
510
+ * @param {AbortSignal} signal - The signal to listen to
511
+ */
512
+ addAbortListener(abortMethod, signal) {
513
+ if (!abortMethod || !signal) return;
514
+ signal.addEventListener('abort', () => {
515
+ if (typeof abortMethod === 'function') {
516
+ try {
517
+ abortMethod();
518
+ } catch (error) {}
519
+ }
520
+ });
521
+ }
522
+
523
+ /**
524
+ * Cancels all active requests.
525
+ * @returns {number} The number of requests that were cancelled
526
+ */
527
+ cancelAll() {
528
+ var requestIds = Array.from(this.activeRequests.keys());
529
+ var cancelledCount = 0;
530
+ requestIds.forEach(requestId => {
531
+ if (this.cancel(requestId)) cancelledCount++;
532
+ });
533
+ return cancelledCount;
534
+ }
535
+ }
536
+ function _resolveAbortController(provided) {
537
+ var abortController = provided || this.abortController || new AbortController();
538
+ this.abortController = null;
539
+ return abortController;
540
+ }
541
+ /**
542
+ * Picks the best abort callback for a client request object.
543
+ * @param {Object} req - The request object
544
+ * @returns {Function|null}
545
+ * @private
546
+ */
547
+ function _resolveAbortMethod(req) {
548
+ if (!req) return null;
549
+ if (typeof req.abort === 'function') return () => req.abort();
550
+ var ExtAjax = typeof globalThis !== 'undefined' && globalThis.Ext && globalThis.Ext.Ajax ? globalThis.Ext.Ajax : null;
551
+ if (req.xhr && ExtAjax && typeof ExtAjax.abort === 'function') {
552
+ return () => ExtAjax.abort(req);
553
+ }
554
+ if (req.xhr && typeof req.xhr.abort === 'function') return () => req.xhr.abort();
555
+ return null;
556
+ }
557
+ /**
558
+ * Prepares request options by merging options and removing custom properties
559
+ * @param {import('./index.d.ts').RequestOptions} options - Configuration options
560
+ * @param {AbortSignal} signal - Abort signal to add to request options
561
+ * @returns {Object} Prepared request options
562
+ * @private
563
+ */
564
+ function _prepareRequestOptions(options, signal) {
565
+ var requestOptions = {};
566
+ var customOptions = ['abortController', 'cancelToken', 'requestKey', 'noCancel', 'includeQuery', 'includeMethod', 'verbose'];
567
+ Object.keys(options).forEach(key => {
568
+ if (customOptions.includes(key)) return;
569
+ requestOptions[key] = options[key];
570
+ });
571
+ requestOptions.signal = signal;
572
+ return requestOptions;
573
+ }
574
+ /**
575
+ * Warns when the provided axios instance predates 0.22.0, the first version
576
+ * supporting AbortSignal cancellation. Older instances silently ignore
577
+ * options.signal, so duplicate requests would not be cancelled.
578
+ * @param {object} axiosLib - The axios instance about to be used
579
+ * @private
580
+ */
581
+ function _checkAxiosVersion(axiosLib) {
582
+ var version = typeof (axiosLib === null || axiosLib === void 0 ? void 0 : axiosLib.VERSION) === 'string' ? axiosLib.VERSION : null;
583
+ if (!version) return;
584
+ var _version$split$map = version.split('.').map(Number),
585
+ _version$split$map2 = _slicedToArray(_version$split$map, 2),
586
+ major = _version$split$map2[0],
587
+ minor = _version$split$map2[1];
588
+ if (major === 0 && minor < 22) {
589
+ console.warn("[request-manager] axios >= 0.22.0 is required: axios ".concat(version, " ignores the AbortSignal used for automatic cancellation. Please upgrade axios."));
590
+ }
591
+ }
592
+ /**
593
+ * Deletes a request from the active requests map and rejects the wrapper promise
594
+ * @param {string} requestId - The unique identifier of the request
595
+ * @param {Function} rejectWrapper - The function to reject the wrapper promise
596
+ * @param {*} error - The error to reject the wrapper promise with; the wrapper is not rejected if null/undefined
597
+ * @private
598
+ */
599
+ function _deleteRequest(requestId, rejectWrapper, error) {
600
+ this.activeRequests.delete(requestId);
601
+ if (error !== null && error !== undefined) {
602
+ rejectWrapper(error);
603
+ }
604
+ }
605
+ /**
606
+ * Completes a request by deleting it from the active requests map and resolving the wrapper promise
607
+ * @param {string} requestId - The unique identifier of the request
608
+ * @param {Function} resolveWrapper - The function to resolve the wrapper promise
609
+ * @param {Promise} requestPromise - The request promise
610
+ * @param {boolean} isCancelled - Whether the request was cancelled
611
+ * @private
612
+ */
613
+ function _completeRequest(requestId, resolveWrapper, requestPromise, isCancelled) {
614
+ this.activeRequests.delete(requestId);
615
+ if (!isCancelled) {
616
+ resolveWrapper(requestPromise);
617
+ }
618
+ }
619
+ /**
620
+ * Internal method that handles the core request logic.
621
+ * @param {string} requestId - Unique identifier for the request
622
+ * @param {Promise|import('./index.d.ts').RequestFunction|string} requestPromise - The request promise, function, or URL string
623
+ * @param {import('./index.d.ts').BaseRequestOptions} options - Configuration options
624
+ * @returns {Promise} A Promise that resolves/rejects based on the most recent request
625
+ * @private
626
+ */
627
+ function _request(requestId, requestPromise) {
628
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
629
+ var abortController = _assertClassBrand(_RequestManager_brand, this, _resolveAbortController).call(this, options.abortController);
630
+
631
+ // Handle different types of requestPromise inputs
632
+ // Priority: Function (Custom logic) > String (URL) > Promise (axios, fetch, etc.)
633
+ if (typeof requestPromise === 'function') {
634
+ // Function: custom logic for any library (axios, ajax, etc.)
635
+ try {
636
+ requestPromise = requestPromise({
637
+ options: _assertClassBrand(_RequestManager_brand, this, _prepareRequestOptions).call(this, options, abortController.signal)
638
+ });
639
+ } catch (error) {
640
+ return Promise.reject(error);
641
+ }
642
+ } else if (typeof requestPromise === 'string') {
643
+ // String (URL): make fetch internally
644
+ try {
645
+ requestPromise = fetch(requestPromise, _assertClassBrand(_RequestManager_brand, this, _prepareRequestOptions).call(this, options, abortController.signal));
646
+ } catch (error) {
647
+ return Promise.reject(error);
648
+ }
649
+ }
650
+
651
+ // Link the request object's abort method (Ext.Ajax, XHR, etc.) to the cancellation signal
652
+ this.addAbortListener(_assertClassBrand(_RequestManager_brand, this, _resolveAbortMethod).call(this, requestPromise), abortController.signal);
653
+
654
+ // Cancel previous request with the same identifier if it exists
655
+ if (!options.noCancel) this.cancel(requestId);
656
+
657
+ // Create a wrapper promise that will be resolved/rejected based on the request
658
+ var resolveWrapper, rejectWrapper;
659
+ var wrapperPromise = new Promise((resolve, reject) => {
660
+ resolveWrapper = resolve;
661
+ rejectWrapper = reject;
662
+ });
663
+
664
+ /**
665
+ * @type {import('./index.d.ts').ActiveRequest}
666
+ */
667
+ var requestInfo = {
668
+ promise: requestPromise,
669
+ abortController: abortController,
670
+ cancelToken: options.cancelToken || null,
671
+ resolveWrapper: resolveWrapper,
672
+ rejectWrapper: rejectWrapper,
673
+ isCancelled: false
674
+ };
675
+ this.activeRequests.set(requestId, requestInfo);
676
+
677
+ // Handle request promise completion
678
+ if (requestPromise && typeof requestPromise.then === 'function') {
679
+ try {
680
+ var req = requestPromise.then(result => {
681
+ if (this.activeRequests.get(requestId) !== requestInfo) return;
682
+ _assertClassBrand(_RequestManager_brand, this, _completeRequest).call(this, requestId, resolveWrapper, result, requestInfo.isCancelled);
683
+ });
684
+ if (req.catch) req.catch(error => {
685
+ onError(this, error);
686
+ });
687
+ } catch (error) {
688
+ onError(this, error);
689
+ }
690
+ function onError(scope, error) {
691
+ // Check if this requestInfo is still the active one, or if it was cancelled
692
+ if (scope.activeRequests.get(requestId) !== requestInfo) return;
693
+ if (requestInfo.isCancelled) {
694
+ // Already cancelled: let cancel() handle cleanup and reject the wrapper promise
695
+ scope.cancel(requestId);
696
+ return;
697
+ }
698
+ // Only delete if this is still the active request
699
+ _assertClassBrand(_RequestManager_brand, scope, _deleteRequest).call(scope, requestId, rejectWrapper, error);
700
+ }
701
+ } else {
702
+ // Non-promise (Ext.Ajax request object, raw XHR, etc.). Keep tracked until the
703
+ // underlying XHR finishes so a later duplicate can still cancel it.
704
+ var xhr = requestPromise && (requestPromise.xhr || (typeof XMLHttpRequest !== 'undefined' && requestPromise instanceof XMLHttpRequest ? requestPromise : null));
705
+ var finish = () => {
706
+ if (this.activeRequests.get(requestId) !== requestInfo) return;
707
+ _assertClassBrand(_RequestManager_brand, this, _completeRequest).call(this, requestId, resolveWrapper, requestPromise, requestInfo.isCancelled);
708
+ };
709
+ if (xhr && typeof xhr.addEventListener === 'function') {
710
+ xhr.addEventListener('loadend', finish);
711
+ } else {
712
+ setTimeout(finish, 0);
713
+ }
714
+ }
715
+ return wrapperPromise;
716
+ }
717
+
718
+ exports.RequestManager = RequestManager;
719
+ exports.default = RequestManager;
720
+ //# sourceMappingURL=request-manager.cjs.map