@replit/river 0.207.2 → 0.207.3

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 (56) hide show
  1. package/dist/adapter-f2b6e211.d.ts +46 -0
  2. package/dist/{chunk-4HE7UYRL.js → chunk-B7REV3ZV.js} +6 -5
  3. package/dist/{chunk-4HE7UYRL.js.map → chunk-B7REV3ZV.js.map} +1 -1
  4. package/dist/{chunk-24EWYOGK.js → chunk-BO7MFCO6.js} +1136 -143
  5. package/dist/chunk-BO7MFCO6.js.map +1 -0
  6. package/dist/{chunk-46IVOKJU.js → chunk-QGPYCXV4.js} +2 -2
  7. package/dist/{chunk-46IVOKJU.js.map → chunk-QGPYCXV4.js.map} +1 -1
  8. package/dist/codec/index.cjs +157 -23
  9. package/dist/codec/index.cjs.map +1 -1
  10. package/dist/codec/index.d.cts +5 -1
  11. package/dist/codec/index.d.ts +5 -1
  12. package/dist/codec/index.js +6 -20
  13. package/dist/codec/index.js.map +1 -1
  14. package/dist/{connection-a18e31d5.d.ts → connection-06d72f2e.d.ts} +3 -2
  15. package/dist/index-02554794.d.ts +37 -0
  16. package/dist/logging/index.d.cts +2 -1
  17. package/dist/logging/index.d.ts +2 -1
  18. package/dist/{message-ffacb98a.d.ts → message-01c3e85a.d.ts} +1 -35
  19. package/dist/router/index.cjs +1 -1
  20. package/dist/router/index.cjs.map +1 -1
  21. package/dist/router/index.d.cts +6 -5
  22. package/dist/router/index.d.ts +6 -5
  23. package/dist/router/index.js +1 -1
  24. package/dist/{services-43528f4b.d.ts → services-87887bc5.d.ts} +16 -12
  25. package/dist/testUtil/index.cjs +809 -657
  26. package/dist/testUtil/index.cjs.map +1 -1
  27. package/dist/testUtil/index.d.cts +4 -3
  28. package/dist/testUtil/index.d.ts +4 -3
  29. package/dist/testUtil/index.js +18 -13
  30. package/dist/testUtil/index.js.map +1 -1
  31. package/dist/transport/impls/ws/client.cjs +293 -204
  32. package/dist/transport/impls/ws/client.cjs.map +1 -1
  33. package/dist/transport/impls/ws/client.d.cts +5 -4
  34. package/dist/transport/impls/ws/client.d.ts +5 -4
  35. package/dist/transport/impls/ws/client.js +5 -7
  36. package/dist/transport/impls/ws/client.js.map +1 -1
  37. package/dist/transport/impls/ws/server.cjs +230 -128
  38. package/dist/transport/impls/ws/server.cjs.map +1 -1
  39. package/dist/transport/impls/ws/server.d.cts +5 -4
  40. package/dist/transport/impls/ws/server.d.ts +5 -4
  41. package/dist/transport/impls/ws/server.js +5 -7
  42. package/dist/transport/impls/ws/server.js.map +1 -1
  43. package/dist/transport/index.cjs +408 -270
  44. package/dist/transport/index.cjs.map +1 -1
  45. package/dist/transport/index.d.cts +7 -6
  46. package/dist/transport/index.d.ts +7 -6
  47. package/dist/transport/index.js +4 -9
  48. package/package.json +1 -1
  49. package/dist/chunk-24EWYOGK.js.map +0 -1
  50. package/dist/chunk-A7RGOVRV.js +0 -438
  51. package/dist/chunk-A7RGOVRV.js.map +0 -1
  52. package/dist/chunk-AJGIY2UB.js +0 -56
  53. package/dist/chunk-AJGIY2UB.js.map +0 -1
  54. package/dist/chunk-XV4RQ62N.js +0 -377
  55. package/dist/chunk-XV4RQ62N.js.map +0 -1
  56. package/dist/types-3e5768ec.d.ts +0 -20
@@ -235,23 +235,20 @@ var NaiveJsonCodec = {
235
235
  );
236
236
  },
