@replit/river 0.200.0-rc.2 → 0.200.0-rc.4

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 (84) hide show
  1. package/README.md +21 -20
  2. package/dist/chunk-2BF4VMUZ.js +50 -0
  3. package/dist/chunk-2BF4VMUZ.js.map +1 -0
  4. package/dist/chunk-BZQQXMVF.js +401 -0
  5. package/dist/chunk-BZQQXMVF.js.map +1 -0
  6. package/dist/{chunk-4VNY34QG.js → chunk-F7Z2QQRL.js} +24 -18
  7. package/dist/chunk-F7Z2QQRL.js.map +1 -0
  8. package/dist/chunk-IMFMNIEO.js +384 -0
  9. package/dist/chunk-IMFMNIEO.js.map +1 -0
  10. package/dist/chunk-RDGHFHXN.js +658 -0
  11. package/dist/chunk-RDGHFHXN.js.map +1 -0
  12. package/dist/chunk-SI4YHBTI.js +277 -0
  13. package/dist/chunk-SI4YHBTI.js.map +1 -0
  14. package/dist/{chunk-7CKIN3JT.js → chunk-SIRRYRLQ.js} +73 -490
  15. package/dist/chunk-SIRRYRLQ.js.map +1 -0
  16. package/dist/{chunk-S5RL45KH.js → chunk-V57VWV5S.js} +80 -44
  17. package/dist/chunk-V57VWV5S.js.map +1 -0
  18. package/dist/{chunk-QMM35C3H.js → chunk-VXYHC666.js} +1 -1
  19. package/dist/chunk-VXYHC666.js.map +1 -0
  20. package/dist/client-f56a6da3.d.ts +49 -0
  21. package/dist/{connection-f900e390.d.ts → connection-11991b13.d.ts} +1 -5
  22. package/dist/connection-6031a354.d.ts +11 -0
  23. package/dist/context-73df8978.d.ts +528 -0
  24. package/dist/logging/index.cjs.map +1 -1
  25. package/dist/logging/index.d.cts +1 -1
  26. package/dist/logging/index.d.ts +1 -1
  27. package/dist/logging/index.js +1 -1
  28. package/dist/{index-10ebd26a.d.ts → message-fd349b27.d.ts} +31 -31
  29. package/dist/router/index.cjs +125 -502
  30. package/dist/router/index.cjs.map +1 -1
  31. package/dist/router/index.d.cts +11 -46
  32. package/dist/router/index.d.ts +11 -46
  33. package/dist/router/index.js +2 -4
  34. package/dist/server-9f31d98f.d.ts +42 -0
  35. package/dist/{services-970f97bb.d.ts → services-c67758fc.d.ts} +5 -602
  36. package/dist/transport/impls/uds/client.cjs +1247 -1238
  37. package/dist/transport/impls/uds/client.cjs.map +1 -1
  38. package/dist/transport/impls/uds/client.d.cts +4 -3
  39. package/dist/transport/impls/uds/client.d.ts +4 -3
  40. package/dist/transport/impls/uds/client.js +7 -13
  41. package/dist/transport/impls/uds/client.js.map +1 -1
  42. package/dist/transport/impls/uds/server.cjs +1311 -1170
  43. package/dist/transport/impls/uds/server.cjs.map +1 -1
  44. package/dist/transport/impls/uds/server.d.cts +4 -4
  45. package/dist/transport/impls/uds/server.d.ts +4 -4
  46. package/dist/transport/impls/uds/server.js +6 -6
  47. package/dist/transport/impls/ws/client.cjs +988 -987
  48. package/dist/transport/impls/ws/client.cjs.map +1 -1
  49. package/dist/transport/impls/ws/client.d.cts +6 -5
  50. package/dist/transport/impls/ws/client.d.ts +6 -5
  51. package/dist/transport/impls/ws/client.js +6 -7
  52. package/dist/transport/impls/ws/client.js.map +1 -1
  53. package/dist/transport/impls/ws/server.cjs +1192 -1066
  54. package/dist/transport/impls/ws/server.cjs.map +1 -1
  55. package/dist/transport/impls/ws/server.d.cts +4 -4
  56. package/dist/transport/impls/ws/server.d.ts +4 -4
  57. package/dist/transport/impls/ws/server.js +6 -6
  58. package/dist/transport/index.cjs +1446 -1380
  59. package/dist/transport/index.cjs.map +1 -1
  60. package/dist/transport/index.d.cts +4 -26
  61. package/dist/transport/index.d.ts +4 -26
  62. package/dist/transport/index.js +9 -9
  63. package/dist/util/testHelpers.cjs +746 -303
  64. package/dist/util/testHelpers.cjs.map +1 -1
  65. package/dist/util/testHelpers.d.cts +9 -4
  66. package/dist/util/testHelpers.d.ts +9 -4
  67. package/dist/util/testHelpers.js +36 -10
  68. package/dist/util/testHelpers.js.map +1 -1
  69. package/package.json +1 -1
  70. package/dist/chunk-47TFNAY2.js +0 -476
  71. package/dist/chunk-47TFNAY2.js.map +0 -1
  72. package/dist/chunk-4VNY34QG.js.map +0 -1
  73. package/dist/chunk-7CKIN3JT.js.map +0 -1
  74. package/dist/chunk-CZP4LK3F.js +0 -335
  75. package/dist/chunk-CZP4LK3F.js.map +0 -1
  76. package/dist/chunk-DJCW3SKT.js +0 -59
  77. package/dist/chunk-DJCW3SKT.js.map +0 -1
  78. package/dist/chunk-NQWDT6GS.js +0 -347
  79. package/dist/chunk-NQWDT6GS.js.map +0 -1
  80. package/dist/chunk-ONUXWVRC.js +0 -492
  81. package/dist/chunk-ONUXWVRC.js.map +0 -1
  82. package/dist/chunk-QMM35C3H.js.map +0 -1
  83. package/dist/chunk-S5RL45KH.js.map +0 -1
  84. package/dist/connection-3f117047.d.ts +0 -17
