@milaboratories/pl-client 2.16.11 → 2.16.13
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/core/driver.cjs +1 -1
- package/dist/core/driver.cjs.map +1 -1
- package/dist/core/driver.js +1 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/errors.cjs +2 -0
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +2 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/ll_client.cjs +32 -9
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +32 -9
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/ll_transaction.cjs +10 -0
- package/dist/core/ll_transaction.cjs.map +1 -1
- package/dist/core/ll_transaction.d.ts +1 -0
- package/dist/core/ll_transaction.d.ts.map +1 -1
- package/dist/core/ll_transaction.js +10 -0
- package/dist/core/ll_transaction.js.map +1 -1
- package/dist/core/websocket_stream.cjs +330 -0
- package/dist/core/websocket_stream.cjs.map +1 -0
- package/dist/core/websocket_stream.d.ts +67 -0
- package/dist/core/websocket_stream.d.ts.map +1 -0
- package/dist/core/websocket_stream.js +328 -0
- package/dist/core/websocket_stream.js.map +1 -0
- package/dist/helpers/retry_strategy.cjs +92 -0
- package/dist/helpers/retry_strategy.cjs.map +1 -0
- package/dist/helpers/retry_strategy.d.ts +24 -0
- package/dist/helpers/retry_strategy.d.ts.map +1 -0
- package/dist/helpers/retry_strategy.js +89 -0
- package/dist/helpers/retry_strategy.js.map +1 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +136 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +75 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +135 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-rest/index.cjs +16 -2
- package/dist/proto-rest/index.cjs.map +1 -1
- package/dist/proto-rest/index.d.ts.map +1 -1
- package/dist/proto-rest/index.js +16 -2
- package/dist/proto-rest/index.js.map +1 -1
- package/package.json +6 -6
- package/src/core/driver.ts +1 -1
- package/src/core/errors.ts +1 -0
- package/src/core/ll_client.ts +42 -9
- package/src/core/ll_transaction.test.ts +18 -0
- package/src/core/ll_transaction.ts +12 -0
- package/src/core/websocket_stream.test.ts +423 -0
- package/src/core/websocket_stream.ts +400 -0
- package/src/helpers/retry_strategy.ts +123 -0
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +179 -1
- package/src/proto-rest/index.ts +17 -2
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { WebSocket, ErrorEvent } from 'undici';
|
|
2
|
+
import Denque from 'denque';
|
|
3
|
+
import { RetryStrategy } from '../helpers/retry_strategy.js';
|
|
4
|
+
|
|
5
|
+
var ConnectionState;
|
|
6
|
+
(function (ConnectionState) {
|
|
7
|
+
ConnectionState[ConnectionState["NEW"] = 0] = "NEW";
|
|
8
|
+
ConnectionState[ConnectionState["CONNECTING"] = 1] = "CONNECTING";
|
|
9
|
+
ConnectionState[ConnectionState["CONNECTED"] = 2] = "CONNECTED";
|
|
10
|
+
ConnectionState[ConnectionState["CLOSING"] = 3] = "CLOSING";
|
|
11
|
+
ConnectionState[ConnectionState["CLOSED"] = 4] = "CLOSED";
|
|
12
|
+
})(ConnectionState || (ConnectionState = {}));
|
|
13
|
+
/**
|
|
14
|
+
* WebSocket-based bidirectional stream implementation for LLTransaction.
|
|
15
|
+
* Implements BiDiStream interface which is compatible with DuplexStreamingCall.
|
|
16
|
+
*/
|
|
17
|
+
class WebSocketBiDiStream {
|
|
18
|
+
url;
|
|
19
|
+
serializeClientMessage;
|
|
20
|
+
parseServerMessage;
|
|
21
|
+
options;
|
|
22
|
+
// Connection
|
|
23
|
+
ws = null;
|
|
24
|
+
connectionState = ConnectionState.NEW;
|
|
25
|
+
reconnection;
|
|
26
|
+
// Send management
|
|
27
|
+
sendQueue = new Denque();
|
|
28
|
+
sendCompleted = false;
|
|
29
|
+
onComplete;
|
|
30
|
+
// Response management
|
|
31
|
+
responseQueue = new Denque();
|
|
32
|
+
responseResolvers = [];
|
|
33
|
+
// Error tracking
|
|
34
|
+
lastError;
|
|
35
|
+
// === Public API ===
|
|
36
|
+
requests = {
|
|
37
|
+
send: async (message) => {
|
|
38
|
+
return await this.enqueueSend(message);
|
|
39
|
+
},
|
|
40
|
+
complete: async () => {
|
|
41
|
+
if (this.sendCompleted)
|
|
42
|
+
return;
|
|
43
|
+
await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream
|
|
44
|
+
try {
|
|
45
|
+
await this.onComplete(this); // custom onComplete may send additional messages
|
|
46
|
+
}
|
|
47
|
+
catch (_) {
|
|
48
|
+
// When 'complete' gets called concurrently with connection break or over a broken
|
|
49
|
+
// transaction stream (server decided it should drop transaction), server would close
|
|
50
|
+
// connection anyway on its end. We can safely ignore error here and just continue working.
|
|
51
|
+
}
|
|
52
|
+
this.sendCompleted = true;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
responses = {
|
|
56
|
+
[Symbol.asyncIterator]: () => this.createResponseIterator(),
|
|
57
|
+
};
|
|
58
|
+
close() {
|
|
59
|
+
this.reconnection.cancel();
|
|
60
|
+
if (this.connectionState < ConnectionState.CONNECTED) {
|
|
61
|
+
// Never reached CONNECTED state. ws.close() will never trigger 'close' event.
|
|
62
|
+
this.ws?.close();
|
|
63
|
+
this.onClose();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!this.progressConnectionState(ConnectionState.CLOSING))
|
|
67
|
+
return;
|
|
68
|
+
this.ws.close();
|
|
69
|
+
}
|
|
70
|
+
constructor(url, serializeClientMessage, parseServerMessage, options = {}) {
|
|
71
|
+
this.url = url;
|
|
72
|
+
this.serializeClientMessage = serializeClientMessage;
|
|
73
|
+
this.parseServerMessage = parseServerMessage;
|
|
74
|
+
this.options = options;
|
|
75
|
+
this.onComplete = this.options.onComplete ?? ((stream) => stream.close());
|
|
76
|
+
const retryConfig = this.options.retryConfig ?? {};
|
|
77
|
+
this.reconnection = new RetryStrategy(retryConfig, {
|
|
78
|
+
onRetry: () => { void this.connect(); },
|
|
79
|
+
onMaxAttemptsReached: (error) => this.handleError(error),
|
|
80
|
+
});
|
|
81
|
+
if (this.options.abortSignal?.aborted) {
|
|
82
|
+
this.progressConnectionState(ConnectionState.CLOSED);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.options.abortSignal?.addEventListener('abort', () => this.close());
|
|
86
|
+
this.connect();
|
|
87
|
+
}
|
|
88
|
+
// === Connection Lifecycle ===
|
|
89
|
+
connect() {
|
|
90
|
+
if (this.options.abortSignal?.aborted)
|
|
91
|
+
return;
|
|
92
|
+
// Prevent reconnecting after first successful connection.
|
|
93
|
+
if (!this.progressConnectionState(ConnectionState.CONNECTING))
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
this.ws = this.createWebSocket();
|
|
97
|
+
this.ws.addEventListener('open', () => this.onOpen());
|
|
98
|
+
this.ws.addEventListener('message', (event) => this.onMessage(event.data));
|
|
99
|
+
this.ws.addEventListener('error', (error) => this.onError(error));
|
|
100
|
+
this.ws.addEventListener('close', () => this.onClose());
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.lastError = this.toError(error);
|
|
104
|
+
this.reconnection.schedule();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
createWebSocket() {
|
|
108
|
+
const options = {};
|
|
109
|
+
if (this.options.jwtToken)
|
|
110
|
+
options.headers = { authorization: `Bearer ${this.options.jwtToken}` };
|
|
111
|
+
if (this.options.dispatcher)
|
|
112
|
+
options.dispatcher = this.options.dispatcher;
|
|
113
|
+
const ws = new WebSocket(this.url, options);
|
|
114
|
+
ws.binaryType = 'arraybuffer';
|
|
115
|
+
return ws;
|
|
116
|
+
}
|
|
117
|
+
onOpen() {
|
|
118
|
+
this.progressConnectionState(ConnectionState.CONNECTED);
|
|
119
|
+
this.processSendQueue();
|
|
120
|
+
}
|
|
121
|
+
onMessage(data) {
|
|
122
|
+
if (!(data instanceof ArrayBuffer)) {
|
|
123
|
+
this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const message = this.parseServerMessage(new Uint8Array(data));
|
|
128
|
+
this.deliverResponse(message);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.handleError(this.toError(error));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
onError(error) {
|
|
135
|
+
if (this.connectionState < ConnectionState.CONNECTED) {
|
|
136
|
+
// Try to connect several times until we succeed or run out of attempts.
|
|
137
|
+
this.lastError = this.toError(error);
|
|
138
|
+
this.reconnection.schedule();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.handleError(this.toError(error));
|
|
142
|
+
}
|
|
143
|
+
onClose() {
|
|
144
|
+
this.progressConnectionState(ConnectionState.CLOSED);
|
|
145
|
+
if (!this.lastError) {
|
|
146
|
+
this.rejectAllSendOperations(this.createStreamClosedError());
|
|
147
|
+
this.resolveAllPendingResponses(); // unblock active async iterator
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.rejectAllPendingOperations(this.lastError);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// === Send Queue Management ===
|
|
154
|
+
enqueueSend(message) {
|
|
155
|
+
if (this.sendCompleted) {
|
|
156
|
+
throw new Error('Cannot send: stream already completed');
|
|
157
|
+
}
|
|
158
|
+
if (this.options.abortSignal?.aborted) {
|
|
159
|
+
throw new Error('Cannot send: stream aborted');
|
|
160
|
+
}
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
this.sendQueue.push({ message, resolve, reject });
|
|
163
|
+
this.processSendQueue();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
processSendQueue() {
|
|
167
|
+
if (!this.canSendMessages())
|
|
168
|
+
return;
|
|
169
|
+
while (this.sendQueue.length > 0) {
|
|
170
|
+
const queued = this.sendQueue.shift();
|
|
171
|
+
this.sendQueuedMessage(queued);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
canSendMessages() {
|
|
175
|
+
return this.connectionState === ConnectionState.CONNECTED;
|
|
176
|
+
}
|
|
177
|
+
sendQueuedMessage(queued) {
|
|
178
|
+
try {
|
|
179
|
+
const ws = this.ws;
|
|
180
|
+
if (!ws) {
|
|
181
|
+
throw new Error('WebSocket is not connected');
|
|
182
|
+
}
|
|
183
|
+
// Check if WebSocket is in a valid state for sending
|
|
184
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
185
|
+
throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);
|
|
186
|
+
}
|
|
187
|
+
const binary = this.serializeClientMessage(queued.message);
|
|
188
|
+
ws.send(binary);
|
|
189
|
+
queued.resolve();
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
queued.reject(this.toError(error));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async drainSendQueue() {
|
|
196
|
+
const POLL_INTERVAL_MS = 5;
|
|
197
|
+
while (this.sendQueue.length > 0) {
|
|
198
|
+
await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
waitForCondition(condition, intervalMs) {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
if (this.options.abortSignal?.aborted) {
|
|
204
|
+
return reject(this.toError(this.options.abortSignal.reason) ?? new Error('Stream aborted'));
|
|
205
|
+
}
|
|
206
|
+
let timeoutId;
|
|
207
|
+
const onAbort = () => {
|
|
208
|
+
clearTimeout(timeoutId);
|
|
209
|
+
reject(this.toError(this.options.abortSignal?.reason) ?? new Error('Stream aborted'));
|
|
210
|
+
};
|
|
211
|
+
this.options.abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
212
|
+
const check = () => {
|
|
213
|
+
if (condition() || this.isStreamEnded()) {
|
|
214
|
+
this.options.abortSignal?.removeEventListener('abort', onAbort);
|
|
215
|
+
resolve();
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
timeoutId = setTimeout(check, intervalMs);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
check();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// === Response Delivery ===
|
|
225
|
+
deliverResponse(message) {
|
|
226
|
+
if (this.responseResolvers.length > 0) {
|
|
227
|
+
const resolver = this.responseResolvers.shift();
|
|
228
|
+
resolver.resolve({ value: message, done: false });
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.responseQueue.push(message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async *createResponseIterator() {
|
|
235
|
+
while (true) {
|
|
236
|
+
const result = await this.nextResponse();
|
|
237
|
+
if (result.done)
|
|
238
|
+
break;
|
|
239
|
+
yield result.value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
nextResponse() {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
// Fast path: message already available
|
|
245
|
+
if (this.responseQueue.length > 0) {
|
|
246
|
+
const message = this.responseQueue.shift();
|
|
247
|
+
resolve({ value: message, done: false });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Stream ended
|
|
251
|
+
if (this.isStreamEnded()) {
|
|
252
|
+
if (this.lastError) {
|
|
253
|
+
reject(this.lastError);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
resolve({ value: undefined, done: true });
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Wait for next message
|
|
261
|
+
this.responseResolvers.push({ resolve, reject });
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
resolveAllPendingResponses() {
|
|
265
|
+
while (this.responseResolvers.length > 0) {
|
|
266
|
+
const resolver = this.responseResolvers.shift();
|
|
267
|
+
resolver.resolve({ value: undefined, done: true });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// === Error Handling ===
|
|
271
|
+
handleError(error) {
|
|
272
|
+
this.lastError = error;
|
|
273
|
+
this.close();
|
|
274
|
+
}
|
|
275
|
+
rejectAllPendingOperations(error) {
|
|
276
|
+
this.rejectAllSendOperations(error);
|
|
277
|
+
this.rejectAllResponseResolvers(error);
|
|
278
|
+
}
|
|
279
|
+
rejectAllSendOperations(error) {
|
|
280
|
+
while (this.sendQueue.length > 0) {
|
|
281
|
+
const queued = this.sendQueue.shift();
|
|
282
|
+
queued.reject(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
rejectAllResponseResolvers(error) {
|
|
286
|
+
while (this.responseResolvers.length > 0) {
|
|
287
|
+
const resolver = this.responseResolvers.shift();
|
|
288
|
+
resolver.reject(error);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
createStreamClosedError() {
|
|
292
|
+
if (this.options.abortSignal?.aborted) {
|
|
293
|
+
const reason = this.options.abortSignal.reason;
|
|
294
|
+
if (reason instanceof Error) {
|
|
295
|
+
return reason;
|
|
296
|
+
}
|
|
297
|
+
return new Error('Stream aborted', { cause: reason });
|
|
298
|
+
}
|
|
299
|
+
return new Error('Stream closed');
|
|
300
|
+
}
|
|
301
|
+
// === Helpers ===
|
|
302
|
+
isStreamEnded() {
|
|
303
|
+
return this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false;
|
|
304
|
+
}
|
|
305
|
+
toError(error) {
|
|
306
|
+
if (error instanceof Error)
|
|
307
|
+
return error;
|
|
308
|
+
if (error instanceof ErrorEvent)
|
|
309
|
+
return error.error;
|
|
310
|
+
return new Error(String(error));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Connection state progresses linearly from NEW to CLOSED and never goes back.
|
|
314
|
+
* This internal contract dramatically simplifies the internal stream state management.
|
|
315
|
+
*
|
|
316
|
+
* If you ever feel the need to make this contract less strict, think twice.
|
|
317
|
+
*/
|
|
318
|
+
progressConnectionState(newState) {
|
|
319
|
+
if (newState < this.connectionState) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
this.connectionState = newState;
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export { WebSocketBiDiStream };
|
|
328
|
+
//# sourceMappingURL=websocket_stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket_stream.js","sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket, type WebSocketInit, type Dispatcher, ErrorEvent } from 'undici';\nimport type { BiDiStream } from './abstract_stream';\nimport Denque from 'denque';\nimport type { RetryConfig } from '../helpers/retry_strategy';\nimport { RetryStrategy } from '../helpers/retry_strategy';\n\ninterface QueuedMessage<InType extends object> {\n message: InType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver<OutType extends object> {\n resolve: (value: IteratorResult<OutType>) => void;\n reject: (error: Error) => void;\n}\n\nenum ConnectionState {\n NEW = 0,\n CONNECTING = 1,\n CONNECTED = 2,\n CLOSING = 3,\n CLOSED = 4,\n}\n\nexport type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {\n abortSignal?: AbortSignal;\n\n dispatcher?: Dispatcher;\n jwtToken?: string;\n retryConfig?: Partial<RetryConfig>;\n\n onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n};\n\n/**\n * WebSocket-based bidirectional stream implementation for LLTransaction.\n * Implements BiDiStream interface which is compatible with DuplexStreamingCall.\n */\nexport class WebSocketBiDiStream<ClientMsg extends object, ServerMsg extends object> implements BiDiStream<ClientMsg, ServerMsg> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = ConnectionState.NEW;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage<ClientMsg>>();\n private sendCompleted = false;\n private readonly onComplete: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMsg>();\n private responseResolvers: ResponseResolver<ServerMsg>[] = [];\n\n // Error tracking\n private lastError?: Error;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMsg): Promise<void> => {\n return await this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream\n try {\n await this.onComplete(this); // custom onComplete may send additional messages\n } catch (_: unknown) {\n // When 'complete' gets called concurrently with connection break or over a broken\n // transaction stream (server decided it should drop transaction), server would close\n // connection anyway on its end. We can safely ignore error here and just continue working.\n }\n this.sendCompleted = true;\n },\n };\n\n public readonly responses: AsyncIterable<ServerMsg> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n public close(): void {\n this.reconnection.cancel();\n\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Never reached CONNECTED state. ws.close() will never trigger 'close' event.\n this.ws?.close();\n this.onClose();\n return;\n }\n\n if (!this.progressConnectionState(ConnectionState.CLOSING)) return;\n this.ws!.close();\n }\n\n constructor(\n private readonly url: string,\n private readonly serializeClientMessage: (message: ClientMsg) => Uint8Array,\n private readonly parseServerMessage: (data: Uint8Array) => ServerMsg,\n private readonly options: WSStreamOptions<ClientMsg, ServerMsg> = {},\n ) {\n this.onComplete = this.options.onComplete ?? ((stream) => stream.close());\n\n const retryConfig = this.options.retryConfig ?? {};\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => { void this.connect(); },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (this.options.abortSignal?.aborted) {\n this.progressConnectionState(ConnectionState.CLOSED);\n return;\n }\n\n this.options.abortSignal?.addEventListener('abort', () => this.close());\n this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.options.abortSignal?.aborted) return;\n\n // Prevent reconnecting after first successful connection.\n if (!this.progressConnectionState(ConnectionState.CONNECTING)) return;\n\n try {\n this.ws = this.createWebSocket();\n\n this.ws.addEventListener('open', () => this.onOpen());\n this.ws.addEventListener('message', (event) => this.onMessage(event.data));\n this.ws.addEventListener('error', (error) => this.onError(error));\n this.ws.addEventListener('close', () => this.onClose());\n } catch (error) {\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options: WebSocketInit = {};\n\n if (this.options.jwtToken) options.headers = { authorization: `Bearer ${this.options.jwtToken}` };\n if (this.options.dispatcher) options.dispatcher = this.options.dispatcher;\n\n const ws = new WebSocket(this.url, options);\n ws.binaryType = 'arraybuffer';\n return ws;\n }\n\n private onOpen(): void {\n this.progressConnectionState(ConnectionState.CONNECTED);\n this.processSendQueue();\n }\n\n private onMessage(data: unknown): void {\n if (!(data instanceof ArrayBuffer)) {\n this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));\n return;\n }\n\n try {\n const message = this.parseServerMessage(new Uint8Array(data));\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private onError(error: unknown): void {\n if (this.connectionState < ConnectionState.CONNECTED) {\n // Try to connect several times until we succeed or run out of attempts.\n this.lastError = this.toError(error);\n this.reconnection.schedule();\n return;\n }\n\n this.handleError(this.toError(error));\n }\n\n private onClose(): void {\n this.progressConnectionState(ConnectionState.CLOSED);\n\n if (!this.lastError) {\n this.rejectAllSendOperations(this.createStreamClosedError());\n this.resolveAllPendingResponses(); // unblock active async iterator\n } else {\n this.rejectAllPendingOperations(this.lastError);\n }\n }\n\n // === Send Queue Management ===\n\n private enqueueSend(message: ClientMsg): Promise<void> {\n if (this.sendCompleted) {\n throw new Error('Cannot send: stream already completed');\n }\n\n if (this.options.abortSignal?.aborted) {\n throw new Error('Cannot send: stream aborted');\n }\n\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n this.processSendQueue();\n });\n }\n\n private processSendQueue(): void {\n if (!this.canSendMessages()) return;\n\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n this.sendQueuedMessage(queued);\n }\n }\n\n private canSendMessages(): boolean {\n return this.connectionState === ConnectionState.CONNECTED;\n }\n\n private sendQueuedMessage(queued: QueuedMessage<ClientMsg>): void {\n try {\n const ws = this.ws;\n if (!ws) {\n throw new Error('WebSocket is not connected');\n }\n\n // Check if WebSocket is in a valid state for sending\n if (ws.readyState !== WebSocket.OPEN) {\n throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);\n }\n\n const binary = this.serializeClientMessage(queued.message);\n ws.send(binary);\n queued.resolve();\n } catch (error) {\n queued.reject(this.toError(error));\n }\n }\n\n private async drainSendQueue(): Promise<void> {\n const POLL_INTERVAL_MS = 5;\n\n while (this.sendQueue.length > 0) {\n await this.waitForCondition(\n () => this.sendQueue.length === 0,\n POLL_INTERVAL_MS,\n );\n }\n }\n\n private waitForCondition(\n condition: () => boolean,\n intervalMs: number,\n ): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.options.abortSignal?.aborted) {\n return reject(this.toError(this.options.abortSignal.reason) ?? new Error('Stream aborted'));\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(this.toError(this.options.abortSignal?.reason) ?? new Error('Stream aborted'));\n };\n\n this.options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.options.abortSignal?.removeEventListener('abort', onAbort);\n resolve();\n } else {\n timeoutId = setTimeout(check, intervalMs);\n }\n };\n\n check();\n });\n }\n\n // === Response Delivery ===\n\n private deliverResponse(message: ServerMsg): void {\n if (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: message, done: false });\n } else {\n this.responseQueue.push(message);\n }\n }\n\n private async *createResponseIterator(): AsyncIterator<ServerMsg> {\n while (true) {\n const result = await this.nextResponse();\n\n if (result.done) break;\n\n yield result.value;\n }\n }\n\n private nextResponse(): Promise<IteratorResult<ServerMsg>> {\n return new Promise<IteratorResult<ServerMsg>>((resolve, reject) => {\n // Fast path: message already available\n if (this.responseQueue.length > 0) {\n const message = this.responseQueue.shift()!;\n resolve({ value: message, done: false });\n return;\n }\n\n // Stream ended\n if (this.isStreamEnded()) {\n if (this.lastError) {\n reject(this.lastError);\n } else {\n resolve({ value: undefined as any, done: true });\n }\n return;\n }\n\n // Wait for next message\n this.responseResolvers.push({ resolve, reject });\n });\n }\n\n private resolveAllPendingResponses(): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.resolve({ value: undefined as any, done: true });\n }\n }\n\n // === Error Handling ===\n\n private handleError(error: Error): void {\n this.lastError = error;\n this.close();\n }\n\n private rejectAllPendingOperations(error: Error): void {\n this.rejectAllSendOperations(error);\n this.rejectAllResponseResolvers(error);\n }\n\n private rejectAllSendOperations(error: Error): void {\n while (this.sendQueue.length > 0) {\n const queued = this.sendQueue.shift()!;\n queued.reject(error);\n }\n }\n\n private rejectAllResponseResolvers(error: Error): void {\n while (this.responseResolvers.length > 0) {\n const resolver = this.responseResolvers.shift()!;\n resolver.reject(error);\n }\n }\n\n private createStreamClosedError(): Error {\n if (this.options.abortSignal?.aborted) {\n const reason = this.options.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error('Stream aborted', { cause: reason });\n }\n\n return new Error('Stream closed');\n }\n\n // === Helpers ===\n\n private isStreamEnded(): boolean {\n return this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false;\n }\n\n private toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (error instanceof ErrorEvent) return error.error;\n return new Error(String(error));\n }\n\n /**\n * Connection state progresses linearly from NEW to CLOSED and never goes back.\n * This internal contract dramatically simplifies the internal stream state management.\n *\n * If you ever feel the need to make this contract less strict, think twice.\n */\n private progressConnectionState(newState: ConnectionState): boolean {\n if (newState < this.connectionState) {\n return false;\n }\n this.connectionState = newState;\n return true;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
4
|
+
maxAttempts: 10,
|
|
5
|
+
initialDelay: 100,
|
|
6
|
+
maxDelay: 30000,
|
|
7
|
+
};
|
|
8
|
+
class RetryStrategy {
|
|
9
|
+
attempts = 0;
|
|
10
|
+
timer = null;
|
|
11
|
+
config;
|
|
12
|
+
callbacks;
|
|
13
|
+
backoff;
|
|
14
|
+
constructor(config, callbacks) {
|
|
15
|
+
this.config = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
16
|
+
this.callbacks = callbacks;
|
|
17
|
+
this.backoff = new ExponentialBackoff({
|
|
18
|
+
initialDelay: this.config.initialDelay,
|
|
19
|
+
maxDelay: this.config.maxDelay,
|
|
20
|
+
factor: 2,
|
|
21
|
+
jitter: 0.1,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
schedule() {
|
|
25
|
+
if (this.timer)
|
|
26
|
+
return;
|
|
27
|
+
if (this.hasExceededLimit()) {
|
|
28
|
+
this.notifyMaxAttemptsReached();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.timer = setTimeout(() => {
|
|
32
|
+
this.timer = null;
|
|
33
|
+
this.attempts++;
|
|
34
|
+
this.callbacks.onRetry();
|
|
35
|
+
}, this.backoff.delay());
|
|
36
|
+
}
|
|
37
|
+
cancel() {
|
|
38
|
+
if (this.timer) {
|
|
39
|
+
clearTimeout(this.timer);
|
|
40
|
+
this.timer = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
this.attempts = 0;
|
|
45
|
+
this.backoff.reset();
|
|
46
|
+
}
|
|
47
|
+
hasExceededLimit() {
|
|
48
|
+
return this.attempts >= this.config.maxAttempts;
|
|
49
|
+
}
|
|
50
|
+
notifyMaxAttemptsReached() {
|
|
51
|
+
const error = new Error(`Max retry attempts (${this.config.maxAttempts}) reached`);
|
|
52
|
+
this.callbacks.onMaxAttemptsReached(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class ExponentialBackoff {
|
|
56
|
+
initialDelay;
|
|
57
|
+
maxDelay;
|
|
58
|
+
currentDelay;
|
|
59
|
+
factor;
|
|
60
|
+
jitter;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.initialDelay = config.initialDelay;
|
|
63
|
+
this.maxDelay = config.maxDelay;
|
|
64
|
+
this.factor = config.factor;
|
|
65
|
+
this.jitter = config.jitter;
|
|
66
|
+
this.currentDelay = config.initialDelay;
|
|
67
|
+
}
|
|
68
|
+
delay() {
|
|
69
|
+
if (this.currentDelay >= this.maxDelay) {
|
|
70
|
+
return this.applyJitter(this.maxDelay);
|
|
71
|
+
}
|
|
72
|
+
this.currentDelay = this.currentDelay * this.factor;
|
|
73
|
+
if (this.currentDelay > this.maxDelay) {
|
|
74
|
+
this.currentDelay = this.maxDelay;
|
|
75
|
+
}
|
|
76
|
+
return this.applyJitter(this.currentDelay);
|
|
77
|
+
}
|
|
78
|
+
reset() {
|
|
79
|
+
this.currentDelay = this.initialDelay;
|
|
80
|
+
}
|
|
81
|
+
applyJitter(delay) {
|
|
82
|
+
if (delay === 0 || this.jitter === 0) {
|
|
83
|
+
return delay;
|
|
84
|
+
}
|
|
85
|
+
const delayFactor = 1 - (this.jitter / 2) + Math.random() * this.jitter;
|
|
86
|
+
return delay * delayFactor;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
|
|
91
|
+
exports.RetryStrategy = RetryStrategy;
|
|
92
|
+
//# sourceMappingURL=retry_strategy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry_strategy.cjs","sources":["../../src/helpers/retry_strategy.ts"],"sourcesContent":["export interface RetryConfig {\n maxAttempts: number;\n initialDelay: number;\n maxDelay: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 10,\n initialDelay: 100,\n maxDelay: 30000,\n};\n\nexport interface RetryCallbacks {\n onRetry: () => void;\n onMaxAttemptsReached: (error: Error) => void;\n}\n\nexport class RetryStrategy {\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly config: RetryConfig;\n private readonly callbacks: RetryCallbacks;\n private readonly backoff: ExponentialBackoff;\n\n constructor(config: Partial<RetryConfig>, callbacks: RetryCallbacks) {\n this.config = { ...DEFAULT_RETRY_CONFIG, ...config };\n this.callbacks = callbacks;\n this.backoff = new ExponentialBackoff({\n initialDelay: this.config.initialDelay,\n maxDelay: this.config.maxDelay,\n factor: 2,\n jitter: 0.1,\n });\n }\n\n schedule(): void {\n if (this.timer) return;\n if (this.hasExceededLimit()) {\n this.notifyMaxAttemptsReached();\n return;\n }\n\n this.timer = setTimeout(() => {\n this.timer = null;\n this.attempts++;\n this.callbacks.onRetry();\n }, this.backoff.delay());\n }\n\n cancel(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n reset(): void {\n this.attempts = 0;\n this.backoff.reset();\n }\n\n private hasExceededLimit(): boolean {\n return this.attempts >= this.config.maxAttempts;\n }\n\n private notifyMaxAttemptsReached(): void {\n const error = new Error(\n `Max retry attempts (${this.config.maxAttempts}) reached`,\n );\n this.callbacks.onMaxAttemptsReached(error);\n }\n}\n\ninterface ExponentialBackoffConfig {\n initialDelay: number;\n maxDelay: number;\n factor: number;\n jitter: number;\n}\n\nclass ExponentialBackoff {\n private readonly initialDelay: number;\n private readonly maxDelay: number;\n\n private currentDelay: number;\n\n private readonly factor: number;\n private readonly jitter: number;\n\n constructor(config: ExponentialBackoffConfig) {\n this.initialDelay = config.initialDelay;\n this.maxDelay = config.maxDelay;\n this.factor = config.factor;\n this.jitter = config.jitter;\n this.currentDelay = config.initialDelay;\n }\n\n delay(): number {\n if (this.currentDelay >= this.maxDelay) {\n return this.applyJitter(this.maxDelay);\n }\n\n this.currentDelay = this.currentDelay * this.factor;\n\n if (this.currentDelay > this.maxDelay) {\n this.currentDelay = this.maxDelay;\n }\n\n return this.applyJitter(this.currentDelay);\n }\n\n reset(): void {\n this.currentDelay = this.initialDelay;\n }\n\n private applyJitter(delay: number): number {\n if (delay === 0 || this.jitter === 0) {\n return delay;\n }\n const delayFactor = 1 - (this.jitter / 2) + Math.random() * this.jitter;\n return delay * delayFactor;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface RetryConfig {
|
|
2
|
+
maxAttempts: number;
|
|
3
|
+
initialDelay: number;
|
|
4
|
+
maxDelay: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
7
|
+
export interface RetryCallbacks {
|
|
8
|
+
onRetry: () => void;
|
|
9
|
+
onMaxAttemptsReached: (error: Error) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare class RetryStrategy {
|
|
12
|
+
private attempts;
|
|
13
|
+
private timer;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly callbacks;
|
|
16
|
+
private readonly backoff;
|
|
17
|
+
constructor(config: Partial<RetryConfig>, callbacks: RetryCallbacks);
|
|
18
|
+
schedule(): void;
|
|
19
|
+
cancel(): void;
|
|
20
|
+
reset(): void;
|
|
21
|
+
private hasExceededLimit;
|
|
22
|
+
private notifyMaxAttemptsReached;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=retry_strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry_strategy.d.ts","sourceRoot":"","sources":["../../src/helpers/retry_strategy.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oBAAoB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC9C;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,cAAc;IAWnE,QAAQ,IAAI,IAAI;IAchB,MAAM,IAAI,IAAI;IAOd,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,wBAAwB;CAMjC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
2
|
+
maxAttempts: 10,
|
|
3
|
+
initialDelay: 100,
|
|
4
|
+
maxDelay: 30000,
|
|
5
|
+
};
|
|
6
|
+
class RetryStrategy {
|
|
7
|
+
attempts = 0;
|
|
8
|
+
timer = null;
|
|
9
|
+
config;
|
|
10
|
+
callbacks;
|
|
11
|
+
backoff;
|
|
12
|
+
constructor(config, callbacks) {
|
|
13
|
+
this.config = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
14
|
+
this.callbacks = callbacks;
|
|
15
|
+
this.backoff = new ExponentialBackoff({
|
|
16
|
+
initialDelay: this.config.initialDelay,
|
|
17
|
+
maxDelay: this.config.maxDelay,
|
|
18
|
+
factor: 2,
|
|
19
|
+
jitter: 0.1,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
schedule() {
|
|
23
|
+
if (this.timer)
|
|
24
|
+
return;
|
|
25
|
+
if (this.hasExceededLimit()) {
|
|
26
|
+
this.notifyMaxAttemptsReached();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.timer = setTimeout(() => {
|
|
30
|
+
this.timer = null;
|
|
31
|
+
this.attempts++;
|
|
32
|
+
this.callbacks.onRetry();
|
|
33
|
+
}, this.backoff.delay());
|
|
34
|
+
}
|
|
35
|
+
cancel() {
|
|
36
|
+
if (this.timer) {
|
|
37
|
+
clearTimeout(this.timer);
|
|
38
|
+
this.timer = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
reset() {
|
|
42
|
+
this.attempts = 0;
|
|
43
|
+
this.backoff.reset();
|
|
44
|
+
}
|
|
45
|
+
hasExceededLimit() {
|
|
46
|
+
return this.attempts >= this.config.maxAttempts;
|
|
47
|
+
}
|
|
48
|
+
notifyMaxAttemptsReached() {
|
|
49
|
+
const error = new Error(`Max retry attempts (${this.config.maxAttempts}) reached`);
|
|
50
|
+
this.callbacks.onMaxAttemptsReached(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class ExponentialBackoff {
|
|
54
|
+
initialDelay;
|
|
55
|
+
maxDelay;
|
|
56
|
+
currentDelay;
|
|
57
|
+
factor;
|
|
58
|
+
jitter;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.initialDelay = config.initialDelay;
|
|
61
|
+
this.maxDelay = config.maxDelay;
|
|
62
|
+
this.factor = config.factor;
|
|
63
|
+
this.jitter = config.jitter;
|
|
64
|
+
this.currentDelay = config.initialDelay;
|
|
65
|
+
}
|
|
66
|
+
delay() {
|
|
67
|
+
if (this.currentDelay >= this.maxDelay) {
|
|
68
|
+
return this.applyJitter(this.maxDelay);
|
|
69
|
+
}
|
|
70
|
+
this.currentDelay = this.currentDelay * this.factor;
|
|
71
|
+
if (this.currentDelay > this.maxDelay) {
|
|
72
|
+
this.currentDelay = this.maxDelay;
|
|
73
|
+
}
|
|
74
|
+
return this.applyJitter(this.currentDelay);
|
|
75
|
+
}
|
|
76
|
+
reset() {
|
|
77
|
+
this.currentDelay = this.initialDelay;
|
|
78
|
+
}
|
|
79
|
+
applyJitter(delay) {
|
|
80
|
+
if (delay === 0 || this.jitter === 0) {
|
|
81
|
+
return delay;
|
|
82
|
+
}
|
|
83
|
+
const delayFactor = 1 - (this.jitter / 2) + Math.random() * this.jitter;
|
|
84
|
+
return delay * delayFactor;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { DEFAULT_RETRY_CONFIG, RetryStrategy };
|
|
89
|
+
//# sourceMappingURL=retry_strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry_strategy.js","sources":["../../src/helpers/retry_strategy.ts"],"sourcesContent":["export interface RetryConfig {\n maxAttempts: number;\n initialDelay: number;\n maxDelay: number;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n maxAttempts: 10,\n initialDelay: 100,\n maxDelay: 30000,\n};\n\nexport interface RetryCallbacks {\n onRetry: () => void;\n onMaxAttemptsReached: (error: Error) => void;\n}\n\nexport class RetryStrategy {\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly config: RetryConfig;\n private readonly callbacks: RetryCallbacks;\n private readonly backoff: ExponentialBackoff;\n\n constructor(config: Partial<RetryConfig>, callbacks: RetryCallbacks) {\n this.config = { ...DEFAULT_RETRY_CONFIG, ...config };\n this.callbacks = callbacks;\n this.backoff = new ExponentialBackoff({\n initialDelay: this.config.initialDelay,\n maxDelay: this.config.maxDelay,\n factor: 2,\n jitter: 0.1,\n });\n }\n\n schedule(): void {\n if (this.timer) return;\n if (this.hasExceededLimit()) {\n this.notifyMaxAttemptsReached();\n return;\n }\n\n this.timer = setTimeout(() => {\n this.timer = null;\n this.attempts++;\n this.callbacks.onRetry();\n }, this.backoff.delay());\n }\n\n cancel(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n reset(): void {\n this.attempts = 0;\n this.backoff.reset();\n }\n\n private hasExceededLimit(): boolean {\n return this.attempts >= this.config.maxAttempts;\n }\n\n private notifyMaxAttemptsReached(): void {\n const error = new Error(\n `Max retry attempts (${this.config.maxAttempts}) reached`,\n );\n this.callbacks.onMaxAttemptsReached(error);\n }\n}\n\ninterface ExponentialBackoffConfig {\n initialDelay: number;\n maxDelay: number;\n factor: number;\n jitter: number;\n}\n\nclass ExponentialBackoff {\n private readonly initialDelay: number;\n private readonly maxDelay: number;\n\n private currentDelay: number;\n\n private readonly factor: number;\n private readonly jitter: number;\n\n constructor(config: ExponentialBackoffConfig) {\n this.initialDelay = config.initialDelay;\n this.maxDelay = config.maxDelay;\n this.factor = config.factor;\n this.jitter = config.jitter;\n this.currentDelay = config.initialDelay;\n }\n\n delay(): number {\n if (this.currentDelay >= this.maxDelay) {\n return this.applyJitter(this.maxDelay);\n }\n\n this.currentDelay = this.currentDelay * this.factor;\n\n if (this.currentDelay > this.maxDelay) {\n this.currentDelay = this.maxDelay;\n }\n\n return this.applyJitter(this.currentDelay);\n }\n\n reset(): void {\n this.currentDelay = this.initialDelay;\n }\n\n private applyJitter(delay: number): number {\n if (delay === 0 || this.jitter === 0) {\n return delay;\n }\n const delayFactor = 1 - (this.jitter / 2) + Math.random() * this.jitter;\n return delay * delayFactor;\n }\n}\n"],"names":[],"mappings":"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"}
|