@d1g1tal/transportr 1.3.2 → 1.4.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/dist/transportr.js +1514 -1598
- package/dist/transportr.min.js +2 -2
- package/dist/transportr.min.js.map +4 -4
- package/package.json +3 -3
- package/src/abort-signal.js +17 -70
- package/src/constants.js +12 -1
- package/src/transportr.js +28 -68
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d1g1tal/transportr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "JavaScript wrapper for the Fetch API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"@skypack/package-check": "^0.2.2",
|
|
46
46
|
"@xmldom/xmldom": "^0.8.10",
|
|
47
47
|
"esbuild": "^0.19.3",
|
|
48
|
-
"eslint": "^8.
|
|
48
|
+
"eslint": "^8.50.0",
|
|
49
49
|
"eslint-plugin-compat": "^4.2.0",
|
|
50
50
|
"eslint-plugin-jsdoc": "^46.8.2",
|
|
51
51
|
"jest": "^29.7.0",
|
|
52
|
-
"rimraf": "^5.0.
|
|
52
|
+
"rimraf": "^5.0.4"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@d1g1tal/chrysalis": "^1.2.3",
|
package/src/abort-signal.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { SignalEvents, abortEvent } from './constants.js';
|
|
2
2
|
|
|
3
|
-
const NativeAbortSignal = globalThis.AbortSignal;
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* @typedef {function(Event):void} EventListener
|
|
7
5
|
* @param {Event} event The event object
|
|
@@ -22,27 +20,6 @@ export default class AbortSignal extends EventTarget {
|
|
|
22
20
|
signal?.addEventListener(SignalEvents.ABORT, (event) => this.#abort(event));
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
/**
|
|
26
|
-
* Returns an {@link AbortSignal} instance that is already set as aborted.
|
|
27
|
-
*
|
|
28
|
-
* @static
|
|
29
|
-
* @returns {AbortSignal} The abort signal
|
|
30
|
-
*/
|
|
31
|
-
static abort() {
|
|
32
|
-
return NativeAbortSignal.abort();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Returns an AbortSignal instance that will automatically abort after a specified time.
|
|
37
|
-
*
|
|
38
|
-
* @static
|
|
39
|
-
* @param {number} time The time in milliseconds to wait before aborting
|
|
40
|
-
* @returns {AbortSignal} The abort signal
|
|
41
|
-
*/
|
|
42
|
-
static timeout(time) {
|
|
43
|
-
return NativeAbortSignal.timeout(time);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
23
|
/**
|
|
47
24
|
* The aborted property is a Boolean that indicates whether the request has been aborted (true) or not (false).
|
|
48
25
|
*
|
|
@@ -61,29 +38,22 @@ export default class AbortSignal extends EventTarget {
|
|
|
61
38
|
return this.#abortController.signal.reason;
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
/**
|
|
65
|
-
* throws the signal's abort reason if the signal has been aborted; otherwise it does nothing.
|
|
66
|
-
*
|
|
67
|
-
* @returns {void}
|
|
68
|
-
*/
|
|
69
|
-
throwIfAborted() {
|
|
70
|
-
this.#abortController.signal.throwIfAborted();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
41
|
/**
|
|
74
42
|
* Returns an AbortSignal instance that will be aborted when the provided amount of milliseconds have passed.
|
|
75
|
-
* A value of
|
|
43
|
+
* A value of {@link Infinity} means there is no timeout.
|
|
76
44
|
* Note: You can't set this property to a value less than 0.
|
|
77
45
|
*
|
|
78
46
|
* @param {number} timeout The timeout in milliseconds
|
|
79
47
|
* @returns {AbortSignal} The abort signal
|
|
80
48
|
*/
|
|
81
|
-
|
|
49
|
+
timeout(timeout) {
|
|
82
50
|
if (timeout < 0) {
|
|
83
51
|
throw new RangeError('The timeout cannot be negative');
|
|
84
52
|
}
|
|
85
53
|
|
|
86
|
-
|
|
54
|
+
if (timeout != Infinity) {
|
|
55
|
+
this.#timeoutId ??= setTimeout(() => this.#abort(new CustomEvent(SignalEvents.TIMEOUT, { detail: { timeout, cause: new DOMException(`The request timed-out after ${timeout / 1000} seconds`, 'TimeoutError') } }), true), timeout);
|
|
56
|
+
}
|
|
87
57
|
|
|
88
58
|
return this.#abortController.signal;
|
|
89
59
|
}
|
|
@@ -99,38 +69,27 @@ export default class AbortSignal extends EventTarget {
|
|
|
99
69
|
}
|
|
100
70
|
|
|
101
71
|
/**
|
|
102
|
-
* Adds an event listener for the
|
|
72
|
+
* Adds an event listener for the 'abort' event.
|
|
103
73
|
*
|
|
104
|
-
* @override
|
|
105
|
-
* @param {string} eventName The name of the event to listen to
|
|
106
74
|
* @param {EventListener} listener The listener to add
|
|
107
|
-
* @returns {
|
|
75
|
+
* @returns {AbortSignal} The AbortSignal
|
|
108
76
|
*/
|
|
109
|
-
|
|
110
|
-
this.#abortController.signal.addEventListener(
|
|
111
|
-
}
|
|
77
|
+
onAbort(listener) {
|
|
78
|
+
this.#abortController.signal.addEventListener(SignalEvents.ABORT, listener);
|
|
112
79
|
|
|
113
|
-
|
|
114
|
-
* Dispatches an event to this EventTarget.
|
|
115
|
-
*
|
|
116
|
-
* @override
|
|
117
|
-
* @param {Event} event The event to dispatch
|
|
118
|
-
* @returns {boolean} Whether the event was dispatched or not
|
|
119
|
-
*/
|
|
120
|
-
dispatchEvent(event) {
|
|
121
|
-
return this.#abortController.signal.dispatchEvent(event);
|
|
80
|
+
return this;
|
|
122
81
|
}
|
|
123
82
|
|
|
124
83
|
/**
|
|
125
|
-
*
|
|
84
|
+
* Adds an event listener for the 'timeout' event.
|
|
126
85
|
*
|
|
127
|
-
* @
|
|
128
|
-
* @
|
|
129
|
-
* @param {EventListener} listener The listener to remove
|
|
130
|
-
* @returns {void}
|
|
86
|
+
* @param {EventListener} listener The listener to add
|
|
87
|
+
* @returns {AbortSignal} The AbortSignal
|
|
131
88
|
*/
|
|
132
|
-
|
|
133
|
-
this.#abortController.signal.
|
|
89
|
+
onTimeout(listener) {
|
|
90
|
+
this.#abortController.signal.addEventListener(SignalEvents.TIMEOUT, listener);
|
|
91
|
+
|
|
92
|
+
return this;
|
|
134
93
|
}
|
|
135
94
|
|
|
136
95
|
/**
|
|
@@ -160,16 +119,4 @@ export default class AbortSignal extends EventTarget {
|
|
|
160
119
|
this.#abortController.signal.dispatchEvent(event);
|
|
161
120
|
}
|
|
162
121
|
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Generates a timeout event.
|
|
166
|
-
*
|
|
167
|
-
* @private
|
|
168
|
-
* @static
|
|
169
|
-
* @param {number} timeout The timeout in milliseconds
|
|
170
|
-
* @returns {CustomEvent} The timeout event
|
|
171
|
-
*/
|
|
172
|
-
static #generateTimeoutEvent(timeout) {
|
|
173
|
-
return new CustomEvent(SignalEvents.TIMEOUT, { detail: { timeout, cause: new DOMException(`The request timed-out after ${timeout / 1000} seconds`, 'TimeoutError') } });
|
|
174
|
-
}
|
|
175
122
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import HttpRequestMethod from './http-request-methods.js';
|
|
2
|
+
import ResponseStatus from './response-status.js';
|
|
2
3
|
import { MediaType } from '@d1g1tal/media-type';
|
|
3
4
|
import HttpMediaType from './http-media-type.js';
|
|
4
5
|
|
|
@@ -47,4 +48,14 @@ const _abortEvent = new CustomEvent(SignalEvents.ABORT, { detail: { cause: new D
|
|
|
47
48
|
|
|
48
49
|
const requestBodyMethods = [ HttpRequestMethod.POST, HttpRequestMethod.PUT, HttpRequestMethod.PATCH ];
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
const internalServerError = new ResponseStatus(500, 'Internal Server Error');
|
|
52
|
+
|
|
53
|
+
const eventResponseStatuses = Object.freeze({
|
|
54
|
+
[RequestEvents.ABORTED]: new ResponseStatus(499, 'Aborted'),
|
|
55
|
+
[RequestEvents.TIMEOUT]: new ResponseStatus(504, 'Request Timeout')
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** @type {ProxyHandler<import('./transportr.js').RequestOptions>} */
|
|
59
|
+
const abortSignalProxyHandler = { get: (target, property) => property == 'signal' ? target.signal.timeout(target.timeout) : Reflect.get(target, property) };
|
|
60
|
+
|
|
61
|
+
export { defaultCharset, endsWithSlashRegEx, mediaTypes, RequestEvents, SignalEvents, _abortEvent as abortEvent, requestBodyMethods, internalServerError, eventResponseStatuses, abortSignalProxyHandler };
|
package/src/transportr.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import SetMultiMap from '@d1g1tal/collections/set-multi-map.js';
|
|
2
2
|
import Subscribr from '@d1g1tal/subscribr';
|
|
3
|
+
import AbortSignal from './abort-signal.js';
|
|
3
4
|
import HttpError from './http-error.js';
|
|
4
5
|
import HttpMediaType from './http-media-type.js';
|
|
5
6
|
import HttpRequestHeader from './http-request-headers.js';
|
|
6
7
|
import HttpRequestMethod from './http-request-methods.js';
|
|
7
8
|
import HttpResponseHeader from './http-response-headers.js';
|
|
8
|
-
import ResponseStatus from './response-status.js';
|
|
9
9
|
import ParameterMap from './parameter-map.js';
|
|
10
|
-
import
|
|
10
|
+
import ResponseStatus from './response-status.js';
|
|
11
11
|
import { MediaType } from '@d1g1tal/media-type';
|
|
12
|
-
import { _objectMerge,
|
|
13
|
-
import {
|
|
12
|
+
import { _objectMerge, _type } from '@d1g1tal/chrysalis';
|
|
13
|
+
import { RequestEvents, abortEvent, abortSignalProxyHandler, endsWithSlashRegEx, eventResponseStatuses, internalServerError, mediaTypes, requestBodyMethods } from './constants.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @template T extends ResponseBody
|
|
@@ -272,26 +272,6 @@ export default class Transportr {
|
|
|
272
272
|
window: null
|
|
273
273
|
});
|
|
274
274
|
|
|
275
|
-
/**
|
|
276
|
-
* @private
|
|
277
|
-
* @static
|
|
278
|
-
* @type {Map<TransportrEvent, ResponseStatus>}
|
|
279
|
-
*/
|
|
280
|
-
static #eventResponseStatuses = new Map([
|
|
281
|
-
[RequestEvents.ABORTED, new ResponseStatus(499, 'Aborted')],
|
|
282
|
-
[RequestEvents.TIMEOUT, new ResponseStatus(504, 'Gateway Timeout')]
|
|
283
|
-
]);
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Returns a {@link AbortSignal} used for aborting requests.
|
|
287
|
-
*
|
|
288
|
-
* @static
|
|
289
|
-
* @returns {AbortSignal} A new {@link AbortSignal} instance.
|
|
290
|
-
*/
|
|
291
|
-
static abortSignal() {
|
|
292
|
-
return new AbortSignal();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
275
|
/**
|
|
296
276
|
* Returns a {@link EventRegistration} used for subscribing to global events.
|
|
297
277
|
*
|
|
@@ -597,12 +577,12 @@ export default class Transportr {
|
|
|
597
577
|
* @async
|
|
598
578
|
* @param {string} [path] The path to the endpoint you want to call.
|
|
599
579
|
* @param {RequestOptions} [userOptions] The options passed to the public function to use for the request.
|
|
600
|
-
* @param {RequestOptions} [options] The options for the request.
|
|
580
|
+
* @param {RequestOptions} [options={}] The options for the request.
|
|
601
581
|
* @param {ResponseHandler<ResponseBody>} [responseHandler] A function that will be called with the response object.
|
|
602
582
|
* @returns {Promise<ResponseBody>} The result of the #request method.
|
|
603
583
|
*/
|
|
604
|
-
async #get(path, userOptions, options, responseHandler) {
|
|
605
|
-
return this.#request(path, userOptions,
|
|
584
|
+
async #get(path, userOptions, options = {}, responseHandler) {
|
|
585
|
+
return this.#request(path, userOptions, Object.assign(options, { method: HttpRequestMethod.GET }), responseHandler);
|
|
606
586
|
}
|
|
607
587
|
|
|
608
588
|
/**
|
|
@@ -621,38 +601,30 @@ export default class Transportr {
|
|
|
621
601
|
async #request(path, userOptions = {}, options = {}, responseHandler) {
|
|
622
602
|
if (_type(path) == Object) { [ path, userOptions ] = [ undefined, path ] }
|
|
623
603
|
|
|
624
|
-
|
|
625
|
-
options = this.#processRequestOptions(userOptions, options);
|
|
626
|
-
} catch (cause) {
|
|
627
|
-
return Promise.reject(new HttpError('Unable to process request options', { cause }));
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
this.#publish({ name: RequestEvents.CONFIGURED, data: options, global: options.global });
|
|
604
|
+
options = this.#processRequestOptions(userOptions, options);
|
|
631
605
|
|
|
606
|
+
let response;
|
|
632
607
|
const url = Transportr.#createUrl(this.#baseUrl, path, options.searchParams);
|
|
633
|
-
if (_type(options.signal) != AbortSignal) { options.signal = new AbortSignal(options.signal) }
|
|
634
|
-
options.signal.addEventListener(SignalEvents.ABORT, (event) => this.#publish({ name: RequestEvents.ABORTED, event, global: options.global }));
|
|
635
|
-
options.signal.addEventListener(SignalEvents.TIMEOUT, (event) => this.#publish({ name: RequestEvents.TIMEOUT, event, global: options.global }));
|
|
636
|
-
|
|
637
|
-
Transportr.#activeRequests.add(options.signal);
|
|
638
|
-
|
|
639
|
-
let response, result;
|
|
640
608
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
609
|
+
Transportr.#activeRequests.add(options.signal);
|
|
610
|
+
// Proxy the options and trap for the `signal` to be accessed to start the timeout timer
|
|
611
|
+
response = await fetch(url, new Proxy(options, abortSignalProxyHandler));
|
|
643
612
|
|
|
644
|
-
if (!responseHandler && response.status != 204
|
|
613
|
+
if (!responseHandler && response.status != 204) {
|
|
645
614
|
responseHandler = this.#getResponseHandler(response.headers.get(HttpResponseHeader.CONTENT_TYPE));
|
|
646
615
|
}
|
|
647
616
|
|
|
648
|
-
result = await responseHandler?.(response) ?? response;
|
|
617
|
+
const result = await responseHandler?.(response) ?? response;
|
|
649
618
|
|
|
650
619
|
if (!response.ok) {
|
|
651
620
|
return Promise.reject(this.#handleError(url, { status: Transportr.#generateResponseStatusFromError('ResponseError', response), entity: result }));
|
|
652
621
|
}
|
|
622
|
+
|
|
653
623
|
this.#publish({ name: RequestEvents.SUCCESS, data: result, global: options.global });
|
|
654
|
-
|
|
655
|
-
return
|
|
624
|
+
|
|
625
|
+
return result;
|
|
626
|
+
} catch (cause) {
|
|
627
|
+
return Promise.reject(this.#handleError(url, { cause, status: Transportr.#generateResponseStatusFromError(cause.name, response) }));
|
|
656
628
|
} finally {
|
|
657
629
|
options.signal.clearTimeout();
|
|
658
630
|
if (!options.signal.aborted) {
|
|
@@ -665,8 +637,6 @@ export default class Transportr {
|
|
|
665
637
|
}
|
|
666
638
|
}
|
|
667
639
|
}
|
|
668
|
-
|
|
669
|
-
return result;
|
|
670
640
|
}
|
|
671
641
|
|
|
672
642
|
/**
|
|
@@ -742,23 +712,13 @@ export default class Transportr {
|
|
|
742
712
|
requestOptions.body = undefined;
|
|
743
713
|
}
|
|
744
714
|
|
|
745
|
-
|
|
746
|
-
|
|
715
|
+
requestOptions.signal = new AbortSignal(requestOptions.signal)
|
|
716
|
+
.onAbort((event) => this.#publish({ name: RequestEvents.ABORTED, event, global: requestOptions.global }))
|
|
717
|
+
.onTimeout((event) => this.#publish({ name: RequestEvents.TIMEOUT, event, global: requestOptions.global }));
|
|
747
718
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
*
|
|
752
|
-
* @private
|
|
753
|
-
* @static
|
|
754
|
-
* @param {number} timeout The timeout in milliseconds before the signal is aborted.
|
|
755
|
-
* @returns {Proxy<RequestOptions>} A proxy for the options object.
|
|
756
|
-
*/
|
|
757
|
-
static #requestOptionsProxyHandler(timeout) {
|
|
758
|
-
return (target, property) => {
|
|
759
|
-
const value = Reflect.get(target, property);
|
|
760
|
-
return property == 'signal' && !value.aborted ? value.withTimeout(timeout) : value;
|
|
761
|
-
};
|
|
719
|
+
this.#publish({ name: RequestEvents.CONFIGURED, data: requestOptions, global: requestOptions.global });
|
|
720
|
+
|
|
721
|
+
return requestOptions;
|
|
762
722
|
}
|
|
763
723
|
|
|
764
724
|
/**
|
|
@@ -809,9 +769,9 @@ export default class Transportr {
|
|
|
809
769
|
*/
|
|
810
770
|
static #generateResponseStatusFromError(errorName, response) {
|
|
811
771
|
switch (errorName) {
|
|
812
|
-
case 'AbortError': return
|
|
813
|
-
case 'TimeoutError': return
|
|
814
|
-
default: return response ? new ResponseStatus(response.status, response.statusText) :
|
|
772
|
+
case 'AbortError': return eventResponseStatuses[RequestEvents.ABORTED];
|
|
773
|
+
case 'TimeoutError': return eventResponseStatuses[RequestEvents.TIMEOUT];
|
|
774
|
+
default: return response ? new ResponseStatus(response.status, response.statusText) : internalServerError;
|
|
815
775
|
}
|
|
816
776
|
}
|
|
817
777
|
|