237
237
  fromBuffer: (buff) => {
238
- try {
239
- const parsed = JSON.parse(
240
- decoder.decode(buff),
241
- function reviver(_key, val) {
242
- if (val?.$t) {
243
- return base64ToUint8Array(val.$t);
244
- } else {
245
- return val;
246
- }
238
+ const parsed = JSON.parse(
239
+ decoder.decode(buff),
240
+ function reviver(_key, val) {
241
+ if (val?.$t) {
242
+ return base64ToUint8Array(val.$t);
243
+ } else {
244
+ return val;
247
245
  }
248
- );
249
- if (typeof parsed === "object")
250
- return parsed;
251
- return null;
252
- } catch {
253
- return null;
246
+ }
247
+ );
248
+ if (typeof parsed !== "object" || parsed === null) {
249
+ throw new Error("unpacked msg is not an object");
254
250
  }
251
+ return parsed;
255
252
  }
256
253
  };
257
254
 
@@ -281,7 +278,6 @@ var defaultServerTransportOptions = {
281
278
  };
282
279
 
283
280
  // transport/sessionStateMachine/common.ts
284
- var import_value = require("@sinclair/typebox/value");
285
281
  var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
286
282
  var StateMachineState = class {
287
283
  /*
@@ -341,34 +337,16 @@ var StateMachineState = class {
341
337
  var CommonSession = class extends StateMachineState {
342
338
  from;
343
339
  options;
340
+ codec;
344
341
  tracer;
345
342
  log;
346
- constructor({ from, options, log, tracer }) {
343
+ constructor({ from, options, log, tracer, codec }) {
347
344
  super();
348
345
  this.from = from;
349
346
  this.options = options;
350
347
  this.log = log;
351
348
  this.tracer = tracer;
352
- }
353
- parseMsg(msg) {
354
- const parsedMsg = this.options.codec.fromBuffer(msg);
355
- if (parsedMsg === null) {
356
- this.log?.error(
357
- `received malformed msg: ${Buffer.from(msg).toString("base64")}`,
358
- this.loggingMetadata
359
- );
360
- return null;
361
- }
362
- if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
363
- this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
364
- ...this.loggingMetadata,
365
- validationErrors: [
366
- ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
367
- ]
368
- });
369
- return null;
370
- }
371
- return parsedMsg;
349
+ this.codec = codec;
372
350
  }
373
351
  };
374
352
  var IdentifiedSession = class extends CommonSession {
@@ -428,9 +406,6 @@ var IdentifiedSession = class extends CommonSession {
428
406
  return metadata;
429
407
  }
430
408
  constructMsg(partialMsg) {
431
- if (this._isConsumed) {
432
- throw new Error(ERR_CONSUMED);
433
- }
434
409
  const msg = {
435
410
  ...partialMsg,
436
411
  id: generateId(),
@@ -448,7 +423,10 @@ var IdentifiedSession = class extends CommonSession {
448
423
  send(msg) {
449
424
  const constructedMsg = this.constructMsg(msg);
450
425
  this.sendBuffer.push(constructedMsg);
451
- return constructedMsg.id;
426
+ return {
427
+ ok: true,
428
+ value: constructedMsg.id
429
+ };
452
430
  }
453
431
  _handleStateExit() {
454
432
  }
@@ -480,6 +458,23 @@ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
480
458
  super._handleClose();
481
459
  }
482
460
  };
461
+ function sendMessage(conn, codec, msg) {
462
+ const buff = codec.toBuffer(msg);
463
+ if (!buff.ok) {
464
+ return buff;
465
+ }
466
+ const sent = conn.send(buff.value);
467
+ if (!sent) {
468
+ return {
469
+ ok: false,
470
+ reason: "failed to send message"
471
+ };
472
+ }
473
+ return {
474
+ ok: true,
475
+ value: msg.id
476
+ };
477
+ }
483
478
 
484
479
  // transport/sessionStateMachine/SessionConnecting.ts
485
480
  var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
@@ -532,8 +527,8 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
532
527
  }
533
528
  }
534
529
  _handleClose() {
535
- this.bestEffortClose();
536
530
  super._handleClose();
531
+ this.bestEffortClose();
537
532
  }
538
533
  };
539
534
 
@@ -560,7 +555,7 @@ function coerceErrorString(err) {
560
555
  }
561
556
 
562
557
  // package.json
563
- var version = "0.207.2";
558
+ var version = "0.207.3";
564
559
 
565
560
  // tracing/index.ts
566
561
  function getPropagationContext(ctx) {
@@ -632,18 +627,18 @@ var SessionWaitingForHandshake = class extends CommonSession {
632
627
  };
633
628
  }
634
629
  onHandshakeData = (msg) => {
635
- const parsedMsg = this.parseMsg(msg);
636
- if (parsedMsg === null) {
630
+ const parsedMsgRes = this.codec.fromBuffer(msg);
631
+ if (!parsedMsgRes.ok) {
637
632
  this.listeners.onInvalidHandshake(
638
- "could not parse message",
633
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
639
634
  "MALFORMED_HANDSHAKE"
640
635
  );
641
636
  return;
642
637
  }
643
- this.listeners.onHandshake(parsedMsg);
638
+ this.listeners.onHandshake(parsedMsgRes.value);
644
639
  };
645
640
  sendHandshake(msg) {
646
- return this.conn.send(this.options.codec.toBuffer(msg));
641
+ return sendMessage(this.conn, this.codec, msg);
647
642
  }
648
643
  _handleStateExit() {
649
644
  this.conn.removeDataListener(this.onHandshakeData);
@@ -681,18 +676,18 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
681
676
  };
682
677
  }
683
678
  onHandshakeData = (msg) => {
684
- const parsedMsg = this.parseMsg(msg);
685
- if (parsedMsg === null) {
679
+ const parsedMsgRes = this.codec.fromBuffer(msg);
680
+ if (!parsedMsgRes.ok) {
686
681
  this.listeners.onInvalidHandshake(
687
- "could not parse message",
682
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
688
683
  "MALFORMED_HANDSHAKE"
689
684
  );
690
685
  return;
691
686
  }
692
- this.listeners.onHandshake(parsedMsg);
687
+ this.listeners.onHandshake(parsedMsgRes.value);
693
688
  };
694
689
  sendHandshake(msg) {
695
- return this.conn.send(this.options.codec.toBuffer(msg));
690
+ return sendMessage(this.conn, this.codec, msg);
696
691
  }
697
692
  _handleStateExit() {
698
693
  super._handleStateExit();
@@ -717,25 +712,15 @@ var SessionConnected = class extends IdentifiedSession {
717
712
  conn;
718
713
  listeners;
719
714
  heartbeatHandle;
720
- heartbeatMisses = 0;
721
- isActivelyHeartbeating;
722
- lastConstructedMsgs = [];
723
- pushLastConstructedMsgs = (msg) => {
724
- const trackedMsg = {
725
- id: msg.id,
726
- seq: msg.seq,
727
- streamId: msg.streamId,
728
- stack: new Error().stack
729
- };
730
- this.lastConstructedMsgs.push(trackedMsg);
731
- if (this.lastConstructedMsgs.length > 10) {
732
- this.lastConstructedMsgs.shift();
733
- }
734
- };
715
+ heartbeatMissTimeout;
716
+ isActivelyHeartbeating = false;
735
717
  updateBookkeeping(ack, seq) {
736
718
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
737
719
  this.ack = seq + 1;
738
- this.heartbeatMisses = 0;
720
+ if (this.heartbeatMissTimeout) {
721
+ clearTimeout(this.heartbeatMissTimeout);
722
+ }
723
+ this.startMissingHeartbeatTimeout();
739
724
  }
740
725
  assertSendOrdering(constructedMsg) {
741
726
  if (constructedMsg.seq > this.seqSent + 1) {
@@ -743,22 +728,22 @@ var SessionConnected = class extends IdentifiedSession {
743
728
  this.log?.error(msg, {
744
729
  ...this.loggingMetadata,
745
730
  transportMessage: constructedMsg,
746
- tags: ["invariant-violation"],
747
- extras: {
748
- lastConstructedMsgs: this.lastConstructedMsgs
749
- }
731
+ tags: ["invariant-violation"]
750
732
  });
751
733
  throw new Error(msg);
752
734
  }
753
735
  }
754
736
  send(msg) {
755
737
  const constructedMsg = this.constructMsg(msg);
756
- this.pushLastConstructedMsgs(constructedMsg);
757
738
  this.assertSendOrdering(constructedMsg);
758
739
  this.sendBuffer.push(constructedMsg);
759
- this.conn.send(this.options.codec.toBuffer(constructedMsg));
740
+ const res = sendMessage(this.conn, this.codec, constructedMsg);
741
+ if (!res.ok) {
742
+ this.listeners.onMessageSendFailure(constructedMsg, res.reason);
743
+ return res;
744
+ }
760
745
  this.seqSent = constructedMsg.seq;
761
- return constructedMsg.id;
746
+ return res;
762
747
  }
763
748
  constructor(props) {
764
749
  super(props);
@@ -767,6 +752,8 @@ var SessionConnected = class extends IdentifiedSession {
767
752
  this.conn.addDataListener(this.onMessageData);
768
753
  this.conn.addCloseListener(this.listeners.onConnectionClosed);
769
754
  this.conn.addErrorListener(this.listeners.onConnectionErrored);
755
+ }
756
+ sendBufferedMessages() {
770
757
  if (this.sendBuffer.length > 0) {
771
758
  this.log?.info(
772
759
  `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
@@ -774,30 +761,15 @@ var SessionConnected = class extends IdentifiedSession {
774
761
  );
775
762
  for (const msg of this.sendBuffer) {
776
763
  this.assertSendOrdering(msg);
777
- this.conn.send(this.options.codec.toBuffer(msg));
764
+ const res = sendMessage(this.conn, this.codec, msg);
765
+ if (!res.ok) {
766
+ this.listeners.onMessageSendFailure(msg, res.reason);
767
+ return res;
768
+ }
778
769
  this.seqSent = msg.seq;
779
770
  }
780
771
  }
781
- this.isActivelyHeartbeating = false;
782
- this.heartbeatHandle = setInterval(() => {
783
- const misses = this.heartbeatMisses;
784
- const missDuration = misses * this.options.heartbeatIntervalMs;
785
- if (misses >= this.options.heartbeatsUntilDead) {
786
- this.log?.info(
787
- `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
788
- this.loggingMetadata
789
- );
790
- this.telemetry.span.addEvent("closing connection due to inactivity");
791
- this.conn.close();
792
- clearInterval(this.heartbeatHandle);
793
- this.heartbeatHandle = void 0;
794
- return;
795
- }
796
- if (this.isActivelyHeartbeating) {
797
- this.sendHeartbeat();
798
- }
799
- this.heartbeatMisses++;
800
- }, this.options.heartbeatIntervalMs);
772
+ return { ok: true, value: void 0 };
801
773
  }
802
774
  get loggingMetadata() {
803
775
  return {
@@ -805,25 +777,46 @@ var SessionConnected = class extends IdentifiedSession {
805
777
  ...this.conn.loggingMetadata
806
778
  };
807
779
  }
780
+ startMissingHeartbeatTimeout() {
781
+ const maxMisses = this.options.heartbeatsUntilDead;
782
+ const missDuration = maxMisses * this.options.heartbeatIntervalMs;
783
+ this.heartbeatMissTimeout = setTimeout(() => {
784
+ this.log?.info(
785
+ `closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
786
+ this.loggingMetadata
787
+ );
788
+ this.telemetry.span.addEvent(
789
+ "closing connection due to missing heartbeat"
790
+ );
791
+ this.conn.close();
792
+ }, missDuration);
793
+ }
808
794
  startActiveHeartbeat() {
809
795
  this.isActivelyHeartbeating = true;
796
+ this.heartbeatHandle = setInterval(() => {
797
+ this.sendHeartbeat();
798
+ }, this.options.heartbeatIntervalMs);
810
799
  }
811
800
  sendHeartbeat() {
812
801
  this.log?.debug("sending heartbeat", this.loggingMetadata);
813
- this.send({
802
+ const heartbeat = {
814
803
  streamId: "heartbeat",
815
804
  controlFlags: 1 /* AckBit */,
816
805
  payload: {
817
806
  type: "ACK"
818
807
  }
819
- });
808
+ };
809
+ this.send(heartbeat);
820
810
  }
