rivetkit 2.0.24-rc.1 → 2.0.24

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 (228) hide show
  1. package/dist/schemas/actor-persist/v2.ts +3 -3
  2. package/dist/schemas/actor-persist/v3.ts +274 -0
  3. package/dist/schemas/client-protocol/v2.ts +432 -0
  4. package/dist/schemas/file-system-driver/v2.ts +136 -0
  5. package/dist/tsup/actor/errors.cjs +2 -4
  6. package/dist/tsup/actor/errors.cjs.map +1 -1
  7. package/dist/tsup/actor/errors.d.cts +7 -10
  8. package/dist/tsup/actor/errors.d.ts +7 -10
  9. package/dist/tsup/actor/errors.js +9 -11
  10. package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.cts → actor-router-consts-DzI2szci.d.cts} +5 -9
  11. package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.ts → actor-router-consts-DzI2szci.d.ts} +5 -9
  12. package/dist/tsup/{chunk-HHFKKVLR.cjs → chunk-3543NCSN.cjs} +45 -57
  13. package/dist/tsup/chunk-3543NCSN.cjs.map +1 -0
  14. package/dist/tsup/chunk-4SHILYS5.cjs +5694 -0
  15. package/dist/tsup/chunk-4SHILYS5.cjs.map +1 -0
  16. package/dist/tsup/{chunk-ZTH3KYFH.cjs → chunk-5BZO5XPS.cjs} +3 -3
  17. package/dist/tsup/{chunk-ZTH3KYFH.cjs.map → chunk-5BZO5XPS.cjs.map} +1 -1
  18. package/dist/tsup/{chunk-PLUN2NQT.js → chunk-BAIGSF64.js} +189 -187
  19. package/dist/tsup/chunk-BAIGSF64.js.map +1 -0
  20. package/dist/tsup/{chunk-SHVX2QUR.cjs → chunk-CHLZBSI2.cjs} +17 -17
  21. package/dist/tsup/chunk-CHLZBSI2.cjs.map +1 -0
  22. package/dist/tsup/chunk-D3SLADUD.cjs +512 -0
  23. package/dist/tsup/chunk-D3SLADUD.cjs.map +1 -0
  24. package/dist/tsup/{chunk-KSRXX3Z4.cjs → chunk-D6762AOA.cjs} +20 -25
  25. package/dist/tsup/chunk-D6762AOA.cjs.map +1 -0
  26. package/dist/tsup/{chunk-7L65NNWP.cjs → chunk-DLK5YCTN.cjs} +187 -185
  27. package/dist/tsup/chunk-DLK5YCTN.cjs.map +1 -0
  28. package/dist/tsup/{chunk-YBG6R7LX.js → chunk-DUJQWGYD.js} +3 -7
  29. package/dist/tsup/chunk-DUJQWGYD.js.map +1 -0
  30. package/dist/tsup/{chunk-CD33GT6Z.js → chunk-EIPANQMF.js} +2 -2
  31. package/dist/tsup/{chunk-2JYPS5YM.cjs → chunk-ESMTDP7G.cjs} +6 -6
  32. package/dist/tsup/chunk-ESMTDP7G.cjs.map +1 -0
  33. package/dist/tsup/{chunk-VHGY7PU5.cjs → chunk-FVAKREFB.cjs} +1900 -1737
  34. package/dist/tsup/chunk-FVAKREFB.cjs.map +1 -0
  35. package/dist/tsup/{chunk-BLK27ES3.js → chunk-I3XT7WOF.js} +44 -56
  36. package/dist/tsup/chunk-I3XT7WOF.js.map +1 -0
  37. package/dist/tsup/{chunk-YBHYXIP6.js → chunk-IMDS5T42.js} +3 -3
  38. package/dist/tsup/chunk-IMDS5T42.js.map +1 -0
  39. package/dist/tsup/{chunk-INNFK746.cjs → chunk-J3HZJF2P.cjs} +10 -14
  40. package/dist/tsup/chunk-J3HZJF2P.cjs.map +1 -0
  41. package/dist/tsup/{chunk-BYMKMOBS.js → chunk-MBBJUHSP.js} +1844 -1681
  42. package/dist/tsup/chunk-MBBJUHSP.js.map +1 -0
  43. package/dist/tsup/{chunk-BOMZS2TJ.js → chunk-MO5CB6MD.js} +9 -9
  44. package/dist/tsup/chunk-MO5CB6MD.js.map +1 -0
  45. package/dist/tsup/chunk-OFOTPKAH.js +512 -0
  46. package/dist/tsup/chunk-OFOTPKAH.js.map +1 -0
  47. package/dist/tsup/{chunk-G64QUEDJ.js → chunk-W6RDS6NW.js} +23 -28
  48. package/dist/tsup/chunk-W6RDS6NW.js.map +1 -0
  49. package/dist/tsup/{chunk-36JJ4IQB.cjs → chunk-YC5DUHPM.cjs} +4 -8
  50. package/dist/tsup/chunk-YC5DUHPM.cjs.map +1 -0
  51. package/dist/tsup/{chunk-FX7TWFQR.js → chunk-YC7YPM2T.js} +2 -6
  52. package/dist/tsup/chunk-YC7YPM2T.js.map +1 -0
  53. package/dist/tsup/{chunk-227FEWMB.js → chunk-ZSPU5R4C.js} +3322 -2251
  54. package/dist/tsup/chunk-ZSPU5R4C.js.map +1 -0
  55. package/dist/tsup/client/mod.cjs +9 -9
  56. package/dist/tsup/client/mod.d.cts +5 -7
  57. package/dist/tsup/client/mod.d.ts +5 -7
  58. package/dist/tsup/client/mod.js +8 -8
  59. package/dist/tsup/common/log.cjs +3 -3
  60. package/dist/tsup/common/log.js +2 -2
  61. package/dist/tsup/common/websocket.cjs +4 -4
  62. package/dist/tsup/common/websocket.js +3 -3
  63. package/dist/tsup/{conn-B3Vhbgnd.d.ts → config-BRDYDraU.d.cts} +1119 -1047
  64. package/dist/tsup/{conn-DJWL3nGx.d.cts → config-Bo-blHpJ.d.ts} +1119 -1047
  65. package/dist/tsup/driver-helpers/mod.cjs +5 -13
  66. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  67. package/dist/tsup/driver-helpers/mod.d.cts +11 -9
  68. package/dist/tsup/driver-helpers/mod.d.ts +11 -9
  69. package/dist/tsup/driver-helpers/mod.js +14 -22
  70. package/dist/tsup/driver-test-suite/mod.cjs +474 -303
  71. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  72. package/dist/tsup/driver-test-suite/mod.d.cts +6 -9
  73. package/dist/tsup/driver-test-suite/mod.d.ts +6 -9
  74. package/dist/tsup/driver-test-suite/mod.js +1085 -914
  75. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  76. package/dist/tsup/inspector/mod.cjs +6 -6
  77. package/dist/tsup/inspector/mod.d.cts +5 -7
  78. package/dist/tsup/inspector/mod.d.ts +5 -7
  79. package/dist/tsup/inspector/mod.js +5 -5
  80. package/dist/tsup/mod.cjs +10 -16
  81. package/dist/tsup/mod.cjs.map +1 -1
  82. package/dist/tsup/mod.d.cts +23 -25
  83. package/dist/tsup/mod.d.ts +23 -25
  84. package/dist/tsup/mod.js +17 -23
  85. package/dist/tsup/test/mod.cjs +11 -11
  86. package/dist/tsup/test/mod.d.cts +4 -6
  87. package/dist/tsup/test/mod.d.ts +4 -6
  88. package/dist/tsup/test/mod.js +10 -10
  89. package/dist/tsup/utils.cjs +3 -5
  90. package/dist/tsup/utils.cjs.map +1 -1
  91. package/dist/tsup/utils.d.cts +1 -2
  92. package/dist/tsup/utils.d.ts +1 -2
  93. package/dist/tsup/utils.js +2 -4
  94. package/package.json +13 -6
  95. package/src/actor/config.ts +56 -44
  96. package/src/actor/conn/driver.ts +61 -0
  97. package/src/actor/conn/drivers/http.ts +17 -0
  98. package/src/actor/conn/drivers/raw-request.ts +24 -0
  99. package/src/actor/conn/drivers/raw-websocket.ts +65 -0
  100. package/src/actor/conn/drivers/websocket.ts +129 -0
  101. package/src/actor/conn/mod.ts +232 -0
  102. package/src/actor/conn/persisted.ts +81 -0
  103. package/src/actor/conn/state-manager.ts +196 -0
  104. package/src/actor/contexts/action.ts +23 -0
  105. package/src/actor/{context.ts → contexts/actor.ts} +19 -8
  106. package/src/actor/contexts/conn-init.ts +31 -0
  107. package/src/actor/contexts/conn.ts +48 -0
  108. package/src/actor/contexts/create-conn-state.ts +13 -0
  109. package/src/actor/contexts/on-before-connect.ts +13 -0
  110. package/src/actor/contexts/on-connect.ts +22 -0
  111. package/src/actor/contexts/request.ts +48 -0
  112. package/src/actor/contexts/websocket.ts +48 -0
  113. package/src/actor/definition.ts +3 -3
  114. package/src/actor/driver.ts +36 -5
  115. package/src/actor/errors.ts +19 -24
  116. package/src/actor/instance/connection-manager.ts +465 -0
  117. package/src/actor/instance/event-manager.ts +292 -0
  118. package/src/actor/instance/kv.ts +15 -0
  119. package/src/actor/instance/mod.ts +1107 -0
  120. package/src/actor/instance/persisted.ts +67 -0
  121. package/src/actor/instance/schedule-manager.ts +349 -0
  122. package/src/actor/instance/state-manager.ts +502 -0
  123. package/src/actor/mod.ts +13 -16
  124. package/src/actor/protocol/old.ts +131 -43
  125. package/src/actor/protocol/serde.ts +19 -4
  126. package/src/actor/router-endpoints.ts +61 -586
  127. package/src/actor/router-websocket-endpoints.ts +408 -0
  128. package/src/actor/router.ts +63 -197
  129. package/src/actor/schedule.ts +1 -1
  130. package/src/client/actor-conn.ts +183 -249
  131. package/src/client/actor-handle.ts +29 -6
  132. package/src/client/client.ts +0 -4
  133. package/src/client/config.ts +1 -4
  134. package/src/client/mod.ts +0 -1
  135. package/src/client/raw-utils.ts +3 -3
  136. package/src/client/utils.ts +85 -39
  137. package/src/common/actor-router-consts.ts +5 -12
  138. package/src/common/{inline-websocket-adapter2.ts → inline-websocket-adapter.ts} +26 -48
  139. package/src/common/log.ts +1 -1
  140. package/src/common/router.ts +28 -17
  141. package/src/common/utils.ts +2 -0
  142. package/src/driver-helpers/mod.ts +7 -10
  143. package/src/driver-helpers/utils.ts +18 -9
  144. package/src/driver-test-suite/mod.ts +26 -50
  145. package/src/driver-test-suite/test-inline-client-driver.ts +27 -51
  146. package/src/driver-test-suite/tests/actor-conn-hibernation.ts +150 -0
  147. package/src/driver-test-suite/tests/actor-conn-state.ts +1 -4
  148. package/src/driver-test-suite/tests/actor-conn.ts +5 -9
  149. package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
  150. package/src/driver-test-suite/tests/actor-driver.ts +0 -7
  151. package/src/driver-test-suite/tests/actor-handle.ts +12 -12
  152. package/src/driver-test-suite/tests/actor-metadata.ts +1 -1
  153. package/src/driver-test-suite/tests/manager-driver.ts +1 -1
  154. package/src/driver-test-suite/tests/raw-http-direct-registry.ts +8 -8
  155. package/src/driver-test-suite/tests/raw-http-request-properties.ts +6 -5
  156. package/src/driver-test-suite/tests/raw-http.ts +5 -5
  157. package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +7 -7
  158. package/src/driver-test-suite/tests/request-access.ts +4 -4
  159. package/src/driver-test-suite/utils.ts +6 -10
  160. package/src/drivers/engine/actor-driver.ts +614 -424
  161. package/src/drivers/engine/mod.ts +0 -1
  162. package/src/drivers/file-system/actor.ts +24 -12
  163. package/src/drivers/file-system/global-state.ts +427 -37
  164. package/src/drivers/file-system/manager.ts +71 -83
  165. package/src/drivers/file-system/mod.ts +3 -0
  166. package/src/drivers/file-system/utils.ts +18 -8
  167. package/src/engine-process/mod.ts +38 -38
  168. package/src/inspector/utils.ts +7 -5
  169. package/src/manager/driver.ts +11 -4
  170. package/src/manager/gateway.ts +4 -29
  171. package/src/manager/protocol/mod.ts +0 -2
  172. package/src/manager/protocol/query.ts +0 -4
  173. package/src/manager/router.ts +67 -64
  174. package/src/manager-api/actors.ts +13 -0
  175. package/src/mod.ts +1 -3
  176. package/src/registry/mod.ts +20 -20
  177. package/src/registry/serve.ts +9 -14
  178. package/src/remote-manager-driver/actor-websocket-client.ts +1 -16
  179. package/src/remote-manager-driver/api-endpoints.ts +13 -1
  180. package/src/remote-manager-driver/api-utils.ts +8 -0
  181. package/src/remote-manager-driver/metadata.ts +58 -0
  182. package/src/remote-manager-driver/mod.ts +47 -62
  183. package/src/remote-manager-driver/ws-proxy.ts +1 -1
  184. package/src/schemas/actor-persist/mod.ts +1 -1
  185. package/src/schemas/actor-persist/versioned.ts +56 -31
  186. package/src/schemas/client-protocol/mod.ts +1 -1
  187. package/src/schemas/client-protocol/versioned.ts +41 -21
  188. package/src/schemas/client-protocol-zod/mod.ts +103 -0
  189. package/src/schemas/file-system-driver/mod.ts +1 -1
  190. package/src/schemas/file-system-driver/versioned.ts +42 -19
  191. package/src/serde.ts +33 -11
  192. package/src/test/mod.ts +7 -3
  193. package/src/utils/node.ts +173 -0
  194. package/src/utils.ts +0 -4
  195. package/dist/tsup/chunk-227FEWMB.js.map +0 -1
  196. package/dist/tsup/chunk-2JYPS5YM.cjs.map +0 -1
  197. package/dist/tsup/chunk-36JJ4IQB.cjs.map +0 -1
  198. package/dist/tsup/chunk-7L65NNWP.cjs.map +0 -1
  199. package/dist/tsup/chunk-BLK27ES3.js.map +0 -1
  200. package/dist/tsup/chunk-BOMZS2TJ.js.map +0 -1
  201. package/dist/tsup/chunk-BYMKMOBS.js.map +0 -1
  202. package/dist/tsup/chunk-FX7TWFQR.js.map +0 -1
  203. package/dist/tsup/chunk-G64QUEDJ.js.map +0 -1
  204. package/dist/tsup/chunk-HHFKKVLR.cjs.map +0 -1
  205. package/dist/tsup/chunk-INNFK746.cjs.map +0 -1
  206. package/dist/tsup/chunk-KSRXX3Z4.cjs.map +0 -1
  207. package/dist/tsup/chunk-O44LFKSB.cjs +0 -4623
  208. package/dist/tsup/chunk-O44LFKSB.cjs.map +0 -1
  209. package/dist/tsup/chunk-PLUN2NQT.js.map +0 -1
  210. package/dist/tsup/chunk-S4UJG7ZE.js +0 -1119
  211. package/dist/tsup/chunk-S4UJG7ZE.js.map +0 -1
  212. package/dist/tsup/chunk-SHVX2QUR.cjs.map +0 -1
  213. package/dist/tsup/chunk-VFB23BYZ.cjs +0 -1119
  214. package/dist/tsup/chunk-VFB23BYZ.cjs.map +0 -1
  215. package/dist/tsup/chunk-VHGY7PU5.cjs.map +0 -1
  216. package/dist/tsup/chunk-YBG6R7LX.js.map +0 -1
  217. package/dist/tsup/chunk-YBHYXIP6.js.map +0 -1
  218. package/src/actor/action.ts +0 -178
  219. package/src/actor/conn-drivers.ts +0 -216
  220. package/src/actor/conn-socket.ts +0 -8
  221. package/src/actor/conn.ts +0 -272
  222. package/src/actor/instance.ts +0 -2336
  223. package/src/actor/persisted.ts +0 -49
  224. package/src/actor/unstable-react.ts +0 -110
  225. package/src/driver-test-suite/tests/actor-reconnect.ts +0 -170
  226. package/src/drivers/engine/kv.ts +0 -3
  227. package/src/manager/hono-websocket-adapter.ts +0 -393
  228. /package/dist/tsup/{chunk-CD33GT6Z.js.map → chunk-EIPANQMF.js.map} +0 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Persisted data structures for actors.
