rivetkit 2.0.22-rc.1 → 2.0.22

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 (124) hide show
  1. package/dist/schemas/actor-persist/v2.ts +259 -0
  2. package/dist/tsup/{chunk-LCQDY73V.cjs → chunk-2GJILCGQ.cjs} +3 -3
  3. package/dist/tsup/{chunk-LCQDY73V.cjs.map → chunk-2GJILCGQ.cjs.map} +1 -1
  4. package/dist/tsup/{chunk-JN6GPVFY.js → chunk-2K2LR56Q.js} +3 -3
  5. package/dist/tsup/{chunk-EEXX243L.js → chunk-2WVCZCJL.js} +6 -6
  6. package/dist/tsup/{chunk-FETQGZN4.js → chunk-3BJJSSTM.js} +272 -89
  7. package/dist/tsup/chunk-3BJJSSTM.js.map +1 -0
  8. package/dist/tsup/{chunk-ZZYMCYAY.cjs → chunk-3LFMVAJV.cjs} +14 -14
  9. package/dist/tsup/{chunk-ZZYMCYAY.cjs.map → chunk-3LFMVAJV.cjs.map} +1 -1
  10. package/dist/tsup/{chunk-NDLOG2JH.js → chunk-6YQKMAMV.js} +2 -2
  11. package/dist/tsup/{chunk-C2U6KGOG.cjs → chunk-AR4S2QJ7.cjs} +3 -3
  12. package/dist/tsup/{chunk-C2U6KGOG.cjs.map → chunk-AR4S2QJ7.cjs.map} +1 -1
  13. package/dist/tsup/{chunk-PELXJCJS.cjs → chunk-B4QZKOMH.cjs} +8 -8
  14. package/dist/tsup/{chunk-PELXJCJS.cjs.map → chunk-B4QZKOMH.cjs.map} +1 -1
  15. package/dist/tsup/{chunk-5EB77IQ2.cjs → chunk-CYA35VI3.cjs} +6 -6
  16. package/dist/tsup/{chunk-5EB77IQ2.cjs.map → chunk-CYA35VI3.cjs.map} +1 -1
  17. package/dist/tsup/{chunk-UBCUW7HD.js → chunk-D7AA2DK5.js} +2 -2
  18. package/dist/tsup/{chunk-I7EJWHYV.js → chunk-EBSGEDD3.js} +51 -47
  19. package/dist/tsup/chunk-EBSGEDD3.js.map +1 -0
  20. package/dist/tsup/{chunk-R6XOZKMU.cjs → chunk-HSO2H2SB.cjs} +467 -284
  21. package/dist/tsup/chunk-HSO2H2SB.cjs.map +1 -0
  22. package/dist/tsup/{chunk-VJLGVVGP.cjs → chunk-HZ4ZM3FL.cjs} +31 -12
  23. package/dist/tsup/chunk-HZ4ZM3FL.cjs.map +1 -0
  24. package/dist/tsup/{chunk-7FEMVD3D.cjs → chunk-LMZSOCYD.cjs} +12 -12
  25. package/dist/tsup/{chunk-7FEMVD3D.cjs.map → chunk-LMZSOCYD.cjs.map} +1 -1
  26. package/dist/tsup/{chunk-ZVEDMBFT.js → chunk-PBFLG45S.js} +3 -3
  27. package/dist/tsup/{chunk-GJPOIJHZ.js → chunk-ST6FGRCH.js} +26 -7
  28. package/dist/tsup/chunk-ST6FGRCH.js.map +1 -0
  29. package/dist/tsup/{chunk-BIOPK7IB.cjs → chunk-TI72NLP3.cjs} +71 -67
  30. package/dist/tsup/chunk-TI72NLP3.cjs.map +1 -0
  31. package/dist/tsup/{chunk-RPI45FGS.js → chunk-TQ4OAC2G.js} +2 -2
  32. package/dist/tsup/{chunk-4B25D5OW.js → chunk-UB4OHFDW.js} +385 -104
  33. package/dist/tsup/chunk-UB4OHFDW.js.map +1 -0
  34. package/dist/tsup/{chunk-6Z3YA6QR.cjs → chunk-V6C34TVH.cjs} +35 -15
  35. package/dist/tsup/chunk-V6C34TVH.cjs.map +1 -0
  36. package/dist/tsup/{chunk-OAB7ECAB.cjs → chunk-WVUAO2F7.cjs} +558 -277
  37. package/dist/tsup/chunk-WVUAO2F7.cjs.map +1 -0
  38. package/dist/tsup/{chunk-JKNDUKFI.js → chunk-WWAZJHTS.js} +36 -16
  39. package/dist/tsup/chunk-WWAZJHTS.js.map +1 -0
  40. package/dist/tsup/client/mod.cjs +9 -9
  41. package/dist/tsup/client/mod.d.cts +2 -2
  42. package/dist/tsup/client/mod.d.ts +2 -2
  43. package/dist/tsup/client/mod.js +8 -8
  44. package/dist/tsup/common/log.cjs +3 -3
  45. package/dist/tsup/common/log.js +2 -2
  46. package/dist/tsup/common/websocket.cjs +4 -4
  47. package/dist/tsup/common/websocket.js +3 -3
  48. package/dist/tsup/{conn-Clu655RU.d.ts → conn-BYXlxnh0.d.ts} +111 -102
  49. package/dist/tsup/{conn-lUvFLo_q.d.cts → conn-BiazosE_.d.cts} +111 -102
  50. package/dist/tsup/driver-helpers/mod.cjs +5 -5
  51. package/dist/tsup/driver-helpers/mod.d.cts +1 -1
  52. package/dist/tsup/driver-helpers/mod.d.ts +1 -1
  53. package/dist/tsup/driver-helpers/mod.js +4 -4
  54. package/dist/tsup/driver-test-suite/mod.cjs +71 -71
  55. package/dist/tsup/driver-test-suite/mod.d.cts +1 -1
  56. package/dist/tsup/driver-test-suite/mod.d.ts +1 -1
  57. package/dist/tsup/driver-test-suite/mod.js +11 -11
  58. package/dist/tsup/inspector/mod.cjs +6 -6
  59. package/dist/tsup/inspector/mod.d.cts +2 -2
  60. package/dist/tsup/inspector/mod.d.ts +2 -2
  61. package/dist/tsup/inspector/mod.js +5 -5
  62. package/dist/tsup/mod.cjs +10 -10
  63. package/dist/tsup/mod.d.cts +3 -3
  64. package/dist/tsup/mod.d.ts +3 -3
  65. package/dist/tsup/mod.js +9 -9
  66. package/dist/tsup/test/mod.cjs +11 -11
  67. package/dist/tsup/test/mod.d.cts +1 -1
  68. package/dist/tsup/test/mod.d.ts +1 -1
  69. package/dist/tsup/test/mod.js +10 -10
  70. package/dist/tsup/utils.cjs +8 -2
  71. package/dist/tsup/utils.cjs.map +1 -1
  72. package/dist/tsup/utils.d.cts +8 -1
  73. package/dist/tsup/utils.d.ts +8 -1
  74. package/dist/tsup/utils.js +7 -1
  75. package/package.json +5 -4
  76. package/src/actor/config.ts +10 -0
  77. package/src/actor/conn-drivers.ts +43 -1
  78. package/src/actor/conn-socket.ts +1 -1
  79. package/src/actor/conn.ts +22 -2
  80. package/src/actor/context.ts +1 -1
  81. package/src/actor/driver.ts +13 -2
  82. package/src/actor/instance.ts +248 -57
  83. package/src/actor/persisted.ts +7 -0
  84. package/src/actor/router-endpoints.ts +67 -45
  85. package/src/actor/router.ts +25 -17
  86. package/src/client/actor-conn.ts +9 -5
  87. package/src/common/cors.ts +57 -0
  88. package/src/common/log.ts +26 -5
  89. package/src/common/utils.ts +5 -9
  90. package/src/common/websocket-interface.ts +10 -0
  91. package/src/driver-helpers/utils.ts +1 -0
  92. package/src/drivers/engine/actor-driver.ts +261 -14
  93. package/src/drivers/engine/config.ts +2 -4
  94. package/src/drivers/file-system/actor.ts +3 -2
  95. package/src/drivers/file-system/global-state.ts +1 -1
  96. package/src/drivers/file-system/manager.ts +3 -0
  97. package/src/engine-process/mod.ts +22 -4
  98. package/src/inspector/config.ts +0 -45
  99. package/src/manager/gateway.ts +45 -32
  100. package/src/manager/hono-websocket-adapter.ts +31 -3
  101. package/src/manager/router.ts +11 -17
  102. package/src/registry/run-config.ts +2 -8
  103. package/src/remote-manager-driver/actor-http-client.ts +5 -8
  104. package/src/remote-manager-driver/actor-websocket-client.ts +2 -14
  105. package/src/remote-manager-driver/mod.ts +0 -1
  106. package/src/schemas/actor-persist/mod.ts +1 -1
  107. package/src/schemas/actor-persist/versioned.ts +22 -10
  108. package/src/utils.ts +26 -0
  109. package/dist/tsup/chunk-4B25D5OW.js.map +0 -1
  110. package/dist/tsup/chunk-6Z3YA6QR.cjs.map +0 -1
  111. package/dist/tsup/chunk-BIOPK7IB.cjs.map +0 -1
  112. package/dist/tsup/chunk-FETQGZN4.js.map +0 -1
  113. package/dist/tsup/chunk-GJPOIJHZ.js.map +0 -1
  114. package/dist/tsup/chunk-I7EJWHYV.js.map +0 -1
  115. package/dist/tsup/chunk-JKNDUKFI.js.map +0 -1
  116. package/dist/tsup/chunk-OAB7ECAB.cjs.map +0 -1
  117. package/dist/tsup/chunk-R6XOZKMU.cjs.map +0 -1
  118. package/dist/tsup/chunk-VJLGVVGP.cjs.map +0 -1
  119. /package/dist/tsup/{chunk-JN6GPVFY.js.map → chunk-2K2LR56Q.js.map} +0 -0
  120. /package/dist/tsup/{chunk-EEXX243L.js.map → chunk-2WVCZCJL.js.map} +0 -0
  121. /package/dist/tsup/{chunk-NDLOG2JH.js.map → chunk-6YQKMAMV.js.map} +0 -0
  122. /package/dist/tsup/{chunk-UBCUW7HD.js.map → chunk-D7AA2DK5.js.map} +0 -0
  123. /package/dist/tsup/{chunk-ZVEDMBFT.js.map → chunk-PBFLG45S.js.map} +0 -0
  124. /package/dist/tsup/{chunk-RPI45FGS.js.map → chunk-TQ4OAC2G.js.map} +0 -0
