@lasersell/lasersell-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,995 @@
1
+ import WebSocket from "ws";
2
+ import { clientMessageToText, serverMessageFromText, } from "./proto.js";
3
+ const MIN_RECONNECT_BACKOFF_MS = 100;
4
+ const MAX_RECONNECT_BACKOFF_MS = 2_000;
5
+ export const STREAM_ENDPOINT = "wss://stream.lasersell.io/v1/ws";
6
+ export const LOCAL_STREAM_ENDPOINT = "ws://localhost:8082/v1/ws";
7
+ export function singleWalletStreamConfigure(walletPubkey, strategy, deadlineTimeoutSec) {
8
+ return {
9
+ wallet_pubkeys: [walletPubkey],
10
+ strategy,
11
+ deadline_timeout_sec: deadlineTimeoutSec,
12
+ };
13
+ }
14
+ export function strategyConfigFromOptional(strategy = {}) {
15
+ return {
16
+ target_profit_pct: strategy.target_profit_pct ?? 0,
17
+ stop_loss_pct: strategy.stop_loss_pct ?? 0,
18
+ };
19
+ }
20
+ export function singleWalletStreamConfigureOptional(walletPubkey, strategy = {}, deadlineTimeoutSec = 0) {
21
+ return {
22
+ wallet_pubkeys: [walletPubkey],
23
+ strategy: strategyConfigFromOptional(strategy),
24
+ deadline_timeout_sec: deadlineTimeoutSec,
25
+ };
26
+ }
27
+ export class StreamClientError extends Error {
28
+ kind;
29
+ constructor(kind, message, cause) {
30
+ super(message, cause === undefined ? undefined : { cause });
31
+ this.name = "StreamClientError";
32
+ this.kind = kind;
33
+ }
34
+ static websocket(cause) {
35
+ return new StreamClientError("websocket", `websocket error: ${stringifyError(cause)}`, cause);
36
+ }
37
+ static json(cause) {
38
+ return new StreamClientError("json", `json error: ${stringifyError(cause)}`, cause);
39
+ }
40
+ static invalidApiKeyHeader(cause) {
41
+ return new StreamClientError("invalid_api_key_header", `invalid api-key header: ${stringifyError(cause)}`, cause);
42
+ }
43
+ static sendQueueClosed() {
44
+ return new StreamClientError("send_queue_closed", "send queue is closed");
45
+ }
46
+ static protocol(message) {
47
+ return new StreamClientError("protocol", `protocol error: ${message}`);
48
+ }
49
+ }
50
+ export class StreamClient {
51
+ apiKey;
52
+ local = false;
53
+ endpointOverride;
54
+ constructor(apiKey) {
55
+ this.apiKey = apiKey;
56
+ }
57
+ withLocalMode(local) {
58
+ this.local = local;
59
+ return this;
60
+ }
61
+ withEndpoint(endpoint) {
62
+ this.endpointOverride = endpoint.trimEnd();
63
+ return this;
64
+ }
65
+ async connect(configure) {
66
+ validateStrategyAndDeadline(configure.strategy, configure.deadline_timeout_sec ?? 0);
67
+ const worker = new StreamConnectionWorker(this.endpoint(), this.apiKey, configure);
68
+ await worker.waitReady();
69
+ return new StreamConnection(worker);
70
+ }
71
+ async connectLanes(configure, options = {}) {
72
+ validateStrategyAndDeadline(configure.strategy, configure.deadline_timeout_sec ?? 0);
73
+ const worker = new StreamConnectionWorker(this.endpoint(), this.apiKey, configure, {
74
+ inboundMode: "lanes",
75
+ lowPriorityCapacity: options.lowPriorityCapacity,
76
+ });
77
+ await worker.waitReady();
78
+ return new StreamConnectionLanes(worker);
79
+ }
80
+ endpoint() {
81
+ if (this.endpointOverride !== undefined) {
82
+ return this.endpointOverride;
83
+ }
84
+ if (this.local) {
85
+ return LOCAL_STREAM_ENDPOINT;
86
+ }
87
+ return STREAM_ENDPOINT;
88
+ }
89
+ }
90
+ export function validateStrategyAndDeadline(strategy, deadlineTimeoutSec) {
91
+ validateStrategyValue(strategy.target_profit_pct, "strategy.target_profit_pct");
92
+ validateStrategyValue(strategy.stop_loss_pct, "strategy.stop_loss_pct");
93
+ const deadline = Number(deadlineTimeoutSec);
94
+ if (!Number.isFinite(deadline)) {
95
+ throw StreamClientError.protocol("deadline_timeout_sec must be a finite number");
96
+ }
97
+ if (deadline < 0) {
98
+ throw StreamClientError.protocol("deadline_timeout_sec must be >= 0");
99
+ }
100
+ if (strategy.target_profit_pct > 0 ||
101
+ strategy.stop_loss_pct > 0 ||
102
+ deadline > 0) {
103
+ return;
104
+ }
105
+ throw StreamClientError.protocol("at least one of strategy.target_profit_pct, strategy.stop_loss_pct, or deadline_timeout_sec must be > 0");
106
+ }
107
+ function validateStrategyValue(value, field) {
108
+ if (!Number.isFinite(value)) {
109
+ throw StreamClientError.protocol(`${field} must be a finite number`);
110
+ }
111
+ if (value < 0) {
112
+ throw StreamClientError.protocol(`${field} must be >= 0`);
113
+ }
114
+ }
115
+ export class StreamConnection {
116
+ worker;
117
+ constructor(worker) {
118
+ this.worker = worker;
119
+ }
120
+ sender() {
121
+ return new StreamSender(this.worker);
122
+ }
123
+ split() {
124
+ return [this.sender(), new StreamReceiver(this.worker)];
125
+ }
126
+ async recv() {
127
+ return await this.worker.recv();
128
+ }
129
+ close() {
130
+ this.worker.close();
131
+ }
132
+ }
133
+ export class StreamConnectionLanes {
134
+ worker;
135
+ constructor(worker) {
136
+ this.worker = worker;
137
+ }
138
+ sender() {
139
+ return new StreamSender(this.worker);
140
+ }
141
+ highReceiver() {
142
+ return new StreamHighReceiver(this.worker);
143
+ }
144
+ lowReceiver() {
145
+ return new StreamLowReceiver(this.worker);
146
+ }
147
+ split() {
148
+ return [this.sender(), this.highReceiver(), this.lowReceiver()];
149
+ }
150
+ close() {
151
+ this.worker.close();
152
+ }
153
+ }
154
+ export class StreamHighReceiver {
155
+ worker;
156
+ constructor(worker) {
157
+ this.worker = worker;
158
+ }
159
+ async recv() {
160
+ return await this.worker.recvHigh();
161
+ }
162
+ async *[Symbol.asyncIterator]() {
163
+ while (true) {
164
+ const message = await this.recv();
165
+ if (message === null) {
166
+ break;
167
+ }
168
+ yield message;
169
+ }
170
+ }
171
+ }
172
+ export class StreamLowReceiver {
173
+ worker;
174
+ constructor(worker) {
175
+ this.worker = worker;
176
+ }
177
+ async recv() {
178
+ return await this.worker.recvLow();
179
+ }
180
+ async *[Symbol.asyncIterator]() {
181
+ while (true) {
182
+ const message = await this.recv();
183
+ if (message === null) {
184
+ break;
185
+ }
186
+ yield message;
187
+ }
188
+ }
189
+ }
190
+ export class StreamReceiver {
191
+ worker;
192
+ constructor(worker) {
193
+ this.worker = worker;
194
+ }
195
+ async recv() {
196
+ return await this.worker.recv();
197
+ }
198
+ async *[Symbol.asyncIterator]() {
199
+ while (true) {
200
+ const message = await this.recv();
201
+ if (message === null) {
202
+ break;
203
+ }
204
+ yield message;
205
+ }
206
+ }
207
+ }
208
+ export class StreamSender {
209
+ worker;
210
+ constructor(worker) {
211
+ this.worker = worker;
212
+ }
213
+ send(message) {
214
+ this.worker.enqueue(message);
215
+ }
216
+ ping(client_time_ms) {
217
+ this.send({
218
+ type: "ping",
219
+ client_time_ms,
220
+ });
221
+ }
222
+ updateStrategy(strategy) {
223
+ this.send({
224
+ type: "update_strategy",
225
+ strategy,
226
+ });
227
+ }
228
+ closePosition(selector) {
229
+ this.send(closeMessage(normalizePositionSelector(selector)));
230
+ }
231
+ closeById(positionId) {
232
+ this.closePosition({ position_id: positionId });
233
+ }
234
+ requestExitSignal(selector, slippage_bps) {
235
+ this.send(requestExitSignalMessage(normalizePositionSelector(selector), slippage_bps));
236
+ }
237
+ requestExitSignalById(positionId, slippage_bps) {
238
+ this.requestExitSignal({ position_id: positionId }, slippage_bps);
239
+ }
240
+ }
241
+ function closeMessage(selector) {
242
+ if ("token_account" in selector) {
243
+ return {
244
+ type: "close_position",
245
+ token_account: selector.token_account,
246
+ };
247
+ }
248
+ return {
249
+ type: "close_position",
250
+ position_id: selector.position_id,
251
+ };
252
+ }
253
+ function requestExitSignalMessage(selector, slippageBps) {
254
+ if ("token_account" in selector) {
255
+ return {
256
+ type: "request_exit_signal",
257
+ token_account: selector.token_account,
258
+ slippage_bps: slippageBps,
259
+ };
260
+ }
261
+ return {
262
+ type: "request_exit_signal",
263
+ position_id: selector.position_id,
264
+ slippage_bps: slippageBps,
265
+ };
266
+ }
267
+ function normalizePositionSelector(selector) {
268
+ if (typeof selector === "string") {
269
+ return {
270
+ token_account: selector,
271
+ };
272
+ }
273
+ if (typeof selector === "number") {
274
+ return {
275
+ position_id: selector,
276
+ };
277
+ }
278
+ if ("token_account" in selector && typeof selector.token_account === "string") {
279
+ return {
280
+ token_account: selector.token_account,
281
+ };
282
+ }
283
+ if ("position_id" in selector && typeof selector.position_id === "number") {
284
+ return {
285
+ position_id: selector.position_id,
286
+ };
287
+ }
288
+ if ("tokenAccount" in selector && typeof selector.tokenAccount === "string") {
289
+ return {
290
+ token_account: selector.tokenAccount,
291
+ };
292
+ }
293
+ if ("positionId" in selector && typeof selector.positionId === "number") {
294
+ return {
295
+ position_id: selector.positionId,
296
+ };
297
+ }
298
+ throw StreamClientError.protocol("position selector must be token account string or position id number");
299
+ }
300
+ class StreamConnectionWorker {
301
+ endpoint;
302
+ apiKey;
303
+ configure;
304
+ inboundMode;
305
+ inboundCombined;
306
+ inboundHigh;
307
+ inboundLow;
308
+ outbound = new AsyncQueue();
309
+ currentSocket = null;
310
+ stopped = false;
311
+ readyPromise;
312
+ readySettled = false;
313
+ resolveReady;
314
+ rejectReady;
315
+ constructor(endpoint, apiKey, configure, options = {}) {
316
+ this.endpoint = endpoint;
317
+ this.apiKey = apiKey;
318
+ this.configure = configure;
319
+ this.inboundMode = options.inboundMode ?? "combined";
320
+ if (this.inboundMode === "combined") {
321
+ this.inboundCombined = new AsyncQueue();
322
+ }
323
+ else {
324
+ this.inboundHigh = new AsyncQueue();
325
+ this.inboundLow = new AsyncQueue({
326
+ maxLen: options.lowPriorityCapacity ?? 1024,
327
+ dropOldest: true,
328
+ });
329
+ }
330
+ this.readyPromise = new Promise((resolve, reject) => {
331
+ this.resolveReady = resolve;
332
+ this.rejectReady = (error) => {
333
+ reject(error);
334
+ };
335
+ });
336
+ void this.run();
337
+ }
338
+ async waitReady() {
339
+ return await this.readyPromise;
340
+ }
341
+ enqueue(message) {
342
+ if (this.stopped) {
343
+ throw StreamClientError.sendQueueClosed();
344
+ }
345
+ this.outbound.push(message);
346
+ }
347
+ async recv() {
348
+ if (this.inboundCombined === undefined) {
349
+ throw StreamClientError.protocol("recv() is not available for lane-mode connections; use StreamConnectionLanes receivers");
350
+ }
351
+ return await this.inboundCombined.shift();
352
+ }
353
+ async recvHigh() {
354
+ if (this.inboundHigh === undefined) {
355
+ throw StreamClientError.protocol("recvHigh() is only available for lane-mode connections");
356
+ }
357
+ return await this.inboundHigh.shift();
358
+ }
359
+ async recvLow() {
360
+ if (this.inboundLow === undefined) {
361
+ throw StreamClientError.protocol("recvLow() is only available for lane-mode connections");
362
+ }
363
+ return await this.inboundLow.shift();
364
+ }
365
+ close() {
366
+ if (this.stopped) {
367
+ return;
368
+ }
369
+ this.stopped = true;
370
+ this.outbound.close();
371
+ if (this.currentSocket !== null) {
372
+ safeClose(this.currentSocket);
373
+ }
374
+ }
375
+ async run() {
376
+ let backoffMs = MIN_RECONNECT_BACKOFF_MS;
377
+ while (!this.stopped) {
378
+ try {
379
+ const outcome = await this.runConnectedSession();
380
+ if (outcome === "graceful_shutdown") {
381
+ break;
382
+ }
383
+ backoffMs = MIN_RECONNECT_BACKOFF_MS;
384
+ }
385
+ catch (error) {
386
+ const mapped = asStreamClientError(error);
387
+ if (!this.readySettled) {
388
+ this.setReadyError(mapped);
389
+ break;
390
+ }
391
+ }
392
+ if (this.stopped) {
393
+ break;
394
+ }
395
+ const shouldContinue = await sleepWithStop(backoffMs, () => this.stopped);
396
+ if (!shouldContinue) {
397
+ break;
398
+ }
399
+ backoffMs = Math.min(backoffMs * 2, MAX_RECONNECT_BACKOFF_MS);
400
+ }
401
+ if (!this.readySettled) {
402
+ this.setReadyError(StreamClientError.sendQueueClosed());
403
+ }
404
+ this.stopped = true;
405
+ this.outbound.close();
406
+ this.closeInbound();
407
+ }
408
+ async runConnectedSession() {
409
+ const { socket, frames } = await openSocket(this.endpoint, this.apiKey);
410
+ this.currentSocket = socket;
411
+ try {
412
+ const firstServerMessage = await recvServerMessageBeforeConfigure(socket, frames);
413
+ if (firstServerMessage.type !== "hello_ok") {
414
+ throw StreamClientError.protocol("expected first server message to be hello_ok");
415
+ }
416
+ this.pushInbound(firstServerMessage);
417
+ const configureMessage = {
418
+ type: "configure",
419
+ wallet_pubkeys: [...this.configure.wallet_pubkeys],
420
+ strategy: { ...this.configure.strategy },
421
+ };
422
+ await sendClientMessage(socket, configureMessage);
423
+ const configuredMessage = await recvServerMessageAfterConfigure(socket, frames);
424
+ this.pushInbound(configuredMessage);
425
+ if (!this.readySettled) {
426
+ this.readySettled = true;
427
+ this.resolveReady();
428
+ }
429
+ while (!this.stopped) {
430
+ const nextOutbound = this.outbound.shiftNow();
431
+ if (nextOutbound !== undefined) {
432
+ try {
433
+ await sendClientMessage(socket, nextOutbound);
434
+ continue;
435
+ }
436
+ catch {
437
+ return "reconnect";
438
+ }
439
+ }
440
+ const frame = frames.shiftNow();
441
+ if (frame !== undefined) {
442
+ const outcome = await this.handleFrame(socket, frame);
443
+ if (outcome === "reconnect") {
444
+ return "reconnect";
445
+ }
446
+ continue;
447
+ }
448
+ const outboundWait = this.outbound.shiftCancelable();
449
+ const frameWait = frames.waitNextCancelable();
450
+ const next = await Promise.race([
451
+ outboundWait.promise.then((message) => ({
452
+ source: "outbound",
453
+ message,
454
+ })),
455
+ frameWait.promise.then((nextFrame) => ({
456
+ source: "frame",
457
+ frame: nextFrame,
458
+ })),
459
+ ]);
460
+ if (next.source === "outbound") {
461
+ frameWait.cancel();
462
+ if (next.message === null) {
463
+ return "graceful_shutdown";
464
+ }
465
+ try {
466
+ await sendClientMessage(socket, next.message);
467
+ continue;
468
+ }
469
+ catch {
470
+ return "reconnect";
471
+ }
472
+ }
473
+ outboundWait.cancel();
474
+ const outcome = await this.handleFrame(socket, next.frame);
475
+ if (outcome === "reconnect") {
476
+ return "reconnect";
477
+ }
478
+ }
479
+ return "graceful_shutdown";
480
+ }
481
+ finally {
482
+ this.currentSocket = null;
483
+ safeClose(socket);
484
+ }
485
+ }
486
+ async handleFrame(socket, frame) {
487
+ switch (frame.kind) {
488
+ case "text": {
489
+ let parsed;
490
+ try {
491
+ parsed = serverMessageFromText(frame.text);
492
+ }
493
+ catch {
494
+ return "reconnect";
495
+ }
496
+ this.pushInbound(parsed);
497
+ return "continue";
498
+ }
499
+ case "ping": {
500
+ try {
501
+ socket.pong(frame.payload);
502
+ }
503
+ catch {
504
+ return "reconnect";
505
+ }
506
+ return "continue";
507
+ }
508
+ case "pong": {
509
+ return "continue";
510
+ }
511
+ case "binary":
512
+ case "close":
513
+ case "error": {
514
+ return "reconnect";
515
+ }
516
+ default: {
517
+ const _unreachable = frame;
518
+ return _unreachable;
519
+ }
520
+ }
521
+ }
522
+ setReadyError(error) {
523
+ if (this.readySettled) {
524
+ return;
525
+ }
526
+ this.readySettled = true;
527
+ this.rejectReady(error);
528
+ }
529
+ pushInbound(message) {
530
+ if (this.inboundMode === "combined") {
531
+ this.inboundCombined.push(message);
532
+ return;
533
+ }
534
+ if (message.type === "pnl_update") {
535
+ this.inboundLow.push(message);
536
+ }
537
+ else {
538
+ this.inboundHigh.push(message);
539
+ }
540
+ }
541
+ closeInbound() {
542
+ this.inboundCombined?.close();
543
+ this.inboundHigh?.close();
544
+ this.inboundLow?.close();
545
+ }
546
+ }
547
+ async function openSocket(url, apiKey) {
548
+ return await new Promise((resolve, reject) => {
549
+ let settled = false;
550
+ let socket;
551
+ try {
552
+ socket = new WebSocket(url, {
553
+ headers: {
554
+ "x-api-key": apiKey,
555
+ },
556
+ });
557
+ }
558
+ catch (error) {
559
+ reject(StreamClientError.invalidApiKeyHeader(error));
560
+ return;
561
+ }
562
+ // Attach frame listeners before waiting for `open` so we never miss an
563
+ // immediate server hello frame on low-latency links.
564
+ const frames = new WebSocketFrameQueue(socket);
565
+ const onOpen = () => {
566
+ if (settled) {
567
+ return;
568
+ }
569
+ settled = true;
570
+ cleanup();
571
+ resolve({ socket, frames });
572
+ };
573
+ const onError = (error) => {
574
+ if (settled) {
575
+ return;
576
+ }
577
+ settled = true;
578
+ cleanup();
579
+ reject(StreamClientError.websocket(error));
580
+ };
581
+ const onClose = () => {
582
+ if (settled) {
583
+ return;
584
+ }
585
+ settled = true;
586
+ cleanup();
587
+ reject(StreamClientError.protocol("socket closed before open"));
588
+ };
589
+ const cleanup = () => {
590
+ socket.off("open", onOpen);
591
+ socket.off("error", onError);
592
+ socket.off("close", onClose);
593
+ };
594
+ socket.on("open", onOpen);
595
+ socket.on("error", onError);
596
+ socket.on("close", onClose);
597
+ });
598
+ }
599
+ async function recvServerMessageBeforeConfigure(socket, frames) {
600
+ while (true) {
601
+ const frame = await frames.waitNext();
602
+ switch (frame.kind) {
603
+ case "text": {
604
+ try {
605
+ return serverMessageFromText(frame.text);
606
+ }
607
+ catch (error) {
608
+ throw StreamClientError.json(error);
609
+ }
610
+ }
611
+ case "ping": {
612
+ socket.pong(frame.payload);
613
+ break;
614
+ }
615
+ case "pong": {
616
+ break;
617
+ }
618
+ case "close": {
619
+ throw StreamClientError.protocol("socket closed before hello_ok");
620
+ }
621
+ case "error": {
622
+ throw StreamClientError.websocket(frame.error);
623
+ }
624
+ case "binary": {
625
+ throw StreamClientError.protocol("received non-text frame before hello_ok");
626
+ }
627
+ default: {
628
+ const _unreachable = frame;
629
+ throw _unreachable;
630
+ }
631
+ }
632
+ }
633
+ }
634
+ async function recvServerMessageAfterConfigure(socket, frames) {
635
+ while (true) {
636
+ const frame = await frames.waitNext();
637
+ switch (frame.kind) {
638
+ case "text": {
639
+ try {
640
+ return serverMessageFromText(frame.text);
641
+ }
642
+ catch (error) {
643
+ throw StreamClientError.json(error);
644
+ }
645
+ }
646
+ case "ping": {
647
+ socket.pong(frame.payload);
648
+ break;
649
+ }
650
+ case "pong": {
651
+ break;
652
+ }
653
+ case "close": {
654
+ throw StreamClientError.protocol("socket closed before configure acknowledgement");
655
+ }
656
+ case "error": {
657
+ throw StreamClientError.websocket(frame.error);
658
+ }
659
+ case "binary": {
660
+ throw StreamClientError.protocol("received non-text frame before configure acknowledgement");
661
+ }
662
+ default: {
663
+ const _unreachable = frame;
664
+ throw _unreachable;
665
+ }
666
+ }
667
+ }
668
+ }
669
+ async function sendClientMessage(socket, message) {
670
+ const text = clientMessageToText(message);
671
+ await new Promise((resolve, reject) => {
672
+ socket.send(text, (error) => {
673
+ if (error) {
674
+ reject(StreamClientError.websocket(error));
675
+ }
676
+ else {
677
+ resolve();
678
+ }
679
+ });
680
+ });
681
+ }
682
+ function safeClose(socket) {
683
+ if (socket.readyState === WebSocket.CLOSING ||
684
+ socket.readyState === WebSocket.CLOSED) {
685
+ return;
686
+ }
687
+ socket.close();
688
+ }
689
+ class Deque {
690
+ buffer;
691
+ head = 0;
692
+ len = 0;
693
+ mask;
694
+ constructor(initialCapacity = 16) {
695
+ const cap = nextPowerOfTwo(Math.max(2, initialCapacity));
696
+ this.buffer = new Array(cap);
697
+ this.mask = cap - 1;
698
+ }
699
+ get length() {
700
+ return this.len;
701
+ }
702
+ push(value) {
703
+ if (this.len === this.buffer.length) {
704
+ this.grow();
705
+ }
706
+ const index = (this.head + this.len) & this.mask;
707
+ this.buffer[index] = value;
708
+ this.len += 1;
709
+ }
710
+ unshift(value) {
711
+ if (this.len === this.buffer.length) {
712
+ this.grow();
713
+ }
714
+ this.head = (this.head - 1) & this.mask;
715
+ this.buffer[this.head] = value;
716
+ this.len += 1;
717
+ }
718
+ shift() {
719
+ if (this.len === 0) {
720
+ return undefined;
721
+ }
722
+ const value = this.buffer[this.head];
723
+ this.buffer[this.head] = undefined;
724
+ this.head = (this.head + 1) & this.mask;
725
+ this.len -= 1;
726
+ return value;
727
+ }
728
+ grow() {
729
+ const old = this.buffer;
730
+ const newCap = old.length * 2;
731
+ const next = new Array(newCap);
732
+ for (let i = 0; i < this.len; i += 1) {
733
+ next[i] = old[(this.head + i) & this.mask];
734
+ }
735
+ this.buffer = next;
736
+ this.head = 0;
737
+ this.mask = newCap - 1;
738
+ }
739
+ }
740
+ function nextPowerOfTwo(value) {
741
+ let v = Math.max(2, Math.floor(value));
742
+ v -= 1;
743
+ v |= v >> 1;
744
+ v |= v >> 2;
745
+ v |= v >> 4;
746
+ v |= v >> 8;
747
+ v |= v >> 16;
748
+ return v + 1;
749
+ }
750
+ class WebSocketFrameQueue {
751
+ frames = new Deque();
752
+ waiters = [];
753
+ constructor(socket) {
754
+ socket.on("message", (data, isBinary) => {
755
+ if (isBinary) {
756
+ this.push({ kind: "binary" });
757
+ return;
758
+ }
759
+ const text = rawDataToString(data);
760
+ this.push({ kind: "text", text });
761
+ });
762
+ socket.on("ping", (payload) => {
763
+ this.push({
764
+ kind: "ping",
765
+ payload: rawDataToBuffer(payload),
766
+ });
767
+ });
768
+ socket.on("pong", (payload) => {
769
+ this.push({
770
+ kind: "pong",
771
+ payload: rawDataToBuffer(payload),
772
+ });
773
+ });
774
+ socket.on("close", (code, reason) => {
775
+ this.push({
776
+ kind: "close",
777
+ code,
778
+ reason: reason.toString("utf8"),
779
+ });
780
+ });
781
+ socket.on("error", (error) => {
782
+ this.push({ kind: "error", error });
783
+ });
784
+ }
785
+ shiftNow() {
786
+ return this.frames.shift();
787
+ }
788
+ waitNextCancelable() {
789
+ const next = this.shiftNow();
790
+ if (next !== undefined) {
791
+ return {
792
+ promise: Promise.resolve(next),
793
+ cancel: () => { },
794
+ };
795
+ }
796
+ let waiter;
797
+ const promise = new Promise((resolve) => {
798
+ waiter = {
799
+ canceled: false,
800
+ hasValue: false,
801
+ value: null,
802
+ settled: false,
803
+ resolve,
804
+ };
805
+ this.waiters.push(waiter);
806
+ });
807
+ const cancel = () => {
808
+ if (waiter === undefined || waiter.canceled) {
809
+ return;
810
+ }
811
+ waiter.canceled = true;
812
+ if (!waiter.settled) {
813
+ const index = this.waiters.indexOf(waiter);
814
+ if (index >= 0) {
815
+ this.waiters.splice(index, 1);
816
+ }
817
+ return;
818
+ }
819
+ if (waiter.hasValue && waiter.value !== null) {
820
+ this.frames.unshift(waiter.value);
821
+ waiter.hasValue = false;
822
+ waiter.value = null;
823
+ }
824
+ };
825
+ return { promise, cancel };
826
+ }
827
+ async waitNext() {
828
+ const { promise } = this.waitNextCancelable();
829
+ return await promise;
830
+ }
831
+ push(frame) {
832
+ const waiter = this.waiters.shift();
833
+ if (waiter !== undefined) {
834
+ waiter.settled = true;
835
+ waiter.hasValue = true;
836
+ waiter.value = frame;
837
+ waiter.resolve(frame);
838
+ return;
839
+ }
840
+ this.frames.push(frame);
841
+ }
842
+ }
843
+ class AsyncQueue {
844
+ items = new Deque();
845
+ waiters = [];
846
+ closed = false;
847
+ maxLen;
848
+ dropOldest;
849
+ constructor(options) {
850
+ this.maxLen = options?.maxLen;
851
+ this.dropOldest = options?.dropOldest ?? false;
852
+ }
853
+ push(item) {
854
+ if (this.closed) {
855
+ return;
856
+ }
857
+ const waiter = this.waiters.shift();
858
+ if (waiter !== undefined) {
859
+ waiter.settled = true;
860
+ waiter.hasValue = true;
861
+ waiter.value = item;
862
+ waiter.resolve(item);
863
+ return;
864
+ }
865
+ if (this.maxLen !== undefined && this.items.length >= this.maxLen) {
866
+ if (this.dropOldest) {
867
+ this.items.shift();
868
+ }
869
+ else {
870
+ return;
871
+ }
872
+ }
873
+ this.items.push(item);
874
+ }
875
+ close() {
876
+ if (this.closed) {
877
+ return;
878
+ }
879
+ this.closed = true;
880
+ while (this.waiters.length > 0) {
881
+ const waiter = this.waiters.shift();
882
+ if (waiter !== undefined) {
883
+ waiter.settled = true;
884
+ waiter.hasValue = true;
885
+ waiter.value = null;
886
+ waiter.resolve(null);
887
+ }
888
+ }
889
+ }
890
+ shiftNow() {
891
+ return this.items.shift();
892
+ }
893
+ shiftCancelable() {
894
+ const next = this.shiftNow();
895
+ if (next !== undefined) {
896
+ return {
897
+ promise: Promise.resolve(next),
898
+ cancel: () => { },
899
+ };
900
+ }
901
+ if (this.closed) {
902
+ return {
903
+ promise: Promise.resolve(null),
904
+ cancel: () => { },
905
+ };
906
+ }
907
+ let waiter;
908
+ const promise = new Promise((resolve) => {
909
+ waiter = {
910
+ canceled: false,
911
+ hasValue: false,
912
+ value: null,
913
+ settled: false,
914
+ resolve,
915
+ };
916
+ this.waiters.push(waiter);
917
+ });
918
+ const cancel = () => {
919
+ if (waiter === undefined || waiter.canceled) {
920
+ return;
921
+ }
922
+ waiter.canceled = true;
923
+ if (!waiter.settled) {
924
+ const index = this.waiters.indexOf(waiter);
925
+ if (index >= 0) {
926
+ this.waiters.splice(index, 1);
927
+ }
928
+ return;
929
+ }
930
+ if (waiter.hasValue && waiter.value !== null && !this.closed) {
931
+ this.items.unshift(waiter.value);
932
+ waiter.hasValue = false;
933
+ waiter.value = null;
934
+ }
935
+ };
936
+ return { promise, cancel };
937
+ }
938
+ async shift() {
939
+ const { promise } = this.shiftCancelable();
940
+ return await promise;
941
+ }
942
+ }
943
+ function rawDataToString(data) {
944
+ if (typeof data === "string") {
945
+ return data;
946
+ }
947
+ return rawDataToBuffer(data).toString("utf8");
948
+ }
949
+ function rawDataToBuffer(data) {
950
+ if (Buffer.isBuffer(data)) {
951
+ return data;
952
+ }
953
+ if (Array.isArray(data)) {
954
+ return Buffer.concat(data);
955
+ }
956
+ const value = data;
957
+ if (value instanceof ArrayBuffer) {
958
+ return Buffer.from(value);
959
+ }
960
+ if (ArrayBuffer.isView(value)) {
961
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
962
+ }
963
+ throw StreamClientError.protocol("unexpected websocket raw data frame");
964
+ }
965
+ function asStreamClientError(error) {
966
+ if (error instanceof StreamClientError) {
967
+ return error;
968
+ }
969
+ return StreamClientError.websocket(error);
970
+ }
971
+ async function sleepWithStop(ms, shouldStop) {
972
+ const intervalMs = 20;
973
+ let remaining = ms;
974
+ while (remaining > 0) {
975
+ if (shouldStop()) {
976
+ return false;
977
+ }
978
+ const step = Math.min(intervalMs, remaining);
979
+ await sleep(step);
980
+ remaining -= step;
981
+ }
982
+ return !shouldStop();
983
+ }
984
+ function sleep(ms) {
985
+ return new Promise((resolve) => {
986
+ setTimeout(resolve, ms);
987
+ });
988
+ }
989
+ function stringifyError(error) {
990
+ if (error instanceof Error) {
991
+ return `${error.name}: ${error.message}`;
992
+ }
993
+ return String(error);
994
+ }
995
+ //# sourceMappingURL=client.js.map