@replit/river 0.23.12 → 0.23.14

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 (75) hide show
  1. package/dist/{chunk-3AW3IXVD.js → chunk-4PVU7J25.js} +1 -21
  2. package/dist/chunk-4PVU7J25.js.map +1 -0
  3. package/dist/{chunk-HDBVL7EF.js → chunk-BEALFLCB.js} +2 -2
  4. package/dist/chunk-D2DHRRBN.js +476 -0
  5. package/dist/chunk-D2DHRRBN.js.map +1 -0
  6. package/dist/{chunk-7RUKEUKE.js → chunk-GCCRVSMR.js} +33 -4
  7. package/dist/chunk-GCCRVSMR.js.map +1 -0
  8. package/dist/{chunk-XZ6IOBM5.js → chunk-GN4YEXT7.js} +2 -2
  9. package/dist/chunk-GN4YEXT7.js.map +1 -0
  10. package/dist/chunk-O2AVDJCQ.js +335 -0
  11. package/dist/chunk-O2AVDJCQ.js.map +1 -0
  12. package/dist/chunk-OTVTKAN6.js +451 -0
  13. package/dist/chunk-OTVTKAN6.js.map +1 -0
  14. package/dist/chunk-WUL63FR6.js +335 -0
  15. package/dist/chunk-WUL63FR6.js.map +1 -0
  16. package/dist/{chunk-H6KTH6W6.js → chunk-YCLZWES2.js} +2 -2
  17. package/dist/client-e13979ac.d.ts +52 -0
  18. package/dist/codec/index.js +20 -2
  19. package/dist/codec/index.js.map +1 -1
  20. package/dist/{connection-8debd45f.d.ts → connection-5d0978ce.d.ts} +1 -1
  21. package/dist/{connection-581558f8.d.ts → connection-e57e98ea.d.ts} +1 -1
  22. package/dist/{transport-47af1c81.d.ts → handshake-5665ffd3.d.ts} +101 -153
  23. package/dist/{index-60f03cb7.d.ts → index-ea74cdbb.d.ts} +1 -1
  24. package/dist/logging/index.d.cts +1 -1
  25. package/dist/logging/index.d.ts +1 -1
  26. package/dist/router/index.cjs +16 -1
  27. package/dist/router/index.cjs.map +1 -1
  28. package/dist/router/index.d.cts +8 -6
  29. package/dist/router/index.d.ts +8 -6
  30. package/dist/router/index.js +2 -2
  31. package/dist/server-1cfc88d1.d.ts +24 -0
  32. package/dist/{services-ca72c9f8.d.ts → services-86c4d10d.d.ts} +3 -2
  33. package/dist/transport/impls/uds/client.cjs +303 -180
  34. package/dist/transport/impls/uds/client.cjs.map +1 -1
  35. package/dist/transport/impls/uds/client.d.cts +6 -5
  36. package/dist/transport/impls/uds/client.d.ts +6 -5
  37. package/dist/transport/impls/uds/client.js +6 -4
  38. package/dist/transport/impls/uds/client.js.map +1 -1
  39. package/dist/transport/impls/uds/server.cjs +396 -234
  40. package/dist/transport/impls/uds/server.cjs.map +1 -1
  41. package/dist/transport/impls/uds/server.d.cts +6 -5
  42. package/dist/transport/impls/uds/server.d.ts +6 -5
  43. package/dist/transport/impls/uds/server.js +8 -6
  44. package/dist/transport/impls/uds/server.js.map +1 -1
  45. package/dist/transport/impls/ws/client.cjs +305 -182
  46. package/dist/transport/impls/ws/client.cjs.map +1 -1
  47. package/dist/transport/impls/ws/client.d.cts +6 -5
  48. package/dist/transport/impls/ws/client.d.ts +6 -5
  49. package/dist/transport/impls/ws/client.js +6 -4
  50. package/dist/transport/impls/ws/client.js.map +1 -1
  51. package/dist/transport/impls/ws/server.cjs +350 -188
  52. package/dist/transport/impls/ws/server.cjs.map +1 -1
  53. package/dist/transport/impls/ws/server.d.cts +4 -3
  54. package/dist/transport/impls/ws/server.d.ts +4 -3
  55. package/dist/transport/impls/ws/server.js +8 -6
  56. package/dist/transport/impls/ws/server.js.map +1 -1
  57. package/dist/transport/index.cjs +338 -142
  58. package/dist/transport/index.cjs.map +1 -1
  59. package/dist/transport/index.d.cts +4 -2
  60. package/dist/transport/index.d.ts +4 -2
  61. package/dist/transport/index.js +14 -8
  62. package/dist/util/testHelpers.cjs +10 -6
  63. package/dist/util/testHelpers.cjs.map +1 -1
  64. package/dist/util/testHelpers.d.cts +5 -4
  65. package/dist/util/testHelpers.d.ts +5 -4
  66. package/dist/util/testHelpers.js +4 -5
  67. package/dist/util/testHelpers.js.map +1 -1
  68. package/package.json +13 -14
  69. package/dist/chunk-3AW3IXVD.js.map +0 -1
  70. package/dist/chunk-7RUKEUKE.js.map +0 -1
  71. package/dist/chunk-VRU4IKRT.js +0 -1392
  72. package/dist/chunk-VRU4IKRT.js.map +0 -1
  73. package/dist/chunk-XZ6IOBM5.js.map +0 -1
  74. /package/dist/{chunk-HDBVL7EF.js.map → chunk-BEALFLCB.js.map} +0 -0
  75. /package/dist/{chunk-H6KTH6W6.js.map → chunk-YCLZWES2.js.map} +0 -0