@@ -15,7 +15,9 @@ import { PERSISTED_ACTOR_VERSIONED } from "@/schemas/actor-persist/versioned";
15
15
  import type * as protocol from "@/schemas/client-protocol/mod";
16
16
  import { TO_CLIENT_VERSIONED } from "@/schemas/client-protocol/versioned";
17
17
  import {
18
+ arrayBuffersEqual,
18
19
  bufferToArrayBuffer,
20
+ EXTRA_ERROR_LOG,
19
21
  getEnvUniversal,
20
22
  promiseWithResolvers,
21
23
  SinglePromiseQueue,
@@ -26,7 +28,7 @@ import {
26
28
  Conn,
27
29
  type ConnId,
28
30
  generateConnId,
29
- generateConnSocketId,
31
+ generateConnRequestId,
30
32
  generateConnToken,
31
33
  } from "./conn";
32
34
  import {
@@ -45,6 +47,7 @@ import { serializeActorKey } from "./keys";
45
47
  import type {
46
48
  PersistedActor,
47
49
  PersistedConn,
50
+ PersistedHibernatableWebSocket,
48
51
  PersistedScheduleEvent,
49
52
  } from "./persisted";
50
53
  import { processMessage } from "./protocol/old";
@@ -52,6 +55,8 @@ import { CachedSerializer } from "./protocol/serde";
52
55
  import { Schedule } from "./schedule";
53
56
  import { DeadlineError, deadline } from "./utils";
54
57
 
58
+ export const PERSIST_SYMBOL = Symbol("persist");
59
+
55
60
  /**
56
61
  * Options for the `_saveState` method.
57
62
  */
@@ -131,6 +136,14 @@ export type ExtractActorConnState<A extends AnyActorInstance> =
131
136
  ? ConnState
132
137
  : never;
133
138
 
139
+ enum CanSleep {
140
+ Yes,
141
+ NotReady,
142
+ ActiveConns,
143
+ ActiveHonoHttpRequests,
144
+ ActiveRawWebSockets,
145
+ }
146
+
134
147
  export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
135
148
  // Shared actor context for this instance
136
149
  actorContext: ActorContext<S, CP, CS, V, I, DB>;
@@ -145,7 +158,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
145
158
  #stopCalled = false;
146
159
 
147
160
  get isStopping() {
148
- return this.#stopCalled || this.#sleepCalled;
161
+ return this.#stopCalled;
149
162
  }
150
163
 
151
164
  #persistChanged = false;
@@ -158,6 +171,10 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
158
171
  */
159
172
  #persist!: PersistedActor<S, CP, CS, I>;
160
173
 
174
+ get [PERSIST_SYMBOL](): PersistedActor<S, CP, CS, I> {
175
+ return this.#persist;
176
+ }
177
+
161
178
  /** Raw state without the proxy wrapper */
162
179
  #persistRaw!: PersistedActor<S, CP, CS, I>;
163
180
 
@@ -186,8 +203,11 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
186
203
 
187
204
  #sleepTimeout?: NodeJS.Timeout;
188
205
 
189
- // Track active raw requests so sleep logic can account for them
190
- #activeRawFetchCount = 0;
206
+ /**
207
+ * Track active HTTP requests through Hono router so sleep logic can
208
+ * account for them. Does not include WebSockets.
209
+ **/
210
+ #activeHonoHttpRequests = 0;
191
211
  #activeRawWebSockets = new Set<UniversalWebSocket>();
192
212
 
193
213
  #schedule!: Schedule;
@@ -217,9 +237,17 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
217
237
  return Array.from(this.#connections.entries()).map(
218
238
  ([id, conn]) => ({
219
239
  id,
220
- stateEnabled: conn.__stateEnabled,
221
240
  params: conn.params as any,
222
241
  state: conn.__stateEnabled ? conn.state : undefined,
242
+ status: conn.status,
243
+ subscriptions: conn.subscriptions.size,
244
+ lastSeen: conn.lastSeen,
245
+ stateEnabled: conn.__stateEnabled,
246
+ isHibernatable: conn.isHibernatable,
247
+ requestId: conn.__socket?.requestId,
248
+ driver: conn.__driverState
249
+ ? getConnDriverKindFromState(conn.__driverState)
250
+ : undefined,
223
251
  }),
224
252
  );
225
253
  },
@@ -235,10 +263,10 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
235
263
  await this.saveState({ immediate: true });
236
264
  },
