@replit/river 0.207.2 → 0.208.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.
Files changed (65) hide show
  1. package/dist/adapter-ChksXKVN.d.ts +46 -0
  2. package/dist/adapter-Cuc4JtfV.d.cts +46 -0
  3. package/dist/chunk-2JNVDUMN.js +2238 -0
  4. package/dist/chunk-2JNVDUMN.js.map +1 -0
  5. package/dist/{chunk-4HE7UYRL.js → chunk-DKW3GC3M.js} +6 -5
  6. package/dist/{chunk-4HE7UYRL.js.map → chunk-DKW3GC3M.js.map} +1 -1
  7. package/dist/{chunk-46IVOKJU.js → chunk-ETZAHFGQ.js} +80 -61
  8. package/dist/chunk-ETZAHFGQ.js.map +1 -0
  9. package/dist/codec/index.cjs +157 -23
  10. package/dist/codec/index.cjs.map +1 -1
  11. package/dist/codec/index.d.cts +5 -1
  12. package/dist/codec/index.d.ts +5 -1
  13. package/dist/codec/index.js +6 -20
  14. package/dist/codec/index.js.map +1 -1
  15. package/dist/connection-BF4zg6Qv.d.cts +35 -0
  16. package/dist/{connection-a18e31d5.d.ts → connection-Donr3JRB.d.ts} +4 -3
  17. package/dist/index-C9tpZjBN.d.cts +37 -0
  18. package/dist/index-D8IOd3LG.d.ts +37 -0
  19. package/dist/logging/index.d.cts +2 -1
  20. package/dist/logging/index.d.ts +2 -1
  21. package/dist/{message-ffacb98a.d.ts → message-Di94OL80.d.cts} +1 -35
  22. package/dist/message-Di94OL80.d.ts +108 -0
  23. package/dist/router/index.cjs +62 -43
  24. package/dist/router/index.cjs.map +1 -1
  25. package/dist/router/index.d.cts +27 -7
  26. package/dist/router/index.d.ts +27 -7
  27. package/dist/router/index.js +1 -1
  28. package/dist/testUtil/index.cjs +828 -725
  29. package/dist/testUtil/index.cjs.map +1 -1
  30. package/dist/testUtil/index.d.cts +5 -4
  31. package/dist/testUtil/index.d.ts +5 -4
  32. package/dist/testUtil/index.js +23 -25
  33. package/dist/testUtil/index.js.map +1 -1
  34. package/dist/transport/impls/ws/client.cjs +293 -233
  35. package/dist/transport/impls/ws/client.cjs.map +1 -1
  36. package/dist/transport/impls/ws/client.d.cts +6 -5
  37. package/dist/transport/impls/ws/client.d.ts +6 -5
  38. package/dist/transport/impls/ws/client.js +5 -7
  39. package/dist/transport/impls/ws/client.js.map +1 -1
  40. package/dist/transport/impls/ws/server.cjs +269 -200
  41. package/dist/transport/impls/ws/server.cjs.map +1 -1
  42. package/dist/transport/impls/ws/server.d.cts +6 -5
  43. package/dist/transport/impls/ws/server.d.ts +6 -5
  44. package/dist/transport/impls/ws/server.js +5 -7
  45. package/dist/transport/impls/ws/server.js.map +1 -1
  46. package/dist/transport/index.cjs +438 -342
  47. package/dist/transport/index.cjs.map +1 -1
  48. package/dist/transport/index.d.cts +7 -6
  49. package/dist/transport/index.d.ts +7 -6
  50. package/dist/transport/index.js +5 -10
  51. package/dist/transport-CCaWx1Rb.d.cts +1566 -0
  52. package/dist/{services-43528f4b.d.ts → transport-CZb3vdB4.d.ts} +294 -293
  53. package/dist/{wslike-e0b32dd5.d.ts → wslike-Dng9H1C7.d.cts} +1 -1
  54. package/dist/wslike-Dng9H1C7.d.ts +40 -0
  55. package/package.json +3 -3
  56. package/dist/chunk-24EWYOGK.js +0 -1287
  57. package/dist/chunk-24EWYOGK.js.map +0 -1
  58. package/dist/chunk-46IVOKJU.js.map +0 -1
  59. package/dist/chunk-A7RGOVRV.js +0 -438
  60. package/dist/chunk-A7RGOVRV.js.map +0 -1
  61. package/dist/chunk-AJGIY2UB.js +0 -56
  62. package/dist/chunk-AJGIY2UB.js.map +0 -1
  63. package/dist/chunk-XV4RQ62N.js +0 -377
  64. package/dist/chunk-XV4RQ62N.js.map +0 -1
  65. package/dist/types-3e5768ec.d.ts +0 -20
