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,1262 @@
1
+ import * as cbor from "cbor-x";
2
+ import invariant from "invariant";
3
+ import pRetry from "p-retry";
4
+ import type { CloseEvent } from "ws";
5
+ import type { AnyActorDefinition } from "@/actor/definition";
6
+ import { inputDataToBuffer } from "@/actor/protocol/old";
7
+ import { type Encoding, jsonStringifyCompat } from "@/actor/protocol/serde";
8
+ import { PATH_CONNECT } from "@/common/actor-router-consts";
9
+ import { assertUnreachable, stringifyError } from "@/common/utils";
10
+ import type { UniversalWebSocket } from "@/common/websocket-interface";
11
+ import type { ManagerDriver } from "@/driver-helpers/mod";
12
+ import type { ActorQuery } from "@/manager/protocol/query";
13
+ import type * as protocol from "@/schemas/client-protocol/mod";
14
+ import {
15
+ CURRENT_VERSION as CLIENT_PROTOCOL_CURRENT_VERSION,
16
+ TO_CLIENT_VERSIONED,
17
+ TO_SERVER_VERSIONED,
18
+ } from "@/schemas/client-protocol/versioned";
19
+ import {
20
+ type ToClient as ToClientJson,
21
+ ToClientSchema,
22
+ type ToServer as ToServerJson,
23
+ ToServerSchema,
24
+ } from "@/schemas/client-protocol-zod/mod";
25
+ import { deserializeWithEncoding, serializeWithEncoding } from "@/serde";
26
+ import { bufferToArrayBuffer, promiseWithResolvers } from "@/utils";
27
+ import { getLogMessage } from "@/utils/env-vars";
28
+ import type { ActorDefinitionActions } from "./actor-common";
29
+ import { checkForSchedulingError, queryActor } from "./actor-query";
30
+ import { ACTOR_CONNS_SYMBOL, type ClientRaw } from "./client";
31
+ import * as errors from "./errors";
32
+ import { logger } from "./log";
33
+ import {
34
+ createQueueSender,
35
+ type QueueSendNoWaitOptions,
36
+ type QueueSendOptions,
37
+ type QueueSendResult,
38
+ type QueueSendWaitOptions,
39
+ } from "./queue";
40
+ import {
41
+ type WebSocketMessage as ConnMessage,
42
+ messageLength,
43
+ parseWebSocketCloseReason,
44
+ sendHttpRequest,
45
+ } from "./utils";
46
+
47
+ /**
48
+ * Connection status for an actor connection.
49
+ *
50
+ * - `"idle"`: Not connected, no auto-reconnect (initial state, after dispose, or disabled)
51
+ * - `"connecting"`: Attempting to establish connection
52
+ * - `"connected"`: Connection is active
53
+ * - `"disconnected"`: Connection was lost, will auto-reconnect
54
+ */
55
+ export type ActorConnStatus =
56
+ | "idle"
57
+ | "connecting"
58
+ | "connected"
59
+ | "disconnected";
60
+
61
+ interface ActionInFlight {
62
+ name: string;
63
+ resolve: (response: { id: bigint; output: unknown }) => void;
64
+ reject: (error: Error) => void;
65
+ }
66
+
67
+ interface EventSubscriptions<Args extends Array<unknown>> {
68
+ callback: (...args: Args) => void;
69
+ once: boolean;
70
+ }
71
+
72
+ /**
73
+ * A function that unsubscribes from an event.
74
+ *
75
+ * @typedef {Function} EventUnsubscribe
76
+ */
77
+ export type EventUnsubscribe = () => void;
78
+
79
+ /**
80
+ * A function that handles connection errors.
81
+ *
82
+ * @typedef {Function} ActorErrorCallback
83
+ */
84
+ export type ActorErrorCallback = (error: errors.ActorError) => void;
85
+
86
+ /**
87
+ * A callback for connection state changes.
88
+ *
89
+ * @typedef {Function} ConnectionStateCallback
90
+ */
91
+ export type ConnectionStateCallback = () => void;
92
+
93
+ /**
94
+ * A callback for connection status changes.
95
+ *
96
+ * @typedef {Function} StatusChangeCallback
97
+ */
98
+ export type StatusChangeCallback = (status: ActorConnStatus) => void;
99
+
100
+ export interface SendHttpMessageOpts {
101
+ ephemeral: boolean;
102
+ signal?: AbortSignal;
103
+ }
104
+
105
+ export const CONNECT_SYMBOL = Symbol("connect");
106
+
107
+ /**
108
+ * Provides underlying functions for {@link ActorConn}. See {@link ActorConn} for using type-safe remote procedure calls.
109
+ *
110
+ * @see {@link ActorConn}
111
+ */
112
+ export class ActorConnRaw {
113
+ #disposed = false;
114
+
115
+ /* Will be aborted on dispose. */
116
+ #abortController = new AbortController();
117
+
118
+ #connStatus: ActorConnStatus = "idle";
119
+
120
+ #actorId?: string;
121
+ #connId?: string;
122
+
123
+ #messageQueue: Array<{
124
+ body:
125
+ | {
126
+ tag: "ActionRequest";
127
+ val: { id: bigint; name: string; args: unknown };
128
+ }
129
+ | {
130
+ tag: "SubscriptionRequest";
131
+ val: { eventName: string; subscribe: boolean };
132
+ };
133
+ }> = [];
134
+ #actionsInFlight = new Map<number, ActionInFlight>();
135
+
136
+ // biome-ignore lint/suspicious/noExplicitAny: Unknown subscription type
137
+ #eventSubscriptions = new Map<string, Set<EventSubscriptions<any[]>>>();
138
+
139
+ #errorHandlers = new Set<ActorErrorCallback>();
140
+ #openHandlers = new Set<ConnectionStateCallback>();
141
+ #openScheduled = false;
142
+ #closeHandlers = new Set<ConnectionStateCallback>();
143
+ #statusChangeHandlers = new Set<StatusChangeCallback>();
144
+
145
+ #actionIdCounter = 0;
146
+ #queueSender: ReturnType<typeof createQueueSender>;
147
+
148
+ /**
149
+ * Interval that keeps the NodeJS process alive if this is the only thing running.
150
+ *
151
+ * See ttps://github.com/nodejs/node/issues/22088
152
+ */
153
+ #keepNodeAliveInterval: NodeJS.Timeout;
154
+
155
+ /** Promise used to indicate the socket has connected successfully. This will be rejected if the connection fails. */
156
+ #onOpenPromise?: ReturnType<typeof promiseWithResolvers<undefined>>;
157
+
158
+ #websocket?: UniversalWebSocket;
159
+
160
+ #client: ClientRaw;
161
+ #driver: ManagerDriver;
162
+ #params: unknown;
163
+ #encoding: Encoding;
164
+ #actorQuery: ActorQuery;
165
+
166
+ // TODO: ws message queue
167
+
168
+ /**
169
+ * Do not call this directly.
170
+ *
171
+ * Creates an instance of ActorConnRaw.
172
+ *
173
+ * @protected
174
+ */
175
+ public constructor(
176
+ client: ClientRaw,
177
+ driver: ManagerDriver,
178
+ params: unknown,
179
+ encoding: Encoding,
180
+ actorQuery: ActorQuery,
181
+ ) {
182
+ this.#client = client;
183
+ this.#driver = driver;
184
+ this.#params = params;
185
+ this.#encoding = encoding;
186
+ this.#actorQuery = actorQuery;
187
+ this.#queueSender = createQueueSender({
188
+ encoding: this.#encoding,
189
+ params: this.#params,
190
+ customFetch: async (request: Request) => {
191
+ if (!this.#actorId) {
192
+ const { actorId } = await queryActor(
193
+ undefined,
194
+ this.#actorQuery,
195
+ this.#driver,
196
+ );
197
+ this.#actorId = actorId;
198
+ }
199
+ return this.#driver.sendRequest(this.#actorId, request);
200
+ },
201
+ });
202
+
203
+ this.#keepNodeAliveInterval = setInterval(() => 60_000);
204
+ }
205
+
206
+ send(
207
+ name: string,
208
+ body: unknown,
209
+ options: QueueSendWaitOptions,
210
+ ): Promise<QueueSendResult>;
211
+ send(
212
+ name: string,
213
+ body: unknown,
214
+ options?: QueueSendNoWaitOptions,
215
+ ): Promise<void>;
216
+ send(
217
+ name: string,
218
+ body: unknown,
219
+ options?: QueueSendOptions,
220
+ ): Promise<QueueSendResult | void> {
221
+ return this.#queueSender.send(name, body, options as any);
222
+ }
223
+
224
+ /**
225
+ * Call a raw action connection. See {@link ActorConn} for type-safe action calls.
226
+ *
227
+ * @see {@link ActorConn}
228
+ * @template Args - The type of arguments to pass to the action function.
229
+ * @template Response - The type of the response returned by the action function.
230
+ * @param {string} name - The name of the action function to call.
231
+ * @param {...Args} args - The arguments to pass to the action function.
232
+ * @returns {Promise<Response>} - A promise that resolves to the response of the action function.
233
+ */
234
+ async action<
235
+ Args extends Array<unknown> = unknown[],
236
+ Response = unknown,
237
+ >(opts: {
238
+ name: string;
239
+ args: Args;
240
+ signal?: AbortSignal;
241
+ }): Promise<Response> {
242
+ logger().debug({ msg: "action", name: opts.name, args: opts.args });
243
+
244
+ // If we have an active connection, use the websockactionId
245
+ const actionId = this.#actionIdCounter;
246
+ this.#actionIdCounter += 1;
247
+
248
+ const { promise, resolve, reject } = promiseWithResolvers<{
249
+ id: bigint;
250
+ output: unknown;
251
+ }>((reason) => logger().warn({ msg: "unhandled action promise rejection", reason }));
252
+ this.#actionsInFlight.set(actionId, {
253
+ name: opts.name,
254
+ resolve,
255
+ reject,
256
+ });
257
+ logger().debug({
258
+ msg: "added action to in-flight map",
259
+ actionId,
260
+ actionName: opts.name,
261
+ inFlightCount: this.#actionsInFlight.size,
262
+ });
263
+
264
+ this.#sendMessage({
265
+ body: {
266
+ tag: "ActionRequest",
267
+ val: {
268
+ id: BigInt(actionId),
269
+ name: opts.name,
270
+ args: opts.args,
271
+ },
272
+ },
273
+ });
274
+
275
+ // TODO: Throw error if disconnect is called
276
+
277
+ const { id: responseId, output } = await promise;
278
+ if (responseId !== BigInt(actionId))
279
+ throw new Error(
280
+ `Request ID ${actionId} does not match response ID ${responseId}`,
281
+ );
282
+
283
+ return output as Response;
284
+ }
285
+
286
+ /**
287
+ * Do not call this directly.
288
+ * Establishes a connection to the server using the specified endpoint & encoding & driver.
289
+ *
290
+ * @protected
291
+ */
292
+ public [CONNECT_SYMBOL]() {
293
+ this.#connectWithRetry();
294
+ }
295
+
296
+ #setConnStatus(status: ActorConnStatus) {
297
+ const prevStatus = this.#connStatus;
298
+ if (prevStatus === status) return;
299
+ this.#connStatus = status;
300
+
301
+ // Notify status change handlers
302
+ for (const handler of [...this.#statusChangeHandlers]) {
303
+ try {
304
+ handler(status);
305
+ } catch (err) {
306
+ logger().error({
307
+ msg: "error in status change handler",
308
+ error: stringifyError(err),
309
+ });
310
+ }
311
+ }
312
+
313
+ // Notify open handlers
314
+ if (status === "connected") {
315
+ for (const handler of [...this.#openHandlers]) {
316
+ try {
317
+ handler();
318
+ } catch (err) {
319
+ logger().error({
320
+ msg: "error in open handler",
321
+ error: stringifyError(err),
322
+ });
323
+ }
324
+ }
325
+ }
326
+
327
+ // Notify close handlers (only if transitioning from Connected to Disconnected or Idle)
328
+ if (
329
+ (status === "disconnected" || status === "idle") &&
330
+ prevStatus === "connected"
331
+ ) {
332
+ for (const handler of [...this.#closeHandlers]) {
333
+ try {
334
+ handler();
335
+ } catch (err) {
336
+ logger().error({
337
+ msg: "error in close handler",
338
+ error: stringifyError(err),
339
+ });
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ #connectWithRetry() {
346
+ this.#setConnStatus("connecting");
347
+
348
+ // Attempt to reconnect indefinitely
349
+ // This is intentionally not awaited - connection happens in background
350
+ pRetry(this.#connectAndWait.bind(this), {
351
+ forever: true,
352
+ minTimeout: 250,
353
+ maxTimeout: 30_000,
354
+
355
+ onFailedAttempt: (error) => {
356
+ logger().warn({
357
+ msg: "failed to reconnect",
358
+ attempt: error.attemptNumber,
359
+ error: stringifyError(error),
360
+ });
361
+ },
362
+
363
+ // Cancel retry if aborted
364
+ signal: this.#abortController.signal,
365
+ }).catch((err) => {
366
+ if ((err as Error).name === "AbortError") {
367
+ logger().info({ msg: "connection retry aborted" });
368
+ } else {
369
+ logger().error({
370
+ msg: "unexpected error in connection retry",
371
+ error: stringifyError(err),
372
+ });
373
+ }
374
+ });
375
+ }
376
+
377
+ async #connectAndWait() {
378
+ try {
379
+ // Create promise for open
380
+ if (this.#onOpenPromise)
381
+ throw new Error("#onOpenPromise already defined");
382
+ this.#onOpenPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled open promise rejection", reason }));
383
+
384
+ await this.#connectWebSocket();
385
+
386
+ // Wait for result
387
+ await this.#onOpenPromise.promise;
388
+ } finally {
389
+ this.#onOpenPromise = undefined;
390
+ }
391
+ }
392
+
393
+ async #connectWebSocket() {
394
+ const { actorId } = await queryActor(
395
+ undefined,
396
+ this.#actorQuery,
397
+ this.#driver,
398
+ );
399
+
400
+ // Store actorId early so we can use it for error lookups
401
+ this.#actorId = actorId;
402
+
403
+ const ws = await this.#driver.openWebSocket(
404
+ PATH_CONNECT,
405
+ actorId,
406
+ this.#encoding,
407
+ this.#params,
408
+ );
409
+ logger().debug({
410
+ msg: "opened websocket",
411
+ connId: this.#connId,
412
+ readyState: ws.readyState,
413
+ messageQueueLength: this.#messageQueue.length,
414
+ });
415
+ this.#websocket = ws;
416
+ ws.addEventListener("open", () => {
417
+ logger().debug({
418
+ msg: "client websocket open",
419
+ connId: this.#connId,
420
+ });
421
+ });
422
+ ws.addEventListener("message", async (ev) => {
423
+ try {
424
+ await this.#handleOnMessage(ev.data);
425
+ } catch (err) {
426
+ logger().error({
427
+ msg: "error in websocket message handler",
428
+ error: stringifyError(err),
429
+ });
430
+ }
431
+ });
432
+ ws.addEventListener("close", async (ev) => {
433
+ try {
434
+ await this.#handleOnClose(ev);
435
+ } catch (err) {
436
+ logger().error({
437
+ msg: "error in websocket close handler",
438
+ error: stringifyError(err),
439
+ });
440
+ }
441
+ });
442
+ ws.addEventListener("error", (_ev) => {
443
+ try {
444
+ this.#handleOnError();
445
+ } catch (err) {
446
+ logger().error({
447
+ msg: "error in websocket error handler",
448
+ error: stringifyError(err),
449
+ });
450
+ }
451
+ });
452
+ }
453
+
454
+ /** Called by the onopen event from drivers. */
455
+ #handleOnOpen() {
456
+ // Connection was disposed before Init message arrived - close the websocket to avoid leak
457
+ if (this.#disposed) {
458
+ logger().debug({
459
+ msg: "handleOnOpen called after dispose, closing websocket",
460
+ });
461
+ if (this.#websocket) {
462
+ this.#websocket.close(1000, "Disposed");
463
+ this.#websocket = undefined;
464
+ }
465
+ return;
466
+ }
467
+
468
+ if (this.#connStatus === "connected" || this.#openScheduled) {
469
+ return;
470
+ }
471
+ this.#openScheduled = true;
472
+
473
+ queueMicrotask(() => {
474
+ this.#openScheduled = false;
475
+ if (this.#disposed) {
476
+ logger().debug({
477
+ msg: "handleOnOpen scheduled after dispose, closing websocket",
478
+ });
479
+ if (this.#websocket) {
480
+ this.#websocket.close(1000, "Disposed");
481
+ this.#websocket = undefined;
482
+ }
483
+ return;
484
+ }
485
+
486
+ logger().debug({
487
+ msg: "socket open",
488
+ messageQueueLength: this.#messageQueue.length,
489
+ connId: this.#connId,
490
+ });
491
+
492
+ // Update connection state (this also notifies handlers)
493
+ this.#setConnStatus("connected");
494
+
495
+ // Resolve open promise
496
+ if (this.#onOpenPromise) {
497
+ this.#onOpenPromise.resolve(undefined);
498
+ } else {
499
+ logger().warn({ msg: "#onOpenPromise is undefined" });
500
+ }
501
+
502
+ // Resubscribe to all active events
503
+ for (const eventName of this.#eventSubscriptions.keys()) {
504
+ this.#sendSubscription(eventName, true);
505
+ }
506
+
507
+ // Flush queue
508
+ //
509
+ // If the message fails to send, the message will be re-queued
510
+ const queue = this.#messageQueue;
511
+ this.#messageQueue = [];
512
+ logger().debug({
513
+ msg: "flushing message queue",
514
+ queueLength: queue.length,
515
+ });
516
+ for (const msg of queue) {
517
+ this.#sendMessage(msg);
518
+ }
519
+ });
520
+ }
521
+
522
+ /** Called by the onmessage event from drivers. */
523
+ async #handleOnMessage(data: any) {
524
+ logger().trace({
525
+ msg: "received message",
526
+ dataType: typeof data,
527
+ isBlob: data instanceof Blob,
528
+ isArrayBuffer: data instanceof ArrayBuffer,
529
+ });
530
+
531
+ const response = await this.#parseMessage(data as ConnMessage);
532
+ logger().trace(
533
+ getLogMessage()
534
+ ? {
535
+ msg: "parsed message",
536
+ message:
537
+ jsonStringifyCompat(response).substring(0, 100) +
538
+ "...",
539
+ }
540
+ : { msg: "parsed message" },
541
+ );
542
+
543
+ if (response.body.tag === "Init") {
544
+ // Store connection info
545
+ this.#actorId = response.body.val.actorId;
546
+ this.#connId = response.body.val.connectionId;
547
+ logger().trace({
548
+ msg: "received init message",
549
+ actorId: this.#actorId,
550
+ connId: this.#connId,
551
+ });
552
+ this.#handleOnOpen();
553
+ } else if (response.body.tag === "Error") {
554
+ // Connection error
555
+ const { group, code, message, metadata, actionId } =
556
+ response.body.val;
557
+
558
+ if (actionId) {
559
+ const inFlight = this.#takeActionInFlight(Number(actionId));
560
+
561
+ logger().warn({
562
+ msg: "action error",
563
+ actionId: actionId,
564
+ actionName: inFlight?.name,
565
+ group,
566
+ code,
567
+ message,
568
+ metadata,
569
+ });
570
+
571
+ inFlight.reject(
572
+ new errors.ActorError(group, code, message, metadata),
573
+ );
574
+ } else {
575
+ logger().warn({
576
+ msg: "connection error",
577
+ group,
578
+ code,
579
+ message,
580
+ metadata,
581
+ });
582
+
583
+ // Check if this is an actor scheduling error and try to get more details
584
+ let errorToThrow = new errors.ActorError(
585
+ group,
586
+ code,
587
+ message,
588
+ metadata,
589
+ );
590
+ if (errors.isSchedulingError(group, code) && this.#actorId) {
591
+ const schedulingError = await checkForSchedulingError(
592
+ group,
593
+ code,
594
+ this.#actorId,
595
+ this.#actorQuery,
596
+ this.#driver,
597
+ );
598
+ if (schedulingError) {
599
+ errorToThrow = schedulingError;
600
+ }
601
+ }
602
+
603
+ // If we have an onOpenPromise, reject it with the error
604
+ if (this.#onOpenPromise) {
605
+ this.#onOpenPromise.reject(errorToThrow);
606
+ }
607
+
608
+ // Reject any in-flight requests
609
+ for (const [id, inFlight] of this.#actionsInFlight.entries()) {
610
+ inFlight.reject(errorToThrow);
611
+ this.#actionsInFlight.delete(id);
612
+ }
613
+
614
+ this.#dispatchActorError(errorToThrow);
615
+ }
616
+ } else if (response.body.tag === "ActionResponse") {
617
+ // Action response OK
618
+ const { id: actionId } = response.body.val;
619
+ logger().debug({
620
+ msg: "received action response",
621
+ actionId: Number(actionId),
622
+ inFlightCount: this.#actionsInFlight.size,
623
+ inFlightIds: Array.from(this.#actionsInFlight.keys()),
624
+ });
625
+
626
+ const inFlight = this.#takeActionInFlight(Number(actionId));
627
+ logger().trace({
628
+ msg: "resolving action promise",
629
+ actionId,
630
+ actionName: inFlight?.name,
631
+ });
632
+ inFlight.resolve(response.body.val);
633
+ } else if (response.body.tag === "Event") {
634
+ logger().trace({
635
+ msg: "received event",
636
+ name: response.body.val.name,
637
+ });
638
+ this.#dispatchEvent(response.body.val);
639
+ } else {
640
+ assertUnreachable(response.body);
641
+ }
642
+ }
643
+
644
+ /** Called by the onclose event from drivers. */
645
+ async #handleOnClose(event: Event | CloseEvent) {
646
+ // We can't use `event instanceof CloseEvent` because it's not defined in NodeJS
647
+ const closeEvent = event as CloseEvent;
648
+ const wasClean = closeEvent.wasClean;
649
+ const wasConnected = this.#connStatus === "connected";
650
+
651
+ logger().info({
652
+ msg: "socket closed",
653
+ code: closeEvent.code,
654
+ reason: closeEvent.reason,
655
+ wasClean,
656
+ disposed: this.#disposed,
657
+ connId: this.#connId,
658
+ });
659
+
660
+ this.#websocket = undefined;
661
+
662
+ if (this.#disposed) {
663
+ // Use ActorConnDisposed error and prevent unhandled rejection
664
+ this.#rejectPendingPromises(new errors.ActorConnDisposed(), true);
665
+ } else {
666
+ this.#setConnStatus("disconnected");
667
+
668
+ // Build error from close event
669
+ let error: Error;
670
+ const reason = closeEvent.reason || "";
671
+ const parsed = parseWebSocketCloseReason(reason);
672
+
673
+ if (parsed) {
674
+ const { group, code } = parsed;
675
+
676
+ // Check if this is a scheduling error
677
+ if (errors.isSchedulingError(group, code) && this.#actorId) {
678
+ const schedulingError = await checkForSchedulingError(
679
+ group,
680
+ code,
681
+ this.#actorId,
682
+ this.#actorQuery,
683
+ this.#driver,
684
+ );
685
+ if (schedulingError) {
686
+ error = schedulingError;
687
+ } else {
688
+ error = new errors.ActorError(
689
+ group,
690
+ code,
691
+ `Connection closed: ${reason}`,
692
+ undefined,
693
+ );
694
+ }
695
+ } else {
696
+ error = new errors.ActorError(
697
+ group,
698
+ code,
699
+ `Connection closed: ${reason}`,
700
+ undefined,
701
+ );
702
+ }
703
+ } else {
704
+ // Default error for non-structured close reasons
705
+ error = new Error(
706
+ `${wasClean ? "Connection closed" : "Connection lost"} (code: ${closeEvent.code}, reason: ${reason})`,
707
+ );
708
+ }
709
+
710
+ this.#rejectPendingPromises(error, false);
711
+
712
+ // Dispatch to error handler if it's an ActorError
713
+ if (error instanceof errors.ActorError) {
714
+ this.#dispatchActorError(error);
715
+ }
716
+
717
+ // Automatically reconnect if we were connected
718
+ if (wasConnected) {
719
+ logger().debug({
720
+ msg: "triggering reconnect",
721
+ connId: this.#connId,
722
+ });
723
+ this.#connectWithRetry();
724
+ }
725
+ }
726
+ }
727
+
728
+ #rejectPendingPromises(error: Error, suppressUnhandled: boolean) {
729
+ if (this.#onOpenPromise) {
730
+ if (suppressUnhandled) {
731
+ this.#onOpenPromise.promise.catch(() => {});
732
+ }
733
+ this.#onOpenPromise.reject(error);
734
+ }
735
+
736
+ for (const actionInfo of this.#actionsInFlight.values()) {
737
+ actionInfo.reject(error);
738
+ }
739
+ this.#actionsInFlight.clear();
740
+ }
741
+
742
+ /** Called by the onerror event from drivers. */
743
+ #handleOnError() {
744
+ if (this.#disposed) return;
745
+
746
+ // More detailed information will be logged in onclose
747
+ logger().warn("socket error");
748
+ }
749
+
750
+ #takeActionInFlight(id: number): ActionInFlight {
751
+ const inFlight = this.#actionsInFlight.get(id);
752
+ if (!inFlight) {
753
+ logger().error({
754
+ msg: "action not found in in-flight map",
755
+ lookupId: id,
756
+ inFlightCount: this.#actionsInFlight.size,
757
+ inFlightIds: Array.from(this.#actionsInFlight.keys()),
758
+ inFlightActions: Array.from(
759
+ this.#actionsInFlight.entries(),
760
+ ).map(([id, action]) => ({
761
+ id,
762
+ name: action.name,
763
+ })),
764
+ });
765
+ throw new errors.InternalError(`No in flight response for ${id}`);
766
+ }
767
+ this.#actionsInFlight.delete(id);
768
+ logger().debug({
769
+ msg: "removed action from in-flight map",
770
+ actionId: id,
771
+ actionName: inFlight.name,
772
+ inFlightCount: this.#actionsInFlight.size,
773
+ });
774
+ return inFlight;
775
+ }
776
+
777
+ #dispatchEvent(event: { name: string; args: unknown }) {
778
+ const { name, args } = event;
779
+
780
+ const listeners = this.#eventSubscriptions.get(name);
781
+ if (!listeners) return;
782
+
783
+ // Create a new array to avoid issues with listeners being removed during iteration
784
+ for (const listener of [...listeners]) {
785
+ listener.callback(...(args as unknown[]));
786
+
787
+ // Remove if this was a one-time listener
788
+ if (listener.once) {
789
+ listeners.delete(listener);
790
+ }
791
+ }
792
+
793
+ // Clean up empty listener sets
794
+ if (listeners.size === 0) {
795
+ this.#eventSubscriptions.delete(name);
796
+ }
797
+ }
798
+
799
+ #dispatchActorError(error: errors.ActorError) {
800
+ // Call all registered error handlers
801
+ for (const handler of [...this.#errorHandlers]) {
802
+ try {
803
+ handler(error);
804
+ } catch (err) {
805
+ logger().error({
806
+ msg: "error in connection error handler",
807
+ error: stringifyError(err),
808
+ });
809
+ }
810
+ }
811
+ }
812
+
813
+ #addEventSubscription<Args extends Array<unknown>>(
814
+ eventName: string,
815
+ callback: (...args: Args) => void,
816
+ once: boolean,
817
+ ): EventUnsubscribe {
818
+ const listener: EventSubscriptions<Args> = {
819
+ callback,
820
+ once,
821
+ };
822
+
823
+ let subscriptionSet = this.#eventSubscriptions.get(eventName);
824
+ if (subscriptionSet === undefined) {
825
+ subscriptionSet = new Set();
826
+ this.#eventSubscriptions.set(eventName, subscriptionSet);
827
+ this.#sendSubscription(eventName, true);
828
+ }
829
+ subscriptionSet.add(listener);
830
+
831
+ // Return unsubscribe function
832
+ return () => {
833
+ const listeners = this.#eventSubscriptions.get(eventName);
834
+ if (listeners) {
835
+ listeners.delete(listener);
836
+ if (listeners.size === 0) {
837
+ this.#eventSubscriptions.delete(eventName);
838
+ this.#sendSubscription(eventName, false);
839
+ }
840
+ }
841
+ };
842
+ }
843
+
844
+ /**
845
+ * Subscribes to an event that will happen repeatedly.
846
+ *
847
+ * @template Args - The type of arguments the event callback will receive.
848
+ * @param {string} eventName - The name of the event to subscribe to.
849
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
850
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
851
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
852
+ */
853
+ on<Args extends Array<unknown> = unknown[]>(
854
+ eventName: string,
855
+ callback: (...args: Args) => void,
856
+ ): EventUnsubscribe {
857
+ return this.#addEventSubscription<Args>(eventName, callback, false);
858
+ }
859
+
860
+ /**
861
+ * Subscribes to an event that will be triggered only once.
862
+ *
863
+ * @template Args - The type of arguments the event callback will receive.
864
+ * @param {string} eventName - The name of the event to subscribe to.
865
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
866
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
867
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
868
+ */
869
+ once<Args extends Array<unknown> = unknown[]>(
870
+ eventName: string,
871
+ callback: (...args: Args) => void,
872
+ ): EventUnsubscribe {
873
+ return this.#addEventSubscription<Args>(eventName, callback, true);
874
+ }
875
+
876
+ /**
877
+ * Subscribes to connection errors.
878
+ *
879
+ * @param {ActorErrorCallback} callback - The callback function to execute when a connection error occurs.
880
+ * @returns {() => void} - A function to unsubscribe from the error handler.
881
+ */
882
+ onError(callback: ActorErrorCallback): () => void {
883
+ this.#errorHandlers.add(callback);
884
+
885
+ // Return unsubscribe function
886
+ return () => {
887
+ this.#errorHandlers.delete(callback);
888
+ };
889
+ }
890
+
891
+ /**
892
+ * Returns the current connection status.
893
+ *
894
+ * @returns {ActorConnStatus} - The current connection status.
895
+ */
896
+ get connStatus(): ActorConnStatus {
897
+ return this.#connStatus;
898
+ }
899
+
900
+ /**
901
+ * Returns whether the connection is currently open.
902
+ *
903
+ * @deprecated Use `connStatus` instead.
904
+ * @returns {boolean} - True if the connection is open, false otherwise.
905
+ */
906
+ get isConnected(): boolean {
907
+ return this.#connStatus === "connected";
908
+ }
909
+
910
+ /**
911
+ * Subscribes to connection open events.
912
+ *
913
+ * This is called when the WebSocket connection is established and the Init message is received.
914
+ *
915
+ * @param {ConnectionStateCallback} callback - The callback function to execute when the connection opens.
916
+ * @returns {() => void} - A function to unsubscribe from the open handler.
917
+ */
918
+ onOpen(callback: ConnectionStateCallback): () => void {
919
+ this.#openHandlers.add(callback);
920
+
921
+ // Return unsubscribe function
922
+ return () => {
923
+ this.#openHandlers.delete(callback);
924
+ };
925
+ }
926
+
927
+ /**
928
+ * Subscribes to connection close events.
929
+ *
930
+ * This is called when the WebSocket connection is closed. The connection will automatically
931
+ * attempt to reconnect unless disposed.
932
+ *
933
+ * @param {ConnectionStateCallback} callback - The callback function to execute when the connection closes.
934
+ * @returns {() => void} - A function to unsubscribe from the close handler.
935
+ */
936
+ onClose(callback: ConnectionStateCallback): () => void {
937
+ this.#closeHandlers.add(callback);
938
+
939
+ // Return unsubscribe function
940
+ return () => {
941
+ this.#closeHandlers.delete(callback);
942
+ };
943
+ }
944
+
945
+ /**
946
+ * Subscribes to connection status changes.
947
+ *
948
+ * This is called whenever the connection status changes between Disconnected, Connecting, and Connected.
949
+ *
950
+ * @param {StatusChangeCallback} callback - The callback function to execute when the status changes.
951
+ * @returns {() => void} - A function to unsubscribe from the status change handler.
952
+ */
953
+ onStatusChange(callback: StatusChangeCallback): () => void {
954
+ this.#statusChangeHandlers.add(callback);
955
+
956
+ // Return unsubscribe function
957
+ return () => {
958
+ this.#statusChangeHandlers.delete(callback);
959
+ };
960
+ }
961
+
962
+ #sendMessage(
963
+ message: {
964
+ body:
965
+ | {
966
+ tag: "ActionRequest";
967
+ val: { id: bigint; name: string; args: unknown };
968
+ }
969
+ | {
970
+ tag: "SubscriptionRequest";
971
+ val: { eventName: string; subscribe: boolean };
972
+ };
973
+ },
974
+ opts?: SendHttpMessageOpts,
975
+ ) {
976
+ if (this.#disposed) {
977
+ if (opts?.ephemeral) {
978
+ return;
979
+ } else {
980
+ throw new errors.ActorConnDisposed();
981
+ }
982
+ }
983
+
984
+ let queueMessage = false;
985
+ if (this.#websocket) {
986
+ const readyState = this.#websocket.readyState;
987
+ logger().debug({
988
+ msg: "websocket send attempt",
989
+ readyState,
990
+ readyStateString:
991
+ readyState === 0
992
+ ? "CONNECTING"
993
+ : readyState === 1
994
+ ? "OPEN"
995
+ : readyState === 2
996
+ ? "CLOSING"
997
+ : "CLOSED",
998
+ connId: this.#connId,
999
+ messageType: (message.body as any).tag,
1000
+ actionName: (message.body as any).val?.name,
1001
+ });
1002
+ if (this.#connStatus !== "connected") {
1003
+ logger().debug({
1004
+ msg: "websocket init pending, queueing message",
1005
+ connStatus: this.#connStatus,
1006
+ messageType: (message.body as any).tag,
1007
+ });
1008
+ queueMessage = true;
1009
+ } else if (readyState === 1) {
1010
+ try {
1011
+ const messageSerialized = serializeWithEncoding(
1012
+ this.#encoding,
1013
+ message,
1014
+ TO_SERVER_VERSIONED,
1015
+ CLIENT_PROTOCOL_CURRENT_VERSION,
1016
+ ToServerSchema,
1017
+ // JSON: args is the raw value
1018
+ (msg): ToServerJson => msg as ToServerJson,
1019
+ // BARE: args needs to be CBOR-encoded to ArrayBuffer
1020
+ (msg): protocol.ToServer => {
1021
+ if (msg.body.tag === "ActionRequest") {
1022
+ return {
1023
+ body: {
1024
+ tag: "ActionRequest",
1025
+ val: {
1026
+ id: msg.body.val.id,
1027
+ name: msg.body.val.name,
1028
+ args: bufferToArrayBuffer(
1029
+ cbor.encode(msg.body.val.args),
1030
+ ),
1031
+ },
1032
+ },
1033
+ };
1034
+ } else {
1035
+ return msg as protocol.ToServer;
1036
+ }
1037
+ },
1038
+ );
1039
+ this.#websocket.send(messageSerialized);
1040
+ logger().trace({
1041
+ msg: "sent websocket message",
1042
+ len: messageLength(messageSerialized),
1043
+ });
1044
+ } catch (error) {
1045
+ logger().warn({
1046
+ msg: "failed to send message, added to queue",
1047
+ error,
1048
+ connId: this.#connId,
1049
+ });
1050
+
1051
+ // Assuming the socket is disconnected and will be reconnected soon
1052
+ queueMessage = true;
1053
+ }
1054
+ } else {
1055
+ logger().debug({
1056
+ msg: "websocket not open, queueing message",
1057
+ readyState,
1058
+ });
1059
+ queueMessage = true;
1060
+ }
1061
+ } else {
1062
+ // No websocket connected yet
1063
+ logger().debug({ msg: "no websocket, queueing message" });
1064
+ queueMessage = true;
1065
+ }
1066
+
1067
+ if (!opts?.ephemeral && queueMessage) {
1068
+ this.#messageQueue.push(message);
1069
+ logger().debug({
1070
+ msg: "queued connection message",
1071
+ queueLength: this.#messageQueue.length,
1072
+ connId: this.#connId,
1073
+ messageType: (message.body as any).tag,
1074
+ actionName: (message.body as any).val?.name,
1075
+ });
1076
+ }
1077
+ }
1078
+
1079
+ async #parseMessage(data: ConnMessage): Promise<{
1080
+ body:
1081
+ | { tag: "Init"; val: { actorId: string; connectionId: string } }
1082
+ | {
1083
+ tag: "Error";
1084
+ val: {
1085
+ group: string;
1086
+ code: string;
1087
+ message: string;
1088
+ metadata: unknown;
1089
+ actionId: bigint | null;
1090
+ };
1091
+ }
1092
+ | { tag: "ActionResponse"; val: { id: bigint; output: unknown } }
1093
+ | { tag: "Event"; val: { name: string; args: unknown } };
1094
+ }> {
1095
+ invariant(this.#websocket, "websocket must be defined");
1096
+
1097
+ const buffer = await inputDataToBuffer(data);
1098
+
1099
+ return deserializeWithEncoding(
1100
+ this.#encoding,
1101
+ buffer,
1102
+ TO_CLIENT_VERSIONED,
1103
+ ToClientSchema,
1104
+ // JSON: values are already the correct type
1105
+ (msg): ToClientJson => msg as ToClientJson,
1106
+ // BARE: need to decode ArrayBuffer fields back to unknown
1107
+ (msg): any => {
1108
+ if (msg.body.tag === "Error") {
1109
+ return {
1110
+ body: {
1111
+ tag: "Error",
1112
+ val: {
1113
+ group: msg.body.val.group,
1114
+ code: msg.body.val.code,
1115
+ message: msg.body.val.message,
1116
+ metadata: msg.body.val.metadata
1117
+ ? cbor.decode(
1118
+ new Uint8Array(
1119
+ msg.body.val.metadata,
1120
+ ),
1121
+ )
1122
+ : null,
1123
+ actionId: msg.body.val.actionId,
1124
+ },
1125
+ },
1126
+ };
1127
+ } else if (msg.body.tag === "ActionResponse") {
1128
+ return {
1129
+ body: {
1130
+ tag: "ActionResponse",
1131
+ val: {
1132
+ id: msg.body.val.id,
1133
+ output: cbor.decode(
1134
+ new Uint8Array(msg.body.val.output),
1135
+ ),
1136
+ },
1137
+ },
1138
+ };
1139
+ } else if (msg.body.tag === "Event") {
1140
+ return {
1141
+ body: {
1142
+ tag: "Event",
1143
+ val: {
1144
+ name: msg.body.val.name,
1145
+ args: cbor.decode(
1146
+ new Uint8Array(msg.body.val.args),
1147
+ ),
1148
+ },
1149
+ },
1150
+ };
1151
+ } else {
1152
+ // Init has no ArrayBuffer fields
1153
+ return msg;
1154
+ }
1155
+ },
1156
+ );
1157
+ }
1158
+
1159
+ /**
1160
+ * Get the actor ID (for testing purposes).
1161
+ * @internal
1162
+ */
1163
+ get actorId(): string | undefined {
1164
+ return this.#actorId;
1165
+ }
1166
+
1167
+ /**
1168
+ * Get the connection ID (for testing purposes).
1169
+ * @internal
1170
+ */
1171
+ get connId(): string | undefined {
1172
+ return this.#connId;
1173
+ }
1174
+
1175
+ /**
1176
+ * Get the connection ID (for testing purposes).
1177
+ * @internal
1178
+ * @deprecated Use `connId` instead.
1179
+ */
1180
+ get connectionId(): string | undefined {
1181
+ return this.#connId;
1182
+ }
1183
+
1184
+ /**
1185
+ * Disconnects from the actor.
1186
+ *
1187
+ * @returns {Promise<void>} A promise that resolves when the socket is gracefully closed.
1188
+ */
1189
+ async dispose(): Promise<void> {
1190
+ // Internally, this "disposes" the connection
1191
+
1192
+ if (this.#disposed) {
1193
+ logger().warn({ msg: "connection already disconnected" });
1194
+ return;
1195
+ }
1196
+ this.#disposed = true;
1197
+
1198
+ logger().debug({ msg: "disposing actor conn" });
1199
+
1200
+ // Set status to Idle (intentionally closed, no auto-reconnect)
1201
+ this.#setConnStatus("idle");
1202
+
1203
+ // Clear interval so NodeJS process can exit
1204
+ clearInterval(this.#keepNodeAliveInterval);
1205
+
1206
+ // Abort retry loop
1207
+ this.#abortController.abort();
1208
+
1209
+ // Remove from registry
1210
+ this.#client[ACTOR_CONNS_SYMBOL].delete(this);
1211
+
1212
+ // Close websocket (#handleOnClose will reject pending promises)
1213
+ if (this.#websocket) {
1214
+ const ws = this.#websocket;
1215
+ if (
1216
+ ws.readyState !== 2 /* CLOSING */ &&
1217
+ ws.readyState !== 3 /* CLOSED */
1218
+ ) {
1219
+ const { promise, resolve } = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled websocket close promise rejection", reason }));
1220
+ ws.addEventListener("close", () => resolve(undefined));
1221
+ ws.close(1000, "Disposed");
1222
+ await promise;
1223
+ }
1224
+ } else {
1225
+ this.#rejectPendingPromises(new errors.ActorConnDisposed(), true);
1226
+ }
1227
+ this.#websocket = undefined;
1228
+ }
1229
+
1230
+ #sendSubscription(eventName: string, subscribe: boolean) {
1231
+ this.#sendMessage(
1232
+ {
1233
+ body: {
1234
+ tag: "SubscriptionRequest",
1235
+ val: {
1236
+ eventName,
1237
+ subscribe,
1238
+ },
1239
+ },
1240
+ },
1241
+ { ephemeral: true },
1242
+ );
1243
+ }
1244
+ }
1245
+
1246
+ /**
1247
+ * Connection to a actor. Allows calling actor's remote procedure calls with inferred types. See {@link ActorConnRaw} for underlying methods.
1248
+ *
1249
+ * @example
1250
+ * ```
1251
+ * const room = client.connect<ChatRoom>(...etc...);
1252
+ * // This calls the action named `sendMessage` on the `ChatRoom` actor.
1253
+ * await room.sendMessage('Hello, world!');
1254
+ * ```
1255
+ *
1256
+ * Private methods (e.g. those starting with `_`) are automatically excluded.
1257
+ *
1258
+ * @template AD The actor class that this connection is for.
1259
+ * @see {@link ActorConnRaw}
1260
+ */
1261
+ export type ActorConn<AD extends AnyActorDefinition> = ActorConnRaw &
1262
+ ActorDefinitionActions<AD>;