@milaboratories/pl-client 2.16.12 → 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.
Files changed (33) hide show
  1. package/dist/core/driver.cjs +1 -1
  2. package/dist/core/driver.cjs.map +1 -1
  3. package/dist/core/driver.js +1 -1
  4. package/dist/core/driver.js.map +1 -1
  5. package/dist/core/ll_client.cjs +17 -7
  6. package/dist/core/ll_client.cjs.map +1 -1
  7. package/dist/core/ll_client.d.ts.map +1 -1
  8. package/dist/core/ll_client.js +17 -7
  9. package/dist/core/ll_client.js.map +1 -1
  10. package/dist/core/websocket_stream.cjs +126 -129
  11. package/dist/core/websocket_stream.cjs.map +1 -1
  12. package/dist/core/websocket_stream.d.ts +29 -22
  13. package/dist/core/websocket_stream.d.ts.map +1 -1
  14. package/dist/core/websocket_stream.js +127 -130
  15. package/dist/core/websocket_stream.js.map +1 -1
  16. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +136 -0
  17. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
  18. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +75 -1
  19. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
  20. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +135 -1
  21. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
  22. package/dist/proto-rest/index.cjs +16 -2
  23. package/dist/proto-rest/index.cjs.map +1 -1
  24. package/dist/proto-rest/index.d.ts.map +1 -1
  25. package/dist/proto-rest/index.js +16 -2
  26. package/dist/proto-rest/index.js.map +1 -1
  27. package/package.json +6 -6
  28. package/src/core/driver.ts +1 -1
  29. package/src/core/ll_client.ts +25 -8
  30. package/src/core/websocket_stream.test.ts +19 -8
  31. package/src/core/websocket_stream.ts +154 -166
  32. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +179 -1
  33. package/src/proto-rest/index.ts +17 -2
@@ -1,181 +1,168 @@
1
1
  'use strict';
2
2
 
3
3
  var undici = require('undici');
4
- var api = require('../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs');
5
4
  var Denque = require('denque');
6
5
  var retry_strategy = require('../helpers/retry_strategy.cjs');
7
6
 
7
+ var ConnectionState;
8
+ (function (ConnectionState) {
9
+ ConnectionState[ConnectionState["NEW"] = 0] = "NEW";
10
+ ConnectionState[ConnectionState["CONNECTING"] = 1] = "CONNECTING";
11
+ ConnectionState[ConnectionState["CONNECTED"] = 2] = "CONNECTED";
12
+ ConnectionState[ConnectionState["CLOSING"] = 3] = "CLOSING";
13
+ ConnectionState[ConnectionState["CLOSED"] = 4] = "CLOSED";
14
+ })(ConnectionState || (ConnectionState = {}));
8
15
  /**
9
16
  * WebSocket-based bidirectional stream implementation for LLTransaction.
10
17
  * Implements BiDiStream interface which is compatible with DuplexStreamingCall.
11
18
  */
