rivetkit 2.0.42 → 2.1.0-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 (322) hide show
  1. package/dist/{tsup/config-CLnylLYY.d.ts → browser/client.d.ts} +2127 -1910
  2. package/dist/browser/client.js +5182 -0
  3. package/dist/browser/client.js.map +1 -0
  4. package/dist/browser/inspector/client.d.ts +130 -0
  5. package/dist/browser/inspector/client.js +2854 -0
  6. package/dist/browser/inspector/client.js.map +1 -0
  7. package/dist/browser/v3-DnYObHH3.d.ts +279 -0
  8. package/dist/schemas/actor-inspector/v2.ts +796 -0
  9. package/dist/schemas/actor-inspector/v3.ts +899 -0
  10. package/dist/schemas/actor-persist/v4.ts +406 -0
  11. package/dist/schemas/client-protocol/v3.ts +554 -0
  12. package/dist/schemas/persist/v1.ts +781 -0
  13. package/dist/schemas/transport/v1.ts +697 -0
  14. package/dist/tsup/actor/errors.cjs +27 -3
  15. package/dist/tsup/actor/errors.cjs.map +1 -1
  16. package/dist/tsup/actor/errors.d.cts +37 -1
  17. package/dist/tsup/actor/errors.d.ts +37 -1
  18. package/dist/tsup/actor/errors.js +26 -1
  19. package/dist/tsup/{actor-router-consts-DzI2szci.d.cts → actor-router-consts-D29T1Z-K.d.cts} +1 -1
  20. package/dist/tsup/{actor-router-consts-DzI2szci.d.ts → actor-router-consts-D29T1Z-K.d.ts} +1 -1
  21. package/dist/tsup/chunk-424PT5DM.js +23 -0
  22. package/dist/tsup/chunk-424PT5DM.js.map +1 -0
  23. package/dist/tsup/{chunk-JDAD2YFA.js → chunk-5ESWDTHJ.js} +148 -273
  24. package/dist/tsup/chunk-5ESWDTHJ.js.map +1 -0
  25. package/dist/tsup/{chunk-FJ3KTN4V.js → chunk-6LIBPELE.js} +119 -11
  26. package/dist/tsup/chunk-6LIBPELE.js.map +1 -0
  27. package/dist/tsup/chunk-6LJAZ5R4.cjs +96 -0
  28. package/dist/tsup/chunk-6LJAZ5R4.cjs.map +1 -0
  29. package/dist/tsup/{chunk-LFVF5SCU.js → chunk-7HTNH26M.js} +126 -1
  30. package/dist/tsup/chunk-7HTNH26M.js.map +1 -0
  31. package/dist/tsup/chunk-7K4CYDGD.js +630 -0
  32. package/dist/tsup/chunk-7K4CYDGD.js.map +1 -0
  33. package/dist/tsup/{chunk-XXGJCOL6.js → chunk-A6YIZWTK.js} +2 -2
  34. package/dist/tsup/chunk-AIYEYMX5.cjs +630 -0
  35. package/dist/tsup/chunk-AIYEYMX5.cjs.map +1 -0
  36. package/dist/tsup/{chunk-Q6W7RJJP.js → chunk-DIGBC2VI.js} +211 -2316
  37. package/dist/tsup/chunk-DIGBC2VI.js.map +1 -0
  38. package/dist/tsup/{chunk-RZW2DNND.cjs → chunk-F6JYU5IK.cjs} +1957 -1039
  39. package/dist/tsup/chunk-F6JYU5IK.cjs.map +1 -0
  40. package/dist/tsup/chunk-HAZL2EPK.cjs +534 -0
  41. package/dist/tsup/chunk-HAZL2EPK.cjs.map +1 -0
  42. package/dist/tsup/chunk-HDQ2JUQT.cjs +23 -0
  43. package/dist/tsup/chunk-HDQ2JUQT.cjs.map +1 -0
  44. package/dist/tsup/chunk-HIDX4C5Y.cjs +1036 -0
  45. package/dist/tsup/chunk-HIDX4C5Y.cjs.map +1 -0
  46. package/dist/tsup/chunk-IVG73YCW.js +534 -0
  47. package/dist/tsup/chunk-IVG73YCW.js.map +1 -0
  48. package/dist/tsup/chunk-KJSYAUOM.js +96 -0
  49. package/dist/tsup/chunk-KJSYAUOM.js.map +1 -0
  50. package/dist/tsup/{chunk-2XQS746M.cjs → chunk-L47L3ZWJ.cjs} +127 -2
  51. package/dist/tsup/chunk-L47L3ZWJ.cjs.map +1 -0
  52. package/dist/tsup/{chunk-H4TB4X25.cjs → chunk-LW6KLR7A.cjs} +126 -18
  53. package/dist/tsup/chunk-LW6KLR7A.cjs.map +1 -0
  54. package/dist/tsup/chunk-LXUQ667X.js +2006 -0
  55. package/dist/tsup/chunk-LXUQ667X.js.map +1 -0
  56. package/dist/tsup/{chunk-GMAVRZSF.js → chunk-M2T62AZQ.js} +1790 -872
  57. package/dist/tsup/chunk-M2T62AZQ.js.map +1 -0
  58. package/dist/tsup/chunk-MZ37VV3P.js +5974 -0
  59. package/dist/tsup/chunk-MZ37VV3P.js.map +1 -0
  60. package/dist/tsup/chunk-N4KRDJ56.js +72 -0
  61. package/dist/tsup/chunk-N4KRDJ56.js.map +1 -0
  62. package/dist/tsup/chunk-NIYZDWMW.cjs +2006 -0
  63. package/dist/tsup/chunk-NIYZDWMW.cjs.map +1 -0
  64. package/dist/tsup/chunk-OMEPCQK2.js +649 -0
  65. package/dist/tsup/chunk-OMEPCQK2.js.map +1 -0
  66. package/dist/tsup/chunk-SR3KQE7Q.cjs +72 -0
  67. package/dist/tsup/chunk-SR3KQE7Q.cjs.map +1 -0
  68. package/dist/tsup/chunk-SSEP6DHP.cjs +2657 -0
  69. package/dist/tsup/chunk-SSEP6DHP.cjs.map +1 -0
  70. package/dist/tsup/chunk-T5YCUGVS.js +1036 -0
  71. package/dist/tsup/chunk-T5YCUGVS.js.map +1 -0
  72. package/dist/tsup/{chunk-EJVBH5VF.cjs → chunk-TPGXWFQT.cjs} +3 -3
  73. package/dist/tsup/{chunk-EJVBH5VF.cjs.map → chunk-TPGXWFQT.cjs.map} +1 -1
  74. package/dist/tsup/{chunk-X35U3YNX.cjs → chunk-TYLXNCA5.cjs} +214 -339
  75. package/dist/tsup/chunk-TYLXNCA5.cjs.map +1 -0
  76. package/dist/tsup/chunk-VKVNIQRQ.js +257 -0
  77. package/dist/tsup/chunk-VKVNIQRQ.js.map +1 -0
  78. package/dist/tsup/chunk-XWBAQO5H.cjs +649 -0
  79. package/dist/tsup/chunk-XWBAQO5H.cjs.map +1 -0
  80. package/dist/tsup/chunk-YQ4LDVD6.cjs +5974 -0
  81. package/dist/tsup/chunk-YQ4LDVD6.cjs.map +1 -0
  82. package/dist/tsup/chunk-ZFY5J2EP.cjs +257 -0
  83. package/dist/tsup/chunk-ZFY5J2EP.cjs.map +1 -0
  84. package/dist/tsup/client/mod.cjs +9 -10
  85. package/dist/tsup/client/mod.cjs.map +1 -1
  86. package/dist/tsup/client/mod.d.cts +11 -5
  87. package/dist/tsup/client/mod.d.ts +11 -5
  88. package/dist/tsup/client/mod.js +8 -8
  89. package/dist/tsup/common/log.cjs +4 -4
  90. package/dist/tsup/common/log.d.cts +2 -2
  91. package/dist/tsup/common/log.d.ts +2 -2
  92. package/dist/tsup/common/log.js +3 -2
  93. package/dist/tsup/common/websocket.cjs +5 -5
  94. package/dist/tsup/common/websocket.js +4 -3
  95. package/dist/tsup/config-BFqid9Gr.d.ts +2574 -0
  96. package/dist/tsup/config-BiNoIHRs.d.cts +80 -0
  97. package/dist/tsup/config-BiNoIHRs.d.ts +80 -0
  98. package/dist/tsup/{config-CZB2-W8x.d.cts → config-CAZphOS1.d.cts} +681 -355
  99. package/dist/tsup/db/drizzle/mod.cjs +49 -0
  100. package/dist/tsup/db/drizzle/mod.cjs.map +1 -0
  101. package/dist/tsup/db/drizzle/mod.d.cts +17 -0
  102. package/dist/tsup/db/drizzle/mod.d.ts +17 -0
  103. package/dist/tsup/db/drizzle/mod.js +49 -0
  104. package/dist/tsup/db/drizzle/mod.js.map +1 -0
  105. package/dist/tsup/db/mod.cjs +9 -0
  106. package/dist/tsup/db/mod.cjs.map +1 -0
  107. package/dist/tsup/db/mod.d.cts +9 -0
  108. package/dist/tsup/db/mod.d.ts +9 -0
  109. package/dist/tsup/db/mod.js +9 -0
  110. package/dist/tsup/db/mod.js.map +1 -0
  111. package/dist/tsup/{driver-D0QX9M11.d.ts → driver-Bxv62E2p.d.ts} +2 -2
  112. package/dist/tsup/{driver-q-zqG7fc.d.cts → driver-DYXwJR5D.d.cts} +2 -2
  113. package/dist/tsup/driver-helpers/mod.cjs +12 -6
  114. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  115. package/dist/tsup/driver-helpers/mod.d.cts +12 -5
  116. package/dist/tsup/driver-helpers/mod.d.ts +12 -5
  117. package/dist/tsup/driver-helpers/mod.js +12 -5
  118. package/dist/tsup/driver-test-suite/mod.cjs +1370 -116
  119. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  120. package/dist/tsup/driver-test-suite/mod.d.cts +10 -4
  121. package/dist/tsup/driver-test-suite/mod.d.ts +10 -4
  122. package/dist/tsup/driver-test-suite/mod.js +2093 -838
  123. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  124. package/dist/tsup/inspector/mod.cjs +29 -3
  125. package/dist/tsup/inspector/mod.cjs.map +1 -1
  126. package/dist/tsup/inspector/mod.d.cts +124 -3
  127. package/dist/tsup/inspector/mod.d.ts +124 -3
  128. package/dist/tsup/inspector/mod.js +72 -45
  129. package/dist/tsup/keys-CydblqMh.d.cts +13 -0
  130. package/dist/tsup/keys-CydblqMh.d.ts +13 -0
  131. package/dist/tsup/mod.cjs +16 -10
  132. package/dist/tsup/mod.cjs.map +1 -1
  133. package/dist/tsup/mod.d.cts +26 -14
  134. package/dist/tsup/mod.d.ts +26 -14
  135. package/dist/tsup/mod.js +20 -13
  136. package/dist/tsup/serve-test-suite/mod.cjs +1165 -83
  137. package/dist/tsup/serve-test-suite/mod.cjs.map +1 -1
  138. package/dist/tsup/serve-test-suite/mod.js +1114 -29
  139. package/dist/tsup/serve-test-suite/mod.js.map +1 -1
  140. package/dist/tsup/test/mod.cjs +84 -11
  141. package/dist/tsup/test/mod.cjs.map +1 -1
  142. package/dist/tsup/test/mod.d.cts +10 -5
  143. package/dist/tsup/test/mod.d.ts +10 -5
  144. package/dist/tsup/test/mod.js +85 -11
  145. package/dist/tsup/test/mod.js.map +1 -1
  146. package/dist/tsup/utils.cjs +10 -4
  147. package/dist/tsup/utils.cjs.map +1 -1
  148. package/dist/tsup/utils.d.cts +72 -2
  149. package/dist/tsup/utils.d.ts +72 -2
  150. package/dist/tsup/utils.js +9 -2
  151. package/dist/tsup/v3-DnYObHH3.d.cts +279 -0
  152. package/dist/tsup/v3-DnYObHH3.d.ts +279 -0
  153. package/dist/tsup/workflow/mod.cjs +16 -0
  154. package/dist/tsup/workflow/mod.cjs.map +1 -0
  155. package/dist/tsup/workflow/mod.d.cts +83 -0
  156. package/dist/tsup/workflow/mod.d.ts +83 -0
  157. package/dist/tsup/workflow/mod.js +16 -0
  158. package/dist/tsup/workflow/mod.js.map +1 -0
  159. package/package.json +62 -5
  160. package/src/actor/config.ts +478 -68
  161. package/src/actor/conn/mod.ts +68 -16
  162. package/src/actor/conn/state-manager.ts +2 -2
  163. package/src/actor/contexts/action.ts +20 -12
  164. package/src/actor/contexts/base/actor.ts +137 -7
  165. package/src/actor/contexts/base/conn-init.ts +27 -7
  166. package/src/actor/contexts/base/conn.ts +27 -18
  167. package/src/actor/contexts/before-action-response.ts +9 -2
  168. package/src/actor/contexts/before-connect.ts +7 -2
  169. package/src/actor/contexts/connect.ts +9 -2
  170. package/src/actor/contexts/create-conn-state.ts +7 -2
  171. package/src/actor/contexts/create-vars.ts +16 -3
  172. package/src/actor/contexts/create.ts +16 -3
  173. package/src/actor/contexts/destroy.ts +9 -3
  174. package/src/actor/contexts/disconnect.ts +10 -4
  175. package/src/actor/contexts/index.ts +4 -3
  176. package/src/actor/contexts/request.ts +23 -6
  177. package/src/actor/contexts/run.ts +47 -0
  178. package/src/actor/contexts/sleep.ts +9 -3
  179. package/src/actor/contexts/state-change.ts +9 -3
  180. package/src/actor/contexts/wake.ts +9 -3
  181. package/src/actor/contexts/websocket.ts +23 -6
  182. package/src/actor/database.ts +8 -18
  183. package/src/actor/definition.ts +20 -6
  184. package/src/actor/driver.ts +32 -3
  185. package/src/actor/errors.ts +127 -0
  186. package/src/actor/instance/connection-manager.ts +183 -80
  187. package/src/actor/instance/event-manager.ts +26 -15
  188. package/src/actor/instance/keys.ts +117 -0
  189. package/src/actor/instance/mod.ts +784 -174
  190. package/src/actor/instance/queue-manager.ts +603 -0
  191. package/src/actor/instance/queue.ts +287 -0
  192. package/src/actor/instance/schedule-manager.ts +49 -7
  193. package/src/actor/instance/state-manager.ts +35 -11
  194. package/src/actor/instance/traces-driver.ts +128 -0
  195. package/src/actor/mod.ts +26 -2
  196. package/src/actor/protocol/old.ts +28 -13
  197. package/src/actor/protocol/serde.ts +1 -1
  198. package/src/actor/router-endpoints.ts +177 -21
  199. package/src/actor/router-websocket-endpoints.ts +18 -29
  200. package/src/actor/router.ts +177 -0
  201. package/src/actor/schema.ts +291 -0
  202. package/src/actor/utils.ts +40 -0
  203. package/src/client/actor-common.ts +1 -1
  204. package/src/client/actor-conn.ts +100 -33
  205. package/src/client/actor-handle.ts +61 -33
  206. package/src/client/client.ts +2 -4
  207. package/src/client/config.ts +1 -1
  208. package/src/client/mod.browser.ts +2 -0
  209. package/src/client/mod.ts +1 -4
  210. package/src/client/queue.ts +146 -0
  211. package/src/client/utils.ts +1 -1
  212. package/src/common/log.ts +1 -1
  213. package/src/common/utils.ts +3 -3
  214. package/src/db/config.ts +100 -0
  215. package/src/db/drizzle/mod.ts +226 -0
  216. package/src/db/drizzle/sqlite-core.ts +22 -0
  217. package/src/db/mod.ts +125 -0
  218. package/src/db/shared.ts +92 -0
  219. package/src/db/sqlite-vfs.ts +12 -0
  220. package/src/driver-helpers/mod.ts +1 -0
  221. package/src/driver-test-suite/mod.ts +69 -43
  222. package/src/driver-test-suite/tests/access-control.ts +218 -0
  223. package/src/driver-test-suite/tests/actor-db-raw.ts +73 -0
  224. package/src/driver-test-suite/tests/actor-db.ts +394 -0
  225. package/src/driver-test-suite/tests/actor-inspector.ts +259 -358
  226. package/src/driver-test-suite/tests/actor-kv.ts +41 -20
  227. package/src/driver-test-suite/tests/actor-queue.ts +324 -0
  228. package/src/driver-test-suite/tests/actor-run.ts +181 -0
  229. package/src/driver-test-suite/tests/actor-schedule.ts +5 -2
  230. package/src/driver-test-suite/tests/actor-sleep.ts +3 -3
  231. package/src/driver-test-suite/tests/actor-stateless.ts +70 -0
  232. package/src/driver-test-suite/tests/actor-workflow.ts +108 -0
  233. package/src/driver-test-suite/tests/manager-driver.ts +11 -0
  234. package/src/driver-test-suite/tests/raw-http-request-properties.ts +1 -1
  235. package/src/driver-test-suite/tests/raw-websocket.ts +12 -12
  236. package/src/drivers/default.ts +7 -2
  237. package/src/drivers/engine/actor-driver.ts +45 -37
  238. package/src/drivers/engine/config.ts +1 -1
  239. package/src/drivers/file-system/actor.ts +20 -2
  240. package/src/drivers/file-system/global-state.ts +569 -258
  241. package/src/drivers/file-system/kv-limits.ts +70 -0
  242. package/src/drivers/file-system/manager.ts +22 -6
  243. package/src/drivers/file-system/mod.ts +39 -16
  244. package/src/drivers/file-system/sqlite-runtime.ts +210 -0
  245. package/src/inspector/actor-inspector.ts +224 -102
  246. package/src/inspector/config.ts +1 -1
  247. package/src/inspector/handler.ts +102 -20
  248. package/src/inspector/mod.browser.ts +8 -0
  249. package/src/inspector/mod.ts +2 -0
  250. package/src/inspector/serve-ui.ts +40 -0
  251. package/src/inspector/transport.ts +18 -0
  252. package/src/inspector/utils.ts +5 -39
  253. package/src/manager/gateway.ts +1 -1
  254. package/src/manager/protocol/mod.ts +1 -1
  255. package/src/manager/protocol/query.ts +1 -1
  256. package/src/manager/router-schema.ts +1 -1
  257. package/src/manager/router.ts +38 -12
  258. package/src/manager-api/actors.ts +1 -1
  259. package/src/manager-api/common.ts +1 -1
  260. package/src/registry/config/driver.ts +1 -1
  261. package/src/registry/config/index.ts +212 -43
  262. package/src/registry/config/legacy-runner.ts +1 -1
  263. package/src/registry/config/runner.ts +1 -1
  264. package/src/registry/config/serverless.ts +1 -1
  265. package/src/registry/index.ts +7 -5
  266. package/src/remote-manager-driver/api-utils.ts +1 -1
  267. package/src/schemas/actor-inspector/mod.ts +1 -1
  268. package/src/schemas/actor-inspector/versioned.ts +195 -8
  269. package/src/schemas/actor-persist/versioned.ts +87 -7
  270. package/src/schemas/client-protocol/mod.ts +1 -1
  271. package/src/schemas/client-protocol/versioned.ts +127 -11
  272. package/src/schemas/client-protocol-zod/mod.ts +16 -1
  273. package/src/schemas/persist/mod.ts +1 -0
  274. package/src/schemas/transport/mod.ts +1 -0
  275. package/src/serde.ts +1 -1
  276. package/src/serve-test-suite/mod.ts +10 -9
  277. package/src/test/mod.ts +15 -56
  278. package/src/utils/endpoint-parser.test.ts +1 -1
  279. package/src/utils/endpoint-parser.ts +1 -1
  280. package/src/utils/env-vars.ts +12 -1
  281. package/src/utils/node.ts +15 -2
  282. package/src/utils.test.ts +34 -0
  283. package/src/utils.ts +140 -6
  284. package/src/workflow/constants.ts +2 -0
  285. package/src/workflow/context.ts +532 -0
  286. package/src/workflow/driver.ts +191 -0
  287. package/src/workflow/inspector.ts +268 -0
  288. package/src/workflow/mod.ts +122 -0
  289. package/dist/tsup/chunk-2IJTYN6K.cjs +0 -278
  290. package/dist/tsup/chunk-2IJTYN6K.cjs.map +0 -1
  291. package/dist/tsup/chunk-2XQS746M.cjs.map +0 -1
  292. package/dist/tsup/chunk-3VP5CSHV.cjs +0 -114
  293. package/dist/tsup/chunk-3VP5CSHV.cjs.map +0 -1
  294. package/dist/tsup/chunk-AQFSQMBG.js +0 -114
  295. package/dist/tsup/chunk-AQFSQMBG.js.map +0 -1
  296. package/dist/tsup/chunk-E6ZE2YEA.js +0 -664
  297. package/dist/tsup/chunk-E6ZE2YEA.js.map +0 -1
  298. package/dist/tsup/chunk-FJ3KTN4V.js.map +0 -1
  299. package/dist/tsup/chunk-GBENOENJ.cjs +0 -8
  300. package/dist/tsup/chunk-GBENOENJ.cjs.map +0 -1
  301. package/dist/tsup/chunk-GD7UXGOE.cjs +0 -4762
  302. package/dist/tsup/chunk-GD7UXGOE.cjs.map +0 -1
  303. package/dist/tsup/chunk-GMAVRZSF.js.map +0 -1
  304. package/dist/tsup/chunk-H4TB4X25.cjs.map +0 -1
  305. package/dist/tsup/chunk-JDAD2YFA.js.map +0 -1
  306. package/dist/tsup/chunk-KCOVZOPS.js +0 -1946
  307. package/dist/tsup/chunk-KCOVZOPS.js.map +0 -1
  308. package/dist/tsup/chunk-KDFWJKMJ.cjs +0 -664
  309. package/dist/tsup/chunk-KDFWJKMJ.cjs.map +0 -1
  310. package/dist/tsup/chunk-LFVF5SCU.js.map +0 -1
  311. package/dist/tsup/chunk-Q6W7RJJP.js.map +0 -1
  312. package/dist/tsup/chunk-RUW5CZ5Z.cjs +0 -1949
  313. package/dist/tsup/chunk-RUW5CZ5Z.cjs.map +0 -1
  314. package/dist/tsup/chunk-RZW2DNND.cjs.map +0 -1
  315. package/dist/tsup/chunk-TCOEBUUE.js +0 -278
  316. package/dist/tsup/chunk-TCOEBUUE.js.map +0 -1
  317. package/dist/tsup/chunk-X35U3YNX.cjs.map +0 -1
  318. package/dist/tsup/keys-Chhy4ylv.d.cts +0 -8
  319. package/dist/tsup/keys-Chhy4ylv.d.ts +0 -8
  320. package/dist/tsup/v1-Gq4avTK3.d.cts +0 -240
  321. package/dist/tsup/v1-Gq4avTK3.d.ts +0 -240
  322. /package/dist/tsup/{chunk-XXGJCOL6.js.map → chunk-A6YIZWTK.js.map} +0 -0
