@d1g1tal/transportr 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d1g1tal/transportr",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "JavaScript wrapper for the Fetch API",
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,11 +44,11 @@
44
44
  "devDependencies": {
45
45
  "@skypack/package-check": "^0.2.2",
46
46
  "@xmldom/xmldom": "^0.8.10",
47
- "esbuild": "^0.19.0",
48
- "eslint": "^8.46.0",
49
- "eslint-plugin-compat": "^4.1.4",
50
- "eslint-plugin-jsdoc": "^46.4.6",
51
- "jest": "^29.6.2",
47
+ "esbuild": "^0.19.3",
48
+ "eslint": "^8.49.0",
49
+ "eslint-plugin-compat": "^4.2.0",
50
+ "eslint-plugin-jsdoc": "^46.8.1",
51
+ "jest": "^29.7.0",
52
52
  "rimraf": "^5.0.1"
53
53
  },
54
54
  "dependencies": {
@@ -0,0 +1,175 @@
1
+ import { SignalEvents, abortEvent } from './constants.js';
2
+
3
+ const NativeAbortSignal = globalThis.AbortSignal;
4
+
5
+ /**
6
+ * @typedef {function(Event):void} EventListener
7
+ * @param {Event} event The event object
8
+ */
9
+
10
+ export default class AbortSignal extends EventTarget {
11
+ /** @type {AbortController} */
12
+ #abortController;
13
+ /** @type {number} */
14
+ #timeoutId;
15
+
16
+ /**
17
+ * @param {AbortSignal} signal The signal to listen to
18
+ */
19
+ constructor(signal) {
20
+ super();
21
+ this.#abortController = new AbortController();
22
+ signal?.addEventListener(SignalEvents.ABORT, (event) => this.#abort(event));
23
+ }
24
+
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
+ /**
47
+ * The aborted property is a Boolean that indicates whether the request has been aborted (true) or not (false).
48
+ *
49
+ * @returns {boolean} Whether the signal was aborted or not
50
+ */
51
+ get aborted() {
52
+ return this.#abortController.signal.aborted;
53
+ }
54
+
55
+ /**
56
+ * The reason property returns a DOMException object indicating the reason the operation was aborted, or null if the operation is not aborted.
57
+ *
58
+ * @returns {DOMException} The reason the signal was aborted
59
+ */
60
+ get reason() {
61
+ return this.#abortController.signal.reason;
62
+ }
63
+
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
+ /**
74
+ * Returns an AbortSignal instance that will be aborted when the provided amount of milliseconds have passed.
75
+ * A value of -1 (which is the default) means there is no timeout.
76
+ * Note: You can't set this property to a value less than 0.
77
+ *
78
+ * @param {number} timeout The timeout in milliseconds
79
+ * @returns {AbortSignal} The abort signal
80
+ */
81
+ withTimeout(timeout) {
82
+ if (timeout < 0) {
83
+ throw new RangeError('The timeout cannot be negative');
84
+ }
85
+
86
+ this.#timeoutId ??= setTimeout(() => this.#abort(AbortSignal.#generateTimeoutEvent(timeout), true), timeout);
87
+
88
+ return this.#abortController.signal;
89
+ }
90
+
91
+ /**
92
+ * Clears the timeout.
93
+ * Note: This does not abort the signal, dispatch the timeout event, or reset the timeout.
94
+ *
95
+ * @returns {void}
96
+ */
97
+ clearTimeout() {
98
+ clearTimeout(this.#timeoutId);
99
+ }
100
+
101
+ /**
102
+ * Adds an event listener for the specified event type.
103
+ *
104
+ * @override
105
+ * @param {string} eventName The name of the event to listen to
106
+ * @param {EventListener} listener The listener to add
107
+ * @returns {void}
108
+ */
109
+ addEventListener(eventName, listener) {
110
+ this.#abortController.signal.addEventListener(eventName, listener);
111
+ }
112
+
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);
122
+ }
123
+
124
+ /**
125
+ * Removes an event listener for the specified event type.
126
+ *
127
+ * @override
128
+ * @param {string} eventName The name of the event to listen to
129
+ * @param {EventListener} listener The listener to remove
130
+ * @returns {void}
131
+ */
132
+ removeEventListener(eventName, listener) {
133
+ this.#abortController.signal.removeEventListener(eventName, listener);
134
+ }
135
+
136
+ /**
137
+ * Aborts the signal. This is so naughty. ¯\_(ツ)_/¯
138
+ *
139
+ * @param {Event} event The event to abort with
140
+ * @returns {void}
141
+ */
142
+ abort(event) {
143
+ this.#abort(event);
144
+ }
145
+
146
+ /**
147
+ * Aborts the signal.
148
+ *
149
+ * @private
150
+ * @param {Event} event The event to abort with
151
+ * @param {boolean} [dispatchEvent = false] Whether to dispatch the event or not
152
+ * @returns {void}
153
+ * @fires SignalEvents.ABORT When the signal is aborted
154
+ * @fires SignalEvents.TIMEOUT When the signal times out
155
+ */
156
+ #abort(event = abortEvent, dispatchEvent = false) {
157
+ clearTimeout(this.#timeoutId);
158
+ this.#abortController.abort(event.detail?.cause);
159
+ if (dispatchEvent) {
160
+ this.#abortController.signal.dispatchEvent(event);
161
+ }
162
+ }
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
+ }
@@ -0,0 +1,50 @@
1
+ import HttpRequestMethod from './http-request-methods.js';
2
+ import { MediaType } from '@d1g1tal/media-type';
3
+ import HttpMediaType from './http-media-type.js';
4
+
5
+ /** @typedef {'configured'|'success'|'error'|'aborted'|'timeout'|'complete'} RequestEvent */
6
+
7
+ /** @type {string} */
8
+ const defaultCharset = 'utf-8';
9
+
10
+ /** @type {RegExp} */
11
+ const endsWithSlashRegEx = /\/$/;
12
+
13
+ /** @typedef {Map<string, MediaType>} MediaTypeMap A map of media types. */
14
+
15
+ /** @type {MediaTypeMap} */
16
+ const mediaTypes = new Map([
17
+ [HttpMediaType.PNG, new MediaType(HttpMediaType.PNG)],
18
+ [HttpMediaType.TEXT, new MediaType(HttpMediaType.TEXT, { defaultCharset })],
19
+ [HttpMediaType.JSON, new MediaType(HttpMediaType.JSON, { defaultCharset })],
20
+ [HttpMediaType.HTML, new MediaType(HttpMediaType.HTML, { defaultCharset })],
21
+ [HttpMediaType.JAVA_SCRIPT, new MediaType(HttpMediaType.JAVA_SCRIPT, { defaultCharset })],
22
+ [HttpMediaType.CSS, new MediaType(HttpMediaType.CSS, { defaultCharset })],
23
+ [HttpMediaType.XML, new MediaType(HttpMediaType.XML, { defaultCharset })],
24
+ [HttpMediaType.BIN, new MediaType(HttpMediaType.BIN)]
25
+ ]);
26
+
27
+ /**
28
+ * @static
29
+ * @constant {Object<string, RequestEvent>}
30
+ */
31
+ const RequestEvents = Object.freeze({
32
+ CONFIGURED: 'configured',
33
+ SUCCESS: 'success',
34
+ ERROR: 'error',
35
+ ABORTED: 'aborted',
36
+ TIMEOUT: 'timeout',
37
+ COMPLETE: 'complete',
38
+ ALL_COMPLETE: 'all-complete'
39
+ });
40
+
41
+ const SignalEvents = Object.freeze({
42
+ ABORT: 'abort',
43
+ TIMEOUT: 'timeout'
44
+ });
45
+
46
+ const _abortEvent = new CustomEvent(SignalEvents.ABORT, { detail: { cause: new DOMException('The request was aborted', 'AbortError') } });
47
+
48
+ const requestBodyMethods = [ HttpRequestMethod.POST, HttpRequestMethod.PUT, HttpRequestMethod.PATCH ];
49
+
50
+ export { defaultCharset, endsWithSlashRegEx, mediaTypes, RequestEvents, SignalEvents, _abortEvent as abortEvent, requestBodyMethods };
@@ -61,6 +61,8 @@ const HttpMediaType = {
61
61
  JSON: 'application/json',
62
62
  /** JavaScript Object Notation LD Format */
63
63
  JSON_LD: 'application/ld+json',
64
+ /** JavaScript Object Notation (JSON) Merge Patch */
65
+ JSON_MERGE_PATCH: 'application/merge-patch+json',
64
66
  /** Musical Instrument Digital Interface (MIDI) */
65
67
  MID: 'audio/midi',
66
68
  /** Musical Instrument Digital Interface (MIDI) */
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @typedef {Map<string, Array<*>>} ParameterMap
3
+ * @extends Map
4
+ */
5
+
6
+ /**
7
+ * A {@link Map} that can contain multiple, unique, values for the same key.
8
+ *
9
+ * @type {ParameterMap<string, Array<*>>}
10
+ */
11
+ export default class ParameterMap extends Map {
12
+ /**
13
+ * @param {Iterable<[string, *]>|Object} parameters The initial parameters to set.
14
+ * @returns {ParameterMap<string, *>} The ParameterMap with the updated key and value.
15
+ */
16
+ constructor(parameters = {}) {
17
+ super();
18
+ for (const [key, value] of ParameterMap.#entries(parameters)) {
19
+ this.append(key, value);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Adds a new element with a specified key and value to the ParameterMap.
25
+ * If an element with the same key already exists, the value will be replaced in the underlying {@link Set}.
26
+ *
27
+ * @override
28
+ * @param {string} key The key to set.
29
+ * @param {*} value The value to add to the ParameterMap
30
+ * @returns {ParameterMap<string, *>} The ParameterMap with the updated key and value.
31
+ */
32
+ set(key, value) {
33
+ const array = super.get(key);
34
+ if (array?.length > 0) {
35
+ array.length = 0;
36
+ array[0] = value;
37
+ } else {
38
+ super.set(key, [value]);
39
+ }
40
+
41
+ return this;
42
+ }
43
+
44
+ /**
45
+ * Adds all key-value pairs from an iterable or object to the ParameterMap.
46
+ * If a key already exists, the value will be replaced in the underlying {@link Array}.
47
+ *
48
+ * @param {Iterable<[string, *]>|Object} parameters The parameters to set.
49
+ * @returns {ParameterMap<string, *>} The ParameterMap with the updated key and value.
50
+ */
51
+ setAll(parameters) {
52
+ for (const [key, value] of ParameterMap.#entries(parameters)) {
53
+ this.set(key, value);
54
+ }
55
+
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Returns the value associated to the key, or undefined if there is none.
61
+ * If the key has multiple values, the first value will be returned.
62
+ * If the key has no values, undefined will be returned.
63
+ *
64
+ * @override
65
+ * @param {string} key The key to get.
66
+ * @returns {*} The value associated to the key, or undefined if there is none.
67
+ */
68
+ get(key) {
69
+ return super.get(key)?.[0];
70
+ }
71
+
72
+ /**
73
+ * Returns an array of all values associated to the key, or undefined if there are none.
74
+ *
75
+ * @param {string} key The key to get.
76
+ * @returns {Array<*>} An array of all values associated to the key, or undefined if there are none.
77
+ */
78
+ getAll(key) {
79
+ return super.get(key);
80
+ }
81
+
82
+ /**
83
+ * Appends a new value to an existing key inside a ParameterMap, or adds the new key if it does not exist.
84
+ *
85
+ * @param {string} key The key to append.
86
+ * @param {*} value The value to append.
87
+ * @returns {ParameterMap<string, *>} The ParameterMap with the updated key and value to allow chaining.
88
+ */
89
+ append(key, value) {
90
+ const array = super.get(key);
91
+ if (array?.length > 0) {
92
+ array.push(value);
93
+ } else {
94
+ super.set(key, [value]);
95
+ }
96
+
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Appends all key-value pairs from an iterable or object to the ParameterMap.
102
+ * If a key already exists, the value will be appended to the underlying {@link Array}.
103
+ * If a key does not exist, the key and value will be added to the ParameterMap.
104
+ *
105
+ * @param {Iterable<[string, *]>|Object} parameters The parameters to append.
106
+ * @returns {ParameterMap<string, *>} The ParameterMap with the updated key and value.
107
+ */
108
+ appendAll(parameters) {
109
+ for (const [key, value] of ParameterMap.#entries(parameters)) {
110
+ this.append(key, value);
111
+ }
112
+
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Checks if a specific key has a specific value.
118
+ *
119
+ * @param {*} value The value to check.
120
+ * @returns {boolean} True if the key has the value, false otherwise.
121
+ */
122
+ hasValue(value) {
123
+ for (const array of super.values()) {
124
+ if (array.includes(value)) {
125
+ return true;
126
+ }
127
+ }
128
+
129
+ return false;
130
+ }
131
+
132
+ /**
133
+ * Removes a specific value from a specific key.
134
+ *
135
+ * @param {*} value The value to remove.
136
+ * @returns {boolean} True if the value was removed, false otherwise.
137
+ */
138
+ deleteValue(value) {
139
+ for (const array of this.values()) {
140
+ if (array.includes(value)) {
141
+ return array.splice(array.indexOf(value), 1).length > 0;
142
+ }
143
+ }
144
+
145
+ return false;
146
+ }
147
+
148
+ /**
149
+ * Determines whether the ParameterMap contains anything.
150
+ *
151
+ * @returns {boolean} True if the ParameterMap size is greater than 0, false otherwise.
152
+ */
153
+ isEmpty() {
154
+ return this.size === 0;
155
+ }
156
+
157
+ /**
158
+ * Returns an Object that can be serialized to JSON.
159
+ * If a key has only one value, the value will be a single value.
160
+ * If a key has multiple values, the value will be an array of values.
161
+ * If a key has no values, the value will be undefined.
162
+ *
163
+ * @override
164
+ * @returns {Object} The Object to be serialized to JSON.
165
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON_behavior}
166
+ */
167
+ toJSON() {
168
+ const obj = Object.create(null);
169
+
170
+ for (const [key, values] of super.entries()) {
171
+ obj[key] = values.length === 1 ? values[0] : values;
172
+ }
173
+
174
+ return obj;
175
+ }
176
+
177
+ /**
178
+ * Returns an iterator that yields all key-value pairs in the map as arrays in their insertion order.
179
+ *
180
+ * @override
181
+ * @yields {[string, *]} An iterator for the key-value pairs in the map.
182
+ */
183
+ *entries() {
184
+ for (const [key, array] of super.entries()) {
185
+ for (const value of array) { yield [key, value] }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Returns an iterator that yields all key-value pairs in the map as arrays in their insertion order.
191
+ *
192
+ * @override
193
+ * @yields {[string, *]} An iterator for the key-value pairs in the map.
194
+ */
195
+ *[Symbol.iterator]() {
196
+ yield* this.entries();
197
+ }
198
+
199
+ /**
200
+ * Returns an iterable of key, value pairs for every entry in the parameters object.
201
+ *
202
+ * @private
203
+ * @static
204
+ * @param {Iterable<[string, *]>|Object} parameters The parameters to set.
205
+ * @returns {Iterable<[string, *]>} An iterable of key, value pairs for every entry in the parameters object.
206
+ */
207
+ static #entries(parameters) {
208
+ return parameters[Symbol.iterator] ? parameters : Object.entries(parameters);
209
+ }
210
+
211
+ /**
212
+ * A String value that is used in the creation of the default string description of an object.
213
+ * Called by the built-in method {@link Object.prototype.toString}.
214
+ *
215
+ * @override
216
+ * @returns {string} The default string description of this object.
217
+ */
218
+ get [Symbol.toStringTag]() {
219
+ return 'ParameterMap';
220
+ }
221
+ }