rivetkit 2.0.6 → 2.0.7

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 (170) hide show
  1. package/dist/schemas/actor-persist/v1.ts +0 -6
  2. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.cts +28 -0
  3. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.ts +28 -0
  4. package/dist/tsup/{chunk-7OUKNSTU.js → chunk-2NL3KGJ7.js} +17 -14
  5. package/dist/tsup/chunk-2NL3KGJ7.js.map +1 -0
  6. package/dist/tsup/{chunk-6P6RA47N.cjs → chunk-3ALZ7EGX.cjs} +14 -10
  7. package/dist/tsup/chunk-3ALZ7EGX.cjs.map +1 -0
  8. package/dist/tsup/chunk-4EXJ4ITR.cjs +102 -0
  9. package/dist/tsup/chunk-4EXJ4ITR.cjs.map +1 -0
  10. package/dist/tsup/{chunk-ZYLTS2EM.js → chunk-54MAHBLL.js} +2 -2
  11. package/dist/tsup/{chunk-NTCUGYSD.cjs → chunk-7OOBMCQI.cjs} +34 -31
  12. package/dist/tsup/chunk-7OOBMCQI.cjs.map +1 -0
  13. package/dist/tsup/{chunk-VCEHU56K.js → chunk-B6N6VM37.js} +2 -2
  14. package/dist/tsup/{chunk-VPV4MWXR.js → chunk-DIHKN7NM.js} +3 -3
  15. package/dist/tsup/{chunk-MRRT2CZD.cjs → chunk-ETDWYT2P.cjs} +7 -7
  16. package/dist/tsup/{chunk-MRRT2CZD.cjs.map → chunk-ETDWYT2P.cjs.map} +1 -1
  17. package/dist/tsup/{chunk-TWGATZ3X.cjs → chunk-F7YL5G7Q.cjs} +922 -872
  18. package/dist/tsup/chunk-F7YL5G7Q.cjs.map +1 -0
  19. package/dist/tsup/{chunk-UTI5NCES.cjs → chunk-GWJTWY3G.cjs} +6 -6
  20. package/dist/tsup/{chunk-UTI5NCES.cjs.map → chunk-GWJTWY3G.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-W6LN7AF5.js → chunk-KHRZPP5T.js} +866 -816
  22. package/dist/tsup/chunk-KHRZPP5T.js.map +1 -0
  23. package/dist/tsup/{chunk-5JBFVV4C.cjs → chunk-LXAVET4A.cjs} +21 -7
  24. package/dist/tsup/chunk-LXAVET4A.cjs.map +1 -0
  25. package/dist/tsup/{chunk-TCUI5JFE.cjs → chunk-NDCVQZBS.cjs} +45 -18
  26. package/dist/tsup/chunk-NDCVQZBS.cjs.map +1 -0
  27. package/dist/tsup/{chunk-4CKHQRXG.js → chunk-NII4KKHD.js} +515 -240
  28. package/dist/tsup/chunk-NII4KKHD.js.map +1 -0
  29. package/dist/tsup/{chunk-2K3JMDAN.js → chunk-NRELKXIX.js} +40 -13
  30. package/dist/tsup/chunk-NRELKXIX.js.map +1 -0
  31. package/dist/tsup/{chunk-UFWAK3X2.cjs → chunk-NUA6LOOJ.cjs} +660 -385
  32. package/dist/tsup/chunk-NUA6LOOJ.cjs.map +1 -0
  33. package/dist/tsup/{chunk-DIAYNQTE.cjs → chunk-OSK2VSJF.cjs} +12 -12
  34. package/dist/tsup/{chunk-DIAYNQTE.cjs.map → chunk-OSK2VSJF.cjs.map} +1 -1
  35. package/dist/tsup/chunk-PD6HCAJE.js +102 -0
  36. package/dist/tsup/chunk-PD6HCAJE.js.map +1 -0
  37. package/dist/tsup/{chunk-RGQR2J7S.js → chunk-RLBM6D4L.js} +20 -6
  38. package/dist/tsup/chunk-RLBM6D4L.js.map +1 -0
  39. package/dist/tsup/{chunk-KG3C7MKR.cjs → chunk-VAF63BEI.cjs} +3 -3
  40. package/dist/tsup/{chunk-KG3C7MKR.cjs.map → chunk-VAF63BEI.cjs.map} +1 -1
  41. package/dist/tsup/{chunk-G75SVQON.js → chunk-WAT5AE7S.js} +9 -5
  42. package/dist/tsup/chunk-WAT5AE7S.js.map +1 -0
  43. package/dist/tsup/{chunk-WC2PSJWN.js → chunk-YL4VZMMT.js} +2 -2
  44. package/dist/tsup/client/mod.cjs +9 -9
  45. package/dist/tsup/client/mod.d.cts +7 -8
  46. package/dist/tsup/client/mod.d.ts +7 -8
  47. package/dist/tsup/client/mod.js +8 -8
  48. package/dist/tsup/common/log.cjs +3 -3
  49. package/dist/tsup/common/log.js +2 -2
  50. package/dist/tsup/common/websocket.cjs +4 -4
  51. package/dist/tsup/common/websocket.js +3 -3
  52. package/dist/tsup/{connection-BLemxi4f.d.ts → conn-DCSQgIlw.d.ts} +1605 -1353
  53. package/dist/tsup/{connection-CpDIydXf.d.cts → conn-DdzHTm2E.d.cts} +1605 -1353
  54. package/dist/tsup/driver-helpers/mod.cjs +31 -5
  55. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  56. package/dist/tsup/driver-helpers/mod.d.cts +7 -8
  57. package/dist/tsup/driver-helpers/mod.d.ts +7 -8
  58. package/dist/tsup/driver-helpers/mod.js +33 -7
  59. package/dist/tsup/driver-test-suite/mod.cjs +317 -222
  60. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  61. package/dist/tsup/driver-test-suite/mod.d.cts +7 -7
  62. package/dist/tsup/driver-test-suite/mod.d.ts +7 -7
  63. package/dist/tsup/driver-test-suite/mod.js +582 -487
  64. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  65. package/dist/tsup/inspector/mod.cjs +16 -6
  66. package/dist/tsup/inspector/mod.cjs.map +1 -1
  67. package/dist/tsup/inspector/mod.d.cts +34 -7
  68. package/dist/tsup/inspector/mod.d.ts +34 -7
  69. package/dist/tsup/inspector/mod.js +17 -7
  70. package/dist/tsup/mod.cjs +10 -20
  71. package/dist/tsup/mod.cjs.map +1 -1
  72. package/dist/tsup/mod.d.cts +9 -7
  73. package/dist/tsup/mod.d.ts +9 -7
  74. package/dist/tsup/mod.js +9 -19
  75. package/dist/tsup/test/mod.cjs +11 -11
  76. package/dist/tsup/test/mod.d.cts +6 -7
  77. package/dist/tsup/test/mod.d.ts +6 -7
  78. package/dist/tsup/test/mod.js +10 -10
  79. package/dist/tsup/utils.cjs +4 -2
  80. package/dist/tsup/utils.cjs.map +1 -1
  81. package/dist/tsup/utils.d.cts +11 -1
  82. package/dist/tsup/utils.d.ts +11 -1
  83. package/dist/tsup/utils.js +3 -1
  84. package/package.json +8 -4
  85. package/src/actor/action.ts +1 -1
  86. package/src/actor/config.ts +1 -1
  87. package/src/actor/conn-drivers.ts +205 -0
  88. package/src/actor/conn-socket.ts +6 -0
  89. package/src/actor/{connection.ts → conn.ts} +78 -84
  90. package/src/actor/context.ts +1 -1
  91. package/src/actor/driver.ts +4 -43
  92. package/src/actor/instance.ts +162 -86
  93. package/src/actor/mod.ts +1 -11
  94. package/src/actor/persisted.ts +2 -5
  95. package/src/actor/protocol/old.ts +1 -1
  96. package/src/actor/router-endpoints.ts +142 -106
  97. package/src/actor/router.ts +81 -45
  98. package/src/actor/utils.ts +5 -1
  99. package/src/client/actor-conn.ts +154 -23
  100. package/src/client/client.ts +1 -1
  101. package/src/client/config.ts +7 -0
  102. package/src/common/actor-router-consts.ts +29 -8
  103. package/src/common/router.ts +2 -1
  104. package/src/common/versioned-data.ts +5 -5
  105. package/src/driver-helpers/mod.ts +14 -1
  106. package/src/driver-test-suite/mod.ts +11 -2
  107. package/src/driver-test-suite/test-inline-client-driver.ts +36 -18
  108. package/src/driver-test-suite/tests/actor-conn-state.ts +66 -22
  109. package/src/driver-test-suite/tests/actor-conn.ts +65 -126
  110. package/src/driver-test-suite/tests/actor-reconnect.ts +160 -0
  111. package/src/driver-test-suite/tests/actor-sleep.ts +0 -1
  112. package/src/driver-test-suite/tests/raw-websocket.ts +0 -35
  113. package/src/driver-test-suite/utils.ts +3 -3
  114. package/src/drivers/default.ts +8 -7
  115. package/src/drivers/engine/actor-driver.ts +53 -31
  116. package/src/drivers/engine/config.ts +4 -0
  117. package/src/drivers/file-system/actor.ts +0 -6
  118. package/src/drivers/file-system/global-state.ts +3 -14
  119. package/src/drivers/file-system/manager.ts +12 -8
  120. package/src/inspector/actor.ts +4 -3
  121. package/src/inspector/config.ts +10 -1
  122. package/src/inspector/mod.ts +1 -0
  123. package/src/inspector/utils.ts +23 -4
  124. package/src/manager/driver.ts +11 -1
  125. package/src/manager/gateway.ts +407 -0
  126. package/src/manager/router.ts +269 -468
  127. package/src/manager-api/actors.ts +61 -0
  128. package/src/manager-api/common.ts +4 -0
  129. package/src/mod.ts +1 -1
  130. package/src/registry/mod.ts +119 -10
  131. package/src/remote-manager-driver/actor-http-client.ts +30 -19
  132. package/src/remote-manager-driver/actor-websocket-client.ts +43 -16
  133. package/src/remote-manager-driver/api-endpoints.ts +19 -21
  134. package/src/remote-manager-driver/api-utils.ts +10 -1
  135. package/src/remote-manager-driver/mod.ts +51 -48
  136. package/src/remote-manager-driver/ws-proxy.ts +2 -9
  137. package/src/test/mod.ts +6 -2
  138. package/src/utils.ts +21 -2
  139. package/dist/tsup/actor-router-consts-BK6arfy8.d.cts +0 -17
  140. package/dist/tsup/actor-router-consts-BK6arfy8.d.ts +0 -17
  141. package/dist/tsup/chunk-2K3JMDAN.js.map +0 -1
  142. package/dist/tsup/chunk-42I3OZ3Q.js +0 -15
  143. package/dist/tsup/chunk-42I3OZ3Q.js.map +0 -1
  144. package/dist/tsup/chunk-4CKHQRXG.js.map +0 -1
  145. package/dist/tsup/chunk-5JBFVV4C.cjs.map +0 -1
  146. package/dist/tsup/chunk-6P6RA47N.cjs.map +0 -1
  147. package/dist/tsup/chunk-7OUKNSTU.js.map +0 -1
  148. package/dist/tsup/chunk-G75SVQON.js.map +0 -1
  149. package/dist/tsup/chunk-KUPQZYUQ.cjs +0 -15
  150. package/dist/tsup/chunk-KUPQZYUQ.cjs.map +0 -1
  151. package/dist/tsup/chunk-NTCUGYSD.cjs.map +0 -1
  152. package/dist/tsup/chunk-RGQR2J7S.js.map +0 -1
  153. package/dist/tsup/chunk-TCUI5JFE.cjs.map +0 -1
  154. package/dist/tsup/chunk-TWGATZ3X.cjs.map +0 -1
  155. package/dist/tsup/chunk-UFWAK3X2.cjs.map +0 -1
  156. package/dist/tsup/chunk-W6LN7AF5.js.map +0 -1
  157. package/dist/tsup/common-CXCe7s6i.d.cts +0 -218
  158. package/dist/tsup/common-CXCe7s6i.d.ts +0 -218
  159. package/src/actor/generic-conn-driver.ts +0 -246
  160. package/src/common/fake-event-source.ts +0 -267
  161. package/src/manager-api/routes/actors-create.ts +0 -16
  162. package/src/manager-api/routes/actors-delete.ts +0 -4
  163. package/src/manager-api/routes/actors-get-by-id.ts +0 -7
  164. package/src/manager-api/routes/actors-get-or-create-by-id.ts +0 -29
  165. package/src/manager-api/routes/actors-get.ts +0 -7
  166. package/src/manager-api/routes/common.ts +0 -18
  167. /package/dist/tsup/{chunk-ZYLTS2EM.js.map → chunk-54MAHBLL.js.map} +0 -0
  168. /package/dist/tsup/{chunk-VCEHU56K.js.map → chunk-B6N6VM37.js.map} +0 -0
  169. /package/dist/tsup/{chunk-VPV4MWXR.js.map → chunk-DIHKN7NM.js.map} +0 -0
  170. /package/dist/tsup/{chunk-WC2PSJWN.js.map → chunk-YL4VZMMT.js.map} +0 -0
