rivetkit 2.0.42 → 2.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{tsup/config-CLnylLYY.d.ts → browser/client.d.ts} +2127 -1910
- 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/schemas/actor-inspector/v2.ts +796 -0
- package/dist/schemas/actor-inspector/v3.ts +899 -0
- package/dist/schemas/actor-persist/v4.ts +406 -0
- package/dist/schemas/client-protocol/v3.ts +554 -0
- package/dist/schemas/persist/v1.ts +781 -0
- package/dist/schemas/transport/v1.ts +697 -0
- package/dist/tsup/actor/errors.cjs +27 -3
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +37 -1
- package/dist/tsup/actor/errors.d.ts +37 -1
- package/dist/tsup/actor/errors.js +26 -1
- package/dist/tsup/{actor-router-consts-DzI2szci.d.cts → actor-router-consts-D29T1Z-K.d.cts} +1 -1
- package/dist/tsup/{actor-router-consts-DzI2szci.d.ts → actor-router-consts-D29T1Z-K.d.ts} +1 -1
- package/dist/tsup/chunk-424PT5DM.js +23 -0
- package/dist/tsup/chunk-424PT5DM.js.map +1 -0
- package/dist/tsup/{chunk-JDAD2YFA.js → chunk-5ESWDTHJ.js} +148 -273
- package/dist/tsup/chunk-5ESWDTHJ.js.map +1 -0
- package/dist/tsup/{chunk-FJ3KTN4V.js → chunk-6LIBPELE.js} +119 -11
- package/dist/tsup/chunk-6LIBPELE.js.map +1 -0
- package/dist/tsup/chunk-6LJAZ5R4.cjs +96 -0
- package/dist/tsup/chunk-6LJAZ5R4.cjs.map +1 -0
- package/dist/tsup/{chunk-LFVF5SCU.js → chunk-7HTNH26M.js} +126 -1
- package/dist/tsup/chunk-7HTNH26M.js.map +1 -0
- package/dist/tsup/chunk-7K4CYDGD.js +630 -0
- package/dist/tsup/chunk-7K4CYDGD.js.map +1 -0
- package/dist/tsup/{chunk-XXGJCOL6.js → chunk-A6YIZWTK.js} +2 -2
- package/dist/tsup/chunk-AIYEYMX5.cjs +630 -0
- package/dist/tsup/chunk-AIYEYMX5.cjs.map +1 -0
- package/dist/tsup/{chunk-Q6W7RJJP.js → chunk-DIGBC2VI.js} +211 -2316
- package/dist/tsup/chunk-DIGBC2VI.js.map +1 -0
- package/dist/tsup/{chunk-RZW2DNND.cjs → chunk-F6JYU5IK.cjs} +1957 -1039
- package/dist/tsup/chunk-F6JYU5IK.cjs.map +1 -0
- package/dist/tsup/chunk-HAZL2EPK.cjs +534 -0
- package/dist/tsup/chunk-HAZL2EPK.cjs.map +1 -0
- package/dist/tsup/chunk-HDQ2JUQT.cjs +23 -0
- package/dist/tsup/chunk-HDQ2JUQT.cjs.map +1 -0
- package/dist/tsup/chunk-HIDX4C5Y.cjs +1036 -0
- package/dist/tsup/chunk-HIDX4C5Y.cjs.map +1 -0
- package/dist/tsup/chunk-IVG73YCW.js +534 -0
- package/dist/tsup/chunk-IVG73YCW.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-2XQS746M.cjs → chunk-L47L3ZWJ.cjs} +127 -2
- package/dist/tsup/chunk-L47L3ZWJ.cjs.map +1 -0
- package/dist/tsup/{chunk-H4TB4X25.cjs → chunk-LW6KLR7A.cjs} +126 -18
- package/dist/tsup/chunk-LW6KLR7A.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-GMAVRZSF.js → chunk-M2T62AZQ.js} +1790 -872
- package/dist/tsup/chunk-M2T62AZQ.js.map +1 -0
- package/dist/tsup/chunk-MZ37VV3P.js +5974 -0
- package/dist/tsup/chunk-MZ37VV3P.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-OMEPCQK2.js +649 -0
- package/dist/tsup/chunk-OMEPCQK2.js.map +1 -0
- package/dist/tsup/chunk-SR3KQE7Q.cjs +72 -0
- package/dist/tsup/chunk-SR3KQE7Q.cjs.map +1 -0
- package/dist/tsup/chunk-SSEP6DHP.cjs +2657 -0
- package/dist/tsup/chunk-SSEP6DHP.cjs.map +1 -0
- package/dist/tsup/chunk-T5YCUGVS.js +1036 -0
- package/dist/tsup/chunk-T5YCUGVS.js.map +1 -0
- package/dist/tsup/{chunk-EJVBH5VF.cjs → chunk-TPGXWFQT.cjs} +3 -3
- package/dist/tsup/{chunk-EJVBH5VF.cjs.map → chunk-TPGXWFQT.cjs.map} +1 -1
- package/dist/tsup/{chunk-X35U3YNX.cjs → chunk-TYLXNCA5.cjs} +214 -339
- package/dist/tsup/chunk-TYLXNCA5.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-XWBAQO5H.cjs +649 -0
- package/dist/tsup/chunk-XWBAQO5H.cjs.map +1 -0
- package/dist/tsup/chunk-YQ4LDVD6.cjs +5974 -0
- package/dist/tsup/chunk-YQ4LDVD6.cjs.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 +9 -10
- package/dist/tsup/client/mod.cjs.map +1 -1
- package/dist/tsup/client/mod.d.cts +11 -5
- package/dist/tsup/client/mod.d.ts +11 -5
- package/dist/tsup/client/mod.js +8 -8
- package/dist/tsup/common/log.cjs +4 -4
- package/dist/tsup/common/log.d.cts +2 -2
- package/dist/tsup/common/log.d.ts +2 -2
- package/dist/tsup/common/log.js +3 -2
- package/dist/tsup/common/websocket.cjs +5 -5
- package/dist/tsup/common/websocket.js +4 -3
- package/dist/tsup/config-BFqid9Gr.d.ts +2574 -0
- package/dist/tsup/config-BiNoIHRs.d.cts +80 -0
- package/dist/tsup/config-BiNoIHRs.d.ts +80 -0
- package/dist/tsup/{config-CZB2-W8x.d.cts → config-CAZphOS1.d.cts} +681 -355
- 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-D0QX9M11.d.ts → driver-Bxv62E2p.d.ts} +2 -2
- package/dist/tsup/{driver-q-zqG7fc.d.cts → driver-DYXwJR5D.d.cts} +2 -2
- package/dist/tsup/driver-helpers/mod.cjs +12 -6
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +12 -5
- package/dist/tsup/driver-helpers/mod.d.ts +12 -5
- package/dist/tsup/driver-helpers/mod.js +12 -5
- package/dist/tsup/driver-test-suite/mod.cjs +1370 -116
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +10 -4
- package/dist/tsup/driver-test-suite/mod.d.ts +10 -4
- package/dist/tsup/driver-test-suite/mod.js +2093 -838
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/inspector/mod.cjs +29 -3
- package/dist/tsup/inspector/mod.cjs.map +1 -1
- package/dist/tsup/inspector/mod.d.cts +124 -3
- package/dist/tsup/inspector/mod.d.ts +124 -3
- package/dist/tsup/inspector/mod.js +72 -45
- package/dist/tsup/keys-CydblqMh.d.cts +13 -0
- package/dist/tsup/keys-CydblqMh.d.ts +13 -0
- package/dist/tsup/mod.cjs +16 -10
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +26 -14
- package/dist/tsup/mod.d.ts +26 -14
- package/dist/tsup/mod.js +20 -13
- package/dist/tsup/serve-test-suite/mod.cjs +1165 -83
- package/dist/tsup/serve-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/serve-test-suite/mod.js +1114 -29
- package/dist/tsup/serve-test-suite/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +84 -11
- package/dist/tsup/test/mod.cjs.map +1 -1
- package/dist/tsup/test/mod.d.cts +10 -5
- package/dist/tsup/test/mod.d.ts +10 -5
- package/dist/tsup/test/mod.js +85 -11
- package/dist/tsup/test/mod.js.map +1 -1
- package/dist/tsup/utils.cjs +10 -4
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +72 -2
- package/dist/tsup/utils.d.ts +72 -2
- package/dist/tsup/utils.js +9 -2
- 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 +83 -0
- package/dist/tsup/workflow/mod.d.ts +83 -0
- package/dist/tsup/workflow/mod.js +16 -0
- package/dist/tsup/workflow/mod.js.map +1 -0
- package/package.json +62 -5
- package/src/actor/config.ts +478 -68
- package/src/actor/conn/mod.ts +68 -16
- package/src/actor/conn/state-manager.ts +2 -2
- package/src/actor/contexts/action.ts +20 -12
- package/src/actor/contexts/base/actor.ts +137 -7
- package/src/actor/contexts/base/conn-init.ts +27 -7
- package/src/actor/contexts/base/conn.ts +27 -18
- package/src/actor/contexts/before-action-response.ts +9 -2
- package/src/actor/contexts/before-connect.ts +7 -2
- package/src/actor/contexts/connect.ts +9 -2
- package/src/actor/contexts/create-conn-state.ts +7 -2
- package/src/actor/contexts/create-vars.ts +16 -3
- package/src/actor/contexts/create.ts +16 -3
- package/src/actor/contexts/destroy.ts +9 -3
- package/src/actor/contexts/disconnect.ts +10 -4
- package/src/actor/contexts/index.ts +4 -3
- package/src/actor/contexts/request.ts +23 -6
- package/src/actor/contexts/run.ts +47 -0
- package/src/actor/contexts/sleep.ts +9 -3
- package/src/actor/contexts/state-change.ts +9 -3
- package/src/actor/contexts/wake.ts +9 -3
- package/src/actor/contexts/websocket.ts +23 -6
- package/src/actor/database.ts +8 -18
- package/src/actor/definition.ts +20 -6
- package/src/actor/driver.ts +32 -3
- package/src/actor/errors.ts +127 -0
- package/src/actor/instance/connection-manager.ts +183 -80
- package/src/actor/instance/event-manager.ts +26 -15
- package/src/actor/instance/keys.ts +117 -0
- package/src/actor/instance/mod.ts +784 -174
- package/src/actor/instance/queue-manager.ts +603 -0
- package/src/actor/instance/queue.ts +287 -0
- package/src/actor/instance/schedule-manager.ts +49 -7
- package/src/actor/instance/state-manager.ts +35 -11
- package/src/actor/instance/traces-driver.ts +128 -0
- package/src/actor/mod.ts +26 -2
- package/src/actor/protocol/old.ts +28 -13
- package/src/actor/protocol/serde.ts +1 -1
- package/src/actor/router-endpoints.ts +177 -21
- package/src/actor/router-websocket-endpoints.ts +18 -29
- package/src/actor/router.ts +177 -0
- package/src/actor/schema.ts +291 -0
- package/src/actor/utils.ts +40 -0
- package/src/client/actor-common.ts +1 -1
- package/src/client/actor-conn.ts +100 -33
- package/src/client/actor-handle.ts +61 -33
- package/src/client/client.ts +2 -4
- package/src/client/config.ts +1 -1
- package/src/client/mod.browser.ts +2 -0
- package/src/client/mod.ts +1 -4
- package/src/client/queue.ts +146 -0
- package/src/client/utils.ts +1 -1
- package/src/common/log.ts +1 -1
- package/src/common/utils.ts +3 -3
- 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/driver-helpers/mod.ts +1 -0
- package/src/driver-test-suite/mod.ts +69 -43
- package/src/driver-test-suite/tests/access-control.ts +218 -0
- package/src/driver-test-suite/tests/actor-db-raw.ts +73 -0
- package/src/driver-test-suite/tests/actor-db.ts +394 -0
- package/src/driver-test-suite/tests/actor-inspector.ts +259 -358
- package/src/driver-test-suite/tests/actor-kv.ts +41 -20
- package/src/driver-test-suite/tests/actor-queue.ts +324 -0
- package/src/driver-test-suite/tests/actor-run.ts +181 -0
- package/src/driver-test-suite/tests/actor-schedule.ts +5 -2
- package/src/driver-test-suite/tests/actor-sleep.ts +3 -3
- package/src/driver-test-suite/tests/actor-stateless.ts +70 -0
- package/src/driver-test-suite/tests/actor-workflow.ts +108 -0
- package/src/driver-test-suite/tests/manager-driver.ts +11 -0
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +1 -1
- package/src/driver-test-suite/tests/raw-websocket.ts +12 -12
- package/src/drivers/default.ts +7 -2
- package/src/drivers/engine/actor-driver.ts +45 -37
- package/src/drivers/engine/config.ts +1 -1
- package/src/drivers/file-system/actor.ts +20 -2
- package/src/drivers/file-system/global-state.ts +569 -258
- package/src/drivers/file-system/kv-limits.ts +70 -0
- package/src/drivers/file-system/manager.ts +22 -6
- package/src/drivers/file-system/mod.ts +39 -16
- package/src/drivers/file-system/sqlite-runtime.ts +210 -0
- package/src/inspector/actor-inspector.ts +224 -102
- package/src/inspector/config.ts +1 -1
- package/src/inspector/handler.ts +102 -20
- package/src/inspector/mod.browser.ts +8 -0
- package/src/inspector/mod.ts +2 -0
- package/src/inspector/serve-ui.ts +40 -0
- package/src/inspector/transport.ts +18 -0
- package/src/inspector/utils.ts +5 -39
- package/src/manager/gateway.ts +1 -1
- package/src/manager/protocol/mod.ts +1 -1
- package/src/manager/protocol/query.ts +1 -1
- package/src/manager/router-schema.ts +1 -1
- package/src/manager/router.ts +38 -12
- package/src/manager-api/actors.ts +1 -1
- package/src/manager-api/common.ts +1 -1
- package/src/registry/config/driver.ts +1 -1
- package/src/registry/config/index.ts +212 -43
- package/src/registry/config/legacy-runner.ts +1 -1
- package/src/registry/config/runner.ts +1 -1
- package/src/registry/config/serverless.ts +1 -1
- package/src/registry/index.ts +7 -5
- package/src/remote-manager-driver/api-utils.ts +1 -1
- package/src/schemas/actor-inspector/mod.ts +1 -1
- package/src/schemas/actor-inspector/versioned.ts +195 -8
- package/src/schemas/actor-persist/versioned.ts +87 -7
- package/src/schemas/client-protocol/mod.ts +1 -1
- package/src/schemas/client-protocol/versioned.ts +127 -11
- package/src/schemas/client-protocol-zod/mod.ts +16 -1
- package/src/schemas/persist/mod.ts +1 -0
- package/src/schemas/transport/mod.ts +1 -0
- package/src/serde.ts +1 -1
- package/src/serve-test-suite/mod.ts +10 -9
- package/src/test/mod.ts +15 -56
- package/src/utils/endpoint-parser.test.ts +1 -1
- package/src/utils/endpoint-parser.ts +1 -1
- package/src/utils/env-vars.ts +12 -1
- package/src/utils/node.ts +15 -2
- package/src/utils.test.ts +34 -0
- package/src/utils.ts +140 -6
- package/src/workflow/constants.ts +2 -0
- package/src/workflow/context.ts +532 -0
- package/src/workflow/driver.ts +191 -0
- package/src/workflow/inspector.ts +268 -0
- package/src/workflow/mod.ts +122 -0
- package/dist/tsup/chunk-2IJTYN6K.cjs +0 -278
- package/dist/tsup/chunk-2IJTYN6K.cjs.map +0 -1
- package/dist/tsup/chunk-2XQS746M.cjs.map +0 -1
- package/dist/tsup/chunk-3VP5CSHV.cjs +0 -114
- package/dist/tsup/chunk-3VP5CSHV.cjs.map +0 -1
- package/dist/tsup/chunk-AQFSQMBG.js +0 -114
- package/dist/tsup/chunk-AQFSQMBG.js.map +0 -1
- package/dist/tsup/chunk-E6ZE2YEA.js +0 -664
- package/dist/tsup/chunk-E6ZE2YEA.js.map +0 -1
- package/dist/tsup/chunk-FJ3KTN4V.js.map +0 -1
- package/dist/tsup/chunk-GBENOENJ.cjs +0 -8
- package/dist/tsup/chunk-GBENOENJ.cjs.map +0 -1
- package/dist/tsup/chunk-GD7UXGOE.cjs +0 -4762
- package/dist/tsup/chunk-GD7UXGOE.cjs.map +0 -1
- package/dist/tsup/chunk-GMAVRZSF.js.map +0 -1
- package/dist/tsup/chunk-H4TB4X25.cjs.map +0 -1
- package/dist/tsup/chunk-JDAD2YFA.js.map +0 -1
- package/dist/tsup/chunk-KCOVZOPS.js +0 -1946
- package/dist/tsup/chunk-KCOVZOPS.js.map +0 -1
- package/dist/tsup/chunk-KDFWJKMJ.cjs +0 -664
- package/dist/tsup/chunk-KDFWJKMJ.cjs.map +0 -1
- package/dist/tsup/chunk-LFVF5SCU.js.map +0 -1
- package/dist/tsup/chunk-Q6W7RJJP.js.map +0 -1
- package/dist/tsup/chunk-RUW5CZ5Z.cjs +0 -1949
- package/dist/tsup/chunk-RUW5CZ5Z.cjs.map +0 -1
- package/dist/tsup/chunk-RZW2DNND.cjs.map +0 -1
- package/dist/tsup/chunk-TCOEBUUE.js +0 -278
- package/dist/tsup/chunk-TCOEBUUE.js.map +0 -1
- package/dist/tsup/chunk-X35U3YNX.cjs.map +0 -1
- package/dist/tsup/keys-Chhy4ylv.d.cts +0 -8
- package/dist/tsup/keys-Chhy4ylv.d.ts +0 -8
- package/dist/tsup/v1-Gq4avTK3.d.cts +0 -240
- package/dist/tsup/v1-Gq4avTK3.d.ts +0 -240
- /package/dist/tsup/{chunk-XXGJCOL6.js.map → chunk-A6YIZWTK.js.map} +0 -0
|
@@ -5,6 +5,7 @@ import type { AnyActorInstance } from "@/actor/instance/mod";
|
|
|
5
5
|
import type { ActorKey } from "@/actor/mod";
|
|
6
6
|
import type { AnyClient } from "@/client/client";
|
|
7
7
|
import { type ActorDriver, getInitialActorKvState } from "@/driver-helpers/mod";
|
|
8
|
+
import type { RegistryConfig } from "@/registry/config";
|
|
8
9
|
import type * as schema from "@/schemas/file-system-driver/mod";
|
|
9
10
|
import {
|
|
10
11
|
ACTOR_ALARM_VERSIONED,
|
|
@@ -12,8 +13,6 @@ import {
|
|
|
12
13
|
CURRENT_VERSION as FILE_SYSTEM_DRIVER_CURRENT_VERSION,
|
|
13
14
|
} from "@/schemas/file-system-driver/versioned";
|
|
14
15
|
import {
|
|
15
|
-
arrayBuffersEqual,
|
|
16
|
-
bufferToArrayBuffer,
|
|
17
16
|
type LongTimeoutHandle,
|
|
18
17
|
promiseWithResolvers,
|
|
19
18
|
setLongTimeout,
|
|
@@ -31,7 +30,19 @@ import {
|
|
|
31
30
|
ensureDirectoryExistsSync,
|
|
32
31
|
getStoragePath,
|
|
33
32
|
} from "./utils";
|
|
34
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
computePrefixUpperBound,
|
|
35
|
+
ensureUint8Array,
|
|
36
|
+
loadSqliteRuntime,
|
|
37
|
+
type SqliteRuntime,
|
|
38
|
+
type SqliteRuntimeDatabase,
|
|
39
|
+
} from "./sqlite-runtime";
|
|
40
|
+
import {
|
|
41
|
+
estimateKvSize,
|
|
42
|
+
validateKvEntries,
|
|
43
|
+
validateKvKey,
|
|
44
|
+
validateKvKeys,
|
|
45
|
+
} from "./kv-limits";
|
|
35
46
|
|
|
36
47
|
// Actor handler to track running instances
|
|
37
48
|
|
|
@@ -54,6 +65,8 @@ interface ActorEntry {
|
|
|
54
65
|
actor?: AnyActorInstance;
|
|
55
66
|
/** Promise for starting the actor. */
|
|
56
67
|
startPromise?: ReturnType<typeof promiseWithResolvers<void>>;
|
|
68
|
+
/** Promise for stopping the actor. */
|
|
69
|
+
stopPromise?: PromiseWithResolvers<void>;
|
|
57
70
|
|
|
58
71
|
alarmTimeout?: LongTimeoutHandle;
|
|
59
72
|
/** The timestamp currently scheduled for this actor's alarm (ms since epoch). */
|
|
@@ -70,6 +83,15 @@ interface ActorEntry {
|
|
|
70
83
|
generation: string;
|
|
71
84
|
}
|
|
72
85
|
|
|
86
|
+
export interface FileSystemDriverOptions {
|
|
87
|
+
/** Whether to persist data to disk */
|
|
88
|
+
persist?: boolean;
|
|
89
|
+
/** Custom path for storage */
|
|
90
|
+
customPath?: string;
|
|
91
|
+
/** Deprecated option retained for explicit migration to sqlite-only KV. */
|
|
92
|
+
useNativeSqlite?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/**
|
|
74
96
|
* Global state for the file system driver
|
|
75
97
|
*/
|
|
@@ -80,6 +102,8 @@ export class FileSystemGlobalState {
|
|
|
80
102
|
#alarmsDir: string;
|
|
81
103
|
|
|
82
104
|
#persist: boolean;
|
|
105
|
+
#sqliteRuntime: SqliteRuntime;
|
|
106
|
+
#actorKvDatabases = new Map<string, SqliteRuntimeDatabase>();
|
|
83
107
|
|
|
84
108
|
// IMPORTANT: Never delete from this map. Doing so will result in race
|
|
85
109
|
// conditions since the actor generation will cease to be tracked
|
|
@@ -106,8 +130,15 @@ export class FileSystemGlobalState {
|
|
|
106
130
|
return this.#actorCountOnStartup;
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
constructor(
|
|
133
|
+
constructor(options: FileSystemDriverOptions = {}) {
|
|
134
|
+
const { persist = true, customPath, useNativeSqlite = true } = options;
|
|
135
|
+
if (!useNativeSqlite) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"File-system driver no longer supports non-SQLite KV storage.",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
110
140
|
this.#persist = persist;
|
|
141
|
+
this.#sqliteRuntime = loadSqliteRuntime();
|
|
111
142
|
this.#storagePath = persist ? (customPath ?? getStoragePath()) : "/tmp";
|
|
112
143
|
const path = getNodePath();
|
|
113
144
|
this.#stateDir = path.join(this.#storagePath, "state");
|
|
@@ -132,6 +163,7 @@ export class FileSystemGlobalState {
|
|
|
132
163
|
msg: "file system driver ready",
|
|
133
164
|
dir: this.#storagePath,
|
|
134
165
|
actorCount: this.#actorCountOnStartup,
|
|
166
|
+
sqliteRuntime: this.#sqliteRuntime.kind,
|
|
135
167
|
});
|
|
136
168
|
|
|
137
169
|
// Cleanup stale temp files on startup
|
|
@@ -143,8 +175,21 @@ export class FileSystemGlobalState {
|
|
|
143
175
|
error: err,
|
|
144
176
|
});
|
|
145
177
|
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
this.#migrateLegacyKvToSqliteOnStartupSync();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger().error({
|
|
183
|
+
msg: "failed legacy kv startup migration",
|
|
184
|
+
error,
|
|
185
|
+
});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
146
188
|
} else {
|
|
147
|
-
logger().debug({
|
|
189
|
+
logger().debug({
|
|
190
|
+
msg: "memory driver ready",
|
|
191
|
+
sqliteRuntime: this.#sqliteRuntime.kind,
|
|
192
|
+
});
|
|
148
193
|
}
|
|
149
194
|
}
|
|
150
195
|
|
|
@@ -160,6 +205,154 @@ export class FileSystemGlobalState {
|
|
|
160
205
|
return getNodePath().join(this.#alarmsDir, actorId);
|
|
161
206
|
}
|
|
162
207
|
|
|
208
|
+
#getActorKvDatabasePath(actorId: string): string {
|
|
209
|
+
if (this.#persist) {
|
|
210
|
+
return this.getActorDbPath(actorId);
|
|
211
|
+
}
|
|
212
|
+
return ":memory:";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#ensureActorKvTables(db: SqliteRuntimeDatabase): void {
|
|
216
|
+
db.exec(`
|
|
217
|
+
CREATE TABLE IF NOT EXISTS kv (
|
|
218
|
+
key BLOB PRIMARY KEY NOT NULL,
|
|
219
|
+
value BLOB NOT NULL
|
|
220
|
+
)
|
|
221
|
+
`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#getOrCreateActorKvDatabase(actorId: string): SqliteRuntimeDatabase {
|
|
225
|
+
const existing = this.#actorKvDatabases.get(actorId);
|
|
226
|
+
if (existing) {
|
|
227
|
+
return existing;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const dbPath = this.#getActorKvDatabasePath(actorId);
|
|
231
|
+
if (this.#persist) {
|
|
232
|
+
const path = getNodePath();
|
|
233
|
+
ensureDirectoryExistsSync(path.dirname(dbPath));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let db: SqliteRuntimeDatabase;
|
|
237
|
+
try {
|
|
238
|
+
db = this.#sqliteRuntime.open(dbPath);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`failed to open actor kv database for actor ${actorId} at ${dbPath}: ${error}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.#ensureActorKvTables(db);
|
|
246
|
+
this.#actorKvDatabases.set(actorId, db);
|
|
247
|
+
return db;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#closeActorKvDatabase(actorId: string): void {
|
|
251
|
+
const db = this.#actorKvDatabases.get(actorId);
|
|
252
|
+
if (!db) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
db.close();
|
|
258
|
+
} finally {
|
|
259
|
+
this.#actorKvDatabases.delete(actorId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#putKvEntriesInDb(
|
|
264
|
+
db: SqliteRuntimeDatabase,
|
|
265
|
+
entries: [Uint8Array, Uint8Array][],
|
|
266
|
+
): void {
|
|
267
|
+
if (entries.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
db.exec("BEGIN");
|
|
272
|
+
try {
|
|
273
|
+
for (const [key, value] of entries) {
|
|
274
|
+
db.run("INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)", [
|
|
275
|
+
key,
|
|
276
|
+
value,
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
db.exec("COMMIT");
|
|
280
|
+
} catch (error) {
|
|
281
|
+
try {
|
|
282
|
+
db.exec("ROLLBACK");
|
|
283
|
+
} catch {
|
|
284
|
+
// Ignore rollback errors, original error is more actionable.
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#isKvDbPopulated(db: SqliteRuntimeDatabase): boolean {
|
|
291
|
+
const row = db.get<{ count: number | bigint }>(
|
|
292
|
+
"SELECT COUNT(*) AS count FROM kv",
|
|
293
|
+
);
|
|
294
|
+
const count = row ? Number(row.count) : 0;
|
|
295
|
+
return count > 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#migrateLegacyKvToSqliteOnStartupSync(): void {
|
|
299
|
+
const fsSync = getNodeFsSync();
|
|
300
|
+
if (!fsSync.existsSync(this.#stateDir)) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const actorIds = fsSync
|
|
305
|
+
.readdirSync(this.#stateDir)
|
|
306
|
+
.filter((id) => !id.includes(".tmp."));
|
|
307
|
+
|
|
308
|
+
for (const actorId of actorIds) {
|
|
309
|
+
const statePath = this.getActorStatePath(actorId);
|
|
310
|
+
let state: schema.ActorState;
|
|
311
|
+
try {
|
|
312
|
+
const stateBytes = fsSync.readFileSync(statePath);
|
|
313
|
+
state = ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
|
|
314
|
+
new Uint8Array(stateBytes),
|
|
315
|
+
);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
logger().warn({
|
|
318
|
+
msg: "failed to parse actor state during startup migration",
|
|
319
|
+
actorId,
|
|
320
|
+
error,
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!state.kvStorage || state.kvStorage.length === 0) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const dbPath = this.getActorDbPath(actorId);
|
|
330
|
+
const path = getNodePath();
|
|
331
|
+
ensureDirectoryExistsSync(path.dirname(dbPath));
|
|
332
|
+
const db = this.#sqliteRuntime.open(dbPath);
|
|
333
|
+
try {
|
|
334
|
+
this.#ensureActorKvTables(db);
|
|
335
|
+
if (this.#isKvDbPopulated(db)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const legacyEntries = state.kvStorage.map((entry) => [
|
|
340
|
+
new Uint8Array(entry.key),
|
|
341
|
+
new Uint8Array(entry.value),
|
|
342
|
+
]) as [Uint8Array, Uint8Array][];
|
|
343
|
+
this.#putKvEntriesInDb(db, legacyEntries);
|
|
344
|
+
|
|
345
|
+
logger().info({
|
|
346
|
+
msg: "migrated legacy actor kv storage to sqlite",
|
|
347
|
+
actorId,
|
|
348
|
+
entryCount: legacyEntries.length,
|
|
349
|
+
});
|
|
350
|
+
} finally {
|
|
351
|
+
db.close();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
163
356
|
async *getActorsIterator(params: {
|
|
164
357
|
cursor?: string;
|
|
165
358
|
}): AsyncGenerator<schema.ActorState> {
|
|
@@ -228,14 +421,16 @@ export class FileSystemGlobalState {
|
|
|
228
421
|
): Promise<ActorEntry> {
|
|
229
422
|
// TODO: Does not check if actor already exists on fs
|
|
230
423
|
|
|
231
|
-
|
|
424
|
+
await this.#waitForActorStop(actorId);
|
|
425
|
+
let entry = this.#upsertEntry(actorId);
|
|
232
426
|
|
|
233
427
|
// Check if actor already exists (has state or is being stopped)
|
|
234
428
|
if (entry.state) {
|
|
235
429
|
throw new ActorDuplicateKey(name, key);
|
|
236
430
|
}
|
|
237
431
|
if (this.isActorStopping(actorId)) {
|
|
238
|
-
|
|
432
|
+
await this.#waitForActorStop(actorId);
|
|
433
|
+
entry = this.#upsertEntry(actorId);
|
|
239
434
|
}
|
|
240
435
|
|
|
241
436
|
// If actor was destroyed, reset to NONEXISTENT and increment generation
|
|
@@ -244,31 +439,35 @@ export class FileSystemGlobalState {
|
|
|
244
439
|
entry.generation = crypto.randomUUID();
|
|
245
440
|
}
|
|
246
441
|
|
|
247
|
-
// Initialize storage
|
|
248
|
-
const kvStorage: schema.ActorKvEntry[] = [];
|
|
442
|
+
// Initialize storage (runtime KV is stored in SQLite; state.kvStorage is legacy-only)
|
|
249
443
|
const initialKvState = getInitialActorKvState(input);
|
|
250
|
-
for (const [key, value] of initialKvState) {
|
|
251
|
-
kvStorage.push({
|
|
252
|
-
key: bufferToArrayBuffer(key),
|
|
253
|
-
value: bufferToArrayBuffer(value),
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
444
|
|
|
257
445
|
// Initialize metadata
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
446
|
+
await this.#withActorWrite(actorId, async (lockedEntry) => {
|
|
447
|
+
lockedEntry.state = {
|
|
448
|
+
actorId,
|
|
449
|
+
name,
|
|
450
|
+
key,
|
|
451
|
+
createdAt: BigInt(Date.now()),
|
|
452
|
+
kvStorage: [],
|
|
453
|
+
startTs: null,
|
|
454
|
+
connectableTs: null,
|
|
455
|
+
sleepTs: null,
|
|
456
|
+
destroyTs: null,
|
|
457
|
+
};
|
|
458
|
+
lockedEntry.lifecycleState = ActorLifecycleState.AWAKE;
|
|
459
|
+
if (this.#persist) {
|
|
460
|
+
await this.#performWrite(
|
|
461
|
+
actorId,
|
|
462
|
+
lockedEntry.generation,
|
|
463
|
+
lockedEntry.state,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (initialKvState.length > 0) {
|
|
467
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
468
|
+
this.#putKvEntriesInDb(db, initialKvState);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
272
471
|
|
|
273
472
|
return entry;
|
|
274
473
|
}
|
|
@@ -313,10 +512,16 @@ export class FileSystemGlobalState {
|
|
|
313
512
|
const fs = getNodeFs();
|
|
314
513
|
const stateData = await fs.readFile(stateFilePath);
|
|
315
514
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
515
|
+
const loadedState =
|
|
516
|
+
ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
|
|
517
|
+
new Uint8Array(stateData),
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// Runtime reads/writes are SQLite-only; legacy kvStorage is for one-time startup migration.
|
|
521
|
+
entry.state = {
|
|
522
|
+
...loadedState,
|
|
523
|
+
kvStorage: [],
|
|
524
|
+
};
|
|
320
525
|
|
|
321
526
|
return entry;
|
|
322
527
|
} catch (innerError: any) {
|
|
@@ -340,13 +545,16 @@ export class FileSystemGlobalState {
|
|
|
340
545
|
key: ActorKey,
|
|
341
546
|
input: unknown | undefined,
|
|
342
547
|
): Promise<ActorEntry> {
|
|
548
|
+
await this.#waitForActorStop(actorId);
|
|
549
|
+
|
|
343
550
|
// Attempt to load actor
|
|
344
551
|
const entry = await this.loadActor(actorId);
|
|
345
552
|
|
|
346
553
|
// If no state for this actor, then create & write state
|
|
347
554
|
if (!entry.state) {
|
|
348
555
|
if (this.isActorStopping(actorId)) {
|
|
349
|
-
|
|
556
|
+
await this.#waitForActorStop(actorId);
|
|
557
|
+
return await this.loadOrCreateActor(actorId, name, key, input);
|
|
350
558
|
}
|
|
351
559
|
|
|
352
560
|
// If actor was destroyed, reset to NONEXISTENT and increment generation
|
|
@@ -355,31 +563,36 @@ export class FileSystemGlobalState {
|
|
|
355
563
|
entry.generation = crypto.randomUUID();
|
|
356
564
|
}
|
|
357
565
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
566
|
+
// Initialize storage (runtime KV is stored in SQLite; state.kvStorage is legacy-only)
|
|
567
|
+
const initialKvState = getInitialActorKvState(input);
|
|
568
|
+
|
|
569
|
+
await this.#withActorWrite(actorId, async (lockedEntry) => {
|
|
570
|
+
lockedEntry.state = {
|
|
571
|
+
actorId,
|
|
572
|
+
name,
|
|
573
|
+
key: key as readonly string[],
|
|
574
|
+
createdAt: BigInt(Date.now()),
|
|
575
|
+
kvStorage: [],
|
|
576
|
+
startTs: null,
|
|
577
|
+
connectableTs: null,
|
|
578
|
+
sleepTs: null,
|
|
579
|
+
destroyTs: null,
|
|
580
|
+
};
|
|
581
|
+
if (this.#persist) {
|
|
582
|
+
await this.#performWrite(
|
|
583
|
+
actorId,
|
|
584
|
+
lockedEntry.generation,
|
|
585
|
+
lockedEntry.state,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
if (initialKvState.length > 0) {
|
|
589
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
590
|
+
this.#putKvEntriesInDb(db, initialKvState);
|
|
591
|
+
}
|
|
365
592
|
});
|
|
366
593
|
}
|
|
367
|
-
|
|
368
|
-
entry.state = {
|
|
369
|
-
actorId,
|
|
370
|
-
name,
|
|
371
|
-
key: key as readonly string[],
|
|
372
|
-
createdAt: BigInt(Date.now()),
|
|
373
|
-
kvStorage,
|
|
374
|
-
startTs: null,
|
|
375
|
-
connectableTs: null,
|
|
376
|
-
sleepTs: null,
|
|
377
|
-
destroyTs: null,
|
|
378
|
-
};
|
|
379
|
-
await this.writeActor(actorId, entry.generation, entry.state);
|
|
594
|
+
return entry;
|
|
380
595
|
}
|
|
381
|
-
return entry;
|
|
382
|
-
}
|
|
383
596
|
|
|
384
597
|
async sleepActor(actorId: string) {
|
|
385
598
|
invariant(
|
|
@@ -396,27 +609,47 @@ export class FileSystemGlobalState {
|
|
|
396
609
|
return;
|
|
397
610
|
}
|
|
398
611
|
actor.lifecycleState = ActorLifecycleState.STARTING_SLEEP;
|
|
612
|
+
actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor sleep stop promise rejection", reason }));
|
|
399
613
|
|
|
400
614
|
// Wait for actor to fully start before stopping it to avoid race conditions
|
|
401
615
|
if (actor.loadPromise) await actor.loadPromise.catch();
|
|
402
616
|
if (actor.startPromise?.promise)
|
|
403
617
|
await actor.startPromise.promise.catch();
|
|
404
618
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
actor.state
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
619
|
+
try {
|
|
620
|
+
// Update state with sleep timestamp
|
|
621
|
+
if (actor.state) {
|
|
622
|
+
await this.#withActorWrite(actorId, async (lockedEntry) => {
|
|
623
|
+
if (!lockedEntry.state) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
lockedEntry.state = {
|
|
627
|
+
...lockedEntry.state,
|
|
628
|
+
sleepTs: BigInt(Date.now()),
|
|
629
|
+
};
|
|
630
|
+
if (this.#persist) {
|
|
631
|
+
await this.#performWrite(
|
|
632
|
+
actorId,
|
|
633
|
+
lockedEntry.generation,
|
|
634
|
+
lockedEntry.state,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
417
639
|
|
|
418
|
-
|
|
419
|
-
|
|
640
|
+
// Stop actor
|
|
641
|
+
invariant(actor.actor, "actor should be loaded");
|
|
642
|
+
await actor.actor.onStop("sleep");
|
|
643
|
+
} finally {
|
|
644
|
+
// Ensure any pending KV writes finish before removing the entry.
|
|
645
|
+
await this.#withActorWrite(actorId, async () => {});
|
|
646
|
+
this.#closeActorKvDatabase(actorId);
|
|
647
|
+
actor.stopPromise?.resolve();
|
|
648
|
+
actor.stopPromise = undefined;
|
|
649
|
+
|
|
650
|
+
// Remove from map after stop is complete
|
|
651
|
+
this.#actors.delete(actorId);
|
|
652
|
+
}
|
|
420
653
|
}
|
|
421
654
|
|
|
422
655
|
async destroyActor(actorId: string) {
|
|
@@ -429,94 +662,117 @@ export class FileSystemGlobalState {
|
|
|
429
662
|
return;
|
|
430
663
|
}
|
|
431
664
|
actor.lifecycleState = ActorLifecycleState.STARTING_DESTROY;
|
|
665
|
+
actor.stopPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor destroy stop promise rejection", reason }));
|
|
432
666
|
|
|
433
667
|
// Wait for actor to fully start before stopping it to avoid race conditions
|
|
434
668
|
if (actor.loadPromise) await actor.loadPromise.catch();
|
|
435
669
|
if (actor.startPromise?.promise)
|
|
436
670
|
await actor.startPromise.promise.catch();
|
|
437
671
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
actor.state
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
672
|
+
try {
|
|
673
|
+
// Update state with destroy timestamp
|
|
674
|
+
if (actor.state) {
|
|
675
|
+
await this.#withActorWrite(actorId, async (lockedEntry) => {
|
|
676
|
+
if (!lockedEntry.state) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
lockedEntry.state = {
|
|
680
|
+
...lockedEntry.state,
|
|
681
|
+
destroyTs: BigInt(Date.now()),
|
|
682
|
+
};
|
|
683
|
+
if (this.#persist) {
|
|
684
|
+
await this.#performWrite(
|
|
685
|
+
actorId,
|
|
686
|
+
lockedEntry.generation,
|
|
687
|
+
lockedEntry.state,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
446
692
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
693
|
+
// Stop actor if it's running
|
|
694
|
+
if (actor.actor) {
|
|
695
|
+
await actor.actor.onStop("destroy");
|
|
696
|
+
}
|
|
451
697
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
698
|
+
// Ensure any pending KV writes finish before deleting files.
|
|
699
|
+
await this.#withActorWrite(actorId, async () => {});
|
|
700
|
+
this.#closeActorKvDatabase(actorId);
|
|
456
701
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
702
|
+
// Clear alarm timeout if exists
|
|
703
|
+
if (actor.alarmTimeout) {
|
|
704
|
+
actor.alarmTimeout.abort();
|
|
705
|
+
}
|
|
460
706
|
|
|
461
|
-
// Delete
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
707
|
+
// Delete persisted files if using file system driver
|
|
708
|
+
if (this.#persist) {
|
|
709
|
+
const fs = getNodeFs();
|
|
710
|
+
|
|
711
|
+
// Delete all actor files in parallel
|
|
712
|
+
await Promise.all([
|
|
713
|
+
// Delete actor state file
|
|
714
|
+
(async () => {
|
|
715
|
+
try {
|
|
716
|
+
await fs.unlink(this.getActorStatePath(actorId));
|
|
717
|
+
} catch (err: any) {
|
|
718
|
+
if (err?.code !== "ENOENT") {
|
|
719
|
+
logger().error({
|
|
720
|
+
msg: "failed to delete actor state file",
|
|
721
|
+
actorId,
|
|
722
|
+
error: stringifyError(err),
|
|
723
|
+
});
|
|
724
|
+
}
|
|
474
725
|
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
726
|
+
})(),
|
|
727
|
+
// Delete actor database file
|
|
728
|
+
(async () => {
|
|
729
|
+
try {
|
|
730
|
+
await fs.unlink(this.getActorDbPath(actorId));
|
|
731
|
+
} catch (err: any) {
|
|
732
|
+
if (err?.code !== "ENOENT") {
|
|
733
|
+
logger().error({
|
|
734
|
+
msg: "failed to delete actor database file",
|
|
735
|
+
actorId,
|
|
736
|
+
error: stringifyError(err),
|
|
737
|
+
});
|
|
738
|
+
}
|
|
488
739
|
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
740
|
+
})(),
|
|
741
|
+
// Delete actor alarm file
|
|
742
|
+
(async () => {
|
|
743
|
+
try {
|
|
744
|
+
await fs.unlink(this.getActorAlarmPath(actorId));
|
|
745
|
+
} catch (err: any) {
|
|
746
|
+
if (err?.code !== "ENOENT") {
|
|
747
|
+
logger().error({
|
|
748
|
+
msg: "failed to delete actor alarm file",
|
|
749
|
+
actorId,
|
|
750
|
+
error: stringifyError(err),
|
|
751
|
+
});
|
|
752
|
+
}
|
|
502
753
|
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
754
|
+
})(),
|
|
755
|
+
]);
|
|
756
|
+
}
|
|
757
|
+
} finally {
|
|
758
|
+
// Ensure any pending KV writes finish before clearing the entry.
|
|
759
|
+
await this.#withActorWrite(actorId, async () => {});
|
|
760
|
+
actor.stopPromise?.resolve();
|
|
761
|
+
actor.stopPromise = undefined;
|
|
762
|
+
|
|
763
|
+
// Reset the entry
|
|
764
|
+
//
|
|
765
|
+
// Do not remove entry in order to avoid race condition with
|
|
766
|
+
// destroying. Next actor creation will increment the generation.
|
|
767
|
+
actor.state = undefined;
|
|
768
|
+
actor.loadPromise = undefined;
|
|
769
|
+
actor.actor = undefined;
|
|
770
|
+
actor.startPromise = undefined;
|
|
771
|
+
actor.alarmTimeout = undefined;
|
|
772
|
+
actor.alarmTimeout = undefined;
|
|
773
|
+
actor.pendingWriteResolver = undefined;
|
|
774
|
+
actor.lifecycleState = ActorLifecycleState.DESTROYED;
|
|
506
775
|
}
|
|
507
|
-
|
|
508
|
-
// Reset the entry
|
|
509
|
-
//
|
|
510
|
-
// Do not remove entry in order to avoid race condition with
|
|
511
|
-
// destroying. Next actor creation will increment the generation.
|
|
512
|
-
actor.state = undefined;
|
|
513
|
-
actor.loadPromise = undefined;
|
|
514
|
-
actor.actor = undefined;
|
|
515
|
-
actor.startPromise = undefined;
|
|
516
|
-
actor.alarmTimeout = undefined;
|
|
517
|
-
actor.alarmTimeout = undefined;
|
|
518
|
-
actor.pendingWriteResolver = undefined;
|
|
519
|
-
actor.lifecycleState = ActorLifecycleState.DESTROYED;
|
|
520
776
|
}
|
|
521
777
|
|
|
522
778
|
/**
|
|
@@ -531,10 +787,9 @@ export class FileSystemGlobalState {
|
|
|
531
787
|
return;
|
|
532
788
|
}
|
|
533
789
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
await this.#performWrite(actorId, generation, state);
|
|
790
|
+
await this.#withActorWrite(actorId, async () => {
|
|
791
|
+
await this.#performWrite(actorId, generation, state);
|
|
792
|
+
});
|
|
538
793
|
}
|
|
539
794
|
|
|
540
795
|
isGenerationCurrentAndNotDestroyed(
|
|
@@ -558,6 +813,65 @@ export class FileSystemGlobalState {
|
|
|
558
813
|
);
|
|
559
814
|
}
|
|
560
815
|
|
|
816
|
+
async #waitForActorStop(actorId: string): Promise<void> {
|
|
817
|
+
while (true) {
|
|
818
|
+
const entry = this.#actors.get(actorId);
|
|
819
|
+
if (!entry?.stopPromise) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
await entry.stopPromise.promise;
|
|
824
|
+
} catch {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async #withActorWrite<T>(
|
|
831
|
+
actorId: string,
|
|
832
|
+
fn: (entry: ActorEntry) => Promise<T>,
|
|
833
|
+
): Promise<T> {
|
|
834
|
+
const entry = this.#actors.get(actorId);
|
|
835
|
+
invariant(entry, "actor entry does not exist");
|
|
836
|
+
|
|
837
|
+
const previousWrite = entry.pendingWriteResolver;
|
|
838
|
+
const currentWrite = promiseWithResolvers<void>((reason) => logger().warn({ msg: "unhandled kv write promise rejection", reason }));
|
|
839
|
+
entry.pendingWriteResolver = currentWrite;
|
|
840
|
+
|
|
841
|
+
if (previousWrite) {
|
|
842
|
+
try {
|
|
843
|
+
await previousWrite.promise;
|
|
844
|
+
} catch {
|
|
845
|
+
// Ignore failed previous writes so later writes can proceed.
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
try {
|
|
850
|
+
return await fn(entry);
|
|
851
|
+
} finally {
|
|
852
|
+
currentWrite.resolve();
|
|
853
|
+
if (entry.pendingWriteResolver === currentWrite) {
|
|
854
|
+
entry.pendingWriteResolver = undefined;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async #waitForPendingWrite(actorId: string): Promise<void> {
|
|
860
|
+
const entry = this.#actors.get(actorId);
|
|
861
|
+
if (!entry?.pendingWriteResolver) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
while (entry.pendingWriteResolver) {
|
|
866
|
+
const pending = entry.pendingWriteResolver;
|
|
867
|
+
try {
|
|
868
|
+
await pending.promise;
|
|
869
|
+
} catch {
|
|
870
|
+
// Ignore write failures to avoid blocking reads forever.
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
561
875
|
async setActorAlarm(actorId: string, timestamp: number) {
|
|
562
876
|
const entry = this.#actors.get(actorId);
|
|
563
877
|
invariant(entry, "actor entry does not exist");
|
|
@@ -725,8 +1039,10 @@ export class FileSystemGlobalState {
|
|
|
725
1039
|
actorDriver: ActorDriver,
|
|
726
1040
|
actorId: string,
|
|
727
1041
|
): Promise<AnyActorInstance> {
|
|
1042
|
+
await this.#waitForActorStop(actorId);
|
|
1043
|
+
|
|
728
1044
|
// Get the actor metadata
|
|
729
|
-
|
|
1045
|
+
let entry = await this.loadActor(actorId);
|
|
730
1046
|
if (!entry.state) {
|
|
731
1047
|
throw new Error(
|
|
732
1048
|
`Actor does not exist and cannot be started: "${actorId}"`,
|
|
@@ -742,19 +1058,27 @@ export class FileSystemGlobalState {
|
|
|
742
1058
|
|
|
743
1059
|
// Actor already loaded
|
|
744
1060
|
if (entry.actor) {
|
|
745
|
-
|
|
1061
|
+
if (entry.actor.isStopping || this.isActorStopping(actorId)) {
|
|
1062
|
+
await this.#waitForActorStop(actorId);
|
|
1063
|
+
entry = await this.loadActor(actorId);
|
|
1064
|
+
if (!entry.state) {
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`Actor does not exist and cannot be started: "${actorId}"`,
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
return entry.actor;
|
|
1071
|
+
}
|
|
746
1072
|
}
|
|
747
1073
|
|
|
748
1074
|
// Create start promise
|
|
749
|
-
entry.startPromise = promiseWithResolvers();
|
|
1075
|
+
entry.startPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor start promise rejection", reason }));
|
|
750
1076
|
|
|
751
1077
|
try {
|
|
752
1078
|
// Create actor
|
|
753
|
-
const definition = lookupInRegistry(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
);
|
|
757
|
-
entry.actor = definition.instantiate();
|
|
1079
|
+
const definition = lookupInRegistry(config, entry.state.name);
|
|
1080
|
+
entry.actor = await definition.instantiate();
|
|
1081
|
+
entry.lifecycleState = ActorLifecycleState.AWAKE;
|
|
758
1082
|
|
|
759
1083
|
// Start actor
|
|
760
1084
|
await entry.actor.start(
|
|
@@ -769,24 +1093,37 @@ export class FileSystemGlobalState {
|
|
|
769
1093
|
// Update state with start timestamp
|
|
770
1094
|
// NOTE: connectableTs is always in sync with startTs since actors become connectable immediately after starting
|
|
771
1095
|
const now = BigInt(Date.now());
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1096
|
+
await this.#withActorWrite(actorId, async (lockedEntry) => {
|
|
1097
|
+
if (!lockedEntry.state) {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
`Actor does not exist and cannot be started: "${actorId}"`,
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
lockedEntry.state = {
|
|
1103
|
+
...lockedEntry.state,
|
|
1104
|
+
startTs: now,
|
|
1105
|
+
connectableTs: now,
|
|
1106
|
+
sleepTs: null, // Clear sleep timestamp when actor wakes up
|
|
1107
|
+
};
|
|
1108
|
+
if (this.#persist) {
|
|
1109
|
+
await this.#performWrite(
|
|
1110
|
+
actorId,
|
|
1111
|
+
lockedEntry.generation,
|
|
1112
|
+
lockedEntry.state,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
779
1116
|
|
|
780
1117
|
// Finish
|
|
781
1118
|
entry.startPromise.resolve();
|
|
782
1119
|
entry.startPromise = undefined;
|
|
783
1120
|
|
|
784
1121
|
return entry.actor;
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1122
|
+
} catch (innerError) {
|
|
1123
|
+
const error = new Error(
|
|
1124
|
+
`Failed to start actor ${actorId}: ${innerError}`,
|
|
1125
|
+
{ cause: innerError },
|
|
1126
|
+
);
|
|
790
1127
|
entry.startPromise?.reject(error);
|
|
791
1128
|
entry.startPromise = undefined;
|
|
792
1129
|
throw error;
|
|
@@ -983,48 +1320,20 @@ export class FileSystemGlobalState {
|
|
|
983
1320
|
actorId: string,
|
|
984
1321
|
entries: [Uint8Array, Uint8Array][],
|
|
985
1322
|
): Promise<void> {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
if (
|
|
989
|
-
|
|
990
|
-
|
|
1323
|
+
await this.loadActor(actorId);
|
|
1324
|
+
await this.#withActorWrite(actorId, async (entry) => {
|
|
1325
|
+
if (!entry.state) {
|
|
1326
|
+
if (this.isActorStopping(actorId)) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
991
1329
|
throw new Error(`Actor ${actorId} state not loaded`);
|
|
992
1330
|
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// Create a mutable copy of kvStorage
|
|
996
|
-
const newKvStorage = [...entry.state.kvStorage];
|
|
997
|
-
|
|
998
|
-
// Update kvStorage with new entries
|
|
999
|
-
for (const [key, value] of entries) {
|
|
1000
|
-
// Find existing entry with the same key
|
|
1001
|
-
const existingIndex = newKvStorage.findIndex((e) =>
|
|
1002
|
-
arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
|
|
1003
|
-
);
|
|
1004
|
-
|
|
1005
|
-
if (existingIndex >= 0) {
|
|
1006
|
-
// Replace existing entry with new one
|
|
1007
|
-
newKvStorage[existingIndex] = {
|
|
1008
|
-
key: bufferToArrayBuffer(key),
|
|
1009
|
-
value: bufferToArrayBuffer(value),
|
|
1010
|
-
};
|
|
1011
|
-
} else {
|
|
1012
|
-
// Add new entry
|
|
1013
|
-
newKvStorage.push({
|
|
1014
|
-
key: bufferToArrayBuffer(key),
|
|
1015
|
-
value: bufferToArrayBuffer(value),
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Update state with new kvStorage
|
|
1021
|
-
entry.state = {
|
|
1022
|
-
...entry.state,
|
|
1023
|
-
kvStorage: newKvStorage,
|
|
1024
|
-
};
|
|
1025
1331
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1332
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
1333
|
+
const totalSize = estimateKvSize(db);
|
|
1334
|
+
validateKvEntries(entries, totalSize);
|
|
1335
|
+
this.#putKvEntriesInDb(db, entries);
|
|
1336
|
+
});
|
|
1028
1337
|
}
|
|
1029
1338
|
|
|
1030
1339
|
/**
|
|
@@ -1035,6 +1344,7 @@ export class FileSystemGlobalState {
|
|
|
1035
1344
|
keys: Uint8Array[],
|
|
1036
1345
|
): Promise<(Uint8Array | null)[]> {
|
|
1037
1346
|
const entry = await this.loadActor(actorId);
|
|
1347
|
+
await this.#waitForPendingWrite(actorId);
|
|
1038
1348
|
if (!entry.state) {
|
|
1039
1349
|
if (this.isActorStopping(actorId)) {
|
|
1040
1350
|
throw new Error(`Actor ${actorId} is stopping`);
|
|
@@ -1043,18 +1353,20 @@ export class FileSystemGlobalState {
|
|
|
1043
1353
|
}
|
|
1044
1354
|
}
|
|
1045
1355
|
|
|
1356
|
+
validateKvKeys(keys);
|
|
1357
|
+
|
|
1358
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
1046
1359
|
const results: (Uint8Array | null)[] = [];
|
|
1047
1360
|
for (const key of keys) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1361
|
+
const row = db.get<{ value: Uint8Array | ArrayBuffer }>(
|
|
1362
|
+
"SELECT value FROM kv WHERE key = ?",
|
|
1363
|
+
[key],
|
|
1051
1364
|
);
|
|
1052
|
-
|
|
1053
|
-
if (foundEntry) {
|
|
1054
|
-
results.push(new Uint8Array(foundEntry.value));
|
|
1055
|
-
} else {
|
|
1365
|
+
if (!row) {
|
|
1056
1366
|
results.push(null);
|
|
1367
|
+
continue;
|
|
1057
1368
|
}
|
|
1369
|
+
results.push(ensureUint8Array(row.value, "value"));
|
|
1058
1370
|
}
|
|
1059
1371
|
return results;
|
|
1060
1372
|
}
|
|
@@ -1063,37 +1375,36 @@ export class FileSystemGlobalState {
|
|
|
1063
1375
|
* Batch delete KV entries for an actor.
|
|
1064
1376
|
*/
|
|
1065
1377
|
async kvBatchDelete(actorId: string, keys: Uint8Array[]): Promise<void> {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (
|
|
1069
|
-
|
|
1070
|
-
|
|
1378
|
+
await this.loadActor(actorId);
|
|
1379
|
+
await this.#withActorWrite(actorId, async (entry) => {
|
|
1380
|
+
if (!entry.state) {
|
|
1381
|
+
if (this.isActorStopping(actorId)) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1071
1384
|
throw new Error(`Actor ${actorId} state not loaded`);
|
|
1072
1385
|
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// Create a mutable copy of kvStorage
|
|
1076
|
-
const newKvStorage = [...entry.state.kvStorage];
|
|
1077
|
-
|
|
1078
|
-
// Delete entries from kvStorage
|
|
1079
|
-
for (const key of keys) {
|
|
1080
|
-
const indexToDelete = newKvStorage.findIndex((e) =>
|
|
1081
|
-
arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
|
|
1082
|
-
);
|
|
1083
1386
|
|
|
1084
|
-
if (
|
|
1085
|
-
|
|
1387
|
+
if (keys.length === 0) {
|
|
1388
|
+
return;
|
|
1086
1389
|
}
|
|
1087
|
-
|
|
1390
|
+
validateKvKeys(keys);
|
|
1088
1391
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1392
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
1393
|
+
db.exec("BEGIN");
|
|
1394
|
+
try {
|
|
1395
|
+
for (const key of keys) {
|
|
1396
|
+
db.run("DELETE FROM kv WHERE key = ?", [key]);
|
|
1397
|
+
}
|
|
1398
|
+
db.exec("COMMIT");
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
try {
|
|
1401
|
+
db.exec("ROLLBACK");
|
|
1402
|
+
} catch {
|
|
1403
|
+
// Ignore rollback errors, original error is more actionable.
|
|
1404
|
+
}
|
|
1405
|
+
throw error;
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1097
1408
|
}
|
|
1098
1409
|
|
|
1099
1410
|
/**
|
|
@@ -1104,6 +1415,7 @@ export class FileSystemGlobalState {
|
|
|
1104
1415
|
prefix: Uint8Array,
|
|
1105
1416
|
): Promise<[Uint8Array, Uint8Array][]> {
|
|
1106
1417
|
const entry = await this.loadActor(actorId);
|
|
1418
|
+
await this.#waitForPendingWrite(actorId);
|
|
1107
1419
|
if (!entry.state) {
|
|
1108
1420
|
if (this.isActorStopping(actorId)) {
|
|
1109
1421
|
throw new Error(`Actor ${actorId} is destroying`);
|
|
@@ -1111,24 +1423,23 @@ export class FileSystemGlobalState {
|
|
|
1111
1423
|
throw new Error(`Actor ${actorId} state not loaded`);
|
|
1112
1424
|
}
|
|
1113
1425
|
}
|
|
1426
|
+
validateKvKey(prefix, "prefix key");
|
|
1427
|
+
|
|
1428
|
+
const db = this.#getOrCreateActorKvDatabase(actorId);
|
|
1429
|
+
const upperBound = computePrefixUpperBound(prefix);
|
|
1430
|
+
const rows = upperBound
|
|
1431
|
+
? db.all<{ key: Uint8Array | ArrayBuffer; value: Uint8Array | ArrayBuffer }>(
|
|
1432
|
+
"SELECT key, value FROM kv WHERE key >= ? AND key < ? ORDER BY key ASC",
|
|
1433
|
+
[prefix, upperBound],
|
|
1434
|
+
)
|
|
1435
|
+
: db.all<{ key: Uint8Array | ArrayBuffer; value: Uint8Array | ArrayBuffer }>(
|
|
1436
|
+
"SELECT key, value FROM kv WHERE key >= ? ORDER BY key ASC",
|
|
1437
|
+
[prefix],
|
|
1438
|
+
);
|
|
1114
1439
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
if (keyBytes.length >= prefix.length) {
|
|
1120
|
-
let hasPrefix = true;
|
|
1121
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
1122
|
-
if (keyBytes[i] !== prefix[i]) {
|
|
1123
|
-
hasPrefix = false;
|
|
1124
|
-
break;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
if (hasPrefix) {
|
|
1128
|
-
results.push([keyBytes, new Uint8Array(kvEntry.value)]);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
return results;
|
|
1440
|
+
return rows.map((row) => [
|
|
1441
|
+
ensureUint8Array(row.key, "key"),
|
|
1442
|
+
ensureUint8Array(row.value, "value"),
|
|
1443
|
+
]);
|
|
1133
1444
|
}
|
|
1134
1445
|
}
|