@@ -1,22 +1,3 @@
1
- // codec/binary.ts
2
- import { decode, encode } from "@msgpack/msgpack";
3
- var BinaryCodec = {
4
- toBuffer(obj) {
5
- return encode(obj, { ignoreUndefined: true });
6
- },
7
- fromBuffer: (buff) => {
8
- try {
9
- const res = decode(buff);
10
- if (typeof res !== "object") {
11
- return null;
12
- }
13
- return res;
14
- } catch {
15
- return null;
16
- }
17
- }
18
- };
19
-
20
1
  // codec/json.ts
21
2
  var encoder = new TextEncoder();
22
3
  var decoder = new TextDecoder();
@@ -70,7 +51,6 @@ var NaiveJsonCodec = {
70
51
  };
71
52
 
72
53
  export {
73
- BinaryCodec,
74
54
  NaiveJsonCodec
75
55
  };
76
- //# sourceMappingURL=chunk-3AW3IXVD.js.map
56
+ //# sourceMappingURL=chunk-4PVU7J25.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../codec/json.ts"],"sourcesContent":["import { Codec } from './types';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n// Convert Uint8Array to base64\nfunction uint8ArrayToBase64(uint8Array: Uint8Array) {\n let binary = '';\n uint8Array.forEach((byte) => {\n binary += String.fromCharCode(byte);\n });\n return btoa(binary);\n}\n\n// Convert base64 to Uint8Array\nfunction base64ToUint8Array(base64: string) {\n const binaryString = atob(base64);\n const uint8Array = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n uint8Array[i] = binaryString.charCodeAt(i);\n }\n return uint8Array;\n}\n\ninterface Base64EncodedValue {\n $t: string;\n}\n\n/**\n * Naive JSON codec implementation using JSON.stringify and JSON.parse.\n * @type {Codec}\n */\nexport const NaiveJsonCodec: Codec = {\n toBuffer: (obj: object) => {\n return encoder.encode(\n JSON.stringify(obj, function replacer<\n T extends object,\n >(this: T, key: keyof T) {\n const val = this[key];\n if (val instanceof Uint8Array) {\n return { $t: uint8ArrayToBase64(val) } satisfies Base64EncodedValue;\n } else {\n return val;\n }\n }),\n );\n },\n fromBuffer: (buff: Uint8Array) => {\n try {\n const parsed = JSON.parse(\n decoder.decode(buff),\n function reviver(_key, val: unknown) {\n if ((val as Base64EncodedValue | undefined)?.$t) {\n return base64ToUint8Array((val as Base64EncodedValue).$t);\n } else {\n return val;\n }\n },\n ) as unknown;\n\n if (typeof parsed === 'object') return parsed;\n return null;\n } catch {\n return null;\n }\n },\n};\n"],"mappings":";AAEA,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,UAAU,IAAI,YAAY;AAGhC,SAAS,mBAAmB,YAAwB;AAClD,MAAI,SAAS;AACb,aAAW,QAAQ,CAAC,SAAS;AAC3B,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC,CAAC;AACD,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,mBAAmB,QAAgB;AAC1C,QAAM,eAAe,KAAK,MAAM;AAChC,QAAM,aAAa,IAAI,WAAW,aAAa,MAAM;AACrD,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,eAAW,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAUO,IAAM,iBAAwB;AAAA,EACnC,UAAU,CAAC,QAAgB;AACzB,WAAO,QAAQ;AAAA,MACb,KAAK,UAAU,KAAK,SAAS,SAElB,KAAc;AACvB,cAAM,MAAM,KAAK,GAAG;AACpB,YAAI,eAAe,YAAY;AAC7B,iBAAO,EAAE,IAAI,mBAAmB,GAAG,EAAE;AAAA,QACvC,OAAO;AACL,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,YAAY,CAAC,SAAqB;AAChC,QAAI;AACF,YAAM,SAAS,KAAK;AAAA,QAClB,QAAQ,OAAO,IAAI;AAAA,QACnB,SAAS,QAAQ,MAAM,KAAc;AACnC,cAAK,KAAwC,IAAI;AAC/C,mBAAO,mBAAoB,IAA2B,EAAE;AAAA,UAC1D,OAAO;AACL,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO,WAAW;AAAU,eAAO;AACvC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Connection
3
- } from "./chunk-VRU4IKRT.js";
3
+ } from "./chunk-O2AVDJCQ.js";
4
4
 
5
5
  // transport/impls/ws/connection.ts