@@ -5,6 +5,7 @@ import type { AnyActorInstance } from "@/actor/instance/mod";
5
5
  import type { ActorKey } from "@/actor/mod";
6
6
  import type { AnyClient } from "@/client/client";
7
7
  import { type ActorDriver, getInitialActorKvState } from "@/driver-helpers/mod";
8
+ import type { RegistryConfig } from "@/registry/config";
8
9
  import type * as schema from "@/schemas/file-system-driver/mod";
9
10
  import {
10
11
  ACTOR_ALARM_VERSIONED,
@@ -12,8 +13,6 @@ import {
12
13
  CURRENT_VERSION as FILE_SYSTEM_DRIVER_CURRENT_VERSION,
13
14
  } from "@/schemas/file-system-driver/versioned";
14
15
  import {
15
- arrayBuffersEqual,
16
- bufferToArrayBuffer,
17
16
  type LongTimeoutHandle,
18
17
  promiseWithResolvers,
19
18
  setLongTimeout,
@@ -31,7 +30,19 @@ import {
31
30
  ensureDirectoryExistsSync,
32
31
  getStoragePath,
33
32
  } from "./utils";
34
- import { RegistryConfig } from "@/registry/config";
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";
35
46
 
36
47
  // Actor handler to track running instances
37
48
 
@@ -54,6 +65,8 @@ interface ActorEntry {
54
65
  actor?: AnyActorInstance;
55
66
  /** Promise for starting the actor. */
56
67
  startPromise?: ReturnType<typeof promiseWithResolvers<void>>;
68
+ /** Promise for stopping the actor. */
69
+ stopPromise?: PromiseWithResolvers<void>;
57
70
 
58
71
  alarmTimeout?: LongTimeoutHandle;
59
72
  /** The timestamp currently scheduled for this actor's alarm (ms since epoch). */
@@ -70,6 +83,15 @@ interface ActorEntry {
70
83
  generation: string;
71
84
  }
72
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
+
73
95
  /**
74
96
  * Global state for the file system driver
75
97
  */
@@ -80,6 +102,8 @@ export class FileSystemGlobalState {
80
102
  #alarmsDir: string;
81
103
 
82
104
  #persist: boolean;
105
+ #sqliteRuntime: SqliteRuntime;
106
+ #actorKvDatabases = new Map<string, SqliteRuntimeDatabase>();
83
107
 
84
108
  // IMPORTANT: Never delete from this map. Doing so will result in race
85
109
  // conditions since the actor generation will cease to be tracked
@@ -106,8 +130,15 @@ export class FileSystemGlobalState {
106
130
  return this.#actorCountOnStartup;
107
131
  }
108
132
 
109
- constructor(persist: boolean = true, customPath?: string) {
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
+ }
110
140
  this.#persist = persist;
141
+ this.#sqliteRuntime = loadSqliteRuntime();
111
142
  this.#storagePath = persist ? (customPath ?? getStoragePath()) : "/tmp";
112
143
  const path = getNodePath();
113
144
  this.#stateDir = path.join(this.#storagePath, "state");
@@ -132,6 +163,7 @@ export class FileSystemGlobalState {
132
163
  msg: "file system driver ready",
133
164
  dir: this.#storagePath,
134
165
  actorCount: this.#actorCountOnStartup,
166
+ sqliteRuntime: this.#sqliteRuntime.kind,
135
167
  });
136
168
 
137
169
  // Cleanup stale temp files on startup
@@ -143,8 +175,21 @@ export class FileSystemGlobalState {
143
175
  error: err,
144
176
  });
145
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
+ }
146
188
  } else {
147
- logger().debug({ msg: "memory driver ready" });
189
+ logger().debug({
190
+ msg: "memory driver ready",
191
+ sqliteRuntime: this.#sqliteRuntime.kind,
192
+ });
148
193
  }