237
265
  executeAction: async (name, params) => {
238
- const socketId = generateConnSocketId();
266
+ const requestId = generateConnRequestId();
239
267
  const conn = await this.createConn(
240
268
  {
241
- socketId,
269
+ requestId: requestId,
242
270
  driverState: { [ConnDriverKind.HTTP]: {} },
243
271
  },
244
272
  undefined,
@@ -252,7 +280,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
252
280
  params || [],
253
281
  );
254
282
  } finally {
255
- this.__connDisconnected(conn, true, socketId);
283
+ this.__connDisconnected(conn, true, requestId);
256
284
  }
257
285
  },
258
286
  };
@@ -271,7 +299,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
271
299
  }
272
300
 
273
301
  get #sleepingSupported(): boolean {
274
- return this.#actorDriver.sleep !== undefined;
302
+ return this.#actorDriver.startSleep !== undefined;
275
303
  }
276
304
 
277
305
  /**
@@ -300,6 +328,9 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
300
328
  actorId,
301
329
  };
302
330
 
331
+ const extraLogParams = actorDriver.getExtraActorLogParams?.();
332
+ if (extraLogParams) Object.assign(logParams, extraLogParams);
333
+
303
334
  this.#log = getBaseLogger().child(
304
335
  Object.assign(
305
336
  getIncludeTarget() ? { target: "actor" } : {},
@@ -861,16 +892,20 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
861
892
  __connDisconnected(
862
893
  conn: Conn<S, CP, CS, V, I, DB>,
863
894
  wasClean: boolean,
864
- socketId: string,
895
+ requestId: string,
865
896
  ) {
866
897
  // If socket ID is provided, check if it matches the current socket ID
867
898
  // If it doesn't match, this is a stale disconnect event from an old socket
868
- if (socketId && conn.__socket && socketId !== conn.__socket.socketId) {
899
+ if (
900
+ requestId &&
901
+ conn.__socket &&
902
+ requestId !== conn.__socket.requestId
903
+ ) {
869
904
  this.#rLog.debug({
870
905
  msg: "ignoring stale disconnect event",
871
906
  connId: conn.id,
872
- eventSocketId: socketId,
873
- currentSocketId: conn.__socket.socketId,
907
+ eventRequestId: requestId,
908
+ currentRequestId: conn.__socket.requestId,
874
909
  });
875
910
  return;
876
911
  }
@@ -1270,8 +1305,6 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1270
1305
 
1271
1306
  #assertReady(allowStoppingState: boolean = false) {
1272
1307
  if (!this.#ready) throw new errors.InternalError("Actor not ready");
1273
- if (!allowStoppingState && this.#sleepCalled)
1274
- throw new errors.InternalError("Actor is going to sleep");
1275
1308
  if (!allowStoppingState && this.#stopCalled)
1276
1309
  throw new errors.InternalError("Actor is stopping");
1277
1310
  }
@@ -1283,13 +1316,19 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1283
1316
  #checkConnectionsLiveness() {
1284
1317
  this.#rLog.debug({ msg: "checking connections liveness" });
1285
1318
 
1319
+ let connected = 0;
1320
+ let reconnecting = 0;
1321
+ let removed = 0;
1286
1322
  for (const conn of this.#connections.values()) {
1287
1323
  if (conn.__status === "connected") {
1324
+ connected += 1;
1288
1325
  this.#rLog.debug({
1289
1326
  msg: "connection is alive",
1290
1327
  connId: conn.id,
1291
1328
  });
1292
1329
  } else {
1330
+ reconnecting += 1;
1331
+
1293
1332
  const lastSeen = conn.__persist.lastSeen;
1294
1333
  const sinceLastSeen = Date.now() - lastSeen;
1295
1334
  if (
@@ -1313,9 +1352,18 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1313
1352
  });
1314
1353
 
1315
1354
  // Assume that the connection is dead here, no need to disconnect anything
1355
+ removed += 1;
1316
1356
  this.#removeConn(conn);
1317
1357
  }
1318
1358
  }
1359
+
1360
+ this.#rLog.debug({
1361
+ msg: "checked connection liveness",
1362
+ total: connected + reconnecting,
1363
+ connected,
1364
+ reconnecting,
1365
+ removed,
1366
+ });
1319
1367
  }
1320
1368
 
1321
1369
  /**
@@ -1482,10 +1530,6 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1482
1530
  throw new errors.FetchHandlerNotDefined();
1483
1531
  }
1484
1532
 
1485
- // Track active raw fetch while handler runs
1486
- this.#activeRawFetchCount++;
1487
- this.#resetSleepTimer();
1488
-
1489
1533
  try {
1490
1534
  const response = await this.#config.onFetch(
1491
1535
  this.actorContext,
@@ -1503,12 +1547,6 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1503
1547
  });
1504
1548
  throw error;
1505
1549
  } finally {
1506
- // Decrement active raw fetch counter and re-evaluate sleep
1507
- this.#activeRawFetchCount = Math.max(
1508
- 0,
1509
- this.#activeRawFetchCount - 1,
1510
- );
1511
- this.#resetSleepTimer();
1512
1550
  this.#savePersistThrottled();
1513
1551
  }
1514
1552
  }
@@ -1534,17 +1572,116 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1534
1572
  this.#activeRawWebSockets.add(websocket);
1535
1573
  this.#resetSleepTimer();
1536
1574
 
1537
- // Track socket close
1538
- const onSocketClosed = () => {
1575
+ // Track hibernatable WebSockets
1576
+ let rivetRequestId: ArrayBuffer | undefined;
1577
+ let persistedHibernatableWebSocket:
1578
+ | PersistedHibernatableWebSocket
1579
+ | undefined;
1580
+
1581
+ const onSocketOpened = (event: any) => {
1582
+ rivetRequestId = event?.rivetRequestId;
1583
+
1584
+ // Find hibernatable WS
1585
+ if (rivetRequestId) {
1586
+ const rivetRequestIdLocal = rivetRequestId;
1587
+ persistedHibernatableWebSocket =
1588
+ this.#persist.hibernatableWebSocket.find((ws) =>
1589
+ arrayBuffersEqual(
1590
+ ws.requestId,
1591
+ rivetRequestIdLocal,
1592
+ ),
1593
+ );
1594
+
1595
+ if (persistedHibernatableWebSocket) {
1596
+ persistedHibernatableWebSocket.lastSeenTimestamp =
1597
+ BigInt(Date.now());
1598
+ }
1599
+ }
1600
+
1601
+ this.#rLog.debug({
1602
+ msg: "actor instance onSocketOpened",
1603
+ rivetRequestId,
1604
+ isHibernatable: !!persistedHibernatableWebSocket,
1605
+ hibernationMsgIndex:
1606
+ persistedHibernatableWebSocket?.msgIndex,
1607
+ });
1608
+ };
1609
+
1610
+ const onSocketMessage = (event: any) => {
1611
+ // Update state of hibernatable WS
1612
+ if (persistedHibernatableWebSocket) {
1613
+ persistedHibernatableWebSocket.lastSeenTimestamp = BigInt(
1614
+ Date.now(),
1615
+ );
1616
+ persistedHibernatableWebSocket.msgIndex = BigInt(
1617
+ event.rivetMessageIndex,
1618
+ );
1619
+ }
1620
+
1621
+ this.#rLog.debug({
1622
+ msg: "actor instance onSocketMessage",
1623
+ rivetRequestId,
1624
+ isHibernatable: !!persistedHibernatableWebSocket,
1625
+ hibernationMsgIndex:
1626
+ persistedHibernatableWebSocket?.msgIndex,
1627
+ });
1628
+ };
1629
+
1630
+ const onSocketClosed = (_event: any) => {
1631
+ // Remove hibernatable WS
1632
+ if (rivetRequestId) {
1633
+ const rivetRequestIdLocal = rivetRequestId;
1634
+ const wsIndex =
1635
+ this.#persist.hibernatableWebSocket.findIndex((ws) =>
1636
+ arrayBuffersEqual(
1637
+ ws.requestId,
1638
+ rivetRequestIdLocal,
1639
+ ),
1640
+ );
1641
+
1642
+ const removed = this.#persist.hibernatableWebSocket.splice(
1643
+ wsIndex,
1644
+ 1,
1645
+ );
1646
+ if (removed.length > 0) {
1647
+ this.#rLog.debug({
1648
+ msg: "removed hibernatable websocket",
1649
+ rivetRequestId,
1650
+ hibernationMsgIndex:
1651
+ persistedHibernatableWebSocket?.msgIndex,
1652
+ });
1653
+ } else {
1654
+ this.#rLog.warn({
1655
+ msg: "could not find hibernatable websocket to remove",
1656
+ rivetRequestId,
1657
+ hibernationMsgIndex:
1658
+ persistedHibernatableWebSocket?.msgIndex,
1659
+ });
1660
+ }
1661
+ }
1662
+
1663
+ this.#rLog.debug({
1664
+ msg: "actor instance onSocketMessage",
1665
+ rivetRequestId,
1666
+ isHibernatable: !!persistedHibernatableWebSocket,
1667
+ hibernatableWebSocketCount:
1668
+ this.#persist.hibernatableWebSocket.length,
1669
+ });
1670
+
1539
1671
  // Remove listener and socket from tracking
1540
1672
  try {
1673
+ websocket.removeEventListener("open", onSocketOpened);
1674
+ websocket.removeEventListener("message", onSocketMessage);
1541
1675
  websocket.removeEventListener("close", onSocketClosed);
1542
1676
  websocket.removeEventListener("error", onSocketClosed);
1543
1677
  } catch {}
1544
1678
  this.#activeRawWebSockets.delete(websocket);
1545
1679
  this.#resetSleepTimer();
1546
1680
  };
1681
+
1547
1682
  try {
1683
+ websocket.addEventListener("open", onSocketOpened);
1684
+ websocket.addEventListener("message", onSocketMessage);
1548
1685
  websocket.addEventListener("close", onSocketClosed);
1549
1686
  websocket.addEventListener("error", onSocketClosed);
1550
1687
  } catch {}
@@ -1746,6 +1883,29 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1746
1883
  }
1747
1884
  }
1748
1885
 
1886
+ /**
1887
+ * Called by router middleware when an HTTP request begins.
1888
+ */
1889
+ __beginHonoHttpRequest() {
1890
+ this.#activeHonoHttpRequests++;
1891
+ this.#resetSleepTimer();
1892
+ }
1893
+
1894
+ /**
1895
+ * Called by router middleware when an HTTP request ends.
1896
+ */
1897
+ __endHonoHttpRequest() {
1898
+ this.#activeHonoHttpRequests--;
1899
+ if (this.#activeHonoHttpRequests < 0) {
1900
+ this.#activeHonoHttpRequests = 0;
1901
+ this.#rLog.warn({
1902
+ msg: "active hono requests went below 0, this is a RivetKit bug",
1903
+ ...EXTRA_ERROR_LOG,
1904
+ });
1905
+ }
1906
+ this.#resetSleepTimer();
1907
+ }
1908
+
1749
1909
  // MARK: Sleep