821
811
  onMessageData = (msg) => {
822
- const parsedMsg = this.parseMsg(msg);
823
- if (parsedMsg === null) {
824
- this.listeners.onInvalidMessage("could not parse message");
812
+ const parsedMsgRes = this.codec.fromBuffer(msg);
813
+ if (!parsedMsgRes.ok) {
814
+ this.listeners.onInvalidMessage(
815
+ `could not parse message: ${parsedMsgRes.reason}`
816
+ );
825
817
  return;
826
818
  }
819
+ const parsedMsg = parsedMsgRes.value;
827
820
  if (parsedMsg.seq !== this.ack) {
828
821
  if (parsedMsg.seq < this.ack) {
829
822
  this.log?.debug(
@@ -862,9 +855,7 @@ var SessionConnected = class extends IdentifiedSession {
862
855
  transportMessage: parsedMsg
863
856
  });
864
857
  if (!this.isActivelyHeartbeating) {
865
- void Promise.resolve().then(() => {
866
- this.sendHeartbeat();
867
- });
858
+ this.sendHeartbeat();
868
859
  }
869
860
  };
870
861
  _handleStateExit() {
@@ -876,6 +867,10 @@ var SessionConnected = class extends IdentifiedSession {
876
867
  clearInterval(this.heartbeatHandle);
877
868
  this.heartbeatHandle = void 0;
878
869
  }
870
+ if (this.heartbeatMissTimeout) {
871
+ clearTimeout(this.heartbeatMissTimeout);
872
+ this.heartbeatMissTimeout = void 0;
873
+ }
879
874
  }
880
875
  _handleClose() {
881
876
  super._handleClose();
@@ -907,425 +902,47 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
907
902
  }
908
903
  };
909
904
 
910
- // transport/sessionStateMachine/transitions.ts
911
- function inheritSharedSession(session) {
912
- return {
913
- id: session.id,
914
- from: session.from,
915
- to: session.to,
916
- seq: session.seq,
917
- ack: session.ack,
918
- seqSent: session.seqSent,
919
- sendBuffer: session.sendBuffer,
920
- telemetry: session.telemetry,
921
- options: session.options,
922
- log: session.log,
923
- tracer: session.tracer,
924
- protocolVersion: session.protocolVersion
925
- };
926
- }
927
- function inheritSharedSessionWithGrace(session) {
928
- return {
929
- ...inheritSharedSession(session),
930
- graceExpiryTime: session.graceExpiryTime
905
+ // codec/adapter.ts
906
+ var import_value3 = require("@sinclair/typebox/value");
907
+
908
+ // logging/log.ts
909
+ var import_api3 = require("@opentelemetry/api");
910
+ var LoggingLevels = {
911
+ debug: -1,
912
+ info: 0,
913
+ warn: 1,
914
+ error: 2
915
+ };
916
+ var cleanedLogFn = (log) => {
917
+ return (msg, metadata) => {
918
+ if (metadata && !metadata.telemetry) {
919
+ const span = import_api3.trace.getSpan(import_api3.context.active());
920
+ if (span) {
921
+ metadata.telemetry = {
922
+ traceId: span.spanContext().traceId,
923
+ spanId: span.spanContext().spanId
924
+ };
925
+ }
926
+ }
927
+ if (!metadata?.transportMessage) {
928
+ log(msg, metadata);
929
+ return;
930
+ }
931
+ const { payload, ...rest } = metadata.transportMessage;
932
+ metadata.transportMessage = rest;
933
+ log(msg, metadata);
931
934
  };
932
- }
933
- var SessionStateGraph = {
934
- entrypoints: {
935
- NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
936
- const id = `session-${generateId()}`;
937
- const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
938
- const sendBuffer = [];
939
- const session = new SessionNoConnection({
940
- listeners,
941
- id,
942
- from,
943
- to,
944
- seq: 0,
945
- ack: 0,
946
- seqSent: 0,
947
- graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
948
- sendBuffer,
949
- telemetry,
950
- options,
951
- protocolVersion,
952
- tracer,
953
- log
954
- });
955
- session.log?.info(`session ${session.id} created in NoConnection state`, {
956
- ...session.loggingMetadata,
957
- tags: ["state-transition"]
958
- });
959
- return session;
960
- },
961
- WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
962
- const session = new SessionWaitingForHandshake({
963
- conn,
964
- listeners,
965
- from,
966
- options,
967
- tracer,
968
- log
969
- });
970
- session.log?.info(`session created in WaitingForHandshake state`, {
971
- ...session.loggingMetadata,
972
- tags: ["state-transition"]
973
- });
974
- return session;
975
- }
976
- },
977
- // All of the transitions 'move'/'consume' the old session and return a new one.
978
- // After a session is transitioned, any usage of the old session will throw.
979
- transition: {
980
- // happy path transitions
981
- NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
982
- const carriedState = inheritSharedSessionWithGrace(oldSession);
983
- oldSession._handleStateExit();
984
- const session = new SessionBackingOff({
985
- backoffMs,
986
- listeners,
987
- ...carriedState
988
- });
989
- session.log?.info(
990
- `session ${session.id} transition from NoConnection to BackingOff`,
991
- {
992
- ...session.loggingMetadata,
993
- tags: ["state-transition"]
994
- }
995
- );
996
- return session;
997
- },
998
- BackingOffToConnecting: (oldSession, connPromise, listeners) => {
999
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1000
- oldSession._handleStateExit();
1001
- const session = new SessionConnecting({
1002
- connPromise,
1003
- listeners,
1004
- ...carriedState
1005
- });
1006
- session.log?.info(
1007
- `session ${session.id} transition from BackingOff to Connecting`,
1008
- {
1009
- ...session.loggingMetadata,
1010
- tags: ["state-transition"]
1011
- }
1012
- );
1013
- return session;
1014
- },
1015
- ConnectingToHandshaking: (oldSession, conn, listeners) => {
1016
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1017
- oldSession._handleStateExit();
1018
- const session = new SessionHandshaking({
1019
- conn,
1020
- listeners,
1021
- ...carriedState
1022
- });
1023
- conn.telemetry = createConnectionTelemetryInfo(
1024
- session.tracer,
1025
- conn,
1026
- session.telemetry
1027
- );
1028
- session.log?.info(
1029
- `session ${session.id} transition from Connecting to Handshaking`,
1030
- {
1031
- ...session.loggingMetadata,
1032
- tags: ["state-transition"]
1033
- }
1034
- );
1035
- return session;
1036
- },
1037
- HandshakingToConnected: (oldSession, listeners) => {
1038
- const carriedState = inheritSharedSession(oldSession);
1039
- const conn = oldSession.conn;
1040
- oldSession._handleStateExit();
1041
- const session = new SessionConnected({
1042
- conn,
1043
- listeners,
1044
- ...carriedState
1045
- });
1046
- session.log?.info(
1047
- `session ${session.id} transition from Handshaking to Connected`,
1048
- {
1049
- ...session.loggingMetadata,
1050
- tags: ["state-transition"]
1051
- }
1052
- );
1053
- return session;
1054
- },
1055
- WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
1056
- const conn = pendingSession.conn;
1057
- const { from, options } = pendingSession;
1058
- const carriedState = oldSession ? (
1059
- // old session exists, inherit state
1060
- inheritSharedSession(oldSession)
1061
- ) : (
1062
- // old session does not exist, create new state
1063
- {
1064
- id: sessionId,
1065
- from,
1066
- to,
1067
- seq: 0,
1068
- ack: 0,
1069
- seqSent: 0,
1070
- sendBuffer: [],
1071
- telemetry: createSessionTelemetryInfo(
1072
- pendingSession.tracer,
1073
- sessionId,
1074
- to,
1075
- from,
1076
- propagationCtx
1077
- ),
1078
- options,
1079
- tracer: pendingSession.tracer,
1080
- log: pendingSession.log,
1081
- protocolVersion
1082
- }
1083
- );
1084
- pendingSession._handleStateExit();
1085
- oldSession?._handleStateExit();
1086
- const session = new SessionConnected({
1087
- conn,
1088
- listeners,
1089
- ...carriedState
1090
- });
1091
- conn.telemetry = createConnectionTelemetryInfo(
1092
- session.tracer,
1093
- conn,
1094
- session.telemetry
1095
- );
1096
- session.log?.info(
1097
- `session ${session.id} transition from WaitingForHandshake to Connected`,
1098
- {
1099
- ...session.loggingMetadata,
1100
- tags: ["state-transition"]
1101
- }
1102
- );
1103
- return session;
1104
- },
1105
- // disconnect paths
1106
- BackingOffToNoConnection: (oldSession, listeners) => {
1107
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1108
- oldSession._handleStateExit();
1109
- const session = new SessionNoConnection({
1110
- listeners,
1111
- ...carriedState
1112
- });
1113
- session.log?.info(
1114
- `session ${session.id} transition from BackingOff to NoConnection`,
1115
- {
1116
- ...session.loggingMetadata,
1117
- tags: ["state-transition"]
1118
- }
1119
- );
1120
- return session;
1121
- },
1122
- ConnectingToNoConnection: (oldSession, listeners) => {
1123
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1124
- oldSession.bestEffortClose();
1125
- oldSession._handleStateExit();
1126
- const session = new SessionNoConnection({
1127
- listeners,
1128
- ...carriedState
1129
- });
1130
- session.log?.info(
1131
- `session ${session.id} transition from Connecting to NoConnection`,
1132
- {
1133
- ...session.loggingMetadata,
1134
- tags: ["state-transition"]
1135
- }
1136
- );
1137
- return session;
1138
- },
1139
- HandshakingToNoConnection: (oldSession, listeners) => {
1140
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1141
- oldSession.conn.close();
1142
- oldSession._handleStateExit();
1143
- const session = new SessionNoConnection({
1144
- listeners,
1145
- ...carriedState
1146
- });
1147
- session.log?.info(
1148
- `session ${session.id} transition from Handshaking to NoConnection`,
1149
- {
1150
- ...session.loggingMetadata,
1151
- tags: ["state-transition"]
1152
- }
1153
- );
1154
- return session;
1155
- },
1156
- ConnectedToNoConnection: (oldSession, listeners) => {
1157
- const carriedState = inheritSharedSession(oldSession);
1158
- const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
1159
- oldSession.conn.close();
1160
- oldSession._handleStateExit();
1161
- const session = new SessionNoConnection({
1162
- listeners,
1163
- graceExpiryTime,
1164
- ...carriedState
1165
- });
1166
- session.log?.info(
1167
- `session ${session.id} transition from Connected to NoConnection`,
1168
- {
1169
- ...session.loggingMetadata,
1170
- tags: ["state-transition"]
1171
- }
1172
- );
1173
- return session;
1174
- }
1175
- }
1176
- };
1177
- var transitions = SessionStateGraph.transition;
1178
- var ClientSessionStateGraph = {
1179
- entrypoint: SessionStateGraph.entrypoints.NoConnection,
1180
- transition: {
1181
- // happy paths
1182
- // NoConnection -> BackingOff: attempt to connect
1183
- NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
1184
- // BackingOff -> Connecting: backoff period elapsed, start connection
1185
- BackingOffToConnecting: transitions.BackingOffToConnecting,
1186
- // Connecting -> Handshaking: connection established, start handshake
1187
- ConnectingToHandshaking: transitions.ConnectingToHandshaking,
1188
- // Handshaking -> Connected: handshake complete, session ready
1189
- HandshakingToConnected: transitions.HandshakingToConnected,
1190
- // disconnect paths
1191
- // BackingOff -> NoConnection: unused
1192
- BackingOffToNoConnection: transitions.BackingOffToNoConnection,
1193
- // Connecting -> NoConnection: connection failed or connection timeout
1194
- ConnectingToNoConnection: transitions.ConnectingToNoConnection,
1195
- // Handshaking -> NoConnection: connection closed or handshake timeout
1196
- HandshakingToNoConnection: transitions.HandshakingToNoConnection,
1197
- // Connected -> NoConnection: connection closed
1198
- ConnectedToNoConnection: transitions.ConnectedToNoConnection
1199
- // destroy/close paths
1200
- // NoConnection -> x: grace period elapsed
1201
- // BackingOff -> x: grace period elapsed
1202
- // Connecting -> x: grace period elapsed
1203
- // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
1204
- // Connected -> x: grace period elapsed or invalid message
1205
- }
1206
- };
1207
- var ServerSessionStateGraph = {
1208
- entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
1209
- transition: {
1210
- // happy paths
1211
- // WaitingForHandshake -> Connected: handshake complete, session ready
1212
- WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
1213
- // disconnect paths
1214
- // Connected -> NoConnection: connection closed
1215
- ConnectedToNoConnection: transitions.ConnectedToNoConnection
1216
- // destroy/close paths
1217
- // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
1218
- }
1219
- };
1220
-
1221
- // transport/client.ts
1222
- var import_api4 = require("@opentelemetry/api");
1223
-
1224
- // transport/rateLimit.ts
1225
- var LeakyBucketRateLimit = class {
1226
- budgetConsumed;
1227
- intervalHandle;
1228
- options;
1229
- constructor(options) {
1230
- this.options = options;
1231
- this.budgetConsumed = 0;
1232
- }
1233
- getBackoffMs() {
1234
- if (this.getBudgetConsumed() === 0) {
1235
- return 0;
1236
- }
1237
- const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1238
- const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1239
- const backoffMs = Math.min(
1240
- this.options.baseIntervalMs * 2 ** exponent,
1241
- this.options.maxBackoffMs
1242
- );
1243
- return backoffMs + jitter;
1244
- }
1245
- get totalBudgetRestoreTime() {
1246
- return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1247
- }
1248
- consumeBudget() {
1249
- this.stopLeak();
1250
- this.budgetConsumed = this.getBudgetConsumed() + 1;
1251
- }
1252
- getBudgetConsumed() {
1253
- return this.budgetConsumed;
1254
- }
1255
- hasBudget() {
1256
- return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1257
- }
1258
- startRestoringBudget() {
1259
- if (this.intervalHandle) {
1260
- return;
1261
- }
1262
- const restoreBudgetForUser = () => {
1263
- const currentBudget = this.budgetConsumed;
1264
- if (!currentBudget) {
1265
- this.stopLeak();
1266
- return;
1267
- }
1268
- const newBudget = currentBudget - 1;
1269
- if (newBudget === 0) {
1270
- return;
1271
- }
1272
- this.budgetConsumed = newBudget;
1273
- };
1274
- this.intervalHandle = setInterval(
1275
- restoreBudgetForUser,
1276
- this.options.budgetRestoreIntervalMs
1277
- );
1278
- }
1279
- stopLeak() {
1280
- if (!this.intervalHandle) {
1281
- return;
1282
- }
1283
- clearInterval(this.intervalHandle);
1284
- this.intervalHandle = void 0;
1285
- }
1286
- close() {
1287
- this.stopLeak();
1288
- }
1289
- };
1290
-
1291
- // logging/log.ts
1292
- var import_api3 = require("@opentelemetry/api");
1293
- var LoggingLevels = {
1294
- debug: -1,
1295
- info: 0,
1296
- warn: 1,
1297
- error: 2
1298
- };
1299
- var cleanedLogFn = (log) => {
1300
- return (msg, metadata) => {
1301
- if (metadata && !metadata.telemetry) {
1302
- const span = import_api3.trace.getSpan(import_api3.context.active());
1303
- if (span) {
1304
- metadata.telemetry = {
1305
- traceId: span.spanContext().traceId,
1306
- spanId: span.spanContext().spanId
1307
- };
1308
- }
1309
- }
1310
- if (!metadata?.transportMessage) {
1311
- log(msg, metadata);
1312
- return;
1313
- }
1314
- const { payload, ...rest } = metadata.transportMessage;
1315
- metadata.transportMessage = rest;
1316
- log(msg, metadata);
1317
- };
1318
- };
1319
- var BaseLogger = class {
1320
- minLevel;
1321
- output;
1322
- constructor(output, minLevel = "info") {
1323
- this.minLevel = minLevel;
1324
- this.output = output;
1325
- }
1326
- debug(msg, metadata) {
1327
- if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
1328
- this.output(msg, metadata ?? {}, "debug");
935
+ };
936
+ var BaseLogger = class {
937
+ minLevel;
938
+ output;
939
+ constructor(output, minLevel = "info") {
940
+ this.minLevel = minLevel;
941
+ this.output = output;
942
+ }
943
+ debug(msg, metadata) {
944
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
945
+ this.output(msg, metadata ?? {}, "debug");
1329
946
  }
1330
947
  }
1331
948
  info(msg, metadata) {
@@ -1356,7 +973,8 @@ var ProtocolError = {
1356
973
  RetriesExceeded: "conn_retry_exceeded",
1357
974
  HandshakeFailed: "handshake_failed",
1358
975
  MessageOrderingViolated: "message_ordering_violated",
1359
- InvalidMessage: "invalid_message"
976
+ InvalidMessage: "invalid_message",
977
+ MessageSendFailure: "message_send_failure"
1360
978
  };
1361
979
  var EventDispatcher = class {
1362
980
  eventListeners = {};
@@ -1599,18 +1217,92 @@ var Transport = class {
1599
1217
  );
1600
1218
  }
1601
1219
  const sameSession = session.id === sessionId;
1602
- if (!sameSession) {
1220
+ if (!sameSession || session._isConsumed) {
1603
1221
  throw new Error(
1604
1222
  `session scope for ${sessionId} has ended (transition), can't send`
1605
1223
  );
1606
1224
  }
1607
- return session.send(msg);
1225
+ const res = session.send(msg);
1226
+ if (!res.ok) {
1227
+ throw new Error(res.reason);
1228
+ }
1229
+ return res.value;
1608
1230
  };
1609
1231
  }
1610
1232
  };
