@milaboratories/pl-client 2.16.10 → 2.16.12

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.
Files changed (36) hide show
  1. package/dist/core/errors.cjs +2 -0
  2. package/dist/core/errors.cjs.map +1 -1
  3. package/dist/core/errors.d.ts.map +1 -1
  4. package/dist/core/errors.js +2 -0
  5. package/dist/core/errors.js.map +1 -1
  6. package/dist/core/ll_client.cjs +22 -8
  7. package/dist/core/ll_client.cjs.map +1 -1
  8. package/dist/core/ll_client.d.ts.map +1 -1
  9. package/dist/core/ll_client.js +22 -8
  10. package/dist/core/ll_client.js.map +1 -1
  11. package/dist/core/ll_transaction.cjs +10 -0
  12. package/dist/core/ll_transaction.cjs.map +1 -1
  13. package/dist/core/ll_transaction.d.ts +1 -0
  14. package/dist/core/ll_transaction.d.ts.map +1 -1
  15. package/dist/core/ll_transaction.js +10 -0
  16. package/dist/core/ll_transaction.js.map +1 -1
  17. package/dist/core/websocket_stream.cjs +333 -0
  18. package/dist/core/websocket_stream.cjs.map +1 -0
  19. package/dist/core/websocket_stream.d.ts +60 -0
  20. package/dist/core/websocket_stream.d.ts.map +1 -0
  21. package/dist/core/websocket_stream.js +331 -0
  22. package/dist/core/websocket_stream.js.map +1 -0
  23. package/dist/helpers/retry_strategy.cjs +92 -0
  24. package/dist/helpers/retry_strategy.cjs.map +1 -0
  25. package/dist/helpers/retry_strategy.d.ts +24 -0
  26. package/dist/helpers/retry_strategy.d.ts.map +1 -0
  27. package/dist/helpers/retry_strategy.js +89 -0
  28. package/dist/helpers/retry_strategy.js.map +1 -0
  29. package/package.json +5 -5
  30. package/src/core/errors.ts +1 -0
  31. package/src/core/ll_client.ts +25 -8
  32. package/src/core/ll_transaction.test.ts +18 -0
  33. package/src/core/ll_transaction.ts +12 -0
  34. package/src/core/websocket_stream.test.ts +412 -0
  35. package/src/core/websocket_stream.ts +412 -0
  36. package/src/helpers/retry_strategy.ts +123 -0