149
194
  }
150
195
 
@@ -160,6 +205,154 @@ export class FileSystemGlobalState {
160
205
  return getNodePath().join(this.#alarmsDir, actorId);
161
206
  }
162
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
+
163
356
  async *getActorsIterator(params: {
164
357
  cursor?: string;
165
358
  }): AsyncGenerator<schema.ActorState> {
@@ -228,14 +421,16 @@ export class FileSystemGlobalState {
228
421
  ): Promise<ActorEntry> {
229
422
  // TODO: Does not check if actor already exists on fs
230
423
 
231
- const entry = this.#upsertEntry(actorId);
424
+ await this.#waitForActorStop(actorId);
425
+ let entry = this.#upsertEntry(actorId);
232
426
 
233
427
  // Check if actor already exists (has state or is being stopped)
234
428
  if (entry.state) {
235
429
  throw new ActorDuplicateKey(name, key);
236
430
  }
237
431
  if (this.isActorStopping(actorId)) {
238
- throw new Error(`Actor ${actorId} is stopping`);
432
+ await this.#waitForActorStop(actorId);
433
+ entry = this.#upsertEntry(actorId);
239
434
  }
240
435
 
241
436
  // If actor was destroyed, reset to NONEXISTENT and increment generation
@@ -244,31 +439,35 @@ export class FileSystemGlobalState {
244
439
  entry.generation = crypto.randomUUID();
245
440
  }
246
441
 
247
- // Initialize storage
248
- const kvStorage: schema.ActorKvEntry[] = [];
442
+ // Initialize storage (runtime KV is stored in SQLite; state.kvStorage is legacy-only)
249
443
  const initialKvState = getInitialActorKvState(input);
250
- for (const [key, value] of initialKvState) {
251
- kvStorage.push({
252
- key: bufferToArrayBuffer(key),
253
- value: bufferToArrayBuffer(value),
254
- });
255
- }
256
444
 
257
445
  // Initialize metadata
258
- entry.state = {
259
- actorId,
260
- name,
261
- key,
262
- createdAt: BigInt(Date.now()),
263
- kvStorage,
264
- startTs: null,
265
- connectableTs: null,
266
- sleepTs: null,
267
- destroyTs: null,
268
- };
269
- entry.lifecycleState = ActorLifecycleState.AWAKE;
270
-
271
- await this.writeActor(actorId, entry.generation, entry.state);
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
+ });
272
471
 
273
472
  return entry;
274
473
  }