@@ -49,60 +49,44 @@ var Connection = class {
49
49
  }
50
50
  return metadata;
51
51
  }
52
- // can't use event emitter because we need this to work in both node + browser
53
- _dataListeners = /* @__PURE__ */ new Set();
54
- _closeListeners = /* @__PURE__ */ new Set();
55
- _errorListeners = /* @__PURE__ */ new Set();
56
- get dataListeners() {
57
- return [...this._dataListeners];
58
- }
59
- get closeListeners() {
60
- return [...this._closeListeners];
61
- }
62
- get errorListeners() {
63
- return [...this._errorListeners];
64
- }
52
+ dataListener;
53
+ closeListener;
54
+ errorListener;
65
55
  onData(msg) {
66
- for (const cb of this.dataListeners) {
67
- cb(msg);
68
- }
56
+ this.dataListener?.(msg);
69
57
  }
70
58
  onError(err) {
71
- for (const cb of this.errorListeners) {
72
- cb(err);
73
- }
59
+ this.errorListener?.(err);
74
60
  }
75
61
  onClose() {
76
- for (const cb of this.closeListeners) {
77
- cb();
78
- }
62
+ this.closeListener?.();
79
63
  this.telemetry?.span.end();
80
64
  }
81
65
  /**
82
- * Handle adding a callback for when a message is received.
83
- * @param msg The message that was received.
66
+ * Set the callback for when a message is received.
67
+ * @param cb The message handler callback.
84
68
  */
