@replit/river 0.200.5 → 0.201.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 (60) hide show
  1. package/dist/{chunk-Z7Q5CDXD.js → chunk-52DVJUVZ.js} +2 -2
  2. package/dist/{chunk-64J442AK.js → chunk-7IUEEQP7.js} +10 -10
  3. package/dist/chunk-7IUEEQP7.js.map +1 -0
  4. package/dist/{chunk-WN7GEBMX.js → chunk-OZ3ITXW5.js} +5 -7
  5. package/dist/chunk-OZ3ITXW5.js.map +1 -0
  6. package/dist/{chunk-3FMY7VTM.js → chunk-PJ4GJ5CU.js} +332 -3
  7. package/dist/chunk-PJ4GJ5CU.js.map +1 -0
  8. package/dist/{chunk-MSJGPQRT.js → chunk-UBUD2LMZ.js} +4 -6
  9. package/dist/chunk-UBUD2LMZ.js.map +1 -0
  10. package/dist/{client-ba6815ae.d.ts → client-a32692b0.d.ts} +2 -2
  11. package/dist/{connection-d217c989.d.ts → connection-48d021ca.d.ts} +2 -2
  12. package/dist/{context-8d1ed9a1.d.ts → context-8d263a7f.d.ts} +2 -2
  13. package/dist/logging/index.d.cts +1 -1
  14. package/dist/logging/index.d.ts +1 -1
  15. package/dist/{message-45658364.d.ts → message-3def9ded.d.ts} +1 -1
  16. package/dist/router/index.cjs +2 -2
  17. package/dist/router/index.cjs.map +1 -1
  18. package/dist/router/index.d.cts +8 -8
  19. package/dist/router/index.d.ts +8 -8
  20. package/dist/router/index.js +318 -40
  21. package/dist/router/index.js.map +1 -1
  22. package/dist/{server-eb7d2cfd.d.ts → server-48d90a77.d.ts} +2 -2
  23. package/dist/{services-94e0afc3.d.ts → services-e71ea921.d.ts} +4 -4
  24. package/dist/testUtil/index.cjs +2616 -0
  25. package/dist/testUtil/index.cjs.map +1 -0
  26. package/dist/{util/testHelpers.d.ts → testUtil/index.d.cts} +27 -18
  27. package/dist/{util/testHelpers.d.cts → testUtil/index.d.ts} +27 -18
  28. package/dist/testUtil/index.js +370 -0
  29. package/dist/testUtil/index.js.map +1 -0
  30. package/dist/transport/impls/ws/client.cjs +2 -2
  31. package/dist/transport/impls/ws/client.cjs.map +1 -1
  32. package/dist/transport/impls/ws/client.d.cts +4 -4
  33. package/dist/transport/impls/ws/client.d.ts +4 -4
  34. package/dist/transport/impls/ws/client.js +4 -5
  35. package/dist/transport/impls/ws/client.js.map +1 -1
  36. package/dist/transport/impls/ws/server.cjs +2 -2
  37. package/dist/transport/impls/ws/server.cjs.map +1 -1
  38. package/dist/transport/impls/ws/server.d.cts +4 -4
  39. package/dist/transport/impls/ws/server.d.ts +4 -4
  40. package/dist/transport/impls/ws/server.js +4 -5
  41. package/dist/transport/impls/ws/server.js.map +1 -1
  42. package/dist/transport/index.cjs +2 -2
  43. package/dist/transport/index.cjs.map +1 -1
  44. package/dist/transport/index.d.cts +4 -4
  45. package/dist/transport/index.d.ts +4 -4
  46. package/dist/transport/index.js +6 -8
  47. package/package.json +3 -3
  48. package/dist/chunk-3FMY7VTM.js.map +0 -1
  49. package/dist/chunk-64J442AK.js.map +0 -1
  50. package/dist/chunk-7Z5MSOKL.js +0 -340
  51. package/dist/chunk-7Z5MSOKL.js.map +0 -1
  52. package/dist/chunk-MSJGPQRT.js.map +0 -1
  53. package/dist/chunk-WN7GEBMX.js.map +0 -1
  54. package/dist/chunk-ZVWJN6V2.js +0 -307
  55. package/dist/chunk-ZVWJN6V2.js.map +0 -1
  56. package/dist/util/testHelpers.cjs +0 -1563
  57. package/dist/util/testHelpers.cjs.map +0 -1
  58. package/dist/util/testHelpers.js +0 -250
  59. package/dist/util/testHelpers.js.map +0 -1
  60. /package/dist/{chunk-Z7Q5CDXD.js.map → chunk-52DVJUVZ.js.map} +0 -0