1611
1233
 
1612
1234
  // transport/client.ts
1613
- var import_value2 = require("@sinclair/typebox/value");
1235
+ var import_api4 = require("@opentelemetry/api");
1236
+
1237
+ // transport/rateLimit.ts
1238
+ var LeakyBucketRateLimit = class {
1239
+ budgetConsumed;
1240
+ intervalHandle;
1241
+ options;
1242
+ constructor(options) {
1243
+ this.options = options;
1244
+ this.budgetConsumed = 0;
1245
+ }
1246
+ getBackoffMs() {
1247
+ if (this.getBudgetConsumed() === 0) {
1248
+ return 0;
1249
+ }
1250
+ const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1251
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1252
+ const backoffMs = Math.min(
1253
+ this.options.baseIntervalMs * 2 ** exponent,
1254
+ this.options.maxBackoffMs
1255
+ );
1256
+ return backoffMs + jitter;
1257
+ }
1258
+ get totalBudgetRestoreTime() {
1259
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1260
+ }
1261
+ consumeBudget() {
1262
+ this.stopLeak();
1263
+ this.budgetConsumed = this.getBudgetConsumed() + 1;
1264
+ }
1265
+ getBudgetConsumed() {
1266
+ return this.budgetConsumed;
1267
+ }
1268
+ hasBudget() {
1269
+ return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1270
+ }
1271
+ startRestoringBudget() {
1272
+ if (this.intervalHandle) {
1273
+ return;
1274
+ }
1275
+ const restoreBudgetForUser = () => {
1276
+ const currentBudget = this.budgetConsumed;
1277
+ if (!currentBudget) {
1278
+ this.stopLeak();
1279
+ return;
1280
+ }
1281
+ const newBudget = currentBudget - 1;
1282
+ if (newBudget === 0) {
1283
+ return;
1284
+ }
1285
+ this.budgetConsumed = newBudget;
1286
+ };
1287
+ this.intervalHandle = setInterval(
1288
+ restoreBudgetForUser,
1289
+ this.options.budgetRestoreIntervalMs
1290
+ );
1291
+ }
1292
+ stopLeak() {
1293
+ if (!this.intervalHandle) {
1294
+ return;
1295
+ }
1296
+ clearInterval(this.intervalHandle);
1297
+ this.intervalHandle = void 0;
1298
+ }
1299
+ close() {
1300
+ this.stopLeak();
1301
+ }
1302
+ };
1303
+
1304
+ // transport/client.ts
1305
+ var import_value = require("@sinclair/typebox/value");
1614
1306
  var ClientTransport = class extends Transport {
1615
1307
  /**
1616
1308
  * The options for this transport.
@@ -1741,19 +1433,19 @@ var ClientTransport = class extends Transport {
1741
1433
  this.deleteSession(session, { unhealthy: true });
1742
1434
  }
1743
1435
  onHandshakeResponse(session, msg) {
1744
- if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1436
+ if (!import_value.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1745
1437
  const reason = `received invalid handshake response`;
1746
1438
  this.rejectHandshakeResponse(session, reason, {
1747
1439
  ...session.loggingMetadata,
1748
1440
  transportMessage: msg,
1749
1441
  validationErrors: [
1750
- ...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1442
+ ...import_value.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1751
1443
  ]
1752
1444
  });
1753
1445
  return;
1754
1446
  }
1755
1447
  if (!msg.payload.status.ok) {
1756
- const retriable = import_value2.Value.Check(
1448
+ const retriable = import_value.Value.Check(
1757
1449
  HandshakeErrorRetriableResponseCodes,
1758
1450
  msg.payload.status.code
1759
1451
  );
@@ -1805,13 +1497,41 @@ var ClientTransport = class extends Transport {
1805
1497
  this.handleMsg(msg2);
1806
1498
  },
1807
1499
  onInvalidMessage: (reason) => {
1808
- this.deleteSession(connectedSession, { unhealthy: true });
1500
+ this.log?.error(`invalid message: ${reason}`, {
1501
+ ...connectedSession.loggingMetadata,
1502
+ transportMessage: msg
1503
+ });
1809
1504
  this.protocolError({
1810
1505
  type: ProtocolError.InvalidMessage,
1811
1506
  message: reason
1812
1507
  });
1508
+ this.deleteSession(connectedSession, { unhealthy: true });
1509
+ },
1510
+ onMessageSendFailure: (msg2, reason) => {
1511
+ this.log?.error(`failed to send message: ${reason}`, {
1512
+ ...connectedSession.loggingMetadata,
1513
+ transportMessage: msg2
1514
+ });
1515
+ this.protocolError({
1516
+ type: ProtocolError.MessageSendFailure,
1517
+ message: reason
1518
+ });
1519
+ this.deleteSession(connectedSession, { unhealthy: true });
1813
1520
  }
1814
1521
  });
1522
+ const res = connectedSession.sendBufferedMessages();
1523
+ if (!res.ok) {
1524
+ this.log?.error(`failed to send buffered messages: ${res.reason}`, {
1525
+ ...connectedSession.loggingMetadata,
1526
+ transportMessage: msg
1527
+ });
1528
+ this.protocolError({
1529
+ type: ProtocolError.MessageSendFailure,
1530
+ message: res.reason
1531
+ });
1532
+ this.deleteSession(connectedSession, { unhealthy: true });
1533
+ return;
1534
+ }
1815
1535
  this.updateSession(connectedSession);
1816
1536
  this.retryBudget.startRestoringBudget();
1817
1537
  }
@@ -1950,7 +1670,18 @@ var ClientTransport = class extends Transport {
1950
1670
  ...session.loggingMetadata,
1951
1671
  transportMessage: requestMsg
1952
1672
  });
1953
- session.sendHandshake(requestMsg);
1673
+ const res = session.sendHandshake(requestMsg);
1674
+ if (!res.ok) {
1675
+ this.log?.error(`failed to send handshake request: ${res.reason}`, {
1676
+ ...session.loggingMetadata,
1677
+ transportMessage: requestMsg
1678
+ });
1679
+ this.protocolError({
1680
+ type: ProtocolError.MessageSendFailure,
1681
+ message: res.reason
1682
+ });
1683
+ this.deleteSession(session, { unhealthy: true });
1684
+ }
1954
1685
  }
1955
1686
  close() {
1956
1687
  this.retryBudget.close();
@@ -1958,96 +1689,9 @@ var ClientTransport = class extends Transport {
1958
1689
  }
1959
1690
  };
1960
1691
 
1961
- // transport/connection.ts
1962
- var Connection = class {
1963
- id;
1964
- telemetry;
1965
- constructor() {
1966
- this.id = `conn-${generateId()}`;
1967
- }
1968
- get loggingMetadata() {
1969
- const metadata = { connId: this.id };
1970
- if (this.telemetry?.span.isRecording()) {
1971
- const spanContext = this.telemetry.span.spanContext();
1972
- metadata.telemetry = {
1973
- traceId: spanContext.traceId,
1974
- spanId: spanContext.spanId
1975
- };
1976
- }
1977
- return metadata;
1978
- }
1979
- // can't use event emitter because we need this to work in both node + browser
1980
- _dataListeners = /* @__PURE__ */ new Set();
1981
- _closeListeners = /* @__PURE__ */ new Set();
1982
- _errorListeners = /* @__PURE__ */ new Set();
1983
- get dataListeners() {
1984
- return [...this._dataListeners];
1985
- }
1986
- get closeListeners() {
1987
- return [...this._closeListeners];
1988
- }
1989
- get errorListeners() {
1990
- return [...this._errorListeners];
1991
- }
1992
- onData(msg) {
1993
- for (const cb of this.dataListeners) {
1994
- cb(msg);
1995
- }
1996
- }
1997
- onError(err) {
1998
- for (const cb of this.errorListeners) {
1999
- cb(err);
2000
- }
2001
- }
2002
- onClose() {
2003
- for (const cb of this.closeListeners) {
2004
- cb();
2005
- }
2006
- this.telemetry?.span.end();
2007
- }
2008
- /**
2009
- * Handle adding a callback for when a message is received.
2010
- * @param msg The message that was received.
2011
- */
2012
- addDataListener(cb) {
2013
- this._dataListeners.add(cb);
2014
- }
2015
- removeDataListener(cb) {
2016
- this._dataListeners.delete(cb);
2017
- }
2018
- /**
2019
- * Handle adding a callback for when the connection is closed.
2020
- * This should also be called if an error happens and after notifying all the error listeners.
2021
- * @param cb The callback to call when the connection is closed.
2022
- */
2023
- addCloseListener(cb) {
2024
- this._closeListeners.add(cb);
2025
- }
2026
- removeCloseListener(cb) {
2027
- this._closeListeners.delete(cb);
2028
- }
2029
- /**
2030
- * Handle adding a callback for when an error is received.
2031
- * This should only be used for this.logging errors, all cleanup
2032
- * should be delegated to addCloseListener.
2033
- *
2034
- * The implementer should take care such that the implemented
2035
- * connection will call both the close and error callbacks
2036
- * on an error.
2037
- *
2038
- * @param cb The callback to call when an error is received.
2039
- */
2040
- addErrorListener(cb) {
2041
- this._errorListeners.add(cb);
2042
- }
2043
- removeErrorListener(cb) {
2044
- this._errorListeners.delete(cb);
2045
- }
2046
- };
2047
-
2048
1692
  // transport/server.ts
