durablews 1.0.1 → 2.0.0-alpha.1

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,765 @@
1
+ 'use strict';
2
+
3
+ // src/backoff.ts
4
+ var RECONNECT_DEFAULTS = {
5
+ baseDelay: 500,
6
+ factor: 2,
7
+ maxDelay: 3e4,
8
+ jitter: true,
9
+ maxRetries: Number.POSITIVE_INFINITY,
10
+ shouldReconnect: () => true
11
+ };
12
+ function resolveReconnect(option) {
13
+ if (option === false) {
14
+ return null;
15
+ }
16
+ return { ...RECONNECT_DEFAULTS, ...option };
17
+ }
18
+ function computeDelay(attempt, options, random = Math.random) {
19
+ const exponential = Math.min(
20
+ options.maxDelay,
21
+ options.baseDelay * options.factor ** attempt
22
+ );
23
+ return options.jitter ? random() * exponential : exponential;
24
+ }
25
+
26
+ // src/codec.ts
27
+ function safeJSONParse(data) {
28
+ try {
29
+ return JSON.parse(data);
30
+ } catch {
31
+ return data;
32
+ }
33
+ }
34
+ function isBinary(data) {
35
+ return data instanceof ArrayBuffer || ArrayBuffer.isView(data) || typeof Blob !== "undefined" && data instanceof Blob;
36
+ }
37
+ var jsonCodec = {
38
+ encode(data) {
39
+ if (typeof data === "string" || isBinary(data)) {
40
+ return data;
41
+ }
42
+ return JSON.stringify(data);
43
+ },
44
+ decode(data) {
45
+ return typeof data === "string" ? safeJSONParse(data) : data;
46
+ }
47
+ };
48
+
49
+ // src/fsm.ts
50
+ var TRANSITIONS = {
51
+ idle: { CONNECT: "connecting" },
52
+ connecting: {
53
+ OPEN: "open",
54
+ CLOSE_REQUESTED: "closing",
55
+ CLOSED: "closed",
56
+ RETRY: "reconnecting"
57
+ },
58
+ open: {
59
+ CLOSE_REQUESTED: "closing",
60
+ CLOSED: "closed",
61
+ RETRY: "reconnecting"
62
+ },
63
+ closing: { CLOSED: "closed" },
64
+ // `reconnecting` = waiting out the backoff delay; no socket exists, so
65
+ // CLOSE_REQUESTED goes straight to `closed` (there is nothing to wait for)
66
+ // and OPEN/CLOSED cannot legally occur.
67
+ reconnecting: {
68
+ CONNECT: "connecting",
69
+ CLOSE_REQUESTED: "closed"
70
+ },
71
+ closed: { CONNECT: "connecting" }
72
+ };
73
+ function nextState(state, event) {
74
+ return TRANSITIONS[state][event] ?? null;
75
+ }
76
+
77
+ // src/heartbeat.ts
78
+ var HEARTBEAT_TIMEOUT_CODE = 4408;
79
+ function resolveHeartbeat(option) {
80
+ if (option === void 0) {
81
+ return null;
82
+ }
83
+ return {
84
+ interval: option.interval,
85
+ message: option.message ?? "ping",
86
+ timeout: option.timeout ?? option.interval
87
+ };
88
+ }
89
+
90
+ // src/helpers/event-bus.ts
91
+ function defineEventBus() {
92
+ const listeners = /* @__PURE__ */ new Map();
93
+ function on(eventName, handler) {
94
+ const handlers = listeners.get(eventName) ?? [];
95
+ handlers.push(handler);
96
+ listeners.set(eventName, handlers);
97
+ }
98
+ function off(eventName, handler) {
99
+ const handlers = listeners.get(eventName);
100
+ if (!handlers) {
101
+ return;
102
+ }
103
+ listeners.set(
104
+ eventName,
105
+ handlers.filter((h) => h !== handler)
106
+ );
107
+ }
108
+ function once(eventName, handler) {
109
+ const onceHandler = (payload) => {
110
+ off(eventName, onceHandler);
111
+ handler(payload);
112
+ };
113
+ on(eventName, onceHandler);
114
+ }
115
+ function emit(eventName, payload) {
116
+ const handlers = listeners.get(eventName);
117
+ handlers?.forEach((fn) => {
118
+ fn(payload);
119
+ });
120
+ }
121
+ return { on, off, emit, once };
122
+ }
123
+
124
+ // src/pipeline.ts
125
+ function runPipeline(middlewares, ctx, terminal) {
126
+ let invoked = -1;
127
+ function dispatch(i) {
128
+ if (i <= invoked) {
129
+ throw new Error("next() called multiple times");
130
+ }
131
+ invoked = i;
132
+ const middleware = middlewares[i];
133
+ if (!middleware) {
134
+ return terminal();
135
+ }
136
+ return middleware(ctx, () => dispatch(i + 1));
137
+ }
138
+ return dispatch(0);
139
+ }
140
+
141
+ // src/queue.ts
142
+ var QUEUE_DEFAULTS = {
143
+ maxSize: 256
144
+ };
145
+ function resolveQueue(option) {
146
+ if (option === false) {
147
+ return null;
148
+ }
149
+ return { ...QUEUE_DEFAULTS, ...option };
150
+ }
151
+
152
+ // src/schema.ts
153
+ var SchemaValidationError = class extends Error {
154
+ issues;
155
+ constructor(issues) {
156
+ super(
157
+ `Inbound message failed schema validation: ${issues.map((issue) => issue.message).join("; ")}`
158
+ );
159
+ this.name = "SchemaValidationError";
160
+ this.issues = issues;
161
+ }
162
+ };
163
+
164
+ // src/client.ts
165
+ function client(config) {
166
+ const bus = defineEventBus();
167
+ const codec = config.codec ?? jsonCodec;
168
+ const reconnect = resolveReconnect(config.reconnect);
169
+ const queue = resolveQueue(config.queue);
170
+ const heartbeat = resolveHeartbeat(config.heartbeat);
171
+ const middlewares = [];
172
+ const outboundMiddlewares = [];
173
+ const queued = [];
174
+ let socket = null;
175
+ let state = "idle";
176
+ let lastError = null;
177
+ let lastInboundAt = 0;
178
+ let heartbeatTimer = null;
179
+ let heartbeatDeadline = null;
180
+ let outboundTail = null;
181
+ let requeueIndex = 0;
182
+ let closeRequested = false;
183
+ let retryAttempt = 0;
184
+ let retryTimer = null;
185
+ const subscribers = /* @__PURE__ */ new Set();
186
+ let snapshot = null;
187
+ let pending = null;
188
+ function notify() {
189
+ snapshot = null;
190
+ for (const listener of [...subscribers]) {
191
+ listener();
192
+ }
193
+ }
194
+ function transition(event) {
195
+ const next = nextState(state, event);
196
+ if (next === null || next === state) {
197
+ return null;
198
+ }
199
+ const previous = state;
200
+ state = next;
201
+ notify();
202
+ bus.emit("statechange", { previous, current: next });
203
+ return next;
204
+ }
205
+ function settleConnected() {
206
+ pending?.resolve();
207
+ pending = null;
208
+ }
209
+ function settleFailed(reason) {
210
+ pending?.reject(reason);
211
+ pending = null;
212
+ }
213
+ function clearRetryTimer() {
214
+ if (retryTimer !== null) {
215
+ clearTimeout(retryTimer);
216
+ retryTimer = null;
217
+ }
218
+ }
219
+ function stopHeartbeat() {
220
+ if (heartbeatTimer !== null) {
221
+ clearInterval(heartbeatTimer);
222
+ heartbeatTimer = null;
223
+ }
224
+ if (heartbeatDeadline !== null) {
225
+ clearTimeout(heartbeatDeadline);
226
+ heartbeatDeadline = null;
227
+ }
228
+ }
229
+ function startHeartbeat() {
230
+ if (heartbeat === null) {
231
+ return;
232
+ }
233
+ stopHeartbeat();
234
+ heartbeatTimer = setInterval(() => {
235
+ if (state !== "open" || !socket) {
236
+ return;
237
+ }
238
+ const pingSentAt = Date.now();
239
+ try {
240
+ socket.send(codec.encode(heartbeat.message));
241
+ } catch (error) {
242
+ bus.emit("error", toError(error));
243
+ }
244
+ if (heartbeatDeadline !== null) {
245
+ clearTimeout(heartbeatDeadline);
246
+ }
247
+ heartbeatDeadline = setTimeout(() => {
248
+ heartbeatDeadline = null;
249
+ if (state === "open" && socket && lastInboundAt < pingSentAt) {
250
+ const failure = new Error(
251
+ `Heartbeat timeout: no inbound traffic within ${heartbeat.timeout}ms of a ping`
252
+ );
253
+ lastError = failure;
254
+ notify();
255
+ bus.emit("error", failure);
256
+ socket.close(HEARTBEAT_TIMEOUT_CODE, "heartbeat timeout");
257
+ }
258
+ }, heartbeat.timeout);
259
+ }, heartbeat.interval);
260
+ }
261
+ function wire(transformed, original) {
262
+ if (socket && state === "open") {
263
+ socket.send(codec.encode(transformed));
264
+ return;
265
+ }
266
+ if (queue !== null && (state === "connecting" || state === "reconnecting")) {
267
+ if (queued.length >= queue.maxSize) {
268
+ bus.emit("drop", {
269
+ data: queued.shift(),
270
+ reason: "overflow"
271
+ });
272
+ if (requeueIndex > 0) {
273
+ requeueIndex -= 1;
274
+ }
275
+ }
276
+ queued.splice(requeueIndex, 0, original);
277
+ requeueIndex += 1;
278
+ notify();
279
+ return;
280
+ }
281
+ bus.emit("drop", { data: original, reason: "close" });
282
+ }
283
+ function runOutbound(data) {
284
+ const ctx = { data, client: api };
285
+ try {
286
+ const result = runPipeline(outboundMiddlewares, ctx, () => {
287
+ wire(ctx.data, data);
288
+ });
289
+ if (result instanceof Promise) {
290
+ return result.catch((error) => {
291
+ bus.emit("error", toError(error));
292
+ });
293
+ }
294
+ } catch (error) {
295
+ bus.emit("error", toError(error));
296
+ }
297
+ }
298
+ function transmit(data) {
299
+ if (outboundTail === null) {
300
+ const result = runOutbound(data);
301
+ if (result instanceof Promise) {
302
+ setOutboundTail(result);
303
+ }
304
+ return;
305
+ }
306
+ setOutboundTail(outboundTail.then(() => runOutbound(data)));
307
+ }
308
+ function setOutboundTail(run) {
309
+ const tail = run.then(() => {
310
+ if (outboundTail === tail) {
311
+ outboundTail = null;
312
+ }
313
+ });
314
+ outboundTail = tail;
315
+ }
316
+ function flushQueue() {
317
+ const hadQueued = queued.length > 0;
318
+ requeueIndex = 0;
319
+ while (queued.length > 0 && socket && state === "open") {
320
+ const data = queued.shift();
321
+ if (outboundMiddlewares.length === 0) {
322
+ try {
323
+ socket.send(codec.encode(data));
324
+ } catch (error) {
325
+ bus.emit("error", toError(error));
326
+ }
327
+ } else {
328
+ transmit(data);
329
+ }
330
+ }
331
+ if (hadQueued) {
332
+ notify();
333
+ }
334
+ }
335
+ function dropQueued() {
336
+ const hadQueued = queued.length > 0;
337
+ while (queued.length > 0) {
338
+ bus.emit("drop", { data: queued.shift(), reason: "close" });
339
+ }
340
+ if (hadQueued) {
341
+ notify();
342
+ }
343
+ }
344
+ function isRetryable(event) {
345
+ return reconnect !== null && !closeRequested && retryAttempt < reconnect.maxRetries && reconnect.shouldReconnect(event);
346
+ }
347
+ function openSocket() {
348
+ if (lastError !== null) {
349
+ lastError = null;
350
+ notify();
351
+ }
352
+ socket = config.protocols ? new WebSocket(config.url, config.protocols) : new WebSocket(config.url);
353
+ if (config.binaryType) {
354
+ socket.binaryType = config.binaryType;
355
+ }
356
+ socket.onopen = () => {
357
+ retryAttempt = 0;
358
+ transition("OPEN");
359
+ flushQueue();
360
+ startHeartbeat();
361
+ bus.emit("open");
362
+ settleConnected();
363
+ };
364
+ socket.onmessage = (event) => {
365
+ lastInboundAt = Date.now();
366
+ deliver(event.data);
367
+ };
368
+ socket.onerror = (event) => {
369
+ lastError = event;
370
+ notify();
371
+ bus.emit("error", event);
372
+ };
373
+ socket.onclose = (event) => {
374
+ const wasConnecting = state === "connecting";
375
+ socket = null;
376
+ stopHeartbeat();
377
+ requeueIndex = 0;
378
+ if (reconnect !== null && isRetryable(event)) {
379
+ retryAttempt += 1;
380
+ const delay = computeDelay(retryAttempt - 1, reconnect);
381
+ transition("RETRY");
382
+ bus.emit("close", event);
383
+ bus.emit("reconnecting", { attempt: retryAttempt, delay });
384
+ retryTimer = setTimeout(() => {
385
+ retryTimer = null;
386
+ if (transition("CONNECT") !== null) {
387
+ openSocket();
388
+ }
389
+ }, delay);
390
+ return;
391
+ }
392
+ transition("CLOSED");
393
+ dropQueued();
394
+ bus.emit("close", event);
395
+ if (wasConnecting || pending) {
396
+ settleFailed(
397
+ new Error(
398
+ retryAttempt > 0 ? `WebSocket reconnect gave up after ${retryAttempt} attempt(s) (code ${event.code})` : `WebSocket closed before opening (code ${event.code}${event.reason ? `: ${event.reason}` : ""})`
399
+ )
400
+ );
401
+ }
402
+ };
403
+ }
404
+ function deliver(raw) {
405
+ const decoded = codec.decode(raw);
406
+ if (config.schema === void 0) {
407
+ dispatchMessage(decoded);
408
+ return;
409
+ }
410
+ let result;
411
+ try {
412
+ result = config.schema["~standard"].validate(decoded);
413
+ } catch (error) {
414
+ bus.emit("error", toError(error));
415
+ return;
416
+ }
417
+ if (result instanceof Promise) {
418
+ result.then(handleValidated, (error) => {
419
+ bus.emit("error", toError(error));
420
+ });
421
+ return;
422
+ }
423
+ handleValidated(result);
424
+ }
425
+ function handleValidated(result) {
426
+ if (result.issues) {
427
+ bus.emit("error", new SchemaValidationError(result.issues));
428
+ return;
429
+ }
430
+ dispatchMessage(result.value);
431
+ }
432
+ function dispatchMessage(data) {
433
+ const ctx = { data, client: api };
434
+ const emit = () => {
435
+ bus.emit("message", ctx.data);
436
+ };
437
+ try {
438
+ const result = runPipeline(middlewares, ctx, emit);
439
+ if (result instanceof Promise) {
440
+ result.catch((error) => {
441
+ bus.emit("error", toError(error));
442
+ });
443
+ }
444
+ } catch (error) {
445
+ bus.emit("error", toError(error));
446
+ }
447
+ }
448
+ function ensurePending() {
449
+ if (pending) {
450
+ return pending.promise;
451
+ }
452
+ let resolve;
453
+ let reject;
454
+ const promise = new Promise((res, rej) => {
455
+ resolve = res;
456
+ reject = rej;
457
+ });
458
+ pending = { promise, resolve, reject };
459
+ return promise;
460
+ }
461
+ const api = {
462
+ get state() {
463
+ return state;
464
+ },
465
+ connect() {
466
+ if (state === "open") {
467
+ return Promise.resolve();
468
+ }
469
+ if (state === "connecting") {
470
+ return ensurePending();
471
+ }
472
+ if (state === "closing") {
473
+ return Promise.reject(
474
+ new Error(
475
+ "Cannot connect() while the connection is closing"
476
+ )
477
+ );
478
+ }
479
+ if (state === "reconnecting") {
480
+ clearRetryTimer();
481
+ const promise2 = ensurePending();
482
+ transition("CONNECT");
483
+ openSocket();
484
+ return promise2;
485
+ }
486
+ closeRequested = false;
487
+ retryAttempt = 0;
488
+ if (transition("CONNECT") === null) {
489
+ return Promise.reject(
490
+ new Error(`Cannot connect() from state "${state}"`)
491
+ );
492
+ }
493
+ const promise = ensurePending();
494
+ try {
495
+ openSocket();
496
+ } catch (error) {
497
+ transition("CLOSED");
498
+ socket = null;
499
+ settleFailed(toError(error));
500
+ }
501
+ return promise;
502
+ },
503
+ send(data) {
504
+ if (socket && state === "open") {
505
+ if (outboundMiddlewares.length === 0) {
506
+ socket.send(codec.encode(data));
507
+ } else {
508
+ transmit(data);
509
+ }
510
+ return;
511
+ }
512
+ if (queue !== null && (state === "connecting" || state === "reconnecting")) {
513
+ if (queued.length >= queue.maxSize) {
514
+ bus.emit("drop", {
515
+ data: queued.shift(),
516
+ reason: "overflow"
517
+ });
518
+ }
519
+ queued.push(data);
520
+ notify();
521
+ return;
522
+ }
523
+ throw new Error(
524
+ `Cannot send: connection is not open (state: "${state}")`
525
+ );
526
+ },
527
+ close(code, reason) {
528
+ closeRequested = true;
529
+ clearRetryTimer();
530
+ stopHeartbeat();
531
+ if (retryAttempt !== 0) {
532
+ retryAttempt = 0;
533
+ notify();
534
+ }
535
+ dropQueued();
536
+ if (state === "reconnecting") {
537
+ transition("CLOSE_REQUESTED");
538
+ settleFailed(
539
+ new Error("close() called before the connection opened")
540
+ );
541
+ return;
542
+ }
543
+ if (!socket) {
544
+ return;
545
+ }
546
+ transition("CLOSE_REQUESTED");
547
+ socket.close(code, reason);
548
+ },
549
+ on(event, handler) {
550
+ bus.on(event, handler);
551
+ return () => bus.off(event, handler);
552
+ },
553
+ use(middleware) {
554
+ if (typeof middleware === "function") {
555
+ middlewares.push(middleware);
556
+ return api;
557
+ }
558
+ if (middleware.inbound) {
559
+ middlewares.push(middleware.inbound);
560
+ }
561
+ if (middleware.outbound) {
562
+ outboundMiddlewares.push(middleware.outbound);
563
+ }
564
+ return api;
565
+ },
566
+ getState() {
567
+ if (snapshot === null) {
568
+ snapshot = Object.freeze({
569
+ state,
570
+ lastError,
571
+ retryAttempt,
572
+ queueLength: queued.length
573
+ });
574
+ }
575
+ return snapshot;
576
+ },
577
+ subscribe(listener) {
578
+ subscribers.add(listener);
579
+ return () => {
580
+ subscribers.delete(listener);
581
+ };
582
+ }
583
+ };
584
+ return api;
585
+ }
586
+ function toError(value) {
587
+ return value instanceof Error ? value : new Error(String(value));
588
+ }
589
+
590
+ // src/compat.ts
591
+ var rawCodec = {
592
+ // The class's send() signature restricts inputs to WebSocket-sendable
593
+ // values, so the cast only widens what TypeScript already enforced there.
594
+ encode: (data) => data,
595
+ decode: (data) => data
596
+ };
597
+ var CloseEventCtor = typeof CloseEvent !== "undefined" ? CloseEvent : class extends Event {
598
+ code;
599
+ reason;
600
+ wasClean;
601
+ constructor(type, init = {}) {
602
+ super(type);
603
+ this.code = init.code ?? 0;
604
+ this.reason = init.reason ?? "";
605
+ this.wasClean = init.wasClean ?? false;
606
+ }
607
+ };
608
+ var DurableWebSocket = class _DurableWebSocket extends EventTarget {
609
+ static CONNECTING = 0;
610
+ static OPEN = 1;
611
+ static CLOSING = 2;
612
+ static CLOSED = 3;
613
+ CONNECTING = 0;
614
+ OPEN = 1;
615
+ CLOSING = 2;
616
+ CLOSED = 3;
617
+ /** The underlying durablews client — the escape hatch to the full API. */
618
+ client;
619
+ url;
620
+ /** Always `""` — see the known-deviations table in the docs. */
621
+ protocol = "";
622
+ /** Always `""` — see the known-deviations table in the docs. */
623
+ extensions = "";
624
+ /** Always `0` — see the known-deviations table in the docs. */
625
+ bufferedAmount = 0;
626
+ onopen = null;
627
+ onmessage = null;
628
+ onclose = null;
629
+ onerror = null;
630
+ #binaryType;
631
+ // Serializes Blob → ArrayBuffer conversions so message order survives the
632
+ // async hop when binaryType is reassigned after construction. While no
633
+ // conversion is pending, delivery is fully synchronous (native-faithful).
634
+ #deliveryChain = Promise.resolve();
635
+ #pendingDeliveries = 0;
636
+ constructor(url, protocols, options = {}) {
637
+ super();
638
+ this.url = String(url);
639
+ this.#binaryType = options.binaryType ?? "blob";
640
+ this.client = client({
641
+ ...options,
642
+ url,
643
+ protocols,
644
+ codec: rawCodec
645
+ });
646
+ this.client.on("open", () => {
647
+ const event = new Event("open");
648
+ this.dispatchEvent(event);
649
+ this.onopen?.call(this, event);
650
+ });
651
+ this.client.on("message", (data) => {
652
+ this.#deliverMessage(data);
653
+ });
654
+ this.client.on("close", (event) => {
655
+ const compatEvent = new CloseEventCtor("close", {
656
+ code: event.code,
657
+ reason: event.reason,
658
+ wasClean: event.wasClean
659
+ });
660
+ this.dispatchEvent(compatEvent);
661
+ this.onclose?.call(this, compatEvent);
662
+ });
663
+ this.client.on("error", () => {
664
+ const event = new Event("error");
665
+ this.dispatchEvent(event);
666
+ this.onerror?.call(this, event);
667
+ });
668
+ this.client.connect().catch(() => {
669
+ });
670
+ }
671
+ /**
672
+ * Maps the durable lifecycle onto the four native states. Deviation by
673
+ * design: during automatic reconnection this returns to `CONNECTING`,
674
+ * which a native (one-shot) socket can never do.
675
+ */
676
+ get readyState() {
677
+ switch (this.client.state) {
678
+ case "open":
679
+ return _DurableWebSocket.OPEN;
680
+ case "closing":
681
+ return _DurableWebSocket.CLOSING;
682
+ case "closed":
683
+ return _DurableWebSocket.CLOSED;
684
+ default:
685
+ return _DurableWebSocket.CONNECTING;
686
+ }
687
+ }
688
+ get binaryType() {
689
+ return this.#binaryType;
690
+ }
691
+ /**
692
+ * Honored at any time: when set to `"arraybuffer"` after construction,
693
+ * `Blob` frames are converted on delivery (order-preserving). Prefer
694
+ * passing `binaryType` in the constructor options, which configures the
695
+ * socket itself and avoids the conversion copy.
696
+ */
697
+ set binaryType(value) {
698
+ this.#binaryType = value;
699
+ }
700
+ /**
701
+ * Sends data, with durability semantics: while (re)connecting the message
702
+ * is queued and flushed on open (a native socket would throw). After
703
+ * `close()` it is silently discarded, matching native behavior.
704
+ */
705
+ send(data) {
706
+ if (this.client.state === "idle" || this.client.state === "closing" || this.client.state === "closed") {
707
+ return;
708
+ }
709
+ this.client.send(data);
710
+ }
711
+ close(code, reason) {
712
+ this.client.close(code, reason);
713
+ }
714
+ #deliverMessage(data) {
715
+ const dispatch = (value) => {
716
+ const event = new MessageEvent("message", { data: value });
717
+ this.dispatchEvent(event);
718
+ this.onmessage?.call(this, event);
719
+ };
720
+ if (this.#binaryType === "arraybuffer" && typeof Blob !== "undefined" && data instanceof Blob) {
721
+ this.#queueDelivery(async () => {
722
+ try {
723
+ dispatch(await blobToArrayBuffer(data));
724
+ } catch {
725
+ dispatch(data);
726
+ }
727
+ });
728
+ return;
729
+ }
730
+ if (this.#pendingDeliveries === 0) {
731
+ dispatch(data);
732
+ return;
733
+ }
734
+ this.#queueDelivery(() => {
735
+ dispatch(data);
736
+ });
737
+ }
738
+ #queueDelivery(deliver) {
739
+ this.#pendingDeliveries += 1;
740
+ this.#deliveryChain = this.#deliveryChain.then(deliver).catch(() => {
741
+ }).finally(() => {
742
+ this.#pendingDeliveries -= 1;
743
+ });
744
+ }
745
+ };
746
+ function blobToArrayBuffer(blob) {
747
+ if (typeof blob.arrayBuffer === "function") {
748
+ return blob.arrayBuffer();
749
+ }
750
+ return new Promise((resolve, reject) => {
751
+ const reader = new FileReader();
752
+ reader.onload = () => {
753
+ resolve(reader.result);
754
+ };
755
+ reader.onerror = () => {
756
+ reject(reader.error ?? new Error("Blob read failed"));
757
+ };
758
+ reader.readAsArrayBuffer(blob);
759
+ });
760
+ }
761
+
762
+ exports.DurableWebSocket = DurableWebSocket;
763
+ exports.WebSocket = DurableWebSocket;
764
+ //# sourceMappingURL=compat.cjs.map
765
+ //# sourceMappingURL=compat.cjs.map