@@ -32,7 +32,12 @@ import {
32
32
  encodingIsBinary,
33
33
  serializeWithEncoding,
34
34
  } from "@/serde";
35
- import { bufferToArrayBuffer, getEnvUniversal, httpUserAgent } from "@/utils";
35
+ import {
36
+ bufferToArrayBuffer,
37
+ getEnvUniversal,
38
+ httpUserAgent,
39
+ promiseWithResolvers,
40
+ } from "@/utils";
36
41
  import type { ActorDefinitionActions } from "./actor-common";
37
42
  import { queryActor } from "./actor-query";
38
43
  import { ACTOR_CONNS_SYMBOL, type ClientRaw, TRANSPORT_SYMBOL } from "./client";
@@ -94,7 +99,7 @@ export class ActorConnRaw {
94
99
  /** If attempting to connect. Helpful for knowing if in a retry loop when reconnecting. */
95
100
  #connecting = false;
96
101
 
97
- // These will only be set on SSE driver
102
+ // Connection info, used for reconnection and HTTP requests
98
103
  #actorId?: string;
99
104
  #connectionId?: string;
100
105
  #connectionToken?: string;
@@ -119,7 +124,7 @@ export class ActorConnRaw {
119
124
  #keepNodeAliveInterval: NodeJS.Timeout;
120
125
 
121
126
  /** Promise used to indicate the socket has connected successfully. This will be rejected if the connection fails. */
122
- #onOpenPromise?: PromiseWithResolvers<undefined>;
127
+ #onOpenPromise?: ReturnType<typeof promiseWithResolvers<undefined>>;
123
128
 
124
129
  #client: ClientRaw;
125
130
  #driver: ManagerDriver;
@@ -177,7 +182,7 @@ export class ActorConnRaw {
177
182
  this.#actionIdCounter += 1;
178
183
 
179
184
  const { promise, resolve, reject } =
180
- Promise.withResolvers<protocol.ActionResponse>();
185
+ promiseWithResolvers<protocol.ActionResponse>();
181
186
  this.#actionsInFlight.set(actionId, { name: opts.name, resolve, reject });
182
187
 
183
188
  this.#sendMessage({
@@ -253,7 +258,7 @@ enc
253
258
  // Create promise for open
254
259
  if (this.#onOpenPromise)
255
260
  throw new Error("#onOpenPromise already defined");
256
- this.#onOpenPromise = Promise.withResolvers();
261
+ this.#onOpenPromise = promiseWithResolvers();
257
262
 
258
263
  // Connect transport
259
264
  if (this.#client[TRANSPORT_SYMBOL] === "websocket") {
@@ -277,15 +282,37 @@ enc
277
282
  this.#actorQuery,
278
283
  this.#driver,
279
284
  );
285
+
286
+ // Check if we have connection info for reconnection
287
+ const isReconnection = this.#connectionId && this.#connectionToken;
288
+ if (isReconnection) {
289
+ logger().debug({
290
+ msg: "attempting websocket reconnection",
291
+ connectionId: this.#connectionId,
292
+ });
293
+ }
294
+
280
295
  const ws = await this.#driver.openWebSocket(
281
296
  PATH_CONNECT_WEBSOCKET,
282
297
  actorId,
283
298
  this.#encoding,
284
299
  this.#params,
300
+ // Pass connection ID and token for reconnection if available
301
+ isReconnection ? this.#connectionId : undefined,
302
+ isReconnection ? this.#connectionToken : undefined,
285
303
  );
304
+ logger().debug({
305
+ msg: "transport set to new websocket",
306
+ connectionId: this.#connectionId,
307
+ readyState: ws.readyState,
308
+ messageQueueLength: this.#messageQueue.length,
309
+ });
286
310
  this.#transport = { websocket: ws };
287
311
  ws.addEventListener("open", () => {
288
- logger().debug({ msg: "websocket open" });
312
+ logger().debug({
313
+ msg: "client websocket open",
314
+ connectionId: this.#connectionId,
315
+ });
289
316
  });
290
317
  ws.addEventListener("message", async (ev) => {
291
318
  this.#handleOnMessage(ev.data);
@@ -316,6 +343,8 @@ enc
316
343
  encoding: this.#encoding,
317
344
  });
318
345
 
346
+ const isReconnection = this.#connectionId && this.#connectionToken;
347
+
319
348
  const eventSource = new EventSource("http://actor/connect/sse", {
320
349
  fetch: (input, init) => {
321
350
  return this.#driver.sendRequest(
@@ -329,6 +358,12 @@ enc
329
358
  ...(this.#params !== undefined
330
359
  ? { [HEADER_CONN_PARAMS]: JSON.stringify(this.#params) }
331
360
  : {}),
361
+ ...(isReconnection
362
+ ? {
363
+ [HEADER_CONN_ID]: this.#connectionId,
364
+ [HEADER_CONN_TOKEN]: this.#connectionToken,
365
+ }
366
+ : {}),
332
367
  },
333
368
  }),
334
369
  );
@@ -338,6 +373,9 @@ enc
338
373
  this.#transport = { sse: eventSource };
339
374
 
340
375
  eventSource.addEventListener("message", (ev: UniversalMessageEvent) => {
376
+ // Ignore pings
377
+ if (ev.type === "ping") return;
378
+
341
379
  this.#handleOnMessage(ev.data);
342
380
  });
343
381
 
@@ -351,6 +389,7 @@ enc
351
389
  logger().debug({
352
390
  msg: "socket open",
353
391
  messageQueueLength: this.#messageQueue.length,
392
+ connectionId: this.#connectionId,
354
393
  });
355
394
 
356
395
  // Resolve open promise
@@ -370,6 +409,10 @@ enc
370
409
  // If the message fails to send, the message will be re-queued
371
410
  const queue = this.#messageQueue;
372
411
  this.#messageQueue = [];
412
+ logger().debug({
413
+ msg: "flushing message queue",
414
+ queueLength: queue.length,
415
+ });
373
416
  for (const msg of queue) {
374
417
  this.#sendMessage(msg);
375
418
  }
@@ -395,7 +438,7 @@ enc
395
438
  );
396
439
 
397
440
  if (response.body.tag === "Init") {
398
- // This is only called for SSE
441
+ // Store connection info for reconnection
399
442
  this.#actorId = response.body.val.actorId;
400
443
  this.#connectionId = response.body.val.connectionId;
401
444
  this.#connectionToken = response.body.val.connectionToken;
@@ -491,26 +534,46 @@ enc
491
534
  //
492
535
  // These properties will be undefined
493
536
  const closeEvent = event as CloseEvent;
494
- if (closeEvent.wasClean) {
495
- logger().info({
496
- msg: "socket closed",
497
- code: closeEvent.code,
498
- reason: closeEvent.reason,
499
- wasClean: closeEvent.wasClean,
500
- });
501
- } else {
502
- logger().warn({
503
- msg: "socket closed",
504
- code: closeEvent.code,
505
- reason: closeEvent.reason,
506
- wasClean: closeEvent.wasClean,
537
+ const wasClean = closeEvent.wasClean;
538
+
539
+ logger().info({
540
+ msg: "socket closed",
541
+ code: closeEvent.code,
542
+ reason: closeEvent.reason,
543
+ wasClean: wasClean,
544
+ connectionId: this.#connectionId,
545
+ messageQueueLength: this.#messageQueue.length,
546
+ actionsInFlight: this.#actionsInFlight.size,
547
+ });
548
+
549
+ // Reject all in-flight actions
550
+ if (this.#actionsInFlight.size > 0) {
551
+ logger().debug({
552
+ msg: "rejecting in-flight actions after disconnect",
553
+ count: this.#actionsInFlight.size,
554
+ connectionId: this.#connectionId,
555
+ wasClean,
507
556
  });
557
+
558
+ const disconnectError = new Error(
559
+ wasClean ? "Connection closed" : "Connection lost",
560
+ );
561
+
562
+ for (const actionInfo of this.#actionsInFlight.values()) {
563
+ actionInfo.reject(disconnectError);
564
+ }
565
+ this.#actionsInFlight.clear();
508
566
  }
509
567
 
510
568
  this.#transport = undefined;
511
569
 
512
570
  // Automatically reconnect. Skip if already attempting to connect.
513
571
  if (!this.#disposed && !this.#connecting) {
572
+ logger().debug({
573
+ msg: "triggering reconnect",
574
+ connectionId: this.#connectionId,
575
+ messageQueueLength: this.#messageQueue.length,
576
+ });
514
577
  // TODO: Fetch actor to check if it's destroyed
515
578
  // TODO: Add backoff for reconnect
516
579
  // TODO: Add a way of preserving connection ID for connection state
@@ -660,9 +723,26 @@ enc
660
723
  let queueMessage = false;
661
724
  if (!this.#transport) {
662
725
  // No transport connected yet
726
+ logger().debug({ msg: "no transport, queueing message" });
663
727
  queueMessage = true;
664
728
  } else if ("websocket" in this.#transport) {
665
- if (this.#transport.websocket.readyState === 1) {
729
+ const readyState = this.#transport.websocket.readyState;
730
+ logger().debug({
731
+ msg: "websocket send attempt",
732
+ readyState,
733
+ readyStateString:
734
+ readyState === 0
735
+ ? "CONNECTING"
736
+ : readyState === 1
737
+ ? "OPEN"
738
+ : readyState === 2
739
+ ? "CLOSING"
740
+ : "CLOSED",
741
+ connectionId: this.#connectionId,
742
+ messageType: (message.body as any).tag,
743
+ actionName: (message.body as any).val?.name,
744
+ });
745
+ if (readyState === 1) {
666
746
  try {
667
747
  const messageSerialized = serializeWithEncoding(
668
748
  this.#encoding,
@@ -678,12 +758,17 @@ enc
678
758
  logger().warn({
679
759
  msg: "failed to send message, added to queue",
680
760
  error,
761
+ connectionId: this.#connectionId,
681
762
  });
682
763
 
683
764
  // Assuming the socket is disconnected and will be reconnected soon
684
765
  queueMessage = true;
685
766
  }
686
767
  } else {
768
+ logger().debug({
769
+ msg: "websocket not open, queueing message",
770
+ readyState,
771
+ });
687
772
  queueMessage = true;
688
773
  }
689
774
  } else if ("sse" in this.#transport) {
@@ -699,7 +784,13 @@ enc
699
784
 
700
785
  if (!opts?.ephemeral && queueMessage) {
701
786
  this.#messageQueue.push(message);
702
- logger().debug({ msg: "queued connection message" });
787
+ logger().debug({
788
+ msg: "queued connection message",
789
+ queueLength: this.#messageQueue.length,
790
+ connectionId: this.#connectionId,
791
+ messageType: (message.body as any).tag,
792
+ actionName: (message.body as any).val?.name,
793
+ });
703
794
  }
704
795
  }
705
796
 
@@ -778,6 +869,22 @@ enc
778
869
  return deserializeWithEncoding(this.#encoding, buffer, TO_CLIENT_VERSIONED);
779
870
  }
780
871
 
872
+ /**
873
+ * Get the actor ID (for testing purposes).
874
+ * @internal
875
+ */
876
+ get actorId(): string | undefined {
877
+ return this.#actorId;
878
+ }
879
+
880
+ /**
881
+ * Get the connection ID (for testing purposes).
882
+ * @internal
883
+ */
884
+ get connectionId(): string | undefined {
885
+ return this.#connectionId;
886
+ }
887
+
781
888
  /**
782
889
  * Disconnects from the actor.
783
890
  *
@@ -815,7 +922,7 @@ enc
815
922
  ) {
816
923
  logger().debug({ msg: "ws already closed or closing" });
817
924
  } else {
818
- const { promise, resolve } = Promise.withResolvers();
925
+ const { promise, resolve } = promiseWithResolvers();
819
926
  ws.addEventListener("close", () => {
820
927
  logger().debug({ msg: "ws closed" });
821
928
  resolve(undefined);
@@ -824,6 +931,30 @@ enc
824
931
  await promise;
825
932
  }
826
933
  } else if ("sse" in this.#transport) {
934
+ // Send close request to server for SSE connections
935
+ if (this.#connectionId && this.#connectionToken) {
936
+ try {
937
+ await sendHttpRequest({
938
+ url: "http://actor/connections/close",
939
+ method: "POST",
940
+ headers: {
941
+ [HEADER_CONN_ID]: this.#connectionId,
942
+ [HEADER_CONN_TOKEN]: this.#connectionToken,
943
+ },
944
+ encoding: this.#encoding,
945
+ skipParseResponse: true,
946
+ customFetch: this.#driver.sendRequest.bind(
947
+ this.#driver,
948
+ this.#actorId!,
949
+ ),
950
+ requestVersionedDataHandler: TO_SERVER_VERSIONED,
951
+ responseVersionedDataHandler: TO_CLIENT_VERSIONED,
952
+ });
953
+ } catch (error) {
954
+ // Ignore errors when closing - connection may already be closed
955
+ logger().warn({ msg: "failed to send close request", error });
956
+ }
957
+ }
827
958
  this.#transport.sse.close();
828
959
  } else {
829
960
  assertUnreachable(this.#transport);
@@ -491,7 +491,7 @@ function createActorProxy<AD extends AnyActorDefinition>(
491
491
 
492
492
  // Handle built-in Promise methods and existing properties
493
493
  if (prop === "constructor" || prop in target) {
494
- const value = Reflect.get(target, prop, receiver);
494
+ const value = Reflect.get(target, prop, target);
495
495
  // Preserve method binding
496
496
  if (typeof value === "function") {
497
497
  return value.bind(target);
@@ -14,6 +14,13 @@ export const ClientConfigSchema = z.object({
14
14
  })
15
15
  .default({}),
16
16
 
17
+ token: z
18
+ .string()
19
+ .optional()
20
+ .transform((x) => x ?? getEnvUniversal("RIVET_TOKEN")),
21
+
22
+ headers: z.record(z.string()).optional().default({}),
23
+
17
24
  /** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */
18
25
  endpoint: z
19
26
  .string()
@@ -5,21 +5,39 @@ export const PATH_CONNECT_WEBSOCKET = "/connect/websocket";
5
5
  export const PATH_RAW_WEBSOCKET_PREFIX = "/raw/websocket/";
6
6
 
7
7
  // MARK: Headers
8
- export const HEADER_ACTOR_QUERY = "X-RivetKit-Query";
8
+ export const HEADER_ACTOR_QUERY = "x-rivet-query";
9
9
 
10
- export const HEADER_ENCODING = "X-RivetKit-Encoding";
10
+ export const HEADER_ENCODING = "x-rivet-encoding";
11
11
 
12
12
  // IMPORTANT: Params must be in headers or in an E2EE part of the request (i.e. NOT the URL or query string) in order to ensure that tokens can be securely passed in params.
13
- export const HEADER_CONN_PARAMS = "X-RivetKit-Conn-Params";
13
+ export const HEADER_CONN_PARAMS = "x-rivet-conn-params";
14
14
 
15
- // Internal header
16
- export const HEADER_AUTH_DATA = "X-RivetKit-Auth-Data";
15
+ export const HEADER_ACTOR_ID = "x-rivet-actor";
17
16
 
18
- export const HEADER_ACTOR_ID = "X-RivetKit-Actor";
17
+ export const HEADER_CONN_ID = "x-rivet-conn";
19
18
 
20
- export const HEADER_CONN_ID = "X-RivetKit-Conn";
19
+ export const HEADER_CONN_TOKEN = "x-rivet-conn-token";
21
20
 
22
- export const HEADER_CONN_TOKEN = "X-RivetKit-Conn-Token";
21
+ export const HEADER_RIVET_TOKEN = "x-rivet-token";
22
+
23
+ // MARK: Manager Gateway Headers
24
+ export const HEADER_RIVET_TARGET = "x-rivet-target";
25
+ export const HEADER_RIVET_ACTOR = "x-rivet-actor";
26
+
27
+ // MARK: WebSocket Protocol Prefixes
28
+ /** Some servers (such as node-ws & Cloudflare) require explicitly match a certain WebSocket protocol. This gives us a static protocol to match against. */
29
+ export const WS_PROTOCOL_STANDARD = "rivet";
30
+ export const WS_PROTOCOL_TARGET = "rivet_target.";
31
+ export const WS_PROTOCOL_ACTOR = "rivet_actor.";
32
+ export const WS_PROTOCOL_ENCODING = "rivet_encoding.";
33
+ export const WS_PROTOCOL_CONN_PARAMS = "rivet_conn_params.";
34
+ export const WS_PROTOCOL_CONN_ID = "rivet_conn.";
35
+ export const WS_PROTOCOL_CONN_TOKEN = "rivet_conn_token.";
36
+ export const WS_PROTOCOL_TOKEN = "rivet_token.";
37
+
38
+ // MARK: WebSocket Inline Test Protocol Prefixes
39
+ export const WS_PROTOCOL_TRANSPORT = "test_transport.";
40
+ export const WS_PROTOCOL_PATH = "test_path.";
23
41
 
24
42
  /**
25
43
  * Headers that publics can send from public clients.
@@ -35,4 +53,7 @@ export const ALLOWED_PUBLIC_HEADERS = [
35
53
  HEADER_ACTOR_ID,
36
54
  HEADER_CONN_ID,
37
55
  HEADER_CONN_TOKEN,
56
+ HEADER_RIVET_TARGET,
57
+ HEADER_RIVET_ACTOR,
58
+ HEADER_RIVET_TOKEN,
38
59
  ];
@@ -76,5 +76,6 @@ export function handleRouteError(error: unknown, c: HonoContext) {
76
76
  HTTP_RESPONSE_ERROR_VERSIONED,
77
77
  );
78
78
 
79
- return c.body(output, { status: statusCode });
79
+ // TODO: Remove any
80
+ return c.body(output as any, { status: statusCode });
80
81
  }
@@ -63,8 +63,8 @@ export class VersionedDataHandler<T> {
63
63
  }
64
64
 
65
65
  private embedVersion(data: VersionedData<Uint8Array>): Uint8Array {
66
- const versionBytes = new Uint8Array(4);
67
- new DataView(versionBytes.buffer).setUint32(0, data.version, true);
66
+ const versionBytes = new Uint8Array(2);
67
+ new DataView(versionBytes.buffer).setUint16(0, data.version, true);
68
68
 
69
69
  const result = new Uint8Array(versionBytes.length + data.data.length);
70
70
  result.set(versionBytes);
@@ -74,15 +74,15 @@ export class VersionedDataHandler<T> {
74
74
  }
75
75
 
76
76
  private extractVersion(bytes: Uint8Array): VersionedData<Uint8Array> {
77
- if (bytes.length < 4) {
77
+ if (bytes.length < 2) {
78
78
  throw new Error("Invalid versioned data: too short");
79
79
  }
80
80
 
81
- const version = new DataView(bytes.buffer, bytes.byteOffset).getUint32(
81
+ const version = new DataView(bytes.buffer, bytes.byteOffset).getUint16(
82
82
  0,
83
83
  true,
84
84
  );
85
- const data = bytes.slice(4);
85
+ const data = bytes.slice(2);
86
86
 
87
87
  return { version, data };
88
88
  }
@@ -1,13 +1,26 @@
1
1
  export type { ActorDriver } from "@/actor/driver";
2
2
  export type { ActorInstance, AnyActorInstance } from "@/actor/instance";
3
3
  export {
4
+ ALLOWED_PUBLIC_HEADERS,
4
5
  HEADER_ACTOR_ID,
5
6
  HEADER_ACTOR_QUERY,
6
- HEADER_AUTH_DATA,
7
7
  HEADER_CONN_ID,
8
8
  HEADER_CONN_PARAMS,
9
9
  HEADER_CONN_TOKEN,
10
10
  HEADER_ENCODING,
11
+ HEADER_RIVET_ACTOR,
12
+ HEADER_RIVET_TARGET,
13
+ PATH_CONNECT_WEBSOCKET,
14
+ PATH_RAW_WEBSOCKET_PREFIX,
15
+ WS_PROTOCOL_ACTOR,
16
+ WS_PROTOCOL_CONN_ID,
17
+ WS_PROTOCOL_CONN_PARAMS,
18
+ WS_PROTOCOL_CONN_TOKEN,
19
+ WS_PROTOCOL_ENCODING,
20
+ WS_PROTOCOL_PATH,
21
+ WS_PROTOCOL_STANDARD,
22
+ WS_PROTOCOL_TARGET,
23
+ WS_PROTOCOL_TRANSPORT,
11
24
  } from "@/common/actor-router-consts";
12
25
  export type {
13
26
  ActorOutput,
@@ -4,6 +4,7 @@ import { bundleRequire } from "bundle-require";
4
4
  import invariant from "invariant";
5
5
  import { describe } from "vitest";
6
6
  import type { Transport } from "@/client/mod";
7
+ import { configureInspectorAccessToken } from "@/inspector/utils";
7
8
  import { createManagerRouter } from "@/manager/router";
8
9
  import type { DriverConfig, Registry, RunConfig } from "@/mod";
9
10
  import { RunConfigSchema } from "@/registry/run-config";
@@ -22,6 +23,7 @@ import { runActorInlineClientTests } from "./tests/actor-inline-client";
22
23
  import { runActorInspectorTests } from "./tests/actor-inspector";
23
24
  import { runActorMetadataTests } from "./tests/actor-metadata";
24
25
  import { runActorOnStateChangeTests } from "./tests/actor-onstatechange";
26
+ import { runActorReconnectTests } from "./tests/actor-reconnect";
25
27
  import { runActorVarsTests } from "./tests/actor-vars";
26
28
  import { runManagerDriverTests } from "./tests/manager-driver";
27
29
  import { runRawHttpTests } from "./tests/raw-http";
@@ -32,6 +34,7 @@ import { runRequestAccessTests } from "./tests/request-access";
32
34
  export interface SkipTests {
33
35
  schedule?: boolean;
34
36
  sleep?: boolean;
37
+ sse?: boolean;
35
38
  }
36
39
 
37
40
  export interface DriverTestConfig {
@@ -86,7 +89,10 @@ export function runDriverTests(
86
89
  runActorDriverTests(driverTestConfig);
87
90
  runManagerDriverTests(driverTestConfig);
88
91
 
89
- for (const transport of ["websocket", "sse"] as Transport[]) {
92
+ const transports: Transport[] = driverTestConfig.skip?.sse
93
+ ? ["websocket"]
94
+ : ["websocket", "sse"];
95
+ for (const transport of transports) {
90
96
  describe(`transport (${transport})`, () => {
91
97
  runActorConnTests({
92
98
  ...driverTestConfig,
@@ -95,6 +101,8 @@ export function runDriverTests(
95
101
 
96
102
  runActorConnStateTests({ ...driverTestConfig, transport });
97
103
 
104
+ runActorReconnectTests({ ...driverTestConfig, transport });
105
+
98
106
  runRequestAccessTests({ ...driverTestConfig, transport });
99
107
 
100
108
  runActorDriverTestsWithTransport({ ...driverTestConfig, transport });
@@ -193,11 +201,12 @@ export async function createTestRuntime(
193
201
 
194
202
  // Create router
195
203
  const managerDriver = driver.manager(registry.config, config);
204
+ configureInspectorAccessToken(config, managerDriver);
196
205
  const { router } = createManagerRouter(
197
206
  registry.config,
198
207
  config,
199
208
  managerDriver,
200
- false,
209
+ undefined,
201
210
  );
202
211
 
203
212
  // Inject WebSocket
@@ -10,6 +10,12 @@ import {
10
10
  HEADER_ACTOR_QUERY,
11
11
  HEADER_CONN_PARAMS,
12
12
  HEADER_ENCODING,
13
+ WS_PROTOCOL_ACTOR,
14
+ WS_PROTOCOL_CONN_PARAMS,
15
+ WS_PROTOCOL_ENCODING,
16
+ WS_PROTOCOL_PATH,
17
+ WS_PROTOCOL_TARGET,
18
+ WS_PROTOCOL_TRANSPORT,
13
19
  } from "@/common/actor-router-consts";
14
20
  import type { UniversalEventSource } from "@/common/eventsource-interface";
15
21
  import type { DeconstructedError } from "@/common/utils";
@@ -111,7 +117,8 @@ export function createTestInlineClientDriver(
111
117
  headers,
112
118
  body: actorRequest.body,
113
119
  signal: actorRequest.signal,
114
- }),
120
+ duplex: "half",
121
+ } as RequestInit),
115
122
  );
116
123
 
117
124
  // Check if it's an error response from our handler
@@ -153,39 +160,48 @@ export function createTestInlineClientDriver(
153
160
  actorId: string,
154
161
  encoding: Encoding,
155
162
  params: unknown,
163
+ connId?: string,
164
+ connToken?: string,
156
165
  ): Promise<UniversalWebSocket> {
157
166
  const WebSocket = await importWebSocket();
158
167
 
159
168
  // Normalize path to match other drivers
160
169
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
161
170
 
162
- logger().debug({
163
- msg: "creating websocket connection via test inline driver",
164
- });
165
-
166
171
  // Create WebSocket connection to the test endpoint
167
- // Use a placeholder path and pass the actual path as a query param to avoid mixing user query params with internal ones
168
172
  const wsUrl = new URL(
169
173
  `${endpoint}/.test/inline-driver/connect-websocket/ws`,
170
174
  );
171
- wsUrl.searchParams.set("path", normalizedPath);
172
- wsUrl.searchParams.set("actorId", actorId);
173
- if (params !== undefined)
174
- wsUrl.searchParams.set("params", JSON.stringify(params));
175
- wsUrl.searchParams.set("encodingKind", encoding);
175
+
176
+ logger().debug({
177
+ msg: "creating websocket connection via test inline driver",
178
+ url: wsUrl.toString(),
179
+ });
176
180
 
177
181
  // Convert http/https to ws/wss
178
182
  const wsProtocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
179
- const finalWsUrl = `${wsProtocol}//${wsUrl.host}${wsUrl.pathname}${wsUrl.search}`;
183
+ const finalWsUrl = `${wsProtocol}//${wsUrl.host}${wsUrl.pathname}`;
180
184
 
181
185
  logger().debug({ msg: "connecting to websocket", url: finalWsUrl });
182
186
 
187
+ // Build protocols for the connection
188
+ const protocols: string[] = [];
189
+ protocols.push(`${WS_PROTOCOL_TARGET}actor`);
190
+ protocols.push(`${WS_PROTOCOL_ACTOR}${actorId}`);
191
+ protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
192
+ protocols.push(`${WS_PROTOCOL_TRANSPORT}${transport}`);
193
+ protocols.push(
194
+ `${WS_PROTOCOL_PATH}${encodeURIComponent(normalizedPath)}`,
195
+ );
196
+ if (params !== undefined) {
197
+ protocols.push(
198
+ `${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`,
199
+ );
200
+ }
201
+
183
202
  // Create and return the WebSocket
184
203
  // Node & browser WebSocket types are incompatible
185
- const ws = new WebSocket(finalWsUrl, [
186
- // HACK: See packages/drivers/cloudflare-workers/src/websocket.ts
187
- "rivetkit",
188
- ]) as any;
204
+ const ws = new WebSocket(finalWsUrl, protocols) as any;
189
205
 
190
206
  return ws;
191
207
  },
@@ -202,7 +218,6 @@ export function createTestInlineClientDriver(
202
218
  _actorId: string,
203
219
  _encoding: Encoding,
204
220
  _params: unknown,
205
- _authData: unknown,
206
221
  ): Promise<Response> {
207
222
  throw "UNIMPLEMENTED";
208
223
  // const upgradeWebSocket = this.#runConfig.getUpgradeWebSocket?.();
@@ -214,6 +229,8 @@ export function createTestInlineClientDriver(
214
229
  displayInformation(): ManagerDisplayInformation {
215
230
  return { name: "Test Inline", properties: {} };
216
231
  },
232
+ // TODO:
233
+ getOrCreateInspectorAccessToken: () => "",
217
234
 
218
235
  // action: async <Args extends Array<unknown> = unknown[], Response = unknown>(
219
236
  // _c: HonoContext | undefined,
@@ -560,7 +577,8 @@ async function makeInlineRequest<T>(
560
577
  method,
561
578
  args,
562
579
  } satisfies TestInlineDriverCallRequest),
563
- });
580
+ duplex: "half",
581
+ } as RequestInit);
564
582
 
565
583
  if (!response.ok) {
566
584
  throw new Error(`Failed to call inline ${method}: ${response.statusText}`);