@@ -313,10 +512,16 @@ export class FileSystemGlobalState {
313
512
  const fs = getNodeFs();
314
513
  const stateData = await fs.readFile(stateFilePath);
315
514
 
316
- // Cache the loaded state in handler
317
- entry.state = ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
318
- new Uint8Array(stateData),
319
- );
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
+ };
320
525
 
321
526
  return entry;
322
527
  } catch (innerError: any) {
@@ -340,13 +545,16 @@ export class FileSystemGlobalState {
340
545
  key: ActorKey,
341
546
  input: unknown | undefined,
342
547
  ): Promise<ActorEntry> {
548
+ await this.#waitForActorStop(actorId);
549
+
343
550
  // Attempt to load actor
344
551
  const entry = await this.loadActor(actorId);
345
552
 
346
553
  // If no state for this actor, then create & write state
347
554
  if (!entry.state) {
348
555
  if (this.isActorStopping(actorId)) {
349
- throw new Error(`Actor ${actorId} stopping`);
556
+ await this.#waitForActorStop(actorId);
557
+ return await this.loadOrCreateActor(actorId, name, key, input);
350
558
  }
351
559
 
352
560
  // If actor was destroyed, reset to NONEXISTENT and increment generation
@@ -355,31 +563,36 @@ export class FileSystemGlobalState {
355
563
  entry.generation = crypto.randomUUID();
356
564
  }
357
565
 
358
- // Initialize kvStorage with the initial persist data
359
- const kvStorage: schema.ActorKvEntry[] = [];
360
- const initialKvState = getInitialActorKvState(input);
361
- for (const [key, value] of initialKvState) {
362
- kvStorage.push({
363
- key: bufferToArrayBuffer(key),
364
- value: bufferToArrayBuffer(value),
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
+ }
365
592
  });
366
593
  }
367
-
368
- entry.state = {
369
- actorId,
370
- name,
371
- key: key as readonly string[],
372
- createdAt: BigInt(Date.now()),
373
- kvStorage,
374
- startTs: null,
375
- connectableTs: null,
376
- sleepTs: null,
377
- destroyTs: null,
378
- };
379
- await this.writeActor(actorId, entry.generation, entry.state);
594
+ return entry;
380
595
  }
381
- return entry;
382
- }
383
596
 
