@d1g1tal/transportr 1.3.2 → 1.4.1
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 +1510 -1640
- package/dist/transportr.min.js +2 -2
- package/dist/transportr.min.js.map +4 -4
- package/package.json +3 -4
- package/src/abort-signal.js +17 -70
- package/src/constants.js +12 -1
- package/src/transportr.js +30 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d1g1tal/transportr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "JavaScript wrapper for the Fetch API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,15 +45,14 @@
|
|
|
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.5"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@d1g1tal/chrysalis": "^1.2.3",
|
|
56
|
-
"@d1g1tal/collections": "^0.2.3",
|
|
57
56
|
"@d1g1tal/media-type": "^4.1.0",
|
|
58
57
|
"@d1g1tal/subscribr": "^3.0.1"
|
|
59
58
|
},
|
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,15 @@
|
|
|
1
|
-
import SetMultiMap from '@d1g1tal/collections/set-multi-map.js';
|
|
2
1
|
import Subscribr from '@d1g1tal/subscribr';
|
|
2
|
+
import AbortSignal from './abort-signal.js';
|
|
3
3
|
import HttpError from './http-error.js';
|
|
4
4
|
import HttpMediaType from './http-media-type.js';
|
|
5
5
|
import HttpRequestHeader from './http-request-headers.js';
|
|
6
6
|
import HttpRequestMethod from './http-request-methods.js';
|
|
7
7
|
import HttpResponseHeader from './http-response-headers.js';
|
|
8
|
-
import ResponseStatus from './response-status.js';
|
|
9
8
|
import ParameterMap from './parameter-map.js';
|
|
10
|
-
import
|
|
9
|
+
import ResponseStatus from './response-status.js';
|
|
11
10
|
import { MediaType } from '@d1g1tal/media-type';
|
|
12
|
-
import { _objectMerge,
|
|
13
|
-
import {
|
|
11
|
+
import { _objectMerge, _type } from '@d1g1tal/chrysalis';
|
|
12
|
+
import { RequestEvents, abortEvent, abortSignalProxyHandler, endsWithSlashRegEx, eventResponseStatuses, internalServerError, mediaTypes, requestBodyMethods } from './constants.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* @template T extends ResponseBody
|
|
@@ -132,8 +131,8 @@ export default class Transportr {
|
|
|
132
131
|
static #globalSubscribr = new Subscribr();
|
|
133
132
|
/** @type {Set<AbortSignal>} */
|
|
134
133
|
static #activeRequests = new Set();
|
|
135
|
-
/** @type {
|
|
136
|
-
static #contentTypeHandlers = new
|
|
134
|
+
/** @type {Map<ResponseHandler<ResponseBody>, string>} */
|
|
135
|
+
static #contentTypeHandlers = new Map([
|
|
137
136
|
[_handleImage, mediaTypes.get(HttpMediaType.PNG).type],
|
|
138
137
|
[_handleText, mediaTypes.get(HttpMediaType.TEXT).type],
|
|
139
138
|
[_handleJson, mediaTypes.get(HttpMediaType.JSON).subtype],
|
|
@@ -272,26 +271,6 @@ export default class Transportr {
|
|
|
272
271
|
window: null
|
|
273
272
|
});
|
|
274
273
|
|
|
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
274
|
/**
|
|
296
275
|
* Returns a {@link EventRegistration} used for subscribing to global events.
|
|
297
276
|
*
|
|
@@ -597,12 +576,12 @@ export default class Transportr {
|
|
|
597
576
|
* @async
|
|
598
577
|
* @param {string} [path] The path to the endpoint you want to call.
|
|
599
578
|
* @param {RequestOptions} [userOptions] The options passed to the public function to use for the request.
|
|
600
|
-
* @param {RequestOptions} [options] The options for the request.
|
|
579
|
+
* @param {RequestOptions} [options={}] The options for the request.
|
|
601
580
|
* @param {ResponseHandler<ResponseBody>} [responseHandler] A function that will be called with the response object.
|
|
602
581
|
* @returns {Promise<ResponseBody>} The result of the #request method.
|
|
603
582
|
*/
|
|
604
|
-
async #get(path, userOptions, options, responseHandler) {
|
|
605
|
-
return this.#request(path, userOptions,
|
|
583
|
+
async #get(path, userOptions, options = {}, responseHandler) {
|
|
584
|
+
return this.#request(path, userOptions, Object.assign(options, { method: HttpRequestMethod.GET }), responseHandler);
|
|
606
585
|
}
|
|
607
586
|
|
|
608
587
|
/**
|
|
@@ -621,38 +600,30 @@ export default class Transportr {
|
|
|
621
600
|
async #request(path, userOptions = {}, options = {}, responseHandler) {
|
|
622
601
|
if (_type(path) == Object) { [ path, userOptions ] = [ undefined, path ] }
|
|
623
602
|
|
|
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 });
|
|
603
|
+
options = this.#processRequestOptions(userOptions, options);
|
|
631
604
|
|
|
605
|
+
let response;
|
|
632
606
|
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
607
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
608
|
+
Transportr.#activeRequests.add(options.signal);
|
|
609
|
+
// Proxy the options and trap for the `signal` to be accessed to start the timeout timer
|
|
610
|
+
response = await fetch(url, new Proxy(options, abortSignalProxyHandler));
|
|
643
611
|
|
|
644
|
-
if (!responseHandler && response.status != 204
|
|
612
|
+
if (!responseHandler && response.status != 204) {
|
|
645
613
|
responseHandler = this.#getResponseHandler(response.headers.get(HttpResponseHeader.CONTENT_TYPE));
|
|
646
614
|
}
|
|
647
615
|
|
|
648
|
-
result = await responseHandler?.(response) ?? response;
|
|
616
|
+
const result = await responseHandler?.(response) ?? response;
|
|
649
617
|
|
|
650
618
|
if (!response.ok) {
|
|
651
619
|
return Promise.reject(this.#handleError(url, { status: Transportr.#generateResponseStatusFromError('ResponseError', response), entity: result }));
|
|
652
620
|
}
|
|
621
|
+
|
|
653
622
|
this.#publish({ name: RequestEvents.SUCCESS, data: result, global: options.global });
|
|
654
|
-
|
|
655
|
-
return
|
|
623
|
+
|
|
624
|
+
return result;
|
|
625
|
+
} catch (cause) {
|
|
626
|
+
return Promise.reject(this.#handleError(url, { cause, status: Transportr.#generateResponseStatusFromError(cause.name, response) }));
|
|
656
627
|
} finally {
|
|
657
628
|
options.signal.clearTimeout();
|
|
658
629
|
if (!options.signal.aborted) {
|
|
@@ -665,8 +636,6 @@ export default class Transportr {
|
|
|
665
636
|
}
|
|
666
637
|
}
|
|
667
638
|
}
|
|
668
|
-
|
|
669
|
-
return result;
|
|
670
639
|
}
|
|
671
640
|
|
|
672
641
|
/**
|
|
@@ -742,23 +711,13 @@ export default class Transportr {
|
|
|
742
711
|
requestOptions.body = undefined;
|
|
743
712
|
}
|
|
744
713
|
|
|
745
|
-
|
|
746
|
-
|
|
714
|
+
requestOptions.signal = new AbortSignal(requestOptions.signal)
|
|
715
|
+
.onAbort((event) => this.#publish({ name: RequestEvents.ABORTED, event, global: requestOptions.global }))
|
|
716
|
+
.onTimeout((event) => this.#publish({ name: RequestEvents.TIMEOUT, event, global: requestOptions.global }));
|
|
747
717
|
|
|
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
|
-
};
|
|
718
|
+
this.#publish({ name: RequestEvents.CONFIGURED, data: requestOptions, global: requestOptions.global });
|
|
719
|
+
|
|
720
|
+
return requestOptions;
|
|
762
721
|
}
|
|
763
722
|
|
|
764
723
|
/**
|
|
@@ -809,9 +768,9 @@ export default class Transportr {
|
|
|
809
768
|
*/
|
|
810
769
|
static #generateResponseStatusFromError(errorName, response) {
|
|
811
770
|
switch (errorName) {
|
|
812
|
-
case 'AbortError': return
|
|
813
|
-
case 'TimeoutError': return
|
|
814
|
-
default: return response ? new ResponseStatus(response.status, response.statusText) :
|
|
771
|
+
case 'AbortError': return eventResponseStatuses[RequestEvents.ABORTED];
|
|
772
|
+
case 'TimeoutError': return eventResponseStatuses[RequestEvents.TIMEOUT];
|
|
773
|
+
default: return response ? new ResponseStatus(response.status, response.statusText) : internalServerError;
|
|
815
774
|
}
|
|
816
775
|
}
|
|
817
776
|
|