pulse-js-framework 1.7.29 → 1.7.31
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/cli/dev.js +5 -2
- package/cli/index.js +20 -5
- package/compiler/parser.js +6 -1
- package/compiler/sourcemap.js +5 -3
- package/compiler/transformer/constants.js +2 -1
- package/compiler/transformer/expressions.js +117 -16
- package/compiler/transformer/imports.js +5 -0
- package/compiler/transformer/index.js +9 -9
- package/compiler/transformer/state.js +51 -0
- package/compiler/transformer/view.js +118 -20
- package/package.json +13 -2
- package/runtime/async.js +14 -26
- package/runtime/devtools/diagnostics.js +51 -3
- package/runtime/devtools.js +58 -1
- package/runtime/errors.js +159 -0
- package/runtime/graphql.js +83 -113
- package/runtime/http.js +18 -100
- package/runtime/interceptor-manager.js +242 -0
- package/runtime/pulse.js +36 -1
- package/runtime/router.js +80 -15
- package/runtime/utils.js +121 -5
- package/runtime/websocket.js +62 -73
package/runtime/graphql.js
CHANGED
|
@@ -7,23 +7,15 @@ import { pulse, computed, batch, effect, onCleanup } from './pulse.js';
|
|
|
7
7
|
import { createHttp, HttpError } from './http.js';
|
|
8
8
|
import { createWebSocket, WebSocketError } from './websocket.js';
|
|
9
9
|
import { createVersionedAsync } from './async.js';
|
|
10
|
-
import {
|
|
10
|
+
import { ClientError } from './errors.js';
|
|
11
|
+
import { LRUCache } from './lru-cache.js';
|
|
12
|
+
import { InterceptorManager } from './interceptor-manager.js';
|
|
13
|
+
import { onWindowFocus, onWindowOnline } from './utils.js';
|
|
11
14
|
|
|
12
15
|
// ============================================================================
|
|
13
16
|
// Constants
|
|
14
17
|
// ============================================================================
|
|
15
18
|
|
|
16
|
-
const GRAPHQL_SUGGESTIONS = {
|
|
17
|
-
GRAPHQL_ERROR: 'Check the GraphQL errors array for specific field errors.',
|
|
18
|
-
NETWORK_ERROR: 'Verify network connectivity and GraphQL endpoint URL.',
|
|
19
|
-
PARSE_ERROR: 'The response was not valid GraphQL JSON. Check server configuration.',
|
|
20
|
-
TIMEOUT: 'Request timed out. Consider increasing timeout or optimizing query.',
|
|
21
|
-
AUTHENTICATION_ERROR: 'Authentication required. Check your credentials or token.',
|
|
22
|
-
AUTHORIZATION_ERROR: 'Insufficient permissions for this operation.',
|
|
23
|
-
VALIDATION_ERROR: 'Invalid input provided. Check variables match schema types.',
|
|
24
|
-
SUBSCRIPTION_ERROR: 'WebSocket subscription failed. Check connection status.'
|
|
25
|
-
};
|
|
26
|
-
|
|
27
19
|
/**
|
|
28
20
|
* graphql-ws protocol message types
|
|
29
21
|
* @see https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
|
|
@@ -48,10 +40,25 @@ const MessageType = {
|
|
|
48
40
|
// ============================================================================
|
|
49
41
|
|
|
50
42
|
/**
|
|
51
|
-
* Error class for GraphQL operations
|
|
52
|
-
*
|
|
43
|
+
* Error class for GraphQL operations.
|
|
44
|
+
* Extends ClientError for consistent error handling patterns.
|
|
53
45
|
*/
|
|
54
|
-
export class GraphQLError extends
|
|
46
|
+
export class GraphQLError extends ClientError {
|
|
47
|
+
static suggestions = {
|
|
48
|
+
GRAPHQL_ERROR: 'Check the GraphQL errors array for specific field errors.',
|
|
49
|
+
NETWORK_ERROR: 'Verify network connectivity and GraphQL endpoint URL.',
|
|
50
|
+
PARSE_ERROR: 'The response was not valid GraphQL JSON. Check server configuration.',
|
|
51
|
+
TIMEOUT: 'Request timed out. Consider increasing timeout or optimizing query.',
|
|
52
|
+
AUTHENTICATION_ERROR: 'Authentication required. Check your credentials or token.',
|
|
53
|
+
AUTHORIZATION_ERROR: 'Insufficient permissions for this operation.',
|
|
54
|
+
VALIDATION_ERROR: 'Invalid input provided. Check variables match schema types.',
|
|
55
|
+
SUBSCRIPTION_ERROR: 'WebSocket subscription failed. Check connection status.'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
static errorName = 'GraphQLError';
|
|
59
|
+
static defaultCode = 'GRAPHQL_ERROR';
|
|
60
|
+
static markerProperty = 'isGraphQLError';
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* @param {string} message - Error message
|
|
57
64
|
* @param {Object} [options={}] - Error options
|
|
@@ -63,17 +70,7 @@ export class GraphQLError extends RuntimeError {
|
|
|
63
70
|
* @param {Object} [options.request] - Request configuration
|
|
64
71
|
*/
|
|
65
72
|
constructor(message, options = {}) {
|
|
66
|
-
|
|
67
|
-
super(
|
|
68
|
-
createErrorMessage({
|
|
69
|
-
code: options.code || 'GRAPHQL_ERROR',
|
|
70
|
-
message,
|
|
71
|
-
suggestion
|
|
72
|
-
}),
|
|
73
|
-
{ code: options.code || 'GRAPHQL_ERROR', suggestion }
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
this.name = 'GraphQLError';
|
|
73
|
+
super(message, options);
|
|
77
74
|
this.errors = options.errors || [];
|
|
78
75
|
this.data = options.data ?? null;
|
|
79
76
|
this.extensions = options.extensions || {};
|
|
@@ -87,7 +84,7 @@ export class GraphQLError extends RuntimeError {
|
|
|
87
84
|
* @returns {boolean}
|
|
88
85
|
*/
|
|
89
86
|
static isGraphQLError(error) {
|
|
90
|
-
return error instanceof GraphQLError;
|
|
87
|
+
return error?.isGraphQLError === true || error instanceof GraphQLError;
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
/**
|
|
@@ -125,22 +122,6 @@ export class GraphQLError extends RuntimeError {
|
|
|
125
122
|
this.errors.some(e => e.extensions?.code === 'BAD_USER_INPUT');
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
/**
|
|
129
|
-
* Check if this is a network error
|
|
130
|
-
* @returns {boolean}
|
|
131
|
-
*/
|
|
132
|
-
isNetworkError() {
|
|
133
|
-
return this.code === 'NETWORK_ERROR';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Check if this is a timeout error
|
|
138
|
-
* @returns {boolean}
|
|
139
|
-
*/
|
|
140
|
-
isTimeout() {
|
|
141
|
-
return this.code === 'TIMEOUT';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
125
|
/**
|
|
145
126
|
* Get the first error message from GraphQL errors
|
|
146
127
|
* @returns {string|null}
|
|
@@ -231,60 +212,6 @@ function generateCacheKey(query, variables) {
|
|
|
231
212
|
return `gql:${queryHash}${variablesHash ? ':' + variablesHash : ''}`;
|
|
232
213
|
}
|
|
233
214
|
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// Interceptor Manager
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Manages request/response interceptors
|
|
240
|
-
*/
|
|
241
|
-
class InterceptorManager {
|
|
242
|
-
#handlers = new Map();
|
|
243
|
-
#nextId = 0;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Add an interceptor
|
|
247
|
-
* @param {Function} fulfilled - Success handler
|
|
248
|
-
* @param {Function} [rejected] - Error handler
|
|
249
|
-
* @returns {number} Interceptor ID
|
|
250
|
-
*/
|
|
251
|
-
use(fulfilled, rejected) {
|
|
252
|
-
const id = this.#nextId++;
|
|
253
|
-
this.#handlers.set(id, { fulfilled, rejected });
|
|
254
|
-
return id;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Remove an interceptor
|
|
259
|
-
* @param {number} id - Interceptor ID
|
|
260
|
-
*/
|
|
261
|
-
eject(id) {
|
|
262
|
-
this.#handlers.delete(id);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Clear all interceptors
|
|
267
|
-
*/
|
|
268
|
-
clear() {
|
|
269
|
-
this.#handlers.clear();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Get handler count
|
|
274
|
-
* @returns {number}
|
|
275
|
-
*/
|
|
276
|
-
get size() {
|
|
277
|
-
return this.#handlers.size;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Iterate over handlers
|
|
282
|
-
*/
|
|
283
|
-
[Symbol.iterator]() {
|
|
284
|
-
return this.#handlers.values();
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
215
|
// ============================================================================
|
|
289
216
|
// Subscription Manager
|
|
290
217
|
// ============================================================================
|
|
@@ -503,7 +430,8 @@ class GraphQLClient {
|
|
|
503
430
|
#subscriptionManager = null;
|
|
504
431
|
#options;
|
|
505
432
|
#inflightQueries = new Map();
|
|
506
|
-
|
|
433
|
+
// LRU cache to prevent unbounded memory growth (default 500 entries)
|
|
434
|
+
#cache;
|
|
507
435
|
|
|
508
436
|
/**
|
|
509
437
|
* Request interceptors
|
|
@@ -536,12 +464,16 @@ class GraphQLClient {
|
|
|
536
464
|
wsMaxRetries: options.wsMaxRetries ?? 5,
|
|
537
465
|
cache: options.cache ?? true,
|
|
538
466
|
cacheTime: options.cacheTime ?? 300000,
|
|
467
|
+
cacheMaxSize: options.cacheMaxSize ?? 500,
|
|
539
468
|
staleTime: options.staleTime ?? 0,
|
|
540
469
|
dedupe: options.dedupe ?? true,
|
|
541
470
|
throwOnError: options.throwOnError ?? true,
|
|
542
471
|
onError: options.onError
|
|
543
472
|
};
|
|
544
473
|
|
|
474
|
+
// Initialize LRU cache to prevent unbounded memory growth
|
|
475
|
+
this.#cache = new LRUCache(this.#options.cacheMaxSize);
|
|
476
|
+
|
|
545
477
|
// Create HTTP client for queries/mutations
|
|
546
478
|
this.#http = createHttp({
|
|
547
479
|
baseURL: '',
|
|
@@ -863,6 +795,7 @@ class GraphQLClient {
|
|
|
863
795
|
* @param {number} [options.wsMaxRetries=5] - Max WebSocket reconnection attempts
|
|
864
796
|
* @param {boolean} [options.cache=true] - Enable query caching
|
|
865
797
|
* @param {number} [options.cacheTime=300000] - Cache TTL in ms
|
|
798
|
+
* @param {number} [options.cacheMaxSize=500] - Maximum cache entries (LRU eviction)
|
|
866
799
|
* @param {number} [options.staleTime=0] - Stale threshold in ms
|
|
867
800
|
* @param {boolean} [options.dedupe=true] - Deduplicate identical in-flight queries
|
|
868
801
|
* @param {boolean} [options.throwOnError=true] - Throw on GraphQL errors
|
|
@@ -1047,21 +980,13 @@ export function useQuery(query, variables, options = {}) {
|
|
|
1047
980
|
}
|
|
1048
981
|
|
|
1049
982
|
// Setup window focus listener
|
|
1050
|
-
if (options.refetchOnFocus
|
|
1051
|
-
|
|
1052
|
-
if (isEnabled()) executeQuery();
|
|
1053
|
-
};
|
|
1054
|
-
window.addEventListener('focus', handleFocus);
|
|
1055
|
-
onCleanup(() => window.removeEventListener('focus', handleFocus));
|
|
983
|
+
if (options.refetchOnFocus) {
|
|
984
|
+
onWindowFocus(() => { if (isEnabled()) executeQuery(); }, onCleanup);
|
|
1056
985
|
}
|
|
1057
986
|
|
|
1058
987
|
// Setup online listener
|
|
1059
|
-
if (options.refetchOnReconnect
|
|
1060
|
-
|
|
1061
|
-
if (isEnabled()) executeQuery();
|
|
1062
|
-
};
|
|
1063
|
-
window.addEventListener('online', handleOnline);
|
|
1064
|
-
onCleanup(() => window.removeEventListener('online', handleOnline));
|
|
988
|
+
if (options.refetchOnReconnect) {
|
|
989
|
+
onWindowOnline(() => { if (isEnabled()) executeQuery(); }, onCleanup);
|
|
1065
990
|
}
|
|
1066
991
|
|
|
1067
992
|
return {
|
|
@@ -1221,6 +1146,9 @@ export function useMutation(mutation, options = {}) {
|
|
|
1221
1146
|
* @param {Function} [options.onError] - Error callback
|
|
1222
1147
|
* @param {Function} [options.onComplete] - Called when subscription ends
|
|
1223
1148
|
* @param {boolean} [options.shouldResubscribe=true] - Resubscribe on error
|
|
1149
|
+
* @param {number} [options.retryBaseDelay=1000] - Base delay for exponential backoff (ms)
|
|
1150
|
+
* @param {number} [options.retryMaxDelay=30000] - Maximum delay between retries (ms)
|
|
1151
|
+
* @param {number} [options.maxRetries=Infinity] - Maximum number of retry attempts
|
|
1224
1152
|
* @returns {Object} Subscription result with reactive state
|
|
1225
1153
|
*/
|
|
1226
1154
|
export function useSubscription(subscription, variables, options = {}) {
|
|
@@ -1229,8 +1157,30 @@ export function useSubscription(subscription, variables, options = {}) {
|
|
|
1229
1157
|
const data = pulse(null);
|
|
1230
1158
|
const error = pulse(null);
|
|
1231
1159
|
const status = pulse('connecting');
|
|
1160
|
+
const retryCount = pulse(0);
|
|
1232
1161
|
|
|
1233
1162
|
let unsubscribeFn = null;
|
|
1163
|
+
let retryTimeoutId = null;
|
|
1164
|
+
|
|
1165
|
+
// Backoff configuration
|
|
1166
|
+
const retryBaseDelay = options.retryBaseDelay ?? 1000;
|
|
1167
|
+
const retryMaxDelay = options.retryMaxDelay ?? 30000;
|
|
1168
|
+
const maxRetries = options.maxRetries ?? Infinity;
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Calculate delay with exponential backoff and jitter
|
|
1172
|
+
* @param {number} attempt - Current retry attempt (0-indexed)
|
|
1173
|
+
* @returns {number} Delay in milliseconds
|
|
1174
|
+
*/
|
|
1175
|
+
function calculateBackoffDelay(attempt) {
|
|
1176
|
+
// Exponential backoff: baseDelay * 2^attempt
|
|
1177
|
+
const exponentialDelay = retryBaseDelay * Math.pow(2, attempt);
|
|
1178
|
+
// Cap at max delay
|
|
1179
|
+
const cappedDelay = Math.min(exponentialDelay, retryMaxDelay);
|
|
1180
|
+
// Add jitter (±25%) to prevent thundering herd
|
|
1181
|
+
const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
|
|
1182
|
+
return Math.max(0, cappedDelay + jitter);
|
|
1183
|
+
}
|
|
1234
1184
|
|
|
1235
1185
|
// Resolve variables
|
|
1236
1186
|
const resolveVariables = () => {
|
|
@@ -1260,6 +1210,8 @@ export function useSubscription(subscription, variables, options = {}) {
|
|
|
1260
1210
|
onData: (payload) => {
|
|
1261
1211
|
status.set('connected');
|
|
1262
1212
|
data.set(payload);
|
|
1213
|
+
// Reset retry count on successful data
|
|
1214
|
+
retryCount.set(0);
|
|
1263
1215
|
options.onData?.(payload);
|
|
1264
1216
|
},
|
|
1265
1217
|
onError: (err) => {
|
|
@@ -1267,10 +1219,21 @@ export function useSubscription(subscription, variables, options = {}) {
|
|
|
1267
1219
|
status.set('error');
|
|
1268
1220
|
options.onError?.(err);
|
|
1269
1221
|
|
|
1270
|
-
// Resubscribe on error if enabled
|
|
1222
|
+
// Resubscribe on error if enabled and under max retries
|
|
1271
1223
|
if (options.shouldResubscribe !== false) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1224
|
+
const currentRetry = retryCount.peek();
|
|
1225
|
+
if (currentRetry < maxRetries) {
|
|
1226
|
+
unsubscribeFn = null;
|
|
1227
|
+
const delay = calculateBackoffDelay(currentRetry);
|
|
1228
|
+
retryCount.set(currentRetry + 1);
|
|
1229
|
+
status.set('reconnecting');
|
|
1230
|
+
retryTimeoutId = setTimeout(() => {
|
|
1231
|
+
retryTimeoutId = null;
|
|
1232
|
+
subscribe();
|
|
1233
|
+
}, delay);
|
|
1234
|
+
} else {
|
|
1235
|
+
status.set('failed');
|
|
1236
|
+
}
|
|
1274
1237
|
}
|
|
1275
1238
|
},
|
|
1276
1239
|
onComplete: () => {
|
|
@@ -1285,11 +1248,17 @@ export function useSubscription(subscription, variables, options = {}) {
|
|
|
1285
1248
|
* Unsubscribe
|
|
1286
1249
|
*/
|
|
1287
1250
|
function unsubscribe() {
|
|
1251
|
+
// Cancel pending retry
|
|
1252
|
+
if (retryTimeoutId) {
|
|
1253
|
+
clearTimeout(retryTimeoutId);
|
|
1254
|
+
retryTimeoutId = null;
|
|
1255
|
+
}
|
|
1288
1256
|
if (unsubscribeFn) {
|
|
1289
1257
|
unsubscribeFn();
|
|
1290
1258
|
unsubscribeFn = null;
|
|
1291
1259
|
status.set('closed');
|
|
1292
1260
|
}
|
|
1261
|
+
retryCount.set(0);
|
|
1293
1262
|
}
|
|
1294
1263
|
|
|
1295
1264
|
/**
|
|
@@ -1325,6 +1294,7 @@ export function useSubscription(subscription, variables, options = {}) {
|
|
|
1325
1294
|
data,
|
|
1326
1295
|
error,
|
|
1327
1296
|
status,
|
|
1297
|
+
retryCount,
|
|
1328
1298
|
unsubscribe,
|
|
1329
1299
|
resubscribe
|
|
1330
1300
|
};
|
package/runtime/http.js
CHANGED
|
@@ -5,27 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
import { pulse, computed, batch } from './pulse.js';
|
|
7
7
|
import { useAsync, useResource } from './async.js';
|
|
8
|
-
import {
|
|
8
|
+
import { ClientError } from './errors.js';
|
|
9
|
+
import { InterceptorManager } from './interceptor-manager.js';
|
|
9
10
|
|
|
10
11
|
// ============================================================================
|
|
11
12
|
// HTTP Error Class
|
|
12
13
|
// ============================================================================
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* HTTP
|
|
16
|
+
* HTTP Error with request/response context.
|
|
17
|
+
* Extends ClientError for consistent error handling patterns.
|
|
16
18
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
export class HttpError extends ClientError {
|
|
20
|
+
static suggestions = {
|
|
21
|
+
TIMEOUT: 'Consider increasing the timeout or checking network conditions.',
|
|
22
|
+
NETWORK: 'Check internet connectivity and ensure the server is reachable.',
|
|
23
|
+
ABORT: 'Request was cancelled. This is usually intentional.',
|
|
24
|
+
HTTP_ERROR: 'Check the response status and server logs for details.',
|
|
25
|
+
PARSE_ERROR: 'The response could not be parsed. Check the Content-Type header.'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static errorName = 'HttpError';
|
|
29
|
+
static defaultCode = 'HTTP_ERROR';
|
|
30
|
+
static markerProperty = 'isHttpError';
|
|
24
31
|
|
|
25
|
-
/**
|
|
26
|
-
* HTTP Error with request/response context
|
|
27
|
-
*/
|
|
28
|
-
export class HttpError extends RuntimeError {
|
|
29
32
|
/**
|
|
30
33
|
* @param {string} message - Error message
|
|
31
34
|
* @param {Object} [options={}] - Error options
|
|
@@ -35,50 +38,22 @@ export class HttpError extends RuntimeError {
|
|
|
35
38
|
* @param {Object} [options.response] - The HttpResponse object
|
|
36
39
|
*/
|
|
37
40
|
constructor(message, options = {}) {
|
|
38
|
-
|
|
39
|
-
const formattedMessage = createErrorMessage({
|
|
40
|
-
code,
|
|
41
|
-
message,
|
|
42
|
-
context: options.context,
|
|
43
|
-
suggestion: options.suggestion || HTTP_SUGGESTIONS[code]
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
super(formattedMessage, { code });
|
|
47
|
-
|
|
48
|
-
this.name = 'HttpError';
|
|
41
|
+
super(message, options);
|
|
49
42
|
this.config = options.config || null;
|
|
50
|
-
this.code = code;
|
|
51
43
|
this.request = options.request || null;
|
|
52
44
|
this.response = options.response || null;
|
|
53
45
|
this.status = options.response?.status || null;
|
|
54
|
-
this.isHttpError = true;
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
/**
|
|
58
49
|
* Check if an error is an HttpError
|
|
59
50
|
* @param {any} error - The error to check
|
|
60
|
-
* @returns {boolean}
|
|
51
|
+
* @returns {boolean}
|
|
61
52
|
*/
|
|
62
53
|
static isHttpError(error) {
|
|
63
54
|
return error?.isHttpError === true;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
/**
|
|
67
|
-
* Check if this is a timeout error
|
|
68
|
-
* @returns {boolean}
|
|
69
|
-
*/
|
|
70
|
-
isTimeout() {
|
|
71
|
-
return this.code === 'TIMEOUT';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Check if this is a network error
|
|
76
|
-
* @returns {boolean}
|
|
77
|
-
*/
|
|
78
|
-
isNetworkError() {
|
|
79
|
-
return this.code === 'NETWORK';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
57
|
/**
|
|
83
58
|
* Check if this is an abort/cancellation error
|
|
84
59
|
* @returns {boolean}
|
|
@@ -88,63 +63,6 @@ export class HttpError extends RuntimeError {
|
|
|
88
63
|
}
|
|
89
64
|
}
|
|
90
65
|
|
|
91
|
-
// ============================================================================
|
|
92
|
-
// Interceptor Manager
|
|
93
|
-
// ============================================================================
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Manages request or response interceptors
|
|
97
|
-
*/
|
|
98
|
-
class InterceptorManager {
|
|
99
|
-
#handlers = new Map();
|
|
100
|
-
#idCounter = 0;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Add an interceptor
|
|
104
|
-
* @param {Function} fulfilled - Function to run on success
|
|
105
|
-
* @param {Function} [rejected] - Function to run on error
|
|
106
|
-
* @returns {number} Interceptor ID (for removal)
|
|
107
|
-
*/
|
|
108
|
-
use(fulfilled, rejected) {
|
|
109
|
-
const id = this.#idCounter++;
|
|
110
|
-
this.#handlers.set(id, { fulfilled, rejected });
|
|
111
|
-
return id;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Remove an interceptor by ID
|
|
116
|
-
* @param {number} id - The interceptor ID
|
|
117
|
-
*/
|
|
118
|
-
eject(id) {
|
|
119
|
-
this.#handlers.delete(id);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Remove all interceptors
|
|
124
|
-
*/
|
|
125
|
-
clear() {
|
|
126
|
-
this.#handlers.clear();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Iterate through handlers
|
|
131
|
-
* @yields {Object} Handler with fulfilled and rejected functions
|
|
132
|
-
*/
|
|
133
|
-
*[Symbol.iterator]() {
|
|
134
|
-
for (const handler of this.#handlers.values()) {
|
|
135
|
-
yield handler;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get the number of interceptors
|
|
141
|
-
* @returns {number}
|
|
142
|
-
*/
|
|
143
|
-
get size() {
|
|
144
|
-
return this.#handlers.size;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
66
|
// ============================================================================
|
|
149
67
|
// HTTP Client
|
|
150
68
|
// ============================================================================
|