2049
1693
  var import_api5 = require("@opentelemetry/api");
2050
- var import_value3 = require("@sinclair/typebox/value");
1694
+ var import_value2 = require("@sinclair/typebox/value");
2051
1695
  var ServerTransport = class extends Transport {
2052
1696
  /**
2053
1697
  * The options for this transport.
@@ -2161,17 +1805,28 @@ var ServerTransport = class extends Transport {
2161
1805
  message: reason
2162
1806
  });
2163
1807
  this.log?.warn(reason, metadata);
2164
- session.sendHandshake(
2165
- handshakeResponseMessage({
2166
- from: this.clientId,
2167
- to,
2168
- status: {
2169
- ok: false,
2170
- code,
2171
- reason
2172
- }
2173
- })
2174
- );
1808
+ const responseMsg = handshakeResponseMessage({
1809
+ from: this.clientId,
1810
+ to,
1811
+ status: {
1812
+ ok: false,
1813
+ code,
1814
+ reason
1815
+ }
1816
+ });
1817
+ const res = session.sendHandshake(responseMsg);
1818
+ if (!res.ok) {
1819
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1820
+ ...session.loggingMetadata,
1821
+ transportMessage: responseMsg
1822
+ });
1823
+ this.protocolError({
1824
+ type: ProtocolError.MessageSendFailure,
1825
+ message: res.reason
1826
+ });
1827
+ this.deletePendingSession(session);
1828
+ return;
1829
+ }
2175
1830
  this.protocolError({
2176
1831
  type: ProtocolError.HandshakeFailed,
2177
1832
  code,
@@ -2180,7 +1835,7 @@ var ServerTransport = class extends Transport {
2180
1835
  this.deletePendingSession(session);
2181
1836
  }
2182
1837
  async onHandshakeRequest(session, msg) {
2183
- if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
1838
+ if (!import_value2.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
2184
1839
  this.rejectHandshakeRequest(
2185
1840
  session,
2186
1841
  msg.from,
@@ -2191,7 +1846,7 @@ var ServerTransport = class extends Transport {
2191
1846
  transportMessage: msg,
2192
1847
  connectedTo: msg.from,
2193
1848
  validationErrors: [
2194
- ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
1849
+ ...import_value2.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
2195
1850
  ]
2196
1851
  }
2197
1852
  );
@@ -2214,7 +1869,7 @@ var ServerTransport = class extends Transport {
2214
1869
  }
2215
1870
  let parsedMetadata = {};
2216
1871
  if (this.handshakeExtensions) {
2217
- if (!import_value3.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
1872
+ if (!import_value2.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
2218
1873
  this.rejectHandshakeRequest(
2219
1874
  session,
2220
1875
  msg.from,
@@ -2224,7 +1879,7 @@ var ServerTransport = class extends Transport {
2224
1879
  ...session.loggingMetadata,
2225
1880
  connectedTo: msg.from,
2226
1881
  validationErrors: [
2227
- ...import_value3.Value.Errors(
1882
+ ...import_value2.Value.Errors(
2228
1883
  this.handshakeExtensions.schema,
2229
1884
  msg.payload.metadata
2230
1885
  )
@@ -2243,7 +1898,7 @@ var ServerTransport = class extends Transport {
2243
1898
  if (session._isConsumed) {
2244
1899
  return;
2245
1900
  }
2246
- if (import_value3.Value.Check(
1901
+ if (import_value2.Value.Check(
2247
1902
  HandshakeErrorCustomHandlerFatalResponseCodes,
2248
1903
  parsedMetadataOrFailureCode
2249
1904
  )) {
@@ -2355,7 +2010,20 @@ var ServerTransport = class extends Transport {
2355
2010
  sessionId
2356
2011
  }
2357
2012
  });
2358
- session.sendHandshake(responseMsg);
2013
+ const res = session.sendHandshake(responseMsg);
2014
+ if (!res.ok) {
2015
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
2016
+ ...session.loggingMetadata,
2017
+ transportMessage: responseMsg
2018
+ });
2019
+ this.protocolError({
2020
+ type: ProtocolError.MessageSendFailure,
2021
+ message: res.reason
2022
+ });
2023
+ this.deletePendingSession(session);
2024
+ return;
2025
+ }
2026
+ this.pendingSessions.delete(session);
2359
2027
  const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2360
2028
  session,
2361
2029
  // by this point oldSession is either no connection or we dont have an old session
@@ -2382,43 +2050,517 @@ var ServerTransport = class extends Transport {
2382
2050
  this.handleMsg(msg2);
2383
2051
  },
2384
2052
  onInvalidMessage: (reason) => {
2053
+ this.log?.error(`invalid message: ${reason}`, {
2054
+ ...connectedSession.loggingMetadata,
2055
+ transportMessage: msg
2056
+ });
2385
2057
  this.protocolError({
2386
2058
  type: ProtocolError.InvalidMessage,
2387
2059
  message: reason
2388
2060
  });
2389
2061
  this.deleteSession(connectedSession, { unhealthy: true });
2062
+ },
2063
+ onMessageSendFailure: (msg2, reason) => {
2064
+ this.log?.error(`failed to send message: ${reason}`, {
2065
+ ...connectedSession.loggingMetadata,
2066
+ transportMessage: msg2
2067
+ });
2068
+ this.protocolError({
2069
+ type: ProtocolError.MessageSendFailure,
2070
+ message: reason
2071
+ });
2072
+ this.deleteSession(connectedSession, { unhealthy: true });
2390
2073
  }
2391
2074
  },
2392
2075
  gotVersion
2393
2076
  );
2077
+ const bufferSendRes = connectedSession.sendBufferedMessages();
2078
+ if (!bufferSendRes.ok) {
2079
+ this.log?.error(
2080
+ `failed to send buffered messages: ${bufferSendRes.reason}`,
2081
+ {
2082
+ ...connectedSession.loggingMetadata,
2083
+ transportMessage: msg
2084
+ }
2085
+ );
2086
+ this.protocolError({
2087
+ type: ProtocolError.MessageSendFailure,
2088
+ message: bufferSendRes.reason
2089
+ });
2090
+ this.deleteSession(connectedSession, { unhealthy: true });
2091
+ return;
2092
+ }
2394
2093
  this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2395
2094
  if (oldSession) {
2396
2095
  this.updateSession(connectedSession);
2397
2096
  } else {
2398
2097
  this.createSession(connectedSession);
2399
2098
  }
2400
- this.pendingSessions.delete(session);
2401
2099
  connectedSession.startActiveHeartbeat();
2402
2100
  }
2403
2101
  };
2404
2102
 
2405
- // testUtil/observable/observable.ts
2406
- var Observable = class {
2407
- value;
2408
- listeners;
2409
- constructor(initialValue) {
2410
- this.value = initialValue;
2411
- this.listeners = /* @__PURE__ */ new Set();
2103
+ // transport/connection.ts
2104
+ var Connection = class {
2105
+ id;
2106
+ telemetry;
2107
+ constructor() {
2108
+ this.id = `conn-${generateId()}`;
2412
2109
  }
2413
- /**
2414
- * Gets the current value of the observable.
2415
- */
2416
- get() {
2417
- return this.value;
2110
+ get loggingMetadata() {
2111
+ const metadata = { connId: this.id };
2112
+ if (this.telemetry?.span.isRecording()) {
2113
+ const spanContext = this.telemetry.span.spanContext();
2114
+ metadata.telemetry = {
2115
+ traceId: spanContext.traceId,
2116
+ spanId: spanContext.spanId
2117
+ };
2118
+ }
2119
+ return metadata;
2418
2120
  }
2419
- /**
2420
- * Sets the current value of the observable. All listeners will get an update with this value.
2421
- * @param newValue - The new value to set.
2121
+ // can't use event emitter because we need this to work in both node + browser
2122
+ _dataListeners = /* @__PURE__ */ new Set();
2123
+ _closeListeners = /* @__PURE__ */ new Set();
2124
+ _errorListeners = /* @__PURE__ */ new Set();
2125
+ get dataListeners() {
2126
+ return [...this._dataListeners];
2127
+ }
2128
+ get closeListeners() {
2129
+ return [...this._closeListeners];
2130
+ }
2131
+ get errorListeners() {
2132
+ return [...this._errorListeners];
2133
+ }
2134
+ onData(msg) {
2135
+ for (const cb of this.dataListeners) {
2136
+ cb(msg);
2137
+ }
2138
+ }
2139
+ onError(err) {
2140
+ for (const cb of this.errorListeners) {
2141
+ cb(err);
2142
+ }
2143
+ }
2144
+ onClose() {
2145
+ for (const cb of this.closeListeners) {
2146
+ cb();
2147
+ }
2148
+ this.telemetry?.span.end();
2149
+ }
2150
+ /**
2151
+ * Handle adding a callback for when a message is received.
2152
+ * @param msg The message that was received.
2153
+ */
2154
+ addDataListener(cb) {
2155
+ this._dataListeners.add(cb);
2156
+ }
2157
+ removeDataListener(cb) {
2158
+ this._dataListeners.delete(cb);
2159
+ }
2160
+ /**
2161
+ * Handle adding a callback for when the connection is closed.
2162
+ * This should also be called if an error happens and after notifying all the error listeners.
2163
+ * @param cb The callback to call when the connection is closed.
2164
+ */
2165
+ addCloseListener(cb) {
2166
+ this._closeListeners.add(cb);
2167
+ }
2168
+ removeCloseListener(cb) {
2169
+ this._closeListeners.delete(cb);
2170
+ }
2171
+ /**
2172
+ * Handle adding a callback for when an error is received.
2173
+ * This should only be used for this.logging errors, all cleanup
2174
+ * should be delegated to addCloseListener.
2175
+ *
2176
+ * The implementer should take care such that the implemented
2177
+ * connection will call both the close and error callbacks
2178
+ * on an error.
2179
+ *
2180
+ * @param cb The callback to call when an error is received.
2181
+ */
2182
+ addErrorListener(cb) {
2183
+ this._errorListeners.add(cb);
2184
+ }
2185
+ removeErrorListener(cb) {
2186
+ this._errorListeners.delete(cb);
2187
+ }
2188
+ };
2189
+
2190
+ // codec/adapter.ts
2191
+ var CodecMessageAdapter = class {
2192
+ constructor(codec) {
2193
+ this.codec = codec;
2194
+ }
2195
+ toBuffer(msg) {
2196
+ try {
2197
+ return {
2198
+ ok: true,
2199
+ value: this.codec.toBuffer(msg)
2200
+ };
2201
+ } catch (e) {
2202
+ return {
2203
+ ok: false,
2204
+ reason: coerceErrorString(e)
2205
+ };
2206
+ }
2207
+ }
2208
+ fromBuffer(buf) {
2209
+ try {
2210
+ const parsedMsg = this.codec.fromBuffer(buf);
2211
+ if (!import_value3.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
2212
+ return {
2213
+ ok: false,
2214
+ reason: "transport message schema mismatch"
2215
+ };
2216
+ }
2217
+ return {
2218
+ ok: true,
2219
+ value: parsedMsg
2220
+ };
2221
+ } catch (e) {
2222
+ return {
2223
+ ok: false,
2224
+ reason: coerceErrorString(e)
2225
+ };
2226
+ }
2227
+ }
2228
+ };
2229
+
2230
+ // transport/sessionStateMachine/transitions.ts
2231
+ function inheritSharedSession(session) {
2232
+ return {
2233
+ id: session.id,
2234
+ from: session.from,
2235
+ to: session.to,
2236
+ seq: session.seq,
2237
+ ack: session.ack,
2238
+ seqSent: session.seqSent,
2239
+ sendBuffer: session.sendBuffer,
2240
+ telemetry: session.telemetry,
2241
+ options: session.options,
2242
+ log: session.log,
2243
+ tracer: session.tracer,
2244
+ protocolVersion: session.protocolVersion,
2245
+ codec: session.codec
2246
+ };
2247
+ }
2248
+ function inheritSharedSessionWithGrace(session) {
2249
+ return {
2250
+ ...inheritSharedSession(session),
2251
+ graceExpiryTime: session.graceExpiryTime
2252
+ };
2253
+ }
2254
+ var SessionStateGraph = {
2255
+ entrypoints: {
2256
+ NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
2257
+ const id = `session-${generateId()}`;
2258
+ const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
2259
+ const sendBuffer = [];
2260
+ const session = new SessionNoConnection({
2261
+ listeners,
2262
+ id,
2263
+ from,
2264
+ to,
2265
+ seq: 0,
2266
+ ack: 0,
2267
+ seqSent: 0,
2268
+ graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
2269
+ sendBuffer,
2270
+ telemetry,
2271
+ options,
2272
+ protocolVersion,
2273
+ tracer,
2274
+ log,
2275
+ codec: new CodecMessageAdapter(options.codec)
2276
+ });
2277
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
2278
+ ...session.loggingMetadata,
2279
+ tags: ["state-transition"]
2280
+ });
2281
+ return session;
2282
+ },
2283
+ WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
2284
+ const session = new SessionWaitingForHandshake({
2285
+ conn,
2286
+ listeners,
2287
+ from,
2288
+ options,
2289
+ tracer,
2290
+ log,
2291
+ codec: new CodecMessageAdapter(options.codec)
2292
+ });
2293
+ session.log?.info(`session created in WaitingForHandshake state`, {
2294
+ ...session.loggingMetadata,
2295
+ tags: ["state-transition"]
2296
+ });
2297
+ return session;
2298
+ }
2299
+ },
2300
+ // All of the transitions 'move'/'consume' the old session and return a new one.
2301
+ // After a session is transitioned, any usage of the old session will throw.
2302
+ transition: {
2303
+ // happy path transitions
2304
+ NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
2305
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2306
+ oldSession._handleStateExit();
2307
+ const session = new SessionBackingOff({
2308
+ backoffMs,
2309
+ listeners,
2310
+ ...carriedState
2311
+ });
2312
+ session.log?.info(
2313
+ `session ${session.id} transition from NoConnection to BackingOff`,
2314
+ {
2315
+ ...session.loggingMetadata,
2316
+ tags: ["state-transition"]
2317
+ }
2318
+ );
2319
+ return session;
2320
+ },
2321
+ BackingOffToConnecting: (oldSession, connPromise, listeners) => {
2322
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2323
+ oldSession._handleStateExit();
2324
+ const session = new SessionConnecting({
2325
+ connPromise,
2326
+ listeners,
2327
+ ...carriedState
2328
+ });
2329
+ session.log?.info(
2330
+ `session ${session.id} transition from BackingOff to Connecting`,
2331
+ {
2332
+ ...session.loggingMetadata,
2333
+ tags: ["state-transition"]
2334
+ }
2335
+ );
2336
+ return session;
2337
+ },
2338
+ ConnectingToHandshaking: (oldSession, conn, listeners) => {
2339
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2340
+ oldSession._handleStateExit();
2341
+ const session = new SessionHandshaking({
2342
+ conn,
2343
+ listeners,
2344
+ ...carriedState
2345
+ });
2346
+ conn.telemetry = createConnectionTelemetryInfo(
2347
+ session.tracer,
2348
+ conn,
2349
+ session.telemetry
2350
+ );
2351
+ session.log?.info(
2352
+ `session ${session.id} transition from Connecting to Handshaking`,
2353
+ {
2354
+ ...session.loggingMetadata,
2355
+ tags: ["state-transition"]
2356
+ }
2357
+ );
2358
+ return session;
2359
+ },
2360
+ HandshakingToConnected: (oldSession, listeners) => {
2361
+ const carriedState = inheritSharedSession(oldSession);
2362
+ const conn = oldSession.conn;
2363
+ oldSession._handleStateExit();
2364
+ const session = new SessionConnected({
2365
+ conn,
2366
+ listeners,
2367
+ ...carriedState
2368
+ });
2369
+ session.startMissingHeartbeatTimeout();
2370
+ session.log?.info(
2371
+ `session ${session.id} transition from Handshaking to Connected`,
2372
+ {
2373
+ ...session.loggingMetadata,
2374
+ tags: ["state-transition"]
2375
+ }
2376
+ );
2377
+ return session;
2378
+ },
2379
+ WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
2380
+ const conn = pendingSession.conn;
2381
+ const { from, options } = pendingSession;
2382
+ const carriedState = oldSession ? (
2383
+ // old session exists, inherit state
2384
+ inheritSharedSession(oldSession)
2385
+ ) : (
2386
+ // old session does not exist, create new state
2387
+ {
2388
+ id: sessionId,
2389
+ from,
2390
+ to,
2391
+ seq: 0,
2392
+ ack: 0,
2393
+ seqSent: 0,
2394
+ sendBuffer: [],
2395
+ telemetry: createSessionTelemetryInfo(
2396
+ pendingSession.tracer,
2397
+ sessionId,
2398
+ to,
2399
+ from,
2400
+ propagationCtx
2401
+ ),
2402
+ options,
2403
+ tracer: pendingSession.tracer,
2404
+ log: pendingSession.log,
2405
+ protocolVersion,
2406
+ codec: new CodecMessageAdapter(options.codec)
2407
+ }
2408
+ );
2409
+ pendingSession._handleStateExit();
2410
+ oldSession?._handleStateExit();
2411
+ const session = new SessionConnected({
2412
+ conn,
2413
+ listeners,
2414
+ ...carriedState
2415
+ });
2416
+ session.startMissingHeartbeatTimeout();
2417
+ conn.telemetry = createConnectionTelemetryInfo(
2418
+ session.tracer,
2419
+ conn,
2420
+ session.telemetry
2421
+ );
2422
+ session.log?.info(
2423
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
2424
+ {
2425
+ ...session.loggingMetadata,
2426
+ tags: ["state-transition"]
2427
+ }
2428
+ );
2429
+ return session;
2430
+ },
2431
+ // disconnect paths
2432
+ BackingOffToNoConnection: (oldSession, listeners) => {
2433
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2434
+ oldSession._handleStateExit();
2435
+ const session = new SessionNoConnection({
2436
+ listeners,
2437
+ ...carriedState
2438
+ });
2439
+ session.log?.info(
2440
+ `session ${session.id} transition from BackingOff to NoConnection`,
2441
+ {
2442
+ ...session.loggingMetadata,
2443
+ tags: ["state-transition"]
2444
+ }
2445
+ );
2446
+ return session;
2447
+ },
2448
+ ConnectingToNoConnection: (oldSession, listeners) => {
2449
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2450
+ oldSession.bestEffortClose();
2451
+ oldSession._handleStateExit();
2452
+ const session = new SessionNoConnection({
2453
+ listeners,
2454
+ ...carriedState
2455
+ });
2456
+ session.log?.info(
2457
+ `session ${session.id} transition from Connecting to NoConnection`,
2458
+ {
2459
+ ...session.loggingMetadata,
2460
+ tags: ["state-transition"]
2461
+ }
2462
+ );
2463
+ return session;
2464
+ },
2465
+ HandshakingToNoConnection: (oldSession, listeners) => {
2466
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2467
+ oldSession.conn.close();
2468
+ oldSession._handleStateExit();
2469
+ const session = new SessionNoConnection({
2470
+ listeners,
2471
+ ...carriedState
2472
+ });
2473
+ session.log?.info(
2474
+ `session ${session.id} transition from Handshaking to NoConnection`,
2475
+ {
2476
+ ...session.loggingMetadata,
2477
+ tags: ["state-transition"]
2478
+ }
2479
+ );
2480
+ return session;
2481
+ },
2482
+ ConnectedToNoConnection: (oldSession, listeners) => {
2483
+ const carriedState = inheritSharedSession(oldSession);
2484
+ const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
2485
+ oldSession.conn.close();
2486
+ oldSession._handleStateExit();
2487
+ const session = new SessionNoConnection({
2488
+ listeners,
2489
+ graceExpiryTime,
2490
+ ...carriedState
2491
+ });
2492
+ session.log?.info(
2493
+ `session ${session.id} transition from Connected to NoConnection`,
2494
+ {
2495
+ ...session.loggingMetadata,
2496
+ tags: ["state-transition"]
2497
+ }
2498
+ );
2499
+ return session;
2500
+ }
2501
+ }
2502
+ };
2503
+ var transitions = SessionStateGraph.transition;
2504
+ var ClientSessionStateGraph = {
2505
+ entrypoint: SessionStateGraph.entrypoints.NoConnection,
2506
+ transition: {
2507
+ // happy paths
2508
+ // NoConnection -> BackingOff: attempt to connect
2509
+ NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
2510
+ // BackingOff -> Connecting: backoff period elapsed, start connection
2511
+ BackingOffToConnecting: transitions.BackingOffToConnecting,
2512
+ // Connecting -> Handshaking: connection established, start handshake
2513
+ ConnectingToHandshaking: transitions.ConnectingToHandshaking,
2514
+ // Handshaking -> Connected: handshake complete, session ready
2515
+ HandshakingToConnected: transitions.HandshakingToConnected,
2516
+ // disconnect paths
2517
+ // BackingOff -> NoConnection: unused
2518
+ BackingOffToNoConnection: transitions.BackingOffToNoConnection,
2519
+ // Connecting -> NoConnection: connection failed or connection timeout
2520
+ ConnectingToNoConnection: transitions.ConnectingToNoConnection,
2521
+ // Handshaking -> NoConnection: connection closed or handshake timeout
2522
+ HandshakingToNoConnection: transitions.HandshakingToNoConnection,
2523
+ // Connected -> NoConnection: connection closed
2524
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2525
+ // destroy/close paths
2526
+ // NoConnection -> x: grace period elapsed
2527
+ // BackingOff -> x: grace period elapsed
2528
+ // Connecting -> x: grace period elapsed
2529
+ // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
2530
+ // Connected -> x: grace period elapsed or invalid message
2531
+ }
2532
+ };
2533
+ var ServerSessionStateGraph = {
2534
+ entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
2535
+ transition: {
2536
+ // happy paths
2537
+ // WaitingForHandshake -> Connected: handshake complete, session ready
2538
+ WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
2539
+ // disconnect paths
2540
+ // Connected -> NoConnection: connection closed
2541
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2542
+ // destroy/close paths
2543
+ // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
2544
+ }
2545
+ };
2546
+
2547
+ // testUtil/observable/observable.ts
2548
+ var Observable = class {
2549
+ value;
2550
+ listeners;
2551
+ constructor(initialValue) {
2552
+ this.value = initialValue;
2553
+ this.listeners = /* @__PURE__ */ new Set();
2554
+ }
2555
+ /**
2556
+ * Gets the current value of the observable.
2557
+ */
2558
+ get() {
2559
+ return this.value;
2560
+ }
2561
+ /**
2562
+ * Sets the current value of the observable. All listeners will get an update with this value.
2563
+ * @param newValue - The new value to set.
2422
2564
  */
2423
2565
  set(tx) {
2424
2566
  const newValue = tx(this.value);
@@ -2488,6 +2630,16 @@ function duplexPair() {
2488
2630
  const side1 = new DuplexSide();
2489
2631
  side0[kInitOtherSide](side1);
2490
2632
  side1[kInitOtherSide](side0);
2633
+ side0.on("close", () => {
2634
+ setImmediate(() => {
2635
+ side1.destroy();
2636
+ });
2637
+ });
2638
+ side1.on("close", () => {
2639
+ setImmediate(() => {
2640
+ side0.destroy();
2641
+ });
2642
+ });
2491
2643
  return [side0, side1];
2492
2644
  }
2493
2645
 
@@ -2600,14 +2752,14 @@ function createMockTransportNetwork(opts) {
2600
2752
  transport.close();
2601
2753
  }
2602
2754
  for (const conn of Object.values(connections.get())) {
2603
- conn.serverToClient.end();
2604
- conn.clientToServer.end();
2755
+ conn.serverToClient.destroy();
2756
+ conn.clientToServer.destroy();
2605
2757
  }
2606
2758
  },
2607
2759
  cleanup() {
2608
2760
  for (const conn of Object.values(connections.get())) {
2609
- conn.serverToClient.end();
2610
- conn.clientToServer.end();
2761
+ conn.serverToClient.destroy();
2762
+ conn.clientToServer.destroy();
2611
2763
  }
2612
2764
  }
2613
2765
  };