3
+ *
4
+ * Keep this file in sync with the Connection section of rivetkit-typescript/packages/rivetkit/schemas/actor-persist/
5
+ */
6
+
7
+ import * as cbor from "cbor-x";
8
+ import type * as persistSchema from "@/schemas/actor-persist/mod";
9
+ import { bufferToArrayBuffer } from "@/utils";
10
+
11
+ export type Cbor = ArrayBuffer;
12
+
13
+ // MARK: Schedule Event
14
+ /** Scheduled event to be executed at a specific timestamp */
15
+ export interface PersistedScheduleEvent {
16
+ eventId: string;
17
+ timestamp: number;
18
+ action: string;
19
+ args?: Cbor;
20
+ }
21
+
22
+ // MARK: Actor
23
+ /** State object that gets automatically persisted to storage */
24
+ export interface PersistedActor<S, I> {
25
+ /** Input data passed to the actor on initialization */
26
+ input?: I;
27
+ hasInitialized: boolean;
28
+ state: S;
29
+ scheduledEvents: PersistedScheduleEvent[];
30
+ }
31
+
32
+ export function convertActorToBarePersisted<S, I>(
33
+ persist: PersistedActor<S, I>,
34
+ ): persistSchema.Actor {
35
+ return {
36
+ input:
37
+ persist.input !== undefined
38
+ ? bufferToArrayBuffer(cbor.encode(persist.input))
39
+ : null,
40
+ hasInitialized: persist.hasInitialized,
41
+ state: bufferToArrayBuffer(cbor.encode(persist.state)),
42
+ scheduledEvents: persist.scheduledEvents.map((event) => ({
43
+ eventId: event.eventId,
44
+ timestamp: BigInt(event.timestamp),
45
+ action: event.action,
46
+ args: event.args ?? null,
47
+ })),
48
+ };
49
+ }
50
+
51
+ export function convertActorFromBarePersisted<S, I>(
52
+ bareData: persistSchema.Actor,
53
+ ): PersistedActor<S, I> {
54
+ return {
55
+ input: bareData.input
56
+ ? cbor.decode(new Uint8Array(bareData.input))
57
+ : undefined,
58
+ hasInitialized: bareData.hasInitialized,
59
+ state: cbor.decode(new Uint8Array(bareData.state)),
60
+ scheduledEvents: bareData.scheduledEvents.map((event) => ({
61
+ eventId: event.eventId,
62
+ timestamp: Number(event.timestamp),
63
+ action: event.action,
64
+ args: event.args ?? undefined,
65
+ })),
66
+ };
67
+ }
@@ -0,0 +1,349 @@
1
+ import * as cbor from "cbor-x";
2
+ import {
3
+ bufferToArrayBuffer,
4
+ SinglePromiseQueue,
5
+ stringifyError,
6
+ } from "@/utils";
7
+ import type { AnyDatabaseProvider } from "../database";
8
+ import type { ActorDriver } from "../driver";
9
+ import type { ActorInstance } from "./mod";
10
+ import type { PersistedScheduleEvent } from "./persisted";
11
+
12
+ /**
13
+ * Manages scheduled events and alarms for actor instances.
14
+ * Handles event scheduling, alarm triggers, and automatic event execution.
15
+ */
16
+ export class ScheduleManager<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
17
+ #actor: ActorInstance<S, CP, CS, V, I, DB>;
18
+ #actorDriver: ActorDriver;
19
+ #alarmWriteQueue = new SinglePromiseQueue();
20
+ #config: any; // ActorConfig type
21
+ #persist: any; // Reference to PersistedActor
22
+
23
+ constructor(
24
+ actor: ActorInstance<S, CP, CS, V, I, DB>,
25
+ actorDriver: ActorDriver,
26
+ config: any,
27
+ ) {
28
+ this.#actor = actor;
29
+ this.#actorDriver = actorDriver;
30
+ this.#config = config;
31
+ }
32
+
33
+ // MARK: - Public API
34
+
35
+ /**
36
+ * Sets the persist object reference.
37
+ * Called after StateManager initializes the persist proxy.
38
+ */
39
+ setPersist(persist: any) {
40
+ this.#persist = persist;
41
+ }
42
+
43
+ /**
44
+ * Schedules an event to be executed at a specific timestamp.
45
+ *
46
+ * @param timestamp - Unix timestamp in milliseconds when the event should fire
47
+ * @param action - The name of the action to execute
48
+ * @param args - Arguments to pass to the action
49
+ */
50
+ async scheduleEvent(
51
+ timestamp: number,
52
+ action: string,
53
+ args: unknown[],
54
+ ): Promise<void> {
55
+ const newEvent: PersistedScheduleEvent = {
56
+ eventId: crypto.randomUUID(),
57
+ timestamp,
58
+ action,
59
+ args: bufferToArrayBuffer(cbor.encode(args)),
60
+ };
61
+
62
+ await this.#scheduleEventInner(newEvent);
63
+ }
64
+
65
+ /**
66
+ * Triggers any pending alarms that are due.
67
+ * This method is idempotent and safe to call multiple times.
68
+ */
69
+ async onAlarm(): Promise<void> {
70
+ const now = Date.now();
71
+ this.#actor.log.debug({
72
+ msg: "alarm triggered",
73
+ now,
74
+ events: this.#persist?.scheduledEvents?.length || 0,
75
+ });
76
+
77
+ if (!this.#persist?.scheduledEvents) {
78
+ this.#actor.rLog.debug({ msg: "no scheduled events" });
79
+ return;
80
+ }
81
+
82
+ // Find events that are due
83
+ const dueIndex = this.#persist.scheduledEvents.findIndex(
84
+ (x: PersistedScheduleEvent) => x.timestamp <= now,
85
+ );
86
+
87
+ if (dueIndex === -1) {
88
+ // No events are due yet
89
+ this.#actor.rLog.debug({ msg: "no events are due yet" });
90
+
91
+ // Reschedule alarm for next event if any exist
92
+ if (this.#persist.scheduledEvents.length > 0) {
93
+ const nextTs = this.#persist.scheduledEvents[0].timestamp;
94
+ this.#actor.log.debug({
95
+ msg: "alarm fired early, rescheduling for next event",
96
+ now,
97
+ nextTs,
98
+ delta: nextTs - now,
99
+ });
100
+ await this.#queueSetAlarm(nextTs);
101
+ }
102
+ return;
103
+ }
104
+
105
+ // Remove and process due events
106
+ const dueEvents = this.#persist.scheduledEvents.splice(0, dueIndex + 1);
107
+ this.#actor.log.debug({
108
+ msg: "running events",
109
+ count: dueEvents.length,
110
+ });
111
+
112
+ // Schedule next alarm if more events remain
113
+ if (this.#persist.scheduledEvents.length > 0) {
114
+ const nextTs = this.#persist.scheduledEvents[0].timestamp;
115
+ this.#actor.log.info({
116
+ msg: "setting next alarm",
117
+ nextTs,
118
+ remainingEvents: this.#persist.scheduledEvents.length,
119
+ });
120
+ await this.#queueSetAlarm(nextTs);
121
+ }
122
+
123
+ // Execute due events
124
+ await this.#executeDueEvents(dueEvents);
125
+ }
126
+
127
+ /**
128
+ * Initializes alarms on actor startup.
129
+ * Sets the alarm for the next scheduled event if any exist.
130
+ */
131
+ async initializeAlarms(): Promise<void> {
132
+ if (this.#persist?.scheduledEvents?.length > 0) {
133
+ await this.#queueSetAlarm(
134
+ this.#persist.scheduledEvents[0].timestamp,
135
+ );
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Waits for any pending alarm write operations to complete.
141
+ */
142
+ async waitForPendingAlarmWrites(): Promise<void> {
143
+ if (this.#alarmWriteQueue.runningDrainLoop) {
144
+ await this.#alarmWriteQueue.runningDrainLoop;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Gets statistics about scheduled events.
150
+ */
151
+ getScheduleStats(): {
152
+ totalEvents: number;
153
+ nextEventTime: number | null;
154
+ overdueCount: number;
155
+ } {
156
+ if (!this.#persist?.scheduledEvents) {
157
+ return {
158
+ totalEvents: 0,
159
+ nextEventTime: null,
160
+ overdueCount: 0,
161
+ };
162
+ }
163
+
164
+ const now = Date.now();
165
+ const events = this.#persist.scheduledEvents;
166
+
167
+ return {
168
+ totalEvents: events.length,
169
+ nextEventTime: events.length > 0 ? events[0].timestamp : null,
170
+ overdueCount: events.filter(
171
+ (e: PersistedScheduleEvent) => e.timestamp <= now,
172
+ ).length,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Cancels a scheduled event by its ID.
178
+ *
179
+ * @param eventId - The ID of the event to cancel
180
+ * @returns True if the event was found and cancelled
181
+ */
182
+ async cancelEvent(eventId: string): Promise<boolean> {
183
+ if (!this.#persist?.scheduledEvents) {
184
+ return false;
185
+ }
186
+
187
+ const index = this.#persist.scheduledEvents.findIndex(
188
+ (e: PersistedScheduleEvent) => e.eventId === eventId,
189
+ );
190
+
191
+ if (index === -1) {
192
+ return false;
193
+ }
194
+
195
+ // Remove the event
196
+ const wasFirst = index === 0;
197
+ this.#persist.scheduledEvents.splice(index, 1);
198
+
199
+ // If we removed the first event, update the alarm
200
+ if (wasFirst && this.#persist.scheduledEvents.length > 0) {
201
+ await this.#queueSetAlarm(
202
+ this.#persist.scheduledEvents[0].timestamp,
203
+ );
204
+ }
205
+
206
+ this.#actor.log.info({
207
+ msg: "cancelled scheduled event",
208
+ eventId,
209
+ remainingEvents: this.#persist.scheduledEvents.length,
210
+ });
211
+
212
+ return true;
213
+ }
214
+
215
+ // MARK: - Private Helpers
216
+
217
+ async #scheduleEventInner(newEvent: PersistedScheduleEvent): Promise<void> {
218
+ this.#actor.log.info({
219
+ msg: "scheduling event",
220
+ eventId: newEvent.eventId,
221
+ timestamp: newEvent.timestamp,
222
+ action: newEvent.action,
223
+ });
224
+
225
+ if (!this.#persist?.scheduledEvents) {
226
+ throw new Error("Persist not initialized");
227
+ }
228
+
229
+ // Find insertion point (events are sorted by timestamp)
230
+ const insertIndex = this.#persist.scheduledEvents.findIndex(
231
+ (x: PersistedScheduleEvent) => x.timestamp > newEvent.timestamp,
232
+ );
233
+
234
+ if (insertIndex === -1) {
235
+ // Add to end
236
+ this.#persist.scheduledEvents.push(newEvent);
237
+ } else {
238
+ // Insert at correct position
239
+ this.#persist.scheduledEvents.splice(insertIndex, 0, newEvent);
240
+ }
241
+
242
+ // Update alarm if this is the newest event
243
+ if (insertIndex === 0 || this.#persist.scheduledEvents.length === 1) {
244
+ this.#actor.log.info({
245
+ msg: "setting alarm for new event",
246
+ timestamp: newEvent.timestamp,
247
+ eventCount: this.#persist.scheduledEvents.length,
248
+ });
249
+ await this.#queueSetAlarm(newEvent.timestamp);
250
+ }
251
+ }
252
+
253
+ async #executeDueEvents(events: PersistedScheduleEvent[]): Promise<void> {
254
+ for (const event of events) {
255
+ try {
256
+ this.#actor.log.info({
257
+ msg: "executing scheduled event",
258
+ eventId: event.eventId,
259
+ timestamp: event.timestamp,
260
+ action: event.action,
261
+ });
262
+
263
+ // Look up the action function
264
+ const fn = this.#config.actions[event.action];
265
+
266
+ if (!fn) {
267
+ throw new Error(
268
+ `Missing action for scheduled event: ${event.action}`,
269
+ );
270
+ }
271
+
272
+ if (typeof fn !== "function") {
273
+ throw new Error(
274
+ `Scheduled event action ${event.action} is not a function (got ${typeof fn})`,
275
+ );
276
+ }
277
+
278
+ // Decode arguments and execute
279
+ const args = event.args
280
+ ? cbor.decode(new Uint8Array(event.args))
281
+ : [];
282
+
283
+ const result = fn.call(
284
+ undefined,
285
+ this.#actor.actorContext,
286
+ ...args,
287
+ );
288
+
289
+ // Handle async actions
290
+ if (result instanceof Promise) {
291
+ await result;
292
+ }
293
+
294
+ this.#actor.log.debug({
295
+ msg: "scheduled event completed",
296
+ eventId: event.eventId,
297
+ action: event.action,
298
+ });
299
+ } catch (error) {
300
+ this.#actor.log.error({
301
+ msg: "error executing scheduled event",
302
+ error: stringifyError(error),
303
+ eventId: event.eventId,
304
+ timestamp: event.timestamp,
305
+ action: event.action,
306
+ });
307
+
308
+ // Continue processing other events even if one fails
309
+ }
310
+ }
311
+ }
312
+
313
+ async #queueSetAlarm(timestamp: number): Promise<void> {
314
+ await this.#alarmWriteQueue.enqueue(async () => {
315
+ await this.#actorDriver.setAlarm(this.#actor, timestamp);
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Gets the next scheduled event, if any.
321
+ */
322
+ getNextEvent(): PersistedScheduleEvent | null {
323
+ if (
324
+ !this.#persist?.scheduledEvents ||
325
+ this.#persist.scheduledEvents.length === 0
326
+ ) {
327
+ return null;
328
+ }
329
+ return this.#persist.scheduledEvents[0];
330
+ }
331
+
332
+ /**
333
+ * Gets all scheduled events.
334
+ */
335
+ getAllEvents(): PersistedScheduleEvent[] {
336
+ return this.#persist?.scheduledEvents || [];
337
+ }
338
+
339
+ /**
340
+ * Clears all scheduled events.
341
+ * Use with caution - this removes all pending scheduled events.
342
+ */
343
+ clearAllEvents(): void {
344
+ if (this.#persist?.scheduledEvents) {
345
+ this.#persist.scheduledEvents = [];
346
+ this.#actor.log.warn({ msg: "cleared all scheduled events" });
347
+ }
348
+ }
349
+ }