12
19
  class WebSocketBiDiStream {
20
+ url;
21
+ serializeClientMessage;
22
+ parseServerMessage;
23
+ options;
13
24
  // Connection
14
25
  ws = null;
15
- connectionState = 'disconnected';
16
- url;
17
- jwtToken;
18
- abortSignal;
26
+ connectionState = ConnectionState.NEW;
19
27
  reconnection;
20
28
  // Send management
21
29
  sendQueue = new Denque();
22
30
  sendCompleted = false;
31
+ onComplete;
23
32
  // Response management
24
33
  responseQueue = new Denque();
25
34
  responseResolvers = [];
26
35
  // Error tracking
27
- connectionError = null;
36
+ lastError;
28
37
  // === Public API ===
29
38
  requests = {
30
39
  send: async (message) => {
31
- this.validateSendState();
32
- return this.enqueueSend(message);
40
+ return await this.enqueueSend(message);
33
41
  },
34
42
  complete: async () => {
35
43
  if (this.sendCompleted)
36
44
  return;
45
+ await this.drainSendQueue(); // ensure we sent all already queued messages before closing the stream
46
+ try {
47
+ await this.onComplete(this); // custom onComplete may send additional messages
48
+ }
49
+ catch (_) {
50
+ // When 'complete' gets called concurrently with connection break or over a broken
51
+ // transaction stream (server decided it should drop transaction), server would close
52
+ // connection anyway on its end. We can safely ignore error here and just continue working.
53
+ }
37
54
  this.sendCompleted = true;
38
- await this.drainSendQueue();
39
- this.closeConnection();
40
55
  },
41
56
  };
42
57
  responses = {
43
58
  [Symbol.asyncIterator]: () => this.createResponseIterator(),
44
59
  };
45
- constructor(url, abortSignal, jwtToken, retryConfig = {}) {
60
+ close() {
61
+ this.reconnection.cancel();
62
+ if (this.connectionState < ConnectionState.CONNECTED) {
63
+ // Never reached CONNECTED state. ws.close() will never trigger 'close' event.
64
+ this.ws?.close();
65
+ this.onClose();
66
+ return;
67
+ }
68
+ if (!this.progressConnectionState(ConnectionState.CLOSING))
69
+ return;
70
+ this.ws.close();
71
+ }
72
+ constructor(url, serializeClientMessage, parseServerMessage, options = {}) {
46
73
  this.url = url;
47
- this.jwtToken = jwtToken;
48
- this.abortSignal = abortSignal;
74
+ this.serializeClientMessage = serializeClientMessage;
75
+ this.parseServerMessage = parseServerMessage;
76
+ this.options = options;
77
+ this.onComplete = this.options.onComplete ?? ((stream) => stream.close());
78
+ const retryConfig = this.options.retryConfig ?? {};
49
79
  this.reconnection = new retry_strategy.RetryStrategy(retryConfig, {
50
80
  onRetry: () => { void this.connect(); },
51
81
  onMaxAttemptsReached: (error) => this.handleError(error),
52
82
  });
53
- if (abortSignal.aborted) {
54
- this.connectionState = 'closed';
83
+ if (this.options.abortSignal?.aborted) {
84
+ this.progressConnectionState(ConnectionState.CLOSED);
55
85
  return;
56
86
  }
57
- this.attachAbortSignalHandler();
58
- void this.connect();
87
+ this.options.abortSignal?.addEventListener('abort', () => this.close());
88
+ this.connect();
59
89
  }
60
90
  // === Connection Lifecycle ===
61
91
  connect() {
62
- if (this.isConnectingOrConnected() || this.abortSignal.aborted)
92
+ if (this.options.abortSignal?.aborted)
93
+ return;
94
+ // Prevent reconnecting after first successful connection.
95
+ if (!this.progressConnectionState(ConnectionState.CONNECTING))
63
96
  return;
64
- this.connectionState = 'connecting';
65
- this.connectionError = null;
66
97
  try {
67
98
  this.ws = this.createWebSocket();
68
- this.attachWebSocketHandlers();
99
+ this.ws.addEventListener('open', () => this.onOpen());
100
+ this.ws.addEventListener('message', (event) => this.onMessage(event.data));
101
+ this.ws.addEventListener('error', (error) => this.onError(error));
102
+ this.ws.addEventListener('close', () => this.onClose());
69
103
  }
70
104
  catch (error) {
71
- this.connectionError = this.toError(error);
72
- this.connectionState = 'disconnected';
105
+ this.lastError = this.toError(error);
73
106
  this.reconnection.schedule();
74
107
  }
75
108
  }
76
109
  createWebSocket() {
77
- const options = this.jwtToken
78
- ? { headers: { authorization: `Bearer ${this.jwtToken}` } }
79
- : undefined;
110
+ const options = {};
111
+ if (this.options.jwtToken)
112
+ options.headers = { authorization: `Bearer ${this.options.jwtToken}` };
113
+ if (this.options.dispatcher)
114
+ options.dispatcher = this.options.dispatcher;
80
115
  const ws = new undici.WebSocket(this.url, options);
81
- if (ws) {
82
- ws.binaryType = 'arraybuffer';
83
- }
116
+ ws.binaryType = 'arraybuffer';
84
117
  return ws;
85
118
  }
86
- attachWebSocketHandlers() {
87
- if (!this.ws)
88
- return;
89
- this.ws.addEventListener('open', () => this.onOpen());
90
- this.ws.addEventListener('message', (event) => this.onMessage(event.data));
91
- this.ws.addEventListener('error', (error) => this.onError(error));
92
- this.ws.addEventListener('close', () => this.onClose());
93
- }
94
- attachAbortSignalHandler() {
95
- this.abortSignal.addEventListener('abort', () => this.close());
96
- }
97
119
  onOpen() {
98
- this.connectionState = 'connected';
99
- this.reconnection.reset();
100
- void this.processSendQueue();
120
+ this.progressConnectionState(ConnectionState.CONNECTED);
121
+ this.processSendQueue();
101
122
  }
102
- onClose() {
103
- this.ws = null;
104
- if (this.isClosed() || this.abortSignal.aborted)
123
+ onMessage(data) {
124
+ if (!(data instanceof ArrayBuffer)) {
125
+ this.handleError(new Error(`Unexpected WS message format: ${typeof data}`));
105
126
  return;
106
- if (this.sendCompleted) {
107
- this.finalizeStream();
108
- }
109
- else {
110
- this.connectionState = 'disconnected';
111
- this.reconnection.schedule();
112
127
  }
113
- }
114
- onError(error) {
115
- this.handleError(this.toError(error));
116
- }
117
- onMessage(data) {
118
128
  try {
119
- const message = this.parseMessage(data);
129
+ const message = this.parseServerMessage(new Uint8Array(data));
120
130
  this.deliverResponse(message);
121
131
  }
122
132
  catch (error) {
123
133
  this.handleError(this.toError(error));
124
134
  }
125
135
  }
126
- closeConnection() {
127
- if (this.ws?.readyState === undici.WebSocket.OPEN) {
128
- this.ws.close();
129
- }
130
- }
131
- close() {
132
- if (this.isClosed())
133
- return;
134
- this.connectionState = 'closed';
135
- this.reconnection.cancel();
136
- this.closeWebSocket();
137
- this.rejectAllPendingOperations();
138
- }
139
- closeWebSocket() {
140
- if (!this.ws)
136
+ onError(error) {
137
+ if (this.connectionState < ConnectionState.CONNECTED) {
138
+ // Try to connect several times until we succeed or run out of attempts.
139
+ this.lastError = this.toError(error);
140
+ this.reconnection.schedule();
141
141
  return;
142
- try {
143
- this.ws.close();
144
- }
145
- catch {
146
- // Suppress close errors
147
142
  }
148
- this.ws = null;
149
- }
150
- finalizeStream() {
151
- this.connectionState = 'closed';
152
- this.resolveAllPendingResponses();
143
+ this.handleError(this.toError(error));
153
144
  }
154
- resolveAllPendingResponses() {
155
- while (this.responseResolvers.length > 0) {
156
- const resolver = this.responseResolvers.shift();
157
- resolver.resolve({ value: undefined, done: true });
145
+ onClose() {
146
+ this.progressConnectionState(ConnectionState.CLOSED);
147
+ if (!this.lastError) {
148
+ this.rejectAllSendOperations(this.createStreamClosedError());
149
+ this.resolveAllPendingResponses(); // unblock active async iterator
158
150
  }
159
- }
160
- parseMessage(data) {
161
- if (data instanceof ArrayBuffer) {
162
- return api.TxAPI_ServerMessage.fromBinary(new Uint8Array(data));
151
+ else {
152
+ this.rejectAllPendingOperations(this.lastError);
163
153
  }
164
- throw new Error(`Unsupported message format: ${typeof data}`);
165
154
  }
166
155
  // === Send Queue Management ===
167
- validateSendState() {
156
+ enqueueSend(message) {
168
157
  if (this.sendCompleted) {
169
158
  throw new Error('Cannot send: stream already completed');
170
159
  }
171
- if (this.abortSignal.aborted) {
160
+ if (this.options.abortSignal?.aborted) {
172
161
  throw new Error('Cannot send: stream aborted');
173
162
  }
174
- }
175
- enqueueSend(message) {
176
163
  return new Promise((resolve, reject) => {
177
164
  this.sendQueue.push({ message, resolve, reject });
178
- void this.processSendQueue();
165
+ this.processSendQueue();
179
166
  });
180
167
  }
181
168
  processSendQueue() {
@@ -187,7 +174,7 @@ class WebSocketBiDiStream {
187
174
  }
188
175
  }
189
176
  canSendMessages() {
190
- return this.connectionState === 'connected' && this.ws !== null;
177
+ return this.connectionState === ConnectionState.CONNECTED;
191
178
  }
192
179
  sendQueuedMessage(queued) {
193
180
  try {
@@ -199,7 +186,7 @@ class WebSocketBiDiStream {
199
186
  if (ws.readyState !== undici.WebSocket.OPEN) {
200
187
  throw new Error(`WebSocket is not open (readyState: ${ws.readyState})`);
201
188
  }
202
- const binary = api.TxAPI_ClientMessage.toBinary(queued.message);
189
+ const binary = this.serializeClientMessage(queued.message);
203
190
  ws.send(binary);
204
191
  queued.resolve();
205
192
  }
@@ -208,25 +195,25 @@ class WebSocketBiDiStream {
208
195
  }
209
196
  }
210
197
  async drainSendQueue() {
211
- const POLL_INTERVAL_MS = 10;
198
+ const POLL_INTERVAL_MS = 5;
212
199
  while (this.sendQueue.length > 0) {
213
200
  await this.waitForCondition(() => this.sendQueue.length === 0, POLL_INTERVAL_MS);
214
201
  }
215
202
  }
216
203
  waitForCondition(condition, intervalMs) {
217
204
  return new Promise((resolve, reject) => {
218
- if (this.abortSignal.aborted) {
219
- return reject(this.toError(this.abortSignal.reason) ?? new Error('Stream aborted'));
205
+ if (this.options.abortSignal?.aborted) {
206
+ return reject(this.toError(this.options.abortSignal.reason) ?? new Error('Stream aborted'));
220
207
  }
221
208
  let timeoutId;
222
209
  const onAbort = () => {
223
210
  clearTimeout(timeoutId);
224
- reject(this.toError(this.abortSignal.reason) ?? new Error('Stream aborted'));
211
+ reject(this.toError(this.options.abortSignal?.reason) ?? new Error('Stream aborted'));
225
212
  };
226
- this.abortSignal.addEventListener('abort', onAbort, { once: true });
213
+ this.options.abortSignal?.addEventListener('abort', onAbort, { once: true });
227
214
  const check = () => {
228
215
  if (condition() || this.isStreamEnded()) {
229
- this.abortSignal.removeEventListener('abort', onAbort);
216
+ this.options.abortSignal?.removeEventListener('abort', onAbort);
230
217
  resolve();
231
218
  }
232
219
  else {
@@ -264,8 +251,8 @@ class WebSocketBiDiStream {
264
251
  }
265
252
  // Stream ended
266
253
  if (this.isStreamEnded()) {
267
- if (this.connectionError) {
268
- reject(this.connectionError);
254
+ if (this.lastError) {
255
+ reject(this.lastError);
269
256
  }
270
257
  else {
271
258
  resolve({ value: undefined, done: true });
@@ -276,20 +263,20 @@ class WebSocketBiDiStream {
276
263
  this.responseResolvers.push({ resolve, reject });
277
264
  });
278
265
  }
266
+ resolveAllPendingResponses() {
267
+ while (this.responseResolvers.length > 0) {
268
+ const resolver = this.responseResolvers.shift();
269
+ resolver.resolve({ value: undefined, done: true });
270
+ }
271
+ }
279
272
  // === Error Handling ===
280
273
  handleError(error) {
281
- if (this.isClosed())
282
- return;
283
- this.connectionState = 'closed';
284
- this.connectionError = error;
285
- this.reconnection.cancel();
286
- this.closeWebSocket();
287
- this.rejectAllPendingOperations(error);
274
+ this.lastError = error;
275
+ this.close();
288
276
  }
289
277
  rejectAllPendingOperations(error) {
290
- const err = error ?? this.createStreamClosedError();
291
- this.rejectAllSendOperations(err);
292
- this.rejectAllResponseResolvers(err);
278
+ this.rejectAllSendOperations(error);
279
+ this.rejectAllResponseResolvers(error);
293
280
  }
294
281
  rejectAllSendOperations(error) {
295
282
  while (this.sendQueue.length > 0) {
@@ -304,8 +291,8 @@ class WebSocketBiDiStream {
304
291
  }
305
292
  }
306
293
  createStreamClosedError() {
307
- if (this.abortSignal.aborted) {
308
- const reason = this.abortSignal.reason;
294
+ if (this.options.abortSignal?.aborted) {
295
+ const reason = this.options.abortSignal.reason;
309
296
  if (reason instanceof Error) {
310
297
  return reason;
311
298
  }
@@ -313,19 +300,29 @@ class WebSocketBiDiStream {
313
300
  }
314
301
  return new Error('Stream closed');
315
302
  }
316
- // === State Checks ===
317
- isConnectingOrConnected() {
318
- return this.connectionState === 'connecting'
319
- || this.connectionState === 'connected';
320
- }
321
- isClosed() {
322
- return this.connectionState === 'closed';
323
- }
303
+ // === Helpers ===
324
304
  isStreamEnded() {
325
- return this.isClosed() || this.abortSignal.aborted;
305
+ return this.connectionState === ConnectionState.CLOSED || this.options.abortSignal?.aborted || false;
326
306
  }
327
307
  toError(error) {
328
- return error instanceof Error ? error : new Error(String(error));
308
+ if (error instanceof Error)
309
+ return error;
310
+ if (error instanceof undici.ErrorEvent)
311
+ return error.error;
312
+ return new Error(String(error));
313
+ }
314
+ /**
315
+ * Connection state progresses linearly from NEW to CLOSED and never goes back.
316
+ * This internal contract dramatically simplifies the internal stream state management.
317
+ *
318
+ * If you ever feel the need to make this contract less strict, think twice.
319
+ */
320
+ progressConnectionState(newState) {
321
+ if (newState < this.connectionState) {
322
+ return false;
323
+ }
324
+ this.connectionState = newState;
325
+ return true;
329
326
  }
330
327
  }
331
328
 
@@ -1 +1 @@
1
- {"version":3,"file":"websocket_stream.cjs","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":["RetryStrategy","WebSocket","ServerMessageType","ClientMessageType"],"mappings":"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"}
1
+ {"version":3,"file":"websocket_stream.cjs","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":["RetryStrategy","WebSocket","ErrorEvent"],"mappings":"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"}
@@ -1,43 +1,44 @@
1
- import { TxAPI_ClientMessage as ClientMessageType, TxAPI_ServerMessage as ServerMessageType } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
1
+ import { type Dispatcher } from 'undici';
2
2
  import type { BiDiStream } from './abstract_stream';
3
3
  import type { RetryConfig } from '../helpers/retry_strategy';
4
+ export type WSStreamOptions<ClientMsg extends object, ServerMsg extends object> = {
5
+ abortSignal?: AbortSignal;
6
+ dispatcher?: Dispatcher;
7
+ jwtToken?: string;
8
+ retryConfig?: Partial<RetryConfig>;
9
+ onComplete?: (stream: WebSocketBiDiStream<ClientMsg, ServerMsg>) => void | Promise<void>;
10
+ };
4
11
  /**
5
12
  * WebSocket-based bidirectional stream implementation for LLTransaction.
6
13
  * Implements BiDiStream interface which is compatible with DuplexStreamingCall.
7
14
  */
8
- export declare class WebSocketBiDiStream implements BiDiStream<ClientMessageType, ServerMessageType> {
15
+ export declare class WebSocketBiDiStream<ClientMsg extends object, ServerMsg extends object> implements BiDiStream<ClientMsg, ServerMsg> {
16
+ private readonly url;
17
+ private readonly serializeClientMessage;
18
+ private readonly parseServerMessage;
19
+ private readonly options;
9
20
  private ws;
10
21
  private connectionState;
11
- private readonly url;
12
- private readonly jwtToken?;
13
- private readonly abortSignal;
14
22
  private readonly reconnection;
15
23
  private readonly sendQueue;
16
24
  private sendCompleted;
25
+ private readonly onComplete;
17
26
  private readonly responseQueue;
18
27
  private responseResolvers;
19
- private connectionError;
28
+ private lastError?;
20
29
  readonly requests: {
21
- send: (message: ClientMessageType) => Promise<void>;
30
+ send: (message: ClientMsg) => Promise<void>;
22
31
  complete: () => Promise<void>;
23
32
  };
24
- readonly responses: AsyncIterable<ServerMessageType>;
25
- constructor(url: string, abortSignal: AbortSignal, jwtToken?: string, retryConfig?: Partial<RetryConfig>);
33
+ readonly responses: AsyncIterable<ServerMsg>;
34
+ close(): void;
35
+ constructor(url: string, serializeClientMessage: (message: ClientMsg) => Uint8Array, parseServerMessage: (data: Uint8Array) => ServerMsg, options?: WSStreamOptions<ClientMsg, ServerMsg>);
26
36
  private connect;
27
37
  private createWebSocket;
28
- private attachWebSocketHandlers;
29
- private attachAbortSignalHandler;
30
38
  private onOpen;
31
- private onClose;
32
- private onError;
33
39
  private onMessage;
34
- private closeConnection;
35
- private close;
36
- private closeWebSocket;
37
- private finalizeStream;
38
- private resolveAllPendingResponses;
39
- private parseMessage;
40
- private validateSendState;
40
+ private onError;
41
+ private onClose;
41
42
  private enqueueSend;
42
43
  private processSendQueue;
43
44
  private canSendMessages;
@@ -47,14 +48,20 @@ export declare class WebSocketBiDiStream implements BiDiStream<ClientMessageType
47
48
  private deliverResponse;
48
49
  private createResponseIterator;
49
50
  private nextResponse;
51
+ private resolveAllPendingResponses;
50
52
  private handleError;
51
53
  private rejectAllPendingOperations;
52
54
  private rejectAllSendOperations;
53
55
  private rejectAllResponseResolvers;
54
56
  private createStreamClosedError;
55
- private isConnectingOrConnected;
56
- private isClosed;
57
57
  private isStreamEnded;
58
58
  private toError;
59
+ /**
60
+ * Connection state progresses linearly from NEW to CLOSED and never goes back.
61
+ * This internal contract dramatically simplifies the internal stream state management.
62
+ *
63
+ * If you ever feel the need to make this contract less strict, think twice.
64
+ */
65
+ private progressConnectionState;
59
66
  }
60
67
  //# sourceMappingURL=websocket_stream.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"websocket_stream.d.ts","sourceRoot":"","sources":["../../src/core/websocket_stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,UAAU,EAAc,MAAM,QAAQ,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAsB7D,MAAM,MAAM,eAAe,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;IAChF,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAEnC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1F,CAAC;AAEF;;;GAGG;AACH,qBAAa,mBAAmB,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,CAAE,YAAW,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC;IA2D5H,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA5D1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAG7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IACpE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8E;IAGzG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,iBAAiB,CAAqC;IAG9D,OAAO,CAAC,SAAS,CAAC,CAAQ;IAI1B,SAAgB,QAAQ;wBACA,SAAS,KAAG,OAAO,CAAC,IAAI,CAAC;wBAI3B,OAAO,CAAC,IAAI,CAAC;MAajC;IAEF,SAAgB,SAAS,EAAE,aAAa,CAAC,SAAS,CAAC,CAEjD;IAEK,KAAK,IAAI,IAAI;gBAeD,GAAG,EAAE,MAAM,EACX,sBAAsB,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,UAAU,EAC1D,kBAAkB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,EACnD,OAAO,GAAE,eAAe,CAAC,SAAS,EAAE,SAAS,CAAM;IAqBtE,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,WAAW;IAenB,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;IAwBpB,OAAO,CAAC,0BAA0B;IASlC,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,0BAA0B;IAKlC,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;IAMf;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;CAOhC"}