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.
- package/README.md +3 -5
- package/dist/browser/client.d.ts +2485 -0
- package/dist/browser/client.js +5182 -0
- package/dist/browser/client.js.map +1 -0
- package/dist/browser/inspector/client.d.ts +130 -0
- package/dist/browser/inspector/client.js +2854 -0
- package/dist/browser/inspector/client.js.map +1 -0
- package/dist/browser/v3-DnYObHH3.d.ts +279 -0
- package/dist/inspector.tar.gz +0 -0
- package/dist/schemas/actor-inspector/v1.ts +784 -0
- package/dist/schemas/actor-inspector/v2.ts +796 -0
- package/dist/schemas/actor-inspector/v3.ts +899 -0
- package/dist/schemas/actor-persist/v1.ts +225 -0
- package/dist/schemas/actor-persist/v2.ts +268 -0
- package/dist/schemas/actor-persist/v3.ts +280 -0
- package/dist/schemas/actor-persist/v4.ts +406 -0
- package/dist/schemas/client-protocol/v1.ts +441 -0
- package/dist/schemas/client-protocol/v2.ts +438 -0
- package/dist/schemas/client-protocol/v3.ts +554 -0
- package/dist/schemas/file-system-driver/v1.ts +108 -0
- package/dist/schemas/file-system-driver/v2.ts +142 -0
- package/dist/schemas/file-system-driver/v3.ts +167 -0
- package/dist/schemas/persist/v1.ts +781 -0
- package/dist/schemas/transport/v1.ts +697 -0
- package/dist/tsup/actor/errors.cjs +106 -0
- package/dist/tsup/actor/errors.cjs.map +1 -0
- package/dist/tsup/actor/errors.d.cts +188 -0
- package/dist/tsup/actor/errors.d.ts +188 -0
- package/dist/tsup/actor/errors.js +106 -0
- package/dist/tsup/actor/errors.js.map +1 -0
- package/dist/tsup/actor-router-consts-D29T1Z-K.d.cts +24 -0
- package/dist/tsup/actor-router-consts-D29T1Z-K.d.ts +24 -0
- package/dist/tsup/chunk-325TLXJT.js +1060 -0
- package/dist/tsup/chunk-325TLXJT.js.map +1 -0
- package/dist/tsup/chunk-424PT5DM.js +23 -0
- package/dist/tsup/chunk-424PT5DM.js.map +1 -0
- package/dist/tsup/chunk-4JVIG3SS.cjs +6289 -0
- package/dist/tsup/chunk-4JVIG3SS.cjs.map +1 -0
- package/dist/tsup/chunk-6LJAZ5R4.cjs +96 -0
- package/dist/tsup/chunk-6LJAZ5R4.cjs.map +1 -0
- package/dist/tsup/chunk-6XU3FMCB.cjs +534 -0
- package/dist/tsup/chunk-6XU3FMCB.cjs.map +1 -0
- package/dist/tsup/chunk-7HTNH26M.js +509 -0
- package/dist/tsup/chunk-7HTNH26M.js.map +1 -0
- package/dist/tsup/chunk-AUVH72RE.cjs +5977 -0
- package/dist/tsup/chunk-AUVH72RE.cjs.map +1 -0
- package/dist/tsup/chunk-D4BYUPNQ.js +645 -0
- package/dist/tsup/chunk-D4BYUPNQ.js.map +1 -0
- package/dist/tsup/chunk-HDQ2JUQT.cjs +23 -0
- package/dist/tsup/chunk-HDQ2JUQT.cjs.map +1 -0
- package/dist/tsup/chunk-HHXX2VRM.js +6289 -0
- package/dist/tsup/chunk-HHXX2VRM.js.map +1 -0
- package/dist/tsup/chunk-JEAEA2PB.js +49 -0
- package/dist/tsup/chunk-JEAEA2PB.js.map +1 -0
- package/dist/tsup/chunk-JYSEG3VF.cjs +642 -0
- package/dist/tsup/chunk-JYSEG3VF.cjs.map +1 -0
- package/dist/tsup/chunk-K6DGYILQ.js +2657 -0
- package/dist/tsup/chunk-K6DGYILQ.js.map +1 -0
- package/dist/tsup/chunk-KJSYAUOM.js +96 -0
- package/dist/tsup/chunk-KJSYAUOM.js.map +1 -0
- package/dist/tsup/chunk-L47L3ZWJ.cjs +509 -0
- package/dist/tsup/chunk-L47L3ZWJ.cjs.map +1 -0
- package/dist/tsup/chunk-LXUQ667X.js +2006 -0
- package/dist/tsup/chunk-LXUQ667X.js.map +1 -0
- package/dist/tsup/chunk-MXNPAB5W.js +5977 -0
- package/dist/tsup/chunk-MXNPAB5W.js.map +1 -0
- package/dist/tsup/chunk-N4KRDJ56.js +72 -0
- package/dist/tsup/chunk-N4KRDJ56.js.map +1 -0
- package/dist/tsup/chunk-NIYZDWMW.cjs +2006 -0
- package/dist/tsup/chunk-NIYZDWMW.cjs.map +1 -0
- package/dist/tsup/chunk-PQZHDKRW.cjs +1060 -0
- package/dist/tsup/chunk-PQZHDKRW.cjs.map +1 -0
- package/dist/tsup/chunk-PVOE6BU7.cjs +1050 -0
- package/dist/tsup/chunk-PVOE6BU7.cjs.map +1 -0
- package/dist/tsup/chunk-Q4UD2GA4.cjs +1810 -0
- package/dist/tsup/chunk-Q4UD2GA4.cjs.map +1 -0
- package/dist/tsup/chunk-QUD664YZ.js +1810 -0
- package/dist/tsup/chunk-QUD664YZ.js.map +1 -0
- package/dist/tsup/chunk-RTOCTWME.js +1050 -0
- package/dist/tsup/chunk-RTOCTWME.js.map +1 -0
- package/dist/tsup/chunk-SAZZ4SB2.cjs +2657 -0
- package/dist/tsup/chunk-SAZZ4SB2.cjs.map +1 -0
- package/dist/tsup/chunk-SR3KQE7Q.cjs +72 -0
- package/dist/tsup/chunk-SR3KQE7Q.cjs.map +1 -0
- package/dist/tsup/chunk-V2GHLYC6.cjs +49 -0
- package/dist/tsup/chunk-V2GHLYC6.cjs.map +1 -0
- package/dist/tsup/chunk-V3WG7XTW.cjs +645 -0
- package/dist/tsup/chunk-V3WG7XTW.cjs.map +1 -0
- package/dist/tsup/chunk-VKVNIQRQ.js +257 -0
- package/dist/tsup/chunk-VKVNIQRQ.js.map +1 -0
- package/dist/tsup/chunk-WMPW7JYC.js +642 -0
- package/dist/tsup/chunk-WMPW7JYC.js.map +1 -0
- package/dist/tsup/chunk-Z7HNQ2WF.js +534 -0
- package/dist/tsup/chunk-Z7HNQ2WF.js.map +1 -0
- package/dist/tsup/chunk-ZFY5J2EP.cjs +257 -0
- package/dist/tsup/chunk-ZFY5J2EP.cjs.map +1 -0
- package/dist/tsup/client/mod.cjs +33 -0
- package/dist/tsup/client/mod.cjs.map +1 -0
- package/dist/tsup/client/mod.d.cts +64 -0
- package/dist/tsup/client/mod.d.ts +64 -0
- package/dist/tsup/client/mod.js +33 -0
- package/dist/tsup/client/mod.js.map +1 -0
- package/dist/tsup/common/log.cjs +21 -0
- package/dist/tsup/common/log.cjs.map +1 -0
- package/dist/tsup/common/log.d.cts +34 -0
- package/dist/tsup/common/log.d.ts +34 -0
- package/dist/tsup/common/log.js +21 -0
- package/dist/tsup/common/log.js.map +1 -0
- package/dist/tsup/common/websocket.cjs +10 -0
- package/dist/tsup/common/websocket.cjs.map +1 -0
- package/dist/tsup/common/websocket.d.cts +3 -0
- package/dist/tsup/common/websocket.d.ts +3 -0
- package/dist/tsup/common/websocket.js +10 -0
- package/dist/tsup/common/websocket.js.map +1 -0
- package/dist/tsup/config-BiNoIHRs.d.cts +80 -0
- package/dist/tsup/config-BiNoIHRs.d.ts +80 -0
- package/dist/tsup/config-P3XujgRr.d.ts +2594 -0
- package/dist/tsup/config-_gfywqqI.d.cts +2594 -0
- package/dist/tsup/context-Bxd8Cx4H.d.cts +75 -0
- package/dist/tsup/context-uNA4TRn3.d.ts +75 -0
- package/dist/tsup/db/drizzle/mod.cjs +49 -0
- package/dist/tsup/db/drizzle/mod.cjs.map +1 -0
- package/dist/tsup/db/drizzle/mod.d.cts +17 -0
- package/dist/tsup/db/drizzle/mod.d.ts +17 -0
- package/dist/tsup/db/drizzle/mod.js +49 -0
- package/dist/tsup/db/drizzle/mod.js.map +1 -0
- package/dist/tsup/db/mod.cjs +9 -0
- package/dist/tsup/db/mod.cjs.map +1 -0
- package/dist/tsup/db/mod.d.cts +9 -0
- package/dist/tsup/db/mod.d.ts +9 -0
- package/dist/tsup/db/mod.js +9 -0
- package/dist/tsup/db/mod.js.map +1 -0
- package/dist/tsup/driver-BcLvZcKl.d.cts +13 -0
- package/dist/tsup/driver-CPGHKXyh.d.ts +13 -0
- package/dist/tsup/driver-helpers/mod.cjs +53 -0
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -0
- package/dist/tsup/driver-helpers/mod.d.cts +47 -0
- package/dist/tsup/driver-helpers/mod.d.ts +47 -0
- package/dist/tsup/driver-helpers/mod.js +53 -0
- package/dist/tsup/driver-helpers/mod.js.map +1 -0
- package/dist/tsup/driver-test-suite/mod.cjs +4974 -0
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -0
- package/dist/tsup/driver-test-suite/mod.d.cts +73 -0
- package/dist/tsup/driver-test-suite/mod.d.ts +73 -0
- package/dist/tsup/driver-test-suite/mod.js +4974 -0
- package/dist/tsup/driver-test-suite/mod.js.map +1 -0
- package/dist/tsup/inspector/mod.cjs +164 -0
- package/dist/tsup/inspector/mod.cjs.map +1 -0
- package/dist/tsup/inspector/mod.d.cts +130 -0
- package/dist/tsup/inspector/mod.d.ts +130 -0
- package/dist/tsup/inspector/mod.js +164 -0
- package/dist/tsup/inspector/mod.js.map +1 -0
- package/dist/tsup/keys-CydblqMh.d.cts +13 -0
- package/dist/tsup/keys-CydblqMh.d.ts +13 -0
- package/dist/tsup/mod.cjs +82 -0
- package/dist/tsup/mod.cjs.map +1 -0
- package/dist/tsup/mod.d.cts +126 -0
- package/dist/tsup/mod.d.ts +126 -0
- package/dist/tsup/mod.js +82 -0
- package/dist/tsup/mod.js.map +1 -0
- package/dist/tsup/serve-test-suite/mod.cjs +2601 -0
- package/dist/tsup/serve-test-suite/mod.cjs.map +1 -0
- package/dist/tsup/serve-test-suite/mod.d.cts +9 -0
- package/dist/tsup/serve-test-suite/mod.d.ts +9 -0
- package/dist/tsup/serve-test-suite/mod.js +2601 -0
- package/dist/tsup/serve-test-suite/mod.js.map +1 -0
- package/dist/tsup/test/mod.cjs +90 -0
- package/dist/tsup/test/mod.cjs.map +1 -0
- package/dist/tsup/test/mod.d.cts +26 -0
- package/dist/tsup/test/mod.d.ts +26 -0
- package/dist/tsup/test/mod.js +90 -0
- package/dist/tsup/test/mod.js.map +1 -0
- package/dist/tsup/utils-fwx3o3K9.d.cts +18 -0
- package/dist/tsup/utils-fwx3o3K9.d.ts +18 -0
- package/dist/tsup/utils.cjs +43 -0
- package/dist/tsup/utils.cjs.map +1 -0
- package/dist/tsup/utils.d.cts +148 -0
- package/dist/tsup/utils.d.ts +148 -0
- package/dist/tsup/utils.js +43 -0
- package/dist/tsup/utils.js.map +1 -0
- package/dist/tsup/v3-DnYObHH3.d.cts +279 -0
- package/dist/tsup/v3-DnYObHH3.d.ts +279 -0
- package/dist/tsup/workflow/mod.cjs +16 -0
- package/dist/tsup/workflow/mod.cjs.map +1 -0
- package/dist/tsup/workflow/mod.d.cts +25 -0
- package/dist/tsup/workflow/mod.d.ts +25 -0
- package/dist/tsup/workflow/mod.js +16 -0
- package/dist/tsup/workflow/mod.js.map +1 -0
- package/package.json +293 -5
- package/src/actor/config.ts +1221 -0
- package/src/actor/conn/driver.ts +61 -0
- package/src/actor/conn/drivers/http.ts +17 -0
- package/src/actor/conn/drivers/raw-request.ts +24 -0
- package/src/actor/conn/drivers/raw-websocket.ts +65 -0
- package/src/actor/conn/drivers/websocket.ts +144 -0
- package/src/actor/conn/mod.ts +288 -0
- package/src/actor/conn/persisted.ts +81 -0
- package/src/actor/conn/state-manager.ts +196 -0
- package/src/actor/contexts/action.ts +47 -0
- package/src/actor/contexts/base/actor.ts +347 -0
- package/src/actor/contexts/base/conn-init.ts +68 -0
- package/src/actor/contexts/base/conn.ts +73 -0
- package/src/actor/contexts/before-action-response.ts +42 -0
- package/src/actor/contexts/before-connect.ts +31 -0
- package/src/actor/contexts/connect.ts +42 -0
- package/src/actor/contexts/create-conn-state.ts +32 -0
- package/src/actor/contexts/create-vars.ts +39 -0
- package/src/actor/contexts/create.ts +39 -0
- package/src/actor/contexts/destroy.ts +42 -0
- package/src/actor/contexts/disconnect.ts +43 -0
- package/src/actor/contexts/index.ts +33 -0
- package/src/actor/contexts/request.ts +80 -0
- package/src/actor/contexts/run.ts +47 -0
- package/src/actor/contexts/sleep.ts +42 -0
- package/src/actor/contexts/state-change.ts +42 -0
- package/src/actor/contexts/wake.ts +42 -0
- package/src/actor/contexts/websocket.ts +80 -0
- package/src/actor/database.ts +13 -0
- package/src/actor/definition.ts +64 -0
- package/src/actor/driver.ts +114 -0
- package/src/actor/errors.ts +556 -0
- package/src/actor/instance/connection-manager.ts +574 -0
- package/src/actor/instance/event-manager.ts +314 -0
- package/src/actor/instance/keys.ts +146 -0
- package/src/actor/instance/kv.ts +241 -0
- package/src/actor/instance/mod.ts +1658 -0
- package/src/actor/instance/persisted.ts +67 -0
- package/src/actor/instance/queue-manager.ts +603 -0
- package/src/actor/instance/queue.ts +345 -0
- package/src/actor/instance/schedule-manager.ts +392 -0
- package/src/actor/instance/state-manager.ts +542 -0
- package/src/actor/instance/traces-driver.ts +128 -0
- package/src/actor/keys.test.ts +275 -0
- package/src/actor/keys.ts +89 -0
- package/src/actor/log.ts +6 -0
- package/src/actor/mod.ts +110 -0
- package/src/actor/protocol/old.ts +416 -0
- package/src/actor/protocol/serde.ts +222 -0
- package/src/actor/router-endpoints.ts +400 -0
- package/src/actor/router-websocket-endpoints.test.ts +54 -0
- package/src/actor/router-websocket-endpoints.ts +405 -0
- package/src/actor/router.ts +380 -0
- package/src/actor/schedule.ts +17 -0
- package/src/actor/schema.ts +291 -0
- package/src/actor/utils.test.ts +48 -0
- package/src/actor/utils.ts +158 -0
- package/src/client/actor-common.ts +32 -0
- package/src/client/actor-conn.ts +1262 -0
- package/src/client/actor-handle.ts +344 -0
- package/src/client/actor-query.ts +112 -0
- package/src/client/client.ts +558 -0
- package/src/client/config.ts +151 -0
- package/src/client/errors.ts +76 -0
- package/src/client/log.ts +5 -0
- package/src/client/mod.browser.ts +2 -0
- package/src/client/mod.ts +70 -0
- package/src/client/queue.ts +146 -0
- package/src/client/raw-utils.ts +149 -0
- package/src/client/test.ts +44 -0
- package/src/client/utils.ts +252 -0
- package/src/common/actor-router-consts.ts +59 -0
- package/src/common/cors.ts +57 -0
- package/src/common/eventsource-interface.ts +47 -0
- package/src/common/eventsource.ts +44 -0
- package/src/common/inline-websocket-adapter.ts +154 -0
- package/src/common/log-levels.ts +27 -0
- package/src/common/log.ts +229 -0
- package/src/common/logfmt.ts +221 -0
- package/src/common/network.ts +2 -0
- package/src/common/router.ts +174 -0
- package/src/common/utils.ts +339 -0
- package/src/common/websocket-interface.ts +7 -0
- package/src/common/websocket.ts +43 -0
- package/src/db/config.ts +100 -0
- package/src/db/drizzle/mod.ts +226 -0
- package/src/db/drizzle/sqlite-core.ts +22 -0
- package/src/db/mod.ts +125 -0
- package/src/db/shared.ts +92 -0
- package/src/db/sqlite-vfs.ts +12 -0
- package/src/devtools-loader/index.ts +33 -0
- package/src/devtools-loader/log.ts +5 -0
- package/src/driver-helpers/mod.ts +33 -0
- package/src/driver-helpers/utils.ts +54 -0
- package/src/driver-test-suite/log.ts +5 -0
- package/src/driver-test-suite/mod.ts +293 -0
- package/src/driver-test-suite/test-inline-client-driver.ts +307 -0
- package/src/driver-test-suite/tests/access-control.ts +218 -0
- package/src/driver-test-suite/tests/action-features.ts +203 -0
- package/src/driver-test-suite/tests/actor-conn-hibernation.ts +152 -0
- package/src/driver-test-suite/tests/actor-conn-state.ts +300 -0
- package/src/driver-test-suite/tests/actor-conn.ts +596 -0
- package/src/driver-test-suite/tests/actor-db-raw.ts +73 -0
- package/src/driver-test-suite/tests/actor-db.ts +477 -0
- package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
- package/src/driver-test-suite/tests/actor-driver.ts +18 -0
- package/src/driver-test-suite/tests/actor-error-handling.ts +150 -0
- package/src/driver-test-suite/tests/actor-handle.ts +312 -0
- package/src/driver-test-suite/tests/actor-inline-client.ts +163 -0
- package/src/driver-test-suite/tests/actor-inspector.ts +264 -0
- package/src/driver-test-suite/tests/actor-kv.ts +65 -0
- package/src/driver-test-suite/tests/actor-metadata.ts +116 -0
- package/src/driver-test-suite/tests/actor-onstatechange.ts +95 -0
- package/src/driver-test-suite/tests/actor-queue.ts +325 -0
- package/src/driver-test-suite/tests/actor-run.ts +181 -0
- package/src/driver-test-suite/tests/actor-schedule.ts +97 -0
- package/src/driver-test-suite/tests/actor-sleep.ts +415 -0
- package/src/driver-test-suite/tests/actor-state.ts +54 -0
- package/src/driver-test-suite/tests/actor-stateless.ts +70 -0
- package/src/driver-test-suite/tests/actor-vars.ts +97 -0
- package/src/driver-test-suite/tests/actor-workflow.ts +118 -0
- package/src/driver-test-suite/tests/manager-driver.ts +388 -0
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -0
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +454 -0
- package/src/driver-test-suite/tests/raw-http.ts +359 -0
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -0
- package/src/driver-test-suite/tests/raw-websocket.ts +513 -0
- package/src/driver-test-suite/tests/request-access.ts +240 -0
- package/src/driver-test-suite/utils.ts +80 -0
- package/src/drivers/default.ts +38 -0
- package/src/drivers/engine/actor-driver.ts +1027 -0
- package/src/drivers/engine/config.ts +43 -0
- package/src/drivers/engine/log.ts +5 -0
- package/src/drivers/engine/mod.ts +36 -0
- package/src/drivers/file-system/actor.ts +102 -0
- package/src/drivers/file-system/global-state.ts +1445 -0
- package/src/drivers/file-system/kv-limits.ts +70 -0
- package/src/drivers/file-system/log.ts +5 -0
- package/src/drivers/file-system/manager.ts +300 -0
- package/src/drivers/file-system/mod.ts +78 -0
- package/src/drivers/file-system/sqlite-runtime.ts +210 -0
- package/src/drivers/file-system/utils.ts +125 -0
- package/src/engine-process/constants.ts +2 -0
- package/src/engine-process/log.ts +5 -0
- package/src/engine-process/mod.ts +464 -0
- package/src/globals.d.ts +35 -0
- package/src/inspector/actor-inspector.ts +352 -0
- package/src/inspector/config.ts +49 -0
- package/src/inspector/handler.ts +273 -0
- package/src/inspector/log.ts +5 -0
- package/src/inspector/mod.browser.ts +8 -0
- package/src/inspector/mod.ts +4 -0
- package/src/inspector/serve-ui.ts +40 -0
- package/src/inspector/transport.ts +18 -0
- package/src/inspector/utils.ts +32 -0
- package/src/manager/driver.ts +106 -0
- package/src/manager/gateway.ts +668 -0
- package/src/manager/log.ts +5 -0
- package/src/manager/mod.ts +2 -0
- package/src/manager/protocol/mod.ts +22 -0
- package/src/manager/protocol/query.ts +85 -0
- package/src/manager/router-schema.ts +22 -0
- package/src/manager/router.ts +660 -0
- package/src/manager-api/actors.ts +83 -0
- package/src/manager-api/common.ts +4 -0
- package/src/mod.ts +24 -0
- package/src/registry/config/driver.ts +21 -0
- package/src/registry/config/index.ts +510 -0
- package/src/registry/config/legacy-runner.ts +157 -0
- package/src/registry/config/runner.ts +21 -0
- package/src/registry/config/serverless.ts +94 -0
- package/src/registry/index.ts +194 -0
- package/src/registry/log.ts +5 -0
- package/src/remote-manager-driver/actor-http-client.ts +84 -0
- package/src/remote-manager-driver/actor-websocket-client.ts +81 -0
- package/src/remote-manager-driver/api-endpoints.ts +159 -0
- package/src/remote-manager-driver/api-utils.ts +69 -0
- package/src/remote-manager-driver/log.ts +5 -0
- package/src/remote-manager-driver/metadata.ts +64 -0
- package/src/remote-manager-driver/mod.ts +414 -0
- package/src/remote-manager-driver/ws-proxy.ts +189 -0
- package/src/schemas/actor-inspector/mod.ts +1 -0
- package/src/schemas/actor-inspector/versioned.ts +233 -0
- package/src/schemas/actor-persist/mod.ts +1 -0
- package/src/schemas/actor-persist/versioned.ts +217 -0
- package/src/schemas/client-protocol/mod.ts +1 -0
- package/src/schemas/client-protocol/versioned.ts +330 -0
- package/src/schemas/client-protocol-zod/mod.ts +118 -0
- package/src/schemas/file-system-driver/mod.ts +1 -0
- package/src/schemas/file-system-driver/versioned.ts +135 -0
- package/src/schemas/persist/mod.ts +1 -0
- package/src/schemas/transport/mod.ts +1 -0
- package/src/serde.ts +138 -0
- package/src/serve-test-suite/mod.ts +148 -0
- package/src/serverless/configure.ts +82 -0
- package/src/serverless/log.ts +5 -0
- package/src/serverless/router.test.ts +299 -0
- package/src/serverless/router.ts +215 -0
- package/src/test/log.ts +5 -0
- package/src/test/mod.ts +99 -0
- package/src/utils/crypto.ts +24 -0
- package/src/utils/endpoint-parser.test.ts +202 -0
- package/src/utils/endpoint-parser.ts +124 -0
- package/src/utils/env-vars.ts +78 -0
- package/src/utils/node.ts +178 -0
- package/src/utils/router.ts +83 -0
- package/src/utils/serve.ts +212 -0
- package/src/utils.test.ts +34 -0
- package/src/utils.ts +437 -0
- package/src/workflow/constants.ts +2 -0
- package/src/workflow/context.ts +597 -0
- package/src/workflow/driver.ts +194 -0
- package/src/workflow/inspector.ts +268 -0
- 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>;
|