384
597
  async sleepActor(actorId: string) {
385
598
  invariant(
@@ -396,27 +609,47 @@ export class FileSystemGlobalState {
396
609
  return;
397
610
  }
398
611
  actor.lifecycleState = ActorLifecycleState.STARTING_SLEEP;
612
+ actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor sleep stop promise rejection", reason }));
399
613
 
400
614
  // Wait for actor to fully start before stopping it to avoid race conditions
401
615
  if (actor.loadPromise) await actor.loadPromise.catch();
402
616
  if (actor.startPromise?.promise)
403
617
  await actor.startPromise.promise.catch();
404
618
 
405
- // Update state with sleep timestamp
406
- if (actor.state) {
407
- actor.state = {
408
- ...actor.state,
409
- sleepTs: BigInt(Date.now()),
410
- };
411
- await this.writeActor(actorId, actor.generation, actor.state);
412
- }
413
-
414
- // Stop actor
415
- invariant(actor.actor, "actor should be loaded");
416
- await actor.actor.onStop("sleep");
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
+ }
417
639
 
418
- // Remove from map after stop is complete
419
- this.#actors.delete(actorId);
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
+ }
420
653
  }
421
654
 
422
655
  async destroyActor(actorId: string) {
@@ -429,94 +662,117 @@ export class FileSystemGlobalState {
429
662
  return;
430
663
  }
431
664
  actor.lifecycleState = ActorLifecycleState.STARTING_DESTROY;
665
+ actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor destroy stop promise rejection", reason }));
432
666
 
433
667
  // Wait for actor to fully start before stopping it to avoid race conditions
434
668
  if (actor.loadPromise) await actor.loadPromise.catch();
435
669
  if (actor.startPromise?.promise)
436
670
  await actor.startPromise.promise.catch();
437
671
 
438
- // Update state with destroy timestamp
439
- if (actor.state) {
440
- actor.state = {
441
- ...actor.state,
442
- destroyTs: BigInt(Date.now()),
443
- };
444
- await this.writeActor(actorId, actor.generation, actor.state);
445
- }
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
+ }
446
692
 