85
- addDataListener(cb) {
86
- this._dataListeners.add(cb);
69
+ setDataListener(cb) {
70
+ this.dataListener = cb;
87
71
  }
88
- removeDataListener(cb) {
89
- this._dataListeners.delete(cb);
72
+ removeDataListener() {
73
+ this.dataListener = void 0;
90
74
  }
91
75
  /**
92
- * Handle adding a callback for when the connection is closed.
93
- * This should also be called if an error happens and after notifying all the error listeners.
76
+ * Set the callback for when the connection is closed.
77
+ * This should also be called if an error happens and after notifying the error listener.
94
78
  * @param cb The callback to call when the connection is closed.
95
79
  */
96
- addCloseListener(cb) {
97
- this._closeListeners.add(cb);
80
+ setCloseListener(cb) {
81
+ this.closeListener = cb;
98
82
  }
99
- removeCloseListener(cb) {
100
- this._closeListeners.delete(cb);
83
+ removeCloseListener() {
84
+ this.closeListener = void 0;
101
85
  }
102
86
  /**
103
- * Handle adding a callback for when an error is received.
104
- * This should only be used for this.logging errors, all cleanup
105
- * should be delegated to addCloseListener.
87
+ * Set the callback for when an error is received.
88
+ * This should only be used for logging errors, all cleanup
89
+ * should be delegated to setCloseListener.
106
90
  *
107
91
  * The implementer should take care such that the implemented
108
92
  * connection will call both the close and error callbacks
@@ -110,11 +94,11 @@ var Connection = class {
110
94
  *
111
95
  * @param cb The callback to call when an error is received.
112
96
  */
113
- addErrorListener(cb) {
114
- this._errorListeners.add(cb);
97
+ setErrorListener(cb) {
98
+ this.errorListener = cb;
115
99
  }
116
- removeErrorListener(cb) {
117
- this._errorListeners.delete(cb);
100
+ removeErrorListener() {
101
+ this.errorListener = void 0;
118
102
  }
119
103
  };
120
104
 
@@ -153,11 +137,12 @@ var WebSocketConnection = class extends Connection {
153
137
  };
154
138
  }
155
139
  send(payload) {
156
- if (this.ws.readyState !== this.ws.OPEN) {
140
+ try {
141
+ this.ws.send(payload);
142
+ return true;
143
+ } catch {
157
144
  return false;
158
145
  }
159
- this.ws.send(payload);
160
- return true;
161
146
  }
162
147
  close() {
163
148
  this.ws.close(WS_HEALTHY_CLOSE_CODE);
@@ -314,23 +299,20 @@ var NaiveJsonCodec = {
314
299
  );
315
300
  },
316
301
  fromBuffer: (buff) => {
317
- try {
318
- const parsed = JSON.parse(
319
- decoder.decode(buff),
320
- function reviver(_key, val) {
321
- if (val?.$t) {
322
- return base64ToUint8Array(val.$t);
323
- } else {
324
- return val;
325
- }
302
+ const parsed = JSON.parse(
303
+ decoder.decode(buff),
304
+ function reviver(_key, val) {
305
+ if (val?.$t) {
306
+ return base64ToUint8Array(val.$t);
307
+ } else {
308
+ return val;
326
309
  }
327
- );
328
- if (typeof parsed === "object")
329
- return parsed;
330
- return null;
331
- } catch {
332
- return null;
310
+ }
311
+ );
312
+ if (typeof parsed !== "object" || parsed === null) {
313
+ throw new Error("unpacked msg is not an object");
333
314
  }
315
+ return parsed;
334
316
  }
335
317
  };
336
318
 
@@ -427,7 +409,8 @@ var ProtocolError = {
427
409
  RetriesExceeded: "conn_retry_exceeded",
428
410
  HandshakeFailed: "handshake_failed",
429
411
  MessageOrderingViolated: "message_ordering_violated",
430
- InvalidMessage: "invalid_message"
412
+ InvalidMessage: "invalid_message",
413
+ MessageSendFailure: "message_send_failure"
431
414
  };
432
415
  var EventDispatcher = class {
433
416
  eventListeners = {};
@@ -461,7 +444,6 @@ var EventDispatcher = class {
461
444
  };
462
445
 
463
446
  // transport/sessionStateMachine/common.ts
464
- var import_value = require("@sinclair/typebox/value");
465
447
  var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
466
448
  var StateMachineState = class {
467
449
  /*
@@ -521,34 +503,16 @@ var StateMachineState = class {
521
503
  var CommonSession = class extends StateMachineState {
522
504
  from;
523
505
  options;
506
+ codec;
524
507
  tracer;
525
508
  log;
526
- constructor({ from, options, log, tracer }) {
509
+ constructor({ from, options, log, tracer, codec }) {
527
510
  super();
528
511
  this.from = from;
529
512
  this.options = options;
530
513
  this.log = log;
531
514
  this.tracer = tracer;
532
- }
533
- parseMsg(msg) {
534
- const parsedMsg = this.options.codec.fromBuffer(msg);
535
- if (parsedMsg === null) {
536
- this.log?.error(
537
- `received malformed msg: ${Buffer.from(msg).toString("base64")}`,
538
- this.loggingMetadata
539
- );
540
- return null;
541
- }
542
- if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
543
- this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
544
- ...this.loggingMetadata,
545
- validationErrors: [
546
- ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
547
- ]
548
- });
549
- return null;
550
- }
551
- return parsedMsg;
515
+ this.codec = codec;
552
516
  }
553
517
  };
554
518
  var IdentifiedSession = class extends CommonSession {
@@ -608,9 +572,6 @@ var IdentifiedSession = class extends CommonSession {
608
572
  return metadata;
609
573
  }
610
574
  constructMsg(partialMsg) {
611
- if (this._isConsumed) {
612
- throw new Error(ERR_CONSUMED);
613
- }
614
575
  const msg = {
615
576
  ...partialMsg,
616
577
  id: generateId(),
@@ -628,7 +589,10 @@ var IdentifiedSession = class extends CommonSession {
628
589
  send(msg) {
629
590
  const constructedMsg = this.constructMsg(msg);
630
591
  this.sendBuffer.push(constructedMsg);
631
- return constructedMsg.id;
592
+ return {
593
+ ok: true,
594
+ value: constructedMsg.id
595
+ };
632
596
  }
633
597
  _handleStateExit() {
634
598
  }
@@ -660,6 +624,23 @@ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
660
624
  super._handleClose();
661
625
  }
662
626
  };
627
+ function sendMessage(conn, codec, msg) {
628
+ const buff = codec.toBuffer(msg);
629
+ if (!buff.ok) {
630
+ return buff;
631
+ }
632
+ const sent = conn.send(buff.value);
633
+ if (!sent) {
634
+ return {
635
+ ok: false,
636
+ reason: "failed to send message"
637
+ };
638
+ }
639
+ return {
640
+ ok: true,
641
+ value: msg.id
642
+ };
643
+ }
663
644
 
664
645
  // transport/sessionStateMachine/SessionConnecting.ts
665
646
  var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
@@ -673,13 +654,11 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
673
654
  this.listeners = props.listeners;
674
655
  this.connPromise.then(
675
656
  (conn) => {
676
- if (this._isConsumed)
677
- return;
657
+ if (this._isConsumed) return;
678
658
  this.listeners.onConnectionEstablished(conn);
679
659
  },
680
660
  (err) => {
681
- if (this._isConsumed)
682
- return;
661
+ if (this._isConsumed) return;
683
662
  this.listeners.onConnectionFailed(err);
684
663
  }
685
664
  );
@@ -712,8 +691,8 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
712
691
  }
713
692
  }
714
693
  _handleClose() {
715
- this.bestEffortClose();
716
694
  super._handleClose();
695
+ this.bestEffortClose();
717
696
  }
718
697
  };
719
698
 
@@ -740,13 +719,13 @@ function coerceErrorString(err) {
740
719
  }
741
720
 
742
721
  // package.json
743
- var version = "0.207.2";
722
+ var version = "0.208.0";
744
723
 
745
724
  // tracing/index.ts
746
725
  function createSessionTelemetryInfo(tracer, sessionId, to, from, propagationCtx) {
747
726
  const parentCtx = propagationCtx ? import_api2.propagation.extract(import_api2.context.active(), propagationCtx) : import_api2.context.active();
748
727
  const span = tracer.startSpan(
749
- `river.session.${sessionId}`,
728
+ `river.session`,
750
729
  {
751
730
  attributes: {
752
731
  component: "river",
@@ -762,7 +741,7 @@ function createSessionTelemetryInfo(tracer, sessionId, to, from, propagationCtx)
762
741
  }
763
742
  function createConnectionTelemetryInfo(tracer, connection, info) {
764
743
  const span = tracer.startSpan(
765
- `connection ${connection.id}`,
744
+ `river.connection`,
766
745
  {
767
746
  attributes: {
768
747
  component: "river",
@@ -792,9 +771,9 @@ var SessionWaitingForHandshake = class extends CommonSession {
792
771
  this.handshakeTimeout = setTimeout(() => {
793
772
  this.listeners.onHandshakeTimeout();
794
773
  }, this.options.handshakeTimeoutMs);
795
- this.conn.addDataListener(this.onHandshakeData);
796
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
797
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
774
+ this.conn.setDataListener(this.onHandshakeData);
775
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
776
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
798
777
  }
799
778
  get loggingMetadata() {
800
779
  return {
@@ -804,23 +783,23 @@ var SessionWaitingForHandshake = class extends CommonSession {
804
783
  };
805
784
  }
806
785
  onHandshakeData = (msg) => {
807
- const parsedMsg = this.parseMsg(msg);
808
- if (parsedMsg === null) {
786
+ const parsedMsgRes = this.codec.fromBuffer(msg);
787
+ if (!parsedMsgRes.ok) {
809
788
  this.listeners.onInvalidHandshake(
810
- "could not parse message",
789
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
811
790
  "MALFORMED_HANDSHAKE"
812
791
  );
813
792
  return;
814
793
  }
815
- this.listeners.onHandshake(parsedMsg);
794
+ this.listeners.onHandshake(parsedMsgRes.value);
816
795
  };
817
796
  sendHandshake(msg) {
818
- return this.conn.send(this.options.codec.toBuffer(msg));
797
+ return sendMessage(this.conn, this.codec, msg);
819
798
  }
820
799
  _handleStateExit() {
821
- this.conn.removeDataListener(this.onHandshakeData);
822
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
823
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
800
+ this.conn.removeDataListener();
801
+ this.conn.removeErrorListener();
802
+ this.conn.removeCloseListener();
824
803
  clearTimeout(this.handshakeTimeout);
825
804
  this.handshakeTimeout = void 0;
826
805
  }
@@ -842,9 +821,9 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
842
821
  this.handshakeTimeout = setTimeout(() => {
843
822
  this.listeners.onHandshakeTimeout();
844
823
  }, this.options.handshakeTimeoutMs);
845
- this.conn.addDataListener(this.onHandshakeData);
846
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
847
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
824
+ this.conn.setDataListener(this.onHandshakeData);
825
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
826
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
848
827
  }
849
828
  get loggingMetadata() {
850
829
  return {
@@ -853,24 +832,24 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
853
832
  };
854
833
  }
855
834
  onHandshakeData = (msg) => {
856
- const parsedMsg = this.parseMsg(msg);
857
- if (parsedMsg === null) {
835
+ const parsedMsgRes = this.codec.fromBuffer(msg);
836
+ if (!parsedMsgRes.ok) {
858
837
  this.listeners.onInvalidHandshake(
859
- "could not parse message",
838
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
860
839
  "MALFORMED_HANDSHAKE"
861
840
  );
862
841
  return;
863
842
  }
864
- this.listeners.onHandshake(parsedMsg);
843
+ this.listeners.onHandshake(parsedMsgRes.value);
865
844
  };
866
845
  sendHandshake(msg) {
867
- return this.conn.send(this.options.codec.toBuffer(msg));
846
+ return sendMessage(this.conn, this.codec, msg);
868
847
  }
869
848
  _handleStateExit() {
870
849
  super._handleStateExit();
871
- this.conn.removeDataListener(this.onHandshakeData);
872
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
873
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
850
+ this.conn.removeDataListener();
851
+ this.conn.removeErrorListener();
852
+ this.conn.removeCloseListener();
874
853
  if (this.handshakeTimeout) {
875
854
  clearTimeout(this.handshakeTimeout);
876
855
  this.handshakeTimeout = void 0;
@@ -889,25 +868,15 @@ var SessionConnected = class extends IdentifiedSession {
889
868
  conn;
890
869
  listeners;
891
870
  heartbeatHandle;
892
- heartbeatMisses = 0;
893
- isActivelyHeartbeating;
894
- lastConstructedMsgs = [];
895
- pushLastConstructedMsgs = (msg) => {
896
- const trackedMsg = {
897
- id: msg.id,
898
- seq: msg.seq,
899
- streamId: msg.streamId,
900
- stack: new Error().stack
901
- };
902
- this.lastConstructedMsgs.push(trackedMsg);
903
- if (this.lastConstructedMsgs.length > 10) {
904
- this.lastConstructedMsgs.shift();
905
- }
906
- };
871
+ heartbeatMissTimeout;
872
+ isActivelyHeartbeating = false;
907
873
  updateBookkeeping(ack, seq) {
908
874
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
909
875
  this.ack = seq + 1;
910
- this.heartbeatMisses = 0;
876
+ if (this.heartbeatMissTimeout) {
877
+ clearTimeout(this.heartbeatMissTimeout);
878
+ }
879
+ this.startMissingHeartbeatTimeout();
911
880
  }
912
881
  assertSendOrdering(constructedMsg) {
913
882
  if (constructedMsg.seq > this.seqSent + 1) {
@@ -915,30 +884,32 @@ var SessionConnected = class extends IdentifiedSession {
915
884
  this.log?.error(msg, {
916
885
  ...this.loggingMetadata,
917
886
  transportMessage: constructedMsg,
918
- tags: ["invariant-violation"],
919
- extras: {
920
- lastConstructedMsgs: this.lastConstructedMsgs
921
- }
887
+ tags: ["invariant-violation"]
922
888
  });
923
889
  throw new Error(msg);
924
890
  }
925
891
  }
926
892
  send(msg) {
927
893
  const constructedMsg = this.constructMsg(msg);
928
- this.pushLastConstructedMsgs(constructedMsg);
929
894
  this.assertSendOrdering(constructedMsg);
930
895
  this.sendBuffer.push(constructedMsg);
931
- this.conn.send(this.options.codec.toBuffer(constructedMsg));
896
+ const res = sendMessage(this.conn, this.codec, constructedMsg);
897
+ if (!res.ok) {
898
+ this.listeners.onMessageSendFailure(constructedMsg, res.reason);
899
+ return res;
900
+ }
932
901
  this.seqSent = constructedMsg.seq;
933
- return constructedMsg.id;
902
+ return res;
934
903
  }
935
904
  constructor(props) {
936
905
  super(props);
937
906
  this.conn = props.conn;
938
907
  this.listeners = props.listeners;
939
- this.conn.addDataListener(this.onMessageData);
940
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
941
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
908
+ this.conn.setDataListener(this.onMessageData);
909
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
910
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
911
+ }
912
+ sendBufferedMessages() {
942
913
  if (this.sendBuffer.length > 0) {
943
914
  this.log?.info(
944
915
  `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
@@ -946,30 +917,15 @@ var SessionConnected = class extends IdentifiedSession {
946
917
  );
947
918
  for (const msg of this.sendBuffer) {
948
919
  this.assertSendOrdering(msg);
949
- this.conn.send(this.options.codec.toBuffer(msg));
920
+ const res = sendMessage(this.conn, this.codec, msg);
921
+ if (!res.ok) {
922
+ this.listeners.onMessageSendFailure(msg, res.reason);
923
+ return res;
924
+ }
950
925
  this.seqSent = msg.seq;
951
926
  }
952
927
  }
953
- this.isActivelyHeartbeating = false;
954
- this.heartbeatHandle = setInterval(() => {
955
- const misses = this.heartbeatMisses;
956
- const missDuration = misses * this.options.heartbeatIntervalMs;
957
- if (misses >= this.options.heartbeatsUntilDead) {
958
- this.log?.info(
959
- `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
960
- this.loggingMetadata
961
- );
962
- this.telemetry.span.addEvent("closing connection due to inactivity");
963
- this.conn.close();
964
- clearInterval(this.heartbeatHandle);
965
- this.heartbeatHandle = void 0;
966
- return;
967
- }
968
- if (this.isActivelyHeartbeating) {
969
- this.sendHeartbeat();
970
- }
971
- this.heartbeatMisses++;
972
- }, this.options.heartbeatIntervalMs);
928
+ return { ok: true, value: void 0 };
973
929
  }
974
930
  get loggingMetadata() {
975
931
  return {
@@ -977,25 +933,46 @@ var SessionConnected = class extends IdentifiedSession {
977
933
  ...this.conn.loggingMetadata
978
934
  };
979
935
  }
936
+ startMissingHeartbeatTimeout() {
937
+ const maxMisses = this.options.heartbeatsUntilDead;
938
+ const missDuration = maxMisses * this.options.heartbeatIntervalMs;
939
+ this.heartbeatMissTimeout = setTimeout(() => {
940
+ this.log?.info(
941
+ `closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
942
+ this.loggingMetadata
943
+ );
944
+ this.telemetry.span.addEvent(
945
+ "closing connection due to missing heartbeat"
946
+ );
947
+ this.conn.close();
948
+ }, missDuration);
949
+ }
980
950
  startActiveHeartbeat() {
981
951
  this.isActivelyHeartbeating = true;
952
+ this.heartbeatHandle = setInterval(() => {
953
+ this.sendHeartbeat();
954
+ }, this.options.heartbeatIntervalMs);
982
955
  }
983
956
  sendHeartbeat() {
984
957
  this.log?.debug("sending heartbeat", this.loggingMetadata);
985
- this.send({
958
+ const heartbeat = {
986
959
  streamId: "heartbeat",
987
960
  controlFlags: 1 /* AckBit */,
988
961
  payload: {
989
962
  type: "ACK"
990
963
  }
991
- });
964
+ };
965
+ this.send(heartbeat);
992
966
  }
993
967
  onMessageData = (msg) => {
994
- const parsedMsg = this.parseMsg(msg);
995
- if (parsedMsg === null) {
996
- this.listeners.onInvalidMessage("could not parse message");
968
+ const parsedMsgRes = this.codec.fromBuffer(msg);
969
+ if (!parsedMsgRes.ok) {
970
+ this.listeners.onInvalidMessage(
971
+ `could not parse message: ${parsedMsgRes.reason}`
972
+ );
997
973
  return;
998
974
  }
975
+ const parsedMsg = parsedMsgRes.value;
999
976
  if (parsedMsg.seq !== this.ack) {
1000
977
  if (parsedMsg.seq < this.ack) {
1001
978
  this.log?.debug(
@@ -1034,20 +1011,22 @@ var SessionConnected = class extends IdentifiedSession {
1034
1011
  transportMessage: parsedMsg
1035
1012
  });
1036
1013
  if (!this.isActivelyHeartbeating) {
1037
- void Promise.resolve().then(() => {
1038
- this.sendHeartbeat();
1039
- });
1014
+ this.sendHeartbeat();
1040
1015
  }
1041
1016
  };
1042
1017
  _handleStateExit() {
1043
1018
  super._handleStateExit();
1044
- this.conn.removeDataListener(this.onMessageData);
1045
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
1046
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
1019
+ this.conn.removeDataListener();
1020
+ this.conn.removeCloseListener();
1021
+ this.conn.removeErrorListener();
1047
1022
  if (this.heartbeatHandle) {
1048
1023
  clearInterval(this.heartbeatHandle);
1049
1024
  this.heartbeatHandle = void 0;
1050
1025
  }
1026
+ if (this.heartbeatMissTimeout) {
1027
+ clearTimeout(this.heartbeatMissTimeout);
1028
+ this.heartbeatMissTimeout = void 0;
1029
+ }
1051
1030
  }
1052
1031
  _handleClose() {
1053
1032
  super._handleClose();
@@ -1079,6 +1058,47 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
1079
1058
  }
1080
1059
  };
1081
1060
 
1061
+ // codec/adapter.ts
1062
+ var import_value = require("@sinclair/typebox/value");
1063
+ var CodecMessageAdapter = class {
1064
+ constructor(codec) {
1065
+ this.codec = codec;
1066
+ }
1067
+ toBuffer(msg) {
1068
+ try {
1069
+ return {
1070
+ ok: true,
1071
+ value: this.codec.toBuffer(msg)
1072
+ };
1073
+ } catch (e) {
1074
+ return {
1075
+ ok: false,
1076
+ reason: coerceErrorString(e)
1077
+ };
1078
+ }
1079
+ }
1080
+ fromBuffer(buf) {
1081
+ try {
1082
+ const parsedMsg = this.codec.fromBuffer(buf);
1083
+ if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
1084
+ return {
1085
+ ok: false,
1086
+ reason: "transport message schema mismatch"
1087
+ };
1088
+ }
1089
+ return {
1090
+ ok: true,
1091
+ value: parsedMsg
1092
+ };
1093
+ } catch (e) {
1094
+ return {
1095
+ ok: false,
1096
+ reason: coerceErrorString(e)
1097
+ };
1098
+ }
1099
+ }
1100
+ };
1101
+
1082
1102
  // transport/sessionStateMachine/transitions.ts
1083
1103
  function inheritSharedSession(session) {
1084
1104
  return {
@@ -1093,7 +1113,8 @@ function inheritSharedSession(session) {
1093
1113
  options: session.options,
1094
1114
  log: session.log,
1095
1115
  tracer: session.tracer,
1096
- protocolVersion: session.protocolVersion
1116
+ protocolVersion: session.protocolVersion,
1117
+ codec: session.codec
1097
1118
  };
1098
1119
  }
1099
1120
  function inheritSharedSessionWithGrace(session) {
@@ -1122,7 +1143,8 @@ var SessionStateGraph = {
1122
1143
  options,
1123
1144
  protocolVersion,
1124
1145
  tracer,
1125
- log
1146
+ log,
1147
+ codec: new CodecMessageAdapter(options.codec)
1126
1148
  });
1127
1149
  session.log?.info(`session ${session.id} created in NoConnection state`, {
1128
1150
  ...session.loggingMetadata,
@@ -1137,7 +1159,8 @@ var SessionStateGraph = {
1137
1159
  from,
1138
1160
  options,
1139
1161
  tracer,
1140
- log
1162
+ log,
1163
+ codec: new CodecMessageAdapter(options.codec)
1141
1164
  });
1142
1165
  session.log?.info(`session created in WaitingForHandshake state`, {
1143
1166
  ...session.loggingMetadata,
@@ -1215,6 +1238,7 @@ var SessionStateGraph = {
1215
1238
  listeners,
1216
1239
  ...carriedState
1217
1240
  });
1241
+ session.startMissingHeartbeatTimeout();
1218
1242
  session.log?.info(
1219
1243
  `session ${session.id} transition from Handshaking to Connected`,
1220
1244
  {
@@ -1250,7 +1274,8 @@ var SessionStateGraph = {
1250
1274
  options,
1251
1275
  tracer: pendingSession.tracer,
1252
1276
  log: pendingSession.log,
1253
- protocolVersion
1277
+ protocolVersion,
1278
+ codec: new CodecMessageAdapter(options.codec)
1254
1279
  }
1255
1280
  );
1256
1281
  pendingSession._handleStateExit();
@@ -1260,6 +1285,7 @@ var SessionStateGraph = {
1260
1285
  listeners,
1261
1286
  ...carriedState
1262
1287
  });
1288
+ session.startMissingHeartbeatTimeout();
1263
1289
  conn.telemetry = createConnectionTelemetryInfo(
1264
1290
  session.tracer,
1265
1291
  conn,
@@ -1437,8 +1463,7 @@ var Transport = class {
1437
1463
  * @param message The received message.
1438
1464
  */
1439
1465
  handleMsg(message) {
1440
- if (this.getStatus() !== "open")
1441
- return;
1466
+ if (this.getStatus() !== "open") return;
1442
1467
  this.eventDispatcher.dispatchEvent("message", message);
1443
1468
  }
1444
1469
  /**
@@ -1526,8 +1551,7 @@ var Transport = class {
1526
1551
  });
1527
1552
  }
1528
1553
  deleteSession(session, options) {
1529
- if (session._isConsumed)
1530
- return;
1554
+ if (session._isConsumed) return;
1531
1555
  const loggingMetadata = session.loggingMetadata;
1532
1556
  if (loggingMetadata.tags && options?.unhealthy) {
1533
1557
  loggingMetadata.tags.push("unhealthy-session");
@@ -1547,7 +1571,7 @@ var Transport = class {
1547
1571
  }
1548
1572
  // common listeners
1549
1573
  onSessionGracePeriodElapsed(session) {
1550
- this.log?.warn(
1574
+ this.log?.info(
1551
1575
  `session to ${session.to} grace period elapsed, closing`,
1552
1576
  session.loggingMetadata
1553
1577
  );
@@ -1600,12 +1624,16 @@ var Transport = class {
1600
1624
  );
1601
1625
  }
1602
1626
  const sameSession = session.id === sessionId;
1603
- if (!sameSession) {
1627
+ if (!sameSession || session._isConsumed) {
1604
1628
  throw new Error(
1605
1629
  `session scope for ${sessionId} has ended (transition), can't send`
1606
1630
  );
1607
1631
  }
1608
- return session.send(msg);
1632
+ const res = session.send(msg);
1633
+ if (!res.ok) {
1634
+ throw new Error(res.reason);
1635
+ }
1636
+ return res.value;
1609
1637
  };
1610
1638
  }
1611
1639
  };
@@ -1651,8 +1679,7 @@ var ServerTransport = class extends Transport {
1651
1679
  super.deleteSession(session, options);
1652
1680
  }
1653
1681
  handleConnection(conn) {
1654
- if (this.getStatus() !== "open")
1655
- return;
1682
+ if (this.getStatus() !== "open") return;
1656
1683
  this.log?.info(`new incoming connection`, {
1657
1684
  ...conn.loggingMetadata,
1658
1685
  clientId: this.clientId
@@ -1725,17 +1752,28 @@ var ServerTransport = class extends Transport {
1725
1752
  message: reason
1726
1753
  });
1727
1754
  this.log?.warn(reason, metadata);
1728
- session.sendHandshake(
1729
- handshakeResponseMessage({
1730
- from: this.clientId,
1731
- to,
1732
- status: {
1733
- ok: false,
1734
- code,
1735
- reason
1736
- }
1737
- })
1738
- );
1755
+ const responseMsg = handshakeResponseMessage({
1756
+ from: this.clientId,
1757
+ to,
1758
+ status: {
1759
+ ok: false,
1760
+ code,
1761
+ reason
1762
+ }
1763
+ });
1764
+ const res = session.sendHandshake(responseMsg);
1765
+ if (!res.ok) {
1766
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1767
+ ...session.loggingMetadata,
1768
+ transportMessage: responseMsg
1769
+ });
1770
+ this.protocolError({
1771
+ type: ProtocolError.MessageSendFailure,
1772
+ message: res.reason
1773
+ });
1774
+ this.deletePendingSession(session);
1775
+ return;
1776
+ }
1739
1777
  this.protocolError({
1740
1778
  type: ProtocolError.HandshakeFailed,
1741
1779
  code,
@@ -1919,7 +1957,20 @@ var ServerTransport = class extends Transport {
1919
1957
  sessionId
1920
1958
  }
1921
1959
  });
1922
- session.sendHandshake(responseMsg);
1960
+ const res = session.sendHandshake(responseMsg);
1961
+ if (!res.ok) {
1962
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1963
+ ...session.loggingMetadata,
1964
+ transportMessage: responseMsg
1965
+ });
1966
+ this.protocolError({
1967
+ type: ProtocolError.MessageSendFailure,
1968
+ message: res.reason
1969
+ });
1970
+ this.deletePendingSession(session);
1971
+ return;
1972
+ }
1973
+ this.pendingSessions.delete(session);
1923
1974
  const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
1924
1975
  session,
1925
1976
  // by this point oldSession is either no connection or we dont have an old session
@@ -1946,22 +1997,40 @@ var ServerTransport = class extends Transport {
1946
1997
  this.handleMsg(msg2);
1947
1998
  },
1948
1999
  onInvalidMessage: (reason) => {
2000
+ this.log?.error(`invalid message: ${reason}`, {
2001
+ ...connectedSession.loggingMetadata,
2002
+ transportMessage: msg
2003
+ });
1949
2004
  this.protocolError({
1950
2005
  type: ProtocolError.InvalidMessage,
1951
2006
  message: reason
1952
2007
  });
1953
2008
  this.deleteSession(connectedSession, { unhealthy: true });
2009
+ },
2010
+ onMessageSendFailure: (msg2, reason) => {
2011
+ this.log?.error(`failed to send message: ${reason}`, {
2012
+ ...connectedSession.loggingMetadata,
2013
+ transportMessage: msg2
2014
+ });
2015
+ this.protocolError({
2016
+ type: ProtocolError.MessageSendFailure,
2017
+ message: reason
2018
+ });
2019
+ this.deleteSession(connectedSession, { unhealthy: true });
1954
2020
  }
1955
2021
  },
1956
2022
  gotVersion
1957
2023
  );
2024
+ const bufferSendRes = connectedSession.sendBufferedMessages();
2025
+ if (!bufferSendRes.ok) {
2026
+ return;
2027
+ }
1958
2028
  this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
1959
2029
  if (oldSession) {
1960
2030
  this.updateSession(connectedSession);
1961
2031
  } else {
1962
2032
  this.createSession(connectedSession);
1963
2033
  }
1964
- this.pendingSessions.delete(session);
1965
2034
  connectedSession.startActiveHeartbeat();
1966
2035
  }
1967
2036
  };