@@ -0,0 +1,60 @@
1
+ import { TxAPI_ClientMessage as ClientMessageType, TxAPI_ServerMessage as ServerMessageType } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
2
+ import type { BiDiStream } from './abstract_stream';
3
+ import type { RetryConfig } from '../helpers/retry_strategy';
4
+ /**
5
+ * WebSocket-based bidirectional stream implementation for LLTransaction.
6
+ * Implements BiDiStream interface which is compatible with DuplexStreamingCall.
7
+ */
8
+ export declare class WebSocketBiDiStream implements BiDiStream<ClientMessageType, ServerMessageType> {
9
+ private ws;
10
+ private connectionState;
11
+ private readonly url;
12
+ private readonly jwtToken?;
13
+ private readonly abortSignal;
14
+ private readonly reconnection;
15
+ private readonly sendQueue;
16
+ private sendCompleted;
17
+ private readonly responseQueue;
18
+ private responseResolvers;
19
+ private connectionError;
20
+ readonly requests: {
21
+ send: (message: ClientMessageType) => Promise<void>;
22
+ complete: () => Promise<void>;
23
+ };
24
+ readonly responses: AsyncIterable<ServerMessageType>;
25
+ constructor(url: string, abortSignal: AbortSignal, jwtToken?: string, retryConfig?: Partial<RetryConfig>);
26
+ private connect;
27
+ private createWebSocket;
28
+ private attachWebSocketHandlers;
29
+ private attachAbortSignalHandler;
30
+ private onOpen;
31
+ private onClose;
32
+ private onError;
33
+ private onMessage;
34
+ private closeConnection;
35
+ private close;
36
+ private closeWebSocket;
37
+ private finalizeStream;
38
+ private resolveAllPendingResponses;
39
+ private parseMessage;
40
+ private validateSendState;
41
+ private enqueueSend;
42
+ private processSendQueue;
43
+ private canSendMessages;
44
+ private sendQueuedMessage;
45
+ private drainSendQueue;
46
+ private waitForCondition;
47
+ private deliverResponse;
48
+ private createResponseIterator;
49
+ private nextResponse;
50
+ private handleError;
51
+ private rejectAllPendingOperations;
52
+ private rejectAllSendOperations;
53
+ private rejectAllResponseResolvers;
54
+ private createStreamClosedError;
55
+ private isConnectingOrConnected;
56
+ private isClosed;
57
+ private isStreamEnded;
58
+ private toError;
59
+ }
60
+ //# sourceMappingURL=websocket_stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket_stream.d.ts","sourceRoot":"","sources":["../../src/core/websocket_stream.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,IAAI,iBAAiB,EACxC,mBAAmB,IAAI,iBAAiB,EACzC,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAgB7D;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAE1F,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAG7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,OAAO,CAAC,iBAAiB,CAA0B;IAGnD,OAAO,CAAC,eAAe,CAAsB;IAI7C,SAAgB,QAAQ;wBACA,iBAAiB,KAAG,OAAO,CAAC,IAAI,CAAC;wBAKnC,OAAO,CAAC,IAAI,CAAC;MAOjC;IAEF,SAAgB,SAAS,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAEzD;gBAGA,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,WAAW,EACxB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,GAAE,OAAO,CAAC,WAAW,CAAM;IAsBxC,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,wBAAwB;IAIhC,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iBAAiB;YAoBX,cAAc;IAW5B,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,eAAe;YASR,sBAAsB;IAUrC,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;CAGhB"}
@@ -0,0 +1,331 @@
1
+ import { WebSocket } from 'undici';
2
+ import { TxAPI_ServerMessage, TxAPI_ClientMessage } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js';
3
+ import Denque from 'denque';
4
+ import { RetryStrategy } from '../helpers/retry_strategy.js';
5
+
6
+ /**
7
+ * WebSocket-based bidirectional stream implementation for LLTransaction.
8
+ * Implements BiDiStream interface which is compatible with DuplexStreamingCall.
9
+ */
10
+ class WebSocketBiDiStream {
11
+ // Connection
12
+ ws = null;
13
+ connectionState = 'disconnected';
14
+ url;
15
+ jwtToken;
16
+ abortSignal;
17
+ reconnection;
18
+ // Send management
19
+ sendQueue = new Denque();
20
+ sendCompleted = false;
21
+ // Response management
22
+ responseQueue = new Denque();
23
+ responseResolvers = [];
24
+ // Error tracking
25
+ connectionError = null;
26
+ // === Public API ===
27
+ requests = {
28
+ send: async (message) => {
29
+ this.validateSendState();
30
+ return this.enqueueSend(message);
31
+ },
32
+ complete: async () => {
33
+ if (this.sendCompleted)
34
+ return;
35
+ this.sendCompleted = true;
36
+ await this.drainSendQueue();
37
+ this.closeConnection();
38
+ },
39
+ };
40
+ responses = {
41
+ [Symbol.asyncIterator]: () => this.createResponseIterator(),
42
+ };
43
+ constructor(url, abortSignal, jwtToken, retryConfig = {}) {
44
+ this.url = url;
45
+ this.jwtToken = jwtToken;
46
+ this.abortSignal = abortSignal;
47
+ this.reconnection = new RetryStrategy(retryConfig, {
48
+ onRetry: () => { void this.connect(); },
49
+ onMaxAttemptsReached: (error) => this.handleError(error),
50
+ });
51
+ if (abortSignal.aborted) {
52
+ this.connectionState = 'closed';
53
+ return;
54
+ }
55
+ this.attachAbortSignalHandler();
56
+ void this.connect();
57
+ }
58
+ // === Connection Lifecycle ===
59
+ connect() {
60
+ if (this.isConnectingOrConnected() || this.abortSignal.aborted)
61
+ return;
62
+ this.connectionState = 'connecting';
63
+ this.connectionError = null;
64
+ try {
65
+ this.ws = this.createWebSocket();
66
+ this.attachWebSocketHandlers();
67
+ }
68
+ catch (error) {
69
+ this.connectionError = this.toError(error);
70
+ this.connectionState = 'disconnected';
71
+ this.reconnection.schedule();
72
+ }
73
+ }
74
+ createWebSocket() {
75
+ const options = this.jwtToken
76
+ ? { headers: { authorization: `Bearer ${this.jwtToken}` } }
77
+ : undefined;
78
+ const ws = new WebSocket(this.url, options);
79
+ if (ws) {
80
+ ws.binaryType = 'arraybuffer';
81
+ }
82
+ return ws;
83
+ }
84
+ attachWebSocketHandlers() {
85
+ if (!this.ws)
86
+ return;
87
+ this.ws.addEventListener('open', () => this.onOpen());
88
+ this.ws.addEventListener('message', (event) => this.onMessage(event.data));
89
+ this.ws.addEventListener('error', (error) => this.onError(error));
90
+ this.ws.addEventListener('close', () => this.onClose());
91
+ }
92
+ attachAbortSignalHandler() {
93
+ this.abortSignal.addEventListener('abort', () => this.close());
94
+ }
95
+ onOpen() {
96
+ this.connectionState = 'connected';
97
+ this.reconnection.reset();
98
+ void this.processSendQueue();
99
+ }
100
+ onClose() {
101
+ this.ws = null;
102
+ if (this.isClosed() || this.abortSignal.aborted)
103
+ return;
104
+ if (this.sendCompleted) {
105
+ this.finalizeStream();
106
+ }
107
+ else {
108
+ this.connectionState = 'disconnected';
109
+ this.reconnection.schedule();
110
+ }
111
+ }
112
+ onError(error) {
113
+ this.handleError(this.toError(error));
114
+ }
115
+ onMessage(data) {
116
+ try {
117
+ const message = this.parseMessage(data);
118
+ this.deliverResponse(message);
119
+ }
120
+ catch (error) {
121
+ this.handleError(this.toError(error));
122
+ }
123
+ }
124
+ closeConnection() {
125
+ if (this.ws?.readyState === WebSocket.OPEN) {
126
+ this.ws.close();
127
+ }
128
+ }
129
+ close() {
130
+ if (this.isClosed())
131
+ return;
132
+ this.connectionState = 'closed';
133
+ this.reconnection.cancel();
134
+ this.closeWebSocket();
135
+ this.rejectAllPendingOperations();
136
+ }
137
+ closeWebSocket() {
138
+ if (!this.ws)
139
+ return;
140
+ try {
141
+ this.ws.close();
142
+ }
143
+ catch {
144
+ // Suppress close errors
145
+ }
146
+ this.ws = null;
147
+ }
148
+ finalizeStream() {
149
+ this.connectionState = 'closed';
150
+ this.resolveAllPendingResponses();
151
+ }
152
+ resolveAllPendingResponses() {
153
+ while (this.responseResolvers.length > 0) {
154
+ const resolver = this.responseResolvers.shift();
155
+ resolver.resolve({ value: undefined, done: true });
156
+ }
157
+ }
158
+ parseMessage(data) {
159
+ if (data instanceof ArrayBuffer) {
160
+ return TxAPI_ServerMessage.fromBinary(new Uint8Array(data));
161
+ }
162
+ throw new Error(`Unsupported message format: ${typeof data}`);
163
+ }
164
+ // === Send Queue Management ===
165
+ validateSendState() {
166
+ if (this.sendCompleted) {
167
+ throw new Error('Cannot send: stream already completed');
168
+ }
169
+ if (this.abortSignal.aborted) {
170
+ throw new Error('Cannot send: stream aborted');
171
+ }
172
+ }
173
+ enqueueSend(message) {
174
+ return new Promise((resolve, reject) => {
175
+ this.sendQueue.push({ message, resolve, reject });
176
+ void this.processSendQueue();
177
+ });
178
+ }
179
+ processSendQueue() {
180
+ if (!this.canSendMessages())
181
+ return;
182
+ while (this.sendQueue.length > 0) {
183
+ const queued = this.sendQueue.shift();
184
+ this.sendQueuedMessage(queued);
185
+ }
186
+ }
187
+ canSendMessages() {
188
+ return this.connectionState === 'connected' && this.ws !== null;
189
+ }
190
+ sendQueuedMessage(queued) {
191
+ try {
192
+ const ws = this.ws;
193
+ if (!ws) {
194
+ throw new Error('WebSocket is not connected');
195
+ }
196
+ // Check if WebSocket is in a valid state for sending
197
+ if (ws.readyState !== WebSocket.OPEN) {
198
+ throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);
199
+ }
200
+ const binary = TxAPI_ClientMessage.toBinary(queued.message);
201
+ ws.send(binary);
202
+ queued.resolve();
203
+ }
204
+ catch (error) {
205
+ queued.reject(this.toError(error));
206
+ }
207
+ }
208
+ async drainSendQueue() {
209
+ const POLL_INTERVAL_MS = 10;
210
+ while (this.sendQueue.length > 0) {
211
+ await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);
212
+ }
213
+ }
214
+ waitForCondition(condition, intervalMs) {
215
+ return new Promise((resolve, reject) => {
216
+ if (this.abortSignal.aborted) {
217
+ return reject(this.toError(this.abortSignal.reason) ?? new Error('Stream aborted'));
218
+ }
219
+ let timeoutId;
220
+ const onAbort = () => {
221
+ clearTimeout(timeoutId);
222
+ reject(this.toError(this.abortSignal.reason) ?? new Error('Stream aborted'));
223
+ };
224
+ this.abortSignal.addEventListener('abort', onAbort, { once: true });
225
+ const check = () => {
226
+ if (condition() || this.isStreamEnded()) {
227
+ this.abortSignal.removeEventListener('abort', onAbort);
228
+ resolve();
229
+ }
230
+ else {
231
+ timeoutId = setTimeout(check, intervalMs);
232
+ }
233
+ };
234
+ check();
235
+ });
236
+ }
237
+ // === Response Delivery ===
238
+ deliverResponse(message) {
239
+ if (this.responseResolvers.length > 0) {
240
+ const resolver = this.responseResolvers.shift();
241
+ resolver.resolve({ value: message, done: false });
242
+ }
243
+ else {
244
+ this.responseQueue.push(message);
245
+ }
246
+ }
247
+ async *createResponseIterator() {
248
+ while (true) {
249
+ const result = await this.nextResponse();
250
+ if (result.done)
251
+ break;
252
+ yield result.value;
253
+ }
254
+ }
255
+ nextResponse() {
256
+ return new Promise((resolve, reject) => {
257
+ // Fast path: message already available
258
+ if (this.responseQueue.length > 0) {
259
+ const message = this.responseQueue.shift();
260
+ resolve({ value: message, done: false });
261
+ return;
262
+ }
263
+ // Stream ended
264
+ if (this.isStreamEnded()) {
265
+ if (this.connectionError) {
266
+ reject(this.connectionError);
267
+ }
268
+ else {
269
+ resolve({ value: undefined, done: true });
270
+ }
271
+ return;
272
+ }
273
+ // Wait for next message
274
+ this.responseResolvers.push({ resolve, reject });
275
+ });
276
+ }
277
+ // === Error Handling ===
278
+ handleError(error) {
279
+ if (this.isClosed())
280
+ return;
281
+ this.connectionState = 'closed';
282
+ this.connectionError = error;
283
+ this.reconnection.cancel();
284
+ this.closeWebSocket();
285
+ this.rejectAllPendingOperations(error);
286
+ }
287
+ rejectAllPendingOperations(error) {
288
+ const err = error ?? this.createStreamClosedError();
289
+ this.rejectAllSendOperations(err);
290
+ this.rejectAllResponseResolvers(err);
291
+ }
292
+ rejectAllSendOperations(error) {
293
+ while (this.sendQueue.length > 0) {
294
+ const queued = this.sendQueue.shift();
295
+ queued.reject(error);
296
+ }
297
+ }
298
+ rejectAllResponseResolvers(error) {
299
+ while (this.responseResolvers.length > 0) {
300
+ const resolver = this.responseResolvers.shift();
301
+ resolver.reject(error);
302
+ }
303
+ }
304
+ createStreamClosedError() {
305
+ if (this.abortSignal.aborted) {
306
+ const reason = this.abortSignal.reason;
307
+ if (reason instanceof Error) {
308
+ return reason;
309
+ }
310
+ return new Error('Stream aborted', { cause: reason });
311
+ }
312
+ return new Error('Stream closed');
313
+ }
314
+ // === State Checks ===
315
+ isConnectingOrConnected() {
316
+ return this.connectionState === 'connecting'
317
+ || this.connectionState === 'connected';
318
+ }
319
+ isClosed() {
320
+ return this.connectionState === 'closed';
321
+ }
322
+ isStreamEnded() {
323
+ return this.isClosed() || this.abortSignal.aborted;
324
+ }
325
+ toError(error) {
326
+ return error instanceof Error ? error : new Error(String(error));
327
+ }
328
+ }
329
+
330
+ export { WebSocketBiDiStream };
331
+ //# sourceMappingURL=websocket_stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket_stream.js","sources":["../../src/core/websocket_stream.ts"],"sourcesContent":["import { WebSocket } from 'undici';\nimport {\n TxAPI_ClientMessage as ClientMessageType,\n TxAPI_ServerMessage as ServerMessageType,\n} from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport type { BiDiStream } from './abstract_stream';\nimport Denque from 'denque';\nimport type { RetryConfig } from '../helpers/retry_strategy';\nimport { RetryStrategy } from '../helpers/retry_strategy';\n\ntype ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'closing' | 'closed';\n\ninterface QueuedMessage {\n message: ClientMessageType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\ninterface ResponseResolver {\n resolve: (value: IteratorResult<ServerMessageType>) => void;\n reject: (error: Error) => 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 implements BiDiStream<ClientMessageType, ServerMessageType> {\n // Connection\n private ws: WebSocket | null = null;\n private connectionState: ConnectionState = 'disconnected';\n private readonly url: string;\n private readonly jwtToken?: string;\n private readonly abortSignal: AbortSignal;\n private readonly reconnection: RetryStrategy;\n\n // Send management\n private readonly sendQueue = new Denque<QueuedMessage>();\n private sendCompleted = false;\n\n // Response management\n private readonly responseQueue = new Denque<ServerMessageType>();\n private responseResolvers: ResponseResolver[] = [];\n\n // Error tracking\n private connectionError: Error | null = null;\n\n // === Public API ===\n\n public readonly requests = {\n send: async (message: ClientMessageType): Promise<void> => {\n this.validateSendState();\n return this.enqueueSend(message);\n },\n\n complete: async (): Promise<void> => {\n if (this.sendCompleted) return;\n\n this.sendCompleted = true;\n await this.drainSendQueue();\n this.closeConnection();\n },\n };\n\n public readonly responses: AsyncIterable<ServerMessageType> = {\n [Symbol.asyncIterator]: () => this.createResponseIterator(),\n };\n\n constructor(\n url: string,\n abortSignal: AbortSignal,\n jwtToken?: string,\n retryConfig: Partial<RetryConfig> = {},\n ) {\n this.url = url;\n this.jwtToken = jwtToken;\n this.abortSignal = abortSignal;\n\n this.reconnection = new RetryStrategy(retryConfig, {\n onRetry: () => { void this.connect(); },\n onMaxAttemptsReached: (error) => this.handleError(error),\n });\n\n if (abortSignal.aborted) {\n this.connectionState = 'closed';\n return;\n }\n\n this.attachAbortSignalHandler();\n void this.connect();\n }\n\n // === Connection Lifecycle ===\n\n private connect(): void {\n if (this.isConnectingOrConnected() || this.abortSignal.aborted) return;\n\n this.connectionState = 'connecting';\n this.connectionError = null;\n\n try {\n this.ws = this.createWebSocket();\n this.attachWebSocketHandlers();\n } catch (error) {\n this.connectionError = this.toError(error);\n this.connectionState = 'disconnected';\n this.reconnection.schedule();\n }\n }\n\n private createWebSocket(): WebSocket {\n const options = this.jwtToken\n ? { headers: { authorization: `Bearer ${this.jwtToken}` } }\n : undefined;\n\n const ws = new (WebSocket as any)(this.url, options);\n if (ws) {\n ws.binaryType = 'arraybuffer';\n }\n return ws;\n }\n\n private attachWebSocketHandlers(): void {\n if (!this.ws) return;\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 }\n\n private attachAbortSignalHandler(): void {\n this.abortSignal.addEventListener('abort', () => this.close());\n }\n\n private onOpen(): void {\n this.connectionState = 'connected';\n this.reconnection.reset();\n void this.processSendQueue();\n }\n\n private onClose(): void {\n this.ws = null;\n\n if (this.isClosed() || this.abortSignal.aborted) return;\n\n if (this.sendCompleted) {\n this.finalizeStream();\n } else {\n this.connectionState = 'disconnected';\n this.reconnection.schedule();\n }\n }\n\n private onError(error: unknown): void {\n this.handleError(this.toError(error));\n }\n\n private onMessage(data: unknown): void {\n try {\n const message = this.parseMessage(data);\n this.deliverResponse(message);\n } catch (error) {\n this.handleError(this.toError(error));\n }\n }\n\n private closeConnection(): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.close();\n }\n }\n\n private close(): void {\n if (this.isClosed()) return;\n\n this.connectionState = 'closed';\n this.reconnection.cancel();\n this.closeWebSocket();\n this.rejectAllPendingOperations();\n }\n\n private closeWebSocket(): void {\n if (!this.ws) return;\n\n try {\n this.ws.close();\n } catch {\n // Suppress close errors\n }\n\n this.ws = null;\n }\n\n private finalizeStream(): void {\n this.connectionState = 'closed';\n this.resolveAllPendingResponses();\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 private parseMessage(data: unknown): ServerMessageType {\n if (data instanceof ArrayBuffer) {\n return ServerMessageType.fromBinary(new Uint8Array(data));\n }\n\n throw new Error(`Unsupported message format: ${typeof data}`);\n }\n\n // === Send Queue Management ===\n\n private validateSendState(): void {\n if (this.sendCompleted) {\n throw new Error('Cannot send: stream already completed');\n }\n\n if (this.abortSignal.aborted) {\n throw new Error('Cannot send: stream aborted');\n }\n }\n\n private enqueueSend(message: ClientMessageType): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.sendQueue.push({ message, resolve, reject });\n void 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 === 'connected' && this.ws !== null;\n }\n\n private sendQueuedMessage(queued: QueuedMessage): 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 = ClientMessageType.toBinary(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 = 10;\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.abortSignal.aborted) {\n return reject(this.toError(this.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.abortSignal.reason) ?? new Error('Stream aborted'));\n };\n\n this.abortSignal.addEventListener('abort', onAbort, { once: true });\n\n const check = () => {\n if (condition() || this.isStreamEnded()) {\n this.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: ServerMessageType): 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<ServerMessageType> {\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<ServerMessageType>> {\n return new Promise<IteratorResult<ServerMessageType>>((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.connectionError) {\n reject(this.connectionError);\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 // === Error Handling ===\n private handleError(error: Error): void {\n if (this.isClosed()) return;\n\n this.connectionState = 'closed';\n this.connectionError = error;\n this.reconnection.cancel();\n this.closeWebSocket();\n this.rejectAllPendingOperations(error);\n }\n\n private rejectAllPendingOperations(error?: Error): void {\n const err = error ?? this.createStreamClosedError();\n this.rejectAllSendOperations(err);\n this.rejectAllResponseResolvers(err);\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.abortSignal.aborted) {\n const reason = this.abortSignal.reason;\n if (reason instanceof Error) {\n return reason;\n }\n return new Error('Stream aborted', { cause: reason });\n }\n return new Error('Stream closed');\n }\n // === State Checks ===\n\n private isConnectingOrConnected(): boolean {\n return this.connectionState === 'connecting'\n || this.connectionState === 'connected';\n }\n\n private isClosed(): boolean {\n return this.connectionState === 'closed';\n }\n\n private isStreamEnded(): boolean {\n return this.isClosed() || this.abortSignal.aborted;\n }\n\n private toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n }\n}\n"],"names":["ServerMessageType","ClientMessageType"],"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