rivetkit 2.0.2 → 2.0.3
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/v1.ts +228 -0
- package/dist/schemas/client-protocol/v1.ts +429 -0
- package/dist/schemas/file-system-driver/v1.ts +102 -0
- package/dist/tsup/actor/errors.cjs +69 -0
- package/dist/tsup/actor/errors.cjs.map +1 -0
- package/dist/tsup/actor/errors.d.cts +143 -0
- package/dist/tsup/actor/errors.d.ts +143 -0
- package/dist/tsup/actor/errors.js +69 -0
- package/dist/tsup/actor/errors.js.map +1 -0
- package/dist/tsup/chunk-2CRLFV6Z.cjs +202 -0
- package/dist/tsup/chunk-2CRLFV6Z.cjs.map +1 -0
- package/dist/tsup/chunk-3H7O2A7I.js +525 -0
- package/dist/tsup/chunk-3H7O2A7I.js.map +1 -0
- package/dist/tsup/chunk-42I3OZ3Q.js +15 -0
- package/dist/tsup/chunk-42I3OZ3Q.js.map +1 -0
- package/dist/tsup/chunk-4NSUQZ2H.js +1790 -0
- package/dist/tsup/chunk-4NSUQZ2H.js.map +1 -0
- package/dist/tsup/chunk-6PDXBYI5.js +132 -0
- package/dist/tsup/chunk-6PDXBYI5.js.map +1 -0
- package/dist/tsup/chunk-6WKQDDUD.cjs +1790 -0
- package/dist/tsup/chunk-6WKQDDUD.cjs.map +1 -0
- package/dist/tsup/chunk-CTBOSFUH.cjs +116 -0
- package/dist/tsup/chunk-CTBOSFUH.cjs.map +1 -0
- package/dist/tsup/chunk-EGVZZFE2.js +2857 -0
- package/dist/tsup/chunk-EGVZZFE2.js.map +1 -0
- package/dist/tsup/chunk-FCCPJNMA.cjs +132 -0
- package/dist/tsup/chunk-FCCPJNMA.cjs.map +1 -0
- package/dist/tsup/chunk-FLMTTN27.js +244 -0
- package/dist/tsup/chunk-FLMTTN27.js.map +1 -0
- package/dist/tsup/chunk-GIR3AFFI.cjs +315 -0
- package/dist/tsup/chunk-GIR3AFFI.cjs.map +1 -0
- package/dist/tsup/chunk-INGJP237.js +315 -0
- package/dist/tsup/chunk-INGJP237.js.map +1 -0
- package/dist/tsup/chunk-KJCJLKRM.js +116 -0
- package/dist/tsup/chunk-KJCJLKRM.js.map +1 -0
- package/dist/tsup/chunk-KUPQZYUQ.cjs +15 -0
- package/dist/tsup/chunk-KUPQZYUQ.cjs.map +1 -0
- package/dist/tsup/chunk-O2MBYIXO.cjs +2857 -0
- package/dist/tsup/chunk-O2MBYIXO.cjs.map +1 -0
- package/dist/tsup/chunk-OGAPU3UG.cjs +525 -0
- package/dist/tsup/chunk-OGAPU3UG.cjs.map +1 -0
- package/dist/tsup/chunk-OV6AYD4S.js +4406 -0
- package/dist/tsup/chunk-OV6AYD4S.js.map +1 -0
- package/dist/tsup/chunk-PO4VLDWA.js +47 -0
- package/dist/tsup/chunk-PO4VLDWA.js.map +1 -0
- package/dist/tsup/chunk-R2OPSKIV.cjs +244 -0
- package/dist/tsup/chunk-R2OPSKIV.cjs.map +1 -0
- package/dist/tsup/chunk-TZJKSBUQ.cjs +47 -0
- package/dist/tsup/chunk-TZJKSBUQ.cjs.map +1 -0
- package/dist/tsup/chunk-UBUC5C3G.cjs +189 -0
- package/dist/tsup/chunk-UBUC5C3G.cjs.map +1 -0
- package/dist/tsup/chunk-UIM22YJL.cjs +4406 -0
- package/dist/tsup/chunk-UIM22YJL.cjs.map +1 -0
- package/dist/tsup/chunk-URVFQMYI.cjs +230 -0
- package/dist/tsup/chunk-URVFQMYI.cjs.map +1 -0
- package/dist/tsup/chunk-UVUPOS46.js +230 -0
- package/dist/tsup/chunk-UVUPOS46.js.map +1 -0
- package/dist/tsup/chunk-VRRHBNJC.js +189 -0
- package/dist/tsup/chunk-VRRHBNJC.js.map +1 -0
- package/dist/tsup/chunk-XFSS33EQ.js +202 -0
- package/dist/tsup/chunk-XFSS33EQ.js.map +1 -0
- package/dist/tsup/client/mod.cjs +32 -0
- package/dist/tsup/client/mod.cjs.map +1 -0
- package/dist/tsup/client/mod.d.cts +26 -0
- package/dist/tsup/client/mod.d.ts +26 -0
- package/dist/tsup/client/mod.js +32 -0
- package/dist/tsup/client/mod.js.map +1 -0
- package/dist/tsup/common/log.cjs +13 -0
- package/dist/tsup/common/log.cjs.map +1 -0
- package/dist/tsup/common/log.d.cts +20 -0
- package/dist/tsup/common/log.d.ts +20 -0
- package/dist/tsup/common/log.js +13 -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/common-CpqORuCq.d.cts +218 -0
- package/dist/tsup/common-CpqORuCq.d.ts +218 -0
- package/dist/tsup/connection-BR_Ve4ku.d.cts +2117 -0
- package/dist/tsup/connection-BwUMoe6n.d.ts +2117 -0
- package/dist/tsup/driver-helpers/mod.cjs +33 -0
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -0
- package/dist/tsup/driver-helpers/mod.d.cts +18 -0
- package/dist/tsup/driver-helpers/mod.d.ts +18 -0
- package/dist/tsup/driver-helpers/mod.js +33 -0
- package/dist/tsup/driver-helpers/mod.js.map +1 -0
- package/dist/tsup/driver-test-suite/mod.cjs +4619 -0
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -0
- package/dist/tsup/driver-test-suite/mod.d.cts +57 -0
- package/dist/tsup/driver-test-suite/mod.d.ts +57 -0
- package/dist/tsup/driver-test-suite/mod.js +4619 -0
- package/dist/tsup/driver-test-suite/mod.js.map +1 -0
- package/dist/tsup/inspector/mod.cjs +53 -0
- package/dist/tsup/inspector/mod.cjs.map +1 -0
- package/dist/tsup/inspector/mod.d.cts +408 -0
- package/dist/tsup/inspector/mod.d.ts +408 -0
- package/dist/tsup/inspector/mod.js +53 -0
- package/dist/tsup/inspector/mod.js.map +1 -0
- package/dist/tsup/mod.cjs +73 -0
- package/dist/tsup/mod.cjs.map +1 -0
- package/dist/tsup/mod.d.cts +100 -0
- package/dist/tsup/mod.d.ts +100 -0
- package/dist/tsup/mod.js +73 -0
- package/dist/tsup/mod.js.map +1 -0
- package/dist/tsup/router-endpoints-AYkXG8Tl.d.cts +66 -0
- package/dist/tsup/router-endpoints-DAbqVFx2.d.ts +66 -0
- package/dist/tsup/test/mod.cjs +21 -0
- package/dist/tsup/test/mod.cjs.map +1 -0
- package/dist/tsup/test/mod.d.cts +27 -0
- package/dist/tsup/test/mod.d.ts +27 -0
- package/dist/tsup/test/mod.js +21 -0
- package/dist/tsup/test/mod.js.map +1 -0
- package/dist/tsup/utils-CT0cv4jd.d.cts +17 -0
- package/dist/tsup/utils-CT0cv4jd.d.ts +17 -0
- package/dist/tsup/utils.cjs +26 -0
- package/dist/tsup/utils.cjs.map +1 -0
- package/dist/tsup/utils.d.cts +36 -0
- package/dist/tsup/utils.d.ts +36 -0
- package/dist/tsup/utils.js +26 -0
- package/dist/tsup/utils.js.map +1 -0
- package/package.json +208 -5
- package/src/actor/action.ts +182 -0
- package/src/actor/config.ts +765 -0
- package/src/actor/connection.ts +260 -0
- package/src/actor/context.ts +171 -0
- package/src/actor/database.ts +23 -0
- package/src/actor/definition.ts +86 -0
- package/src/actor/driver.ts +84 -0
- package/src/actor/errors.ts +360 -0
- package/src/actor/generic-conn-driver.ts +234 -0
- package/src/actor/instance.ts +1800 -0
- package/src/actor/log.ts +15 -0
- package/src/actor/mod.ts +113 -0
- package/src/actor/persisted.ts +42 -0
- package/src/actor/protocol/old.ts +281 -0
- package/src/actor/protocol/serde.ts +131 -0
- package/src/actor/router-endpoints.ts +685 -0
- package/src/actor/router.ts +263 -0
- package/src/actor/schedule.ts +17 -0
- package/src/actor/unstable-react.ts +110 -0
- package/src/actor/utils.ts +98 -0
- package/src/client/actor-common.ts +30 -0
- package/src/client/actor-conn.ts +804 -0
- package/src/client/actor-handle.ts +208 -0
- package/src/client/client.ts +623 -0
- package/src/client/errors.ts +41 -0
- package/src/client/http-client-driver.ts +326 -0
- package/src/client/log.ts +7 -0
- package/src/client/mod.ts +56 -0
- package/src/client/raw-utils.ts +92 -0
- package/src/client/test.ts +44 -0
- package/src/client/utils.ts +150 -0
- package/src/common/eventsource-interface.ts +47 -0
- package/src/common/eventsource.ts +80 -0
- package/src/common/fake-event-source.ts +266 -0
- package/src/common/inline-websocket-adapter2.ts +445 -0
- package/src/common/log-levels.ts +27 -0
- package/src/common/log.ts +139 -0
- package/src/common/logfmt.ts +228 -0
- package/src/common/network.ts +2 -0
- package/src/common/router.ts +87 -0
- package/src/common/utils.ts +322 -0
- package/src/common/versioned-data.ts +95 -0
- package/src/common/websocket-interface.ts +49 -0
- package/src/common/websocket.ts +43 -0
- package/src/driver-helpers/mod.ts +22 -0
- package/src/driver-helpers/utils.ts +17 -0
- package/src/driver-test-suite/log.ts +7 -0
- package/src/driver-test-suite/mod.ts +213 -0
- package/src/driver-test-suite/test-inline-client-driver.ts +402 -0
- package/src/driver-test-suite/tests/action-features.ts +136 -0
- package/src/driver-test-suite/tests/actor-auth.ts +591 -0
- package/src/driver-test-suite/tests/actor-conn-state.ts +249 -0
- package/src/driver-test-suite/tests/actor-conn.ts +349 -0
- package/src/driver-test-suite/tests/actor-driver.ts +25 -0
- package/src/driver-test-suite/tests/actor-error-handling.ts +158 -0
- package/src/driver-test-suite/tests/actor-handle.ts +259 -0
- package/src/driver-test-suite/tests/actor-inline-client.ts +152 -0
- package/src/driver-test-suite/tests/actor-inspector.ts +570 -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-schedule.ts +108 -0
- package/src/driver-test-suite/tests/actor-sleep.ts +413 -0
- package/src/driver-test-suite/tests/actor-state.ts +54 -0
- package/src/driver-test-suite/tests/actor-vars.ts +93 -0
- package/src/driver-test-suite/tests/manager-driver.ts +365 -0
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +226 -0
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +414 -0
- package/src/driver-test-suite/tests/raw-http.ts +347 -0
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +392 -0
- package/src/driver-test-suite/tests/raw-websocket.ts +484 -0
- package/src/driver-test-suite/tests/request-access.ts +244 -0
- package/src/driver-test-suite/utils.ts +68 -0
- package/src/drivers/default.ts +31 -0
- package/src/drivers/engine/actor-driver.ts +360 -0
- package/src/drivers/engine/api-endpoints.ts +128 -0
- package/src/drivers/engine/api-utils.ts +70 -0
- package/src/drivers/engine/config.ts +39 -0
- package/src/drivers/engine/keys.test.ts +266 -0
- package/src/drivers/engine/keys.ts +89 -0
- package/src/drivers/engine/kv.ts +3 -0
- package/src/drivers/engine/log.ts +7 -0
- package/src/drivers/engine/manager-driver.ts +391 -0
- package/src/drivers/engine/mod.ts +36 -0
- package/src/drivers/engine/ws-proxy.ts +170 -0
- package/src/drivers/file-system/actor.ts +91 -0
- package/src/drivers/file-system/global-state.ts +673 -0
- package/src/drivers/file-system/log.ts +7 -0
- package/src/drivers/file-system/manager.ts +306 -0
- package/src/drivers/file-system/mod.ts +48 -0
- package/src/drivers/file-system/utils.ts +109 -0
- package/src/globals.d.ts +6 -0
- package/src/inline-client-driver/log.ts +7 -0
- package/src/inline-client-driver/mod.ts +385 -0
- package/src/inspector/actor.ts +298 -0
- package/src/inspector/config.ts +83 -0
- package/src/inspector/log.ts +5 -0
- package/src/inspector/manager.ts +86 -0
- package/src/inspector/mod.ts +2 -0
- package/src/inspector/protocol/actor.ts +10 -0
- package/src/inspector/protocol/common.ts +196 -0
- package/src/inspector/protocol/manager.ts +10 -0
- package/src/inspector/protocol/mod.ts +2 -0
- package/src/inspector/utils.ts +76 -0
- package/src/manager/auth.ts +121 -0
- package/src/manager/driver.ts +80 -0
- package/src/manager/hono-websocket-adapter.ts +333 -0
- package/src/manager/log.ts +7 -0
- package/src/manager/mod.ts +2 -0
- package/src/manager/protocol/mod.ts +24 -0
- package/src/manager/protocol/query.ts +89 -0
- package/src/manager/router.ts +1792 -0
- package/src/mod.ts +20 -0
- package/src/registry/config.ts +32 -0
- package/src/registry/log.ts +7 -0
- package/src/registry/mod.ts +124 -0
- package/src/registry/run-config.ts +54 -0
- package/src/registry/serve.ts +53 -0
- package/src/schemas/actor-persist/mod.ts +1 -0
- package/src/schemas/actor-persist/versioned.ts +25 -0
- package/src/schemas/client-protocol/mod.ts +1 -0
- package/src/schemas/client-protocol/versioned.ts +63 -0
- package/src/schemas/file-system-driver/mod.ts +1 -0
- package/src/schemas/file-system-driver/versioned.ts +28 -0
- package/src/serde.ts +84 -0
- package/src/test/config.ts +16 -0
- package/src/test/log.ts +7 -0
- package/src/test/mod.ts +153 -0
- package/src/utils.ts +172 -0
- package/README.md +0 -13
|
@@ -0,0 +1,673 @@
|
|
|
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
|
+
import invariant from "invariant";
|
|
6
|
+
import { lookupInRegistry } from "@/actor/definition";
|
|
7
|
+
import { ActorAlreadyExists } from "@/actor/errors";
|
|
8
|
+
import {
|
|
9
|
+
createGenericConnDrivers,
|
|
10
|
+
GenericConnGlobalState,
|
|
11
|
+
} from "@/actor/generic-conn-driver";
|
|
12
|
+
import type { AnyActorInstance } from "@/actor/instance";
|
|
13
|
+
import type { ActorKey } from "@/actor/mod";
|
|
14
|
+
import { generateRandomString } from "@/actor/utils";
|
|
15
|
+
import type { AnyClient } from "@/client/client";
|
|
16
|
+
import {
|
|
17
|
+
type ActorDriver,
|
|
18
|
+
serializeEmptyPersistData,
|
|
19
|
+
} from "@/driver-helpers/mod";
|
|
20
|
+
import type { RegistryConfig } from "@/registry/config";
|
|
21
|
+
import type { RunConfig } from "@/registry/run-config";
|
|
22
|
+
import type * as schema from "@/schemas/file-system-driver/mod";
|
|
23
|
+
import {
|
|
24
|
+
ACTOR_ALARM_VERSIONED,
|
|
25
|
+
ACTOR_STATE_VERSIONED,
|
|
26
|
+
} from "@/schemas/file-system-driver/versioned";
|
|
27
|
+
import {
|
|
28
|
+
bufferToArrayBuffer,
|
|
29
|
+
type LongTimeoutHandle,
|
|
30
|
+
SinglePromiseQueue,
|
|
31
|
+
setLongTimeout,
|
|
32
|
+
stringifyError,
|
|
33
|
+
} from "@/utils";
|
|
34
|
+
import { logger } from "./log";
|
|
35
|
+
import {
|
|
36
|
+
ensureDirectoryExists,
|
|
37
|
+
ensureDirectoryExistsSync,
|
|
38
|
+
getStoragePath,
|
|
39
|
+
} from "./utils";
|
|
40
|
+
|
|
41
|
+
// Actor handler to track running instances
|
|
42
|
+
|
|
43
|
+
interface ActorEntry {
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
state?: schema.ActorState;
|
|
47
|
+
/** Promise for loading the actor state. */
|
|
48
|
+
loadPromise?: Promise<ActorEntry>;
|
|
49
|
+
|
|
50
|
+
actor?: AnyActorInstance;
|
|
51
|
+
/** Promise for starting the actor. */
|
|
52
|
+
startPromise?: PromiseWithResolvers<void>;
|
|
53
|
+
|
|
54
|
+
genericConnGlobalState: GenericConnGlobalState;
|
|
55
|
+
|
|
56
|
+
alarmTimeout?: LongTimeoutHandle;
|
|
57
|
+
/** The timestamp currently scheduled for this actor's alarm (ms since epoch). */
|
|
58
|
+
alarmTimestamp?: number;
|
|
59
|
+
|
|
60
|
+
/** Resolver for pending write operations that need to be notified when any write completes */
|
|
61
|
+
pendingWriteResolver?: PromiseWithResolvers<void>;
|
|
62
|
+
|
|
63
|
+
/** If the actor has been removed by destroy or sleep. */
|
|
64
|
+
removed: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Global state for the file system driver
|
|
69
|
+
*/
|
|
70
|
+
export class FileSystemGlobalState {
|
|
71
|
+
#storagePath: string;
|
|
72
|
+
#stateDir: string;
|
|
73
|
+
#dbsDir: string;
|
|
74
|
+
#alarmsDir: string;
|
|
75
|
+
|
|
76
|
+
#persist: boolean;
|
|
77
|
+
#actors = new Map<string, ActorEntry>();
|
|
78
|
+
#actorCountOnStartup: number = 0;
|
|
79
|
+
|
|
80
|
+
#runnerParams?: {
|
|
81
|
+
registryConfig: RegistryConfig;
|
|
82
|
+
runConfig: RunConfig;
|
|
83
|
+
inlineClient: AnyClient;
|
|
84
|
+
actorDriver: ActorDriver;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
get storagePath() {
|
|
88
|
+
return this.#storagePath;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get actorCountOnStartup() {
|
|
92
|
+
return this.#actorCountOnStartup;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
constructor(persist: boolean = true, customPath?: string) {
|
|
96
|
+
this.#persist = persist;
|
|
97
|
+
this.#storagePath = persist ? getStoragePath(customPath) : "/tmp";
|
|
98
|
+
this.#stateDir = path.join(this.#storagePath, "state");
|
|
99
|
+
this.#dbsDir = path.join(this.#storagePath, "databases");
|
|
100
|
+
this.#alarmsDir = path.join(this.#storagePath, "alarms");
|
|
101
|
+
|
|
102
|
+
if (this.#persist) {
|
|
103
|
+
// Ensure storage directories exist synchronously during initialization
|
|
104
|
+
ensureDirectoryExistsSync(this.#stateDir);
|
|
105
|
+
ensureDirectoryExistsSync(this.#dbsDir);
|
|
106
|
+
ensureDirectoryExistsSync(this.#alarmsDir);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const actorIds = fsSync.readdirSync(this.#stateDir);
|
|
110
|
+
this.#actorCountOnStartup = actorIds.length;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger().error("failed to count actors", { error });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
logger().debug("file system driver ready", {
|
|
116
|
+
dir: this.#storagePath,
|
|
117
|
+
actorCount: this.#actorCountOnStartup,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Cleanup stale temp files on startup
|
|
121
|
+
try {
|
|
122
|
+
this.#cleanupTempFilesSync();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
logger().error("failed to cleanup temp files", { error: err });
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
logger().debug("memory driver ready");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getActorStatePath(actorId: string): string {
|
|
132
|
+
return path.join(this.#stateDir, actorId);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getActorDbPath(actorId: string): string {
|
|
136
|
+
return path.join(this.#dbsDir, `${actorId}.db`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getActorAlarmPath(actorId: string): string {
|
|
140
|
+
return path.join(this.#alarmsDir, actorId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async *getActorsIterator(params: {
|
|
144
|
+
cursor?: string;
|
|
145
|
+
}): AsyncGenerator<schema.ActorState> {
|
|
146
|
+
let actorIds = Array.from(this.#actors.keys()).sort();
|
|
147
|
+
|
|
148
|
+
// Check if state directory exists first
|
|
149
|
+
if (fsSync.existsSync(this.#stateDir)) {
|
|
150
|
+
actorIds = fsSync
|
|
151
|
+
.readdirSync(this.#stateDir)
|
|
152
|
+
.filter((id) => !id.includes(".tmp"))
|
|
153
|
+
.sort();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const startIndex = params.cursor ? actorIds.indexOf(params.cursor) + 1 : 0;
|
|
157
|
+
|
|
158
|
+
for (let i = startIndex; i < actorIds.length; i++) {
|
|
159
|
+
const actorId = actorIds[i];
|
|
160
|
+
if (!actorId) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const state = await this.loadActorStateOrError(actorId);
|
|
166
|
+
yield state;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger().error("failed to load actor state", { actorId, error });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Ensures an entry exists for this actor.
|
|
175
|
+
*
|
|
176
|
+
* Used for #createActor and #loadActor.
|
|
177
|
+
*/
|
|
178
|
+
#upsertEntry(actorId: string): ActorEntry {
|
|
179
|
+
let entry = this.#actors.get(actorId);
|
|
180
|
+
if (entry) {
|
|
181
|
+
return entry;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
entry = {
|
|
185
|
+
id: actorId,
|
|
186
|
+
genericConnGlobalState: new GenericConnGlobalState(),
|
|
187
|
+
removed: false,
|
|
188
|
+
};
|
|
189
|
+
this.#actors.set(actorId, entry);
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a new actor and writes to file system.
|
|
195
|
+
*/
|
|
196
|
+
async createActor(
|
|
197
|
+
actorId: string,
|
|
198
|
+
name: string,
|
|
199
|
+
key: ActorKey,
|
|
200
|
+
input: unknown | undefined,
|
|
201
|
+
): Promise<ActorEntry> {
|
|
202
|
+
// TODO: Does not check if actor already exists on fs
|
|
203
|
+
|
|
204
|
+
if (this.#actors.has(actorId)) {
|
|
205
|
+
throw new ActorAlreadyExists(name, key);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const entry = this.#upsertEntry(actorId);
|
|
209
|
+
entry.state = {
|
|
210
|
+
actorId,
|
|
211
|
+
name,
|
|
212
|
+
key,
|
|
213
|
+
createdAt: BigInt(Date.now()),
|
|
214
|
+
persistedData: bufferToArrayBuffer(serializeEmptyPersistData(input)),
|
|
215
|
+
};
|
|
216
|
+
await this.writeActor(actorId, entry.state);
|
|
217
|
+
return entry;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Loads the actor from disk or returns the existing actor entry. This will return an entry even if the actor does not actually exist.
|
|
222
|
+
*/
|
|
223
|
+
async loadActor(actorId: string): Promise<ActorEntry> {
|
|
224
|
+
const entry = this.#upsertEntry(actorId);
|
|
225
|
+
|
|
226
|
+
// Check if already loaded
|
|
227
|
+
if (entry.state) {
|
|
228
|
+
return entry;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// If not persisted, then don't load from FS
|
|
232
|
+
if (!this.#persist) {
|
|
233
|
+
return entry;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// If state is currently being loaded, wait for it
|
|
237
|
+
if (entry.loadPromise) {
|
|
238
|
+
await entry.loadPromise;
|
|
239
|
+
return entry;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Start loading state
|
|
243
|
+
entry.loadPromise = this.loadActorState(entry);
|
|
244
|
+
return entry.loadPromise;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async loadActorState(entry: ActorEntry) {
|
|
248
|
+
const stateFilePath = this.getActorStatePath(entry.id);
|
|
249
|
+
|
|
250
|
+
// Read & parse file
|
|
251
|
+
try {
|
|
252
|
+
const stateData = await fs.readFile(stateFilePath);
|
|
253
|
+
|
|
254
|
+
// Cache the loaded state in handler
|
|
255
|
+
entry.state = ACTOR_STATE_VERSIONED.deserializeWithEmbeddedVersion(
|
|
256
|
+
new Uint8Array(stateData),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return entry;
|
|
260
|
+
} catch (innerError: any) {
|
|
261
|
+
// File does not exist, meaning the actor does not exist
|
|
262
|
+
if (innerError.code === "ENOENT") {
|
|
263
|
+
entry.loadPromise = undefined;
|
|
264
|
+
return entry;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// For other errors, throw
|
|
268
|
+
const error = new Error(`Failed to load actor state: ${innerError}`);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async loadOrCreateActor(
|
|
274
|
+
actorId: string,
|
|
275
|
+
name: string,
|
|
276
|
+
key: ActorKey,
|
|
277
|
+
input: unknown | undefined,
|
|
278
|
+
): Promise<ActorEntry> {
|
|
279
|
+
// Attempt to load actor
|
|
280
|
+
const entry = await this.loadActor(actorId);
|
|
281
|
+
|
|
282
|
+
// If no state for this actor, then create & write state
|
|
283
|
+
if (!entry.state) {
|
|
284
|
+
entry.state = {
|
|
285
|
+
actorId,
|
|
286
|
+
name,
|
|
287
|
+
key: key as readonly string[],
|
|
288
|
+
createdAt: BigInt(Date.now()),
|
|
289
|
+
persistedData: bufferToArrayBuffer(serializeEmptyPersistData(input)),
|
|
290
|
+
};
|
|
291
|
+
await this.writeActor(actorId, entry.state);
|
|
292
|
+
}
|
|
293
|
+
return entry;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async sleepActor(actorId: string) {
|
|
297
|
+
invariant(
|
|
298
|
+
this.#persist,
|
|
299
|
+
"cannot sleep actor with memory driver, must use file system driver",
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const actor = this.#actors.get(actorId);
|
|
303
|
+
invariant(actor, `tried to sleep ${actorId}, does not exist`);
|
|
304
|
+
|
|
305
|
+
// Wait for actor to fully start before stopping it to avoid race conditions
|
|
306
|
+
if (actor.loadPromise) await actor.loadPromise.catch();
|
|
307
|
+
if (actor.startPromise?.promise) await actor.startPromise.promise.catch();
|
|
308
|
+
|
|
309
|
+
// Mark as removed
|
|
310
|
+
actor.removed = true;
|
|
311
|
+
|
|
312
|
+
// Stop actor
|
|
313
|
+
invariant(actor.actor, "actor should be loaded");
|
|
314
|
+
await actor.actor._stop();
|
|
315
|
+
|
|
316
|
+
// Remove from map after stop is complete
|
|
317
|
+
this.#actors.delete(actorId);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Save actor state to disk.
|
|
322
|
+
*/
|
|
323
|
+
async writeActor(actorId: string, state: schema.ActorState): Promise<void> {
|
|
324
|
+
if (!this.#persist) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const entry = this.#actors.get(actorId);
|
|
329
|
+
invariant(entry, "actor entry does not exist");
|
|
330
|
+
|
|
331
|
+
await this.#performWrite(actorId, state);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async setActorAlarm(actorId: string, timestamp: number) {
|
|
335
|
+
const entry = this.#actors.get(actorId);
|
|
336
|
+
invariant(entry, "actor entry does not exist");
|
|
337
|
+
|
|
338
|
+
// Persist alarm to disk
|
|
339
|
+
if (this.#persist) {
|
|
340
|
+
const alarmPath = this.getActorAlarmPath(actorId);
|
|
341
|
+
const tempPath = `${alarmPath}.tmp.${crypto.randomUUID()}`;
|
|
342
|
+
try {
|
|
343
|
+
await ensureDirectoryExists(path.dirname(alarmPath));
|
|
344
|
+
const alarmData: schema.ActorAlarm = {
|
|
345
|
+
actorId,
|
|
346
|
+
timestamp: BigInt(timestamp),
|
|
347
|
+
};
|
|
348
|
+
const data =
|
|
349
|
+
ACTOR_ALARM_VERSIONED.serializeWithEmbeddedVersion(alarmData);
|
|
350
|
+
await fs.writeFile(tempPath, data);
|
|
351
|
+
await fs.rename(tempPath, alarmPath);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
try {
|
|
354
|
+
await fs.unlink(tempPath);
|
|
355
|
+
} catch {}
|
|
356
|
+
logger().error("failed to write alarm", { actorId, error });
|
|
357
|
+
throw new Error(`Failed to write alarm: ${error}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Schedule timeout
|
|
362
|
+
this.#scheduleAlarmTimeout(actorId, timestamp);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Perform the actual write operation with atomic writes
|
|
367
|
+
*/
|
|
368
|
+
async #performWrite(
|
|
369
|
+
actorId: string,
|
|
370
|
+
state: schema.ActorState,
|
|
371
|
+
): Promise<void> {
|
|
372
|
+
const dataPath = this.getActorStatePath(actorId);
|
|
373
|
+
// Generate unique temp filename to prevent any race conditions
|
|
374
|
+
const tempPath = `${dataPath}.tmp.${crypto.randomUUID()}`;
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
// Create directory if needed
|
|
378
|
+
await ensureDirectoryExists(path.dirname(dataPath));
|
|
379
|
+
|
|
380
|
+
// Convert to BARE types for serialization
|
|
381
|
+
const bareState: schema.ActorState = {
|
|
382
|
+
actorId: state.actorId,
|
|
383
|
+
name: state.name,
|
|
384
|
+
key: state.key,
|
|
385
|
+
createdAt: state.createdAt,
|
|
386
|
+
persistedData: state.persistedData,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Perform atomic write
|
|
390
|
+
const serializedState =
|
|
391
|
+
ACTOR_STATE_VERSIONED.serializeWithEmbeddedVersion(bareState);
|
|
392
|
+
await fs.writeFile(tempPath, serializedState);
|
|
393
|
+
await fs.rename(tempPath, dataPath);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
// Cleanup temp file on error
|
|
396
|
+
try {
|
|
397
|
+
await fs.unlink(tempPath);
|
|
398
|
+
} catch {
|
|
399
|
+
// Ignore cleanup errors
|
|
400
|
+
}
|
|
401
|
+
logger().error("failed to save actor state", { actorId, error });
|
|
402
|
+
throw new Error(`Failed to save actor state: ${error}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Call this method after the actor driver has been initiated.
|
|
408
|
+
*
|
|
409
|
+
* This will trigger all initial alarms from the file system.
|
|
410
|
+
*
|
|
411
|
+
* This needs to be sync since DriverConfig.actor is sync
|
|
412
|
+
*/
|
|
413
|
+
onRunnerStart(
|
|
414
|
+
registryConfig: RegistryConfig,
|
|
415
|
+
runConfig: RunConfig,
|
|
416
|
+
inlineClient: AnyClient,
|
|
417
|
+
actorDriver: ActorDriver,
|
|
418
|
+
) {
|
|
419
|
+
if (this.#runnerParams) {
|
|
420
|
+
logger().warn("already called onRunnerStart");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Save runner params for future use
|
|
425
|
+
this.#runnerParams = {
|
|
426
|
+
registryConfig,
|
|
427
|
+
runConfig,
|
|
428
|
+
inlineClient,
|
|
429
|
+
actorDriver,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Load alarms from disk and schedule timeouts
|
|
433
|
+
try {
|
|
434
|
+
this.#loadAlarmsSync();
|
|
435
|
+
} catch (err) {
|
|
436
|
+
logger().error("failed to load alarms on startup", { error: err });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async startActor(
|
|
441
|
+
registryConfig: RegistryConfig,
|
|
442
|
+
runConfig: RunConfig,
|
|
443
|
+
inlineClient: AnyClient,
|
|
444
|
+
actorDriver: ActorDriver,
|
|
445
|
+
actorId: string,
|
|
446
|
+
): Promise<AnyActorInstance> {
|
|
447
|
+
// Get the actor metadata
|
|
448
|
+
const entry = await this.loadActor(actorId);
|
|
449
|
+
if (!entry.state) {
|
|
450
|
+
throw new Error(`Actor does exist and cannot be started: ${actorId}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Actor already starting
|
|
454
|
+
if (entry.startPromise) {
|
|
455
|
+
await entry.startPromise.promise;
|
|
456
|
+
invariant(entry.actor, "actor should have loaded");
|
|
457
|
+
return entry.actor;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Actor already loaded
|
|
461
|
+
if (entry.actor) {
|
|
462
|
+
return entry.actor;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Create start promise
|
|
466
|
+
entry.startPromise = Promise.withResolvers();
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
// Create actor
|
|
470
|
+
const definition = lookupInRegistry(registryConfig, entry.state.name);
|
|
471
|
+
entry.actor = definition.instantiate();
|
|
472
|
+
|
|
473
|
+
// Start actor
|
|
474
|
+
const connDrivers = createGenericConnDrivers(
|
|
475
|
+
entry.genericConnGlobalState,
|
|
476
|
+
);
|
|
477
|
+
await entry.actor.start(
|
|
478
|
+
connDrivers,
|
|
479
|
+
actorDriver,
|
|
480
|
+
inlineClient,
|
|
481
|
+
actorId,
|
|
482
|
+
entry.state.name,
|
|
483
|
+
entry.state.key as string[],
|
|
484
|
+
"unknown",
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Finish
|
|
488
|
+
entry.startPromise.resolve();
|
|
489
|
+
entry.startPromise = undefined;
|
|
490
|
+
|
|
491
|
+
return entry.actor;
|
|
492
|
+
} catch (innerError) {
|
|
493
|
+
const error = new Error(
|
|
494
|
+
`Failed to start actor ${actorId}: ${innerError}`,
|
|
495
|
+
{ cause: innerError },
|
|
496
|
+
);
|
|
497
|
+
entry.startPromise?.reject(error);
|
|
498
|
+
entry.startPromise = undefined;
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async loadActorStateOrError(actorId: string): Promise<schema.ActorState> {
|
|
504
|
+
const state = (await this.loadActor(actorId)).state;
|
|
505
|
+
if (!state) throw new Error(`Actor does not exist: ${actorId}`);
|
|
506
|
+
return state;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
getActorOrError(actorId: string): ActorEntry {
|
|
510
|
+
const entry = this.#actors.get(actorId);
|
|
511
|
+
if (!entry) throw new Error(`No entry for actor: ${actorId}`);
|
|
512
|
+
return entry;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async createDatabase(actorId: string): Promise<string | undefined> {
|
|
516
|
+
return this.getActorDbPath(actorId);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Load all persisted alarms from disk and schedule their timers.
|
|
521
|
+
*/
|
|
522
|
+
#loadAlarmsSync(): void {
|
|
523
|
+
try {
|
|
524
|
+
const files = fsSync.existsSync(this.#alarmsDir)
|
|
525
|
+
? fsSync.readdirSync(this.#alarmsDir)
|
|
526
|
+
: [];
|
|
527
|
+
for (const file of files) {
|
|
528
|
+
// Skip temp files
|
|
529
|
+
if (file.includes(".tmp.")) continue;
|
|
530
|
+
const fullPath = path.join(this.#alarmsDir, file);
|
|
531
|
+
try {
|
|
532
|
+
const buf = fsSync.readFileSync(fullPath);
|
|
533
|
+
const alarmData =
|
|
534
|
+
ACTOR_ALARM_VERSIONED.deserializeWithEmbeddedVersion(
|
|
535
|
+
new Uint8Array(buf),
|
|
536
|
+
);
|
|
537
|
+
const timestamp = Number(alarmData.timestamp);
|
|
538
|
+
if (Number.isFinite(timestamp)) {
|
|
539
|
+
this.#scheduleAlarmTimeout(alarmData.actorId, timestamp);
|
|
540
|
+
} else {
|
|
541
|
+
logger().debug("invalid alarm file contents", { file });
|
|
542
|
+
}
|
|
543
|
+
} catch (err) {
|
|
544
|
+
logger().error("failed to read alarm file", {
|
|
545
|
+
file,
|
|
546
|
+
error: stringifyError(err),
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} catch (err) {
|
|
551
|
+
logger().error("failed to list alarms directory", { error: err });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Schedule an alarm timer for an actor without writing to disk.
|
|
557
|
+
*/
|
|
558
|
+
#scheduleAlarmTimeout(actorId: string, timestamp: number) {
|
|
559
|
+
const entry = this.#upsertEntry(actorId);
|
|
560
|
+
|
|
561
|
+
// If there's already an earlier alarm scheduled, do not override it.
|
|
562
|
+
if (
|
|
563
|
+
entry.alarmTimestamp !== undefined &&
|
|
564
|
+
timestamp >= entry.alarmTimestamp
|
|
565
|
+
) {
|
|
566
|
+
logger().debug("skipping alarm schedule (later than existing)", {
|
|
567
|
+
actorId,
|
|
568
|
+
timestamp,
|
|
569
|
+
current: entry.alarmTimestamp,
|
|
570
|
+
});
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
logger().debug("scheduling alarm", { actorId, timestamp });
|
|
575
|
+
|
|
576
|
+
// Cancel existing timeout and update the current scheduled timestamp
|
|
577
|
+
entry.alarmTimeout?.abort();
|
|
578
|
+
entry.alarmTimestamp = timestamp;
|
|
579
|
+
|
|
580
|
+
const delay = Math.max(0, timestamp - Date.now());
|
|
581
|
+
entry.alarmTimeout = setLongTimeout(async () => {
|
|
582
|
+
// Clear currently scheduled timestamp as this alarm is firing now
|
|
583
|
+
entry.alarmTimestamp = undefined;
|
|
584
|
+
// On trigger: remove persisted alarm file
|
|
585
|
+
if (this.#persist) {
|
|
586
|
+
try {
|
|
587
|
+
await fs.unlink(this.getActorAlarmPath(actorId));
|
|
588
|
+
} catch (err: any) {
|
|
589
|
+
if (err?.code !== "ENOENT") {
|
|
590
|
+
logger().debug("failed to remove alarm file", {
|
|
591
|
+
actorId,
|
|
592
|
+
error: stringifyError(err),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
logger().debug("triggering alarm", { actorId, timestamp });
|
|
600
|
+
|
|
601
|
+
// Ensure actor state exists and start actor if needed
|
|
602
|
+
const loaded = await this.loadActor(actorId);
|
|
603
|
+
if (!loaded.state) throw new Error(`Actor does not exist: ${actorId}`);
|
|
604
|
+
|
|
605
|
+
// Start actor if not already running
|
|
606
|
+
const runnerParams = this.#runnerParams;
|
|
607
|
+
invariant(runnerParams, "missing runner params");
|
|
608
|
+
if (!loaded.actor) {
|
|
609
|
+
await this.startActor(
|
|
610
|
+
runnerParams.registryConfig,
|
|
611
|
+
runnerParams.runConfig,
|
|
612
|
+
runnerParams.inlineClient,
|
|
613
|
+
runnerParams.actorDriver,
|
|
614
|
+
actorId,
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
invariant(loaded.actor, "actor should be loaded after wake");
|
|
619
|
+
await loaded.actor._onAlarm();
|
|
620
|
+
} catch (err) {
|
|
621
|
+
logger().error("failed to handle alarm", {
|
|
622
|
+
actorId,
|
|
623
|
+
error: stringifyError(err),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}, delay);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
getOrCreateInspectorAccessToken(): string {
|
|
630
|
+
const tokenPath = path.join(this.#storagePath, "inspector-token");
|
|
631
|
+
if (fsSync.existsSync(tokenPath)) {
|
|
632
|
+
return fsSync.readFileSync(tokenPath, "utf-8");
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const newToken = generateRandomString();
|
|
636
|
+
fsSync.writeFileSync(tokenPath, newToken);
|
|
637
|
+
return newToken;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Cleanup stale temp files on startup (synchronous)
|
|
642
|
+
*/
|
|
643
|
+
#cleanupTempFilesSync(): void {
|
|
644
|
+
try {
|
|
645
|
+
const files = fsSync.readdirSync(this.#stateDir);
|
|
646
|
+
const tempFiles = files.filter((f) => f.includes(".tmp."));
|
|
647
|
+
|
|
648
|
+
const oneHourAgo = Date.now() - 3600000; // 1 hour in ms
|
|
649
|
+
|
|
650
|
+
for (const tempFile of tempFiles) {
|
|
651
|
+
try {
|
|
652
|
+
const fullPath = path.join(this.#stateDir, tempFile);
|
|
653
|
+
const stat = fsSync.statSync(fullPath);
|
|
654
|
+
|
|
655
|
+
// Remove if older than 1 hour
|
|
656
|
+
if (stat.mtimeMs < oneHourAgo) {
|
|
657
|
+
fsSync.unlinkSync(fullPath);
|
|
658
|
+
logger().info("cleaned up stale temp file", { file: tempFile });
|
|
659
|
+
}
|
|
660
|
+
} catch (err) {
|
|
661
|
+
logger().debug("failed to cleanup temp file", {
|
|
662
|
+
file: tempFile,
|
|
663
|
+
error: err,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
logger().error("failed to read actors directory for cleanup", {
|
|
669
|
+
error: err,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|