447
- // Stop actor if it's running
448
- if (actor.actor) {
449
- await actor.actor.onStop("destroy");
450
- }
693
+ // Stop actor if it's running
694
+ if (actor.actor) {
695
+ await actor.actor.onStop("destroy");
696
+ }
451
697
 
452
- // Clear alarm timeout if exists
453
- if (actor.alarmTimeout) {
454
- actor.alarmTimeout.abort();
455
- }
698
+ // Ensure any pending KV writes finish before deleting files.
699
+ await this.#withActorWrite(actorId, async () => {});
700
+ this.#closeActorKvDatabase(actorId);
456
701
 
457
- // Delete persisted files if using file system driver
458
- if (this.#persist) {
459
- const fs = getNodeFs();
702
+ // Clear alarm timeout if exists
703
+ if (actor.alarmTimeout) {
704
+ actor.alarmTimeout.abort();
705
+ }
460
706
 
461
- // Delete all actor files in parallel
462
- await Promise.all([
463
- // Delete actor state file
464
- (async () => {
465
- try {
466
- await fs.unlink(this.getActorStatePath(actorId));
467
- } catch (err: any) {
468
- if (err?.code !== "ENOENT") {
469
- logger().error({
470
- msg: "failed to delete actor state file",
471
- actorId,
472
- error: stringifyError(err),
473
- });
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
+ }
474
725
  }
475
- }
476
- })(),
477
- // Delete actor database file
478
- (async () => {
479
- try {
480
- await fs.unlink(this.getActorDbPath(actorId));
481
- } catch (err: any) {
482
- if (err?.code !== "ENOENT") {
483
- logger().error({
484
- msg: "failed to delete actor database file",
485
- actorId,
486
- error: stringifyError(err),
487
- });
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
+ }
488
739
  }
489
- }
490
- })(),
491
- // Delete actor alarm file
492
- (async () => {
493
- try {
494
- await fs.unlink(this.getActorAlarmPath(actorId));
495
- } catch (err: any) {
496
- if (err?.code !== "ENOENT") {
497
- logger().error({
498
- msg: "failed to delete actor alarm file",
499
- actorId,
500
- error: stringifyError(err),
501
- });
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
+ }
502
753
  }
503
- }
504
- })(),
505
- ]);
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;
506
775
  }
