rivetkit 2.0.24-rc.1 → 2.0.24
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/schemas/actor-persist/v2.ts +3 -3
- package/dist/schemas/actor-persist/v3.ts +274 -0
- package/dist/schemas/client-protocol/v2.ts +432 -0
- package/dist/schemas/file-system-driver/v2.ts +136 -0
- package/dist/tsup/actor/errors.cjs +2 -4
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +7 -10
- package/dist/tsup/actor/errors.d.ts +7 -10
- package/dist/tsup/actor/errors.js +9 -11
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.cts → actor-router-consts-DzI2szci.d.cts} +5 -9
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.ts → actor-router-consts-DzI2szci.d.ts} +5 -9
- package/dist/tsup/{chunk-HHFKKVLR.cjs → chunk-3543NCSN.cjs} +45 -57
- package/dist/tsup/chunk-3543NCSN.cjs.map +1 -0
- package/dist/tsup/chunk-4SHILYS5.cjs +5694 -0
- package/dist/tsup/chunk-4SHILYS5.cjs.map +1 -0
- package/dist/tsup/{chunk-ZTH3KYFH.cjs → chunk-5BZO5XPS.cjs} +3 -3
- package/dist/tsup/{chunk-ZTH3KYFH.cjs.map → chunk-5BZO5XPS.cjs.map} +1 -1
- package/dist/tsup/{chunk-PLUN2NQT.js → chunk-BAIGSF64.js} +189 -187
- package/dist/tsup/chunk-BAIGSF64.js.map +1 -0
- package/dist/tsup/{chunk-SHVX2QUR.cjs → chunk-CHLZBSI2.cjs} +17 -17
- package/dist/tsup/chunk-CHLZBSI2.cjs.map +1 -0
- package/dist/tsup/chunk-D3SLADUD.cjs +512 -0
- package/dist/tsup/chunk-D3SLADUD.cjs.map +1 -0
- package/dist/tsup/{chunk-KSRXX3Z4.cjs → chunk-D6762AOA.cjs} +20 -25
- package/dist/tsup/chunk-D6762AOA.cjs.map +1 -0
- package/dist/tsup/{chunk-7L65NNWP.cjs → chunk-DLK5YCTN.cjs} +187 -185
- package/dist/tsup/chunk-DLK5YCTN.cjs.map +1 -0
- package/dist/tsup/{chunk-YBG6R7LX.js → chunk-DUJQWGYD.js} +3 -7
- package/dist/tsup/chunk-DUJQWGYD.js.map +1 -0
- package/dist/tsup/{chunk-CD33GT6Z.js → chunk-EIPANQMF.js} +2 -2
- package/dist/tsup/{chunk-2JYPS5YM.cjs → chunk-ESMTDP7G.cjs} +6 -6
- package/dist/tsup/chunk-ESMTDP7G.cjs.map +1 -0
- package/dist/tsup/{chunk-VHGY7PU5.cjs → chunk-FVAKREFB.cjs} +1900 -1737
- package/dist/tsup/chunk-FVAKREFB.cjs.map +1 -0
- package/dist/tsup/{chunk-BLK27ES3.js → chunk-I3XT7WOF.js} +44 -56
- package/dist/tsup/chunk-I3XT7WOF.js.map +1 -0
- package/dist/tsup/{chunk-YBHYXIP6.js → chunk-IMDS5T42.js} +3 -3
- package/dist/tsup/chunk-IMDS5T42.js.map +1 -0
- package/dist/tsup/{chunk-INNFK746.cjs → chunk-J3HZJF2P.cjs} +10 -14
- package/dist/tsup/chunk-J3HZJF2P.cjs.map +1 -0
- package/dist/tsup/{chunk-BYMKMOBS.js → chunk-MBBJUHSP.js} +1844 -1681
- package/dist/tsup/chunk-MBBJUHSP.js.map +1 -0
- package/dist/tsup/{chunk-BOMZS2TJ.js → chunk-MO5CB6MD.js} +9 -9
- package/dist/tsup/chunk-MO5CB6MD.js.map +1 -0
- package/dist/tsup/chunk-OFOTPKAH.js +512 -0
- package/dist/tsup/chunk-OFOTPKAH.js.map +1 -0
- package/dist/tsup/{chunk-G64QUEDJ.js → chunk-W6RDS6NW.js} +23 -28
- package/dist/tsup/chunk-W6RDS6NW.js.map +1 -0
- package/dist/tsup/{chunk-36JJ4IQB.cjs → chunk-YC5DUHPM.cjs} +4 -8
- package/dist/tsup/chunk-YC5DUHPM.cjs.map +1 -0
- package/dist/tsup/{chunk-FX7TWFQR.js → chunk-YC7YPM2T.js} +2 -6
- package/dist/tsup/chunk-YC7YPM2T.js.map +1 -0
- package/dist/tsup/{chunk-227FEWMB.js → chunk-ZSPU5R4C.js} +3322 -2251
- package/dist/tsup/chunk-ZSPU5R4C.js.map +1 -0
- package/dist/tsup/client/mod.cjs +9 -9
- package/dist/tsup/client/mod.d.cts +5 -7
- package/dist/tsup/client/mod.d.ts +5 -7
- package/dist/tsup/client/mod.js +8 -8
- package/dist/tsup/common/log.cjs +3 -3
- package/dist/tsup/common/log.js +2 -2
- package/dist/tsup/common/websocket.cjs +4 -4
- package/dist/tsup/common/websocket.js +3 -3
- package/dist/tsup/{conn-B3Vhbgnd.d.ts → config-BRDYDraU.d.cts} +1119 -1047
- package/dist/tsup/{conn-DJWL3nGx.d.cts → config-Bo-blHpJ.d.ts} +1119 -1047
- package/dist/tsup/driver-helpers/mod.cjs +5 -13
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +11 -9
- package/dist/tsup/driver-helpers/mod.d.ts +11 -9
- package/dist/tsup/driver-helpers/mod.js +14 -22
- package/dist/tsup/driver-test-suite/mod.cjs +474 -303
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +6 -9
- package/dist/tsup/driver-test-suite/mod.d.ts +6 -9
- package/dist/tsup/driver-test-suite/mod.js +1085 -914
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.d.cts +5 -7
- package/dist/tsup/inspector/mod.d.ts +5 -7
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/mod.cjs +10 -16
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +23 -25
- package/dist/tsup/mod.d.ts +23 -25
- package/dist/tsup/mod.js +17 -23
- package/dist/tsup/test/mod.cjs +11 -11
- package/dist/tsup/test/mod.d.cts +4 -6
- package/dist/tsup/test/mod.d.ts +4 -6
- package/dist/tsup/test/mod.js +10 -10
- package/dist/tsup/utils.cjs +3 -5
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +1 -2
- package/dist/tsup/utils.d.ts +1 -2
- package/dist/tsup/utils.js +2 -4
- package/package.json +13 -6
- package/src/actor/config.ts +56 -44
- 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 +129 -0
- package/src/actor/conn/mod.ts +232 -0
- package/src/actor/conn/persisted.ts +81 -0
- package/src/actor/conn/state-manager.ts +196 -0
- package/src/actor/contexts/action.ts +23 -0
- package/src/actor/{context.ts → contexts/actor.ts} +19 -8
- package/src/actor/contexts/conn-init.ts +31 -0
- package/src/actor/contexts/conn.ts +48 -0
- package/src/actor/contexts/create-conn-state.ts +13 -0
- package/src/actor/contexts/on-before-connect.ts +13 -0
- package/src/actor/contexts/on-connect.ts +22 -0
- package/src/actor/contexts/request.ts +48 -0
- package/src/actor/contexts/websocket.ts +48 -0
- package/src/actor/definition.ts +3 -3
- package/src/actor/driver.ts +36 -5
- package/src/actor/errors.ts +19 -24
- package/src/actor/instance/connection-manager.ts +465 -0
- package/src/actor/instance/event-manager.ts +292 -0
- package/src/actor/instance/kv.ts +15 -0
- package/src/actor/instance/mod.ts +1107 -0
- package/src/actor/instance/persisted.ts +67 -0
- package/src/actor/instance/schedule-manager.ts +349 -0
- package/src/actor/instance/state-manager.ts +502 -0
- package/src/actor/mod.ts +13 -16
- package/src/actor/protocol/old.ts +131 -43
- package/src/actor/protocol/serde.ts +19 -4
- package/src/actor/router-endpoints.ts +61 -586
- package/src/actor/router-websocket-endpoints.ts +408 -0
- package/src/actor/router.ts +63 -197
- package/src/actor/schedule.ts +1 -1
- package/src/client/actor-conn.ts +183 -249
- package/src/client/actor-handle.ts +29 -6
- package/src/client/client.ts +0 -4
- package/src/client/config.ts +1 -4
- package/src/client/mod.ts +0 -1
- package/src/client/raw-utils.ts +3 -3
- package/src/client/utils.ts +85 -39
- package/src/common/actor-router-consts.ts +5 -12
- package/src/common/{inline-websocket-adapter2.ts → inline-websocket-adapter.ts} +26 -48
- package/src/common/log.ts +1 -1
- package/src/common/router.ts +28 -17
- package/src/common/utils.ts +2 -0
- package/src/driver-helpers/mod.ts +7 -10
- package/src/driver-helpers/utils.ts +18 -9
- package/src/driver-test-suite/mod.ts +26 -50
- package/src/driver-test-suite/test-inline-client-driver.ts +27 -51
- package/src/driver-test-suite/tests/actor-conn-hibernation.ts +150 -0
- package/src/driver-test-suite/tests/actor-conn-state.ts +1 -4
- package/src/driver-test-suite/tests/actor-conn.ts +5 -9
- package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
- package/src/driver-test-suite/tests/actor-driver.ts +0 -7
- package/src/driver-test-suite/tests/actor-handle.ts +12 -12
- package/src/driver-test-suite/tests/actor-metadata.ts +1 -1
- package/src/driver-test-suite/tests/manager-driver.ts +1 -1
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +8 -8
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +6 -5
- package/src/driver-test-suite/tests/raw-http.ts +5 -5
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +7 -7
- package/src/driver-test-suite/tests/request-access.ts +4 -4
- package/src/driver-test-suite/utils.ts +6 -10
- package/src/drivers/engine/actor-driver.ts +614 -424
- package/src/drivers/engine/mod.ts +0 -1
- package/src/drivers/file-system/actor.ts +24 -12
- package/src/drivers/file-system/global-state.ts +427 -37
- package/src/drivers/file-system/manager.ts +71 -83
- package/src/drivers/file-system/mod.ts +3 -0
- package/src/drivers/file-system/utils.ts +18 -8
- package/src/engine-process/mod.ts +38 -38
- package/src/inspector/utils.ts +7 -5
- package/src/manager/driver.ts +11 -4
- package/src/manager/gateway.ts +4 -29
- package/src/manager/protocol/mod.ts +0 -2
- package/src/manager/protocol/query.ts +0 -4
- package/src/manager/router.ts +67 -64
- package/src/manager-api/actors.ts +13 -0
- package/src/mod.ts +1 -3
- package/src/registry/mod.ts +20 -20
- package/src/registry/serve.ts +9 -14
- package/src/remote-manager-driver/actor-websocket-client.ts +1 -16
- package/src/remote-manager-driver/api-endpoints.ts +13 -1
- package/src/remote-manager-driver/api-utils.ts +8 -0
- package/src/remote-manager-driver/metadata.ts +58 -0
- package/src/remote-manager-driver/mod.ts +47 -62
- package/src/remote-manager-driver/ws-proxy.ts +1 -1
- package/src/schemas/actor-persist/mod.ts +1 -1
- package/src/schemas/actor-persist/versioned.ts +56 -31
- package/src/schemas/client-protocol/mod.ts +1 -1
- package/src/schemas/client-protocol/versioned.ts +41 -21
- package/src/schemas/client-protocol-zod/mod.ts +103 -0
- package/src/schemas/file-system-driver/mod.ts +1 -1
- package/src/schemas/file-system-driver/versioned.ts +42 -19
- package/src/serde.ts +33 -11
- package/src/test/mod.ts +7 -3
- package/src/utils/node.ts +173 -0
- package/src/utils.ts +0 -4
- package/dist/tsup/chunk-227FEWMB.js.map +0 -1
- package/dist/tsup/chunk-2JYPS5YM.cjs.map +0 -1
- package/dist/tsup/chunk-36JJ4IQB.cjs.map +0 -1
- package/dist/tsup/chunk-7L65NNWP.cjs.map +0 -1
- package/dist/tsup/chunk-BLK27ES3.js.map +0 -1
- package/dist/tsup/chunk-BOMZS2TJ.js.map +0 -1
- package/dist/tsup/chunk-BYMKMOBS.js.map +0 -1
- package/dist/tsup/chunk-FX7TWFQR.js.map +0 -1
- package/dist/tsup/chunk-G64QUEDJ.js.map +0 -1
- package/dist/tsup/chunk-HHFKKVLR.cjs.map +0 -1
- package/dist/tsup/chunk-INNFK746.cjs.map +0 -1
- package/dist/tsup/chunk-KSRXX3Z4.cjs.map +0 -1
- package/dist/tsup/chunk-O44LFKSB.cjs +0 -4623
- package/dist/tsup/chunk-O44LFKSB.cjs.map +0 -1
- package/dist/tsup/chunk-PLUN2NQT.js.map +0 -1
- package/dist/tsup/chunk-S4UJG7ZE.js +0 -1119
- package/dist/tsup/chunk-S4UJG7ZE.js.map +0 -1
- package/dist/tsup/chunk-SHVX2QUR.cjs.map +0 -1
- package/dist/tsup/chunk-VFB23BYZ.cjs +0 -1119
- package/dist/tsup/chunk-VFB23BYZ.cjs.map +0 -1
- package/dist/tsup/chunk-VHGY7PU5.cjs.map +0 -1
- package/dist/tsup/chunk-YBG6R7LX.js.map +0 -1
- package/dist/tsup/chunk-YBHYXIP6.js.map +0 -1
- package/src/actor/action.ts +0 -178
- package/src/actor/conn-drivers.ts +0 -216
- package/src/actor/conn-socket.ts +0 -8
- package/src/actor/conn.ts +0 -272
- package/src/actor/instance.ts +0 -2336
- package/src/actor/persisted.ts +0 -49
- package/src/actor/unstable-react.ts +0 -110
- package/src/driver-test-suite/tests/actor-reconnect.ts +0 -170
- package/src/drivers/engine/kv.ts +0 -3
- package/src/manager/hono-websocket-adapter.ts +0 -393
- /package/dist/tsup/{chunk-CD33GT6Z.js.map → chunk-EIPANQMF.js.map} +0 -0
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import * as crypto from "node:crypto";
|
|
2
|
-
import * as fsSync from "node:fs";
|
|
3
|
-
import * as fs from "node:fs/promises";
|
|
4
|
-
import * as path from "node:path";
|
|
5
1
|
import invariant from "invariant";
|
|
6
2
|
import { lookupInRegistry } from "@/actor/definition";
|
|
7
|
-
import {
|
|
8
|
-
import type { AnyActorInstance } from "@/actor/instance";
|
|
3
|
+
import { ActorDuplicateKey } from "@/actor/errors";
|
|
4
|
+
import type { AnyActorInstance } from "@/actor/instance/mod";
|
|
9
5
|
import type { ActorKey } from "@/actor/mod";
|
|
10
6
|
import { generateRandomString } from "@/actor/utils";
|
|
11
7
|
import type { AnyClient } from "@/client/client";
|
|
12
|
-
import {
|
|
13
|
-
type ActorDriver,
|
|
14
|
-
serializeEmptyPersistData,
|
|
15
|
-
} from "@/driver-helpers/mod";
|
|
8
|
+
import { type ActorDriver, getInitialActorKvState } from "@/driver-helpers/mod";
|
|
16
9
|
import type { RegistryConfig } from "@/registry/config";
|
|
17
10
|
import type { RunnerConfig } from "@/registry/run-config";
|
|
18
11
|
import type * as schema from "@/schemas/file-system-driver/mod";
|
|
@@ -21,12 +14,19 @@ import {
|
|
|
21
14
|
ACTOR_STATE_VERSIONED,
|
|
22
15
|
} from "@/schemas/file-system-driver/versioned";
|
|
23
16
|
import {
|
|
17
|
+
arrayBuffersEqual,
|
|
24
18
|
bufferToArrayBuffer,
|
|
25
19
|
type LongTimeoutHandle,
|
|
26
20
|
promiseWithResolvers,
|
|
27
21
|
setLongTimeout,
|
|
28
22
|
stringifyError,
|
|
29
23
|
} from "@/utils";
|
|
24
|
+
import {
|
|
25
|
+
getNodeCrypto,
|
|
26
|
+
getNodeFs,
|
|
27
|
+
getNodeFsSync,
|
|
28
|
+
getNodePath,
|
|
29
|
+
} from "@/utils/node";
|
|
30
30
|
import { logger } from "./log";
|
|
31
31
|
import {
|
|
32
32
|
ensureDirectoryExists,
|
|
@@ -36,10 +36,19 @@ import {
|
|
|
36
36
|
|
|
37
37
|
// Actor handler to track running instances
|
|
38
38
|
|
|
39
|
+
enum ActorLifecycleState {
|
|
40
|
+
NONEXISTENT, // Entry exists but actor not yet created
|
|
41
|
+
AWAKE, // Actor is running normally
|
|
42
|
+
STARTING_SLEEP, // Actor is being put to sleep
|
|
43
|
+
STARTING_DESTROY, // Actor is being destroyed
|
|
44
|
+
DESTROYED, // Actor was destroyed, should not be recreated
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
interface ActorEntry {
|
|
40
48
|
id: string;
|
|
41
49
|
|
|
42
50
|
state?: schema.ActorState;
|
|
51
|
+
|
|
43
52
|
/** Promise for loading the actor state. */
|
|
44
53
|
loadPromise?: Promise<ActorEntry>;
|
|
45
54
|
|
|
@@ -54,8 +63,12 @@ interface ActorEntry {
|
|
|
54
63
|
/** Resolver for pending write operations that need to be notified when any write completes */
|
|
55
64
|
pendingWriteResolver?: PromiseWithResolvers<void>;
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
lifecycleState: ActorLifecycleState;
|
|
67
|
+
|
|
68
|
+
// TODO: This might make sense to move in to actorstate, but we have a
|
|
69
|
+
// single reader/writer so it's not an issue
|
|
70
|
+
/** Generation of this actor when creating/destroying. */
|
|
71
|
+
generation: string;
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
/**
|
|
@@ -68,7 +81,12 @@ export class FileSystemGlobalState {
|
|
|
68
81
|
#alarmsDir: string;
|
|
69
82
|
|
|
70
83
|
#persist: boolean;
|
|
84
|
+
|
|
85
|
+
// IMPORTANT: Never delete from this map. Doing so will result in race
|
|
86
|
+
// conditions since the actor generation will cease to be tracked
|
|
87
|
+
// correctly. Always increment generation if a new actor is created.
|
|
71
88
|
#actors = new Map<string, ActorEntry>();
|
|
89
|
+
|
|
72
90
|
#actorCountOnStartup: number = 0;
|
|
73
91
|
|
|
74
92
|
#runnerParams?: {
|
|
@@ -92,7 +110,8 @@ export class FileSystemGlobalState {
|
|
|
92
110
|
|
|
93
111
|
constructor(persist: boolean = true, customPath?: string) {
|
|
94
112
|
this.#persist = persist;
|
|
95
|
-
this.#storagePath = persist ? getStoragePath(
|
|
113
|
+
this.#storagePath = persist ? (customPath ?? getStoragePath()) : "/tmp";
|
|
114
|
+
const path = getNodePath();
|
|
96
115
|
this.#stateDir = path.join(this.#storagePath, "state");
|
|
97
116
|
this.#dbsDir = path.join(this.#storagePath, "databases");
|
|
98
117
|
this.#alarmsDir = path.join(this.#storagePath, "alarms");
|
|
@@ -104,6 +123,7 @@ export class FileSystemGlobalState {
|
|
|
104
123
|
ensureDirectoryExistsSync(this.#alarmsDir);
|
|
105
124
|
|
|
106
125
|
try {
|
|
126
|
+
const fsSync = getNodeFsSync();
|
|
107
127
|
const actorIds = fsSync.readdirSync(this.#stateDir);
|
|
108
128
|
this.#actorCountOnStartup = actorIds.length;
|
|
109
129
|
} catch (error) {
|
|
@@ -131,15 +151,15 @@ export class FileSystemGlobalState {
|
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
getActorStatePath(actorId: string): string {
|
|
134
|
-
return
|
|
154
|
+
return getNodePath().join(this.#stateDir, actorId);
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
getActorDbPath(actorId: string): string {
|
|
138
|
-
return
|
|
158
|
+
return getNodePath().join(this.#dbsDir, `${actorId}.db`);
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
getActorAlarmPath(actorId: string): string {
|
|
142
|
-
return
|
|
162
|
+
return getNodePath().join(this.#alarmsDir, actorId);
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
async *getActorsIterator(params: {
|
|
@@ -148,6 +168,7 @@ export class FileSystemGlobalState {
|
|
|
148
168
|
let actorIds = Array.from(this.#actors.keys()).sort();
|
|
149
169
|
|
|
150
170
|
// Check if state directory exists first
|
|
171
|
+
const fsSync = getNodeFsSync();
|
|
151
172
|
if (fsSync.existsSync(this.#stateDir)) {
|
|
152
173
|
actorIds = fsSync
|
|
153
174
|
.readdirSync(this.#stateDir)
|
|
@@ -191,7 +212,8 @@ export class FileSystemGlobalState {
|
|
|
191
212
|
|
|
192
213
|
entry = {
|
|
193
214
|
id: actorId,
|
|
194
|
-
|
|
215
|
+
lifecycleState: ActorLifecycleState.NONEXISTENT,
|
|
216
|
+
generation: crypto.randomUUID(),
|
|
195
217
|
};
|
|
196
218
|
this.#actors.set(actorId, entry);
|
|
197
219
|
return entry;
|
|
@@ -208,21 +230,44 @@ export class FileSystemGlobalState {
|
|
|
208
230
|
): Promise<ActorEntry> {
|
|
209
231
|
// TODO: Does not check if actor already exists on fs
|
|
210
232
|
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
const entry = this.#upsertEntry(actorId);
|
|
234
|
+
|
|
235
|
+
// Check if actor already exists (has state or is being stopped)
|
|
236
|
+
if (entry.state) {
|
|
237
|
+
throw new ActorDuplicateKey(name, key);
|
|
238
|
+
}
|
|
239
|
+
if (this.isActorStopping(actorId)) {
|
|
240
|
+
throw new Error(`Actor ${actorId} is stopping`);
|
|
213
241
|
}
|
|
214
242
|
|
|
215
|
-
|
|
243
|
+
// If actor was destroyed, reset to NONEXISTENT and increment generation
|
|
244
|
+
if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
|
|
245
|
+
entry.lifecycleState = ActorLifecycleState.NONEXISTENT;
|
|
246
|
+
entry.generation = crypto.randomUUID();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Initialize storage
|
|
250
|
+
const kvStorage: schema.ActorKvEntry[] = [];
|
|
251
|
+
const initialKvState = getInitialActorKvState(input);
|
|
252
|
+
for (const [key, value] of initialKvState) {
|
|
253
|
+
kvStorage.push({
|
|
254
|
+
key: bufferToArrayBuffer(key),
|
|
255
|
+
value: bufferToArrayBuffer(value),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Initialize metadata
|
|
216
260
|
entry.state = {
|
|
217
261
|
actorId,
|
|
218
262
|
name,
|
|
219
263
|
key,
|
|
220
264
|
createdAt: BigInt(Date.now()),
|
|
221
|
-
|
|
222
|
-
serializeEmptyPersistData(input),
|
|
223
|
-
),
|
|
265
|
+
kvStorage,
|
|
224
266
|
};
|
|
225
|
-
|
|
267
|
+
entry.lifecycleState = ActorLifecycleState.AWAKE;
|
|
268
|
+
|
|
269
|
+
await this.writeActor(actorId, entry.generation, entry.state);
|
|
270
|
+
|
|
226
271
|
return entry;
|
|
227
272
|
}
|
|
228
273
|
|
|
@@ -232,6 +277,11 @@ export class FileSystemGlobalState {
|
|
|
232
277
|
async loadActor(actorId: string): Promise<ActorEntry> {
|
|
233
278
|
const entry = this.#upsertEntry(actorId);
|
|
234
279
|
|
|
280
|
+
// Check if destroyed - don't load from disk
|
|
281
|
+
if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
|
|
282
|
+
return entry;
|
|
283
|
+
}
|
|
284
|
+
|
|
235
285
|
// Check if already loaded
|
|
236
286
|
if (entry.state) {
|
|
237
287
|
return entry;
|
|
@@ -258,6 +308,7 @@ export class FileSystemGlobalState {
|
|
|
258
308
|
|
|
259
309
|
// Read & parse file
|
|
260
310
|
try {
|
|
311
|
+
const fs = getNodeFs();
|
|
261
312
|
const stateData = await fs.readFile(stateFilePath);
|
|
262
313
|
|
|
263
314
|
// Cache the loaded state in handler
|
|
@@ -292,16 +343,34 @@ export class FileSystemGlobalState {
|
|
|
292
343
|
|
|
293
344
|
// If no state for this actor, then create & write state
|
|
294
345
|
if (!entry.state) {
|
|
346
|
+
if (this.isActorStopping(actorId)) {
|
|
347
|
+
throw new Error(`Actor ${actorId} stopping`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// If actor was destroyed, reset to NONEXISTENT and increment generation
|
|
351
|
+
if (entry.lifecycleState === ActorLifecycleState.DESTROYED) {
|
|
352
|
+
entry.lifecycleState = ActorLifecycleState.NONEXISTENT;
|
|
353
|
+
entry.generation = crypto.randomUUID();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Initialize kvStorage with the initial persist data
|
|
357
|
+
const kvStorage: schema.ActorKvEntry[] = [];
|
|
358
|
+
const initialKvState = getInitialActorKvState(input);
|
|
359
|
+
for (const [key, value] of initialKvState) {
|
|
360
|
+
kvStorage.push({
|
|
361
|
+
key: bufferToArrayBuffer(key),
|
|
362
|
+
value: bufferToArrayBuffer(value),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
295
366
|
entry.state = {
|
|
296
367
|
actorId,
|
|
297
368
|
name,
|
|
298
369
|
key: key as readonly string[],
|
|
299
370
|
createdAt: BigInt(Date.now()),
|
|
300
|
-
|
|
301
|
-
serializeEmptyPersistData(input),
|
|
302
|
-
),
|
|
371
|
+
kvStorage,
|
|
303
372
|
};
|
|
304
|
-
await this.writeActor(actorId, entry.state);
|
|
373
|
+
await this.writeActor(actorId, entry.generation, entry.state);
|
|
305
374
|
}
|
|
306
375
|
return entry;
|
|
307
376
|
}
|
|
@@ -312,29 +381,128 @@ export class FileSystemGlobalState {
|
|
|
312
381
|
"cannot sleep actor with memory driver, must use file system driver",
|
|
313
382
|
);
|
|
314
383
|
|
|
315
|
-
|
|
384
|
+
// Get the actor. We upsert it even though we're about to destroy it so we have a lock on flagging `destroying` as true.
|
|
385
|
+
const actor = this.#upsertEntry(actorId);
|
|
316
386
|
invariant(actor, `tried to sleep ${actorId}, does not exist`);
|
|
317
387
|
|
|
388
|
+
// Check if already destroying
|
|
389
|
+
if (this.isActorStopping(actorId)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
actor.lifecycleState = ActorLifecycleState.STARTING_SLEEP;
|
|
393
|
+
|
|
318
394
|
// Wait for actor to fully start before stopping it to avoid race conditions
|
|
319
395
|
if (actor.loadPromise) await actor.loadPromise.catch();
|
|
320
396
|
if (actor.startPromise?.promise)
|
|
321
397
|
await actor.startPromise.promise.catch();
|
|
322
398
|
|
|
323
|
-
// Mark as removed
|
|
324
|
-
actor.removed = true;
|
|
325
|
-
|
|
326
399
|
// Stop actor
|
|
327
400
|
invariant(actor.actor, "actor should be loaded");
|
|
328
|
-
await actor.actor.
|
|
401
|
+
await actor.actor.onStop("sleep");
|
|
329
402
|
|
|
330
403
|
// Remove from map after stop is complete
|
|
331
404
|
this.#actors.delete(actorId);
|
|
332
405
|
}
|
|
333
406
|
|
|
407
|
+
async destroyActor(actorId: string) {
|
|
408
|
+
// Get the actor. We upsert it even though we're about to destroy it so we have a lock on flagging `destroying` as true.
|
|
409
|
+
const actor = this.#upsertEntry(actorId);
|
|
410
|
+
|
|
411
|
+
// If actor is loaded, stop it first
|
|
412
|
+
// Check if already destroying
|
|
413
|
+
if (this.isActorStopping(actorId)) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
actor.lifecycleState = ActorLifecycleState.STARTING_DESTROY;
|
|
417
|
+
|
|
418
|
+
// Wait for actor to fully start before stopping it to avoid race conditions
|
|
419
|
+
if (actor.loadPromise) await actor.loadPromise.catch();
|
|
420
|
+
if (actor.startPromise?.promise)
|
|
421
|
+
await actor.startPromise.promise.catch();
|
|
422
|
+
|
|
423
|
+
// Stop actor if it's running
|
|
424
|
+
if (actor.actor) {
|
|
425
|
+
await actor.actor.onStop("destroy");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Clear alarm timeout if exists
|
|
429
|
+
if (actor.alarmTimeout) {
|
|
430
|
+
actor.alarmTimeout.abort();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Delete persisted files if using file system driver
|
|
434
|
+
if (this.#persist) {
|
|
435
|
+
const fs = getNodeFs();
|
|
436
|
+
|
|
437
|
+
// Delete all actor files in parallel
|
|
438
|
+
await Promise.all([
|
|
439
|
+
// Delete actor state file
|
|
440
|
+
(async () => {
|
|
441
|
+
try {
|
|
442
|
+
await fs.unlink(this.getActorStatePath(actorId));
|
|
443
|
+
} catch (err: any) {
|
|
444
|
+
if (err?.code !== "ENOENT") {
|
|
445
|
+
logger().error({
|
|
446
|
+
msg: "failed to delete actor state file",
|
|
447
|
+
actorId,
|
|
448
|
+
error: stringifyError(err),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
})(),
|
|
453
|
+
// Delete actor database file
|
|
454
|
+
(async () => {
|
|
455
|
+
try {
|
|
456
|
+
await fs.unlink(this.getActorDbPath(actorId));
|
|
457
|
+
} catch (err: any) {
|
|
458
|
+
if (err?.code !== "ENOENT") {
|
|
459
|
+
logger().error({
|
|
460
|
+
msg: "failed to delete actor database file",
|
|
461
|
+
actorId,
|
|
462
|
+
error: stringifyError(err),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
})(),
|
|
467
|
+
// Delete actor alarm file
|
|
468
|
+
(async () => {
|
|
469
|
+
try {
|
|
470
|
+
await fs.unlink(this.getActorAlarmPath(actorId));
|
|
471
|
+
} catch (err: any) {
|
|
472
|
+
if (err?.code !== "ENOENT") {
|
|
473
|
+
logger().error({
|
|
474
|
+
msg: "failed to delete actor alarm file",
|
|
475
|
+
actorId,
|
|
476
|
+
error: stringifyError(err),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
})(),
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Reset the entry
|
|
485
|
+
//
|
|
486
|
+
// Do not remove entry in order to avoid race condition with
|
|
487
|
+
// destroying. Next actor creation will increment the generation.
|
|
488
|
+
actor.state = undefined;
|
|
489
|
+
actor.loadPromise = undefined;
|
|
490
|
+
actor.actor = undefined;
|
|
491
|
+
actor.startPromise = undefined;
|
|
492
|
+
actor.alarmTimeout = undefined;
|
|
493
|
+
actor.alarmTimeout = undefined;
|
|
494
|
+
actor.pendingWriteResolver = undefined;
|
|
495
|
+
actor.lifecycleState = ActorLifecycleState.DESTROYED;
|
|
496
|
+
}
|
|
497
|
+
|
|
334
498
|
/**
|
|
335
499
|
* Save actor state to disk.
|
|
336
500
|
*/
|
|
337
|
-
async writeActor(
|
|
501
|
+
async writeActor(
|
|
502
|
+
actorId: string,
|
|
503
|
+
generation: string,
|
|
504
|
+
state: schema.ActorState,
|
|
505
|
+
): Promise<void> {
|
|
338
506
|
if (!this.#persist) {
|
|
339
507
|
return;
|
|
340
508
|
}
|
|
@@ -342,18 +510,49 @@ export class FileSystemGlobalState {
|
|
|
342
510
|
const entry = this.#actors.get(actorId);
|
|
343
511
|
invariant(entry, "actor entry does not exist");
|
|
344
512
|
|
|
345
|
-
await this.#performWrite(actorId, state);
|
|
513
|
+
await this.#performWrite(actorId, generation, state);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
isGenerationCurrentAndNotDestroyed(
|
|
517
|
+
actorId: string,
|
|
518
|
+
generation: string,
|
|
519
|
+
): boolean {
|
|
520
|
+
const entry = this.#upsertEntry(actorId);
|
|
521
|
+
if (!entry) return false;
|
|
522
|
+
return (
|
|
523
|
+
entry.generation === generation &&
|
|
524
|
+
entry.lifecycleState !== ActorLifecycleState.STARTING_DESTROY
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
isActorStopping(actorId: string) {
|
|
529
|
+
const entry = this.#upsertEntry(actorId);
|
|
530
|
+
if (!entry) return false;
|
|
531
|
+
return (
|
|
532
|
+
entry.lifecycleState === ActorLifecycleState.STARTING_SLEEP ||
|
|
533
|
+
entry.lifecycleState === ActorLifecycleState.STARTING_DESTROY
|
|
534
|
+
);
|
|
346
535
|
}
|
|
347
536
|
|
|
348
537
|
async setActorAlarm(actorId: string, timestamp: number) {
|
|
349
538
|
const entry = this.#actors.get(actorId);
|
|
350
539
|
invariant(entry, "actor entry does not exist");
|
|
351
540
|
|
|
541
|
+
// Track generation of the actor when the write started to detect
|
|
542
|
+
// destroy/create race condition
|
|
543
|
+
const writeGeneration = entry.generation;
|
|
544
|
+
if (this.isActorStopping(actorId)) {
|
|
545
|
+
logger().info("skipping set alarm since actor stopping");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
352
549
|
// Persist alarm to disk
|
|
353
550
|
if (this.#persist) {
|
|
354
551
|
const alarmPath = this.getActorAlarmPath(actorId);
|
|
552
|
+
const crypto = getNodeCrypto();
|
|
355
553
|
const tempPath = `${alarmPath}.tmp.${crypto.randomUUID()}`;
|
|
356
554
|
try {
|
|
555
|
+
const path = getNodePath();
|
|
357
556
|
await ensureDirectoryExists(path.dirname(alarmPath));
|
|
358
557
|
const alarmData: schema.ActorAlarm = {
|
|
359
558
|
actorId,
|
|
@@ -363,10 +562,25 @@ export class FileSystemGlobalState {
|
|
|
363
562
|
ACTOR_ALARM_VERSIONED.serializeWithEmbeddedVersion(
|
|
364
563
|
alarmData,
|
|
365
564
|
);
|
|
565
|
+
const fs = getNodeFs();
|
|
366
566
|
await fs.writeFile(tempPath, data);
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
!this.isGenerationCurrentAndNotDestroyed(
|
|
570
|
+
actorId,
|
|
571
|
+
writeGeneration,
|
|
572
|
+
)
|
|
573
|
+
) {
|
|
574
|
+
logger().debug(
|
|
575
|
+
"skipping writing alarm since actor destroying or new generation",
|
|
576
|
+
);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
367
580
|
await fs.rename(tempPath, alarmPath);
|
|
368
581
|
} catch (error) {
|
|
369
582
|
try {
|
|
583
|
+
const fs = getNodeFs();
|
|
370
584
|
await fs.unlink(tempPath);
|
|
371
585
|
} catch {}
|
|
372
586
|
logger().error({
|
|
@@ -387,14 +601,17 @@ export class FileSystemGlobalState {
|
|
|
387
601
|
*/
|
|
388
602
|
async #performWrite(
|
|
389
603
|
actorId: string,
|
|
604
|
+
generation: string,
|
|
390
605
|
state: schema.ActorState,
|
|
391
606
|
): Promise<void> {
|
|
392
607
|
const dataPath = this.getActorStatePath(actorId);
|
|
393
608
|
// Generate unique temp filename to prevent any race conditions
|
|
609
|
+
const crypto = getNodeCrypto();
|
|
394
610
|
const tempPath = `${dataPath}.tmp.${crypto.randomUUID()}`;
|
|
395
611
|
|
|
396
612
|
try {
|
|
397
613
|
// Create directory if needed
|
|
614
|
+
const path = getNodePath();
|
|
398
615
|
await ensureDirectoryExists(path.dirname(dataPath));
|
|
399
616
|
|
|
400
617
|
// Convert to BARE types for serialization
|
|
@@ -403,17 +620,27 @@ export class FileSystemGlobalState {
|
|
|
403
620
|
name: state.name,
|
|
404
621
|
key: state.key,
|
|
405
622
|
createdAt: state.createdAt,
|
|
406
|
-
|
|
623
|
+
kvStorage: state.kvStorage,
|
|
407
624
|
};
|
|
408
625
|
|
|
409
626
|
// Perform atomic write
|
|
410
627
|
const serializedState =
|
|
411
628
|
ACTOR_STATE_VERSIONED.serializeWithEmbeddedVersion(bareState);
|
|
629
|
+
const fs = getNodeFs();
|
|
412
630
|
await fs.writeFile(tempPath, serializedState);
|
|
631
|
+
|
|
632
|
+
if (!this.isGenerationCurrentAndNotDestroyed(actorId, generation)) {
|
|
633
|
+
logger().debug(
|
|
634
|
+
"skipping writing alarm since actor destroying or new generation",
|
|
635
|
+
);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
413
639
|
await fs.rename(tempPath, dataPath);
|
|
414
640
|
} catch (error) {
|
|
415
641
|
// Cleanup temp file on error
|
|
416
642
|
try {
|
|
643
|
+
const fs = getNodeFs();
|
|
417
644
|
await fs.unlink(tempPath);
|
|
418
645
|
} catch {
|
|
419
646
|
// Ignore cleanup errors
|
|
@@ -548,12 +775,14 @@ export class FileSystemGlobalState {
|
|
|
548
775
|
*/
|
|
549
776
|
#loadAlarmsSync(): void {
|
|
550
777
|
try {
|
|
778
|
+
const fsSync = getNodeFsSync();
|
|
551
779
|
const files = fsSync.existsSync(this.#alarmsDir)
|
|
552
780
|
? fsSync.readdirSync(this.#alarmsDir)
|
|
553
781
|
: [];
|
|
554
782
|
for (const file of files) {
|
|
555
783
|
// Skip temp files
|
|
556
784
|
if (file.includes(".tmp.")) continue;
|
|
785
|
+
const path = getNodePath();
|
|
557
786
|
const fullPath = path.join(this.#alarmsDir, file);
|
|
558
787
|
try {
|
|
559
788
|
const buf = fsSync.readFileSync(fullPath);
|
|
@@ -622,6 +851,7 @@ export class FileSystemGlobalState {
|
|
|
622
851
|
// On trigger: remove persisted alarm file
|
|
623
852
|
if (this.#persist) {
|
|
624
853
|
try {
|
|
854
|
+
const fs = getNodeFs();
|
|
625
855
|
await fs.unlink(this.getActorAlarmPath(actorId));
|
|
626
856
|
} catch (err: any) {
|
|
627
857
|
if (err?.code !== "ENOENT") {
|
|
@@ -656,7 +886,7 @@ export class FileSystemGlobalState {
|
|
|
656
886
|
}
|
|
657
887
|
|
|
658
888
|
invariant(loaded.actor, "actor should be loaded after wake");
|
|
659
|
-
await loaded.actor.
|
|
889
|
+
await loaded.actor.onAlarm();
|
|
660
890
|
} catch (err) {
|
|
661
891
|
logger().error({
|
|
662
892
|
msg: "failed to handle alarm",
|
|
@@ -668,6 +898,8 @@ export class FileSystemGlobalState {
|
|
|
668
898
|
}
|
|
669
899
|
|
|
670
900
|
getOrCreateInspectorAccessToken(): string {
|
|
901
|
+
const path = getNodePath();
|
|
902
|
+
const fsSync = getNodeFsSync();
|
|
671
903
|
const tokenPath = path.join(this.#storagePath, "inspector-token");
|
|
672
904
|
if (fsSync.existsSync(tokenPath)) {
|
|
673
905
|
return fsSync.readFileSync(tokenPath, "utf-8");
|
|
@@ -683,6 +915,7 @@ export class FileSystemGlobalState {
|
|
|
683
915
|
*/
|
|
684
916
|
#cleanupTempFilesSync(): void {
|
|
685
917
|
try {
|
|
918
|
+
const fsSync = getNodeFsSync();
|
|
686
919
|
const files = fsSync.readdirSync(this.#stateDir);
|
|
687
920
|
const tempFiles = files.filter((f) => f.includes(".tmp."));
|
|
688
921
|
|
|
@@ -690,6 +923,7 @@ export class FileSystemGlobalState {
|
|
|
690
923
|
|
|
691
924
|
for (const tempFile of tempFiles) {
|
|
692
925
|
try {
|
|
926
|
+
const path = getNodePath();
|
|
693
927
|
const fullPath = path.join(this.#stateDir, tempFile);
|
|
694
928
|
const stat = fsSync.statSync(fullPath);
|
|
695
929
|
|
|
@@ -716,4 +950,160 @@ export class FileSystemGlobalState {
|
|
|
716
950
|
});
|
|
717
951
|
}
|
|
718
952
|
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Batch put KV entries for an actor.
|
|
956
|
+
*/
|
|
957
|
+
async kvBatchPut(
|
|
958
|
+
actorId: string,
|
|
959
|
+
entries: [Uint8Array, Uint8Array][],
|
|
960
|
+
): Promise<void> {
|
|
961
|
+
const entry = await this.loadActor(actorId);
|
|
962
|
+
if (!entry.state) {
|
|
963
|
+
if (this.isActorStopping(actorId)) {
|
|
964
|
+
return;
|
|
965
|
+
} else {
|
|
966
|
+
throw new Error(`Actor ${actorId} state not loaded`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Create a mutable copy of kvStorage
|
|
971
|
+
const newKvStorage = [...entry.state.kvStorage];
|
|
972
|
+
|
|
973
|
+
// Update kvStorage with new entries
|
|
974
|
+
for (const [key, value] of entries) {
|
|
975
|
+
// Find existing entry with the same key
|
|
976
|
+
const existingIndex = newKvStorage.findIndex((e) =>
|
|
977
|
+
arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
if (existingIndex >= 0) {
|
|
981
|
+
// Replace existing entry with new one
|
|
982
|
+
newKvStorage[existingIndex] = {
|
|
983
|
+
key: bufferToArrayBuffer(key),
|
|
984
|
+
value: bufferToArrayBuffer(value),
|
|
985
|
+
};
|
|
986
|
+
} else {
|
|
987
|
+
// Add new entry
|
|
988
|
+
newKvStorage.push({
|
|
989
|
+
key: bufferToArrayBuffer(key),
|
|
990
|
+
value: bufferToArrayBuffer(value),
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Update state with new kvStorage
|
|
996
|
+
entry.state = {
|
|
997
|
+
...entry.state,
|
|
998
|
+
kvStorage: newKvStorage,
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
// Save state to disk
|
|
1002
|
+
await this.writeActor(actorId, entry.generation, entry.state);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Batch get KV entries for an actor.
|
|
1007
|
+
*/
|
|
1008
|
+
async kvBatchGet(
|
|
1009
|
+
actorId: string,
|
|
1010
|
+
keys: Uint8Array[],
|
|
1011
|
+
): Promise<(Uint8Array | null)[]> {
|
|
1012
|
+
const entry = await this.loadActor(actorId);
|
|
1013
|
+
if (!entry.state) {
|
|
1014
|
+
if (this.isActorStopping(actorId)) {
|
|
1015
|
+
throw new Error(`Actor ${actorId} is stopping`);
|
|
1016
|
+
} else {
|
|
1017
|
+
throw new Error(`Actor ${actorId} state not loaded`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const results: (Uint8Array | null)[] = [];
|
|
1022
|
+
for (const key of keys) {
|
|
1023
|
+
// Find entry with the same key
|
|
1024
|
+
const foundEntry = entry.state.kvStorage.find((e) =>
|
|
1025
|
+
arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
if (foundEntry) {
|
|
1029
|
+
results.push(new Uint8Array(foundEntry.value));
|
|
1030
|
+
} else {
|
|
1031
|
+
results.push(null);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return results;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Batch delete KV entries for an actor.
|
|
1039
|
+
*/
|
|
1040
|
+
async kvBatchDelete(actorId: string, keys: Uint8Array[]): Promise<void> {
|
|
1041
|
+
const entry = await this.loadActor(actorId);
|
|
1042
|
+
if (!entry.state) {
|
|
1043
|
+
if (this.isActorStopping(actorId)) {
|
|
1044
|
+
return;
|
|
1045
|
+
} else {
|
|
1046
|
+
throw new Error(`Actor ${actorId} state not loaded`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Create a mutable copy of kvStorage
|
|
1051
|
+
const newKvStorage = [...entry.state.kvStorage];
|
|
1052
|
+
|
|
1053
|
+
// Delete entries from kvStorage
|
|
1054
|
+
for (const key of keys) {
|
|
1055
|
+
const indexToDelete = newKvStorage.findIndex((e) =>
|
|
1056
|
+
arrayBuffersEqual(e.key, bufferToArrayBuffer(key)),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
if (indexToDelete >= 0) {
|
|
1060
|
+
newKvStorage.splice(indexToDelete, 1);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Update state with new kvStorage
|
|
1065
|
+
entry.state = {
|
|
1066
|
+
...entry.state,
|
|
1067
|
+
kvStorage: newKvStorage,
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// Save state to disk
|
|
1071
|
+
await this.writeActor(actorId, entry.generation, entry.state);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* List KV entries with a given prefix for an actor.
|
|
1076
|
+
*/
|
|
1077
|
+
async kvListPrefix(
|
|
1078
|
+
actorId: string,
|
|
1079
|
+
prefix: Uint8Array,
|
|
1080
|
+
): Promise<[Uint8Array, Uint8Array][]> {
|
|
1081
|
+
const entry = await this.loadActor(actorId);
|
|
1082
|
+
if (!entry.state) {
|
|
1083
|
+
if (this.isActorStopping(actorId)) {
|
|
1084
|
+
throw new Error(`Actor ${actorId} is destroying`);
|
|
1085
|
+
} else {
|
|
1086
|
+
throw new Error(`Actor ${actorId} state not loaded`);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const results: [Uint8Array, Uint8Array][] = [];
|
|
1091
|
+
for (const kvEntry of entry.state.kvStorage) {
|
|
1092
|
+
const keyBytes = new Uint8Array(kvEntry.key);
|
|
1093
|
+
// Check if key starts with prefix
|
|
1094
|
+
if (keyBytes.length >= prefix.length) {
|
|
1095
|
+
let hasPrefix = true;
|
|
1096
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
1097
|
+
if (keyBytes[i] !== prefix[i]) {
|
|
1098
|
+
hasPrefix = false;
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (hasPrefix) {
|
|
1103
|
+
results.push([keyBytes, new Uint8Array(kvEntry.value)]);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return results;
|
|
1108
|
+
}
|
|
719
1109
|
}
|