rivetkit 2.0.2 → 2.0.4-rc.1

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 (403) hide show
  1. package/README.md +3 -5
  2. package/dist/browser/client.d.ts +2485 -0
  3. package/dist/browser/client.js +5182 -0
  4. package/dist/browser/client.js.map +1 -0
  5. package/dist/browser/inspector/client.d.ts +130 -0
  6. package/dist/browser/inspector/client.js +2854 -0
  7. package/dist/browser/inspector/client.js.map +1 -0
  8. package/dist/browser/v3-DnYObHH3.d.ts +279 -0
  9. package/dist/inspector.tar.gz +0 -0
  10. package/dist/schemas/actor-inspector/v1.ts +784 -0
  11. package/dist/schemas/actor-inspector/v2.ts +796 -0
  12. package/dist/schemas/actor-inspector/v3.ts +899 -0
  13. package/dist/schemas/actor-persist/v1.ts +225 -0
  14. package/dist/schemas/actor-persist/v2.ts +268 -0
  15. package/dist/schemas/actor-persist/v3.ts +280 -0
  16. package/dist/schemas/actor-persist/v4.ts +406 -0
  17. package/dist/schemas/client-protocol/v1.ts +441 -0
  18. package/dist/schemas/client-protocol/v2.ts +438 -0
  19. package/dist/schemas/client-protocol/v3.ts +554 -0
  20. package/dist/schemas/file-system-driver/v1.ts +108 -0
  21. package/dist/schemas/file-system-driver/v2.ts +142 -0
  22. package/dist/schemas/file-system-driver/v3.ts +167 -0
  23. package/dist/schemas/persist/v1.ts +781 -0
  24. package/dist/schemas/transport/v1.ts +697 -0
  25. package/dist/tsup/actor/errors.cjs +106 -0
  26. package/dist/tsup/actor/errors.cjs.map +1 -0
  27. package/dist/tsup/actor/errors.d.cts +188 -0
  28. package/dist/tsup/actor/errors.d.ts +188 -0
  29. package/dist/tsup/actor/errors.js +106 -0
  30. package/dist/tsup/actor/errors.js.map +1 -0
  31. package/dist/tsup/actor-router-consts-D29T1Z-K.d.cts +24 -0
  32. package/dist/tsup/actor-router-consts-D29T1Z-K.d.ts +24 -0
  33. package/dist/tsup/chunk-325TLXJT.js +1060 -0
  34. package/dist/tsup/chunk-325TLXJT.js.map +1 -0
  35. package/dist/tsup/chunk-424PT5DM.js +23 -0
  36. package/dist/tsup/chunk-424PT5DM.js.map +1 -0
  37. package/dist/tsup/chunk-4JVIG3SS.cjs +6289 -0
  38. package/dist/tsup/chunk-4JVIG3SS.cjs.map +1 -0
  39. package/dist/tsup/chunk-6LJAZ5R4.cjs +96 -0
  40. package/dist/tsup/chunk-6LJAZ5R4.cjs.map +1 -0
  41. package/dist/tsup/chunk-6XU3FMCB.cjs +534 -0
  42. package/dist/tsup/chunk-6XU3FMCB.cjs.map +1 -0
  43. package/dist/tsup/chunk-7HTNH26M.js +509 -0
  44. package/dist/tsup/chunk-7HTNH26M.js.map +1 -0
  45. package/dist/tsup/chunk-AUVH72RE.cjs +5977 -0
  46. package/dist/tsup/chunk-AUVH72RE.cjs.map +1 -0
  47. package/dist/tsup/chunk-D4BYUPNQ.js +645 -0
  48. package/dist/tsup/chunk-D4BYUPNQ.js.map +1 -0
  49. package/dist/tsup/chunk-HDQ2JUQT.cjs +23 -0
  50. package/dist/tsup/chunk-HDQ2JUQT.cjs.map +1 -0
  51. package/dist/tsup/chunk-HHXX2VRM.js +6289 -0
  52. package/dist/tsup/chunk-HHXX2VRM.js.map +1 -0
  53. package/dist/tsup/chunk-JEAEA2PB.js +49 -0
  54. package/dist/tsup/chunk-JEAEA2PB.js.map +1 -0
  55. package/dist/tsup/chunk-JYSEG3VF.cjs +642 -0
  56. package/dist/tsup/chunk-JYSEG3VF.cjs.map +1 -0
  57. package/dist/tsup/chunk-K6DGYILQ.js +2657 -0
  58. package/dist/tsup/chunk-K6DGYILQ.js.map +1 -0
  59. package/dist/tsup/chunk-KJSYAUOM.js +96 -0
  60. package/dist/tsup/chunk-KJSYAUOM.js.map +1 -0
  61. package/dist/tsup/chunk-L47L3ZWJ.cjs +509 -0
  62. package/dist/tsup/chunk-L47L3ZWJ.cjs.map +1 -0
  63. package/dist/tsup/chunk-LXUQ667X.js +2006 -0
  64. package/dist/tsup/chunk-LXUQ667X.js.map +1 -0
  65. package/dist/tsup/chunk-MXNPAB5W.js +5977 -0
  66. package/dist/tsup/chunk-MXNPAB5W.js.map +1 -0
  67. package/dist/tsup/chunk-N4KRDJ56.js +72 -0
  68. package/dist/tsup/chunk-N4KRDJ56.js.map +1 -0
  69. package/dist/tsup/chunk-NIYZDWMW.cjs +2006 -0
  70. package/dist/tsup/chunk-NIYZDWMW.cjs.map +1 -0
  71. package/dist/tsup/chunk-PQZHDKRW.cjs +1060 -0
  72. package/dist/tsup/chunk-PQZHDKRW.cjs.map +1 -0
  73. package/dist/tsup/chunk-PVOE6BU7.cjs +1050 -0
  74. package/dist/tsup/chunk-PVOE6BU7.cjs.map +1 -0
  75. package/dist/tsup/chunk-Q4UD2GA4.cjs +1810 -0
  76. package/dist/tsup/chunk-Q4UD2GA4.cjs.map +1 -0
  77. package/dist/tsup/chunk-QUD664YZ.js +1810 -0
  78. package/dist/tsup/chunk-QUD664YZ.js.map +1 -0
  79. package/dist/tsup/chunk-RTOCTWME.js +1050 -0
  80. package/dist/tsup/chunk-RTOCTWME.js.map +1 -0
  81. package/dist/tsup/chunk-SAZZ4SB2.cjs +2657 -0
  82. package/dist/tsup/chunk-SAZZ4SB2.cjs.map +1 -0
  83. package/dist/tsup/chunk-SR3KQE7Q.cjs +72 -0
  84. package/dist/tsup/chunk-SR3KQE7Q.cjs.map +1 -0
  85. package/dist/tsup/chunk-V2GHLYC6.cjs +49 -0
  86. package/dist/tsup/chunk-V2GHLYC6.cjs.map +1 -0
  87. package/dist/tsup/chunk-V3WG7XTW.cjs +645 -0
  88. package/dist/tsup/chunk-V3WG7XTW.cjs.map +1 -0
  89. package/dist/tsup/chunk-VKVNIQRQ.js +257 -0
  90. package/dist/tsup/chunk-VKVNIQRQ.js.map +1 -0
  91. package/dist/tsup/chunk-WMPW7JYC.js +642 -0
  92. package/dist/tsup/chunk-WMPW7JYC.js.map +1 -0
  93. package/dist/tsup/chunk-Z7HNQ2WF.js +534 -0
  94. package/dist/tsup/chunk-Z7HNQ2WF.js.map +1 -0
  95. package/dist/tsup/chunk-ZFY5J2EP.cjs +257 -0
  96. package/dist/tsup/chunk-ZFY5J2EP.cjs.map +1 -0
  97. package/dist/tsup/client/mod.cjs +33 -0
  98. package/dist/tsup/client/mod.cjs.map +1 -0
  99. package/dist/tsup/client/mod.d.cts +64 -0
  100. package/dist/tsup/client/mod.d.ts +64 -0
  101. package/dist/tsup/client/mod.js +33 -0
  102. package/dist/tsup/client/mod.js.map +1 -0
  103. package/dist/tsup/common/log.cjs +21 -0
  104. package/dist/tsup/common/log.cjs.map +1 -0
  105. package/dist/tsup/common/log.d.cts +34 -0
  106. package/dist/tsup/common/log.d.ts +34 -0
  107. package/dist/tsup/common/log.js +21 -0
  108. package/dist/tsup/common/log.js.map +1 -0
  109. package/dist/tsup/common/websocket.cjs +10 -0
  110. package/dist/tsup/common/websocket.cjs.map +1 -0
  111. package/dist/tsup/common/websocket.d.cts +3 -0
  112. package/dist/tsup/common/websocket.d.ts +3 -0
  113. package/dist/tsup/common/websocket.js +10 -0
  114. package/dist/tsup/common/websocket.js.map +1 -0
  115. package/dist/tsup/config-BiNoIHRs.d.cts +80 -0
  116. package/dist/tsup/config-BiNoIHRs.d.ts +80 -0
  117. package/dist/tsup/config-P3XujgRr.d.ts +2594 -0
  118. package/dist/tsup/config-_gfywqqI.d.cts +2594 -0
  119. package/dist/tsup/context-Bxd8Cx4H.d.cts +75 -0
  120. package/dist/tsup/context-uNA4TRn3.d.ts +75 -0
  121. package/dist/tsup/db/drizzle/mod.cjs +49 -0
  122. package/dist/tsup/db/drizzle/mod.cjs.map +1 -0
  123. package/dist/tsup/db/drizzle/mod.d.cts +17 -0
  124. package/dist/tsup/db/drizzle/mod.d.ts +17 -0
  125. package/dist/tsup/db/drizzle/mod.js +49 -0
  126. package/dist/tsup/db/drizzle/mod.js.map +1 -0
  127. package/dist/tsup/db/mod.cjs +9 -0
  128. package/dist/tsup/db/mod.cjs.map +1 -0
  129. package/dist/tsup/db/mod.d.cts +9 -0
  130. package/dist/tsup/db/mod.d.ts +9 -0
  131. package/dist/tsup/db/mod.js +9 -0
  132. package/dist/tsup/db/mod.js.map +1 -0
  133. package/dist/tsup/driver-BcLvZcKl.d.cts +13 -0
  134. package/dist/tsup/driver-CPGHKXyh.d.ts +13 -0
  135. package/dist/tsup/driver-helpers/mod.cjs +53 -0
  136. package/dist/tsup/driver-helpers/mod.cjs.map +1 -0
  137. package/dist/tsup/driver-helpers/mod.d.cts +47 -0
  138. package/dist/tsup/driver-helpers/mod.d.ts +47 -0
  139. package/dist/tsup/driver-helpers/mod.js +53 -0
  140. package/dist/tsup/driver-helpers/mod.js.map +1 -0
  141. package/dist/tsup/driver-test-suite/mod.cjs +4974 -0
  142. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -0
  143. package/dist/tsup/driver-test-suite/mod.d.cts +73 -0
  144. package/dist/tsup/driver-test-suite/mod.d.ts +73 -0
  145. package/dist/tsup/driver-test-suite/mod.js +4974 -0
  146. package/dist/tsup/driver-test-suite/mod.js.map +1 -0
  147. package/dist/tsup/inspector/mod.cjs +164 -0
  148. package/dist/tsup/inspector/mod.cjs.map +1 -0
  149. package/dist/tsup/inspector/mod.d.cts +130 -0
  150. package/dist/tsup/inspector/mod.d.ts +130 -0
  151. package/dist/tsup/inspector/mod.js +164 -0
  152. package/dist/tsup/inspector/mod.js.map +1 -0
  153. package/dist/tsup/keys-CydblqMh.d.cts +13 -0
  154. package/dist/tsup/keys-CydblqMh.d.ts +13 -0
  155. package/dist/tsup/mod.cjs +82 -0
  156. package/dist/tsup/mod.cjs.map +1 -0
  157. package/dist/tsup/mod.d.cts +126 -0
  158. package/dist/tsup/mod.d.ts +126 -0
  159. package/dist/tsup/mod.js +82 -0
  160. package/dist/tsup/mod.js.map +1 -0
  161. package/dist/tsup/serve-test-suite/mod.cjs +2601 -0
  162. package/dist/tsup/serve-test-suite/mod.cjs.map +1 -0
  163. package/dist/tsup/serve-test-suite/mod.d.cts +9 -0
  164. package/dist/tsup/serve-test-suite/mod.d.ts +9 -0
  165. package/dist/tsup/serve-test-suite/mod.js +2601 -0
  166. package/dist/tsup/serve-test-suite/mod.js.map +1 -0
  167. package/dist/tsup/test/mod.cjs +90 -0
  168. package/dist/tsup/test/mod.cjs.map +1 -0
  169. package/dist/tsup/test/mod.d.cts +26 -0
  170. package/dist/tsup/test/mod.d.ts +26 -0
  171. package/dist/tsup/test/mod.js +90 -0
  172. package/dist/tsup/test/mod.js.map +1 -0
  173. package/dist/tsup/utils-fwx3o3K9.d.cts +18 -0
  174. package/dist/tsup/utils-fwx3o3K9.d.ts +18 -0
  175. package/dist/tsup/utils.cjs +43 -0
  176. package/dist/tsup/utils.cjs.map +1 -0
  177. package/dist/tsup/utils.d.cts +148 -0
  178. package/dist/tsup/utils.d.ts +148 -0
  179. package/dist/tsup/utils.js +43 -0
  180. package/dist/tsup/utils.js.map +1 -0
  181. package/dist/tsup/v3-DnYObHH3.d.cts +279 -0
  182. package/dist/tsup/v3-DnYObHH3.d.ts +279 -0
  183. package/dist/tsup/workflow/mod.cjs +16 -0
  184. package/dist/tsup/workflow/mod.cjs.map +1 -0
  185. package/dist/tsup/workflow/mod.d.cts +25 -0
  186. package/dist/tsup/workflow/mod.d.ts +25 -0
  187. package/dist/tsup/workflow/mod.js +16 -0
  188. package/dist/tsup/workflow/mod.js.map +1 -0
  189. package/package.json +293 -5
  190. package/src/actor/config.ts +1221 -0
  191. package/src/actor/conn/driver.ts +61 -0
  192. package/src/actor/conn/drivers/http.ts +17 -0
  193. package/src/actor/conn/drivers/raw-request.ts +24 -0
  194. package/src/actor/conn/drivers/raw-websocket.ts +65 -0
  195. package/src/actor/conn/drivers/websocket.ts +144 -0
  196. package/src/actor/conn/mod.ts +288 -0
  197. package/src/actor/conn/persisted.ts +81 -0
  198. package/src/actor/conn/state-manager.ts +196 -0
  199. package/src/actor/contexts/action.ts +47 -0
  200. package/src/actor/contexts/base/actor.ts +347 -0
  201. package/src/actor/contexts/base/conn-init.ts +68 -0
  202. package/src/actor/contexts/base/conn.ts +73 -0
  203. package/src/actor/contexts/before-action-response.ts +42 -0
  204. package/src/actor/contexts/before-connect.ts +31 -0
  205. package/src/actor/contexts/connect.ts +42 -0
  206. package/src/actor/contexts/create-conn-state.ts +32 -0
  207. package/src/actor/contexts/create-vars.ts +39 -0
  208. package/src/actor/contexts/create.ts +39 -0
  209. package/src/actor/contexts/destroy.ts +42 -0
  210. package/src/actor/contexts/disconnect.ts +43 -0
  211. package/src/actor/contexts/index.ts +33 -0
  212. package/src/actor/contexts/request.ts +80 -0
  213. package/src/actor/contexts/run.ts +47 -0
  214. package/src/actor/contexts/sleep.ts +42 -0
  215. package/src/actor/contexts/state-change.ts +42 -0
  216. package/src/actor/contexts/wake.ts +42 -0
  217. package/src/actor/contexts/websocket.ts +80 -0
  218. package/src/actor/database.ts +13 -0
  219. package/src/actor/definition.ts +64 -0
  220. package/src/actor/driver.ts +114 -0
  221. package/src/actor/errors.ts +556 -0
  222. package/src/actor/instance/connection-manager.ts +574 -0
  223. package/src/actor/instance/event-manager.ts +314 -0
  224. package/src/actor/instance/keys.ts +146 -0
  225. package/src/actor/instance/kv.ts +241 -0
  226. package/src/actor/instance/mod.ts +1658 -0
  227. package/src/actor/instance/persisted.ts +67 -0
  228. package/src/actor/instance/queue-manager.ts +603 -0
  229. package/src/actor/instance/queue.ts +345 -0
  230. package/src/actor/instance/schedule-manager.ts +392 -0
  231. package/src/actor/instance/state-manager.ts +542 -0
  232. package/src/actor/instance/traces-driver.ts +128 -0
  233. package/src/actor/keys.test.ts +275 -0
  234. package/src/actor/keys.ts +89 -0
  235. package/src/actor/log.ts +6 -0
  236. package/src/actor/mod.ts +110 -0
  237. package/src/actor/protocol/old.ts +416 -0
  238. package/src/actor/protocol/serde.ts +222 -0
  239. package/src/actor/router-endpoints.ts +400 -0
  240. package/src/actor/router-websocket-endpoints.test.ts +54 -0
  241. package/src/actor/router-websocket-endpoints.ts +405 -0
  242. package/src/actor/router.ts +380 -0
  243. package/src/actor/schedule.ts +17 -0
  244. package/src/actor/schema.ts +291 -0
  245. package/src/actor/utils.test.ts +48 -0
  246. package/src/actor/utils.ts +158 -0
  247. package/src/client/actor-common.ts +32 -0
  248. package/src/client/actor-conn.ts +1262 -0
  249. package/src/client/actor-handle.ts +344 -0
  250. package/src/client/actor-query.ts +112 -0
  251. package/src/client/client.ts +558 -0
  252. package/src/client/config.ts +151 -0
  253. package/src/client/errors.ts +76 -0
  254. package/src/client/log.ts +5 -0
  255. package/src/client/mod.browser.ts +2 -0
  256. package/src/client/mod.ts +70 -0
  257. package/src/client/queue.ts +146 -0
  258. package/src/client/raw-utils.ts +149 -0
  259. package/src/client/test.ts +44 -0
  260. package/src/client/utils.ts +252 -0
  261. package/src/common/actor-router-consts.ts +59 -0
  262. package/src/common/cors.ts +57 -0
  263. package/src/common/eventsource-interface.ts +47 -0
  264. package/src/common/eventsource.ts +44 -0
  265. package/src/common/inline-websocket-adapter.ts +154 -0
  266. package/src/common/log-levels.ts +27 -0
  267. package/src/common/log.ts +229 -0
  268. package/src/common/logfmt.ts +221 -0
  269. package/src/common/network.ts +2 -0
  270. package/src/common/router.ts +174 -0
  271. package/src/common/utils.ts +339 -0
  272. package/src/common/websocket-interface.ts +7 -0
  273. package/src/common/websocket.ts +43 -0
  274. package/src/db/config.ts +100 -0
  275. package/src/db/drizzle/mod.ts +226 -0
  276. package/src/db/drizzle/sqlite-core.ts +22 -0
  277. package/src/db/mod.ts +125 -0
  278. package/src/db/shared.ts +92 -0
  279. package/src/db/sqlite-vfs.ts +12 -0
  280. package/src/devtools-loader/index.ts +33 -0
  281. package/src/devtools-loader/log.ts +5 -0
  282. package/src/driver-helpers/mod.ts +33 -0
  283. package/src/driver-helpers/utils.ts +54 -0
  284. package/src/driver-test-suite/log.ts +5 -0
  285. package/src/driver-test-suite/mod.ts +293 -0
  286. package/src/driver-test-suite/test-inline-client-driver.ts +307 -0
  287. package/src/driver-test-suite/tests/access-control.ts +218 -0
  288. package/src/driver-test-suite/tests/action-features.ts +203 -0
  289. package/src/driver-test-suite/tests/actor-conn-hibernation.ts +152 -0
  290. package/src/driver-test-suite/tests/actor-conn-state.ts +300 -0
  291. package/src/driver-test-suite/tests/actor-conn.ts +596 -0
  292. package/src/driver-test-suite/tests/actor-db-raw.ts +73 -0
  293. package/src/driver-test-suite/tests/actor-db.ts +477 -0
  294. package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
  295. package/src/driver-test-suite/tests/actor-driver.ts +18 -0
  296. package/src/driver-test-suite/tests/actor-error-handling.ts +150 -0
  297. package/src/driver-test-suite/tests/actor-handle.ts +312 -0
  298. package/src/driver-test-suite/tests/actor-inline-client.ts +163 -0
  299. package/src/driver-test-suite/tests/actor-inspector.ts +264 -0
  300. package/src/driver-test-suite/tests/actor-kv.ts +65 -0
  301. package/src/driver-test-suite/tests/actor-metadata.ts +116 -0
  302. package/src/driver-test-suite/tests/actor-onstatechange.ts +95 -0
  303. package/src/driver-test-suite/tests/actor-queue.ts +325 -0
  304. package/src/driver-test-suite/tests/actor-run.ts +181 -0
  305. package/src/driver-test-suite/tests/actor-schedule.ts +97 -0
  306. package/src/driver-test-suite/tests/actor-sleep.ts +415 -0
  307. package/src/driver-test-suite/tests/actor-state.ts +54 -0
  308. package/src/driver-test-suite/tests/actor-stateless.ts +70 -0
  309. package/src/driver-test-suite/tests/actor-vars.ts +97 -0
  310. package/src/driver-test-suite/tests/actor-workflow.ts +118 -0
  311. package/src/driver-test-suite/tests/manager-driver.ts +388 -0
  312. package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -0
  313. package/src/driver-test-suite/tests/raw-http-request-properties.ts +454 -0
  314. package/src/driver-test-suite/tests/raw-http.ts +359 -0
  315. package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -0
  316. package/src/driver-test-suite/tests/raw-websocket.ts +513 -0
  317. package/src/driver-test-suite/tests/request-access.ts +240 -0
  318. package/src/driver-test-suite/utils.ts +80 -0
  319. package/src/drivers/default.ts +38 -0
  320. package/src/drivers/engine/actor-driver.ts +1027 -0
  321. package/src/drivers/engine/config.ts +43 -0
  322. package/src/drivers/engine/log.ts +5 -0
  323. package/src/drivers/engine/mod.ts +36 -0
  324. package/src/drivers/file-system/actor.ts +102 -0
  325. package/src/drivers/file-system/global-state.ts +1445 -0
  326. package/src/drivers/file-system/kv-limits.ts +70 -0
  327. package/src/drivers/file-system/log.ts +5 -0
  328. package/src/drivers/file-system/manager.ts +300 -0
  329. package/src/drivers/file-system/mod.ts +78 -0
  330. package/src/drivers/file-system/sqlite-runtime.ts +210 -0
  331. package/src/drivers/file-system/utils.ts +125 -0
  332. package/src/engine-process/constants.ts +2 -0
  333. package/src/engine-process/log.ts +5 -0
  334. package/src/engine-process/mod.ts +464 -0
  335. package/src/globals.d.ts +35 -0
  336. package/src/inspector/actor-inspector.ts +352 -0
  337. package/src/inspector/config.ts +49 -0
  338. package/src/inspector/handler.ts +273 -0
  339. package/src/inspector/log.ts +5 -0
  340. package/src/inspector/mod.browser.ts +8 -0
  341. package/src/inspector/mod.ts +4 -0
  342. package/src/inspector/serve-ui.ts +40 -0
  343. package/src/inspector/transport.ts +18 -0
  344. package/src/inspector/utils.ts +32 -0
  345. package/src/manager/driver.ts +106 -0
  346. package/src/manager/gateway.ts +668 -0
  347. package/src/manager/log.ts +5 -0
  348. package/src/manager/mod.ts +2 -0
  349. package/src/manager/protocol/mod.ts +22 -0
  350. package/src/manager/protocol/query.ts +85 -0
  351. package/src/manager/router-schema.ts +22 -0
  352. package/src/manager/router.ts +660 -0
  353. package/src/manager-api/actors.ts +83 -0
  354. package/src/manager-api/common.ts +4 -0
  355. package/src/mod.ts +24 -0
  356. package/src/registry/config/driver.ts +21 -0
  357. package/src/registry/config/index.ts +510 -0
  358. package/src/registry/config/legacy-runner.ts +157 -0
  359. package/src/registry/config/runner.ts +21 -0
  360. package/src/registry/config/serverless.ts +94 -0
  361. package/src/registry/index.ts +194 -0
  362. package/src/registry/log.ts +5 -0
  363. package/src/remote-manager-driver/actor-http-client.ts +84 -0
  364. package/src/remote-manager-driver/actor-websocket-client.ts +81 -0
  365. package/src/remote-manager-driver/api-endpoints.ts +159 -0
  366. package/src/remote-manager-driver/api-utils.ts +69 -0
  367. package/src/remote-manager-driver/log.ts +5 -0
  368. package/src/remote-manager-driver/metadata.ts +64 -0
  369. package/src/remote-manager-driver/mod.ts +414 -0
  370. package/src/remote-manager-driver/ws-proxy.ts +189 -0
  371. package/src/schemas/actor-inspector/mod.ts +1 -0
  372. package/src/schemas/actor-inspector/versioned.ts +233 -0
  373. package/src/schemas/actor-persist/mod.ts +1 -0
  374. package/src/schemas/actor-persist/versioned.ts +217 -0
  375. package/src/schemas/client-protocol/mod.ts +1 -0
  376. package/src/schemas/client-protocol/versioned.ts +330 -0
  377. package/src/schemas/client-protocol-zod/mod.ts +118 -0
  378. package/src/schemas/file-system-driver/mod.ts +1 -0
  379. package/src/schemas/file-system-driver/versioned.ts +135 -0
  380. package/src/schemas/persist/mod.ts +1 -0
  381. package/src/schemas/transport/mod.ts +1 -0
  382. package/src/serde.ts +138 -0
  383. package/src/serve-test-suite/mod.ts +148 -0
  384. package/src/serverless/configure.ts +82 -0
  385. package/src/serverless/log.ts +5 -0
  386. package/src/serverless/router.test.ts +299 -0
  387. package/src/serverless/router.ts +215 -0
  388. package/src/test/log.ts +5 -0
  389. package/src/test/mod.ts +99 -0
  390. package/src/utils/crypto.ts +24 -0
  391. package/src/utils/endpoint-parser.test.ts +202 -0
  392. package/src/utils/endpoint-parser.ts +124 -0
  393. package/src/utils/env-vars.ts +78 -0
  394. package/src/utils/node.ts +178 -0
  395. package/src/utils/router.ts +83 -0
  396. package/src/utils/serve.ts +212 -0
  397. package/src/utils.test.ts +34 -0
  398. package/src/utils.ts +437 -0
  399. package/src/workflow/constants.ts +2 -0
  400. package/src/workflow/context.ts +597 -0
  401. package/src/workflow/driver.ts +194 -0
  402. package/src/workflow/inspector.ts +268 -0
  403. package/src/workflow/mod.ts +128 -0