1750
1910
  /**
1751
1911
  * Reset timer from the last actor interaction that allows it to be put to sleep.
@@ -1764,8 +1924,9 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1764
1924
 
1765
1925
  this.#rLog.debug({
1766
1926
  msg: "resetting sleep timer",
1767
- canSleep,
1927
+ canSleep: CanSleep[canSleep],
1768
1928
  existingTimeout: !!this.#sleepTimeout,
1929
+ timeout: this.#config.options.sleepTimeout,
1769
1930
  });
1770
1931
 
1771
1932
  if (this.#sleepTimeout) {
@@ -1776,64 +1937,84 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1776
1937
  // Don't set a new timer if already sleeping
1777
1938
  if (this.#sleepCalled) return;
1778
1939
 
1779
- if (canSleep) {
1940
+ if (canSleep === CanSleep.Yes) {
1780
1941
  this.#sleepTimeout = setTimeout(() => {
1781
- this._sleep().catch((error) => {
1782
- this.#rLog.error({
1783
- msg: "error during sleep",
1784
- error: stringifyError(error),
1785
- });
1786
- });
1942
+ this._startSleep();
1787
1943
  }, this.#config.options.sleepTimeout);
1788
1944
  }
1789
1945
  }
1790
1946
 
1791
1947
  /** If this actor can be put in a sleeping state. */