6
6
  var WebSocketConnection = class extends Connection {
@@ -56,4 +56,4 @@ var WebSocketConnection = class extends Connection {
56
56
  export {
57
57
  WebSocketConnection
58
58
  };
59
- //# sourceMappingURL=chunk-HDBVL7EF.js.map
59
+ //# sourceMappingURL=chunk-BEALFLCB.js.map
@@ -0,0 +1,476 @@
1
+ import {
2
+ ProtocolError,
3
+ Transport
4
+ } from "./chunk-OTVTKAN6.js";
5
+ import {
6
+ defaultClientTransportOptions
7
+ } from "./chunk-O2AVDJCQ.js";
8
+ import {
9
+ ControlMessageHandshakeResponseSchema,
10
+ SESSION_STATE_MISMATCH,
11
+ coerceErrorString,
12
+ getPropagationContext,
13
+ handshakeRequestMessage,
14
+ tracing_default
15
+ } from "./chunk-GCCRVSMR.js";
16
+
17
+ // transport/client.ts
18
+ import { SpanStatusCode } from "@opentelemetry/api";
19
+
20
+ // transport/rateLimit.ts
21
+ var LeakyBucketRateLimit = class {
22
+ budgetConsumed;
23
+ intervalHandles;
24
+ options;
25
+ constructor(options) {
26
+ this.options = options;
27
+ this.budgetConsumed = /* @__PURE__ */ new Map();
28
+ this.intervalHandles = /* @__PURE__ */ new Map();
29
+ }
30
+ getBackoffMs(user) {
31
+ if (!this.budgetConsumed.has(user))
32
+ return 0;
33
+ const exponent = Math.max(0, this.getBudgetConsumed(user) - 1);
34
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
35
+ const backoffMs = Math.min(
36
+ this.options.baseIntervalMs * 2 ** exponent,
37
+ this.options.maxBackoffMs
38
+ );
39
+ return backoffMs + jitter;
40
+ }
41
+ get totalBudgetRestoreTime() {
42
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
43
+ }
44
+ consumeBudget(user) {
45
+ this.stopLeak(user);
46
+ this.budgetConsumed.set(user, this.getBudgetConsumed(user) + 1);
47
+ }
48
+ getBudgetConsumed(user) {
49
+ return this.budgetConsumed.get(user) ?? 0;
50
+ }
51
+ hasBudget(user) {
52
+ return this.getBudgetConsumed(user) < this.options.attemptBudgetCapacity;
53
+ }
54
+ startRestoringBudget(user) {
55
+ if (this.intervalHandles.has(user)) {
56
+ return;
57
+ }
58
+ const restoreBudgetForUser = () => {
59
+ const currentBudget = this.budgetConsumed.get(user);
60
+ if (!currentBudget) {
61
+ this.stopLeak(user);
62
+ return;
63
+ }
64
+ const newBudget = currentBudget - 1;
65
+ if (newBudget === 0) {
66
+ this.budgetConsumed.delete(user);
67
+ return;
68
+ }
69
+ this.budgetConsumed.set(user, newBudget);
70
+ };
71
+ const intervalHandle = setInterval(
72
+ restoreBudgetForUser,
73
+ this.options.budgetRestoreIntervalMs
74
+ );
75
+ this.intervalHandles.set(user, intervalHandle);
76
+ }
77
+ stopLeak(user) {
78
+ if (!this.intervalHandles.has(user)) {
79
+ return;
80
+ }
81
+ clearInterval(this.intervalHandles.get(user));
82
+ this.intervalHandles.delete(user);
83
+ }
84
+ close() {
85
+ for (const user of this.intervalHandles.keys()) {
86
+ this.stopLeak(user);
87
+ }
88
+ }
89
+ };
90
+
91
+ // transport/client.ts
92
+ import { Value } from "@sinclair/typebox/value";
93
+ var ClientTransport = class extends Transport {
94
+ /**
95
+ * The options for this transport.
96
+ */
97
+ options;
98
+ /**
99
+ * The map of reconnect promises for each client ID.
100
+ */
101
+ inflightConnectionPromises;
102
+ retryBudget;
103
+ /**
104
+ * A flag indicating whether the transport should automatically reconnect
105
+ * when a connection is dropped.
106
+ * Realistically, this should always be true for clients unless you are writing
107
+ * tests or a special case where you don't want to reconnect.
108
+ */
109
+ reconnectOnConnectionDrop = true;
110
+ /**
111
+ * Optional handshake options for this client.
112
+ */
113
+ handshakeExtensions;
114
+ constructor(clientId, providedOptions) {
115
+ super(clientId, providedOptions);
116
+ this.options = {
117
+ ...defaultClientTransportOptions,
118
+ ...providedOptions
119
+ };
120
+ this.inflightConnectionPromises = /* @__PURE__ */ new Map();
121
+ this.retryBudget = new LeakyBucketRateLimit(this.options);
122
+ }
123
+ extendHandshake(options) {
124
+ this.handshakeExtensions = options;
125
+ }
126
+ handleConnection(conn, to) {
127
+ if (this.getStatus() !== "open")
128
+ return;
129
+ let session = void 0;
130
+ const handshakeTimeout = setTimeout(() => {
131
+ if (session)
132
+ return;
133
+ this.log?.warn(
134
+ `connection to ${to} timed out waiting for handshake, closing`,
135
+ { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to }
136
+ );
137
+ conn.close();
138
+ }, this.options.sessionDisconnectGraceMs);
139
+ const handshakeHandler = (data) => {
140
+ const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
141
+ clearTimeout(handshakeTimeout);
142
+ if (!maybeSession) {
143
+ conn.close();
144
+ return;
145
+ } else {
146
+ session = maybeSession;
147
+ }
148
+ conn.removeDataListener(handshakeHandler);
149
+ conn.addDataListener((data2) => {
150
+ const parsed = this.parseMsg(data2, conn);
151
+ if (!parsed) {
152
+ conn.telemetry?.span.setStatus({
153
+ code: SpanStatusCode.ERROR,
154
+ message: "message parse failure"
155
+ });
156
+ conn.close();
157
+ return;
158
+ }
159
+ this.handleMsg(parsed, conn);
160
+ });
161
+ };
162
+ conn.addDataListener(handshakeHandler);
163
+ conn.addCloseListener(() => {
164
+ if (session) {
165
+ this.onDisconnect(conn, session);
166
+ }
167
+ const willReconnect = this.reconnectOnConnectionDrop && this.getStatus() === "open";
168
+ this.log?.info(
169
+ `connection to ${to} disconnected` + (willReconnect ? ", reconnecting" : ""),
170
+ {
171
+ ...conn.loggingMetadata,
172
+ ...session?.loggingMetadata,
173
+ clientId: this.clientId,
174
+ connectedTo: to
175
+ }
176
+ );
177
+ this.inflightConnectionPromises.delete(to);
178
+ if (this.reconnectOnConnectionDrop) {
179
+ void this.connect(to);
180
+ }
181
+ });
182
+ conn.addErrorListener((err) => {
183
+ conn.telemetry?.span.setStatus({
184
+ code: SpanStatusCode.ERROR,
185
+ message: "connection error"
186
+ });
187
+ this.log?.warn(
188
+ `error in connection to ${to}: ${coerceErrorString(err)}`,
189
+ {
190
+ ...conn.loggingMetadata,
191
+ ...session?.loggingMetadata,
192
+ clientId: this.clientId,
193
+ connectedTo: to
194
+ }
195
+ );
196
+ });
197
+ }
198
+ receiveHandshakeResponseMessage(data, conn) {
199
+ const parsed = this.parseMsg(data, conn);
200
+ if (!parsed) {
201
+ conn.telemetry?.span.setStatus({
202
+ code: SpanStatusCode.ERROR,
203
+ message: "non-transport message"
204
+ });
205
+ this.protocolError(
206
+ ProtocolError.HandshakeFailed,
207
+ "received non-transport message"
208
+ );
209
+ return false;
210
+ }
211
+ if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
212
+ conn.telemetry?.span.setStatus({
213
+ code: SpanStatusCode.ERROR,
214
+ message: "invalid handshake response"
215
+ });
216
+ this.log?.warn(`received invalid handshake resp`, {
217
+ ...conn.loggingMetadata,
218
+ clientId: this.clientId,
219
+ connectedTo: parsed.from,
220
+ transportMessage: parsed,
221
+ validationErrors: [
222
+ ...Value.Errors(
223
+ ControlMessageHandshakeResponseSchema,
224
+ parsed.payload
225
+ )
226
+ ]
227
+ });
228
+ this.protocolError(
229
+ ProtocolError.HandshakeFailed,
230
+ "invalid handshake resp"
231
+ );
232
+ return false;
233
+ }
234
+ const previousSession = this.sessions.get(parsed.from);
235
+ if (!parsed.payload.status.ok) {
236
+ if (parsed.payload.status.reason === SESSION_STATE_MISMATCH) {
237
+ if (previousSession) {
238
+ this.deleteSession({
239
+ session: previousSession,
240
+ closeHandshakingConnection: true
241
+ });
242
+ }
243
+ conn.telemetry?.span.setStatus({
244
+ code: SpanStatusCode.ERROR,
245
+ message: parsed.payload.status.reason
246
+ });
247
+ } else {
248
+ conn.telemetry?.span.setStatus({
249
+ code: SpanStatusCode.ERROR,
250
+ message: "handshake rejected"
251
+ });
252
+ }
253
+ this.log?.warn(
254
+ `received handshake rejection: ${parsed.payload.status.reason}`,
255
+ {
256
+ ...conn.loggingMetadata,
257
+ clientId: this.clientId,
258
+ connectedTo: parsed.from,
259
+ transportMessage: parsed
260
+ }
261
+ );
262
+ this.protocolError(
263
+ ProtocolError.HandshakeFailed,
264
+ parsed.payload.status.reason
265
+ );
266
+ return false;
267
+ }
268
+ if (previousSession?.advertisedSessionId && previousSession.advertisedSessionId !== parsed.payload.status.sessionId) {
269
+ this.deleteSession({
270
+ session: previousSession,
271
+ closeHandshakingConnection: true
272
+ });
273
+ conn.telemetry?.span.setStatus({
274
+ code: SpanStatusCode.ERROR,
275
+ message: "session id mismatch"
276
+ });
277
+ this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
278
+ ...conn.loggingMetadata,
279
+ clientId: this.clientId,
280
+ connectedTo: parsed.from,
281
+ transportMessage: parsed
282
+ });
283
+ this.protocolError(ProtocolError.HandshakeFailed, "session id mismatch");
284
+ return false;
285
+ }
286
+ this.log?.debug(`handshake from ${parsed.from} ok`, {
287
+ ...conn.loggingMetadata,
288
+ clientId: this.clientId,
289
+ connectedTo: parsed.from,
290
+ transportMessage: parsed
291
+ });
292
+ const { session, isTransparentReconnect } = this.getOrCreateSession({
293
+ to: parsed.from,
294
+ conn,
295
+ sessionId: parsed.payload.status.sessionId
296
+ });
297
+ this.onConnect(conn, session, isTransparentReconnect);
298
+ this.retryBudget.startRestoringBudget(session.to);
299
+ return session;
300
+ }
301
+ /**
302
+ * Manually attempts to connect to a client.
303
+ * @param to The client ID of the node to connect to.
304
+ */
305
+ async connect(to) {
306
+ if (this.connections.has(to)) {
307
+ this.log?.info(`already connected to ${to}, skipping connect attempt`, {
308
+ clientId: this.clientId,
309
+ connectedTo: to
310
+ });
311
+ return;
312
+ }
313
+ const canProceedWithConnection = () => this.getStatus() === "open";
314
+ if (!canProceedWithConnection()) {
315
+ this.log?.info(
316
+ `transport state is no longer open, cancelling attempt to connect to ${to}`,
317
+ { clientId: this.clientId, connectedTo: to }
318
+ );
319
+ return;
320
+ }
321
+ let reconnectPromise = this.inflightConnectionPromises.get(to);
322
+ if (!reconnectPromise) {
323
+ if (!this.retryBudget.hasBudget(to)) {
324
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
325
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
326
+ this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });
327
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
328
+ return;
329
+ }
330
+ let sleep = Promise.resolve();
331
+ const backoffMs = this.retryBudget.getBackoffMs(to);
332
+ if (backoffMs > 0) {
333
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
334
+ }
335
+ this.log?.info(
336
+ `attempting connection to ${to} (${backoffMs}ms backoff)`,
337
+ {
338
+ clientId: this.clientId,
339
+ connectedTo: to
340
+ }
341
+ );
342
+ this.retryBudget.consumeBudget(to);
343
+ reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
344
+ try {
345
+ span.addEvent("backoff", { backoffMs });
346
+ await sleep;
347
+ if (!canProceedWithConnection()) {
348
+ throw new Error("transport state is no longer open");
349
+ }
350
+ span.addEvent("connecting");
351
+ const conn = await this.createNewOutgoingConnection(to);
352
+ if (!canProceedWithConnection()) {
353
+ this.log?.info(
354
+ `transport state is no longer open, closing pre-handshake connection to ${to}`,
355
+ {
356
+ ...conn.loggingMetadata,
357
+ clientId: this.clientId,
358
+ connectedTo: to
359
+ }
360
+ );
361
+ conn.close();
362
+ throw new Error("transport state is no longer open");
363
+ }
364
+ span.addEvent("sending handshake");
365
+ const ok = await this.sendHandshake(to, conn);
366
+ if (!ok) {
367
+ conn.close();
368
+ throw new Error("failed to send handshake");
369
+ }
370
+ return conn;
371
+ } catch (err) {
372
+ const errStr = coerceErrorString(err);
373
+ span.recordException(errStr);
374
+ span.setStatus({ code: SpanStatusCode.ERROR });
375
+ throw err;
376
+ } finally {
377
+ span.end();
378
+ }
379
+ });
380
+ this.inflightConnectionPromises.set(to, reconnectPromise);
381
+ } else {
382
+ this.log?.info(
383
+ `attempting connection to ${to} (reusing previous attempt)`,
384
+ {
385
+ clientId: this.clientId,
386
+ connectedTo: to
387
+ }
388
+ );
389
+ }
390
+ try {
391
+ await reconnectPromise;
392
+ } catch (error) {
393
+ this.inflightConnectionPromises.delete(to);
394
+ const errStr = coerceErrorString(error);
395
+ if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
396
+ this.log?.warn(`connection to ${to} failed (${errStr})`, {
397
+ clientId: this.clientId,
398
+ connectedTo: to
399
+ });
400
+ } else {
401
+ this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
402
+ clientId: this.clientId,
403
+ connectedTo: to
404
+ });
405
+ await this.connect(to);
406
+ }
407
+ }
408
+ }
409
+ deleteSession({
410
+ session,
411
+ closeHandshakingConnection,
412
+ handshakingConn
413
+ }) {
414
+ this.inflightConnectionPromises.delete(session.to);
415
+ super.deleteSession({
416
+ session,
417
+ closeHandshakingConnection,
418
+ handshakingConn
419
+ });
420
+ }
421
+ async sendHandshake(to, conn) {
422
+ let metadata = void 0;
423
+ if (this.handshakeExtensions) {
424
+ metadata = await this.handshakeExtensions.construct();
425
+ if (!Value.Check(this.handshakeExtensions.schema, metadata)) {
426
+ this.log?.error(`constructed handshake metadata did not match schema`, {
427
+ ...conn.loggingMetadata,
428
+ clientId: this.clientId,
429
+ connectedTo: to,
430
+ validationErrors: [
431
+ ...Value.Errors(this.handshakeExtensions.schema, metadata)
432
+ ],
433
+ tags: ["invariant-violation"]
434
+ });
435
+ this.protocolError(
436
+ ProtocolError.HandshakeFailed,
437
+ "handshake metadata did not match schema"
438
+ );
439
+ conn.telemetry?.span.setStatus({
440
+ code: SpanStatusCode.ERROR,
441
+ message: "handshake meta mismatch"
442
+ });
443
+ return false;
444
+ }
445
+ }
446
+ const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
447
+ const requestMsg = handshakeRequestMessage({
448
+ from: this.clientId,
449
+ to,
450
+ sessionId: session.id,
451
+ expectedSessionState: {
452
+ reconnect: session.advertisedSessionId !== void 0,
453
+ nextExpectedSeq: session.nextExpectedSeq
454
+ },
455
+ metadata,
456
+ tracing: getPropagationContext(session.telemetry.ctx)
457
+ });
458
+ this.log?.debug(`sending handshake request to ${to}`, {
459
+ ...conn.loggingMetadata,
460
+ clientId: this.clientId,
461
+ connectedTo: to,
462
+ transportMessage: requestMsg
463
+ });
464
+ conn.send(this.codec.toBuffer(requestMsg));
465
+ return true;
466
+ }
467
+ close() {
468
+ this.retryBudget.close();
469
+ super.close();
470
+ }
471
+ };
472
+
473
+ export {
474
+ ClientTransport
475
+ };
476
+ //# sourceMappingURL=chunk-D2DHRRBN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../transport/client.ts","../transport/rateLimit.ts"],"sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport { ClientHandshakeOptions } from '../router/handshake';\nimport {\n ControlMessageHandshakeResponseSchema,\n SESSION_STATE_MISMATCH,\n TransportClientId,\n handshakeRequestMessage,\n} from './message';\nimport {\n ClientTransportOptions,\n ProvidedClientTransportOptions,\n defaultClientTransportOptions,\n} from './options';\nimport { LeakyBucketRateLimit } from './rateLimit';\nimport { Connection, Session } from './session';\nimport { Transport } from './transport';\nimport { coerceErrorString } from '../util/stringify';\nimport { ProtocolError } from './events';\nimport { Value } from '@sinclair/typebox/value';\nimport tracer, { getPropagationContext } from '../tracing';\n\nexport abstract class ClientTransport<\n ConnType extends Connection,\n> extends Transport<ConnType> {\n /**\n * The options for this transport.\n */\n protected options: ClientTransportOptions;\n\n /**\n * The map of reconnect promises for each client ID.\n */\n inflightConnectionPromises: Map<TransportClientId, Promise<ConnType>>;\n retryBudget: LeakyBucketRateLimit;\n\n /**\n * A flag indicating whether the transport should automatically reconnect\n * when a connection is dropped.\n * Realistically, this should always be true for clients unless you are writing\n * tests or a special case where you don't want to reconnect.\n */\n reconnectOnConnectionDrop = true;\n\n /**\n * Optional handshake options for this client.\n */\n handshakeExtensions?: ClientHandshakeOptions;\n\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedClientTransportOptions,\n ) {\n super(clientId, providedOptions);\n this.options = {\n ...defaultClientTransportOptions,\n ...providedOptions,\n };\n this.inflightConnectionPromises = new Map();\n this.retryBudget = new LeakyBucketRateLimit(this.options);\n }\n\n extendHandshake(options: ClientHandshakeOptions) {\n this.handshakeExtensions = options;\n }\n\n protected handleConnection(conn: ConnType, to: TransportClientId): void {\n if (this.getStatus() !== 'open') return;\n let session: Session<ConnType> | undefined = undefined;\n\n // kill the conn after the grace period if we haven't received a handshake\n const handshakeTimeout = setTimeout(() => {\n if (session) return;\n this.log?.warn(\n `connection to ${to} timed out waiting for handshake, closing`,\n { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to },\n );\n conn.close();\n }, this.options.sessionDisconnectGraceMs);\n\n const handshakeHandler = (data: Uint8Array) => {\n const maybeSession = this.receiveHandshakeResponseMessage(data, conn);\n clearTimeout(handshakeTimeout);\n if (!maybeSession) {\n conn.close();\n return;\n } else {\n session = maybeSession;\n }\n\n // when we are done handshake sequence,\n // remove handshake listener and use the normal message listener\n conn.removeDataListener(handshakeHandler);\n conn.addDataListener((data) => {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message parse failure',\n });\n conn.close();\n return;\n }\n\n this.handleMsg(parsed, conn);\n });\n };\n\n conn.addDataListener(handshakeHandler);\n conn.addCloseListener(() => {\n if (session) {\n this.onDisconnect(conn, session);\n }\n\n const willReconnect =\n this.reconnectOnConnectionDrop && this.getStatus() === 'open';\n\n this.log?.info(\n `connection to ${to} disconnected` +\n (willReconnect ? ', reconnecting' : ''),\n {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n\n this.inflightConnectionPromises.delete(to);\n if (this.reconnectOnConnectionDrop) {\n void this.connect(to);\n }\n });\n conn.addErrorListener((err) => {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'connection error',\n });\n this.log?.warn(\n `error in connection to ${to}: ${coerceErrorString(err)}`,\n {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n });\n }\n\n receiveHandshakeResponseMessage(\n data: Uint8Array,\n conn: ConnType,\n ): Session<ConnType> | false {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'non-transport message',\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'received non-transport message',\n );\n return false;\n }\n\n if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'invalid handshake response',\n });\n this.log?.warn(`received invalid handshake resp`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n validationErrors: [\n ...Value.Errors(\n ControlMessageHandshakeResponseSchema,\n parsed.payload,\n ),\n ],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'invalid handshake resp',\n );\n return false;\n }\n\n const previousSession = this.sessions.get(parsed.from);\n if (!parsed.payload.status.ok) {\n if (parsed.payload.status.reason === SESSION_STATE_MISMATCH) {\n if (previousSession) {\n // The server has told us that we cannot continue with the session because it has the\n // wrong state. We should delete this session and start fresh.\n this.deleteSession({\n session: previousSession,\n closeHandshakingConnection: true,\n });\n }\n\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: parsed.payload.status.reason,\n });\n } else {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake rejected',\n });\n }\n this.log?.warn(\n `received handshake rejection: ${parsed.payload.status.reason}`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n },\n );\n this.protocolError(\n ProtocolError.HandshakeFailed,\n parsed.payload.status.reason,\n );\n return false;\n }\n\n // before we claim victory and we deem that the handshake is fully established, check that our\n // session matches the remote's. if they do not match, proactively close the connection.\n // otherwise we will end up breaking a lot of invariants.\n //\n // TODO: Remove this once we finish rolling out the handshake-initiated session agreement.\n if (\n previousSession?.advertisedSessionId &&\n previousSession.advertisedSessionId !== parsed.payload.status.sessionId\n ) {\n this.deleteSession({\n session: previousSession,\n closeHandshakingConnection: true,\n });\n\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'session id mismatch',\n });\n this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n this.protocolError(ProtocolError.HandshakeFailed, 'session id mismatch');\n return false;\n }\n\n this.log?.debug(`handshake from ${parsed.from} ok`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n\n const { session, isTransparentReconnect } = this.getOrCreateSession({\n to: parsed.from,\n conn,\n sessionId: parsed.payload.status.sessionId,\n });\n\n this.onConnect(conn, session, isTransparentReconnect);\n\n // After a successful connection, we start restoring the budget\n // so that the next time we try to connect, we don't hit the client\n // with backoff forever.\n this.retryBudget.startRestoringBudget(session.to);\n return session;\n }\n\n /**\n * Abstract method that creates a new {@link Connection} object.\n * This should call {@link handleConnection} when the connection is created.\n * The downstream client implementation needs to implement this.\n *\n * @param to The client ID of the node to connect to.\n * @returns The new connection object.\n */\n protected abstract createNewOutgoingConnection(\n to: TransportClientId,\n ): Promise<ConnType>;\n\n /**\n * Manually attempts to connect to a client.\n * @param to The client ID of the node to connect to.\n */\n async connect(to: TransportClientId): Promise<void> {\n if (this.connections.has(to)) {\n this.log?.info(`already connected to ${to}, skipping connect attempt`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n return;\n }\n\n const canProceedWithConnection = () => this.getStatus() === 'open';\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, cancelling attempt to connect to ${to}`,\n { clientId: this.clientId, connectedTo: to },\n );\n return;\n }\n\n let reconnectPromise = this.inflightConnectionPromises.get(to);\n if (!reconnectPromise) {\n // check budget\n if (!this.retryBudget.hasBudget(to)) {\n const budgetConsumed = this.retryBudget.getBudgetConsumed(to);\n const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;\n this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });\n this.protocolError(ProtocolError.RetriesExceeded, errMsg);\n return;\n }\n\n let sleep = Promise.resolve();\n const backoffMs = this.retryBudget.getBackoffMs(to);\n if (backoffMs > 0) {\n sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n\n this.log?.info(\n `attempting connection to ${to} (${backoffMs}ms backoff)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n this.retryBudget.consumeBudget(to);\n reconnectPromise = tracer.startActiveSpan('connect', async (span) => {\n try {\n span.addEvent('backoff', { backoffMs });\n await sleep;\n if (!canProceedWithConnection()) {\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('connecting');\n const conn = await this.createNewOutgoingConnection(to);\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, closing pre-handshake connection to ${to}`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n conn.close();\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('sending handshake');\n const ok = await this.sendHandshake(to, conn);\n if (!ok) {\n conn.close();\n throw new Error('failed to send handshake');\n }\n\n return conn;\n } catch (err) {\n // rethrow the error so that the promise is rejected\n // as it was before we wrapped it in a span\n const errStr = coerceErrorString(err);\n span.recordException(errStr);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw err;\n } finally {\n span.end();\n }\n });\n\n this.inflightConnectionPromises.set(to, reconnectPromise);\n } else {\n this.log?.info(\n `attempting connection to ${to} (reusing previous attempt)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n }\n\n try {\n await reconnectPromise;\n } catch (error: unknown) {\n this.inflightConnectionPromises.delete(to);\n const errStr = coerceErrorString(error);\n\n if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {\n this.log?.warn(`connection to ${to} failed (${errStr})`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n } else {\n this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n await this.connect(to);\n }\n }\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n this.inflightConnectionPromises.delete(session.to);\n super.deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n });\n }\n\n protected async sendHandshake(to: TransportClientId, conn: ConnType) {\n let metadata: unknown = undefined;\n\n if (this.handshakeExtensions) {\n metadata = await this.handshakeExtensions.construct();\n if (!Value.Check(this.handshakeExtensions.schema, metadata)) {\n this.log?.error(`constructed handshake metadata did not match schema`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n validationErrors: [\n ...Value.Errors(this.handshakeExtensions.schema, metadata),\n ],\n tags: ['invariant-violation'],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'handshake metadata did not match schema',\n );\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake meta mismatch',\n });\n return false;\n }\n }\n\n // don't pass conn here as we dont want the session to start using the conn\n // until we have finished the handshake. Still, let the session know that\n // it is semi-associated with the conn, and it can close it if .close() is called.\n const { session } = this.getOrCreateSession({ to, handshakingConn: conn });\n const requestMsg = handshakeRequestMessage({\n from: this.clientId,\n to,\n sessionId: session.id,\n expectedSessionState: {\n reconnect: session.advertisedSessionId !== undefined,\n nextExpectedSeq: session.nextExpectedSeq,\n },\n metadata,\n tracing: getPropagationContext(session.telemetry.ctx),\n });\n this.log?.debug(`sending handshake request to ${to}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n transportMessage: requestMsg,\n });\n conn.send(this.codec.toBuffer(requestMsg));\n return true;\n }\n\n close() {\n this.retryBudget.close();\n super.close();\n }\n}\n","import { TransportClientId } from './message';\n\n/**\n * Options to control the backoff and retry behavior of the client transport's connection behaviour.\n *\n * River implements exponential backoff with jitter to prevent flooding the server\n * when there's an issue with connection establishment.\n *\n * The backoff is calculated via the following:\n * backOff = min(jitter + {@link baseIntervalMs} * 2 ^ budget_consumed, {@link maxBackoffMs})\n *\n * We use a leaky bucket rate limit with a budget of {@link attemptBudgetCapacity} reconnection attempts.\n * Budget only starts to restore after a successful handshake at a rate of one budget per {@link budgetRestoreIntervalMs}.\n */\nexport interface ConnectionRetryOptions {\n /**\n * The base interval to wait before retrying a connection.\n */\n baseIntervalMs: number;\n\n /**\n * The maximum random jitter to add to the total backoff time.\n */\n maxJitterMs: number;\n\n /**\n * The maximum amount of time to wait before retrying a connection.\n * This does not include the jitter.\n */\n maxBackoffMs: number;\n\n /**\n * The max number of times to attempt a connection before a successful handshake.\n * This persists across connections but starts restoring budget after a successful handshake.\n * The restoration interval depends on {@link budgetRestoreIntervalMs}\n */\n attemptBudgetCapacity: number;\n\n /**\n * After a successful connection attempt, how long to wait before we restore a single budget.\n */\n budgetRestoreIntervalMs: number;\n}\n\nexport class LeakyBucketRateLimit {\n private budgetConsumed: Map<TransportClientId, number>;\n private intervalHandles: Map<\n TransportClientId,\n ReturnType<typeof setInterval>\n >;\n private readonly options: ConnectionRetryOptions;\n\n constructor(options: ConnectionRetryOptions) {\n this.options = options;\n this.budgetConsumed = new Map();\n this.intervalHandles = new Map();\n }\n\n getBackoffMs(user: TransportClientId) {\n if (!this.budgetConsumed.has(user)) return 0;\n\n const exponent = Math.max(0, this.getBudgetConsumed(user) - 1);\n const jitter = Math.floor(Math.random() * this.options.maxJitterMs);\n const backoffMs = Math.min(\n this.options.baseIntervalMs * 2 ** exponent,\n this.options.maxBackoffMs,\n );\n\n return backoffMs + jitter;\n }\n\n get totalBudgetRestoreTime() {\n return (\n this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity\n );\n }\n\n consumeBudget(user: TransportClientId) {\n // If we're consuming again, let's ensure that we're not leaking\n this.stopLeak(user);\n this.budgetConsumed.set(user, this.getBudgetConsumed(user) + 1);\n }\n\n getBudgetConsumed(user: TransportClientId) {\n return this.budgetConsumed.get(user) ?? 0;\n }\n\n hasBudget(user: TransportClientId) {\n return this.getBudgetConsumed(user) < this.options.attemptBudgetCapacity;\n }\n\n startRestoringBudget(user: TransportClientId) {\n if (this.intervalHandles.has(user)) {\n return;\n }\n\n const restoreBudgetForUser = () => {\n const currentBudget = this.budgetConsumed.get(user);\n if (!currentBudget) {\n this.stopLeak(user);\n return;\n }\n\n const newBudget = currentBudget - 1;\n if (newBudget === 0) {\n this.budgetConsumed.delete(user);\n return;\n }\n\n this.budgetConsumed.set(user, newBudget);\n };\n\n const intervalHandle = setInterval(\n restoreBudgetForUser,\n this.options.budgetRestoreIntervalMs,\n );\n\n this.intervalHandles.set(user, intervalHandle);\n }\n\n private stopLeak(user: TransportClientId) {\n if (!this.intervalHandles.has(user)) {\n return;\n }\n\n clearInterval(this.intervalHandles.get(user));\n this.intervalHandles.delete(user);\n }\n\n close() {\n for (const user of this.intervalHandles.keys()) {\n this.stopLeak(user);\n }\n }\n}\n"],"mappings":"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","names":["data"]}
@@ -30,6 +30,21 @@ var ControlMessageHandshakeRequestSchema = Type.Object({
30
30
  type: Type.Literal("HANDSHAKE_REQ"),
31
31
  protocolVersion: Type.String(),
32
32
  sessionId: Type.String(),
33
+ /**
34
+ * Specifies what the server's expected session state (from the pov of the client). This can be
35
+ * used by the server to know whether this is a new or a reestablished connection, and whether it
36
+ * is compatible with what it already has.
37
+ */
38
+ expectedSessionState: Type.Optional(
39
+ Type.Object({
40
+ /**
41
+ * reconnect is set to true if the client explicitly wants to reestablish an existing
42
+ * connection.
43
+ */
44
+ reconnect: Type.Boolean(),
45
+ nextExpectedSeq: Type.Integer()
46
+ })
47
+ ),
33
48
  metadata: Type.Optional(Type.Unknown())
34
49
  });
