@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
@@ -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 {
@@ -493,13 +488,11 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
493
488
  this.listeners = props.listeners;
494
489
  this.connPromise.then(
495
490
  (conn) => {
496
- if (this._isConsumed)
497
- return;
491
+ if (this._isConsumed) return;
498
492
  this.listeners.onConnectionEstablished(conn);
499
493
  },
500
494
  (err) => {
501
- if (this._isConsumed)
502
- return;
495
+ if (this._isConsumed) return;
503
496
  this.listeners.onConnectionFailed(err);
504
497
  }
505
498
  );
@@ -532,8 +525,8 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
532
525
  }
533
526
  }
534
527
  _handleClose() {
535
- this.bestEffortClose();
536
528
  super._handleClose();
529
+ this.bestEffortClose();
537
530
  }
538
531
  };
539
532
 
@@ -560,7 +553,7 @@ function coerceErrorString(err) {
560
553
  }
561
554
 
562
555
  // package.json
563
- var version = "0.207.2";
556
+ var version = "0.208.0";
564
557
 
565
558
  // tracing/index.ts
566
559
  function getPropagationContext(ctx) {
@@ -574,7 +567,7 @@ function getPropagationContext(ctx) {
574
567
  function createSessionTelemetryInfo(tracer, sessionId, to, from, propagationCtx) {
575
568
  const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
576
569
  const span = tracer.startSpan(
577
- `river.session.${sessionId}`,
570
+ `river.session`,
578
571
  {
579
572
  attributes: {
580
573
  component: "river",
@@ -590,7 +583,7 @@ function createSessionTelemetryInfo(tracer, sessionId, to, from, propagationCtx)
590
583
  }
591
584
  function createConnectionTelemetryInfo(tracer, connection, info) {
592
585
  const span = tracer.startSpan(
593
- `connection ${connection.id}`,
586
+ `river.connection`,
594
587
  {
595
588
  attributes: {
596
589
  component: "river",
@@ -620,9 +613,9 @@ var SessionWaitingForHandshake = class extends CommonSession {
620
613
  this.handshakeTimeout = setTimeout(() => {
621
614
  this.listeners.onHandshakeTimeout();
622
615
  }, this.options.handshakeTimeoutMs);
623
- this.conn.addDataListener(this.onHandshakeData);
624
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
625
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
616
+ this.conn.setDataListener(this.onHandshakeData);
617
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
618
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
626
619
  }
627
620
  get loggingMetadata() {
628
621
  return {
@@ -632,23 +625,23 @@ var SessionWaitingForHandshake = class extends CommonSession {
632
625
  };
633
626
  }
634
627
  onHandshakeData = (msg) => {
635
- const parsedMsg = this.parseMsg(msg);
636
- if (parsedMsg === null) {
628
+ const parsedMsgRes = this.codec.fromBuffer(msg);
629
+ if (!parsedMsgRes.ok) {
637
630
  this.listeners.onInvalidHandshake(
638
- "could not parse message",
631
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
639
632
  "MALFORMED_HANDSHAKE"
640
633
  );
641
634
  return;
642
635
  }
643
- this.listeners.onHandshake(parsedMsg);
636
+ this.listeners.onHandshake(parsedMsgRes.value);
644
637
  };
645
638
  sendHandshake(msg) {
646
- return this.conn.send(this.options.codec.toBuffer(msg));
639
+ return sendMessage(this.conn, this.codec, msg);
647
640
  }
648
641
  _handleStateExit() {
649
- this.conn.removeDataListener(this.onHandshakeData);
650
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
651
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
642
+ this.conn.removeDataListener();
643
+ this.conn.removeErrorListener();
644
+ this.conn.removeCloseListener();
652
645
  clearTimeout(this.handshakeTimeout);
653
646
  this.handshakeTimeout = void 0;
654
647
  }
@@ -670,9 +663,9 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
670
663
  this.handshakeTimeout = setTimeout(() => {
671
664
  this.listeners.onHandshakeTimeout();
672
665
  }, this.options.handshakeTimeoutMs);
673
- this.conn.addDataListener(this.onHandshakeData);
674
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
675
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
666
+ this.conn.setDataListener(this.onHandshakeData);
667
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
668
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
676
669
  }
677
670
  get loggingMetadata() {
678
671
  return {
@@ -681,24 +674,24 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
681
674
  };
682
675
  }
683
676
  onHandshakeData = (msg) => {
684
- const parsedMsg = this.parseMsg(msg);
685
- if (parsedMsg === null) {
677
+ const parsedMsgRes = this.codec.fromBuffer(msg);
678
+ if (!parsedMsgRes.ok) {
686
679
  this.listeners.onInvalidHandshake(
687
- "could not parse message",
680
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
688
681
  "MALFORMED_HANDSHAKE"
689
682
  );
690
683
  return;
691
684
  }
692
- this.listeners.onHandshake(parsedMsg);
685
+ this.listeners.onHandshake(parsedMsgRes.value);
693
686
  };
694
687
  sendHandshake(msg) {
695
- return this.conn.send(this.options.codec.toBuffer(msg));
688
+ return sendMessage(this.conn, this.codec, msg);
696
689
  }
697
690
  _handleStateExit() {
698
691
  super._handleStateExit();
699
- this.conn.removeDataListener(this.onHandshakeData);
700
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
701
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
692
+ this.conn.removeDataListener();
693
+ this.conn.removeErrorListener();
694
+ this.conn.removeCloseListener();
702
695
  if (this.handshakeTimeout) {
703
696
  clearTimeout(this.handshakeTimeout);
704
697
  this.handshakeTimeout = void 0;
@@ -717,25 +710,15 @@ var SessionConnected = class extends IdentifiedSession {
717
710
  conn;
718
711
  listeners;
719
712
  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
- };
713
+ heartbeatMissTimeout;
714
+ isActivelyHeartbeating = false;
735
715
  updateBookkeeping(ack, seq) {
736
716
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
737
717
  this.ack = seq + 1;
738
- this.heartbeatMisses = 0;
718
+ if (this.heartbeatMissTimeout) {
719
+ clearTimeout(this.heartbeatMissTimeout);
720
+ }
721
+ this.startMissingHeartbeatTimeout();
739
722
  }
740
723
  assertSendOrdering(constructedMsg) {
741
724
  if (constructedMsg.seq > this.seqSent + 1) {
@@ -743,30 +726,32 @@ var SessionConnected = class extends IdentifiedSession {
743
726
  this.log?.error(msg, {
744
727
  ...this.loggingMetadata,
745
728
  transportMessage: constructedMsg,
746
- tags: ["invariant-violation"],
747
- extras: {
748
- lastConstructedMsgs: this.lastConstructedMsgs
749
- }
729
+ tags: ["invariant-violation"]
750
730
  });
751
731
  throw new Error(msg);
752
732
  }
753
733
  }
754
734
  send(msg) {
755
735
  const constructedMsg = this.constructMsg(msg);
756
- this.pushLastConstructedMsgs(constructedMsg);
757
736
  this.assertSendOrdering(constructedMsg);
758
737
  this.sendBuffer.push(constructedMsg);
759
- this.conn.send(this.options.codec.toBuffer(constructedMsg));
738
+ const res = sendMessage(this.conn, this.codec, constructedMsg);
739
+ if (!res.ok) {
740
+ this.listeners.onMessageSendFailure(constructedMsg, res.reason);
741
+ return res;
742
+ }
760
743
  this.seqSent = constructedMsg.seq;
761
- return constructedMsg.id;
744
+ return res;
762
745
  }
763
746
  constructor(props) {
764
747
  super(props);
765
748
  this.conn = props.conn;
766
749
  this.listeners = props.listeners;
767
- this.conn.addDataListener(this.onMessageData);
768
- this.conn.addCloseListener(this.listeners.onConnectionClosed);
769
- this.conn.addErrorListener(this.listeners.onConnectionErrored);
750
+ this.conn.setDataListener(this.onMessageData);
751
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
752
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
753
+ }
754
+ sendBufferedMessages() {
770
755
  if (this.sendBuffer.length > 0) {
771
756
  this.log?.info(
772
757
  `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
@@ -774,30 +759,15 @@ var SessionConnected = class extends IdentifiedSession {
774
759
  );
775
760
  for (const msg of this.sendBuffer) {
776
761
  this.assertSendOrdering(msg);
777
- this.conn.send(this.options.codec.toBuffer(msg));
762
+ const res = sendMessage(this.conn, this.codec, msg);
763
+ if (!res.ok) {
764
+ this.listeners.onMessageSendFailure(msg, res.reason);
765
+ return res;
766
+ }
778
767
  this.seqSent = msg.seq;
779
768
  }
780
769
  }
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);
770
+ return { ok: true, value: void 0 };
801
771
  }
802
772
  get loggingMetadata() {
803
773
  return {
@@ -805,25 +775,46 @@ var SessionConnected = class extends IdentifiedSession {
805
775
  ...this.conn.loggingMetadata
806
776
  };
807
777
  }
778
+ startMissingHeartbeatTimeout() {
779
+ const maxMisses = this.options.heartbeatsUntilDead;
780
+ const missDuration = maxMisses * this.options.heartbeatIntervalMs;
781
+ this.heartbeatMissTimeout = setTimeout(() => {
782
+ this.log?.info(
783
+ `closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
784
+ this.loggingMetadata
785
+ );
786
+ this.telemetry.span.addEvent(
787
+ "closing connection due to missing heartbeat"
788
+ );
789
+ this.conn.close();
790
+ }, missDuration);
791
+ }
808
792
  startActiveHeartbeat() {
809
793
  this.isActivelyHeartbeating = true;
794
+ this.heartbeatHandle = setInterval(() => {
795
+ this.sendHeartbeat();
796
+ }, this.options.heartbeatIntervalMs);
810
797
  }
811
798
  sendHeartbeat() {
812
799
  this.log?.debug("sending heartbeat", this.loggingMetadata);
813
- this.send({
800
+ const heartbeat = {
814
801
  streamId: "heartbeat",
815
802
  controlFlags: 1 /* AckBit */,
816
803
  payload: {
817
804
  type: "ACK"
818
805
  }
819
- });
806
+ };
807
+ this.send(heartbeat);
820
808
  }
821
809
  onMessageData = (msg) => {
822
- const parsedMsg = this.parseMsg(msg);
823
- if (parsedMsg === null) {
824
- this.listeners.onInvalidMessage("could not parse message");
810
+ const parsedMsgRes = this.codec.fromBuffer(msg);
811
+ if (!parsedMsgRes.ok) {
812
+ this.listeners.onInvalidMessage(
813
+ `could not parse message: ${parsedMsgRes.reason}`
814
+ );
825
815
  return;
826
816
  }
817
+ const parsedMsg = parsedMsgRes.value;
827
818
  if (parsedMsg.seq !== this.ack) {
828
819
  if (parsedMsg.seq < this.ack) {
829
820
  this.log?.debug(
@@ -862,20 +853,22 @@ var SessionConnected = class extends IdentifiedSession {
862
853
  transportMessage: parsedMsg
863
854
  });
864
855
  if (!this.isActivelyHeartbeating) {
865
- void Promise.resolve().then(() => {
866
- this.sendHeartbeat();
867
- });
856
+ this.sendHeartbeat();
868
857
  }
869
858
  };
870
859
  _handleStateExit() {
871
860
  super._handleStateExit();
872
- this.conn.removeDataListener(this.onMessageData);
873
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
874
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
861
+ this.conn.removeDataListener();
862
+ this.conn.removeCloseListener();
863
+ this.conn.removeErrorListener();
875
864
  if (this.heartbeatHandle) {
876
865
  clearInterval(this.heartbeatHandle);
877
866
  this.heartbeatHandle = void 0;
878
867
  }
868
+ if (this.heartbeatMissTimeout) {
869
+ clearTimeout(this.heartbeatMissTimeout);
870
+ this.heartbeatMissTimeout = void 0;
871
+ }
879
872
  }
880
873
  _handleClose() {
881
874
  super._handleClose();
@@ -907,425 +900,47 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
907
900
  }
908
901
  };
909
902
 
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
903
+ // codec/adapter.ts
904
+ var import_value3 = require("@sinclair/typebox/value");
905
+
906
+ // logging/log.ts
907
+ var import_api3 = require("@opentelemetry/api");
908
+ var LoggingLevels = {
909
+ debug: -1,
910
+ info: 0,
911
+ warn: 1,
912
+ error: 2
913
+ };
914
+ var cleanedLogFn = (log) => {
915
+ return (msg, metadata) => {
916
+ if (metadata && !metadata.telemetry) {
917
+ const span = import_api3.trace.getSpan(import_api3.context.active());
918
+ if (span) {
919
+ metadata.telemetry = {
920
+ traceId: span.spanContext().traceId,
921
+ spanId: span.spanContext().spanId
922
+ };
923
+ }
924
+ }
925
+ if (!metadata?.transportMessage) {
926
+ log(msg, metadata);
927
+ return;
928
+ }
929
+ const { payload, ...rest } = metadata.transportMessage;
930
+ metadata.transportMessage = rest;
931
+ log(msg, metadata);
931
932
  };
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");
933
+ };
934
+ var BaseLogger = class {
935
+ minLevel;
936
+ output;
937
+ constructor(output, minLevel = "info") {
938
+ this.minLevel = minLevel;
939
+ this.output = output;
940
+ }
941
+ debug(msg, metadata) {
942
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
943
+ this.output(msg, metadata ?? {}, "debug");
1329
944
  }
1330
945
  }
1331
946
  info(msg, metadata) {
@@ -1356,7 +971,8 @@ var ProtocolError = {
1356
971
  RetriesExceeded: "conn_retry_exceeded",
1357
972
  HandshakeFailed: "handshake_failed",
1358
973
  MessageOrderingViolated: "message_ordering_violated",
1359
- InvalidMessage: "invalid_message"
974
+ InvalidMessage: "invalid_message",
975
+ MessageSendFailure: "message_send_failure"
1360
976
  };
1361
977
  var EventDispatcher = class {
1362
978
  eventListeners = {};
@@ -1436,8 +1052,7 @@ var Transport = class {
1436
1052
  * @param message The received message.
1437
1053
  */
1438
1054
  handleMsg(message) {
1439
- if (this.getStatus() !== "open")
1440
- return;
1055
+ if (this.getStatus() !== "open") return;
1441
1056
  this.eventDispatcher.dispatchEvent("message", message);
1442
1057
  }
1443
1058
  /**
@@ -1525,8 +1140,7 @@ var Transport = class {
1525
1140
  });
1526
1141
  }
1527
1142
  deleteSession(session, options) {
1528
- if (session._isConsumed)
1529
- return;
1143
+ if (session._isConsumed) return;
1530
1144
  const loggingMetadata = session.loggingMetadata;
1531
1145
  if (loggingMetadata.tags && options?.unhealthy) {
1532
1146
  loggingMetadata.tags.push("unhealthy-session");
@@ -1546,7 +1160,7 @@ var Transport = class {
1546
1160
  }
1547
1161
  // common listeners
1548
1162
  onSessionGracePeriodElapsed(session) {
1549
- this.log?.warn(
1163
+ this.log?.info(
1550
1164
  `session to ${session.to} grace period elapsed, closing`,
1551
1165
  session.loggingMetadata
1552
1166
  );
@@ -1599,18 +1213,92 @@ var Transport = class {
1599
1213
  );
1600
1214
  }
1601
1215
  const sameSession = session.id === sessionId;
1602
- if (!sameSession) {
1216
+ if (!sameSession || session._isConsumed) {
1603
1217
  throw new Error(
1604
1218
  `session scope for ${sessionId} has ended (transition), can't send`
1605
1219
  );
1606
1220
  }
1607
- return session.send(msg);
1221
+ const res = session.send(msg);
1222
+ if (!res.ok) {
1223
+ throw new Error(res.reason);
1224
+ }
1225
+ return res.value;
1608
1226
  };
1609
1227
  }
1610
1228
  };
1611
1229
 
1612
1230
  // transport/client.ts
1613
- var import_value2 = require("@sinclair/typebox/value");
1231
+ var import_api4 = require("@opentelemetry/api");
1232
+
1233
+ // transport/rateLimit.ts
1234
+ var LeakyBucketRateLimit = class {
1235
+ budgetConsumed;
1236
+ intervalHandle;
1237
+ options;
1238
+ constructor(options) {
1239
+ this.options = options;
1240
+ this.budgetConsumed = 0;
1241
+ }
1242
+ getBackoffMs() {
1243
+ if (this.getBudgetConsumed() === 0) {
1244
+ return 0;
1245
+ }
1246
+ const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1247
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1248
+ const backoffMs = Math.min(
1249
+ this.options.baseIntervalMs * 2 ** exponent,
1250
+ this.options.maxBackoffMs
1251
+ );
1252
+ return backoffMs + jitter;
1253
+ }
1254
+ get totalBudgetRestoreTime() {
1255
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1256
+ }
1257
+ consumeBudget() {
1258
+ this.stopLeak();
1259
+ this.budgetConsumed = this.getBudgetConsumed() + 1;
1260
+ }
1261
+ getBudgetConsumed() {
1262
+ return this.budgetConsumed;
1263
+ }
1264
+ hasBudget() {
1265
+ return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1266
+ }
1267
+ startRestoringBudget() {
1268
+ if (this.intervalHandle) {
1269
+ return;
1270
+ }
1271
+ const restoreBudgetForUser = () => {
1272
+ const currentBudget = this.budgetConsumed;
1273
+ if (!currentBudget) {
1274
+ this.stopLeak();
1275
+ return;
1276
+ }
1277
+ const newBudget = currentBudget - 1;
1278
+ if (newBudget === 0) {
1279
+ return;
1280
+ }
1281
+ this.budgetConsumed = newBudget;
1282
+ };
1283
+ this.intervalHandle = setInterval(
1284
+ restoreBudgetForUser,
1285
+ this.options.budgetRestoreIntervalMs
1286
+ );
1287
+ }
1288
+ stopLeak() {
1289
+ if (!this.intervalHandle) {
1290
+ return;
1291
+ }
1292
+ clearInterval(this.intervalHandle);
1293
+ this.intervalHandle = void 0;
1294
+ }
1295
+ close() {
1296
+ this.stopLeak();
1297
+ }
1298
+ };
1299
+
1300
+ // transport/client.ts
1301
+ var import_value = require("@sinclair/typebox/value");
1614
1302
  var ClientTransport = class extends Transport {
1615
1303
  /**
1616
1304
  * The options for this transport.
@@ -1741,19 +1429,19 @@ var ClientTransport = class extends Transport {
1741
1429
  this.deleteSession(session, { unhealthy: true });
1742
1430
  }
1743
1431
  onHandshakeResponse(session, msg) {
1744
- if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1432
+ if (!import_value.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1745
1433
  const reason = `received invalid handshake response`;
1746
1434
  this.rejectHandshakeResponse(session, reason, {
1747
1435
  ...session.loggingMetadata,
1748
1436
  transportMessage: msg,
1749
1437
  validationErrors: [
1750
- ...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1438
+ ...import_value.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1751
1439
  ]
1752
1440
  });
1753
1441
  return;
1754
1442
  }
1755
1443
  if (!msg.payload.status.ok) {
1756
- const retriable = import_value2.Value.Check(
1444
+ const retriable = import_value.Value.Check(
1757
1445
  HandshakeErrorRetriableResponseCodes,
1758
1446
  msg.payload.status.code
1759
1447
  );
@@ -1805,13 +1493,32 @@ var ClientTransport = class extends Transport {
1805
1493
  this.handleMsg(msg2);
1806
1494
  },
1807
1495
  onInvalidMessage: (reason) => {
1808
- this.deleteSession(connectedSession, { unhealthy: true });
1496
+ this.log?.error(`invalid message: ${reason}`, {
1497
+ ...connectedSession.loggingMetadata,
1498
+ transportMessage: msg
1499
+ });
1809
1500
  this.protocolError({
1810
1501
  type: ProtocolError.InvalidMessage,
1811
1502
  message: reason
1812
1503
  });
1504
+ this.deleteSession(connectedSession, { unhealthy: true });
1505
+ },
1506
+ onMessageSendFailure: (msg2, reason) => {
1507
+ this.log?.error(`failed to send message: ${reason}`, {
1508
+ ...connectedSession.loggingMetadata,
1509
+ transportMessage: msg2
1510
+ });
1511
+ this.protocolError({
1512
+ type: ProtocolError.MessageSendFailure,
1513
+ message: reason
1514
+ });
1515
+ this.deleteSession(connectedSession, { unhealthy: true });
1813
1516
  }
1814
1517
  });
1518
+ const res = connectedSession.sendBufferedMessages();
1519
+ if (!res.ok) {
1520
+ return;
1521
+ }
1815
1522
  this.updateSession(connectedSession);
1816
1523
  this.retryBudget.startRestoringBudget();
1817
1524
  }
@@ -1950,7 +1657,18 @@ var ClientTransport = class extends Transport {
1950
1657
  ...session.loggingMetadata,
1951
1658
  transportMessage: requestMsg
1952
1659
  });
1953
- session.sendHandshake(requestMsg);
1660
+ const res = session.sendHandshake(requestMsg);
1661
+ if (!res.ok) {
1662
+ this.log?.error(`failed to send handshake request: ${res.reason}`, {
1663
+ ...session.loggingMetadata,
1664
+ transportMessage: requestMsg
1665
+ });
1666
+ this.protocolError({
1667
+ type: ProtocolError.MessageSendFailure,
1668
+ message: res.reason
1669
+ });
1670
+ this.deleteSession(session, { unhealthy: true });
1671
+ }
1954
1672
  }
1955
1673
  close() {
1956
1674
  this.retryBudget.close();
@@ -1958,96 +1676,9 @@ var ClientTransport = class extends Transport {
1958
1676
  }
1959
1677
  };
1960
1678
 
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
1679
  // transport/server.ts
2049
1680
  var import_api5 = require("@opentelemetry/api");
2050
- var import_value3 = require("@sinclair/typebox/value");
1681
+ var import_value2 = require("@sinclair/typebox/value");
2051
1682
  var ServerTransport = class extends Transport {
2052
1683
  /**
2053
1684
  * The options for this transport.
@@ -2087,8 +1718,7 @@ var ServerTransport = class extends Transport {
2087
1718
  super.deleteSession(session, options);
2088
1719
  }
2089
1720
  handleConnection(conn) {
2090
- if (this.getStatus() !== "open")
2091
- return;
1721
+ if (this.getStatus() !== "open") return;
2092
1722
  this.log?.info(`new incoming connection`, {
2093
1723
  ...conn.loggingMetadata,
2094
1724
  clientId: this.clientId
@@ -2161,17 +1791,28 @@ var ServerTransport = class extends Transport {
2161
1791
  message: reason
2162
1792
  });
2163
1793
  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
- );
1794
+ const responseMsg = handshakeResponseMessage({
1795
+ from: this.clientId,
1796
+ to,
1797
+ status: {
1798
+ ok: false,
1799
+ code,
1800
+ reason
1801
+ }
1802
+ });
1803
+ const res = session.sendHandshake(responseMsg);
1804
+ if (!res.ok) {
1805
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1806
+ ...session.loggingMetadata,
1807
+ transportMessage: responseMsg
1808
+ });
1809
+ this.protocolError({
1810
+ type: ProtocolError.MessageSendFailure,
1811
+ message: res.reason
1812
+ });
1813
+ this.deletePendingSession(session);
1814
+ return;
1815
+ }
2175
1816
  this.protocolError({
2176
1817
  type: ProtocolError.HandshakeFailed,
2177
1818
  code,
@@ -2180,7 +1821,7 @@ var ServerTransport = class extends Transport {
2180
1821
  this.deletePendingSession(session);
2181
1822
  }
2182
1823
  async onHandshakeRequest(session, msg) {
2183
- if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
1824
+ if (!import_value2.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
2184
1825
  this.rejectHandshakeRequest(
2185
1826
  session,
2186
1827
  msg.from,
@@ -2191,7 +1832,7 @@ var ServerTransport = class extends Transport {
2191
1832
  transportMessage: msg,
2192
1833
  connectedTo: msg.from,
2193
1834
  validationErrors: [
2194
- ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
1835
+ ...import_value2.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
2195
1836
  ]
2196
1837
  }
2197
1838
  );
@@ -2214,7 +1855,7 @@ var ServerTransport = class extends Transport {
2214
1855
  }
2215
1856
  let parsedMetadata = {};
2216
1857
  if (this.handshakeExtensions) {
2217
- if (!import_value3.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
1858
+ if (!import_value2.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
2218
1859
  this.rejectHandshakeRequest(
2219
1860
  session,
2220
1861
  msg.from,
@@ -2224,7 +1865,7 @@ var ServerTransport = class extends Transport {
2224
1865
  ...session.loggingMetadata,
2225
1866
  connectedTo: msg.from,
2226
1867
  validationErrors: [
2227
- ...import_value3.Value.Errors(
1868
+ ...import_value2.Value.Errors(
2228
1869
  this.handshakeExtensions.schema,
2229
1870
  msg.payload.metadata
2230
1871
  )
@@ -2243,7 +1884,7 @@ var ServerTransport = class extends Transport {
2243
1884
  if (session._isConsumed) {
2244
1885
  return;
2245
1886
  }
2246
- if (import_value3.Value.Check(
1887
+ if (import_value2.Value.Check(
2247
1888
  HandshakeErrorCustomHandlerFatalResponseCodes,
2248
1889
  parsedMetadataOrFailureCode
2249
1890
  )) {
@@ -2355,7 +1996,20 @@ var ServerTransport = class extends Transport {
2355
1996
  sessionId
2356
1997
  }
2357
1998
  });
2358
- session.sendHandshake(responseMsg);
1999
+ const res = session.sendHandshake(responseMsg);
2000
+ if (!res.ok) {
2001
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
2002
+ ...session.loggingMetadata,
2003
+ transportMessage: responseMsg
2004
+ });
2005
+ this.protocolError({
2006
+ type: ProtocolError.MessageSendFailure,
2007
+ message: res.reason
2008
+ });
2009
+ this.deletePendingSession(session);
2010
+ return;
2011
+ }
2012
+ this.pendingSessions.delete(session);
2359
2013
  const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2360
2014
  session,
2361
2015
  // by this point oldSession is either no connection or we dont have an old session
@@ -2382,81 +2036,527 @@ var ServerTransport = class extends Transport {
2382
2036
  this.handleMsg(msg2);
2383
2037
  },
2384
2038
  onInvalidMessage: (reason) => {
2039
+ this.log?.error(`invalid message: ${reason}`, {
2040
+ ...connectedSession.loggingMetadata,
2041
+ transportMessage: msg
2042
+ });
2385
2043
  this.protocolError({
2386
2044
  type: ProtocolError.InvalidMessage,
2387
2045
  message: reason
2388
2046
  });
2389
2047
  this.deleteSession(connectedSession, { unhealthy: true });
2048
+ },
2049
+ onMessageSendFailure: (msg2, reason) => {
2050
+ this.log?.error(`failed to send message: ${reason}`, {
2051
+ ...connectedSession.loggingMetadata,
2052
+ transportMessage: msg2
2053
+ });
2054
+ this.protocolError({
2055
+ type: ProtocolError.MessageSendFailure,
2056
+ message: reason
2057
+ });
2058
+ this.deleteSession(connectedSession, { unhealthy: true });
2390
2059
  }
2391
2060
  },
2392
2061
  gotVersion
2393
2062
  );
2063
+ const bufferSendRes = connectedSession.sendBufferedMessages();
2064
+ if (!bufferSendRes.ok) {
2065
+ return;
2066
+ }
2394
2067
  this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2395
2068
  if (oldSession) {
2396
2069
  this.updateSession(connectedSession);
2397
2070
  } else {
2398
2071
  this.createSession(connectedSession);
2399
2072
  }
2400
- this.pendingSessions.delete(session);
2401
2073
  connectedSession.startActiveHeartbeat();
2402
2074
  }
2403
2075
  };
2404
2076
 
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();
2077
+ // transport/connection.ts
2078
+ var Connection = class {
2079
+ id;
2080
+ telemetry;
2081
+ constructor() {
2082
+ this.id = `conn-${generateId()}`;
2412
2083
  }
2413
- /**
2414
- * Gets the current value of the observable.
2415
- */
2416
- get() {
2417
- return this.value;
2084
+ get loggingMetadata() {
2085
+ const metadata = { connId: this.id };
2086
+ if (this.telemetry?.span.isRecording()) {
2087
+ const spanContext = this.telemetry.span.spanContext();
2088
+ metadata.telemetry = {
2089
+ traceId: spanContext.traceId,
2090
+ spanId: spanContext.spanId
2091
+ };
2092
+ }
2093
+ return metadata;
2094
+ }
2095
+ dataListener;
2096
+ closeListener;
2097
+ errorListener;
2098
+ onData(msg) {
2099
+ this.dataListener?.(msg);
2100
+ }
2101
+ onError(err) {
2102
+ this.errorListener?.(err);
2103
+ }
2104
+ onClose() {
2105
+ this.closeListener?.();
2106
+ this.telemetry?.span.end();
2418
2107
  }
2419
2108
  /**
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.
2109
+ * Set the callback for when a message is received.
2110
+ * @param cb The message handler callback.
2422
2111
  */
2423
- set(tx) {
2424
- const newValue = tx(this.value);
2425
- this.value = newValue;
2426
- this.listeners.forEach((listener) => listener(newValue));
2112
+ setDataListener(cb) {
2113
+ this.dataListener = cb;
2114
+ }
2115
+ removeDataListener() {
2116
+ this.dataListener = void 0;
2427
2117
  }
2428
2118
  /**
2429
- * Subscribes to changes in the observable value.
2430
- * @param listener - A callback function that will be called when the value changes.
2431
- * @returns A function that can be called to unsubscribe from further notifications.
2119
+ * Set the callback for when the connection is closed.
2120
+ * This should also be called if an error happens and after notifying the error listener.
2121
+ * @param cb The callback to call when the connection is closed.
2432
2122
  */
2433
- observe(listener) {
2434
- this.listeners.add(listener);
2435
- listener(this.get());
2436
- return () => this.listeners.delete(listener);
2123
+ setCloseListener(cb) {
2124
+ this.closeListener = cb;
2125
+ }
2126
+ removeCloseListener() {
2127
+ this.closeListener = void 0;
2437
2128
  }
2438
2129
  /**
2439
- * Returns the number of listeners currently observing the observable
2130
+ * Set the callback for when an error is received.
2131
+ * This should only be used for logging errors, all cleanup
2132
+ * should be delegated to setCloseListener.
2133
+ *
2134
+ * The implementer should take care such that the implemented
2135
+ * connection will call both the close and error callbacks
2136
+ * on an error.
2137
+ *
2138
+ * @param cb The callback to call when an error is received.
2440
2139
  */
2441
- get listenerCount() {
2442
- return this.listeners.size;
2140
+ setErrorListener(cb) {
2141
+ this.errorListener = cb;
2142
+ }
2143
+ removeErrorListener() {
2144
+ this.errorListener = void 0;
2443
2145
  }
2444
2146
  };
2445
2147
 
2446
- // testUtil/duplex/duplexPair.ts
2447
- var import_node_stream = require("stream");
2448
- var import_assert = __toESM(require("assert"), 1);
2449
- var kCallback = Symbol("Callback");
2450
- var kInitOtherSide = Symbol("InitOtherSide");
2451
- var DuplexSide = class extends import_node_stream.Duplex {
2452
- otherSide;
2453
- [kCallback];
2454
- constructor() {
2455
- super();
2456
- this[kCallback] = null;
2457
- this.otherSide = null;
2148
+ // codec/adapter.ts
2149
+ var CodecMessageAdapter = class {
2150
+ constructor(codec) {
2151
+ this.codec = codec;
2458
2152
  }
2459
- [kInitOtherSide](otherSide) {
2153
+ toBuffer(msg) {
2154
+ try {
2155
+ return {
2156
+ ok: true,
2157
+ value: this.codec.toBuffer(msg)
2158
+ };
2159
+ } catch (e) {
2160
+ return {
2161
+ ok: false,
2162
+ reason: coerceErrorString(e)
2163
+ };
2164
+ }
2165
+ }
2166
+ fromBuffer(buf) {
2167
+ try {
2168
+ const parsedMsg = this.codec.fromBuffer(buf);
2169
+ if (!import_value3.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
2170
+ return {
2171
+ ok: false,
2172
+ reason: "transport message schema mismatch"
2173
+ };
2174
+ }
2175
+ return {
2176
+ ok: true,
2177
+ value: parsedMsg
2178
+ };
2179
+ } catch (e) {
2180
+ return {
2181
+ ok: false,
2182
+ reason: coerceErrorString(e)
2183
+ };
2184
+ }
2185
+ }
2186
+ };
2187
+
2188
+ // transport/sessionStateMachine/transitions.ts
2189
+ function inheritSharedSession(session) {
2190
+ return {
2191
+ id: session.id,
2192
+ from: session.from,
2193
+ to: session.to,
2194
+ seq: session.seq,
2195
+ ack: session.ack,
2196
+ seqSent: session.seqSent,
2197
+ sendBuffer: session.sendBuffer,
2198
+ telemetry: session.telemetry,
2199
+ options: session.options,
2200
+ log: session.log,
2201
+ tracer: session.tracer,
2202
+ protocolVersion: session.protocolVersion,
2203
+ codec: session.codec
2204
+ };
2205
+ }
2206
+ function inheritSharedSessionWithGrace(session) {
2207
+ return {
2208
+ ...inheritSharedSession(session),
2209
+ graceExpiryTime: session.graceExpiryTime
2210
+ };
2211
+ }
2212
+ var SessionStateGraph = {
2213
+ entrypoints: {
2214
+ NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
2215
+ const id = `session-${generateId()}`;
2216
+ const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
2217
+ const sendBuffer = [];
2218
+ const session = new SessionNoConnection({
2219
+ listeners,
2220
+ id,
2221
+ from,
2222
+ to,
2223
+ seq: 0,
2224
+ ack: 0,
2225
+ seqSent: 0,
2226
+ graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
2227
+ sendBuffer,
2228
+ telemetry,
2229
+ options,
2230
+ protocolVersion,
2231
+ tracer,
2232
+ log,
2233
+ codec: new CodecMessageAdapter(options.codec)
2234
+ });
2235
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
2236
+ ...session.loggingMetadata,
2237
+ tags: ["state-transition"]
2238
+ });
2239
+ return session;
2240
+ },
2241
+ WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
2242
+ const session = new SessionWaitingForHandshake({
2243
+ conn,
2244
+ listeners,
2245
+ from,
2246
+ options,
2247
+ tracer,
2248
+ log,
2249
+ codec: new CodecMessageAdapter(options.codec)
2250
+ });
2251
+ session.log?.info(`session created in WaitingForHandshake state`, {
2252
+ ...session.loggingMetadata,
2253
+ tags: ["state-transition"]
2254
+ });
2255
+ return session;
2256
+ }
2257
+ },
2258
+ // All of the transitions 'move'/'consume' the old session and return a new one.
2259
+ // After a session is transitioned, any usage of the old session will throw.
2260
+ transition: {
2261
+ // happy path transitions
2262
+ NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
2263
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2264
+ oldSession._handleStateExit();
2265
+ const session = new SessionBackingOff({
2266
+ backoffMs,
2267
+ listeners,
2268
+ ...carriedState
2269
+ });
2270
+ session.log?.info(
2271
+ `session ${session.id} transition from NoConnection to BackingOff`,
2272
+ {
2273
+ ...session.loggingMetadata,
2274
+ tags: ["state-transition"]
2275
+ }
2276
+ );
2277
+ return session;
2278
+ },
2279
+ BackingOffToConnecting: (oldSession, connPromise, listeners) => {
2280
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2281
+ oldSession._handleStateExit();
2282
+ const session = new SessionConnecting({
2283
+ connPromise,
2284
+ listeners,
2285
+ ...carriedState
2286
+ });
2287
+ session.log?.info(
2288
+ `session ${session.id} transition from BackingOff to Connecting`,
2289
+ {
2290
+ ...session.loggingMetadata,
2291
+ tags: ["state-transition"]
2292
+ }
2293
+ );
2294
+ return session;
2295
+ },
2296
+ ConnectingToHandshaking: (oldSession, conn, listeners) => {
2297
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2298
+ oldSession._handleStateExit();
2299
+ const session = new SessionHandshaking({
2300
+ conn,
2301
+ listeners,
2302
+ ...carriedState
2303
+ });
2304
+ conn.telemetry = createConnectionTelemetryInfo(
2305
+ session.tracer,
2306
+ conn,
2307
+ session.telemetry
2308
+ );
2309
+ session.log?.info(
2310
+ `session ${session.id} transition from Connecting to Handshaking`,
2311
+ {
2312
+ ...session.loggingMetadata,
2313
+ tags: ["state-transition"]
2314
+ }
2315
+ );
2316
+ return session;
2317
+ },
2318
+ HandshakingToConnected: (oldSession, listeners) => {
2319
+ const carriedState = inheritSharedSession(oldSession);
2320
+ const conn = oldSession.conn;
2321
+ oldSession._handleStateExit();
2322
+ const session = new SessionConnected({
2323
+ conn,
2324
+ listeners,
2325
+ ...carriedState
2326
+ });
2327
+ session.startMissingHeartbeatTimeout();
2328
+ session.log?.info(
2329
+ `session ${session.id} transition from Handshaking to Connected`,
2330
+ {
2331
+ ...session.loggingMetadata,
2332
+ tags: ["state-transition"]
2333
+ }
2334
+ );
2335
+ return session;
2336
+ },
2337
+ WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
2338
+ const conn = pendingSession.conn;
2339
+ const { from, options } = pendingSession;
2340
+ const carriedState = oldSession ? (
2341
+ // old session exists, inherit state
2342
+ inheritSharedSession(oldSession)
2343
+ ) : (
2344
+ // old session does not exist, create new state
2345
+ {
2346
+ id: sessionId,
2347
+ from,
2348
+ to,
2349
+ seq: 0,
2350
+ ack: 0,
2351
+ seqSent: 0,
2352
+ sendBuffer: [],
2353
+ telemetry: createSessionTelemetryInfo(
2354
+ pendingSession.tracer,
2355
+ sessionId,
2356
+ to,
2357
+ from,
2358
+ propagationCtx
2359
+ ),
2360
+ options,
2361
+ tracer: pendingSession.tracer,
2362
+ log: pendingSession.log,
2363
+ protocolVersion,
2364
+ codec: new CodecMessageAdapter(options.codec)
2365
+ }
2366
+ );
2367
+ pendingSession._handleStateExit();
2368
+ oldSession?._handleStateExit();
2369
+ const session = new SessionConnected({
2370
+ conn,
2371
+ listeners,
2372
+ ...carriedState
2373
+ });
2374
+ session.startMissingHeartbeatTimeout();
2375
+ conn.telemetry = createConnectionTelemetryInfo(
2376
+ session.tracer,
2377
+ conn,
2378
+ session.telemetry
2379
+ );
2380
+ session.log?.info(
2381
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
2382
+ {
2383
+ ...session.loggingMetadata,
2384
+ tags: ["state-transition"]
2385
+ }
2386
+ );
2387
+ return session;
2388
+ },
2389
+ // disconnect paths
2390
+ BackingOffToNoConnection: (oldSession, listeners) => {
2391
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2392
+ oldSession._handleStateExit();
2393
+ const session = new SessionNoConnection({
2394
+ listeners,
2395
+ ...carriedState
2396
+ });
2397
+ session.log?.info(
2398
+ `session ${session.id} transition from BackingOff to NoConnection`,
2399
+ {
2400
+ ...session.loggingMetadata,
2401
+ tags: ["state-transition"]
2402
+ }
2403
+ );
2404
+ return session;
2405
+ },
2406
+ ConnectingToNoConnection: (oldSession, listeners) => {
2407
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2408
+ oldSession.bestEffortClose();
2409
+ oldSession._handleStateExit();
2410
+ const session = new SessionNoConnection({
2411
+ listeners,
2412
+ ...carriedState
2413
+ });
2414
+ session.log?.info(
2415
+ `session ${session.id} transition from Connecting to NoConnection`,
2416
+ {
2417
+ ...session.loggingMetadata,
2418
+ tags: ["state-transition"]
2419
+ }
2420
+ );
2421
+ return session;
2422
+ },
2423
+ HandshakingToNoConnection: (oldSession, listeners) => {
2424
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2425
+ oldSession.conn.close();
2426
+ oldSession._handleStateExit();
2427
+ const session = new SessionNoConnection({
2428
+ listeners,
2429
+ ...carriedState
2430
+ });
2431
+ session.log?.info(
2432
+ `session ${session.id} transition from Handshaking to NoConnection`,
2433
+ {
2434
+ ...session.loggingMetadata,
2435
+ tags: ["state-transition"]
2436
+ }
2437
+ );
2438
+ return session;
2439
+ },
2440
+ ConnectedToNoConnection: (oldSession, listeners) => {
2441
+ const carriedState = inheritSharedSession(oldSession);
2442
+ const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
2443
+ oldSession.conn.close();
2444
+ oldSession._handleStateExit();
2445
+ const session = new SessionNoConnection({
2446
+ listeners,
2447
+ graceExpiryTime,
2448
+ ...carriedState
2449
+ });
2450
+ session.log?.info(
2451
+ `session ${session.id} transition from Connected to NoConnection`,
2452
+ {
2453
+ ...session.loggingMetadata,
2454
+ tags: ["state-transition"]
2455
+ }
2456
+ );
2457
+ return session;
2458
+ }
2459
+ }
2460
+ };
2461
+ var transitions = SessionStateGraph.transition;
2462
+ var ClientSessionStateGraph = {
2463
+ entrypoint: SessionStateGraph.entrypoints.NoConnection,
2464
+ transition: {
2465
+ // happy paths
2466
+ // NoConnection -> BackingOff: attempt to connect
2467
+ NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
2468
+ // BackingOff -> Connecting: backoff period elapsed, start connection
2469
+ BackingOffToConnecting: transitions.BackingOffToConnecting,
2470
+ // Connecting -> Handshaking: connection established, start handshake
2471
+ ConnectingToHandshaking: transitions.ConnectingToHandshaking,
2472
+ // Handshaking -> Connected: handshake complete, session ready
2473
+ HandshakingToConnected: transitions.HandshakingToConnected,
2474
+ // disconnect paths
2475
+ // BackingOff -> NoConnection: unused
2476
+ BackingOffToNoConnection: transitions.BackingOffToNoConnection,
2477
+ // Connecting -> NoConnection: connection failed or connection timeout
2478
+ ConnectingToNoConnection: transitions.ConnectingToNoConnection,
2479
+ // Handshaking -> NoConnection: connection closed or handshake timeout
2480
+ HandshakingToNoConnection: transitions.HandshakingToNoConnection,
2481
+ // Connected -> NoConnection: connection closed
2482
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2483
+ // destroy/close paths
2484
+ // NoConnection -> x: grace period elapsed
2485
+ // BackingOff -> x: grace period elapsed
2486
+ // Connecting -> x: grace period elapsed
2487
+ // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
2488
+ // Connected -> x: grace period elapsed or invalid message
2489
+ }
2490
+ };
2491
+ var ServerSessionStateGraph = {
2492
+ entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
2493
+ transition: {
2494
+ // happy paths
2495
+ // WaitingForHandshake -> Connected: handshake complete, session ready
2496
+ WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
2497
+ // disconnect paths
2498
+ // Connected -> NoConnection: connection closed
2499
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2500
+ // destroy/close paths
2501
+ // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
2502
+ }
2503
+ };
2504
+
2505
+ // testUtil/observable/observable.ts
2506
+ var Observable = class {
2507
+ value;
2508
+ listeners;
2509
+ constructor(initialValue) {
2510
+ this.value = initialValue;
2511
+ this.listeners = /* @__PURE__ */ new Set();
2512
+ }
2513
+ /**
2514
+ * Gets the current value of the observable.
2515
+ */
2516
+ get() {
2517
+ return this.value;
2518
+ }
2519
+ /**
2520
+ * Sets the current value of the observable. All listeners will get an update with this value.
2521
+ * @param newValue - The new value to set.
2522
+ */
2523
+ set(tx) {
2524
+ const newValue = tx(this.value);
2525
+ this.value = newValue;
2526
+ this.listeners.forEach((listener) => listener(newValue));
2527
+ }
2528
+ /**
2529
+ * Subscribes to changes in the observable value.
2530
+ * @param listener - A callback function that will be called when the value changes.
2531
+ * @returns A function that can be called to unsubscribe from further notifications.
2532
+ */
2533
+ observe(listener) {
2534
+ this.listeners.add(listener);
2535
+ listener(this.get());
2536
+ return () => this.listeners.delete(listener);
2537
+ }
2538
+ /**
2539
+ * Returns the number of listeners currently observing the observable
2540
+ */
2541
+ get listenerCount() {
2542
+ return this.listeners.size;
2543
+ }
2544
+ };
2545
+
2546
+ // testUtil/duplex/duplexPair.ts
2547
+ var import_node_stream = require("stream");
2548
+ var import_assert = __toESM(require("assert"), 1);
2549
+ var kCallback = Symbol("Callback");
2550
+ var kInitOtherSide = Symbol("InitOtherSide");
2551
+ var DuplexSide = class extends import_node_stream.Duplex {
2552
+ otherSide;
2553
+ [kCallback];
2554
+ constructor() {
2555
+ super();
2556
+ this[kCallback] = null;
2557
+ this.otherSide = null;
2558
+ }
2559
+ [kInitOtherSide](otherSide) {
2460
2560
  if (this.otherSide === null) {
2461
2561
  this.otherSide = otherSide;
2462
2562
  }
@@ -2488,6 +2588,16 @@ function duplexPair() {
2488
2588
  const side1 = new DuplexSide();
2489
2589
  side0[kInitOtherSide](side1);
2490
2590
  side1[kInitOtherSide](side0);
2591
+ side0.on("close", () => {
2592
+ setImmediate(() => {
2593
+ side1.destroy();
2594
+ });
2595
+ });
2596
+ side1.on("close", () => {
2597
+ setImmediate(() => {
2598
+ side0.destroy();
2599
+ });
2600
+ });
2491
2601
  return [side0, side1];
2492
2602
  }
2493
2603
 
@@ -2500,19 +2610,13 @@ var InMemoryConnection = class extends Connection {
2500
2610
  this.conn = pipe;
2501
2611
  this.conn.allowHalfOpen = false;
2502
2612
  this.conn.on("data", (data) => {
2503
- for (const cb of this.dataListeners) {
2504
- cb(data);
2505
- }
2613
+ this.dataListener?.(data);
2506
2614
  });
2507
2615
  this.conn.on("close", () => {
2508
- for (const cb of this.closeListeners) {
2509
- cb();
2510
- }
2616
+ this.closeListener?.();
2511
2617
  });
2512
2618
  this.conn.on("error", (err) => {
2513
- for (const cb of this.errorListeners) {
2514
- cb(err);
2515
- }
2619
+ this.errorListener?.(err);
2516
2620
  });
2517
2621
  }
2518
2622
  send(payload) {
@@ -2595,19 +2699,18 @@ function createMockTransportNetwork(opts) {
2595
2699
  },
2596
2700
  async restartServer() {
2597
2701
  for (const transport of transports) {
2598
- if (transport.clientId !== "SERVER")
2599
- continue;
2702
+ if (transport.clientId !== "SERVER") continue;
2600
2703
  transport.close();
2601
2704
  }
2602
2705
  for (const conn of Object.values(connections.get())) {
2603
- conn.serverToClient.end();
2604
- conn.clientToServer.end();
2706
+ conn.serverToClient.destroy();
2707
+ conn.clientToServer.destroy();
2605
2708
  }
2606
2709
  },
2607
2710
  cleanup() {
2608
2711
  for (const conn of Object.values(connections.get())) {
2609
- conn.serverToClient.end();
2610
- conn.clientToServer.end();
2712
+ conn.serverToClient.destroy();
2713
+ conn.clientToServer.destroy();
2611
2714
  }
2612
2715
  }
2613
2716
  };