@@ -34,13 +34,16 @@ __export(testHelpers_exports, {
34
34
  asClientStream: () => asClientStream,
35
35
  asClientSubscription: () => asClientSubscription,
36
36
  asClientUpload: () => asClientUpload,
37
+ closeAllConnections: () => closeAllConnections,
37
38
  createDummyTransportMessage: () => createDummyTransportMessage,
38
39
  createLocalWebSocketClient: () => createLocalWebSocketClient,
39
40
  createWebSocketServer: () => createWebSocketServer,
40
41
  dummySession: () => dummySession,
41
42
  getIteratorFromStream: () => getIteratorFromStream,
43
+ getTransportConnections: () => getTransportConnections,
42
44
  getUnixSocketPath: () => getUnixSocketPath,
43
45
  iterNext: () => iterNext,
46
+ numberOfConnections: () => numberOfConnections,
44
47
  onUdsServeReady: () => onUdsServeReady,
45
48
  onWsServerReady: () => onWsServerReady,
46
49
  payloadToTransportMessage: () => payloadToTransportMessage,
@@ -376,23 +379,120 @@ var InputReaderErrorSchema = import_typebox2.Type.Object({
376
379
  message: import_typebox2.Type.String()
377
380
  });
378
381
 
382
+ // transport/message.ts
383
+ var import_typebox3 = require("@sinclair/typebox");
384
+
385
+ // transport/id.ts
386
+ var import_nanoid = require("nanoid");
387
+ var alphabet = (0, import_nanoid.customAlphabet)(
388
+ "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
389
+ );
390
+ var generateId = () => alphabet(12);
391
+
392
+ // transport/message.ts
393
+ var TransportMessageSchema = (t) => import_typebox3.Type.Object({
394
+ id: import_typebox3.Type.String(),
395
+ from: import_typebox3.Type.String(),
396
+ to: import_typebox3.Type.String(),
397
+ seq: import_typebox3.Type.Integer(),
398
+ ack: import_typebox3.Type.Integer(),
399
+ serviceName: import_typebox3.Type.Optional(import_typebox3.Type.String()),
400
+ procedureName: import_typebox3.Type.Optional(import_typebox3.Type.String()),
401
+ streamId: import_typebox3.Type.String(),
402
+ controlFlags: import_typebox3.Type.Integer(),
403
+ tracing: import_typebox3.Type.Optional(
404
+ import_typebox3.Type.Object({
405
+ traceparent: import_typebox3.Type.String(),
406
+ tracestate: import_typebox3.Type.String()
407
+ })
408
+ ),
409
+ payload: t
410
+ });
411
+ var ControlMessageAckSchema = import_typebox3.Type.Object({
412
+ type: import_typebox3.Type.Literal("ACK")
413
+ });
414
+ var ControlMessageCloseSchema = import_typebox3.Type.Object({
415
+ type: import_typebox3.Type.Literal("CLOSE")
416
+ });
417
+ var currentProtocolVersion = "v2.0";
418
+ var ControlMessageHandshakeRequestSchema = import_typebox3.Type.Object({
419
+ type: import_typebox3.Type.Literal("HANDSHAKE_REQ"),
420
+ protocolVersion: import_typebox3.Type.String(),
421
+ sessionId: import_typebox3.Type.String(),
422
+ /**
423
+ * Specifies what the server's expected session state (from the pov of the client). This can be
424
+ * used by the server to know whether this is a new or a reestablished connection, and whether it
425
+ * is compatible with what it already has.
426
+ */
427
+ expectedSessionState: import_typebox3.Type.Object({
428
+ // what the client expects the server to send next
429
+ nextExpectedSeq: import_typebox3.Type.Integer(),
430
+ // TODO: remove optional once we know all servers
431
+ // are nextSentSeq here
432
+ // what the server expects the client to send next
433
+ nextSentSeq: import_typebox3.Type.Optional(import_typebox3.Type.Integer())
434
+ }),
435
+ metadata: import_typebox3.Type.Optional(import_typebox3.Type.Unknown())
436
+ });
437
+ var HandshakeErrorRetriableResponseCodes = import_typebox3.Type.Union([
438
+ import_typebox3.Type.Literal("SESSION_STATE_MISMATCH")
439
+ ]);
440
+ var HandshakeErrorFatalResponseCodes = import_typebox3.Type.Union([
441
+ import_typebox3.Type.Literal("MALFORMED_HANDSHAKE_META"),
442
+ import_typebox3.Type.Literal("MALFORMED_HANDSHAKE"),
443
+ import_typebox3.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
444
+ import_typebox3.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
445
+ ]);
446
+ var HandshakeErrorResponseCodes = import_typebox3.Type.Union([
447
+ HandshakeErrorRetriableResponseCodes,
448
+ HandshakeErrorFatalResponseCodes
449
+ ]);
450
+ var ControlMessageHandshakeResponseSchema = import_typebox3.Type.Object({
451
+ type: import_typebox3.Type.Literal("HANDSHAKE_RESP"),
452
+ status: import_typebox3.Type.Union([
453
+ import_typebox3.Type.Object({
454
+ ok: import_typebox3.Type.Literal(true),
455
+ sessionId: import_typebox3.Type.String()
456
+ }),
457
+ import_typebox3.Type.Object({
458
+ ok: import_typebox3.Type.Literal(false),
459
+ reason: import_typebox3.Type.String(),
460
+ // TODO: remove optional once we know all servers
461
+ // are sending code here
462
+ code: import_typebox3.Type.Optional(HandshakeErrorResponseCodes)
463
+ })
464
+ ])
465
+ });
466
+ var ControlMessagePayloadSchema = import_typebox3.Type.Union([
467
+ ControlMessageCloseSchema,
468
+ ControlMessageAckSchema,
469
+ ControlMessageHandshakeRequestSchema,
470
+ ControlMessageHandshakeResponseSchema
471
+ ]);
472
+ var OpaqueTransportMessageSchema = TransportMessageSchema(
473
+ import_typebox3.Type.Unknown()
474
+ );
475
+ function isAck(controlFlag) {
476
+ return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
477
+ }
478
+
379
479
  // tracing/index.ts
380
480
  var import_api = require("@opentelemetry/api");
381
481
 
382
482
  // package.json
383
- var version = "0.200.0-rc.2";
483
+ var version = "0.200.0-rc.4";
384
484
 
385
485
  // tracing/index.ts
386
- function createSessionTelemetryInfo(session, propagationCtx) {
486
+ function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
387
487
  const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
388
488
  const span = tracer.startSpan(
389
- `session ${session.id}`,
489
+ `session ${sessionId}`,
390
490
  {
391
491
  attributes: {
392
492
  component: "river",
393
- "river.session.id": session.id,
394
- "river.session.to": session.to,
395
- "river.session.from": session.from
493
+ "river.session.id": sessionId,
494
+ "river.session.to": to,
495
+ "river.session.from": from
396
496
  }
397
497
  },
398
498
  parentCtx
@@ -413,298 +513,6 @@ function coerceErrorString(err) {
413
513
  // util/testHelpers.ts
414
514
  var import_nanoid2 = require("nanoid");
415
515
 
416
- // transport/session.ts
417
- var import_nanoid = require("nanoid");
418
- var import_api2 = require("@opentelemetry/api");
419
- var nanoid = (0, import_nanoid.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
420
- var unsafeId = () => nanoid();
421
- var Session = class {
422
- codec;
423
- options;
424
- telemetry;
425
- /**
426
- * The buffer of messages that have been sent but not yet acknowledged.
427
- */
428
- sendBuffer = [];
429
- /**
430
- * The active connection associated with this session
431
- */
432
- connection;
433
- /**
434
- * A connection that is currently undergoing handshaking. Used to distinguish between the active
435
- * connection, but still be able to close it if needed.
436
- */
437
- handshakingConnection;
438
- from;
439
- to;
440
- /**
441
- * The unique ID of this session.
442
- */
443
- id;
444
- /**
445
- * What the other side advertised as their session ID
446
- * for this session.
447
- */
448
- advertisedSessionId;
449
- /**
450
- * Number of messages we've sent along this session (excluding handshake and acks)
451
- */
452
- seq = 0;
453
- /**
454
- * Number of unique messages we've received this session (excluding handshake and acks)
455
- */
456
- ack = 0;
457
- /**
458
- * The grace period between when the inner connection is disconnected
459
- * and when we should consider the entire session disconnected.
460
- */
461
- disconnectionGrace;
462
- /**
463
- * Number of heartbeats we've sent without a response.
464
- */
465
- heartbeatMisses;
466
- /**
467
- * The interval for sending heartbeats.
468
- */
469
- heartbeat;
470
- log;
471
- constructor(conn, from, to, options, propagationCtx) {
472
- this.id = `session-${nanoid(12)}`;
473
- this.options = options;
474
- this.from = from;
475
- this.to = to;
476
- this.connection = conn;
477
- this.codec = options.codec;
478
- this.heartbeatMisses = 0;
479
- this.heartbeat = setInterval(
480
- () => this.sendHeartbeat(),
481
- options.heartbeatIntervalMs
482
- );
483
- this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
484
- }
485
- bindLogger(log) {
486
- this.log = log;
487
- }
488
- get loggingMetadata() {
489
- const spanContext = this.telemetry.span.spanContext();
490
- return {
491
- clientId: this.from,
492
- connectedTo: this.to,
493
- sessionId: this.id,
494
- connId: this.connection?.id,
495
- telemetry: {
496
- traceId: spanContext.traceId,
497
- spanId: spanContext.spanId
498
- }
499
- };
500
- }
501
- /**
502
- * Sends a message over the session's connection.
503
- * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
504
- *
505
- * @param msg The partial message to be sent, which will be constructed into a full message.
506
- * @param addToSendBuff Whether to add the message to the send buffer for retry.
507
- * @returns The full transport ID of the message that was attempted to be sent.
508
- */
509
- send(msg) {
510
- const fullMsg = this.constructMsg(msg);
511
- this.log?.debug(`sending msg`, {
512
- ...this.loggingMetadata,
513
- transportMessage: fullMsg
514
- });
515
- if (this.connection) {
516
- const ok = this.connection.send(this.codec.toBuffer(fullMsg));
517
- if (ok)
518
- return fullMsg.id;
519
- this.log?.info(
520
- `failed to send msg to ${fullMsg.to}, connection is probably dead`,
521
- {
522
- ...this.loggingMetadata,
523
- transportMessage: fullMsg
524
- }
525
- );
526
- } else {
527
- this.log?.debug(
528
- `buffering msg to ${fullMsg.to}, connection not ready yet`,
529
- { ...this.loggingMetadata, transportMessage: fullMsg }
530
- );
531
- }
532
- return fullMsg.id;
533
- }
534
- sendHeartbeat() {
535
- const misses = this.heartbeatMisses;
536
- const missDuration = misses * this.options.heartbeatIntervalMs;
537
- if (misses > this.options.heartbeatsUntilDead) {
538
- if (this.connection) {
539
- this.log?.info(
540
- `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
541
- this.loggingMetadata
542
- );
543
- this.telemetry.span.addEvent("closing connection due to inactivity");
544
- this.closeStaleConnection();
545
- }
546
- return;
547
- }
548
- this.send({
549
- streamId: "heartbeat",
550
- controlFlags: 1 /* AckBit */,
551
- payload: {
552
- type: "ACK"
553
- }
554
- });
555
- this.heartbeatMisses++;
556
- }
557
- resetBufferedMessages() {
558
- this.sendBuffer = [];
559
- this.seq = 0;
560
- this.ack = 0;
561
- }
562
- sendBufferedMessages(conn) {
563
- this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
564
- ...this.loggingMetadata,
565
- connId: conn.id
566
- });
567
- for (const msg of this.sendBuffer) {
568
- this.log?.debug(`resending msg`, {
569
- ...this.loggingMetadata,
570
- transportMessage: msg,
571
- connId: conn.id
572
- });
573
- const ok = conn.send(this.codec.toBuffer(msg));
574
- if (!ok) {
575
- const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
576
- conn.telemetry?.span.setStatus({
577
- code: import_api2.SpanStatusCode.ERROR,
578
- message: errMsg
579
- });
580
- this.log?.error(errMsg, {
581
- ...this.loggingMetadata,
582
- transportMessage: msg,
583
- connId: conn.id,
584
- tags: ["invariant-violation"]
585
- });
586
- conn.close();
587
- return;
588
- }
589
- }
590
- }
591
- updateBookkeeping(ack, seq) {
592
- if (seq + 1 < this.ack) {
593
- this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
594
- ...this.loggingMetadata,
595
- tags: ["invariant-violation"]
596
- });
597
- return;
598
- }
599
- this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
600
- this.ack = seq + 1;
601
- }
602
- closeStaleConnection(conn) {
603
- if (this.connection === void 0 || this.connection === conn)
604
- return;
605
- this.log?.info(
606
- `closing old inner connection from session to ${this.to}`,
607
- this.loggingMetadata
608
- );
609
- this.connection.close();
610
- this.connection = void 0;
611
- }
612
- replaceWithNewConnection(newConn, isTransparentReconnect) {
613
- this.closeStaleConnection(newConn);
614
- this.cancelGrace();
615
- if (isTransparentReconnect) {
616
- this.sendBufferedMessages(newConn);
617
- }
618
- this.connection = newConn;
619
- this.handshakingConnection = void 0;
620
- }
621
- replaceWithNewHandshakingConnection(newConn) {
622
- this.handshakingConnection = newConn;
623
- }
624
- beginGrace(cb) {
625
- this.log?.info(
626
- `starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
627
- this.loggingMetadata
628
- );
629
- this.cancelGrace();
630
- this.disconnectionGrace = setTimeout(() => {
631
- this.log?.info(
632
- `grace period for ${this.to} elapsed`,
633
- this.loggingMetadata
634
- );
635
- cb();
636
- }, this.options.sessionDisconnectGraceMs);
637
- }
638
- // called on reconnect of the underlying session
639
- cancelGrace() {
640
- this.heartbeatMisses = 0;
641
- clearTimeout(this.disconnectionGrace);
642
- this.disconnectionGrace = void 0;
643
- }
644
- /**
645
- * Used to close the handshaking connection, if set.
646
- */
647
- closeHandshakingConnection(expectedHandshakingConn) {
648
- if (this.handshakingConnection === void 0)
649
- return;
650
- if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
651
- return;
652
- }
653
- this.handshakingConnection.close();
654
- this.handshakingConnection = void 0;
655
- }
656
- // closed when we want to discard the whole session
657
- // (i.e. shutdown or session disconnect)
658
- close() {
659
- this.closeStaleConnection();
660
- this.cancelGrace();
661
- this.resetBufferedMessages();
662
- clearInterval(this.heartbeat);
663
- }
664
- get connected() {
665
- return this.connection !== void 0;
666
- }
667
- get nextExpectedAck() {
668
- return this.seq;
669
- }
670
- get nextExpectedSeq() {
671
- return this.ack;
672
- }
673
- /**
674
- * Check that the peer's next expected seq number matches something that is in our send buffer
675
- * _or_ matches our actual next seq.
676
- */
677
- nextExpectedSeqInRange(nextExpectedSeq) {
678
- for (const msg of this.sendBuffer) {
679
- if (nextExpectedSeq === msg.seq) {
680
- return true;
681
- }
682
- }
683
- return nextExpectedSeq === this.seq;
684
- }
685
- // This is only used in tests to make the session misbehave.
686
- /* @internal */
687
- advanceAckForTesting(by) {
688
- this.ack += by;
689
- }
690
- constructMsg(partialMsg) {
691
- const msg = {
692
- ...partialMsg,
693
- id: unsafeId(),
694
- to: this.to,
695
- from: this.from,
696
- seq: this.seq,
697
- ack: this.ack
698
- };
699
- this.seq++;
700
- this.sendBuffer.push(msg);
701
- return msg;
702
- }
703
- inspectSendBuffer() {
704
- return this.sendBuffer;
705
- }
706
- };
707
-
708
516
  // codec/json.ts
709
517
  var encoder = new TextEncoder();
710
518
  var decoder = new TextDecoder();
@@ -762,6 +570,8 @@ var defaultTransportOptions = {
762
570
  heartbeatIntervalMs: 1e3,
763
571
  heartbeatsUntilDead: 2,
764
572
  sessionDisconnectGraceMs: 5e3,
573
+ connectionTimeoutMs: 2e3,
574
+ handshakeTimeoutMs: 1e3,
765
575
  codec: NaiveJsonCodec
766
576
  };
767
577
  var defaultConnectionRetryOptions = {
@@ -779,6 +589,614 @@ var defaultServerTransportOptions = {
779
589
  ...defaultTransportOptions
780
590
  };
781
591
 
592
+ // transport/sessionStateMachine/common.ts
593
+ var import_value = require("@sinclair/typebox/value");
594
+ var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
595
+ var StateMachineState = class {
596
+ /*
597
+ * Whether this state has been consumed
598
+ * and we've moved on to another state
599
+ */
600
+ _isConsumed;
601
+ close() {
602
+ this._handleClose();
603
+ }
604
+ constructor() {
605
+ this._isConsumed = false;
606
+ return new Proxy(this, {
607
+ get(target, prop) {
608
+ if (prop === "_isConsumed" || prop === "id" || prop === "state") {
609
+ return Reflect.get(target, prop);
610
+ }
611
+ if (prop === "_handleStateExit") {
612
+ return () => {
613
+ target._isConsumed = true;
614
+ target._handleStateExit();
615
+ };
616
+ }
617
+ if (prop === "_handleClose") {
618
+ return () => {
619
+ target._handleStateExit();
620
+ target._handleClose();
621
+ };
622
+ }
623
+ if (target._isConsumed) {
624
+ throw new Error(
625
+ `${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
626
+ );
627
+ }
628
+ return Reflect.get(target, prop);
629
+ },
630
+ set(target, prop, value) {
631
+ if (target._isConsumed) {
632
+ throw new Error(
633
+ `${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
634
+ );
635
+ }
636
+ return Reflect.set(target, prop, value);
637
+ }
638
+ });
639
+ }
640
+ };
641
+ var CommonSession = class extends StateMachineState {
642
+ from;
643
+ options;
644
+ log;
645
+ constructor(from, options, log) {
646
+ super();
647
+ this.from = from;
648
+ this.options = options;
649
+ this.log = log;
650
+ }
651
+ parseMsg(msg) {
652
+ const parsedMsg = this.options.codec.fromBuffer(msg);
653
+ if (parsedMsg === null) {
654
+ const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
655
+ this.log?.error(
656
+ `received malformed msg: ${decodedBuffer}`,
657
+ this.loggingMetadata
658
+ );
659
+ return null;
660
+ }
661
+ if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
662
+ this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
663
+ ...this.loggingMetadata,
664
+ validationErrors: [
665
+ ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
666
+ ]
667
+ });
668
+ return null;
669
+ }
670
+ return parsedMsg;
671
+ }
672
+ };
673
+ var IdentifiedSession = class extends CommonSession {
674
+ id;
675
+ telemetry;
676
+ to;
677
+ protocolVersion;
678
+ /**
679
+ * Index of the message we will send next (excluding handshake)
680
+ */
681
+ seq;
682
+ /**
683
+ * Number of unique messages we've received this session (excluding handshake)
684
+ */
685
+ ack;
686
+ sendBuffer;
687
+ constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, protocolVersion, log) {
688
+ super(from, options, log);
689
+ this.id = id;
690
+ this.to = to;
691
+ this.seq = seq;
692
+ this.ack = ack;
693
+ this.sendBuffer = sendBuffer;
694
+ this.telemetry = telemetry;
695
+ this.log = log;
696
+ this.protocolVersion = protocolVersion;
697
+ }
698
+ get loggingMetadata() {
699
+ const spanContext = this.telemetry.span.spanContext();
700
+ return {
701
+ clientId: this.from,
702
+ connectedTo: this.to,
703
+ sessionId: this.id,
704
+ telemetry: {
705
+ traceId: spanContext.traceId,
706
+ spanId: spanContext.spanId
707
+ }
708
+ };
709
+ }
710
+ constructMsg(partialMsg) {
711
+ const msg = {
712
+ ...partialMsg,
713
+ id: generateId(),
714
+ to: this.to,
715
+ from: this.from,
716
+ seq: this.seq,
717
+ ack: this.ack
718
+ };
719
+ this.seq++;
720
+ return msg;
721
+ }
722
+ nextSeq() {
723
+ return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
724
+ }
725
+ send(msg) {
726
+ const constructedMsg = this.constructMsg(msg);
727
+ this.sendBuffer.push(constructedMsg);
728
+ return constructedMsg.id;
729
+ }
730
+ _handleStateExit() {
731
+ }
732
+ _handleClose() {
733
+ this.sendBuffer.length = 0;
734
+ this.telemetry.span.end();
735
+ }
736
+ };
737
+
738
+ // transport/sessionStateMachine/SessionConnecting.ts
739
+ var SessionConnecting = class extends IdentifiedSession {
740
+ state = "Connecting" /* Connecting */;
741
+ connPromise;
742
+ listeners;
743
+ connectionTimeout;
744
+ constructor(connPromise, listeners, ...args) {
745
+ super(...args);
746
+ this.connPromise = connPromise;
747
+ this.listeners = listeners;
748
+ this.connectionTimeout = setTimeout(() => {
749
+ listeners.onConnectionTimeout();
750
+ }, this.options.connectionTimeoutMs);
751
+ connPromise.then(
752
+ (conn) => {
753
+ if (this._isConsumed)
754
+ return;
755
+ listeners.onConnectionEstablished(conn);
756
+ },
757
+ (err) => {
758
+ if (this._isConsumed)
759
+ return;
760
+ listeners.onConnectionFailed(err);
761
+ }
762
+ );
763
+ }
764
+ // close a pending connection if it resolves, ignore errors if the promise
765
+ // ends up rejected anyways
766
+ bestEffortClose() {
767
+ void this.connPromise.then((conn) => conn.close()).catch(() => {
768
+ });
769
+ }
770
+ _handleStateExit() {
771
+ super._handleStateExit();
772
+ clearTimeout(this.connectionTimeout);
773
+ this.connectionTimeout = void 0;
774
+ }
775
+ _handleClose() {
776
+ this.bestEffortClose();
777
+ super._handleClose();
778
+ }
779
+ };
780
+
781
+ // transport/sessionStateMachine/SessionNoConnection.ts
782
+ var SessionNoConnection = class extends IdentifiedSession {
783
+ state = "NoConnection" /* NoConnection */;
784
+ listeners;
785
+ gracePeriodTimeout;
786
+ constructor(listeners, ...args) {
787
+ super(...args);
788
+ this.listeners = listeners;
789
+ this.gracePeriodTimeout = setTimeout(() => {
790
+ this.listeners.onSessionGracePeriodElapsed();
791
+ }, this.options.sessionDisconnectGraceMs);
792
+ }
793
+ _handleClose() {
794
+ super._handleClose();
795
+ }
796
+ _handleStateExit() {
797
+ super._handleStateExit();
798
+ if (this.gracePeriodTimeout) {
799
+ clearTimeout(this.gracePeriodTimeout);
800
+ this.gracePeriodTimeout = void 0;
801
+ }
802
+ }
803
+ };
804
+
805
+ // transport/sessionStateMachine/SessionWaitingForHandshake.ts
806
+ var SessionWaitingForHandshake = class extends CommonSession {
807
+ state = "WaitingForHandshake" /* WaitingForHandshake */;
808
+ conn;
809
+ listeners;
810
+ handshakeTimeout;
811
+ constructor(conn, listeners, ...args) {
812
+ super(...args);
813
+ this.conn = conn;
814
+ this.listeners = listeners;
815
+ this.handshakeTimeout = setTimeout(() => {
816
+ listeners.onHandshakeTimeout();
817
+ }, this.options.handshakeTimeoutMs);
818
+ this.conn.addDataListener(this.onHandshakeData);
819
+ this.conn.addErrorListener(listeners.onConnectionErrored);
820
+ this.conn.addCloseListener(listeners.onConnectionClosed);
821
+ }
822
+ onHandshakeData = (msg) => {
823
+ const parsedMsg = this.parseMsg(msg);
824
+ if (parsedMsg === null) {
825
+ this.listeners.onInvalidHandshake("could not parse message");
826
+ return;
827
+ }
828
+ this.listeners.onHandshake(parsedMsg);
829
+ };
830
+ get loggingMetadata() {
831
+ return {
832
+ clientId: this.from,
833
+ connId: this.conn.id
834
+ };
835
+ }
836
+ sendHandshake(msg) {
837
+ return this.conn.send(this.options.codec.toBuffer(msg));
838
+ }
839
+ _handleStateExit() {
840
+ this.conn.removeDataListener(this.onHandshakeData);
841
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
842
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
843
+ clearTimeout(this.handshakeTimeout);
844
+ this.handshakeTimeout = void 0;
845
+ }
846
+ _handleClose() {
847
+ this.conn.close();
848
+ }
849
+ };
850
+
851
+ // transport/sessionStateMachine/SessionHandshaking.ts
852
+ var SessionHandshaking = class extends IdentifiedSession {
853
+ state = "Handshaking" /* Handshaking */;
854
+ conn;
855
+ listeners;
856
+ handshakeTimeout;
857
+ constructor(conn, listeners, ...args) {
858
+ super(...args);
859
+ this.conn = conn;
860
+ this.listeners = listeners;
861
+ this.handshakeTimeout = setTimeout(() => {
862
+ listeners.onHandshakeTimeout();
863
+ }, this.options.handshakeTimeoutMs);
864
+ this.conn.addDataListener(this.onHandshakeData);
865
+ this.conn.addErrorListener(listeners.onConnectionErrored);
866
+ this.conn.addCloseListener(listeners.onConnectionClosed);
867
+ }
868
+ onHandshakeData = (msg) => {
869
+ const parsedMsg = this.parseMsg(msg);
870
+ if (parsedMsg === null) {
871
+ this.listeners.onInvalidHandshake("could not parse message");
872
+ return;
873
+ }
874
+ this.listeners.onHandshake(parsedMsg);
875
+ };
876
+ sendHandshake(msg) {
877
+ return this.conn.send(this.options.codec.toBuffer(msg));
878
+ }
879
+ _handleStateExit() {
880
+ super._handleStateExit();
881
+ this.conn.removeDataListener(this.onHandshakeData);
882
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
883
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
884
+ clearTimeout(this.handshakeTimeout);
885
+ }
886
+ _handleClose() {
887
+ super._handleClose();
888
+ this.conn.close();
889
+ }
890
+ };
891
+
892
+ // transport/sessionStateMachine/SessionConnected.ts
893
+ var import_api2 = require("@opentelemetry/api");
894
+ var SessionConnected = class extends IdentifiedSession {
895
+ state = "Connected" /* Connected */;
896
+ conn;
897
+ listeners;
898
+ heartbeatHandle;
899
+ heartbeatMisses = 0;
900
+ get isActivelyHeartbeating() {
901
+ return this.heartbeatHandle !== void 0;
902
+ }
903
+ updateBookkeeping(ack, seq) {
904
+ this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
905
+ this.ack = seq + 1;
906
+ this.heartbeatMisses = 0;
907
+ }
908
+ send(msg) {
909
+ const constructedMsg = this.constructMsg(msg);
910
+ this.sendBuffer.push(constructedMsg);
911
+ this.conn.send(this.options.codec.toBuffer(constructedMsg));
912
+ return constructedMsg.id;
913
+ }
914
+ constructor(conn, listeners, ...args) {
915
+ super(...args);
916
+ this.conn = conn;
917
+ this.listeners = listeners;
918
+ this.conn.addDataListener(this.onMessageData);
919
+ this.conn.addCloseListener(listeners.onConnectionClosed);
920
+ this.conn.addErrorListener(listeners.onConnectionErrored);
921
+ if (this.sendBuffer.length > 0) {
922
+ this.log?.debug(
923
+ `sending ${this.sendBuffer.length} buffered messages`,
924
+ this.loggingMetadata
925
+ );
926
+ }
927
+ for (const msg of this.sendBuffer) {
928
+ conn.send(this.options.codec.toBuffer(msg));
929
+ }
930
+ }
931
+ startActiveHeartbeat() {
932
+ this.heartbeatHandle = setInterval(() => {
933
+ const misses = this.heartbeatMisses;
934
+ const missDuration = misses * this.options.heartbeatIntervalMs;
935
+ if (misses >= this.options.heartbeatsUntilDead) {
936
+ this.log?.info(
937
+ `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
938
+ this.loggingMetadata
939
+ );
940
+ this.telemetry.span.addEvent("closing connection due to inactivity");
941
+ this.conn.close();
942
+ clearInterval(this.heartbeatHandle);
943
+ this.heartbeatHandle = void 0;
944
+ return;
945
+ }
946
+ this.sendHeartbeat();
947
+ this.heartbeatMisses++;
948
+ }, this.options.heartbeatIntervalMs);
949
+ }
950
+ sendHeartbeat() {
951
+ this.send({
952
+ streamId: "heartbeat",
953
+ controlFlags: 1 /* AckBit */,
954
+ payload: {
955
+ type: "ACK"
956
+ }
957
+ });
958
+ }
959
+ onMessageData = (msg) => {
960
+ const parsedMsg = this.parseMsg(msg);
961
+ if (parsedMsg === null)
962
+ return;
963
+ if (parsedMsg.seq !== this.ack) {
964
+ if (parsedMsg.seq < this.ack) {
965
+ this.log?.debug(
966
+ `received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
967
+ {
968
+ ...this.loggingMetadata,
969
+ transportMessage: parsedMsg
970
+ }
971
+ );
972
+ } else {
973
+ const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
974
+ this.log?.error(reason, {
975
+ ...this.loggingMetadata,
976
+ transportMessage: parsedMsg,
977
+ tags: ["invariant-violation"]
978
+ });
979
+ this.telemetry.span.setStatus({
980
+ code: import_api2.SpanStatusCode.ERROR,
981
+ message: reason
982
+ });
983
+ this.listeners.onInvalidMessage(reason);
984
+ }
985
+ return;
986
+ }
987
+ this.log?.debug(`received msg`, {
988
+ ...this.loggingMetadata,
989
+ transportMessage: parsedMsg
990
+ });
991
+ this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
992
+ if (!isAck(parsedMsg.controlFlags)) {
993
+ this.listeners.onMessage(parsedMsg);
994
+ return;
995
+ }
996
+ this.log?.debug(`discarding msg (ack bit set)`, {
997
+ ...this.loggingMetadata,
998
+ transportMessage: parsedMsg
999
+ });
1000
+ if (!this.isActivelyHeartbeating) {
1001
+ this.sendHeartbeat();
1002
+ }
1003
+ };
1004
+ _handleStateExit() {
1005
+ super._handleStateExit();
1006
+ this.conn.removeDataListener(this.onMessageData);
1007
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
1008
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
1009
+ clearInterval(this.heartbeatHandle);
1010
+ this.heartbeatHandle = void 0;
1011
+ }
1012
+ _handleClose() {
1013
+ super._handleClose();
1014
+ this.conn.close();
1015
+ }
1016
+ };
1017
+
1018
+ // transport/sessionStateMachine/transitions.ts
1019
+ function inheritSharedSession(session) {
1020
+ return [
1021
+ session.id,
1022
+ session.from,
1023
+ session.to,
1024
+ session.seq,
1025
+ session.ack,
1026
+ session.sendBuffer,
1027
+ session.telemetry,
1028
+ session.options,
1029
+ session.protocolVersion,
1030
+ session.log
1031
+ ];
1032
+ }
1033
+ var SessionStateGraph = {
1034
+ entrypoints: {
1035
+ NoConnection(to, from, listeners, options, protocolVersion, log) {
1036
+ const id = `session-${generateId()}`;
1037
+ const telemetry = createSessionTelemetryInfo(id, to, from);
1038
+ const sendBuffer = [];
1039
+ const session = new SessionNoConnection(
1040
+ listeners,
1041
+ id,
1042
+ from,
1043
+ to,
1044
+ 0,
1045
+ 0,
1046
+ sendBuffer,
1047
+ telemetry,
1048
+ options,
1049
+ protocolVersion,
1050
+ log
1051
+ );
1052
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
1053
+ ...session.loggingMetadata,
1054
+ tags: ["state-transition"]
1055
+ });
1056
+ return session;
1057
+ },
1058
+ WaitingForHandshake(from, conn, listeners, options, log) {
1059
+ const session = new SessionWaitingForHandshake(
1060
+ conn,
1061
+ listeners,
1062
+ from,
1063
+ options,
1064
+ log
1065
+ );
1066
+ session.log?.info(`session created in WaitingForHandshake state`, {
1067
+ ...session.loggingMetadata,
1068
+ tags: ["state-transition"]
1069
+ });
1070
+ return session;
1071
+ }
1072
+ },
1073
+ // All of the transitions 'move'/'consume' the old session and return a new one.
1074
+ // After a session is transitioned, any usage of the old session will throw.
1075
+ transition: {
1076
+ // happy path transitions
1077
+ NoConnectionToConnecting(oldSession, connPromise, listeners) {
1078
+ const carriedState = inheritSharedSession(oldSession);
1079
+ oldSession._handleStateExit();
1080
+ const session = new SessionConnecting(
1081
+ connPromise,
1082
+ listeners,
1083
+ ...carriedState
1084
+ );
1085
+ session.log?.info(
1086
+ `session ${session.id} transition from NoConnection to Connecting`,
1087
+ {
1088
+ ...session.loggingMetadata,
1089
+ tags: ["state-transition"]
1090
+ }
1091
+ );
1092
+ return session;
1093
+ },
1094
+ ConnectingToHandshaking(oldSession, conn, listeners) {
1095
+ const carriedState = inheritSharedSession(oldSession);
1096
+ oldSession._handleStateExit();
1097
+ const session = new SessionHandshaking(conn, listeners, ...carriedState);
1098
+ session.log?.info(
1099
+ `session ${session.id} transition from Connecting to Handshaking`,
1100
+ {
1101
+ ...session.loggingMetadata,
1102
+ tags: ["state-transition"]
1103
+ }
1104
+ );
1105
+ return session;
1106
+ },
1107
+ HandshakingToConnected(oldSession, listeners) {
1108
+ const carriedState = inheritSharedSession(oldSession);
1109
+ const conn = oldSession.conn;
1110
+ oldSession._handleStateExit();
1111
+ const session = new SessionConnected(conn, listeners, ...carriedState);
1112
+ session.log?.info(
1113
+ `session ${session.id} transition from Handshaking to Connected`,
1114
+ {
1115
+ ...session.loggingMetadata,
1116
+ tags: ["state-transition"]
1117
+ }
1118
+ );
1119
+ return session;
1120
+ },
1121
+ WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) {
1122
+ const conn = pendingSession.conn;
1123
+ const { from, options } = pendingSession;
1124
+ const carriedState = oldSession ? (
1125
+ // old session exists, inherit state
1126
+ inheritSharedSession(oldSession)
1127
+ ) : (
1128
+ // old session does not exist, create new state
1129
+ [
1130
+ sessionId,
1131
+ from,
1132
+ to,
1133
+ 0,
1134
+ 0,
1135
+ [],
1136
+ createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
1137
+ options,
1138
+ protocolVersion,
1139
+ pendingSession.log
1140
+ ]
1141
+ );
1142
+ pendingSession._handleStateExit();
1143
+ oldSession?._handleStateExit();
1144
+ const session = new SessionConnected(conn, listeners, ...carriedState);
1145
+ session.log?.info(
1146
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
1147
+ {
1148
+ ...session.loggingMetadata,
1149
+ tags: ["state-transition"]
1150
+ }
1151
+ );
1152
+ return session;
1153
+ },
1154
+ // disconnect paths
1155
+ ConnectingToNoConnection(oldSession, listeners) {
1156
+ const carriedState = inheritSharedSession(oldSession);
1157
+ oldSession.bestEffortClose();
1158
+ oldSession._handleStateExit();
1159
+ const session = new SessionNoConnection(listeners, ...carriedState);
1160
+ session.log?.info(
1161
+ `session ${session.id} transition from Connecting to NoConnection`,
1162
+ {
1163
+ ...session.loggingMetadata,
1164
+ tags: ["state-transition"]
1165
+ }
1166
+ );
1167
+ return session;
1168
+ },
1169
+ HandshakingToNoConnection(oldSession, listeners) {
1170
+ const carriedState = inheritSharedSession(oldSession);
1171
+ oldSession.conn.close();
1172
+ oldSession._handleStateExit();
1173
+ const session = new SessionNoConnection(listeners, ...carriedState);
1174
+ session.log?.info(
1175
+ `session ${session.id} transition from Handshaking to NoConnection`,
1176
+ {
1177
+ ...session.loggingMetadata,
1178
+ tags: ["state-transition"]
1179
+ }
1180
+ );
1181
+ return session;
1182
+ },
1183
+ ConnectedToNoConnection(oldSession, listeners) {
1184
+ const carriedState = inheritSharedSession(oldSession);
1185
+ oldSession.conn.close();
1186
+ oldSession._handleStateExit();
1187
+ const session = new SessionNoConnection(listeners, ...carriedState);
1188
+ session.log?.info(
1189
+ `session ${session.id} transition from Connected to NoConnection`,
1190
+ {
1191
+ ...session.loggingMetadata,
1192
+ tags: ["state-transition"]
1193
+ }
1194
+ );
1195
+ return session;
1196
+ }
1197
+ }
1198
+ };
1199
+
782
1200
  // util/testHelpers.ts
783
1201
  function createLocalWebSocketClient(port) {
784
1202
  const sock = new import_ws.default(`ws://localhost:${port}`);
@@ -847,18 +1265,23 @@ function catchProcError(err) {
847
1265
  }
848
1266
  var testingSessionOptions = defaultTransportOptions;
849
1267
  function dummySession() {
850
- return new Session(
851
- void 0,
1268
+ return SessionStateGraph.entrypoints.NoConnection(
852
1269
  "client",
853
1270
  "server",
854
- testingSessionOptions
1271
+ {
1272
+ onSessionGracePeriodElapsed: () => {
1273
+ }
1274
+ },
1275
+ testingSessionOptions,
1276
+ currentProtocolVersion
855
1277
  );
856
1278
  }
857
1279
  function dummyCtx(state, session, extendedContext) {
858
1280
  return {
859
1281
  ...extendedContext,
860
- from: session.from,
861
1282
  state,
1283
+ sessionId: session.id,
1284
+ from: session.from,
862
1285
  metadata: {},
863
1286
  abortController: new AbortController(),
864
1287
  clientAbortSignal: new AbortController().signal,
@@ -936,21 +1359,41 @@ function asClientUpload(state, proc, init, extendedContext, session = dummySessi
936
1359
  return [inputPipe.writer, () => result];
937
1360
  }
938
1361
  var getUnixSocketPath = () => {
939
- return process.platform === "win32" ? `\\\\?\\pipe\\${(0, import_nanoid2.nanoid)()}` : `/tmp/${(0, import_nanoid2.nanoid)()}.sock`;
1362
+ return `/tmp/${(0, import_nanoid2.nanoid)()}.sock`;
940
1363
  };
1364
+ function getTransportConnections(transport) {
1365
+ const connections = [];
1366
+ for (const session of transport.sessions.values()) {
1367
+ if (session.state === "Connected" /* Connected */) {
1368
+ connections.push(session.conn);
1369
+ }
1370
+ }
1371
+ return connections;
1372
+ }
1373
+ function numberOfConnections(transport) {
1374
+ return getTransportConnections(transport).length;
1375
+ }
1376
+ function closeAllConnections(transport) {
1377
+ for (const conn of getTransportConnections(transport)) {
1378
+ conn.close();
1379
+ }
1380
+ }
941
1381
  // Annotate the CommonJS export names for ESM import in node:
942
1382
  0 && (module.exports = {
943
1383
  asClientRpc,
944
1384
  asClientStream,
945
1385
  asClientSubscription,
946
1386
  asClientUpload,
1387
+ closeAllConnections,
947
1388
  createDummyTransportMessage,
948
1389
  createLocalWebSocketClient,
949
1390
  createWebSocketServer,
950
1391
  dummySession,
951
1392
  getIteratorFromStream,
1393
+ getTransportConnections,
952
1394
  getUnixSocketPath,
953
1395
  iterNext,
1396
+ numberOfConnections,
954
1397
  onUdsServeReady,
955
1398
  onWsServerReady,
956
1399
  payloadToTransportMessage,