507
-
508
- // Reset the entry
509
- //
510
- // Do not remove entry in order to avoid race condition with
511
- // destroying. Next actor creation will increment the generation.
512
- actor.state = undefined;
513
- actor.loadPromise = undefined;
514
- actor.actor = undefined;
515
- actor.startPromise = undefined;
516
- actor.alarmTimeout = undefined;
517
- actor.alarmTimeout = undefined;
518
- actor.pendingWriteResolver = undefined;
519
- actor.lifecycleState = ActorLifecycleState.DESTROYED;
520
776
  }
521
777
 
522
778
  /**
@@ -531,10 +787,9 @@ export class FileSystemGlobalState {
531
787
  return;
532
788
  }
533
789
 
534
- const entry = this.#actors.get(actorId);
535
- invariant(entry, "actor entry does not exist");
536
-
537
- await this.#performWrite(actorId, generation, state);
790
+ await this.#withActorWrite(actorId, async () => {
791
+ await this.#performWrite(actorId, generation, state);
792
+ });
538
793
  }
539
794
 
540
795
  isGenerationCurrentAndNotDestroyed(
@@ -558,6 +813,65 @@ export class FileSystemGlobalState {
558
813
  );
559
814
  }
560
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
+
561
875
  async setActorAlarm(actorId: string, timestamp: number) {
562
876
  const entry = this.#actors.get(actorId);
563
877
  invariant(entry, "actor entry does not exist");
@@ -725,8 +1039,10 @@ export class FileSystemGlobalState {
725
1039
  actorDriver: ActorDriver,
726
1040
  actorId: string,
727
1041
  ): Promise<AnyActorInstance> {
1042
+ await this.#waitForActorStop(actorId);
1043
+
728
1044
  // Get the actor metadata
729
- const entry = await this.loadActor(actorId);
1045
+ let entry = await this.loadActor(actorId);
730
1046
  if (!entry.state) {
731
1047
  throw new Error(
732
1048
  `Actor does not exist and cannot be started: "${actorId}"`,
@@ -742,19 +1058,27 @@ export class FileSystemGlobalState {
742
1058
 
743
1059
  // Actor already loaded
744
1060
  if (entry.actor) {
745
- return 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
+ }
746
1072
  }
747
1073
 
748
1074
  // Create start promise
749
- entry.startPromise = promiseWithResolvers();
1075
+ entry.startPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor start promise rejection", reason }));
750
1076
 
751
1077
  try {
752
1078
  // Create actor
753
- const definition = lookupInRegistry(
754
- config,
755
- entry.state.name,
756
- );
757
- entry.actor = definition.instantiate();
1079
+ const definition = lookupInRegistry(config, entry.state.name);
1080
+ entry.actor = await definition.instantiate();
1081
+ entry.lifecycleState = ActorLifecycleState.AWAKE;
758
1082
 
759
1083
  // Start actor
760
1084
  await entry.actor.start(
@@ -769,24 +1093,37 @@ export class FileSystemGlobalState {
769
1093
  // Update state with start timestamp
770
1094
  // NOTE: connectableTs is always in sync with startTs since actors become connectable immediately after starting
771
1095
  const now = BigInt(Date.now());
772
- entry.state = {
773
- ...entry.state,
774
- startTs: now,
775
- connectableTs: now,
776
- sleepTs: null, // Clear sleep timestamp when actor wakes up
777
- };
778
- await this.writeActor(actorId, entry.generation, entry.state);
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
+ });
779
1116
 
780
1117
  // Finish
781
1118
  entry.startPromise.resolve();
782
1119
  entry.startPromise = undefined;
783
1120
 
784
1121
  return entry.actor;
785
- } catch (innerError) {
786
- const error = new Error(
787
- `Failed to start actor ${actorId}: ${innerError}`,
788
- { cause: innerError },
789
- );
1122
+ } catch (innerError) {
1123
+ const error = new Error(
1124
+ `Failed to start actor ${actorId}: ${innerError}`,
1125
+ { cause: innerError },
1126
+ );
790
1127
  entry.startPromise?.reject(error);
791
1128
  entry.startPromise = undefined;
792
1129
  throw error;
@@ -983,48 +1320,20 @@ export class FileSystemGlobalState {
983
1320
  actorId: string,
984
1321
  entries: [Uint8Array, Uint8Array][],
985
1322
  ): Promise<void> {
986
- const entry = await this.loadActor(actorId);
987
- if (!entry.state) {
988
- if (this.isActorStopping(actorId)) {
989
- return;
990
- } else {
1323
+ await this.loadActor(actorId);
1324
+ await this.#withActorWrite(actorId, async (entry) => {
1325
+ if (!entry.state) {
1326
+ if (this.isActorStopping(actorId)) {
1327
+ return;
1328
+ }
991
1329
  throw new Error(`Actor ${actorId} state not loaded`);
992
1330
  }
993
- }
994
-
995
- // Create a mutable copy of kvStorage
996
- const newKvStorage = [...entry.state.kvStorage];
997
-
998
- // Update kvStorage with new entries
999
- for (const [key, value] of entries) {
1000
- // Find existing entry with the same key
1001
- const existingIndex = newKvStorage.findIndex((e) =>
1002
- arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
1003
- );
1004
-
1005
- if (existingIndex >= 0) {
1006
- // Replace existing entry with new one
1007
- newKvStorage[existingIndex] = {
1008
- key: bufferToArrayBuffer(key),
1009
- value: bufferToArrayBuffer(value),
1010
- };
1011
- } else {
1012
- // Add new entry
1013
- newKvStorage.push({
1014
- key: bufferToArrayBuffer(key),
1015
- value: bufferToArrayBuffer(value),
1016
- });
1017
- }
1018
- }
1019
-
1020
- // Update state with new kvStorage
1021
- entry.state = {
1022
- ...entry.state,
1023
- kvStorage: newKvStorage,
1024
- };
1025
1331
 
1026
- // Save state to disk
1027
- await this.writeActor(actorId, entry.generation, entry.state);
1332
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1333
+ const totalSize = estimateKvSize(db);
1334
+ validateKvEntries(entries, totalSize);
1335
+ this.#putKvEntriesInDb(db, entries);
1336
+ });
1028
1337
  }
1029
1338
 
1030
1339
  /**
@@ -1035,6 +1344,7 @@ export class FileSystemGlobalState {
1035
1344
  keys: Uint8Array[],
1036
1345
  ): Promise<(Uint8Array | null)[]> {
1037
1346
  const entry = await this.loadActor(actorId);
1347
+ await this.#waitForPendingWrite(actorId);
1038
1348
  if (!entry.state) {
1039
1349
  if (this.isActorStopping(actorId)) {
1040
1350
  throw new Error(`Actor ${actorId} is stopping`);
@@ -1043,18 +1353,20 @@ export class FileSystemGlobalState {
1043
1353
  }
1044
1354
  }
1045
1355
 
1356
+ validateKvKeys(keys);
1357
+
1358
+ const db = this.#getOrCreateActorKvDatabase(actorId);
1046
1359
  const results: (Uint8Array | null)[] = [];
1047
1360
  for (const key of keys) {
1048
- // Find entry with the same key
1049
- const foundEntry = entry.state.kvStorage.find((e) =>
1050
- arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
1361
+ const row = db.get<{ value: Uint8Array | ArrayBuffer }>(
1362
+ "SELECT value FROM kv WHERE key = ?",
1363
+ [key],
1051
1364
  );
1052
-
1053
- if (foundEntry) {
1054
- results.push(new Uint8Array(foundEntry.value));
1055
- } else {
1365
+ if (!row) {
1056
1366
  results.push(null);
1367
+ continue;
1057
1368
  }
1369
+ results.push(ensureUint8Array(row.value, "value"));
1058
1370
  }
1059
1371
  return results;
1060
1372
  }
@@ -1063,37 +1375,36 @@ export class FileSystemGlobalState {
1063
1375
  * Batch delete KV entries for an actor.
1064
1376
  */