@@ -0,0 +1,1445 @@
1
+ import invariant from "invariant";
2
+ import { lookupInRegistry } from "@/actor/definition";
3
+ import { ActorDuplicateKey } from "@/actor/errors";
4
+ import type { AnyActorInstance } from "@/actor/instance/mod";
5
+ import type { ActorKey } from "@/actor/mod";
6
+ import type { AnyClient } from "@/client/client";
7
+ import { type ActorDriver, getInitialActorKvState } from "@/driver-helpers/mod";
8
+ import type { RegistryConfig } from "@/registry/config";
9
+ import type * as schema from "@/schemas/file-system-driver/mod";
10
+ import {
11
+ ACTOR_ALARM_VERSIONED,
12
+ ACTOR_STATE_VERSIONED,
13
+ CURRENT_VERSION as FILE_SYSTEM_DRIVER_CURRENT_VERSION,
14
+ } from "@/schemas/file-system-driver/versioned";
15
+ import {
16
+ type LongTimeoutHandle,
17
+ promiseWithResolvers,
18
+ setLongTimeout,
19
+ stringifyError,
20
+ } from "@/utils";
21
+ import {
22
+ getNodeCrypto,
23
+ getNodeFs,
24
+ getNodeFsSync,
25
+ getNodePath,
26
+ } from "@/utils/node";
27
+ import { logger } from "./log";
28
+ import {
29
+ ensureDirectoryExists,
30
+ ensureDirectoryExistsSync,
31
+ getStoragePath,
32
+ } from "./utils";
33
+ import {
34
+ computePrefixUpperBound,
35
+ ensureUint8Array,
36
+ loadSqliteRuntime,
37
+ type SqliteRuntime,
38
+ type SqliteRuntimeDatabase,
39
+ } from "./sqlite-runtime";
40
+ import {
41
+ estimateKvSize,
42
+ validateKvEntries,
43
+ validateKvKey,
44
+ validateKvKeys,
45
+ } from "./kv-limits";
46
+
47
+ // Actor handler to track running instances
48
+
49
+ enum ActorLifecycleState {
50
+ NONEXISTENT, // Entry exists but actor not yet created
51
+ AWAKE, // Actor is running normally
52
+ STARTING_SLEEP, // Actor is being put to sleep
53
+ STARTING_DESTROY, // Actor is being destroyed
54
+ DESTROYED, // Actor was destroyed, should not be recreated
55
+ }
56
+
57
+ interface ActorEntry {
58
+ id: string;
59
+
60
+ state?: schema.ActorState;
61
+
62
+ /** Promise for loading the actor state. */
63
+ loadPromise?: Promise<ActorEntry>;
64
+
65
+ actor?: AnyActorInstance;
66
+ /** Promise for starting the actor. */
67
+ startPromise?: ReturnType<typeof promiseWithResolvers<void>>;
68
+ /** Promise for stopping the actor. */
69
+ stopPromise?: PromiseWithResolvers<void>;
70
+
71
+ alarmTimeout?: LongTimeoutHandle;
72
+ /** The timestamp currently scheduled for this actor's alarm (ms since epoch). */
73
+ alarmTimestamp?: number;
74
+
75
+ /** Resolver for pending write operations that need to be notified when any write completes */
76
+ pendingWriteResolver?: PromiseWithResolvers<void>;
77
+
78
+ lifecycleState: ActorLifecycleState;
79
+
80
+ // TODO: This might make sense to move in to actorstate, but we have a
81
+ // single reader/writer so it's not an issue
82
+ /** Generation of this actor when creating/destroying. */
83
+ generation: string;
84
+ }
85
+
86
+ export interface FileSystemDriverOptions {
87
+ /** Whether to persist data to disk */
88
+ persist?: boolean;
89
+ /** Custom path for storage */
90
+ customPath?: string;
91
+ /** Deprecated option retained for explicit migration to sqlite-only KV. */
92
+ useNativeSqlite?: boolean;
93
+ }
94
+
95
+ /**
96
+ * Global state for the file system driver
97
+ */
98
+ export class FileSystemGlobalState {
99
+ #storagePath: string;
100
+ #stateDir: string;
101
+ #dbsDir: string;
102
+ #alarmsDir: string;
103
+
104
+ #persist: boolean;
105
+ #sqliteRuntime: SqliteRuntime;
106
+ #actorKvDatabases = new Map<string, SqliteRuntimeDatabase>();
107
+
108
+ // IMPORTANT: Never delete from this map. Doing so will result in race
109
+ // conditions since the actor generation will cease to be tracked
110
+ // correctly. Always increment generation if a new actor is created.
111
+ #actors = new Map<string, ActorEntry>();
112
+
113
+ #actorCountOnStartup: number = 0;
114
+
115
+ #runnerParams?: {
116
+ config: RegistryConfig;
117
+ inlineClient: AnyClient;
118
+ actorDriver: ActorDriver;
119
+ };
120
+
121
+ get persist(): boolean {
122
+ return this.#persist;
123
+ }
124
+
125
+ get storagePath() {
126
+ return this.#storagePath;
127
+ }
128
+
129
+ get actorCountOnStartup() {
130
+ return this.#actorCountOnStartup;
131
+ }
132
+
133
+ constructor(options: FileSystemDriverOptions = {}) {
134
+ const { persist = true, customPath, useNativeSqlite = true } = options;
135
+ if (!useNativeSqlite) {
136
+ throw new Error(
137
+ "File-system driver no longer supports non-SQLite KV storage.",
138
+ );
139
+ }
140
+ this.#persist = persist;
141
+ this.#sqliteRuntime = loadSqliteRuntime();
142
+ this.#storagePath = persist ? (customPath ?? getStoragePath()) : "/tmp";
143
+ const path = getNodePath();
144
+ this.#stateDir = path.join(this.#storagePath, "state");
145
+ this.#dbsDir = path.join(this.#storagePath, "databases");
146
+ this.#alarmsDir = path.join(this.#storagePath, "alarms");
147
+
148
+ if (this.#persist) {
149
+ // Ensure storage directories exist synchronously during initialization
150
+ ensureDirectoryExistsSync(this.#stateDir);
151
+ ensureDirectoryExistsSync(this.#dbsDir);
152
+ ensureDirectoryExistsSync(this.#alarmsDir);
153
+
154
+ try {
155
+ const fsSync = getNodeFsSync();
156
+ const actorIds = fsSync.readdirSync(this.#stateDir);
157
+ this.#actorCountOnStartup = actorIds.length;
158
+ } catch (error) {
159
+ logger().error({ msg: "failed to count actors", error });
160
+ }
161
+
162
+ logger().debug({
163
+ msg: "file system driver ready",
164
+ dir: this.#storagePath,
165
+ actorCount: this.#actorCountOnStartup,
166
+ sqliteRuntime: this.#sqliteRuntime.kind,
167
+ });
168
+
169
+ // Cleanup stale temp files on startup
170
+ try {
171
+ this.#cleanupTempFilesSync();
172
+ } catch (err) {
173
+ logger().error({
174
+ msg: "failed to cleanup temp files",
175
+ error: err,
176
+ });
177
+ }
178
+
179
+ try {
180
+ this.#migrateLegacyKvToSqliteOnStartupSync();
181
+ } catch (error) {
182
+ logger().error({
183
+ msg: "failed legacy kv startup migration",
184
+ error,
185
+ });
186
+ throw error;
187
+ }
188
+ } else {
189
+ logger().debug({
190
+ msg: "memory driver ready",
191
+ sqliteRuntime: this.#sqliteRuntime.kind,
192
+ });
193
+ }
194
+ }
195
+
196
+ getActorStatePath(actorId: string): string {
197
+ return getNodePath().join(this.#stateDir, actorId);
198
+ }
199
+
200
+ getActorDbPath(actorId: string): string {
201
+ return getNodePath().join(this.#dbsDir, `${actorId}.db`);
202
+ }
203
+
204
+ getActorAlarmPath(actorId: string): string {
205
+ return getNodePath().join(this.#alarmsDir, actorId);
206
+ }
207
+
208
+ #getActorKvDatabasePath(actorId: string): string {
209
+ if (this.#persist) {
210
+ return this.getActorDbPath(actorId);
211
+ }
212
+ return ":memory:";
213
+ }
214
+
215
+ #ensureActorKvTables(db: SqliteRuntimeDatabase): void {
216
+ db.exec(`
217
+ CREATE TABLE IF NOT EXISTS kv (
218
+ key BLOB PRIMARY KEY NOT NULL,
219
+ value BLOB NOT NULL
220
+ )
221
+ `);
222
+ }
223
+
224
+ #getOrCreateActorKvDatabase(actorId: string): SqliteRuntimeDatabase {
225
+ const existing = this.#actorKvDatabases.get(actorId);
226
+ if (existing) {
227
+ return existing;
228
+ }
229
+
230
+ const dbPath = this.#getActorKvDatabasePath(actorId);
231
+ if (this.#persist) {
232
+ const path = getNodePath();
233
+ ensureDirectoryExistsSync(path.dirname(dbPath));
234
+ }
235
+
236
+ let db: SqliteRuntimeDatabase;
237
+ try {
238
+ db = this.#sqliteRuntime.open(dbPath);
239
+ } catch (error) {
240
+ throw new Error(
241
+ `failed to open actor kv database for actor ${actorId} at ${dbPath}: ${error}`,
242
+ );
243
+ }
244
+
245
+ this.#ensureActorKvTables(db);
246
+ this.#actorKvDatabases.set(actorId, db);
247
+ return db;
248
+ }
249
+
250
+ #closeActorKvDatabase(actorId: string): void {
251
+ const db = this.#actorKvDatabases.get(actorId);
252
+ if (!db) {
253
+ return;
254
+ }
255
+
256
+ try {
257
+ db.close();
258
+ } finally {
259
+ this.#actorKvDatabases.delete(actorId);
260
+ }
261
+ }
262
+
263
+ #putKvEntriesInDb(
264
+ db: SqliteRuntimeDatabase,
265
+ entries: [Uint8Array, Uint8Array][],
266
+ ): void {
267
+ if (entries.length === 0) {
268
+ return;
269
+ }
270
+
271
+ db.exec("BEGIN");
272
+ try {
273
+ for (const [key, value] of entries) {
274
+ db.run("INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)", [
275
+ key,
276
+ value,
277
+ ]);
278
+ }
279
+ db.exec("COMMIT");
280
+ } catch (error) {
281
+ try {
282
+ db.exec("ROLLBACK");
283
+ } catch {
284
+ // Ignore rollback errors, original error is more actionable.
285
+ }
286
+ throw error;
287
+ }
288
+ }
289
+
290
+ #isKvDbPopulated(db: SqliteRuntimeDatabase): boolean {
291
+ const row = db.get<{ count: number | bigint }>(
292
+ "SELECT COUNT(*) AS count FROM kv",
293
+ );
294
+ const count = row ? Number(row.count) : 0;
295
+ return count > 0;
296
+ }
297
+
298
+ #migrateLegacyKvToSqliteOnStartupSync(): void {
299
+ const fsSync = getNodeFsSync();
300
+ if (!fsSync.existsSync(this.#stateDir)) {
301
+ return;
302
+ }
303
+
304
+ const actorIds = fsSync
305
+ .readdirSync(this.#stateDir)
306
+ .filter((id) => !id.includes(".tmp."));
307
+
308
+ for (const actorId of actorIds) {
309
+ const statePath = this.getActorStatePath(actorId);
310
+ let state: schema.ActorState;
311
+ try {
312
+ const stateBytes = fsSync.readFileSync(statePath);
313
+ state = ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
314
+ new Uint8Array(stateBytes),
315
+ );
316
+ } catch (error) {
317
+ logger().warn({
318
+ msg: "failed to parse actor state during startup migration",
319
+ actorId,
320
+ error,
321
+ });
322
+ continue;
323
+ }
324
+
325
+ if (!state.kvStorage || state.kvStorage.length === 0) {
326
+ continue;
327
+ }
328
+
329
+ const dbPath = this.getActorDbPath(actorId);
330
+ const path = getNodePath();
331
+ ensureDirectoryExistsSync(path.dirname(dbPath));
332
+ const db = this.#sqliteRuntime.open(dbPath);
333
+ try {
334
+ this.#ensureActorKvTables(db);
335
+ if (this.#isKvDbPopulated(db)) {
336
+ continue;
337
+ }
338
+
339
+ const legacyEntries = state.kvStorage.map((entry) => [
340
+ new Uint8Array(entry.key),
341
+ new Uint8Array(entry.value),
342
+ ]) as [Uint8Array, Uint8Array][];
343
+ this.#putKvEntriesInDb(db, legacyEntries);
344
+
345
+ logger().info({
346
+ msg: "migrated legacy actor kv storage to sqlite",
347
+ actorId,
348
+ entryCount: legacyEntries.length,
349
+ });
350
+ } finally {
351
+ db.close();
352
+ }
353
+ }
354
+ }
355
+
356
+ async *getActorsIterator(params: {
357
+ cursor?: string;
358
+ }): AsyncGenerator<schema.ActorState> {
359
+ let actorIds = Array.from(this.#actors.keys()).sort();
360
+
361
+ // Check if state directory exists first
362
+ const fsSync = getNodeFsSync();
363
+ if (fsSync.existsSync(this.#stateDir)) {
364
+ actorIds = fsSync
365
+ .readdirSync(this.#stateDir)
366
+ .filter((id) => !id.includes(".tmp"))
367
+ .sort();
368
+ }
369
+
370
+ const startIndex = params.cursor
371
+ ? actorIds.indexOf(params.cursor) + 1
372
+ : 0;
373
+
374
+ for (let i = startIndex; i < actorIds.length; i++) {
375
+ const actorId = actorIds[i];
376
+ if (!actorId) {
377
+ continue;
378
+ }
379
+
380
+ try {
381
+ const state = await this.loadActorStateOrError(actorId);
382
+ yield state;
383
+ } catch (error) {
384
+ logger().error({
385
+ msg: "failed to load actor state",
386
+ actorId,
387
+ error,
388
+ });
389
+ }
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Ensures an entry exists for this actor.
395
+ *
396
+ * Used for #createActor and #loadActor.
397
+ */
398
+ #upsertEntry(actorId: string): ActorEntry {
399
+ let entry = this.#actors.get(actorId);
400
+ if (entry) {
401
+ return entry;
402
+ }
403
+
404
+ entry = {
405
+ id: actorId,
406
+ lifecycleState: ActorLifecycleState.NONEXISTENT,
407
+ generation: crypto.randomUUID(),
408
+ };
409
+ this.#actors.set(actorId, entry);
410
+ return entry;
411
+ }
412
+
413
+ /**
414
+ * Creates a new actor and writes to file system.
415
+ */
416
+ async createActor(
417
+ actorId: string,
418
+ name: string,
419
+ key: ActorKey,
420
+ input: unknown | undefined,
421
+ ): Promise<ActorEntry> {
422
+ // TODO: Does not check if actor already exists on fs
423
+
424
+ await this.#waitForActorStop(actorId);
425
+ let entry = this.#upsertEntry(actorId);
426
+
427
+ // Check if actor already exists (has state or is being stopped)
428
+ if (entry.state) {
429
+ throw new ActorDuplicateKey(name, key);
430
+ }
431
+ if (this.isActorStopping(actorId)) {
432
+ await this.#waitForActorStop(actorId);
433
+ entry = this.#upsertEntry(actorId);
434
+ }
435
+
436
+ // If actor was destroyed, reset to NONEXISTENT and increment generation
437
+ if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
438
+ entry.lifecycleState = ActorLifecycleState.NONEXISTENT;
439
+ entry.generation = crypto.randomUUID();
440
+ }
441
+
442
+ // Initialize storage (runtime KV is stored in SQLite; state.kvStorage is legacy-only)
443
+ const initialKvState = getInitialActorKvState(input);
444
+
445
+ // Initialize metadata
446
+ await this.#withActorWrite(actorId, async (lockedEntry) => {
447
+ lockedEntry.state = {
448
+ actorId,
449
+ name,
450
+ key,
451
+ createdAt: BigInt(Date.now()),
452
+ kvStorage: [],
453
+ startTs: null,
454
+ connectableTs: null,
455
+ sleepTs: null,
456
+ destroyTs: null,
457
+ };
458
+ lockedEntry.lifecycleState = ActorLifecycleState.AWAKE;
459
+ if (this.#persist) {
460
+ await this.#performWrite(
461
+ actorId,
462
+ lockedEntry.generation,
463
+ lockedEntry.state,
464
+ );
465
+ }
466
+ if (initialKvState.length > 0) {
467
+ const db = this.#getOrCreateActorKvDatabase(actorId);
468
+ this.#putKvEntriesInDb(db, initialKvState);
469
+ }
470
+ });
471
+
472
+ return entry;
473
+ }
474
+
475
+ /**
476
+ * Loads the actor from disk or returns the existing actor entry. This will return an entry even if the actor does not actually exist.
477
+ */
478
+ async loadActor(actorId: string): Promise<ActorEntry> {
479
+ const entry = this.#upsertEntry(actorId);
480
+
481
+ // Check if destroyed - don't load from disk
482
+ if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
483
+ return entry;
484
+ }
485
+
486
+ // Check if already loaded
487
+ if (entry.state) {
488
+ return entry;
489
+ }
490
+
491
+ // If not persisted, then don't load from FS
492
+ if (!this.#persist) {
493
+ return entry;
494
+ }
495
+
496
+ // If state is currently being loaded, wait for it
497
+ if (entry.loadPromise) {
498
+ await entry.loadPromise;
499
+ return entry;
500
+ }
501
+
502
+ // Start loading state
503
+ entry.loadPromise = this.loadActorState(entry);
504
+ return entry.loadPromise;
505
+ }
506
+
507
+ private async loadActorState(entry: ActorEntry) {
508
+ const stateFilePath = this.getActorStatePath(entry.id);
509
+
510
+ // Read & parse file
511
+ try {
512
+ const fs = getNodeFs();
513
+ const stateData = await fs.readFile(stateFilePath);
514
+
515
+ const loadedState =
516
+ ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
517
+ new Uint8Array(stateData),
518
+ );
519
+
520
+ // Runtime reads/writes are SQLite-only; legacy kvStorage is for one-time startup migration.
521
+ entry.state = {
522
+ ...loadedState,
523
+ kvStorage: [],
524
+ };
525
+
526
+ return entry;
527
+ } catch (innerError: any) {
528
+ // File does not exist, meaning the actor does not exist
529
+ if (innerError.code === "ENOENT") {
530
+ entry.loadPromise = undefined;
531
+ return entry;
532
+ }
533
+
534
+ // For other errors, throw
535
+ const error = new Error(
536
+ `Failed to load actor state: ${innerError}`,
537
+ );
538
+ throw error;
539
+ }
540
+ }
541
+
542
+ async loadOrCreateActor(
543
+ actorId: string,
544
+ name: string,
545
+ key: ActorKey,
546
+ input: unknown | undefined,
547
+ ): Promise<ActorEntry> {
548
+ await this.#waitForActorStop(actorId);
549
+
550
+ // Attempt to load actor
551
+ const entry = await this.loadActor(actorId);
552
+
553
+ // If no state for this actor, then create & write state
554
+ if (!entry.state) {
555
+ if (this.isActorStopping(actorId)) {
556
+ await this.#waitForActorStop(actorId);
557
+ return await this.loadOrCreateActor(actorId, name, key, input);
558
+ }
559
+
560
+ // If actor was destroyed, reset to NONEXISTENT and increment generation
561
+ if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
562
+ entry.lifecycleState = ActorLifecycleState.NONEXISTENT;
563
+ entry.generation = crypto.randomUUID();
564
+ }
565
+
566
+ // Initialize storage (runtime KV is stored in SQLite; state.kvStorage is legacy-only)
567
+ const initialKvState = getInitialActorKvState(input);
568
+
569
+ await this.#withActorWrite(actorId, async (lockedEntry) => {
570
+ lockedEntry.state = {
571
+ actorId,
572
+ name,
573
+ key: key as readonly string[],
574
+ createdAt: BigInt(Date.now()),
575
+ kvStorage: [],
576
+ startTs: null,
577
+ connectableTs: null,
578
+ sleepTs: null,
579
+ destroyTs: null,
580
+ };
581
+ if (this.#persist) {
582
+ await this.#performWrite(
583
+ actorId,
584
+ lockedEntry.generation,
585
+ lockedEntry.state,
586
+ );
587
+ }
588
+ if (initialKvState.length > 0) {
589
+ const db = this.#getOrCreateActorKvDatabase(actorId);
590
+ this.#putKvEntriesInDb(db, initialKvState);
591
+ }
592
+ });
593
+ }
594
+ return entry;
595
+ }
596
+
597
+ async sleepActor(actorId: string) {
598
+ invariant(
599
+ this.#persist,
600
+ "cannot sleep actor with memory driver, must use file system driver",
601
+ );
602
+
603
+ // Get the actor. We upsert it even though we're about to destroy it so we have a lock on flagging `destroying` as true.
604
+ const actor = this.#upsertEntry(actorId);
605
+ invariant(actor, `tried to sleep ${actorId}, does not exist`);
606
+
607
+ // Check if already destroying
608
+ if (this.isActorStopping(actorId)) {
609
+ return;
610
+ }
611
+ actor.lifecycleState = ActorLifecycleState.STARTING_SLEEP;
612
+ actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor sleep stop promise rejection", reason }));
613
+
614
+ // Wait for actor to fully start before stopping it to avoid race conditions
615
+ if (actor.loadPromise) await actor.loadPromise.catch();
616
+ if (actor.startPromise?.promise)
617
+ await actor.startPromise.promise.catch();
618
+
619
+ try {
620
+ // Update state with sleep timestamp
621
+ if (actor.state) {
622
+ await this.#withActorWrite(actorId, async (lockedEntry) => {
623
+ if (!lockedEntry.state) {
624
+ return;
625
+ }
626
+ lockedEntry.state = {
627
+ ...lockedEntry.state,
628
+ sleepTs: BigInt(Date.now()),
629
+ };
630
+ if (this.#persist) {
631
+ await this.#performWrite(
632
+ actorId,
633
+ lockedEntry.generation,
634
+ lockedEntry.state,
635
+ );
636
+ }
637
+ });
638
+ }
639
+
640
+ // Stop actor
641
+ invariant(actor.actor, "actor should be loaded");
642
+ await actor.actor.onStop("sleep");
643
+ } finally {
644
+ // Ensure any pending KV writes finish before removing the entry.
645
+ await this.#withActorWrite(actorId, async () => {});
646
+ this.#closeActorKvDatabase(actorId);
647
+ actor.stopPromise?.resolve();
648
+ actor.stopPromise = undefined;
649
+
650
+ // Remove from map after stop is complete
651
+ this.#actors.delete(actorId);
652
+ }
653
+ }
654
+
655
+ async destroyActor(actorId: string) {
656
+ // Get the actor. We upsert it even though we're about to destroy it so we have a lock on flagging `destroying` as true.
657
+ const actor = this.#upsertEntry(actorId);
658
+
659
+ // If actor is loaded, stop it first
660
+ // Check if already destroying
661
+ if (this.isActorStopping(actorId)) {
662
+ return;
663
+ }
664
+ actor.lifecycleState = ActorLifecycleState.STARTING_DESTROY;
665
+ actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor destroy stop promise rejection", reason }));
666
+
667
+ // Wait for actor to fully start before stopping it to avoid race conditions
668
+ if (actor.loadPromise) await actor.loadPromise.catch();
669
+ if (actor.startPromise?.promise)
670
+ await actor.startPromise.promise.catch();
671
+
672
+ try {
673
+ // Update state with destroy timestamp
674
+ if (actor.state) {
675
+ await this.#withActorWrite(actorId, async (lockedEntry) => {
676
+ if (!lockedEntry.state) {
677
+ return;
678
+ }
679
+ lockedEntry.state = {
680
+ ...lockedEntry.state,
681
+ destroyTs: BigInt(Date.now()),
682
+ };
683
+ if (this.#persist) {
684
+ await this.#performWrite(
685
+ actorId,
686
+ lockedEntry.generation,
687
+ lockedEntry.state,
688
+ );
689
+ }
690
+ });
691
+ }
692
+
693
+ // Stop actor if it's running
694
+ if (actor.actor) {
695
+ await actor.actor.onStop("destroy");
696
+ }
697
+
698
+ // Ensure any pending KV writes finish before deleting files.
699
+ await this.#withActorWrite(actorId, async () => {});
700
+ this.#closeActorKvDatabase(actorId);
701
+
702
+ // Clear alarm timeout if exists
703
+ if (actor.alarmTimeout) {
704
+ actor.alarmTimeout.abort();
705
+ }
706
+
707
+ // Delete persisted files if using file system driver
708
+ if (this.#persist) {
709
+ const fs = getNodeFs();
710
+
711
+ // Delete all actor files in parallel
712
+ await Promise.all([
713
+ // Delete actor state file
714
+ (async () => {
715
+ try {
716
+ await fs.unlink(this.getActorStatePath(actorId));
717
+ } catch (err: any) {
718
+ if (err?.code !== "ENOENT") {
719
+ logger().error({
720
+ msg: "failed to delete actor state file",
721
+ actorId,
722
+ error: stringifyError(err),
723
+ });
724
+ }
725
+ }
726
+ })(),
727
+ // Delete actor database file
728
+ (async () => {
729
+ try {
730
+ await fs.unlink(this.getActorDbPath(actorId));
731
+ } catch (err: any) {
732
+ if (err?.code !== "ENOENT") {
733
+ logger().error({
734
+ msg: "failed to delete actor database file",
735
+ actorId,
736
+ error: stringifyError(err),
737
+ });
738
+ }
739
+ }
740
+ })(),
741
+ // Delete actor alarm file
742
+ (async () => {
743
+ try {
744
+ await fs.unlink(this.getActorAlarmPath(actorId));
745
+ } catch (err: any) {
746
+ if (err?.code !== "ENOENT") {
747
+ logger().error({
748
+ msg: "failed to delete actor alarm file",
749
+ actorId,
750
+ error: stringifyError(err),
751
+ });
752
+ }
753
+ }
754
+ })(),
755
+ ]);
756
+ }
757
+ } finally {
758
+ // Ensure any pending KV writes finish before clearing the entry.
759
+ await this.#withActorWrite(actorId, async () => {});
760
+ actor.stopPromise?.resolve();
761
+ actor.stopPromise = undefined;
762
+
763
+ // Reset the entry
764
+ //
765
+ // Do not remove entry in order to avoid race condition with
766
+ // destroying. Next actor creation will increment the generation.
767
+ actor.state = undefined;
768
+ actor.loadPromise = undefined;
769
+ actor.actor = undefined;
770
+ actor.startPromise = undefined;
771
+ actor.alarmTimeout = undefined;
772
+ actor.alarmTimeout = undefined;
773
+ actor.pendingWriteResolver = undefined;
774
+ actor.lifecycleState = ActorLifecycleState.DESTROYED;
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Save actor state to disk.
780
+ */
781
+ async writeActor(
782
+ actorId: string,
783
+ generation: string,
784
+ state: schema.ActorState,
785
+ ): Promise<void> {
786
+ if (!this.#persist) {
787
+ return;
788
+ }
789
+
790
+ await this.#withActorWrite(actorId, async () => {
791
+ await this.#performWrite(actorId, generation, state);
792
+ });
793
+ }
794
+
795
+ isGenerationCurrentAndNotDestroyed(
796
+ actorId: string,
797
+ generation: string,
798
+ ): boolean {
799
+ const entry = this.#upsertEntry(actorId);
800
+ if (!entry) return false;
801
+ return (
802
+ entry.generation === generation &&
803
+ entry.lifecycleState !== ActorLifecycleState.STARTING_DESTROY
804
+ );
805
+ }
806
+
807
+ isActorStopping(actorId: string) {
808
+ const entry = this.#upsertEntry(actorId);
809
+ if (!entry) return false;
810
+ return (
811
+ entry.lifecycleState === ActorLifecycleState.STARTING_SLEEP ||
812
+ entry.lifecycleState === ActorLifecycleState.STARTING_DESTROY
813
+ );
814
+ }
815
+
816
+ async #waitForActorStop(actorId: string): Promise<void> {
817
+ while (true) {
818
+ const entry = this.#actors.get(actorId);
819
+ if (!entry?.stopPromise) {
820
+ return;
821
+ }
822
+ try {
823
+ await entry.stopPromise.promise;
824
+ } catch {
825
+ return;
826
+ }
827
+ }
828
+ }
829
+
830
+ async #withActorWrite<T>(
831
+ actorId: string,
832
+ fn: (entry: ActorEntry) => Promise<T>,
833
+ ): Promise<T> {
834
+ const entry = this.#actors.get(actorId);
835
+ invariant(entry, "actor entry does not exist");
836
+
837
+ const previousWrite = entry.pendingWriteResolver;
838
+ const currentWrite = promiseWithResolvers<void>((reason) => logger().warn({ msg: "unhandled kv write promise rejection", reason }));
839
+ entry.pendingWriteResolver = currentWrite;
840
+
841
+ if (previousWrite) {
842
+ try {
843
+ await previousWrite.promise;
844
+ } catch {
845
+ // Ignore failed previous writes so later writes can proceed.
846
+ }
847
+ }
848
+
849
+ try {
850
+ return await fn(entry);
851
+ } finally {
852
+ currentWrite.resolve();
853
+ if (entry.pendingWriteResolver === currentWrite) {
854
+ entry.pendingWriteResolver = undefined;
855
+ }
856
+ }
857
+ }
858
+
859
+ async #waitForPendingWrite(actorId: string): Promise<void> {
860
+ const entry = this.#actors.get(actorId);
861
+ if (!entry?.pendingWriteResolver) {
862
+ return;
863
+ }
864
+
865
+ while (entry.pendingWriteResolver) {
866
+ const pending = entry.pendingWriteResolver;
867
+ try {
868
+ await pending.promise;
869
+ } catch {
870
+ // Ignore write failures to avoid blocking reads forever.
871
+ }
872
+ }
873
+ }
874
+
875
+ async setActorAlarm(actorId: string, timestamp: number) {
876
+ const entry = this.#actors.get(actorId);
877
+ invariant(entry, "actor entry does not exist");
878
+
879
+ // Track generation of the actor when the write started to detect
880
+ // destroy/create race condition
881
+ const writeGeneration = entry.generation;
882
+ if (this.isActorStopping(actorId)) {
883
+ logger().info("skipping set alarm since actor stopping");
884
+ return;
885
+ }
886
+
887
+ // Persist alarm to disk
888
+ if (this.#persist) {
889
+ const alarmPath = this.getActorAlarmPath(actorId);
890
+ const crypto = getNodeCrypto();
891
+ const tempPath = `${alarmPath}.tmp.${crypto.randomUUID()}`;
892
+ try {
893
+ const path = getNodePath();
894
+ await ensureDirectoryExists(path.dirname(alarmPath));
895
+ const alarmData: schema.ActorAlarm = {
896
+ actorId,
897
+ timestamp: BigInt(timestamp),
898
+ };
899
+ const data = ACTOR_ALARM_VERSIONED.serializeWithEmbeddedVersion(
900
+ alarmData,
901
+ FILE_SYSTEM_DRIVER_CURRENT_VERSION,
902
+ );
903
+ const fs = getNodeFs();
904
+ await fs.writeFile(tempPath, data);
905
+
906
+ if (
907
+ !this.isGenerationCurrentAndNotDestroyed(
908
+ actorId,
909
+ writeGeneration,
910
+ )
911
+ ) {
912
+ logger().debug(
913
+ "skipping writing alarm since actor destroying or new generation",
914
+ );
915
+ return;
916
+ }
917
+
918
+ await fs.rename(tempPath, alarmPath);
919
+ } catch (error) {
920
+ try {
921
+ const fs = getNodeFs();
922
+ await fs.unlink(tempPath);
923
+ } catch {}
924
+ logger().error({
925
+ msg: "failed to write alarm",
926
+ actorId,
927
+ error,
928
+ });
929
+ throw new Error(`Failed to write alarm: ${error}`);
930
+ }
931
+ }
932
+
933
+ // Schedule timeout
934
+ this.#scheduleAlarmTimeout(actorId, timestamp);
935
+ }
936
+
937
+ /**
938
+ * Perform the actual write operation with atomic writes
939
+ */
940
+ async #performWrite(
941
+ actorId: string,
942
+ generation: string,
943
+ state: schema.ActorState,
944
+ ): Promise<void> {
945
+ const dataPath = this.getActorStatePath(actorId);
946
+ // Generate unique temp filename to prevent any race conditions
947
+ const crypto = getNodeCrypto();
948
+ const tempPath = `${dataPath}.tmp.${crypto.randomUUID()}`;
949
+
950
+ try {
951
+ // Create directory if needed
952
+ const path = getNodePath();
953
+ await ensureDirectoryExists(path.dirname(dataPath));
954
+
955
+ // Convert to BARE types for serialization
956
+ const bareState: schema.ActorState = {
957
+ actorId: state.actorId,
958
+ name: state.name,
959
+ key: state.key,
960
+ createdAt: state.createdAt,
961
+ kvStorage: state.kvStorage,
962
+ startTs: state.startTs,
963
+ connectableTs: state.connectableTs,
964
+ sleepTs: state.sleepTs,
965
+ destroyTs: state.destroyTs,
966
+ };
967
+
968
+ // Perform atomic write
969
+ const serializedState =
970
+ ACTOR_STATE_VERSIONED.serializeWithEmbeddedVersion(
971
+ bareState,
972
+ FILE_SYSTEM_DRIVER_CURRENT_VERSION,
973
+ );
974
+ const fs = getNodeFs();
975
+ await fs.writeFile(tempPath, serializedState);
976
+
977
+ if (!this.isGenerationCurrentAndNotDestroyed(actorId, generation)) {
978
+ logger().debug(
979
+ "skipping writing alarm since actor destroying or new generation",
980
+ );
981
+ return;
982
+ }
983
+
984
+ await fs.rename(tempPath, dataPath);
985
+ } catch (error) {
986
+ // Cleanup temp file on error
987
+ try {
988
+ const fs = getNodeFs();
989
+ await fs.unlink(tempPath);
990
+ } catch {
991
+ // Ignore cleanup errors
992
+ }
993
+ logger().error({
994
+ msg: "failed to save actor state",
995
+ actorId,
996
+ error,
997
+ });
998
+ throw new Error(`Failed to save actor state: ${error}`);
999
+ }
1000
+ }
1001
+
1002
+ /**
1003
+ * Call this method after the actor driver has been initiated.
1004
+ *
1005
+ * This will trigger all initial alarms from the file system.
1006
+ *
1007
+ * This needs to be sync since DriverConfig.actor is sync
1008
+ */
1009
+ onRunnerStart(
1010
+ config: RegistryConfig,
1011
+ inlineClient: AnyClient,
1012
+ actorDriver: ActorDriver,
1013
+ ) {
1014
+ if (this.#runnerParams) {
1015
+ return;
1016
+ }
1017
+
1018
+ // Save runner params for future use
1019
+ this.#runnerParams = {
1020
+ config: config,
1021
+ inlineClient,
1022
+ actorDriver,
1023
+ };
1024
+
1025
+ // Load alarms from disk and schedule timeouts
1026
+ try {
1027
+ this.#loadAlarmsSync();
1028
+ } catch (err) {
1029
+ logger().error({
1030
+ msg: "failed to load alarms on startup",
1031
+ error: err,
1032
+ });
1033
+ }
1034
+ }
1035
+
1036
+ async startActor(
1037
+ config: RegistryConfig,
1038
+ inlineClient: AnyClient,
1039
+ actorDriver: ActorDriver,
1040
+ actorId: string,
1041
+ ): Promise<AnyActorInstance> {
1042
+ await this.#waitForActorStop(actorId);
1043
+
1044
+ // Get the actor metadata
1045
+ let entry = await this.loadActor(actorId);
1046
+ if (!entry.state) {
1047
+ throw new Error(
1048
+ `Actor does not exist and cannot be started: "${actorId}"`,
1049
+ );
1050
+ }
1051
+
1052
+ // Actor already starting
1053
+ if (entry.startPromise) {
1054
+ await entry.startPromise.promise;
1055
+ invariant(entry.actor, "actor should have loaded");
1056
+ return entry.actor;
1057
+ }
1058
+
1059
+ // Actor already loaded
1060
+ if (entry.actor) {
1061
+ if (entry.actor.isStopping || this.isActorStopping(actorId)) {
1062
+ await this.#waitForActorStop(actorId);
1063
+ entry = await this.loadActor(actorId);
1064
+ if (!entry.state) {
1065
+ throw new Error(
1066
+ `Actor does not exist and cannot be started: "${actorId}"`,
1067
+ );
1068
+ }
1069
+ } else {
1070
+ return entry.actor;
1071
+ }
1072
+ }
1073
+
1074
+ // Create start promise
1075
+ entry.startPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor start promise rejection", reason }));
1076
+
1077
+ try {
1078
+ // Create actor
1079
+ const definition = lookupInRegistry(config, entry.state.name);
1080
+ entry.actor = await definition.instantiate();
1081
+ entry.lifecycleState = ActorLifecycleState.AWAKE;
1082
+
1083
+ // Start actor
1084
+ await entry.actor.start(
1085
+ actorDriver,
1086
+ inlineClient,
1087
+ actorId,
1088
+ entry.state.name,
1089
+ entry.state.key as string[],
1090
+ "unknown",
1091
+ );
1092
+
1093
+ // Update state with start timestamp
1094
+ // NOTE: connectableTs is always in sync with startTs since actors become connectable immediately after starting
1095
+ const now = BigInt(Date.now());
1096
+ await this.#withActorWrite(actorId, async (lockedEntry) => {
1097
+ if (!lockedEntry.state) {
1098
+ throw new Error(
1099
+ `Actor does not exist and cannot be started: "${actorId}"`,
1100
+ );
1101
+ }
1102
+ lockedEntry.state = {
1103
+ ...lockedEntry.state,
1104
+ startTs: now,
1105
+ connectableTs: now,
1106
+ sleepTs: null, // Clear sleep timestamp when actor wakes up
1107
+ };
1108
+ if (this.#persist) {
1109
+ await this.#performWrite(
1110
+ actorId,
1111
+ lockedEntry.generation,
1112
+ lockedEntry.state,
1113
+ );
1114
+ }
1115
+ });
1116
+
1117
+ // Finish
1118
+ entry.startPromise.resolve();
1119
+ entry.startPromise = undefined;
1120
+
1121
+ return entry.actor;
1122
+ } catch (innerError) {
1123
+ const error = new Error(
1124
+ `Failed to start actor ${actorId}: ${innerError}`,
1125
+ { cause: innerError },
1126
+ );
1127
+ entry.startPromise?.reject(error);
1128
+ entry.startPromise = undefined;
1129
+ throw error;
1130
+ }
1131
+ }
1132
+
1133
+ async loadActorStateOrError(actorId: string): Promise<schema.ActorState> {
1134
+ const state = (await this.loadActor(actorId)).state;
1135
+ if (!state) throw new Error(`Actor does not exist: ${actorId}`);
1136
+ return state;
1137
+ }
1138
+
1139
+ getActorOrError(actorId: string): ActorEntry {
1140
+ const entry = this.#actors.get(actorId);
1141
+ if (!entry) throw new Error(`No entry for actor: ${actorId}`);
1142
+ return entry;
1143
+ }
1144
+
1145
+ async createDatabase(actorId: string): Promise<string | undefined> {
1146
+ return this.getActorDbPath(actorId);
1147
+ }
1148
+
1149
+ /**
1150
+ * Load all persisted alarms from disk and schedule their timers.
1151
+ */
1152
+ #loadAlarmsSync(): void {
1153
+ try {
1154
+ const fsSync = getNodeFsSync();
1155
+ const files = fsSync.existsSync(this.#alarmsDir)
1156
+ ? fsSync.readdirSync(this.#alarmsDir)
1157
+ : [];
1158
+ for (const file of files) {
1159
+ // Skip temp files
1160
+ if (file.includes(".tmp.")) continue;
1161
+ const path = getNodePath();
1162
+ const fullPath = path.join(this.#alarmsDir, file);
1163
+ try {
1164
+ const buf = fsSync.readFileSync(fullPath);
1165
+ const alarmData =
1166
+ ACTOR_ALARM_VERSIONED.deserializeWithEmbeddedVersion(
1167
+ new Uint8Array(buf),
1168
+ );
1169
+ const timestamp = Number(alarmData.timestamp);
1170
+ if (Number.isFinite(timestamp)) {
1171
+ this.#scheduleAlarmTimeout(
1172
+ alarmData.actorId,
1173
+ timestamp,
1174
+ );
1175
+ } else {
1176
+ logger().debug({
1177
+ msg: "invalid alarm file contents",
1178
+ file,
1179
+ });
1180
+ }
1181
+ } catch (err) {
1182
+ logger().error({
1183
+ msg: "failed to read alarm file",
1184
+ file,
1185
+ error: stringifyError(err),
1186
+ });
1187
+ }
1188
+ }
1189
+ } catch (err) {
1190
+ logger().error({
1191
+ msg: "failed to list alarms directory",
1192
+ error: err,
1193
+ });
1194
+ }
1195
+ }
1196
+
1197
+ /**
1198
+ * Schedule an alarm timer for an actor without writing to disk.
1199
+ */
1200
+ #scheduleAlarmTimeout(actorId: string, timestamp: number) {
1201
+ const entry = this.#upsertEntry(actorId);
1202
+
1203
+ // If there's already an earlier alarm scheduled, do not override it.
1204
+ if (
1205
+ entry.alarmTimestamp !== undefined &&
1206
+ timestamp >= entry.alarmTimestamp
1207
+ ) {
1208
+ logger().debug({
1209
+ msg: "skipping alarm schedule (later than existing)",
1210
+ actorId,
1211
+ timestamp,
1212
+ current: entry.alarmTimestamp,
1213
+ });
1214
+ return;
1215
+ }
1216
+
1217
+ logger().debug({ msg: "scheduling alarm", actorId, timestamp });
1218
+
1219
+ // Cancel existing timeout and update the current scheduled timestamp
1220
+ entry.alarmTimeout?.abort();
1221
+ entry.alarmTimestamp = timestamp;
1222
+
1223
+ const delay = Math.max(0, timestamp - Date.now());
1224
+ entry.alarmTimeout = setLongTimeout(async () => {
1225
+ // Clear currently scheduled timestamp as this alarm is firing now
1226
+ entry.alarmTimestamp = undefined;
1227
+ // On trigger: remove persisted alarm file
1228
+ if (this.#persist) {
1229
+ try {
1230
+ const fs = getNodeFs();
1231
+ await fs.unlink(this.getActorAlarmPath(actorId));
1232
+ } catch (err: any) {
1233
+ if (err?.code !== "ENOENT") {
1234
+ logger().debug({
1235
+ msg: "failed to remove alarm file",
1236
+ actorId,
1237
+ error: stringifyError(err),
1238
+ });
1239
+ }
1240
+ }
1241
+ }
1242
+
1243
+ try {
1244
+ logger().debug({ msg: "triggering alarm", actorId, timestamp });
1245
+
1246
+ // Ensure actor state exists and start actor if needed
1247
+ const loaded = await this.loadActor(actorId);
1248
+ if (!loaded.state)
1249
+ throw new Error(`Actor does not exist: ${actorId}`);
1250
+
1251
+ // Start actor if not already running
1252
+ const runnerParams = this.#runnerParams;
1253
+ invariant(runnerParams, "missing runner params");
1254
+ if (!loaded.actor) {
1255
+ await this.startActor(
1256
+ runnerParams.config,
1257
+ runnerParams.inlineClient,
1258
+ runnerParams.actorDriver,
1259
+ actorId,
1260
+ );
1261
+ }
1262
+
1263
+ invariant(loaded.actor, "actor should be loaded after wake");
1264
+ await loaded.actor.onAlarm();
1265
+ } catch (err) {
1266
+ logger().error({
1267
+ msg: "failed to handle alarm",
1268
+ actorId,
1269
+ error: stringifyError(err),
1270
+ });
1271
+ }
1272
+ }, delay);
1273
+ }
1274
+
1275
+ /**
1276
+ * Cleanup stale temp files on startup (synchronous)
1277
+ */
1278
+ #cleanupTempFilesSync(): void {
1279
+ try {
1280
+ const fsSync = getNodeFsSync();
1281
+ const files = fsSync.readdirSync(this.#stateDir);
1282
+ const tempFiles = files.filter((f) => f.includes(".tmp."));
1283
+
1284
+ const oneHourAgo = Date.now() - 3600000; // 1 hour in ms
1285
+
1286
+ for (const tempFile of tempFiles) {
1287
+ try {
1288
+ const path = getNodePath();
1289
+ const fullPath = path.join(this.#stateDir, tempFile);
1290
+ const stat = fsSync.statSync(fullPath);
1291
+
1292
+ // Remove if older than 1 hour
1293
+ if (stat.mtimeMs < oneHourAgo) {
1294
+ fsSync.unlinkSync(fullPath);
1295
+ logger().info({
1296
+ msg: "cleaned up stale temp file",
1297
+ file: tempFile,
1298
+ });
1299
+ }
1300
+ } catch (err) {
1301
+ logger().debug({
1302
+ msg: "failed to cleanup temp file",
1303
+ file: tempFile,
1304
+ error: err,
1305
+ });
1306
+ }
1307
+ }
1308
+ } catch (err) {
1309
+ logger().error({
1310
+ msg: "failed to read actors directory for cleanup",
1311
+ error: err,
1312
+ });
1313
+ }
1314
+ }
1315
+
1316
+ /**
1317
+ * Batch put KV entries for an actor.
1318
+ */
1319
+ async kvBatchPut(
1320
+ actorId: string,
1321
+ entries: [Uint8Array, Uint8Array][],
1322
+ ): Promise<void> {
1323
+ await this.loadActor(actorId);
1324
+ await this.#withActorWrite(actorId, async (entry) => {
1325
+ if (!entry.state) {
1326
+ if (this.isActorStopping(actorId)) {
1327
+ return;
1328
+ }
1329
+ throw new Error(`Actor ${actorId} state not loaded`);
1330
+ }
1331
+
1332
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1333
+ const totalSize = estimateKvSize(db);
1334
+ validateKvEntries(entries, totalSize);
1335
+ this.#putKvEntriesInDb(db, entries);
1336
+ });
1337
+ }
1338
+
1339
+ /**
1340
+ * Batch get KV entries for an actor.
1341
+ */
1342
+ async kvBatchGet(
1343
+ actorId: string,
1344
+ keys: Uint8Array[],
1345
+ ): Promise<(Uint8Array | null)[]> {
1346
+ const entry = await this.loadActor(actorId);
1347
+ await this.#waitForPendingWrite(actorId);
1348
+ if (!entry.state) {
1349
+ if (this.isActorStopping(actorId)) {
1350
+ throw new Error(`Actor ${actorId} is stopping`);
1351
+ } else {
1352
+ throw new Error(`Actor ${actorId} state not loaded`);
1353
+ }
1354
+ }
1355
+
1356
+ validateKvKeys(keys);
1357
+
1358
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1359
+ const results: (Uint8Array | null)[] = [];
1360
+ for (const key of keys) {
1361
+ const row = db.get<{ value: Uint8Array | ArrayBuffer }>(
1362
+ "SELECT value FROM kv WHERE key = ?",
1363
+ [key],
1364
+ );
1365
+ if (!row) {
1366
+ results.push(null);
1367
+ continue;
1368
+ }
1369
+ results.push(ensureUint8Array(row.value, "value"));
1370
+ }
1371
+ return results;
1372
+ }
1373
+
1374
+ /**
1375
+ * Batch delete KV entries for an actor.
1376
+ */
1377
+ async kvBatchDelete(actorId: string, keys: Uint8Array[]): Promise<void> {
1378
+ await this.loadActor(actorId);
1379
+ await this.#withActorWrite(actorId, async (entry) => {
1380
+ if (!entry.state) {
1381
+ if (this.isActorStopping(actorId)) {
1382
+ return;
1383
+ }
1384
+ throw new Error(`Actor ${actorId} state not loaded`);
1385
+ }
1386
+
1387
+ if (keys.length === 0) {
1388
+ return;
1389
+ }
1390
+ validateKvKeys(keys);
1391
+
1392
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1393
+ db.exec("BEGIN");
1394
+ try {
1395
+ for (const key of keys) {
1396
+ db.run("DELETE FROM kv WHERE key = ?", [key]);
1397
+ }
1398
+ db.exec("COMMIT");
1399
+ } catch (error) {
1400
+ try {
1401
+ db.exec("ROLLBACK");
1402
+ } catch {
1403
+ // Ignore rollback errors, original error is more actionable.
1404
+ }
1405
+ throw error;
1406
+ }
1407
+ });
1408
+ }
1409
+
1410
+ /**
1411
+ * List KV entries with a given prefix for an actor.
1412
+ */
1413
+ async kvListPrefix(
1414
+ actorId: string,
1415
+ prefix: Uint8Array,
1416
+ ): Promise<[Uint8Array, Uint8Array][]> {
1417
+ const entry = await this.loadActor(actorId);
1418
+ await this.#waitForPendingWrite(actorId);
1419
+ if (!entry.state) {
1420
+ if (this.isActorStopping(actorId)) {
1421
+ throw new Error(`Actor ${actorId} is destroying`);
1422
+ } else {
1423
+ throw new Error(`Actor ${actorId} state not loaded`);
1424
+ }
1425
+ }
1426
+ validateKvKey(prefix, "prefix key");
1427
+
1428
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1429
+ const upperBound = computePrefixUpperBound(prefix);
1430
+ const rows = upperBound
1431
+ ? db.all<{ key: Uint8Array | ArrayBuffer; value: Uint8Array | ArrayBuffer }>(
1432
+ "SELECT key, value FROM kv WHERE key >= ? AND key < ? ORDER BY key ASC",
1433
+ [prefix, upperBound],
1434
+ )
1435
+ : db.all<{ key: Uint8Array | ArrayBuffer; value: Uint8Array | ArrayBuffer }>(
1436
+ "SELECT key, value FROM kv WHERE key >= ? ORDER BY key ASC",
1437
+ [prefix],
1438
+ );
1439
+
1440
+ return rows.map((row) => [
1441
+ ensureUint8Array(row.key, "key"),
1442
+ ensureUint8Array(row.value, "value"),
1443
+ ]);
1444
+ }
1445
+ }