1792
- #canSleep(): boolean {
1793
- if (!this.#ready) return false;
1948
+ #canSleep(): CanSleep {
1949
+ if (!this.#ready) return CanSleep.NotReady;
1950
+
1951
+ // Do not sleep if Hono HTTP requests are in-flight
1952
+ if (this.#activeHonoHttpRequests > 0)
1953
+ return CanSleep.ActiveHonoHttpRequests;
1954
+
1955
+ // TODO: When WS hibernation is ready, update this to only count non-hibernatable websockets
1956
+ // Do not sleep if there are raw websockets open
1957
+ if (this.#activeRawWebSockets.size > 0)
1958
+ return CanSleep.ActiveRawWebSockets;
1794
1959
 
1795
1960
  // Check for active conns. This will also cover active actions, since all actions have a connection.
1796
1961
  for (const conn of this.#connections.values()) {
1797
- if (conn.status === "connected") return false;
1798
- }
1799
-
1800
- // Do not sleep if raw fetches are in-flight
1801
- if (this.#activeRawFetchCount > 0) return false;
1962
+ // TODO: Enable this when hibernation is implemented. We're waiting on support for Guard to not auto-wake the actor if it sleeps.
1963
+ // if (conn.status === "connected" && !conn.isHibernatable)
1964
+ // return false;
1802
1965
 
1803
- // Do not sleep if there are raw websockets open
1804
- if (this.#activeRawWebSockets.size > 0) return false;
1966
+ if (conn.status === "connected") return CanSleep.ActiveConns;
1967
+ }
1805
1968
 
1806
- return true;
1969
+ return CanSleep.Yes;
1807
1970
  }
1808
1971
 
1809
- /** Puts an actor to sleep. This should just start the sleep sequence, most shutdown logic should be in _stop (which is called by the ActorDriver when sleeping). */
1810
- async _sleep() {
1811
- const sleep = this.#actorDriver.sleep?.bind(
1812
- this.#actorDriver,
1813
- this.#actorId,
1814
- );
1815
- invariant(this.#sleepingSupported, "sleeping not supported");
1816
- invariant(sleep, "no sleep on driver");
1817
-
1972
+ /**
1973
+ * Puts an actor to sleep. This should just start the sleep sequence, most shutdown logic should be in _stop (which is called by the ActorDriver when sleeping).
1974
+ *
1975
+ * For the engine, this will:
1976
+ * 1. Publish EventActorIntent with ActorIntentSleep (via driver.startSleep)
1977
+ * 2. Engine runner will wait for CommandStopActor
1978
+ * 3. Engine runner will call _onStop and wait for it to finish
1979
+ * 4. Engine runner will publish EventActorStateUpdate with ActorStateSTop
1980
+ **/
1981
+ _startSleep() {
1982
+ // IMPORTANT: #sleepCalled should have no effect on the actor's
1983
+ // behavior aside from preventing calling _startSleep twice. Wait for
1984
+ // `_onStop` before putting in a stopping state.
1818
1985
  if (this.#sleepCalled) {
1819
1986
  this.#rLog.warn({ msg: "already sleeping actor" });
1820
1987
  return;
1821
1988
  }
1822
1989
  this.#sleepCalled = true;
1823
1990
 
1991
+ // NOTE: Publishes ActorIntentSleep
1992
+ const sleep = this.#actorDriver.startSleep?.bind(
1993
+ this.#actorDriver,
1994
+ this.#actorId,
1995
+ );
1996
+ invariant(this.#sleepingSupported, "sleeping not supported");
1997
+ invariant(sleep, "no sleep on driver");
1998
+
1824
1999
  this.#rLog.info({ msg: "actor sleeping" });
1825
2000
 
1826
2001
  // Schedule sleep to happen on the next tick. This allows for any action that calls _sleep to complete.
1827
- setImmediate(async () => {
2002
+ setImmediate(() => {
1828
2003
  // The actor driver should call stop when ready to stop
1829
2004
  //
1830
2005
  // This will call _stop once Pegboard responds with the new status
1831
- await sleep();
2006
+ sleep();
1832
2007
  });
1833
2008
  }
1834
2009
 
1835
2010
  // MARK: Stop
1836
- async _stop() {
2011
+ /**
2012
+ * For the engine:
2013
+ * 1. Engine runner receives CommandStopActor
2014
+ * 2. Engine runner calls _onStop and waits for it to finish
2015
+ * 3. Engine runner publishes EventActorStateUpdate with ActorStateSTop
2016
+ */
2017
+ async _onStop() {
1837
2018
  if (this.#stopCalled) {
1838
2019
  this.#rLog.warn({ msg: "already stopping actor" });
1839
2020
  return;
@@ -1980,6 +2161,11 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
1980
2161
  },
1981
2162
  },
1982
2163
  })),
2164
+ hibernatableWebSocket: persist.hibernatableWebSocket.map((ws) => ({
2165
+ requestId: ws.requestId,
2166
+ lastSeenTimestamp: ws.lastSeenTimestamp,
2167
+ msgIndex: ws.msgIndex,
2168
+ })),
1983
2169
  };
1984
2170
  }
1985
2171
 
@@ -2012,6 +2198,11 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
2012
2198
  },
2013
2199
  },
2014
2200
  })),
2201
+ hibernatableWebSocket: bareData.hibernatableWebSocket.map((ws) => ({
2202
+ requestId: ws.requestId,
2203
+ lastSeenTimestamp: ws.lastSeenTimestamp,
2204
+ msgIndex: ws.msgIndex,
2205
+ })),
2015
2206
  };
2016
2207
  }
2017
2208
  }
@@ -5,6 +5,7 @@ export interface PersistedActor<S, CP, CS, I> {
5
5
  state: S;
6
6
  connections: PersistedConn<CP, CS>[];
7
7
  scheduledEvents: PersistedScheduleEvent[];
8
+ hibernatableWebSocket: PersistedHibernatableWebSocket[];
8
9
  }
9
10
 
10
11
  /** Object representing connection that gets persisted to storage. */
@@ -37,3 +38,9 @@ export interface PersistedScheduleEvent {
37
38
  timestamp: number;
38
39
  kind: PersistedScheduleEventKind;
39
40
  }
41
+
42
+ export interface PersistedHibernatableWebSocket {
43
+ requestId: ArrayBuffer;
44
+ lastSeenTimestamp: bigint;
45
+ msgIndex: bigint;
46
+ }