1065
1377
  async kvBatchDelete(actorId: string, keys: Uint8Array[]): Promise<void> {
1066
- const entry = await this.loadActor(actorId);
1067
- if (!entry.state) {
1068
- if (this.isActorStopping(actorId)) {
1069
- return;
1070
- } else {
1378
+ await this.loadActor(actorId);
1379
+ await this.#withActorWrite(actorId, async (entry) => {
1380
+ if (!entry.state) {
1381
+ if (this.isActorStopping(actorId)) {
1382
+ return;
1383
+ }
1071
1384
  throw new Error(`Actor ${actorId} state not loaded`);
1072
1385
  }
1073
- }
1074
-
1075
- // Create a mutable copy of kvStorage
1076
- const newKvStorage = [...entry.state.kvStorage];
1077
-
1078
- // Delete entries from kvStorage
1079
- for (const key of keys) {
1080
- const indexToDelete = newKvStorage.findIndex((e) =>
1081
- arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
1082
- );
1083
1386
 
1084
- if (indexToDelete >= 0) {
1085
- newKvStorage.splice(indexToDelete, 1);
1387
+ if (keys.length === 0) {
1388
+ return;
1086
1389
  }
1087
- }
1390
+ validateKvKeys(keys);
1088
1391
 
1089
- // Update state with new kvStorage
1090
- entry.state = {
1091
- ...entry.state,
1092
- kvStorage: newKvStorage,
1093
- };
1094
-
1095
- // Save state to disk
1096
- await this.writeActor(actorId, entry.generation, entry.state);
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
+ });
1097
1408
  }
1098
1409
 
1099
1410
  /**
@@ -1104,6 +1415,7 @@ export class FileSystemGlobalState {
1104
1415
  prefix: Uint8Array,
1105
1416
  ): Promise<[Uint8Array, Uint8Array][]> {
1106
1417
  const entry = await this.loadActor(actorId);
1418
+ await this.#waitForPendingWrite(actorId);
1107
1419
  if (!entry.state) {
1108
1420
  if (this.isActorStopping(actorId)) {
1109
1421
  throw new Error(`Actor ${actorId} is destroying`);
@@ -1111,24 +1423,23 @@ export class FileSystemGlobalState {
1111
1423
  throw new Error(`Actor ${actorId} state not loaded`);
1112
1424
  }
1113
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
+ );
1114
1439
 
1115
- const results: [Uint8Array, Uint8Array][] = [];
1116
- for (const kvEntry of entry.state.kvStorage) {
1117
- const keyBytes = new Uint8Array(kvEntry.key);
1118
- // Check if key starts with prefix
1119
- if (keyBytes.length >= prefix.length) {
1120
- let hasPrefix = true;
1121
- for (let i = 0; i < prefix.length; i++) {
1122
- if (keyBytes[i] !== prefix[i]) {
1123
- hasPrefix = false;
1124
- break;
1125
- }
1126
- }
1127
- if (hasPrefix) {
1128
- results.push([keyBytes, new Uint8Array(kvEntry.value)]);
1129
- }
1130
- }
1131
- }
1132
- return results;
1440
+ return rows.map((row) => [
1441
+ ensureUint8Array(row.key, "key"),
1442
+ ensureUint8Array(row.value, "value"),
1443
+ ]);
1133
1444
  }
1134
1445
  }