@@ -0,0 +1,2616 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // testUtil/index.ts
31
+ var testUtil_exports = {};
32
+ __export(testUtil_exports, {
33
+ InMemoryConnection: () => InMemoryConnection,
34
+ closeAllConnections: () => closeAllConnections,
35
+ createDummyTransportMessage: () => createDummyTransportMessage,
36
+ createLocalWebSocketClient: () => createLocalWebSocketClient,
37
+ createMockTransportNetwork: () => createMockTransportNetwork,
38
+ createWebSocketServer: () => createWebSocketServer,
39
+ dummySession: () => dummySession,
40
+ getClientSendFn: () => getClientSendFn,
41
+ getReadableIterator: () => getReadableIterator,
42
+ getServerSendFn: () => getServerSendFn,
43
+ getTransportConnections: () => getTransportConnections,
44
+ isReadableDone: () => isReadableDone,
45
+ numberOfConnections: () => numberOfConnections,
46
+ onWsServerReady: () => onWsServerReady,
47
+ payloadToTransportMessage: () => payloadToTransportMessage,
48
+ readNextResult: () => readNextResult,
49
+ testingClientSessionOptions: () => testingClientSessionOptions,
50
+ testingSessionOptions: () => testingSessionOptions,
51
+ waitForMessage: () => waitForMessage
52
+ });
53
+ module.exports = __toCommonJS(testUtil_exports);
54
+ var import_ws = __toESM(require("ws"), 1);
55
+
56
+ // transport/message.ts
57
+ var import_typebox = require("@sinclair/typebox");
58
+
59
+ // transport/id.ts
60
+ var import_nanoid = require("nanoid");
61
+ var alphabet = (0, import_nanoid.customAlphabet)(
62
+ "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
63
+ );
64
+ var generateId = () => alphabet(12);
65
+
66
+ // transport/message.ts
67
+ var TransportMessageSchema = (t) => import_typebox.Type.Object({
68
+ id: import_typebox.Type.String(),
69
+ from: import_typebox.Type.String(),
70
+ to: import_typebox.Type.String(),
71
+ seq: import_typebox.Type.Integer(),
72
+ ack: import_typebox.Type.Integer(),
73
+ serviceName: import_typebox.Type.Optional(import_typebox.Type.String()),
74
+ procedureName: import_typebox.Type.Optional(import_typebox.Type.String()),
75
+ streamId: import_typebox.Type.String(),
76
+ controlFlags: import_typebox.Type.Integer(),
77
+ tracing: import_typebox.Type.Optional(
78
+ import_typebox.Type.Object({
79
+ traceparent: import_typebox.Type.String(),
80
+ tracestate: import_typebox.Type.String()
81
+ })
82
+ ),
83
+ payload: t
84
+ });
85
+ var ControlMessageAckSchema = import_typebox.Type.Object({
86
+ type: import_typebox.Type.Literal("ACK")
87
+ });
88
+ var ControlMessageCloseSchema = import_typebox.Type.Object({
89
+ type: import_typebox.Type.Literal("CLOSE")
90
+ });
91
+ var currentProtocolVersion = "v2.0";
92
+ var acceptedProtocolVersions = ["v1.1", currentProtocolVersion];
93
+ function isAcceptedProtocolVersion(version2) {
94
+ return acceptedProtocolVersions.includes(version2);
95
+ }
96
+ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
97
+ type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
98
+ protocolVersion: import_typebox.Type.String(),
99
+ sessionId: import_typebox.Type.String(),
100
+ /**
101
+ * Specifies what the server's expected session state (from the pov of the client). This can be
102
+ * used by the server to know whether this is a new or a reestablished connection, and whether it
103
+ * is compatible with what it already has.
104
+ */
105
+ expectedSessionState: import_typebox.Type.Object({
106
+ // what the client expects the server to send next
107
+ nextExpectedSeq: import_typebox.Type.Integer(),
108
+ nextSentSeq: import_typebox.Type.Integer()
109
+ }),
110
+ metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
111
+ });
112
+ var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
113
+ import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
114
+ ]);
115
+ var HandshakeErrorCustomHandlerFatalResponseCodes = import_typebox.Type.Union([
116
+ // The custom validation handler rejected the handler because the client is unsupported.
117
+ import_typebox.Type.Literal("REJECTED_UNSUPPORTED_CLIENT"),
118
+ // The custom validation handler rejected the handshake.
119
+ import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
120
+ ]);
121
+ var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
122
+ HandshakeErrorCustomHandlerFatalResponseCodes,
123
+ // The ciient sent a handshake that doesn't comply with the extended handshake metadata.
124
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
125
+ // The ciient sent a handshake that doesn't comply with ControlMessageHandshakeRequestSchema.
126
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
127
+ // The client's protocol version does not match the server's.
128
+ import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH")
129
+ ]);
130
+ var HandshakeErrorResponseCodes = import_typebox.Type.Union([
131
+ HandshakeErrorRetriableResponseCodes,
132
+ HandshakeErrorFatalResponseCodes
133
+ ]);
134
+ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
135
+ type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
136
+ status: import_typebox.Type.Union([
137
+ import_typebox.Type.Object({
138
+ ok: import_typebox.Type.Literal(true),
139
+ sessionId: import_typebox.Type.String()
140
+ }),
141
+ import_typebox.Type.Object({
142
+ ok: import_typebox.Type.Literal(false),
143
+ reason: import_typebox.Type.String(),
144
+ code: HandshakeErrorResponseCodes
145
+ })
146
+ ])
147
+ });
148
+ var ControlMessagePayloadSchema = import_typebox.Type.Union([
149
+ ControlMessageCloseSchema,
150
+ ControlMessageAckSchema,
151
+ ControlMessageHandshakeRequestSchema,
152
+ ControlMessageHandshakeResponseSchema
153
+ ]);
154
+ var OpaqueTransportMessageSchema = TransportMessageSchema(
155
+ import_typebox.Type.Unknown()
156
+ );
157
+ function handshakeRequestMessage({
158
+ from,
159
+ to,
160
+ sessionId,
161
+ expectedSessionState,
162
+ metadata,
163
+ tracing
164
+ }) {
165
+ return {
166
+ id: generateId(),
167
+ from,
168
+ to,
169
+ seq: 0,
170
+ ack: 0,
171
+ streamId: generateId(),
172
+ controlFlags: 0,
173
+ tracing,
174
+ payload: {
175
+ type: "HANDSHAKE_REQ",
176
+ protocolVersion: currentProtocolVersion,
177
+ sessionId,
178
+ expectedSessionState,
179
+ metadata
180
+ }
181
+ };
182
+ }
183
+ function handshakeResponseMessage({
184
+ from,
185
+ to,
186
+ status
187
+ }) {
188
+ return {
189
+ id: generateId(),
190
+ from,
191
+ to,
192
+ seq: 0,
193
+ ack: 0,
194
+ streamId: generateId(),
195
+ controlFlags: 0,
196
+ payload: {
197
+ type: "HANDSHAKE_RESP",
198
+ status
199
+ }
200
+ };
201
+ }
202
+ function isAck(controlFlag) {
203
+ return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
204
+ }
205
+
206
+ // codec/json.ts
207
+ var encoder = new TextEncoder();
208
+ var decoder = new TextDecoder();
209
+ function uint8ArrayToBase64(uint8Array) {
210
+ let binary = "";
211
+ uint8Array.forEach((byte) => {
212
+ binary += String.fromCharCode(byte);
213
+ });
214
+ return btoa(binary);
215
+ }
216
+ function base64ToUint8Array(base64) {
217
+ const binaryString = atob(base64);
218
+ const uint8Array = new Uint8Array(binaryString.length);
219
+ for (let i = 0; i < binaryString.length; i++) {
220
+ uint8Array[i] = binaryString.charCodeAt(i);
221
+ }
222
+ return uint8Array;
223
+ }
224
+ var NaiveJsonCodec = {
225
+ toBuffer: (obj) => {
226
+ return encoder.encode(
227
+ JSON.stringify(obj, function replacer(key) {
228
+ const val = this[key];
229
+ if (val instanceof Uint8Array) {
230
+ return { $t: uint8ArrayToBase64(val) };
231
+ } else {
232
+ return val;
233
+ }
234
+ })
235
+ );
236
+ },
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
+ }
247
+ }
248
+ );
249
+ if (typeof parsed === "object")
250
+ return parsed;
251
+ return null;
252
+ } catch {
253
+ return null;
254
+ }
255
+ }
256
+ };
257
+
258
+ // transport/options.ts
259
+ var defaultTransportOptions = {
260
+ heartbeatIntervalMs: 1e3,
261
+ heartbeatsUntilDead: 2,
262
+ sessionDisconnectGraceMs: 5e3,
263
+ connectionTimeoutMs: 2e3,
264
+ handshakeTimeoutMs: 1e3,
265
+ enableTransparentSessionReconnects: true,
266
+ codec: NaiveJsonCodec
267
+ };
268
+ var defaultConnectionRetryOptions = {
269
+ baseIntervalMs: 150,
270
+ maxJitterMs: 200,
271
+ maxBackoffMs: 32e3,
272
+ attemptBudgetCapacity: 5,
273
+ budgetRestoreIntervalMs: 200
274
+ };
275
+ var defaultClientTransportOptions = {
276
+ ...defaultTransportOptions,
277
+ ...defaultConnectionRetryOptions
278
+ };
279
+ var defaultServerTransportOptions = {
280
+ ...defaultTransportOptions
281
+ };
282
+
283
+ // transport/sessionStateMachine/common.ts
284
+ var import_value = require("@sinclair/typebox/value");
285
+ var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
286
+ var StateMachineState = class {
287
+ /*
288
+ * Whether this state has been consumed
289
+ * and we've moved on to another state
290
+ */
291
+ _isConsumed;
292
+ close() {
293
+ this._handleClose();
294
+ }
295
+ constructor() {
296
+ this._isConsumed = false;
297
+ return new Proxy(this, {
298
+ get(target, prop) {
299
+ if (prop === "_isConsumed" || prop === "id" || prop === "state") {
300
+ return Reflect.get(target, prop);
301
+ }
302
+ if (prop === "_handleStateExit") {
303
+ return () => {
304
+ target._isConsumed = true;
305
+ target._handleStateExit();
306
+ };
307
+ }
308
+ if (prop === "_handleClose") {
309
+ return () => {
310
+ target._isConsumed = true;
311
+ target._handleStateExit();
312
+ target._handleClose();
313
+ };
314
+ }
315
+ if (target._isConsumed) {
316
+ throw new Error(
317
+ `${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
318
+ );
319
+ }
320
+ return Reflect.get(target, prop);
321
+ },
322
+ set(target, prop, value) {
323
+ if (target._isConsumed) {
324
+ throw new Error(
325
+ `${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
326
+ );
327
+ }
328
+ return Reflect.set(target, prop, value);
329
+ }
330
+ });
331
+ }
332
+ };
333
+ var CommonSession = class extends StateMachineState {
334
+ from;
335
+ options;
336
+ log;
337
+ constructor({ from, options, log }) {
338
+ super();
339
+ this.from = from;
340
+ this.options = options;
341
+ this.log = log;
342
+ }
343
+ parseMsg(msg) {
344
+ const parsedMsg = this.options.codec.fromBuffer(msg);
345
+ if (parsedMsg === null) {
346
+ const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
347
+ this.log?.error(
348
+ `received malformed msg: ${decodedBuffer}`,
349
+ this.loggingMetadata
350
+ );
351
+ return null;
352
+ }
353
+ if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
354
+ this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
355
+ ...this.loggingMetadata,
356
+ validationErrors: [
357
+ ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
358
+ ]
359
+ });
360
+ return null;
361
+ }
362
+ return parsedMsg;
363
+ }
364
+ };
365
+ var IdentifiedSession = class extends CommonSession {
366
+ id;
367
+ telemetry;
368
+ to;
369
+ protocolVersion;
370
+ /**
371
+ * Index of the message we will send next (excluding handshake)
372
+ */
373
+ seq;
374
+ /**
375
+ * Number of unique messages we've received this session (excluding handshake)
376
+ */
377
+ ack;
378
+ sendBuffer;
379
+ constructor(props) {
380
+ const { id, to, seq, ack, sendBuffer, telemetry, log, protocolVersion } = props;
381
+ super(props);
382
+ this.id = id;
383
+ this.to = to;
384
+ this.seq = seq;
385
+ this.ack = ack;
386
+ this.sendBuffer = sendBuffer;
387
+ this.telemetry = telemetry;
388
+ this.log = log;
389
+ this.protocolVersion = protocolVersion;
390
+ }
391
+ get loggingMetadata() {
392
+ const spanContext = this.telemetry.span.spanContext();
393
+ const metadata = {
394
+ clientId: this.from,
395
+ connectedTo: this.to,
396
+ sessionId: this.id
397
+ };
398
+ if (this.telemetry.span.isRecording()) {
399
+ metadata.telemetry = {
400
+ traceId: spanContext.traceId,
401
+ spanId: spanContext.spanId
402
+ };
403
+ }
404
+ return metadata;
405
+ }
406
+ constructMsg(partialMsg) {
407
+ const msg = {
408
+ ...partialMsg,
409
+ id: generateId(),
410
+ to: this.to,
411
+ from: this.from,
412
+ seq: this.seq,
413
+ ack: this.ack
414
+ };
415
+ this.seq++;
416
+ return msg;
417
+ }
418
+ nextSeq() {
419
+ return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
420
+ }
421
+ send(msg) {
422
+ const constructedMsg = this.constructMsg(msg);
423
+ this.sendBuffer.push(constructedMsg);
424
+ return constructedMsg.id;
425
+ }
426
+ _handleStateExit() {
427
+ }
428
+ _handleClose() {
429
+ this.sendBuffer.length = 0;
430
+ this.telemetry.span.end();
431
+ }
432
+ };
433
+ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
434
+ graceExpiryTime;
435
+ gracePeriodTimeout;
436
+ listeners;
437
+ constructor(props) {
438
+ super(props);
439
+ this.listeners = props.listeners;
440
+ this.graceExpiryTime = props.graceExpiryTime;
441
+ this.gracePeriodTimeout = setTimeout(() => {
442
+ this.listeners.onSessionGracePeriodElapsed();
443
+ }, this.graceExpiryTime - Date.now());
444
+ }
445
+ _handleStateExit() {
446
+ super._handleStateExit();
447
+ if (this.gracePeriodTimeout) {
448
+ clearTimeout(this.gracePeriodTimeout);
449
+ this.gracePeriodTimeout = void 0;
450
+ }
451
+ }
452
+ _handleClose() {
453
+ super._handleClose();
454
+ }
455
+ };
456
+
457
+ // transport/sessionStateMachine/SessionConnecting.ts
458
+ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
459
+ state = "Connecting" /* Connecting */;
460
+ connPromise;
461
+ listeners;
462
+ connectionTimeout;
463
+ constructor(props) {
464
+ super(props);
465
+ this.connPromise = props.connPromise;
466
+ this.listeners = props.listeners;
467
+ this.connPromise.then(
468
+ (conn) => {
469
+ if (this._isConsumed)
470
+ return;
471
+ this.listeners.onConnectionEstablished(conn);
472
+ },
473
+ (err) => {
474
+ if (this._isConsumed)
475
+ return;
476
+ this.listeners.onConnectionFailed(err);
477
+ }
478
+ );
479
+ this.connectionTimeout = setTimeout(() => {
480
+ this.listeners.onConnectionTimeout();
481
+ }, this.options.connectionTimeoutMs);
482
+ }
483
+ // close a pending connection if it resolves, ignore errors if the promise
484
+ // ends up rejected anyways
485
+ bestEffortClose() {
486
+ const logger = this.log;
487
+ const metadata = this.loggingMetadata;
488
+ this.connPromise.then((conn) => {
489
+ conn.close();
490
+ logger?.info(
491
+ "connection eventually resolved but session has transitioned, closed connection",
492
+ {
493
+ ...metadata,
494
+ ...conn.loggingMetadata
495
+ }
496
+ );
497
+ }).catch(() => {
498
+ });
499
+ }
500
+ _handleStateExit() {
501
+ super._handleStateExit();
502
+ if (this.connectionTimeout) {
503
+ clearTimeout(this.connectionTimeout);
504
+ this.connectionTimeout = void 0;
505
+ }
506
+ }
507
+ _handleClose() {
508
+ this.bestEffortClose();
509
+ super._handleClose();
510
+ }
511
+ };
512
+
513
+ // transport/sessionStateMachine/SessionNoConnection.ts
514
+ var SessionNoConnection = class extends IdentifiedSessionWithGracePeriod {
515
+ state = "NoConnection" /* NoConnection */;
516
+ _handleClose() {
517
+ super._handleClose();
518
+ }
519
+ _handleStateExit() {
520
+ super._handleStateExit();
521
+ }
522
+ };
523
+
524
+ // tracing/index.ts
525
+ var import_api = require("@opentelemetry/api");
526
+
527
+ // package.json
528
+ var version = "0.201.0";
529
+
530
+ // tracing/index.ts
531
+ function getPropagationContext(ctx) {
532
+ const tracing = {
533
+ traceparent: "",
534
+ tracestate: ""
535
+ };
536
+ import_api.propagation.inject(ctx, tracing);
537
+ return tracing;
538
+ }
539
+ function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
540
+ const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
541
+ const span = tracer.startSpan(
542
+ `session ${sessionId}`,
543
+ {
544
+ attributes: {
545
+ component: "river",
546
+ "river.session.id": sessionId,
547
+ "river.session.to": to,
548
+ "river.session.from": from
549
+ }
550
+ },
551
+ parentCtx
552
+ );
553
+ const ctx = import_api.trace.setSpan(parentCtx, span);
554
+ return { span, ctx };
555
+ }
556
+ var tracer = import_api.trace.getTracer("river", version);
557
+ var tracing_default = tracer;
558
+
559
+ // transport/sessionStateMachine/SessionWaitingForHandshake.ts
560
+ var SessionWaitingForHandshake = class extends CommonSession {
561
+ state = "WaitingForHandshake" /* WaitingForHandshake */;
562
+ conn;
563
+ listeners;
564
+ handshakeTimeout;
565
+ constructor(props) {
566
+ super(props);
567
+ this.conn = props.conn;
568
+ this.listeners = props.listeners;
569
+ this.handshakeTimeout = setTimeout(() => {
570
+ this.listeners.onHandshakeTimeout();
571
+ }, this.options.handshakeTimeoutMs);
572
+ this.conn.addDataListener(this.onHandshakeData);
573
+ this.conn.addErrorListener(this.listeners.onConnectionErrored);
574
+ this.conn.addCloseListener(this.listeners.onConnectionClosed);
575
+ }
576
+ get loggingMetadata() {
577
+ return {
578
+ clientId: this.from,
579
+ connId: this.conn.id,
580
+ ...this.conn.loggingMetadata
581
+ };
582
+ }
583
+ onHandshakeData = (msg) => {
584
+ const parsedMsg = this.parseMsg(msg);
585
+ if (parsedMsg === null) {
586
+ this.listeners.onInvalidHandshake(
587
+ "could not parse message",
588
+ "MALFORMED_HANDSHAKE"
589
+ );
590
+ return;
591
+ }
592
+ this.listeners.onHandshake(parsedMsg);
593
+ };
594
+ sendHandshake(msg) {
595
+ return this.conn.send(this.options.codec.toBuffer(msg));
596
+ }
597
+ _handleStateExit() {
598
+ this.conn.removeDataListener(this.onHandshakeData);
599
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
600
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
601
+ clearTimeout(this.handshakeTimeout);
602
+ this.handshakeTimeout = void 0;
603
+ }
604
+ _handleClose() {
605
+ this.conn.close();
606
+ }
607
+ };
608
+
609
+ // transport/sessionStateMachine/SessionHandshaking.ts
610
+ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
611
+ state = "Handshaking" /* Handshaking */;
612
+ conn;
613
+ listeners;
614
+ handshakeTimeout;
615
+ constructor(props) {
616
+ super(props);
617
+ this.conn = props.conn;
618
+ this.listeners = props.listeners;
619
+ this.handshakeTimeout = setTimeout(() => {
620
+ this.listeners.onHandshakeTimeout();
621
+ }, this.options.handshakeTimeoutMs);
622
+ this.conn.addDataListener(this.onHandshakeData);
623
+ this.conn.addErrorListener(this.listeners.onConnectionErrored);
624
+ this.conn.addCloseListener(this.listeners.onConnectionClosed);
625
+ }
626
+ get loggingMetadata() {
627
+ return {
628
+ ...super.loggingMetadata,
629
+ ...this.conn.loggingMetadata
630
+ };
631
+ }
632
+ onHandshakeData = (msg) => {
633
+ const parsedMsg = this.parseMsg(msg);
634
+ if (parsedMsg === null) {
635
+ this.listeners.onInvalidHandshake(
636
+ "could not parse message",
637
+ "MALFORMED_HANDSHAKE"
638
+ );
639
+ return;
640
+ }
641
+ this.listeners.onHandshake(parsedMsg);
642
+ };
643
+ sendHandshake(msg) {
644
+ return this.conn.send(this.options.codec.toBuffer(msg));
645
+ }
646
+ _handleStateExit() {
647
+ super._handleStateExit();
648
+ this.conn.removeDataListener(this.onHandshakeData);
649
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
650
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
651
+ if (this.handshakeTimeout) {
652
+ clearTimeout(this.handshakeTimeout);
653
+ this.handshakeTimeout = void 0;
654
+ }
655
+ }
656
+ _handleClose() {
657
+ super._handleClose();
658
+ this.conn.close();
659
+ }
660
+ };
661
+
662
+ // transport/sessionStateMachine/SessionConnected.ts
663
+ var import_api2 = require("@opentelemetry/api");
664
+ var SessionConnected = class extends IdentifiedSession {
665
+ state = "Connected" /* Connected */;
666
+ conn;
667
+ listeners;
668
+ heartbeatHandle;
669
+ heartbeatMisses = 0;
670
+ isActivelyHeartbeating;
671
+ updateBookkeeping(ack, seq) {
672
+ this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
673
+ this.ack = seq + 1;
674
+ this.heartbeatMisses = 0;
675
+ }
676
+ send(msg) {
677
+ const constructedMsg = this.constructMsg(msg);
678
+ this.sendBuffer.push(constructedMsg);
679
+ this.conn.send(this.options.codec.toBuffer(constructedMsg));
680
+ return constructedMsg.id;
681
+ }
682
+ constructor(props) {
683
+ super(props);
684
+ this.conn = props.conn;
685
+ this.listeners = props.listeners;
686
+ this.conn.addDataListener(this.onMessageData);
687
+ this.conn.addCloseListener(this.listeners.onConnectionClosed);
688
+ this.conn.addErrorListener(this.listeners.onConnectionErrored);
689
+ if (this.sendBuffer.length > 0) {
690
+ this.log?.info(
691
+ `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
692
+ this.loggingMetadata
693
+ );
694
+ for (const msg of this.sendBuffer) {
695
+ this.conn.send(this.options.codec.toBuffer(msg));
696
+ }
697
+ }
698
+ this.isActivelyHeartbeating = false;
699
+ this.heartbeatHandle = setInterval(() => {
700
+ const misses = this.heartbeatMisses;
701
+ const missDuration = misses * this.options.heartbeatIntervalMs;
702
+ if (misses >= this.options.heartbeatsUntilDead) {
703
+ this.log?.info(
704
+ `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
705
+ this.loggingMetadata
706
+ );
707
+ this.telemetry.span.addEvent("closing connection due to inactivity");
708
+ this.conn.close();
709
+ clearInterval(this.heartbeatHandle);
710
+ this.heartbeatHandle = void 0;
711
+ return;
712
+ }
713
+ if (this.isActivelyHeartbeating) {
714
+ this.sendHeartbeat();
715
+ }
716
+ this.heartbeatMisses++;
717
+ }, this.options.heartbeatIntervalMs);
718
+ }
719
+ get loggingMetadata() {
720
+ return {
721
+ ...super.loggingMetadata,
722
+ ...this.conn.loggingMetadata
723
+ };
724
+ }
725
+ startActiveHeartbeat() {
726
+ this.isActivelyHeartbeating = true;
727
+ }
728
+ sendHeartbeat() {
729
+ this.log?.debug("sending heartbeat", this.loggingMetadata);
730
+ this.send({
731
+ streamId: "heartbeat",
732
+ controlFlags: 1 /* AckBit */,
733
+ payload: {
734
+ type: "ACK"
735
+ }
736
+ });
737
+ }
738
+ closeConnection() {
739
+ this.conn.removeDataListener(this.onMessageData);
740
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
741
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
742
+ this.conn.close();
743
+ }
744
+ onMessageData = (msg) => {
745
+ const parsedMsg = this.parseMsg(msg);
746
+ if (parsedMsg === null) {
747
+ this.listeners.onInvalidMessage("could not parse message");
748
+ return;
749
+ }
750
+ if (parsedMsg.seq !== this.ack) {
751
+ if (parsedMsg.seq < this.ack) {
752
+ this.log?.debug(
753
+ `received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
754
+ {
755
+ ...this.loggingMetadata,
756
+ transportMessage: parsedMsg
757
+ }
758
+ );
759
+ } else {
760
+ const reason = `received out-of-order msg, closing connection (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
761
+ this.log?.warn(reason, {
762
+ ...this.loggingMetadata,
763
+ transportMessage: parsedMsg,
764
+ tags: ["invariant-violation"]
765
+ });
766
+ this.telemetry.span.setStatus({
767
+ code: import_api2.SpanStatusCode.ERROR,
768
+ message: reason
769
+ });
770
+ this.closeConnection();
771
+ }
772
+ return;
773
+ }
774
+ this.log?.debug(`received msg`, {
775
+ ...this.loggingMetadata,
776
+ transportMessage: parsedMsg
777
+ });
778
+ this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
779
+ if (!isAck(parsedMsg.controlFlags)) {
780
+ this.listeners.onMessage(parsedMsg);
781
+ return;
782
+ }
783
+ this.log?.debug(`discarding msg (ack bit set)`, {
784
+ ...this.loggingMetadata,
785
+ transportMessage: parsedMsg
786
+ });
787
+ if (!this.isActivelyHeartbeating) {
788
+ this.sendHeartbeat();
789
+ }
790
+ };
791
+ _handleStateExit() {
792
+ super._handleStateExit();
793
+ this.conn.removeDataListener(this.onMessageData);
794
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
795
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
796
+ if (this.heartbeatHandle) {
797
+ clearInterval(this.heartbeatHandle);
798
+ this.heartbeatHandle = void 0;
799
+ }
800
+ }
801
+ _handleClose() {
802
+ super._handleClose();
803
+ this.conn.close();
804
+ }
805
+ };
806
+
807
+ // transport/sessionStateMachine/SessionBackingOff.ts
808
+ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
809
+ state = "BackingOff" /* BackingOff */;
810
+ listeners;
811
+ backoffTimeout;
812
+ constructor(props) {
813
+ super(props);
814
+ this.listeners = props.listeners;
815
+ this.backoffTimeout = setTimeout(() => {
816
+ this.listeners.onBackoffFinished();
817
+ }, props.backoffMs);
818
+ }
819
+ _handleClose() {
820
+ super._handleClose();
821
+ }
822
+ _handleStateExit() {
823
+ super._handleStateExit();
824
+ if (this.backoffTimeout) {
825
+ clearTimeout(this.backoffTimeout);
826
+ this.backoffTimeout = void 0;
827
+ }
828
+ }
829
+ };
830
+
831
+ // transport/sessionStateMachine/transitions.ts
832
+ function inheritSharedSession(session) {
833
+ return {
834
+ id: session.id,
835
+ from: session.from,
836
+ to: session.to,
837
+ seq: session.seq,
838
+ ack: session.ack,
839
+ sendBuffer: session.sendBuffer,
840
+ telemetry: session.telemetry,
841
+ options: session.options,
842
+ log: session.log,
843
+ protocolVersion: session.protocolVersion
844
+ };
845
+ }
846
+ function inheritSharedSessionWithGrace(session) {
847
+ return {
848
+ ...inheritSharedSession(session),
849
+ graceExpiryTime: session.graceExpiryTime
850
+ };
851
+ }
852
+ var SessionStateGraph = {
853
+ entrypoints: {
854
+ NoConnection: (to, from, listeners, options, protocolVersion, log) => {
855
+ const id = `session-${generateId()}`;
856
+ const telemetry = createSessionTelemetryInfo(id, to, from);
857
+ const sendBuffer = [];
858
+ const session = new SessionNoConnection({
859
+ listeners,
860
+ id,
861
+ from,
862
+ to,
863
+ seq: 0,
864
+ ack: 0,
865
+ graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
866
+ sendBuffer,
867
+ telemetry,
868
+ options,
869
+ protocolVersion,
870
+ log
871
+ });
872
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
873
+ ...session.loggingMetadata,
874
+ tags: ["state-transition"]
875
+ });
876
+ return session;
877
+ },
878
+ WaitingForHandshake: (from, conn, listeners, options, log) => {
879
+ const session = new SessionWaitingForHandshake({
880
+ conn,
881
+ listeners,
882
+ from,
883
+ options,
884
+ log
885
+ });
886
+ session.log?.info(`session created in WaitingForHandshake state`, {
887
+ ...session.loggingMetadata,
888
+ tags: ["state-transition"]
889
+ });
890
+ return session;
891
+ }
892
+ },
893
+ // All of the transitions 'move'/'consume' the old session and return a new one.
894
+ // After a session is transitioned, any usage of the old session will throw.
895
+ transition: {
896
+ // happy path transitions
897
+ NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
898
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
899
+ oldSession._handleStateExit();
900
+ const session = new SessionBackingOff({
901
+ backoffMs,
902
+ listeners,
903
+ ...carriedState
904
+ });
905
+ session.log?.info(
906
+ `session ${session.id} transition from NoConnection to BackingOff`,
907
+ {
908
+ ...session.loggingMetadata,
909
+ tags: ["state-transition"]
910
+ }
911
+ );
912
+ return session;
913
+ },
914
+ BackingOffToConnecting: (oldSession, connPromise, listeners) => {
915
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
916
+ oldSession._handleStateExit();
917
+ const session = new SessionConnecting({
918
+ connPromise,
919
+ listeners,
920
+ ...carriedState
921
+ });
922
+ session.log?.info(
923
+ `session ${session.id} transition from BackingOff to Connecting`,
924
+ {
925
+ ...session.loggingMetadata,
926
+ tags: ["state-transition"]
927
+ }
928
+ );
929
+ return session;
930
+ },
931
+ ConnectingToHandshaking: (oldSession, conn, listeners) => {
932
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
933
+ oldSession._handleStateExit();
934
+ const session = new SessionHandshaking({
935
+ conn,
936
+ listeners,
937
+ ...carriedState
938
+ });
939
+ session.log?.info(
940
+ `session ${session.id} transition from Connecting to Handshaking`,
941
+ {
942
+ ...session.loggingMetadata,
943
+ tags: ["state-transition"]
944
+ }
945
+ );
946
+ return session;
947
+ },
948
+ HandshakingToConnected: (oldSession, listeners) => {
949
+ const carriedState = inheritSharedSession(oldSession);
950
+ const conn = oldSession.conn;
951
+ oldSession._handleStateExit();
952
+ const session = new SessionConnected({
953
+ conn,
954
+ listeners,
955
+ ...carriedState
956
+ });
957
+ session.log?.info(
958
+ `session ${session.id} transition from Handshaking to Connected`,
959
+ {
960
+ ...session.loggingMetadata,
961
+ tags: ["state-transition"]
962
+ }
963
+ );
964
+ return session;
965
+ },
966
+ WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
967
+ const conn = pendingSession.conn;
968
+ const { from, options } = pendingSession;
969
+ const carriedState = oldSession ? (
970
+ // old session exists, inherit state
971
+ inheritSharedSession(oldSession)
972
+ ) : (
973
+ // old session does not exist, create new state
974
+ {
975
+ id: sessionId,
976
+ from,
977
+ to,
978
+ seq: 0,
979
+ ack: 0,
980
+ sendBuffer: [],
981
+ telemetry: createSessionTelemetryInfo(
982
+ sessionId,
983
+ to,
984
+ from,
985
+ propagationCtx
986
+ ),
987
+ options,
988
+ log: pendingSession.log,
989
+ protocolVersion
990
+ }
991
+ );
992
+ pendingSession._handleStateExit();
993
+ oldSession?._handleStateExit();
994
+ const session = new SessionConnected({
995
+ conn,
996
+ listeners,
997
+ ...carriedState
998
+ });
999
+ session.log?.info(
1000
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
1001
+ {
1002
+ ...session.loggingMetadata,
1003
+ tags: ["state-transition"]
1004
+ }
1005
+ );
1006
+ return session;
1007
+ },
1008
+ // disconnect paths
1009
+ BackingOffToNoConnection: (oldSession, listeners) => {
1010
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
1011
+ oldSession._handleStateExit();
1012
+ const session = new SessionNoConnection({
1013
+ listeners,
1014
+ ...carriedState
1015
+ });
1016
+ session.log?.info(
1017
+ `session ${session.id} transition from BackingOff to NoConnection`,
1018
+ {
1019
+ ...session.loggingMetadata,
1020
+ tags: ["state-transition"]
1021
+ }
1022
+ );
1023
+ return session;
1024
+ },
1025
+ ConnectingToNoConnection: (oldSession, listeners) => {
1026
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
1027
+ oldSession.bestEffortClose();
1028
+ oldSession._handleStateExit();
1029
+ const session = new SessionNoConnection({
1030
+ listeners,
1031
+ ...carriedState
1032
+ });
1033
+ session.log?.info(
1034
+ `session ${session.id} transition from Connecting to NoConnection`,
1035
+ {
1036
+ ...session.loggingMetadata,
1037
+ tags: ["state-transition"]
1038
+ }
1039
+ );
1040
+ return session;
1041
+ },
1042
+ HandshakingToNoConnection: (oldSession, listeners) => {
1043
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
1044
+ oldSession.conn.close();
1045
+ oldSession._handleStateExit();
1046
+ const session = new SessionNoConnection({
1047
+ listeners,
1048
+ ...carriedState
1049
+ });
1050
+ session.log?.info(
1051
+ `session ${session.id} transition from Handshaking to NoConnection`,
1052
+ {
1053
+ ...session.loggingMetadata,
1054
+ tags: ["state-transition"]
1055
+ }
1056
+ );
1057
+ return session;
1058
+ },
1059
+ ConnectedToNoConnection: (oldSession, listeners) => {
1060
+ const carriedState = inheritSharedSession(oldSession);
1061
+ const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
1062
+ oldSession.conn.close();
1063
+ oldSession._handleStateExit();
1064
+ const session = new SessionNoConnection({
1065
+ listeners,
1066
+ graceExpiryTime,
1067
+ ...carriedState
1068
+ });
1069
+ session.log?.info(
1070
+ `session ${session.id} transition from Connected to NoConnection`,
1071
+ {
1072
+ ...session.loggingMetadata,
1073
+ tags: ["state-transition"]
1074
+ }
1075
+ );
1076
+ return session;
1077
+ }
1078
+ }
1079
+ };
1080
+ var transitions = SessionStateGraph.transition;
1081
+ var ClientSessionStateGraph = {
1082
+ entrypoint: SessionStateGraph.entrypoints.NoConnection,
1083
+ transition: {
1084
+ // happy paths
1085
+ // NoConnection -> BackingOff: attempt to connect
1086
+ NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
1087
+ // BackingOff -> Connecting: backoff period elapsed, start connection
1088
+ BackingOffToConnecting: transitions.BackingOffToConnecting,
1089
+ // Connecting -> Handshaking: connection established, start handshake
1090
+ ConnectingToHandshaking: transitions.ConnectingToHandshaking,
1091
+ // Handshaking -> Connected: handshake complete, session ready
1092
+ HandshakingToConnected: transitions.HandshakingToConnected,
1093
+ // disconnect paths
1094
+ // BackingOff -> NoConnection: unused
1095
+ BackingOffToNoConnection: transitions.BackingOffToNoConnection,
1096
+ // Connecting -> NoConnection: connection failed or connection timeout
1097
+ ConnectingToNoConnection: transitions.ConnectingToNoConnection,
1098
+ // Handshaking -> NoConnection: connection closed or handshake timeout
1099
+ HandshakingToNoConnection: transitions.HandshakingToNoConnection,
1100
+ // Connected -> NoConnection: connection closed
1101
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
1102
+ // destroy/close paths
1103
+ // NoConnection -> x: grace period elapsed
1104
+ // BackingOff -> x: grace period elapsed
1105
+ // Connecting -> x: grace period elapsed
1106
+ // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
1107
+ // Connected -> x: grace period elapsed or invalid message
1108
+ }
1109
+ };
1110
+ var ServerSessionStateGraph = {
1111
+ entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
1112
+ transition: {
1113
+ // happy paths
1114
+ // WaitingForHandshake -> Connected: handshake complete, session ready
1115
+ WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
1116
+ // disconnect paths
1117
+ // Connected -> NoConnection: connection closed
1118
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
1119
+ // destroy/close paths
1120
+ // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
1121
+ }
1122
+ };
1123
+
1124
+ // transport/client.ts
1125
+ var import_api3 = require("@opentelemetry/api");
1126
+
1127
+ // transport/rateLimit.ts
1128
+ var LeakyBucketRateLimit = class {
1129
+ budgetConsumed;
1130
+ intervalHandle;
1131
+ options;
1132
+ constructor(options) {
1133
+ this.options = options;
1134
+ this.budgetConsumed = 0;
1135
+ }
1136
+ getBackoffMs() {
1137
+ if (this.getBudgetConsumed() === 0) {
1138
+ return 0;
1139
+ }
1140
+ const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1141
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1142
+ const backoffMs = Math.min(
1143
+ this.options.baseIntervalMs * 2 ** exponent,
1144
+ this.options.maxBackoffMs
1145
+ );
1146
+ return backoffMs + jitter;
1147
+ }
1148
+ get totalBudgetRestoreTime() {
1149
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1150
+ }
1151
+ consumeBudget() {
1152
+ this.stopLeak();
1153
+ this.budgetConsumed = this.getBudgetConsumed() + 1;
1154
+ }
1155
+ getBudgetConsumed() {
1156
+ return this.budgetConsumed;
1157
+ }
1158
+ hasBudget() {
1159
+ return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1160
+ }
1161
+ startRestoringBudget() {
1162
+ if (this.intervalHandle) {
1163
+ return;
1164
+ }
1165
+ const restoreBudgetForUser = () => {
1166
+ const currentBudget = this.budgetConsumed;
1167
+ if (!currentBudget) {
1168
+ this.stopLeak();
1169
+ return;
1170
+ }
1171
+ const newBudget = currentBudget - 1;
1172
+ if (newBudget === 0) {
1173
+ return;
1174
+ }
1175
+ this.budgetConsumed = newBudget;
1176
+ };
1177
+ this.intervalHandle = setInterval(
1178
+ restoreBudgetForUser,
1179
+ this.options.budgetRestoreIntervalMs
1180
+ );
1181
+ }
1182
+ stopLeak() {
1183
+ if (!this.intervalHandle) {
1184
+ return;
1185
+ }
1186
+ clearInterval(this.intervalHandle);
1187
+ this.intervalHandle = void 0;
1188
+ }
1189
+ close() {
1190
+ this.stopLeak();
1191
+ }
1192
+ };
1193
+
1194
+ // logging/log.ts
1195
+ var LoggingLevels = {
1196
+ debug: -1,
1197
+ info: 0,
1198
+ warn: 1,
1199
+ error: 2
1200
+ };
1201
+ var cleanedLogFn = (log) => {
1202
+ return (msg, metadata) => {
1203
+ if (!metadata?.transportMessage) {
1204
+ log(msg, metadata);
1205
+ return;
1206
+ }
1207
+ const { payload, ...rest } = metadata.transportMessage;
1208
+ metadata.transportMessage = rest;
1209
+ log(msg, metadata);
1210
+ };
1211
+ };
1212
+ var BaseLogger = class {
1213
+ minLevel;
1214
+ output;
1215
+ constructor(output, minLevel = "info") {
1216
+ this.minLevel = minLevel;
1217
+ this.output = output;
1218
+ }
1219
+ debug(msg, metadata) {
1220
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
1221
+ this.output(msg, metadata ?? {}, "debug");
1222
+ }
1223
+ }
1224
+ info(msg, metadata) {
1225
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
1226
+ this.output(msg, metadata ?? {}, "info");
1227
+ }
1228
+ }
1229
+ warn(msg, metadata) {
1230
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
1231
+ this.output(msg, metadata ?? {}, "warn");
1232
+ }
1233
+ }
1234
+ error(msg, metadata) {
1235
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
1236
+ this.output(msg, metadata ?? {}, "error");
1237
+ }
1238
+ }
1239
+ };
1240
+ var createLogProxy = (log) => ({
1241
+ debug: cleanedLogFn(log.debug.bind(log)),
1242
+ info: cleanedLogFn(log.info.bind(log)),
1243
+ warn: cleanedLogFn(log.warn.bind(log)),
1244
+ error: cleanedLogFn(log.error.bind(log))
1245
+ });
1246
+
1247
+ // transport/events.ts
1248
+ var ProtocolError = {
1249
+ RetriesExceeded: "conn_retry_exceeded",
1250
+ HandshakeFailed: "handshake_failed",
1251
+ MessageOrderingViolated: "message_ordering_violated",
1252
+ InvalidMessage: "invalid_message"
1253
+ };
1254
+ var EventDispatcher = class {
1255
+ eventListeners = {};
1256
+ removeAllListeners() {
1257
+ this.eventListeners = {};
1258
+ }
1259
+ numberOfListeners(eventType) {
1260
+ return this.eventListeners[eventType]?.size ?? 0;
1261
+ }
1262
+ addEventListener(eventType, handler) {
1263
+ if (!this.eventListeners[eventType]) {
1264
+ this.eventListeners[eventType] = /* @__PURE__ */ new Set();
1265
+ }
1266
+ this.eventListeners[eventType]?.add(handler);
1267
+ }
1268
+ removeEventListener(eventType, handler) {
1269
+ const handlers = this.eventListeners[eventType];
1270
+ if (handlers) {
1271
+ this.eventListeners[eventType]?.delete(handler);
1272
+ }
1273
+ }
1274
+ dispatchEvent(eventType, event) {
1275
+ const handlers = this.eventListeners[eventType];
1276
+ if (handlers) {
1277
+ const copy = [...handlers];
1278
+ for (const handler of copy) {
1279
+ handler(event);
1280
+ }
1281
+ }
1282
+ }
1283
+ };
1284
+
1285
+ // transport/transport.ts
1286
+ var Transport = class {
1287
+ /**
1288
+ * The status of the transport.
1289
+ */
1290
+ status;
1291
+ /**
1292
+ * The client ID of this transport.
1293
+ */
1294
+ clientId;
1295
+ /**
1296
+ * The event dispatcher for handling events of type EventTypes.
1297
+ */
1298
+ eventDispatcher;
1299
+ /**
1300
+ * The options for this transport.
1301
+ */
1302
+ options;
1303
+ log;
1304
+ sessions;
1305
+ /**
1306
+ * Creates a new Transport instance.
1307
+ * @param codec The codec used to encode and decode messages.
1308
+ * @param clientId The client ID of this transport.
1309
+ */
1310
+ constructor(clientId, providedOptions) {
1311
+ this.options = { ...defaultTransportOptions, ...providedOptions };
1312
+ this.eventDispatcher = new EventDispatcher();
1313
+ this.clientId = clientId;
1314
+ this.status = "open";
1315
+ this.sessions = /* @__PURE__ */ new Map();
1316
+ }
1317
+ bindLogger(fn, level) {
1318
+ if (typeof fn === "function") {
1319
+ this.log = createLogProxy(new BaseLogger(fn, level));
1320
+ return;
1321
+ }
1322
+ this.log = createLogProxy(fn);
1323
+ }
1324
+ /**
1325
+ * Called when a message is received by this transport.
1326
+ * You generally shouldn't need to override this in downstream transport implementations.
1327
+ * @param message The received message.
1328
+ */
1329
+ handleMsg(message) {
1330
+ if (this.getStatus() !== "open")
1331
+ return;
1332
+ this.eventDispatcher.dispatchEvent("message", message);
1333
+ }
1334
+ /**
1335
+ * Adds a listener to this transport.
1336
+ * @param the type of event to listen for
1337
+ * @param handler The message handler to add.
1338
+ */
1339
+ addEventListener(type, handler) {
1340
+ this.eventDispatcher.addEventListener(type, handler);
1341
+ }
1342
+ /**
1343
+ * Removes a listener from this transport.
1344
+ * @param the type of event to un-listen on
1345
+ * @param handler The message handler to remove.
1346
+ */
1347
+ removeEventListener(type, handler) {
1348
+ this.eventDispatcher.removeEventListener(type, handler);
1349
+ }
1350
+ protocolError(message) {
1351
+ this.eventDispatcher.dispatchEvent("protocolError", message);
1352
+ }
1353
+ /**
1354
+ * Default close implementation for transports. You should override this in the downstream
1355
+ * implementation if you need to do any additional cleanup and call super.close() at the end.
1356
+ * Closes the transport. Any messages sent while the transport is closed will be silently discarded.
1357
+ */
1358
+ close() {
1359
+ this.status = "closed";
1360
+ for (const session of this.sessions.values()) {
1361
+ this.deleteSession(session);
1362
+ }
1363
+ this.eventDispatcher.dispatchEvent("transportStatus", {
1364
+ status: this.status
1365
+ });
1366
+ this.eventDispatcher.removeAllListeners();
1367
+ this.log?.info(`manually closed transport`, { clientId: this.clientId });
1368
+ }
1369
+ getStatus() {
1370
+ return this.status;
1371
+ }
1372
+ // state transitions
1373
+ createSession(session) {
1374
+ const activeSession = this.sessions.get(session.to);
1375
+ if (activeSession) {
1376
+ const msg = `attempt to create session for ${session.to} but active session (${activeSession.id}) already exists`;
1377
+ this.log?.error(msg, {
1378
+ ...session.loggingMetadata,
1379
+ tags: ["invariant-violation"]
1380
+ });
1381
+ throw new Error(msg);
1382
+ }
1383
+ this.sessions.set(session.to, session);
1384
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1385
+ status: "connect",
1386
+ session
1387
+ });
1388
+ this.eventDispatcher.dispatchEvent("sessionTransition", {
1389
+ state: session.state,
1390
+ session
1391
+ });
1392
+ }
1393
+ updateSession(session) {
1394
+ const activeSession = this.sessions.get(session.to);
1395
+ if (!activeSession) {
1396
+ const msg = `attempt to transition session for ${session.to} but no active session exists`;
1397
+ this.log?.error(msg, {
1398
+ ...session.loggingMetadata,
1399
+ tags: ["invariant-violation"]
1400
+ });
1401
+ throw new Error(msg);
1402
+ }
1403
+ if (activeSession.id !== session.id) {
1404
+ const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
1405
+ this.log?.error(msg, {
1406
+ ...session.loggingMetadata,
1407
+ tags: ["invariant-violation"]
1408
+ });
1409
+ throw new Error(msg);
1410
+ }
1411
+ this.sessions.set(session.to, session);
1412
+ this.eventDispatcher.dispatchEvent("sessionTransition", {
1413
+ state: session.state,
1414
+ session
1415
+ });
1416
+ }
1417
+ deleteSession(session, options) {
1418
+ if (session._isConsumed)
1419
+ return;
1420
+ const loggingMetadata = session.loggingMetadata;
1421
+ if (loggingMetadata.tags && options?.unhealthy) {
1422
+ loggingMetadata.tags.push("unhealthy-session");
1423
+ }
1424
+ session.log?.info(`closing session ${session.id}`, loggingMetadata);
1425
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1426
+ status: "disconnect",
1427
+ session
1428
+ });
1429
+ const to = session.to;
1430
+ session.close();
1431
+ this.sessions.delete(to);
1432
+ }
1433
+ // common listeners
1434
+ onSessionGracePeriodElapsed(session) {
1435
+ this.log?.warn(
1436
+ `session to ${session.to} grace period elapsed, closing`,
1437
+ session.loggingMetadata
1438
+ );
1439
+ this.deleteSession(session);
1440
+ }
1441
+ onConnectingFailed(session) {
1442
+ const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
1443
+ onSessionGracePeriodElapsed: () => {
1444
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1445
+ }
1446
+ });
1447
+ this.updateSession(noConnectionSession);
1448
+ return noConnectionSession;
1449
+ }
1450
+ onConnClosed(session) {
1451
+ let noConnectionSession;
1452
+ if (session.state === "Handshaking" /* Handshaking */) {
1453
+ noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
1454
+ onSessionGracePeriodElapsed: () => {
1455
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1456
+ }
1457
+ });
1458
+ } else {
1459
+ noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
1460
+ onSessionGracePeriodElapsed: () => {
1461
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1462
+ }
1463
+ });
1464
+ }
1465
+ this.updateSession(noConnectionSession);
1466
+ return noConnectionSession;
1467
+ }
1468
+ /**
1469
+ * Gets a send closure scoped to a specific session. Sending using the returned
1470
+ * closure after the session has transitioned to a different state will be a noop.
1471
+ *
1472
+ * Session objects themselves can become stale as they transition between
1473
+ * states. As stale sessions cannot be used again (and will throw), holding
1474
+ * onto a session object is not recommended.
1475
+ */
1476
+ getSessionBoundSendFn(to, sessionId) {
1477
+ if (this.getStatus() !== "open") {
1478
+ throw new Error("cannot get a bound send function on a closed transport");
1479
+ }
1480
+ return (msg) => {
1481
+ const session = this.sessions.get(to);
1482
+ if (!session) {
1483
+ throw new Error(
1484
+ `session scope for ${sessionId} has ended (close), can't send`
1485
+ );
1486
+ }
1487
+ const sameSession = session.id === sessionId;
1488
+ if (!sameSession) {
1489
+ throw new Error(
1490
+ `session scope for ${sessionId} has ended (transition), can't send`
1491
+ );
1492
+ }
1493
+ return session.send(msg);
1494
+ };
1495
+ }
1496
+ };
1497
+
1498
+ // transport/stringifyError.ts
1499
+ function coerceErrorString(err) {
1500
+ if (err instanceof Error) {
1501
+ return err.message || "unknown reason";
1502
+ }
1503
+ return `[coerced to error] ${String(err)}`;
1504
+ }
1505
+
1506
+ // transport/client.ts
1507
+ var import_value2 = require("@sinclair/typebox/value");
1508
+ var ClientTransport = class extends Transport {
1509
+ /**
1510
+ * The options for this transport.
1511
+ */
1512
+ options;
1513
+ retryBudget;
1514
+ /**
1515
+ * A flag indicating whether the transport should automatically reconnect
1516
+ * when a connection is dropped.
1517
+ * Realistically, this should always be true for clients unless you are writing
1518
+ * tests or a special case where you don't want to reconnect.
1519
+ */
1520
+ reconnectOnConnectionDrop = true;
1521
+ /**
1522
+ * Optional handshake options for this client.
1523
+ */
1524
+ handshakeExtensions;
1525
+ sessions;
1526
+ constructor(clientId, providedOptions) {
1527
+ super(clientId, providedOptions);
1528
+ this.sessions = /* @__PURE__ */ new Map();
1529
+ this.options = {
1530
+ ...defaultClientTransportOptions,
1531
+ ...providedOptions
1532
+ };
1533
+ this.retryBudget = new LeakyBucketRateLimit(this.options);
1534
+ }
1535
+ extendHandshake(options) {
1536
+ this.handshakeExtensions = options;
1537
+ }
1538
+ tryReconnecting(to) {
1539
+ const oldSession = this.sessions.get(to);
1540
+ if (!this.options.enableTransparentSessionReconnects && oldSession) {
1541
+ this.deleteSession(oldSession);
1542
+ }
1543
+ if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
1544
+ this.connect(to);
1545
+ }
1546
+ }
1547
+ /*
1548
+ * Creates a raw unconnected session object.
1549
+ * This is mostly a River internal, you shouldn't need to use this directly.
1550
+ */
1551
+ createUnconnectedSession(to) {
1552
+ const session = ClientSessionStateGraph.entrypoint(
1553
+ to,
1554
+ this.clientId,
1555
+ {
1556
+ onSessionGracePeriodElapsed: () => {
1557
+ this.onSessionGracePeriodElapsed(session);
1558
+ }
1559
+ },
1560
+ this.options,
1561
+ currentProtocolVersion,
1562
+ this.log
1563
+ );
1564
+ this.createSession(session);
1565
+ return session;
1566
+ }
1567
+ // listeners
1568
+ onConnectingFailed(session) {
1569
+ const noConnectionSession = super.onConnectingFailed(session);
1570
+ this.tryReconnecting(noConnectionSession.to);
1571
+ return noConnectionSession;
1572
+ }
1573
+ onConnClosed(session) {
1574
+ const noConnectionSession = super.onConnClosed(session);
1575
+ this.tryReconnecting(noConnectionSession.to);
1576
+ return noConnectionSession;
1577
+ }
1578
+ onConnectionEstablished(session, conn) {
1579
+ const handshakingSession = ClientSessionStateGraph.transition.ConnectingToHandshaking(
1580
+ session,
1581
+ conn,
1582
+ {
1583
+ onConnectionErrored: (err) => {
1584
+ const errStr = coerceErrorString(err);
1585
+ this.log?.error(
1586
+ `connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
1587
+ handshakingSession.loggingMetadata
1588
+ );
1589
+ },
1590
+ onConnectionClosed: () => {
1591
+ this.log?.warn(
1592
+ `connection to ${handshakingSession.to} closed during handshake`,
1593
+ handshakingSession.loggingMetadata
1594
+ );
1595
+ this.onConnClosed(handshakingSession);
1596
+ },
1597
+ onHandshake: (msg) => {
1598
+ this.onHandshakeResponse(handshakingSession, msg);
1599
+ },
1600
+ onInvalidHandshake: (reason, code) => {
1601
+ this.log?.error(
1602
+ `invalid handshake: ${reason}`,
1603
+ handshakingSession.loggingMetadata
1604
+ );
1605
+ this.deleteSession(session, { unhealthy: true });
1606
+ this.protocolError({
1607
+ type: ProtocolError.HandshakeFailed,
1608
+ code,
1609
+ message: reason
1610
+ });
1611
+ },
1612
+ onHandshakeTimeout: () => {
1613
+ this.log?.error(
1614
+ `connection to ${handshakingSession.to} timed out during handshake`,
1615
+ handshakingSession.loggingMetadata
1616
+ );
1617
+ this.onConnClosed(handshakingSession);
1618
+ },
1619
+ onSessionGracePeriodElapsed: () => {
1620
+ this.onSessionGracePeriodElapsed(handshakingSession);
1621
+ }
1622
+ }
1623
+ );
1624
+ this.updateSession(handshakingSession);
1625
+ void this.sendHandshake(handshakingSession);
1626
+ return handshakingSession;
1627
+ }
1628
+ rejectHandshakeResponse(session, reason, metadata) {
1629
+ session.conn.telemetry?.span.setStatus({
1630
+ code: import_api3.SpanStatusCode.ERROR,
1631
+ message: reason
1632
+ });
1633
+ this.log?.warn(reason, metadata);
1634
+ this.deleteSession(session, { unhealthy: true });
1635
+ }
1636
+ onHandshakeResponse(session, msg) {
1637
+ if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1638
+ const reason = `received invalid handshake response`;
1639
+ this.rejectHandshakeResponse(session, reason, {
1640
+ ...session.loggingMetadata,
1641
+ transportMessage: msg,
1642
+ validationErrors: [
1643
+ ...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1644
+ ]
1645
+ });
1646
+ return;
1647
+ }
1648
+ if (!msg.payload.status.ok) {
1649
+ const retriable = import_value2.Value.Check(
1650
+ HandshakeErrorRetriableResponseCodes,
1651
+ msg.payload.status.code
1652
+ );
1653
+ const reason = `handshake failed: ${msg.payload.status.reason}`;
1654
+ const to = session.to;
1655
+ this.rejectHandshakeResponse(session, reason, {
1656
+ ...session.loggingMetadata,
1657
+ transportMessage: msg
1658
+ });
1659
+ if (retriable) {
1660
+ this.tryReconnecting(to);
1661
+ } else {
1662
+ this.protocolError({
1663
+ type: ProtocolError.HandshakeFailed,
1664
+ code: msg.payload.status.code,
1665
+ message: reason
1666
+ });
1667
+ }
1668
+ return;
1669
+ }
1670
+ if (msg.payload.status.sessionId !== session.id) {
1671
+ const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
1672
+ this.rejectHandshakeResponse(session, reason, {
1673
+ ...session.loggingMetadata,
1674
+ transportMessage: msg
1675
+ });
1676
+ return;
1677
+ }
1678
+ this.log?.info(`handshake from ${msg.from} ok`, {
1679
+ ...session.loggingMetadata,
1680
+ transportMessage: msg
1681
+ });
1682
+ const connectedSession = ClientSessionStateGraph.transition.HandshakingToConnected(session, {
1683
+ onConnectionErrored: (err) => {
1684
+ const errStr = coerceErrorString(err);
1685
+ this.log?.warn(
1686
+ `connection to ${connectedSession.to} errored: ${errStr}`,
1687
+ connectedSession.loggingMetadata
1688
+ );
1689
+ },
1690
+ onConnectionClosed: () => {
1691
+ this.log?.info(
1692
+ `connection to ${connectedSession.to} closed`,
1693
+ connectedSession.loggingMetadata
1694
+ );
1695
+ this.onConnClosed(connectedSession);
1696
+ },
1697
+ onMessage: (msg2) => {
1698
+ this.handleMsg(msg2);
1699
+ },
1700
+ onInvalidMessage: (reason) => {
1701
+ this.deleteSession(connectedSession, { unhealthy: true });
1702
+ this.protocolError({
1703
+ type: ProtocolError.InvalidMessage,
1704
+ message: reason
1705
+ });
1706
+ }
1707
+ });
1708
+ this.updateSession(connectedSession);
1709
+ this.retryBudget.startRestoringBudget();
1710
+ }
1711
+ /**
1712
+ * Manually attempts to connect to a client.
1713
+ * @param to The client ID of the node to connect to.
1714
+ */
1715
+ connect(to) {
1716
+ if (this.getStatus() !== "open") {
1717
+ this.log?.info(
1718
+ `transport state is no longer open, cancelling attempt to connect to ${to}`
1719
+ );
1720
+ return;
1721
+ }
1722
+ const session = this.sessions.get(to) ?? this.createUnconnectedSession(to);
1723
+ if (session.state !== "NoConnection" /* NoConnection */) {
1724
+ this.log?.debug(
1725
+ `session to ${to} has state ${session.state}, skipping connect attempt`,
1726
+ session.loggingMetadata
1727
+ );
1728
+ return;
1729
+ }
1730
+ if (!this.retryBudget.hasBudget()) {
1731
+ const budgetConsumed = this.retryBudget.getBudgetConsumed();
1732
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1733
+ this.log?.error(errMsg, session.loggingMetadata);
1734
+ this.protocolError({
1735
+ type: ProtocolError.RetriesExceeded,
1736
+ message: errMsg
1737
+ });
1738
+ return;
1739
+ }
1740
+ const backoffMs = this.retryBudget.getBackoffMs();
1741
+ this.log?.info(
1742
+ `attempting connection to ${to} (${backoffMs}ms backoff)`,
1743
+ session.loggingMetadata
1744
+ );
1745
+ this.retryBudget.consumeBudget();
1746
+ const backingOffSession = ClientSessionStateGraph.transition.NoConnectionToBackingOff(
1747
+ session,
1748
+ backoffMs,
1749
+ {
1750
+ onBackoffFinished: () => {
1751
+ this.onBackoffFinished(backingOffSession);
1752
+ },
1753
+ onSessionGracePeriodElapsed: () => {
1754
+ this.onSessionGracePeriodElapsed(backingOffSession);
1755
+ }
1756
+ }
1757
+ );
1758
+ this.updateSession(backingOffSession);
1759
+ }
1760
+ onBackoffFinished(session) {
1761
+ const connPromise = tracing_default.startActiveSpan("connect", async (span) => {
1762
+ try {
1763
+ return await this.createNewOutgoingConnection(session.to);
1764
+ } catch (err) {
1765
+ const errStr = coerceErrorString(err);
1766
+ span.recordException(errStr);
1767
+ span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
1768
+ throw err;
1769
+ } finally {
1770
+ span.end();
1771
+ }
1772
+ });
1773
+ const connectingSession = ClientSessionStateGraph.transition.BackingOffToConnecting(
1774
+ session,
1775
+ connPromise,
1776
+ {
1777
+ onConnectionEstablished: (conn) => {
1778
+ this.log?.debug(
1779
+ `connection to ${connectingSession.to} established`,
1780
+ {
1781
+ ...conn.loggingMetadata,
1782
+ ...connectingSession.loggingMetadata
1783
+ }
1784
+ );
1785
+ this.onConnectionEstablished(connectingSession, conn);
1786
+ },
1787
+ onConnectionFailed: (error) => {
1788
+ const errStr = coerceErrorString(error);
1789
+ this.log?.error(
1790
+ `error connecting to ${connectingSession.to}: ${errStr}`,
1791
+ connectingSession.loggingMetadata
1792
+ );
1793
+ this.onConnectingFailed(connectingSession);
1794
+ },
1795
+ onConnectionTimeout: () => {
1796
+ this.log?.error(
1797
+ `connection to ${connectingSession.to} timed out`,
1798
+ connectingSession.loggingMetadata
1799
+ );
1800
+ this.onConnectingFailed(connectingSession);
1801
+ },
1802
+ onSessionGracePeriodElapsed: () => {
1803
+ this.onSessionGracePeriodElapsed(connectingSession);
1804
+ }
1805
+ }
1806
+ );
1807
+ this.updateSession(connectingSession);
1808
+ }
1809
+ async sendHandshake(session) {
1810
+ let metadata = void 0;
1811
+ if (this.handshakeExtensions) {
1812
+ metadata = await this.handshakeExtensions.construct();
1813
+ }
1814
+ if (session._isConsumed) {
1815
+ return;
1816
+ }
1817
+ const requestMsg = handshakeRequestMessage({
1818
+ from: this.clientId,
1819
+ to: session.to,
1820
+ sessionId: session.id,
1821
+ expectedSessionState: {
1822
+ nextExpectedSeq: session.ack,
1823
+ nextSentSeq: session.nextSeq()
1824
+ },
1825
+ metadata,
1826
+ tracing: getPropagationContext(session.telemetry.ctx)
1827
+ });
1828
+ this.log?.debug(`sending handshake request to ${session.to}`, {
1829
+ ...session.loggingMetadata,
1830
+ transportMessage: requestMsg
1831
+ });
1832
+ session.sendHandshake(requestMsg);
1833
+ }
1834
+ close() {
1835
+ this.retryBudget.close();
1836
+ super.close();
1837
+ }
1838
+ };
1839
+
1840
+ // transport/connection.ts
1841
+ var Connection = class {
1842
+ id;
1843
+ telemetry;
1844
+ constructor() {
1845
+ this.id = `conn-${generateId()}`;
1846
+ }
1847
+ get loggingMetadata() {
1848
+ const metadata = { connId: this.id };
1849
+ const spanContext = this.telemetry?.span.spanContext();
1850
+ if (this.telemetry?.span.isRecording() && spanContext) {
1851
+ metadata.telemetry = {
1852
+ traceId: spanContext.traceId,
1853
+ spanId: spanContext.spanId
1854
+ };
1855
+ }
1856
+ return metadata;
1857
+ }
1858
+ // can't use event emitter because we need this to work in both node + browser
1859
+ _dataListeners = /* @__PURE__ */ new Set();
1860
+ _closeListeners = /* @__PURE__ */ new Set();
1861
+ _errorListeners = /* @__PURE__ */ new Set();
1862
+ get dataListeners() {
1863
+ return [...this._dataListeners];
1864
+ }
1865
+ get closeListeners() {
1866
+ return [...this._closeListeners];
1867
+ }
1868
+ get errorListeners() {
1869
+ return [...this._errorListeners];
1870
+ }
1871
+ /**
1872
+ * Handle adding a callback for when a message is received.
1873
+ * @param msg The message that was received.
1874
+ */
1875
+ addDataListener(cb) {
1876
+ this._dataListeners.add(cb);
1877
+ }
1878
+ removeDataListener(cb) {
1879
+ this._dataListeners.delete(cb);
1880
+ }
1881
+ /**
1882
+ * Handle adding a callback for when the connection is closed.
1883
+ * This should also be called if an error happens and after notifying all the error listeners.
1884
+ * @param cb The callback to call when the connection is closed.
1885
+ */
1886
+ addCloseListener(cb) {
1887
+ this._closeListeners.add(cb);
1888
+ }
1889
+ removeCloseListener(cb) {
1890
+ this._closeListeners.delete(cb);
1891
+ }
1892
+ /**
1893
+ * Handle adding a callback for when an error is received.
1894
+ * This should only be used for this.logging errors, all cleanup
1895
+ * should be delegated to addCloseListener.
1896
+ *
1897
+ * The implementer should take care such that the implemented
1898
+ * connection will call both the close and error callbacks
1899
+ * on an error.
1900
+ *
1901
+ * @param cb The callback to call when an error is received.
1902
+ */
1903
+ addErrorListener(cb) {
1904
+ this._errorListeners.add(cb);
1905
+ }
1906
+ removeErrorListener(cb) {
1907
+ this._errorListeners.delete(cb);
1908
+ }
1909
+ };
1910
+
1911
+ // transport/server.ts
1912
+ var import_api4 = require("@opentelemetry/api");
1913
+ var import_value3 = require("@sinclair/typebox/value");
1914
+ var ServerTransport = class extends Transport {
1915
+ /**
1916
+ * The options for this transport.
1917
+ */
1918
+ options;
1919
+ /**
1920
+ * Optional handshake options for the server.
1921
+ */
1922
+ handshakeExtensions;
1923
+ /**
1924
+ * A map of session handshake data for each session.
1925
+ */
1926
+ sessionHandshakeMetadata = /* @__PURE__ */ new Map();
1927
+ sessions = /* @__PURE__ */ new Map();
1928
+ pendingSessions = /* @__PURE__ */ new Set();
1929
+ constructor(clientId, providedOptions) {
1930
+ super(clientId, providedOptions);
1931
+ this.sessions = /* @__PURE__ */ new Map();
1932
+ this.options = {
1933
+ ...defaultServerTransportOptions,
1934
+ ...providedOptions
1935
+ };
1936
+ this.log?.info(`initiated server transport`, {
1937
+ clientId: this.clientId,
1938
+ protocolVersion: currentProtocolVersion
1939
+ });
1940
+ }
1941
+ extendHandshake(options) {
1942
+ this.handshakeExtensions = options;
1943
+ }
1944
+ deletePendingSession(pendingSession) {
1945
+ pendingSession.close();
1946
+ this.pendingSessions.delete(pendingSession);
1947
+ }
1948
+ deleteSession(session, options) {
1949
+ this.sessionHandshakeMetadata.delete(session.to);
1950
+ super.deleteSession(session, options);
1951
+ }
1952
+ handleConnection(conn) {
1953
+ if (this.getStatus() !== "open")
1954
+ return;
1955
+ this.log?.info(`new incoming connection`, {
1956
+ ...conn.loggingMetadata,
1957
+ clientId: this.clientId
1958
+ });
1959
+ let receivedHandshake = false;
1960
+ const pendingSession = ServerSessionStateGraph.entrypoint(
1961
+ this.clientId,
1962
+ conn,
1963
+ {
1964
+ onConnectionClosed: () => {
1965
+ this.log?.warn(
1966
+ `connection from unknown closed before handshake finished`,
1967
+ pendingSession.loggingMetadata
1968
+ );
1969
+ this.deletePendingSession(pendingSession);
1970
+ },
1971
+ onConnectionErrored: (err) => {
1972
+ const errorString = coerceErrorString(err);
1973
+ this.log?.warn(
1974
+ `connection from unknown errored before handshake finished: ${errorString}`,
1975
+ pendingSession.loggingMetadata
1976
+ );
1977
+ this.deletePendingSession(pendingSession);
1978
+ },
1979
+ onHandshakeTimeout: () => {
1980
+ this.log?.warn(
1981
+ `connection from unknown timed out before handshake finished`,
1982
+ pendingSession.loggingMetadata
1983
+ );
1984
+ this.deletePendingSession(pendingSession);
1985
+ },
1986
+ onHandshake: (msg) => {
1987
+ if (receivedHandshake) {
1988
+ this.log?.error(
1989
+ `received multiple handshake messages from pending session`,
1990
+ {
1991
+ ...pendingSession.loggingMetadata,
1992
+ connectedTo: msg.from,
1993
+ transportMessage: msg
1994
+ }
1995
+ );
1996
+ this.deletePendingSession(pendingSession);
1997
+ return;
1998
+ }
1999
+ receivedHandshake = true;
2000
+ void this.onHandshakeRequest(pendingSession, msg);
2001
+ },
2002
+ onInvalidHandshake: (reason, code) => {
2003
+ this.log?.error(
2004
+ `invalid handshake: ${reason}`,
2005
+ pendingSession.loggingMetadata
2006
+ );
2007
+ this.deletePendingSession(pendingSession);
2008
+ this.protocolError({
2009
+ type: ProtocolError.HandshakeFailed,
2010
+ code,
2011
+ message: reason
2012
+ });
2013
+ }
2014
+ },
2015
+ this.options,
2016
+ this.log
2017
+ );
2018
+ this.pendingSessions.add(pendingSession);
2019
+ }
2020
+ rejectHandshakeRequest(session, to, reason, code, metadata) {
2021
+ session.conn.telemetry?.span.setStatus({
2022
+ code: import_api4.SpanStatusCode.ERROR,
2023
+ message: reason
2024
+ });
2025
+ this.log?.warn(reason, metadata);
2026
+ session.sendHandshake(
2027
+ handshakeResponseMessage({
2028
+ from: this.clientId,
2029
+ to,
2030
+ status: {
2031
+ ok: false,
2032
+ code,
2033
+ reason
2034
+ }
2035
+ })
2036
+ );
2037
+ this.protocolError({
2038
+ type: ProtocolError.HandshakeFailed,
2039
+ code,
2040
+ message: reason
2041
+ });
2042
+ this.deletePendingSession(session);
2043
+ }
2044
+ async onHandshakeRequest(session, msg) {
2045
+ if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
2046
+ this.rejectHandshakeRequest(
2047
+ session,
2048
+ msg.from,
2049
+ "received invalid handshake request",
2050
+ "MALFORMED_HANDSHAKE",
2051
+ {
2052
+ ...session.loggingMetadata,
2053
+ transportMessage: msg,
2054
+ connectedTo: msg.from,
2055
+ validationErrors: [
2056
+ ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
2057
+ ]
2058
+ }
2059
+ );
2060
+ return;
2061
+ }
2062
+ const gotVersion = msg.payload.protocolVersion;
2063
+ if (!isAcceptedProtocolVersion(gotVersion)) {
2064
+ this.rejectHandshakeRequest(
2065
+ session,
2066
+ msg.from,
2067
+ `expected protocol version oneof [${acceptedProtocolVersions.toString()}], got ${gotVersion}`,
2068
+ "PROTOCOL_VERSION_MISMATCH",
2069
+ {
2070
+ ...session.loggingMetadata,
2071
+ connectedTo: msg.from,
2072
+ transportMessage: msg
2073
+ }
2074
+ );
2075
+ return;
2076
+ }
2077
+ let parsedMetadata = {};
2078
+ if (this.handshakeExtensions) {
2079
+ if (!import_value3.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
2080
+ this.rejectHandshakeRequest(
2081
+ session,
2082
+ msg.from,
2083
+ "received malformed handshake metadata",
2084
+ "MALFORMED_HANDSHAKE_META",
2085
+ {
2086
+ ...session.loggingMetadata,
2087
+ connectedTo: msg.from,
2088
+ validationErrors: [
2089
+ ...import_value3.Value.Errors(
2090
+ this.handshakeExtensions.schema,
2091
+ msg.payload.metadata
2092
+ )
2093
+ ]
2094
+ }
2095
+ );
2096
+ return;
2097
+ }
2098
+ const previousParsedMetadata = this.sessionHandshakeMetadata.get(
2099
+ msg.from
2100
+ );
2101
+ const parsedMetadataOrFailureCode = await this.handshakeExtensions.validate(
2102
+ msg.payload.metadata,
2103
+ previousParsedMetadata
2104
+ );
2105
+ if (session._isConsumed) {
2106
+ return;
2107
+ }
2108
+ if (import_value3.Value.Check(
2109
+ HandshakeErrorCustomHandlerFatalResponseCodes,
2110
+ parsedMetadataOrFailureCode
2111
+ )) {
2112
+ this.rejectHandshakeRequest(
2113
+ session,
2114
+ msg.from,
2115
+ "rejected by handshake handler",
2116
+ parsedMetadataOrFailureCode,
2117
+ {
2118
+ ...session.loggingMetadata,
2119
+ connectedTo: msg.from,
2120
+ clientId: this.clientId
2121
+ }
2122
+ );
2123
+ return;
2124
+ }
2125
+ parsedMetadata = parsedMetadataOrFailureCode;
2126
+ }
2127
+ let connectCase = "new session";
2128
+ const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
2129
+ const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq;
2130
+ let oldSession = this.sessions.get(msg.from);
2131
+ if (this.options.enableTransparentSessionReconnects && oldSession && oldSession.id === msg.payload.sessionId) {
2132
+ connectCase = "transparent reconnection";
2133
+ const ourNextSeq = oldSession.nextSeq();
2134
+ const ourAck = oldSession.ack;
2135
+ if (clientNextSentSeq > ourAck) {
2136
+ this.rejectHandshakeRequest(
2137
+ session,
2138
+ msg.from,
2139
+ `client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
2140
+ "SESSION_STATE_MISMATCH",
2141
+ {
2142
+ ...session.loggingMetadata,
2143
+ connectedTo: msg.from,
2144
+ transportMessage: msg
2145
+ }
2146
+ );
2147
+ return;
2148
+ }
2149
+ if (ourNextSeq > clientNextExpectedSeq) {
2150
+ this.rejectHandshakeRequest(
2151
+ session,
2152
+ msg.from,
2153
+ `server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
2154
+ "SESSION_STATE_MISMATCH",
2155
+ {
2156
+ ...session.loggingMetadata,
2157
+ connectedTo: msg.from,
2158
+ transportMessage: msg
2159
+ }
2160
+ );
2161
+ return;
2162
+ }
2163
+ if (oldSession.state !== "NoConnection" /* NoConnection */) {
2164
+ const noConnectionSession = ServerSessionStateGraph.transition.ConnectedToNoConnection(
2165
+ oldSession,
2166
+ {
2167
+ onSessionGracePeriodElapsed: () => {
2168
+ this.onSessionGracePeriodElapsed(noConnectionSession);
2169
+ }
2170
+ }
2171
+ );
2172
+ oldSession = noConnectionSession;
2173
+ }
2174
+ this.updateSession(oldSession);
2175
+ } else if (oldSession) {
2176
+ connectCase = "hard reconnection";
2177
+ this.log?.info(
2178
+ `client is reconnecting to a new session (${msg.payload.sessionId}) with an old session (${oldSession.id}) already existing, closing old session`,
2179
+ {
2180
+ ...session.loggingMetadata,
2181
+ connectedTo: msg.from,
2182
+ sessionId: msg.payload.sessionId
2183
+ }
2184
+ );
2185
+ this.deleteSession(oldSession);
2186
+ oldSession = void 0;
2187
+ }
2188
+ if (!oldSession && (clientNextSentSeq > 0 || clientNextExpectedSeq > 0)) {
2189
+ connectCase = "unknown session";
2190
+ const rejectionMessage = this.options.enableTransparentSessionReconnects ? `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}` : `client is attempting a transparent reconnect to a session but the server does not support it: ${msg.payload.sessionId}`;
2191
+ this.rejectHandshakeRequest(
2192
+ session,
2193
+ msg.from,
2194
+ rejectionMessage,
2195
+ "SESSION_STATE_MISMATCH",
2196
+ {
2197
+ ...session.loggingMetadata,
2198
+ connectedTo: msg.from,
2199
+ transportMessage: msg
2200
+ }
2201
+ );
2202
+ return;
2203
+ }
2204
+ const sessionId = msg.payload.sessionId;
2205
+ this.log?.info(
2206
+ `handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
2207
+ {
2208
+ ...session.loggingMetadata,
2209
+ connectedTo: msg.from
2210
+ }
2211
+ );
2212
+ const responseMsg = handshakeResponseMessage({
2213
+ from: this.clientId,
2214
+ to: msg.from,
2215
+ status: {
2216
+ ok: true,
2217
+ sessionId
2218
+ }
2219
+ });
2220
+ session.sendHandshake(responseMsg);
2221
+ const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2222
+ session,
2223
+ // by this point oldSession is either no connection or we dont have an old session
2224
+ oldSession,
2225
+ sessionId,
2226
+ msg.from,
2227
+ msg.tracing,
2228
+ {
2229
+ onConnectionErrored: (err) => {
2230
+ const errStr = coerceErrorString(err);
2231
+ this.log?.warn(
2232
+ `connection to ${connectedSession.to} errored: ${errStr}`,
2233
+ connectedSession.loggingMetadata
2234
+ );
2235
+ },
2236
+ onConnectionClosed: () => {
2237
+ this.log?.info(
2238
+ `connection to ${connectedSession.to} closed`,
2239
+ connectedSession.loggingMetadata
2240
+ );
2241
+ this.onConnClosed(connectedSession);
2242
+ },
2243
+ onMessage: (msg2) => {
2244
+ this.handleMsg(msg2);
2245
+ },
2246
+ onInvalidMessage: (reason) => {
2247
+ this.protocolError({
2248
+ type: ProtocolError.InvalidMessage,
2249
+ message: reason
2250
+ });
2251
+ this.deleteSession(connectedSession, { unhealthy: true });
2252
+ }
2253
+ },
2254
+ gotVersion
2255
+ );
2256
+ this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2257
+ if (oldSession) {
2258
+ this.updateSession(connectedSession);
2259
+ } else {
2260
+ this.createSession(connectedSession);
2261
+ }
2262
+ this.pendingSessions.delete(session);
2263
+ connectedSession.startActiveHeartbeat();
2264
+ }
2265
+ };
2266
+
2267
+ // testUtil/observable/observable.ts
2268
+ var Observable = class {
2269
+ value;
2270
+ listeners;
2271
+ constructor(initialValue) {
2272
+ this.value = initialValue;
2273
+ this.listeners = /* @__PURE__ */ new Set();
2274
+ }
2275
+ /**
2276
+ * Gets the current value of the observable.
2277
+ */
2278
+ get() {
2279
+ return this.value;
2280
+ }
2281
+ /**
2282
+ * Sets the current value of the observable. All listeners will get an update with this value.
2283
+ * @param newValue - The new value to set.
2284
+ */
2285
+ set(tx) {
2286
+ const newValue = tx(this.value);
2287
+ this.value = newValue;
2288
+ this.listeners.forEach((listener) => listener(newValue));
2289
+ }
2290
+ /**
2291
+ * Subscribes to changes in the observable value.
2292
+ * @param listener - A callback function that will be called when the value changes.
2293
+ * @returns A function that can be called to unsubscribe from further notifications.
2294
+ */
2295
+ observe(listener) {
2296
+ this.listeners.add(listener);
2297
+ listener(this.get());
2298
+ return () => this.listeners.delete(listener);
2299
+ }
2300
+ /**
2301
+ * Returns the number of listeners currently observing the observable
2302
+ */
2303
+ get listenerCount() {
2304
+ return this.listeners.size;
2305
+ }
2306
+ };
2307
+
2308
+ // testUtil/duplex/duplexPair.ts
2309
+ var import_node_stream = require("stream");
2310
+ var import_assert = __toESM(require("assert"), 1);
2311
+ var kCallback = Symbol("Callback");
2312
+ var kInitOtherSide = Symbol("InitOtherSide");
2313
+ var DuplexSide = class extends import_node_stream.Duplex {
2314
+ otherSide;
2315
+ [kCallback];
2316
+ constructor() {
2317
+ super();
2318
+ this[kCallback] = null;
2319
+ this.otherSide = null;
2320
+ }
2321
+ [kInitOtherSide](otherSide) {
2322
+ if (this.otherSide === null) {
2323
+ this.otherSide = otherSide;
2324
+ }
2325
+ }
2326
+ _read() {
2327
+ const callback = this[kCallback];
2328
+ if (callback) {
2329
+ this[kCallback] = null;
2330
+ callback();
2331
+ }
2332
+ }
2333
+ _write(chunk, _encoding, callback) {
2334
+ (0, import_assert.default)(this.otherSide !== null);
2335
+ (0, import_assert.default)(this.otherSide[kCallback] === null);
2336
+ if (chunk.length === 0) {
2337
+ process.nextTick(callback);
2338
+ } else {
2339
+ this.otherSide.push(chunk);
2340
+ this.otherSide[kCallback] = callback;
2341
+ }
2342
+ }
2343
+ _final(callback) {
2344
+ this.otherSide?.on("end", callback);
2345
+ this.otherSide?.push(null);
2346
+ }
2347
+ };
2348
+ function duplexPair() {
2349
+ const side0 = new DuplexSide();
2350
+ const side1 = new DuplexSide();
2351
+ side0[kInitOtherSide](side1);
2352
+ side1[kInitOtherSide](side0);
2353
+ return [side0, side1];
2354
+ }
2355
+
2356
+ // testUtil/fixtures/mockTransport.ts
2357
+ var import_nanoid2 = require("nanoid");
2358
+ var InMemoryConnection = class extends Connection {
2359
+ conn;
2360
+ constructor(pipe) {
2361
+ super();
2362
+ this.conn = pipe;
2363
+ this.conn.on("data", (data) => {
2364
+ for (const cb of this.dataListeners) {
2365
+ cb(data);
2366
+ }
2367
+ });
2368
+ this.conn.on("end", () => {
2369
+ for (const cb of this.closeListeners) {
2370
+ cb();
2371
+ }
2372
+ });
2373
+ this.conn.on("error", (err) => {
2374
+ for (const cb of this.errorListeners) {
2375
+ cb(err);
2376
+ }
2377
+ });
2378
+ }
2379
+ send(payload) {
2380
+ setImmediate(() => {
2381
+ this.conn.write(payload);
2382
+ });
2383
+ return true;
2384
+ }
2385
+ close() {
2386
+ setImmediate(() => {
2387
+ this.conn.end();
2388
+ });
2389
+ }
2390
+ };
2391
+ function createMockTransportNetwork(opts) {
2392
+ const connections = new Observable({});
2393
+ const transports = [];
2394
+ class MockClientTransport extends ClientTransport {
2395
+ async createNewOutgoingConnection(to) {
2396
+ const [clientToServer, serverToClient] = duplexPair();
2397
+ await new Promise((resolve) => setImmediate(resolve));
2398
+ const connId = (0, import_nanoid2.nanoid)();
2399
+ connections.set((prev) => ({
2400
+ ...prev,
2401
+ [connId]: {
2402
+ id: connId,
2403
+ clientToServer,
2404
+ serverToClient,
2405
+ clientId: this.clientId,
2406
+ serverId: to,
2407
+ handled: false
2408
+ }
2409
+ }));
2410
+ return new InMemoryConnection(clientToServer);
2411
+ }
2412
+ }
2413
+ class MockServerTransport extends ServerTransport {
2414
+ subscribeCleanup;
2415
+ constructor(clientId, options) {
2416
+ super(clientId, options);
2417
+ this.subscribeCleanup = connections.observe((conns) => {
2418
+ for (const conn of Object.values(conns)) {
2419
+ if (conn.handled || conn.serverId !== this.clientId) {
2420
+ continue;
2421
+ }
2422
+ conn.handled = true;
2423
+ const connection = new InMemoryConnection(conn.serverToClient);
2424
+ this.handleConnection(connection);
2425
+ }
2426
+ });
2427
+ }
2428
+ close() {
2429
+ this.subscribeCleanup();
2430
+ super.close();
2431
+ }
2432
+ }
2433
+ return {
2434
+ getClientTransport: (id, handshakeOptions) => {
2435
+ const clientTransport = new MockClientTransport(id, opts?.client);
2436
+ if (handshakeOptions) {
2437
+ clientTransport.extendHandshake(handshakeOptions);
2438
+ }
2439
+ transports.push(clientTransport);
2440
+ return clientTransport;
2441
+ },
2442
+ getServerTransport: (id = "SERVER", handshakeOptions) => {
2443
+ const serverTransport = new MockServerTransport(id, opts?.server);
2444
+ if (handshakeOptions) {
2445
+ serverTransport.extendHandshake(handshakeOptions);
2446
+ }
2447
+ transports.push(serverTransport);
2448
+ return serverTransport;
2449
+ },
2450
+ simulatePhantomDisconnect() {
2451
+ for (const conn of Object.values(connections.get())) {
2452
+ conn.serverToClient.pause();
2453
+ }
2454
+ },
2455
+ async restartServer() {
2456
+ for (const transport of transports) {
2457
+ if (transport.clientId !== "SERVER")
2458
+ continue;
2459
+ transport.close();
2460
+ }
2461
+ for (const conn of Object.values(connections.get())) {
2462
+ conn.serverToClient.end();
2463
+ conn.clientToServer.end();
2464
+ }
2465
+ },
2466
+ cleanup() {
2467
+ for (const conn of Object.values(connections.get())) {
2468
+ conn.serverToClient.end();
2469
+ conn.clientToServer.end();
2470
+ }
2471
+ }
2472
+ };
2473
+ }
2474
+
2475
+ // testUtil/index.ts
2476
+ function createLocalWebSocketClient(port) {
2477
+ const sock = new import_ws.default(`ws://localhost:${port}`);
2478
+ sock.binaryType = "arraybuffer";
2479
+ return sock;
2480
+ }
2481
+ function createWebSocketServer(server) {
2482
+ return new import_ws.WebSocketServer({ server });
2483
+ }
2484
+ function onWsServerReady(server) {
2485
+ return new Promise((resolve, reject) => {
2486
+ server.listen(() => {
2487
+ const addr = server.address();
2488
+ if (typeof addr === "object" && addr) {
2489
+ resolve(addr.port);
2490
+ } else {
2491
+ reject(new Error("couldn't find a port to allocate"));
2492
+ }
2493
+ });
2494
+ });
2495
+ }
2496
+ var readableIterators = /* @__PURE__ */ new WeakMap();
2497
+ function getReadableIterator(readable) {
2498
+ let iter = readableIterators.get(readable);
2499
+ if (!iter) {
2500
+ iter = readable[Symbol.asyncIterator]();
2501
+ readableIterators.set(readable, iter);
2502
+ }
2503
+ return iter;
2504
+ }
2505
+ async function readNextResult(readable) {
2506
+ const res = await getReadableIterator(readable).next();
2507
+ if (res.done) {
2508
+ throw new Error("readNext from a done Readable");
2509
+ }
2510
+ return res.value;
2511
+ }
2512
+ async function isReadableDone(readable) {
2513
+ const res = await getReadableIterator(readable).next();
2514
+ return res.done;
2515
+ }
2516
+ function payloadToTransportMessage(payload) {
2517
+ return {
2518
+ streamId: "stream",
2519
+ controlFlags: 0,
2520
+ payload
2521
+ };
2522
+ }
2523
+ function createDummyTransportMessage() {
2524
+ return payloadToTransportMessage({
2525
+ msg: "cool",
2526
+ test: Math.random()
2527
+ });
2528
+ }
2529
+ async function waitForMessage(t, filter, rejectMismatch) {
2530
+ return new Promise((resolve, reject) => {
2531
+ function cleanup() {
2532
+ t.removeEventListener("message", onMessage);
2533
+ }
2534
+ function onMessage(msg) {
2535
+ if (!filter || filter(msg)) {
2536
+ cleanup();
2537
+ resolve(msg.payload);
2538
+ } else if (rejectMismatch) {
2539
+ cleanup();
2540
+ reject(new Error("message didnt match the filter"));
2541
+ }
2542
+ }
2543
+ t.addEventListener("message", onMessage);
2544
+ });
2545
+ }
2546
+ var testingSessionOptions = defaultTransportOptions;
2547
+ var testingClientSessionOptions = defaultClientTransportOptions;
2548
+ function dummySession() {
2549
+ return SessionStateGraph.entrypoints.NoConnection(
2550
+ "client",
2551
+ "server",
2552
+ {
2553
+ onSessionGracePeriodElapsed: () => {
2554
+ }
2555
+ },
2556
+ testingSessionOptions,
2557
+ currentProtocolVersion
2558
+ );
2559
+ }
2560
+ function getClientSendFn(clientTransport, serverTransport) {
2561
+ const session = clientTransport.sessions.get(serverTransport.clientId) ?? clientTransport.createUnconnectedSession(serverTransport.clientId);
2562
+ return clientTransport.getSessionBoundSendFn(
2563
+ serverTransport.clientId,
2564
+ session.id
2565
+ );
2566
+ }
2567
+ function getServerSendFn(serverTransport, clientTransport) {
2568
+ const session = serverTransport.sessions.get(clientTransport.clientId);
2569
+ if (!session) {
2570
+ throw new Error("session not found");
2571
+ }
2572
+ return serverTransport.getSessionBoundSendFn(
2573
+ clientTransport.clientId,
2574
+ session.id
2575
+ );
2576
+ }
2577
+ function getTransportConnections(transport) {
2578
+ const connections = [];
2579
+ for (const session of transport.sessions.values()) {
2580
+ if (session.state === "Connected" /* Connected */) {
2581
+ connections.push(session.conn);
2582
+ }
2583
+ }
2584
+ return connections;
2585
+ }
2586
+ function numberOfConnections(transport) {
2587
+ return getTransportConnections(transport).length;
2588
+ }
2589
+ function closeAllConnections(transport) {
2590
+ for (const conn of getTransportConnections(transport)) {
2591
+ conn.close();
2592
+ }
2593
+ }
2594
+ // Annotate the CommonJS export names for ESM import in node:
2595
+ 0 && (module.exports = {
2596
+ InMemoryConnection,
2597
+ closeAllConnections,
2598
+ createDummyTransportMessage,
2599
+ createLocalWebSocketClient,
2600
+ createMockTransportNetwork,
2601
+ createWebSocketServer,
2602
+ dummySession,
2603
+ getClientSendFn,
2604
+ getReadableIterator,
2605
+ getServerSendFn,
2606
+ getTransportConnections,
2607
+ isReadableDone,
2608
+ numberOfConnections,
2609
+ onWsServerReady,
2610
+ payloadToTransportMessage,
2611
+ readNextResult,
2612
+ testingClientSessionOptions,
2613
+ testingSessionOptions,
2614
+ waitForMessage
2615
+ });
2616
+ //# sourceMappingURL=index.cjs.map