35
50
  var ControlMessageHandshakeResponseSchema = Type.Object({
@@ -54,7 +69,14 @@ var ControlMessagePayloadSchema = Type.Union([
54
69
  var OpaqueTransportMessageSchema = TransportMessageSchema(
55
70
  Type.Unknown()
56
71
  );
57
- function handshakeRequestMessage(from, to, sessionId, metadata, tracing) {
72
+ function handshakeRequestMessage({
73
+ from,
74
+ to,
75
+ sessionId,
76
+ expectedSessionState,
77
+ metadata,
78
+ tracing
79
+ }) {
58
80
  return {
59
81
  id: nanoid(),
60
82
  from,
@@ -68,11 +90,17 @@ function handshakeRequestMessage(from, to, sessionId, metadata, tracing) {
68
90
  type: "HANDSHAKE_REQ",
69
91
  protocolVersion: PROTOCOL_VERSION,
70
92
  sessionId,
93
+ expectedSessionState,
71
94
  metadata
72
95
  }
73
96
  };
74
97
  }
75
- function handshakeResponseMessage(from, to, status) {
98
+ var SESSION_STATE_MISMATCH = "session state mismatch";
99
+ function handshakeResponseMessage({
100
+ from,
101
+ to,
102
+ status
103
+ }) {
76
104
  return {
77
105
  id: nanoid(),
78
106
  from,
@@ -104,7 +132,7 @@ function isStreamClose(controlFlag) {
104
132
  }
105
133
 
106
134
  // package.json
107
- var version = "0.23.12";
135
+ var version = "0.23.14";
108
136
 
109
137
  // util/stringify.ts
110
138
  function coerceErrorString(err) {
@@ -222,6 +250,7 @@ export {
222
250
  ControlMessagePayloadSchema,
223
251
  OpaqueTransportMessageSchema,
224
252
  handshakeRequestMessage,
253
+ SESSION_STATE_MISMATCH,
225
254
  handshakeResponseMessage,
226
255
  isAck,
227
256
  isStreamOpen,
@@ -235,4 +264,4 @@ export {
235
264
  tracing_default,
236
265
  coerceErrorString
237
266
  };
238
- //# sourceMappingURL=chunk-7RUKEUKE.js.map
267
+ //# sourceMappingURL=chunk-GCCRVSMR.js.map