@rivetkit/cloudflare-workers 2.0.2 → 2.0.4-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/mod.cjs +793 -366
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +70 -161
- package/dist/mod.d.ts +70 -161
- package/dist/mod.js +804 -377
- package/dist/mod.js.map +1 -1
- package/package.json +9 -5
- package/src/actor-driver.ts +227 -76
- package/src/actor-handler-do.ts +338 -113
- package/src/actor-id.ts +38 -0
- package/src/actor-kv.ts +71 -0
- package/src/config.ts +22 -10
- package/src/global-kv.ts +6 -0
- package/src/handler.ts +103 -52
- package/src/log.ts +2 -4
- package/src/manager-driver.ts +237 -134
- package/src/mod.ts +9 -1
- package/src/websocket.ts +17 -6
package/dist/mod.js
CHANGED
|
@@ -1,266 +1,132 @@
|
|
|
1
|
-
// src/handler.ts
|
|
2
|
-
import { env as env2 } from "cloudflare:workers";
|
|
3
|
-
import { Hono } from "hono";
|
|
4
|
-
|
|
5
1
|
// src/actor-handler-do.ts
|
|
6
|
-
import { DurableObject, env } from "cloudflare:workers";
|
|
7
|
-
import {
|
|
8
|
-
createActorRouter,
|
|
9
|
-
createClientWithDriver,
|
|
10
|
-
createInlineClientDriver
|
|
11
|
-
} from "@rivetkit/core";
|
|
12
|
-
import { serializeEmptyPersistData } from "@rivetkit/core/driver-helpers";
|
|
2
|
+
import { DurableObject, env as env2 } from "cloudflare:workers";
|
|
13
3
|
import invariant2 from "invariant";
|
|
4
|
+
import { createActorRouter, createClientWithDriver as createClientWithDriver2 } from "rivetkit";
|
|
5
|
+
import { getInitialActorKvState } from "rivetkit/driver-helpers";
|
|
14
6
|
|
|
15
7
|
// src/actor-driver.ts
|
|
16
|
-
import {
|
|
17
|
-
createGenericConnDrivers,
|
|
18
|
-
GenericConnGlobalState,
|
|
19
|
-
lookupInRegistry
|
|
20
|
-
} from "@rivetkit/core";
|
|
21
8
|
import invariant from "invariant";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#registryConfig;
|
|
41
|
-
#runConfig;
|
|
42
|
-
#managerDriver;
|
|
43
|
-
#inlineClient;
|
|
44
|
-
#globalState;
|
|
45
|
-
#actors = /* @__PURE__ */ new Map();
|
|
46
|
-
constructor(registryConfig, runConfig, managerDriver, inlineClient, globalState) {
|
|
47
|
-
this.#registryConfig = registryConfig;
|
|
48
|
-
this.#runConfig = runConfig;
|
|
49
|
-
this.#managerDriver = managerDriver;
|
|
50
|
-
this.#inlineClient = inlineClient;
|
|
51
|
-
this.#globalState = globalState;
|
|
52
|
-
}
|
|
53
|
-
#getDOCtx(actorId) {
|
|
54
|
-
return this.#globalState.getDOState(actorId).ctx;
|
|
9
|
+
import { lookupInRegistry } from "rivetkit";
|
|
10
|
+
import { promiseWithResolvers } from "rivetkit/utils";
|
|
11
|
+
|
|
12
|
+
// src/log.ts
|
|
13
|
+
import { getLogger } from "rivetkit/log";
|
|
14
|
+
function logger() {
|
|
15
|
+
return getLogger("driver-cloudflare-workers");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/actor-kv.ts
|
|
19
|
+
function kvGet(sql, key) {
|
|
20
|
+
const cursor = sql.exec(
|
|
21
|
+
"SELECT value FROM _rivetkit_kv_storage WHERE key = ?",
|
|
22
|
+
key
|
|
23
|
+
);
|
|
24
|
+
const result = cursor.raw().next();
|
|
25
|
+
if (!result.done && result.value) {
|
|
26
|
+
return toUint8Array(result.value[0]);
|
|
55
27
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw new Error(`Actor ${actorId} is not initialized - missing key`);
|
|
77
|
-
}
|
|
78
|
-
const definition = lookupInRegistry(this.#registryConfig, name);
|
|
79
|
-
handler.actor = definition.instantiate();
|
|
80
|
-
const connDrivers = createGenericConnDrivers(
|
|
81
|
-
handler.genericConnGlobalState
|
|
82
|
-
);
|
|
83
|
-
await handler.actor.start(
|
|
84
|
-
connDrivers,
|
|
85
|
-
this,
|
|
86
|
-
this.#inlineClient,
|
|
87
|
-
actorId,
|
|
88
|
-
name,
|
|
89
|
-
key,
|
|
90
|
-
"unknown"
|
|
91
|
-
// TODO: Support regions in Cloudflare
|
|
92
|
-
);
|
|
93
|
-
(_a = handler.actorPromise) == null ? void 0 : _a.resolve();
|
|
94
|
-
handler.actorPromise = void 0;
|
|
95
|
-
return handler.actor;
|
|
96
|
-
}
|
|
97
|
-
getGenericConnGlobalState(actorId) {
|
|
98
|
-
const handler = this.#actors.get(actorId);
|
|
99
|
-
if (!handler) {
|
|
100
|
-
throw new Error(`Actor ${actorId} not loaded`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function kvPut(sql, key, value) {
|
|
31
|
+
sql.exec(
|
|
32
|
+
"INSERT OR REPLACE INTO _rivetkit_kv_storage (key, value) VALUES (?, ?)",
|
|
33
|
+
key,
|
|
34
|
+
value
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function kvDelete(sql, key) {
|
|
38
|
+
sql.exec("DELETE FROM _rivetkit_kv_storage WHERE key = ?", key);
|
|
39
|
+
}
|
|
40
|
+
function kvListPrefix(sql, prefix) {
|
|
41
|
+
const cursor = sql.exec("SELECT key, value FROM _rivetkit_kv_storage");
|
|
42
|
+
const entries = [];
|
|
43
|
+
for (const row of cursor.raw()) {
|
|
44
|
+
const key = toUint8Array(row[0]);
|
|
45
|
+
const value = toUint8Array(row[1]);
|
|
46
|
+
if (hasPrefix(key, prefix)) {
|
|
47
|
+
entries.push([key, value]);
|
|
101
48
|
}
|
|
102
|
-
return handler.genericConnGlobalState;
|
|
103
49
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
async writePersistedData(actorId, data) {
|
|
112
|
-
await this.#getDOCtx(actorId).storage.put(KEYS.PERSIST_DATA, data);
|
|
113
|
-
}
|
|
114
|
-
async setAlarm(actor, timestamp) {
|
|
115
|
-
await this.#getDOCtx(actor.id).storage.setAlarm(timestamp);
|
|
50
|
+
return entries;
|
|
51
|
+
}
|
|
52
|
+
function toUint8Array(value) {
|
|
53
|
+
var _a;
|
|
54
|
+
if (value instanceof Uint8Array) {
|
|
55
|
+
return value;
|
|
116
56
|
}
|
|
117
|
-
|
|
118
|
-
return
|
|
57
|
+
if (value instanceof ArrayBuffer) {
|
|
58
|
+
return new Uint8Array(value);
|
|
119
59
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return new CloudflareActorsActorDriver(
|
|
124
|
-
registryConfig,
|
|
125
|
-
runConfig,
|
|
126
|
-
managerDriver,
|
|
127
|
-
inlineClient,
|
|
128
|
-
globalState
|
|
129
|
-
);
|
|
130
|
-
};
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Unexpected SQL value type: ${typeof value} (${(_a = value == null ? void 0 : value.constructor) == null ? void 0 : _a.name})`
|
|
62
|
+
);
|
|
131
63
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return
|
|
64
|
+
function hasPrefix(arr, prefix) {
|
|
65
|
+
if (prefix.length > arr.length) return false;
|
|
66
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
67
|
+
if (arr[i] !== prefix[i]) return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
138
70
|
}
|
|
139
71
|
|
|
140
|
-
// src/
|
|
141
|
-
var
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
72
|
+
// src/global-kv.ts
|
|
73
|
+
var GLOBAL_KV_KEYS = {
|
|
74
|
+
actorMetadata: (actorId) => {
|
|
75
|
+
return `actor:${actorId}:metadata`;
|
|
76
|
+
}
|
|
145
77
|
};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
#initializedPromise;
|
|
152
|
-
#actor;
|
|
153
|
-
async #loadActor() {
|
|
154
|
-
if (!this.#initialized) {
|
|
155
|
-
if (this.#initializedPromise) {
|
|
156
|
-
await this.#initializedPromise.promise;
|
|
157
|
-
} else {
|
|
158
|
-
this.#initializedPromise = Promise.withResolvers();
|
|
159
|
-
const res = await this.ctx.storage.get([
|
|
160
|
-
KEYS.NAME,
|
|
161
|
-
KEYS.KEY,
|
|
162
|
-
KEYS.PERSIST_DATA
|
|
163
|
-
]);
|
|
164
|
-
if (res.get(KEYS.PERSIST_DATA)) {
|
|
165
|
-
const name = res.get(KEYS.NAME);
|
|
166
|
-
if (!name) throw new Error("missing actor name");
|
|
167
|
-
const key = res.get(KEYS.KEY);
|
|
168
|
-
if (!key) throw new Error("missing actor key");
|
|
169
|
-
logger().debug("already initialized", { name, key });
|
|
170
|
-
this.#initialized = { name, key };
|
|
171
|
-
this.#initializedPromise.resolve();
|
|
172
|
-
} else {
|
|
173
|
-
logger().debug("waiting to initialize");
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (this.#actor) {
|
|
178
|
-
return this.#actor;
|
|
179
|
-
}
|
|
180
|
-
if (!this.#initialized) throw new Error("Not initialized");
|
|
181
|
-
const actorId = this.ctx.id.toString();
|
|
182
|
-
globalState.setDOState(actorId, { ctx: this.ctx, env });
|
|
183
|
-
invariant2(runConfig.driver, "runConfig.driver");
|
|
184
|
-
runConfig.driver.actor = createCloudflareActorsActorDriverBuilder(globalState);
|
|
185
|
-
const managerDriver = runConfig.driver.manager(
|
|
186
|
-
registry.config,
|
|
187
|
-
runConfig
|
|
188
|
-
);
|
|
189
|
-
const inlineClient = createClientWithDriver(
|
|
190
|
-
createInlineClientDriver(managerDriver)
|
|
191
|
-
);
|
|
192
|
-
const actorDriver = runConfig.driver.actor(
|
|
193
|
-
registry.config,
|
|
194
|
-
runConfig,
|
|
195
|
-
managerDriver,
|
|
196
|
-
inlineClient
|
|
197
|
-
);
|
|
198
|
-
const actorRouter = createActorRouter(runConfig, actorDriver);
|
|
199
|
-
this.#actor = {
|
|
200
|
-
actorRouter
|
|
201
|
-
};
|
|
202
|
-
await actorDriver.loadActor(actorId);
|
|
203
|
-
return this.#actor;
|
|
204
|
-
}
|
|
205
|
-
/** RPC called by the service that creates the DO to initialize it. */
|
|
206
|
-
async initialize(req) {
|
|
207
|
-
await this.ctx.storage.put({
|
|
208
|
-
[KEYS.NAME]: req.name,
|
|
209
|
-
[KEYS.KEY]: req.key,
|
|
210
|
-
[KEYS.PERSIST_DATA]: serializeEmptyPersistData(req.input)
|
|
211
|
-
});
|
|
212
|
-
this.#initialized = {
|
|
213
|
-
name: req.name,
|
|
214
|
-
key: req.key
|
|
215
|
-
};
|
|
216
|
-
logger().debug("initialized actor", { key: req.key });
|
|
217
|
-
await this.#loadActor();
|
|
218
|
-
}
|
|
219
|
-
async fetch(request) {
|
|
220
|
-
const { actorRouter } = await this.#loadActor();
|
|
221
|
-
const actorId = this.ctx.id.toString();
|
|
222
|
-
return await actorRouter.fetch(request, {
|
|
223
|
-
actorId
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
async alarm() {
|
|
227
|
-
await this.#loadActor();
|
|
228
|
-
const actorId = this.ctx.id.toString();
|
|
229
|
-
invariant2(runConfig.driver, "runConfig.driver");
|
|
230
|
-
const managerDriver = runConfig.driver.manager(
|
|
231
|
-
registry.config,
|
|
232
|
-
runConfig
|
|
233
|
-
);
|
|
234
|
-
const inlineClient = createClientWithDriver(
|
|
235
|
-
createInlineClientDriver(managerDriver)
|
|
236
|
-
);
|
|
237
|
-
const actorDriver = runConfig.driver.actor(
|
|
238
|
-
registry.config,
|
|
239
|
-
runConfig,
|
|
240
|
-
managerDriver,
|
|
241
|
-
inlineClient
|
|
242
|
-
);
|
|
243
|
-
const actor = await actorDriver.loadActor(actorId);
|
|
244
|
-
await actor.onAlarm();
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
}
|
|
78
|
+
|
|
79
|
+
// src/handler.ts
|
|
80
|
+
import { env } from "cloudflare:workers";
|
|
81
|
+
import { createClientWithDriver } from "rivetkit";
|
|
82
|
+
import { buildManagerRouter } from "rivetkit/driver-helpers";
|
|
248
83
|
|
|
249
84
|
// src/config.ts
|
|
250
|
-
import {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
85
|
+
import { z } from "zod/v4";
|
|
86
|
+
var ConfigSchemaBase = z.object({
|
|
87
|
+
/** Path that the Rivet manager API will be mounted. */
|
|
88
|
+
managerPath: z.string().optional().default("/api/rivet"),
|
|
89
|
+
/** Runner key for authentication. */
|
|
90
|
+
runnerKey: z.string().optional(),
|
|
91
|
+
/** Disable the welcome message. */
|
|
92
|
+
noWelcome: z.boolean().optional().default(false),
|
|
93
|
+
fetch: z.custom().optional()
|
|
94
|
+
});
|
|
95
|
+
var ConfigSchema = ConfigSchemaBase.default(
|
|
96
|
+
() => ConfigSchemaBase.parse({})
|
|
97
|
+
);
|
|
255
98
|
|
|
256
99
|
// src/manager-driver.ts
|
|
257
100
|
import {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
101
|
+
WS_PROTOCOL_ACTOR,
|
|
102
|
+
WS_PROTOCOL_CONN_PARAMS,
|
|
103
|
+
WS_PROTOCOL_ENCODING,
|
|
104
|
+
WS_PROTOCOL_STANDARD,
|
|
105
|
+
WS_PROTOCOL_TARGET
|
|
106
|
+
} from "rivetkit/driver-helpers";
|
|
107
|
+
import {
|
|
108
|
+
ActorDuplicateKey,
|
|
109
|
+
ActorNotFound,
|
|
110
|
+
InternalError
|
|
111
|
+
} from "rivetkit/errors";
|
|
112
|
+
import { assertUnreachable } from "rivetkit/utils";
|
|
113
|
+
|
|
114
|
+
// src/actor-id.ts
|
|
115
|
+
function buildActorId(doId, generation) {
|
|
116
|
+
return `${doId}:${generation}`;
|
|
117
|
+
}
|
|
118
|
+
function parseActorId(actorId) {
|
|
119
|
+
const parts = actorId.split(":");
|
|
120
|
+
if (parts.length !== 2) {
|
|
121
|
+
throw new Error(`Invalid actor ID format: ${actorId}`);
|
|
122
|
+
}
|
|
123
|
+
const [doId, generationStr] = parts;
|
|
124
|
+
const generation = parseInt(generationStr, 10);
|
|
125
|
+
if (Number.isNaN(generation)) {
|
|
126
|
+
throw new Error(`Invalid generation number in actor ID: ${actorId}`);
|
|
127
|
+
}
|
|
128
|
+
return [doId, generation];
|
|
129
|
+
}
|
|
264
130
|
|
|
265
131
|
// src/util.ts
|
|
266
132
|
var EMPTY_KEY = "(none)";
|
|
@@ -289,16 +155,6 @@ function serializeKey(key) {
|
|
|
289
155
|
}
|
|
290
156
|
|
|
291
157
|
// src/manager-driver.ts
|
|
292
|
-
var KEYS2 = {
|
|
293
|
-
ACTOR: {
|
|
294
|
-
// Combined key for actor metadata (name and key)
|
|
295
|
-
metadata: (actorId) => `actor:${actorId}:metadata`,
|
|
296
|
-
// Key index function for actor lookup
|
|
297
|
-
keyIndex: (name, key = []) => {
|
|
298
|
-
return `actor_key:${serializeKey(key)}`;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
158
|
var STANDARD_WEBSOCKET_HEADERS = [
|
|
303
159
|
"connection",
|
|
304
160
|
"upgrade",
|
|
@@ -310,45 +166,61 @@ var STANDARD_WEBSOCKET_HEADERS = [
|
|
|
310
166
|
var CloudflareActorsManagerDriver = class {
|
|
311
167
|
async sendRequest(actorId, actorRequest) {
|
|
312
168
|
const env3 = getCloudflareAmbientEnv();
|
|
313
|
-
|
|
169
|
+
const [doId] = parseActorId(actorId);
|
|
170
|
+
logger().debug({
|
|
171
|
+
msg: "sending request to durable object",
|
|
314
172
|
actorId,
|
|
173
|
+
doId,
|
|
315
174
|
method: actorRequest.method,
|
|
316
175
|
url: actorRequest.url
|
|
317
176
|
});
|
|
318
|
-
const id = env3.ACTOR_DO.idFromString(
|
|
177
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
319
178
|
const stub = env3.ACTOR_DO.get(id);
|
|
320
179
|
return await stub.fetch(actorRequest);
|
|
321
180
|
}
|
|
322
181
|
async openWebSocket(path, actorId, encoding, params) {
|
|
323
182
|
const env3 = getCloudflareAmbientEnv();
|
|
324
|
-
|
|
325
|
-
|
|
183
|
+
const [doId] = parseActorId(actorId);
|
|
184
|
+
logger().debug({
|
|
185
|
+
msg: "opening websocket to durable object",
|
|
186
|
+
actorId,
|
|
187
|
+
doId,
|
|
188
|
+
path
|
|
189
|
+
});
|
|
190
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
326
191
|
const stub = env3.ACTOR_DO.get(id);
|
|
192
|
+
const protocols = [];
|
|
193
|
+
protocols.push(WS_PROTOCOL_STANDARD);
|
|
194
|
+
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
195
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
196
|
+
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
197
|
+
if (params) {
|
|
198
|
+
protocols.push(
|
|
199
|
+
`${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
327
202
|
const headers = {
|
|
328
203
|
Upgrade: "websocket",
|
|
329
204
|
Connection: "Upgrade",
|
|
330
|
-
|
|
331
|
-
[HEADER_ENCODING]: encoding
|
|
205
|
+
"sec-websocket-protocol": protocols.join(", ")
|
|
332
206
|
};
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
headers["sec-websocket-protocol"] = "rivetkit";
|
|
337
|
-
const url = `http://actor${path}`;
|
|
338
|
-
logger().debug("rewriting websocket url", {
|
|
339
|
-
from: path,
|
|
340
|
-
to: url
|
|
341
|
-
});
|
|
207
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
208
|
+
const url = `http://actor${normalizedPath}`;
|
|
209
|
+
logger().debug({ msg: "rewriting websocket url", from: path, to: url });
|
|
342
210
|
const response = await stub.fetch(url, {
|
|
343
211
|
headers
|
|
344
212
|
});
|
|
345
213
|
const webSocket = response.webSocket;
|
|
346
214
|
if (!webSocket) {
|
|
347
215
|
throw new InternalError(
|
|
348
|
-
|
|
216
|
+
`missing websocket connection in response from DO
|
|
217
|
+
|
|
218
|
+
Status: ${response.status}
|
|
219
|
+
Response: ${await response.text()}`
|
|
349
220
|
);
|
|
350
221
|
}
|
|
351
|
-
logger().debug(
|
|
222
|
+
logger().debug({
|
|
223
|
+
msg: "durable object websocket connection open",
|
|
352
224
|
actorId
|
|
353
225
|
});
|
|
354
226
|
webSocket.accept();
|
|
@@ -360,18 +232,26 @@ var CloudflareActorsManagerDriver = class {
|
|
|
360
232
|
}, 0);
|
|
361
233
|
return webSocket;
|
|
362
234
|
}
|
|
235
|
+
async buildGatewayUrl(actorId) {
|
|
236
|
+
return `http://actor/gateway/${encodeURIComponent(actorId)}`;
|
|
237
|
+
}
|
|
363
238
|
async proxyRequest(c, actorRequest, actorId) {
|
|
364
|
-
|
|
239
|
+
const env3 = getCloudflareAmbientEnv();
|
|
240
|
+
const [doId] = parseActorId(actorId);
|
|
241
|
+
logger().debug({
|
|
242
|
+
msg: "forwarding request to durable object",
|
|
365
243
|
actorId,
|
|
244
|
+
doId,
|
|
366
245
|
method: actorRequest.method,
|
|
367
246
|
url: actorRequest.url
|
|
368
247
|
});
|
|
369
|
-
const id =
|
|
370
|
-
const stub =
|
|
248
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
249
|
+
const stub = env3.ACTOR_DO.get(id);
|
|
371
250
|
return await stub.fetch(actorRequest);
|
|
372
251
|
}
|
|
373
|
-
async proxyWebSocket(c, path, actorId, encoding, params
|
|
374
|
-
logger().debug(
|
|
252
|
+
async proxyWebSocket(c, path, actorId, encoding, params) {
|
|
253
|
+
logger().debug({
|
|
254
|
+
msg: "forwarding websocket to durable object",
|
|
375
255
|
actorId,
|
|
376
256
|
path
|
|
377
257
|
});
|
|
@@ -383,44 +263,72 @@ var CloudflareActorsManagerDriver = class {
|
|
|
383
263
|
}
|
|
384
264
|
const newUrl = new URL(`http://actor${path}`);
|
|
385
265
|
const actorRequest = new Request(newUrl, c.req.raw);
|
|
386
|
-
logger().debug(
|
|
266
|
+
logger().debug({
|
|
267
|
+
msg: "rewriting websocket url",
|
|
387
268
|
from: c.req.url,
|
|
388
269
|
to: actorRequest.url
|
|
389
270
|
});
|
|
390
271
|
const headerKeys = [];
|
|
391
|
-
actorRequest.headers.forEach((v, k) =>
|
|
272
|
+
actorRequest.headers.forEach((v, k) => {
|
|
273
|
+
headerKeys.push(k);
|
|
274
|
+
});
|
|
392
275
|
for (const k of headerKeys) {
|
|
393
276
|
if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) {
|
|
394
277
|
actorRequest.headers.delete(k);
|
|
395
278
|
}
|
|
396
279
|
}
|
|
397
|
-
|
|
398
|
-
|
|
280
|
+
const protocols = [];
|
|
281
|
+
protocols.push(WS_PROTOCOL_STANDARD);
|
|
282
|
+
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
283
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
284
|
+
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
399
285
|
if (params) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
actorRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData));
|
|
286
|
+
protocols.push(
|
|
287
|
+
`${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`
|
|
288
|
+
);
|
|
404
289
|
}
|
|
405
|
-
|
|
406
|
-
|
|
290
|
+
actorRequest.headers.set(
|
|
291
|
+
"sec-websocket-protocol",
|
|
292
|
+
protocols.join(", ")
|
|
293
|
+
);
|
|
294
|
+
const env3 = getCloudflareAmbientEnv();
|
|
295
|
+
const [doId] = parseActorId(actorId);
|
|
296
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
297
|
+
const stub = env3.ACTOR_DO.get(id);
|
|
407
298
|
return await stub.fetch(actorRequest);
|
|
408
299
|
}
|
|
409
300
|
async getForId({
|
|
410
301
|
c,
|
|
302
|
+
name,
|
|
411
303
|
actorId
|
|
412
304
|
}) {
|
|
413
305
|
const env3 = getCloudflareAmbientEnv();
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
306
|
+
const [doId, expectedGeneration] = parseActorId(actorId);
|
|
307
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
308
|
+
const stub = env3.ACTOR_DO.get(id);
|
|
309
|
+
const result = await stub.getMetadata();
|
|
310
|
+
if (!result) {
|
|
311
|
+
logger().debug({
|
|
312
|
+
msg: "getForId: actor not found",
|
|
313
|
+
actorId
|
|
314
|
+
});
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
if (result.actorId !== actorId) {
|
|
318
|
+
logger().debug({
|
|
319
|
+
msg: "getForId: generation mismatch",
|
|
320
|
+
requestedActorId: actorId,
|
|
321
|
+
actualActorId: result.actorId
|
|
322
|
+
});
|
|
418
323
|
return void 0;
|
|
419
324
|
}
|
|
325
|
+
if (result.destroying) {
|
|
326
|
+
throw new ActorNotFound(actorId);
|
|
327
|
+
}
|
|
420
328
|
return {
|
|
421
|
-
actorId,
|
|
422
|
-
name:
|
|
423
|
-
key:
|
|
329
|
+
actorId: result.actorId,
|
|
330
|
+
name: result.name,
|
|
331
|
+
key: result.key
|
|
424
332
|
};
|
|
425
333
|
}
|
|
426
334
|
async getWithKey({
|
|
@@ -429,33 +337,68 @@ var CloudflareActorsManagerDriver = class {
|
|
|
429
337
|
key
|
|
430
338
|
}) {
|
|
431
339
|
const env3 = getCloudflareAmbientEnv();
|
|
432
|
-
logger().debug("getWithKey: searching for actor",
|
|
340
|
+
logger().debug({ msg: "getWithKey: searching for actor", name, key });
|
|
433
341
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (
|
|
439
|
-
logger().debug(
|
|
342
|
+
const doId = env3.ACTOR_DO.idFromName(nameKeyString).toString();
|
|
343
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
344
|
+
const stub = env3.ACTOR_DO.get(id);
|
|
345
|
+
const result = await stub.getMetadata();
|
|
346
|
+
if (result) {
|
|
347
|
+
logger().debug({
|
|
348
|
+
msg: "getWithKey: found actor with matching name and key",
|
|
349
|
+
actorId: result.actorId,
|
|
350
|
+
name: result.name,
|
|
351
|
+
key: result.key
|
|
352
|
+
});
|
|
353
|
+
return {
|
|
354
|
+
actorId: result.actorId,
|
|
355
|
+
name: result.name,
|
|
356
|
+
key: result.key
|
|
357
|
+
};
|
|
358
|
+
} else {
|
|
359
|
+
logger().debug({
|
|
360
|
+
msg: "getWithKey: no actor found with matching name and key",
|
|
440
361
|
name,
|
|
441
362
|
key,
|
|
442
|
-
|
|
363
|
+
doId
|
|
443
364
|
});
|
|
444
365
|
return void 0;
|
|
445
366
|
}
|
|
446
|
-
|
|
447
|
-
|
|
367
|
+
}
|
|
368
|
+
async getOrCreateWithKey({
|
|
369
|
+
c,
|
|
370
|
+
name,
|
|
371
|
+
key,
|
|
372
|
+
input
|
|
373
|
+
}) {
|
|
374
|
+
const env3 = getCloudflareAmbientEnv();
|
|
375
|
+
const nameKeyString = serializeNameAndKey(name, key);
|
|
376
|
+
const doId = env3.ACTOR_DO.idFromName(nameKeyString);
|
|
377
|
+
const actor = env3.ACTOR_DO.get(doId);
|
|
378
|
+
const result = await actor.create({
|
|
448
379
|
name,
|
|
449
|
-
key
|
|
380
|
+
key,
|
|
381
|
+
input,
|
|
382
|
+
allowExisting: true
|
|
450
383
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
384
|
+
if ("success" in result) {
|
|
385
|
+
const { actorId, created } = result.success;
|
|
386
|
+
logger().debug({
|
|
387
|
+
msg: "getOrCreateWithKey result",
|
|
388
|
+
actorId,
|
|
389
|
+
name,
|
|
390
|
+
key,
|
|
391
|
+
created
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
actorId,
|
|
395
|
+
name,
|
|
396
|
+
key
|
|
397
|
+
};
|
|
398
|
+
} else if ("error" in result) {
|
|
399
|
+
throw new Error(`Error: ${JSON.stringify(result.error)}`);
|
|
457
400
|
} else {
|
|
458
|
-
|
|
401
|
+
assertUnreachable(result);
|
|
459
402
|
}
|
|
460
403
|
}
|
|
461
404
|
async createActor({
|
|
@@ -465,50 +408,62 @@ var CloudflareActorsManagerDriver = class {
|
|
|
465
408
|
input
|
|
466
409
|
}) {
|
|
467
410
|
const env3 = getCloudflareAmbientEnv();
|
|
468
|
-
const existingActor = await this.getWithKey({ c, name, key });
|
|
469
|
-
if (existingActor) {
|
|
470
|
-
throw new ActorAlreadyExists(name, key);
|
|
471
|
-
}
|
|
472
411
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
473
412
|
const doId = env3.ACTOR_DO.idFromName(nameKeyString);
|
|
474
|
-
const actorId = doId.toString();
|
|
475
413
|
const actor = env3.ACTOR_DO.get(doId);
|
|
476
|
-
await actor.
|
|
414
|
+
const result = await actor.create({
|
|
477
415
|
name,
|
|
478
416
|
key,
|
|
479
|
-
input
|
|
417
|
+
input,
|
|
418
|
+
allowExisting: false
|
|
480
419
|
});
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
420
|
+
if ("success" in result) {
|
|
421
|
+
const { actorId } = result.success;
|
|
422
|
+
return {
|
|
423
|
+
actorId,
|
|
424
|
+
name,
|
|
425
|
+
key
|
|
426
|
+
};
|
|
427
|
+
} else if ("error" in result) {
|
|
428
|
+
if (result.error.actorAlreadyExists) {
|
|
429
|
+
throw new ActorDuplicateKey(name, key);
|
|
430
|
+
}
|
|
431
|
+
throw new InternalError(
|
|
432
|
+
`Unknown error creating actor: ${JSON.stringify(result.error)}`
|
|
433
|
+
);
|
|
434
|
+
} else {
|
|
435
|
+
assertUnreachable(result);
|
|
436
|
+
}
|
|
492
437
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
type: "json"
|
|
438
|
+
async listActors({ c, name }) {
|
|
439
|
+
logger().warn({
|
|
440
|
+
msg: "listActors not fully implemented for Cloudflare Workers",
|
|
441
|
+
name
|
|
498
442
|
});
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
displayInformation() {
|
|
502
446
|
return {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
447
|
+
properties: {
|
|
448
|
+
Driver: "Cloudflare Workers"
|
|
449
|
+
}
|
|
506
450
|
};
|
|
507
451
|
}
|
|
452
|
+
setGetUpgradeWebSocket() {
|
|
453
|
+
}
|
|
454
|
+
async kvGet(actorId, key) {
|
|
455
|
+
const env3 = getCloudflareAmbientEnv();
|
|
456
|
+
const [doId] = parseActorId(actorId);
|
|
457
|
+
const id = env3.ACTOR_DO.idFromString(doId);
|
|
458
|
+
const stub = env3.ACTOR_DO.get(id);
|
|
459
|
+
const value = await stub.managerKvGet(key);
|
|
460
|
+
return value !== null ? new TextDecoder().decode(value) : null;
|
|
461
|
+
}
|
|
508
462
|
};
|
|
509
463
|
|
|
510
464
|
// src/websocket.ts
|
|
511
465
|
import { defineWebSocketHelper, WSContext } from "hono/ws";
|
|
466
|
+
import { WS_PROTOCOL_STANDARD as WS_PROTOCOL_STANDARD2 } from "rivetkit/driver-helpers";
|
|
512
467
|
var upgradeWebSocket = defineWebSocketHelper(async (c, events) => {
|
|
513
468
|
var _a, _b;
|
|
514
469
|
const upgradeHeader = c.req.header("Upgrade");
|
|
@@ -559,60 +514,532 @@ var upgradeWebSocket = defineWebSocketHelper(async (c, events) => {
|
|
|
559
514
|
}
|
|
560
515
|
(_a = server.accept) == null ? void 0 : _a.call(server);
|
|
561
516
|
(_b = events.onOpen) == null ? void 0 : _b.call(events, new Event("open"), wsContext);
|
|
517
|
+
const headers = {};
|
|
518
|
+
const protocols = c.req.header("Sec-WebSocket-Protocol");
|
|
519
|
+
if (typeof protocols === "string" && protocols.split(",").map((x) => x.trim()).includes(WS_PROTOCOL_STANDARD2)) {
|
|
520
|
+
headers["Sec-WebSocket-Protocol"] = WS_PROTOCOL_STANDARD2;
|
|
521
|
+
}
|
|
562
522
|
return new Response(null, {
|
|
563
523
|
status: 101,
|
|
564
|
-
headers
|
|
565
|
-
// HACK: Required in order for Cloudflare to not error with "Network connection lost"
|
|
566
|
-
//
|
|
567
|
-
// This bug undocumented. Cannot easily reproduce outside of RivetKit.
|
|
568
|
-
"Sec-WebSocket-Protocol": "rivetkit"
|
|
569
|
-
},
|
|
524
|
+
headers,
|
|
570
525
|
webSocket: client
|
|
571
526
|
});
|
|
572
527
|
});
|
|
573
528
|
|
|
574
529
|
// src/handler.ts
|
|
575
530
|
function getCloudflareAmbientEnv() {
|
|
576
|
-
return
|
|
577
|
-
}
|
|
578
|
-
function createServerHandler(registry, inputConfig) {
|
|
579
|
-
const { createHandler } = createServer(registry, inputConfig);
|
|
580
|
-
return createHandler();
|
|
531
|
+
return env;
|
|
581
532
|
}
|
|
582
|
-
function
|
|
533
|
+
function createInlineClient(registry, inputConfig) {
|
|
534
|
+
inputConfig = { ...inputConfig, runnerKey: "" };
|
|
583
535
|
const config = ConfigSchema.parse(inputConfig);
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
536
|
+
const ActorHandler = createActorDurableObject(
|
|
537
|
+
registry,
|
|
538
|
+
() => upgradeWebSocket
|
|
539
|
+
);
|
|
540
|
+
registry.config.noWelcome = true;
|
|
541
|
+
registry.config.inspector = {
|
|
542
|
+
enabled: false,
|
|
543
|
+
token: () => ""
|
|
544
|
+
};
|
|
545
|
+
registry.config.managerBasePath = "/";
|
|
546
|
+
const parsedConfig = registry.parseConfig();
|
|
547
|
+
const managerDriver = new CloudflareActorsManagerDriver();
|
|
548
|
+
const { router } = buildManagerRouter(
|
|
549
|
+
parsedConfig,
|
|
550
|
+
managerDriver,
|
|
551
|
+
() => upgradeWebSocket
|
|
552
|
+
);
|
|
553
|
+
const client = createClientWithDriver(managerDriver);
|
|
554
|
+
return { client, fetch: router.fetch.bind(router), config, ActorHandler };
|
|
555
|
+
}
|
|
556
|
+
function createHandler(registry, inputConfig) {
|
|
557
|
+
const { client, fetch, config, ActorHandler } = createInlineClient(
|
|
558
|
+
registry,
|
|
559
|
+
inputConfig
|
|
560
|
+
);
|
|
561
|
+
const handler = {
|
|
562
|
+
fetch: async (request, cfEnv, ctx) => {
|
|
563
|
+
const url = new URL(request.url);
|
|
564
|
+
const env3 = Object.assign({ RIVET: client }, cfEnv);
|
|
565
|
+
if (url.pathname.startsWith(config.managerPath)) {
|
|
566
|
+
const strippedPath = url.pathname.substring(
|
|
567
|
+
config.managerPath.length
|
|
568
|
+
);
|
|
569
|
+
url.pathname = strippedPath;
|
|
570
|
+
const modifiedRequest = new Request(url.toString(), request);
|
|
571
|
+
return fetch(modifiedRequest, env3, ctx);
|
|
572
|
+
}
|
|
573
|
+
if (config.fetch) {
|
|
574
|
+
return config.fetch(request, env3, ctx);
|
|
575
|
+
} else {
|
|
576
|
+
return new Response(
|
|
577
|
+
"This is a RivetKit server.\n\nLearn more at https://rivet.dev\n",
|
|
578
|
+
{ status: 200 }
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
return { handler, ActorHandler };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/actor-driver.ts
|
|
587
|
+
var CloudflareDurableObjectGlobalState = class {
|
|
588
|
+
// Map of actor ID -> DO state
|
|
589
|
+
#dos = /* @__PURE__ */ new Map();
|
|
590
|
+
// WeakMap of DO state -> ActorGlobalState for proper GC
|
|
591
|
+
#actors = /* @__PURE__ */ new WeakMap();
|
|
592
|
+
getDOState(doId) {
|
|
593
|
+
const state = this.#dos.get(doId);
|
|
594
|
+
invariant(
|
|
595
|
+
state !== void 0,
|
|
596
|
+
"durable object state not in global state"
|
|
597
|
+
);
|
|
598
|
+
return state;
|
|
599
|
+
}
|
|
600
|
+
setDOState(doId, state) {
|
|
601
|
+
this.#dos.set(doId, state);
|
|
602
|
+
}
|
|
603
|
+
getActorState(ctx) {
|
|
604
|
+
return this.#actors.get(ctx);
|
|
605
|
+
}
|
|
606
|
+
setActorState(ctx, actorState) {
|
|
607
|
+
this.#actors.set(ctx, actorState);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var ActorGlobalState = class {
|
|
611
|
+
// Initialization state
|
|
612
|
+
initialized;
|
|
613
|
+
// Loaded actor state
|
|
614
|
+
actor;
|
|
615
|
+
actorInstance;
|
|
616
|
+
actorPromise;
|
|
617
|
+
/**
|
|
618
|
+
* Indicates if `startDestroy` has been called.
|
|
619
|
+
*
|
|
620
|
+
* This is stored in memory instead of SQLite since the destroy may be cancelled.
|
|
621
|
+
*
|
|
622
|
+
* See the corresponding `destroyed` property in SQLite metadata.
|
|
623
|
+
*/
|
|
624
|
+
destroying = false;
|
|
625
|
+
reset() {
|
|
626
|
+
this.initialized = void 0;
|
|
627
|
+
this.actor = void 0;
|
|
628
|
+
this.actorInstance = void 0;
|
|
629
|
+
this.actorPromise = void 0;
|
|
630
|
+
this.destroying = false;
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
var CloudflareActorsActorDriver = class {
|
|
634
|
+
#registryConfig;
|
|
635
|
+
#managerDriver;
|
|
636
|
+
#inlineClient;
|
|
637
|
+
#globalState;
|
|
638
|
+
constructor(registryConfig, managerDriver, inlineClient, globalState) {
|
|
639
|
+
this.#registryConfig = registryConfig;
|
|
640
|
+
this.#managerDriver = managerDriver;
|
|
641
|
+
this.#inlineClient = inlineClient;
|
|
642
|
+
this.#globalState = globalState;
|
|
643
|
+
}
|
|
644
|
+
#getDOCtx(actorId) {
|
|
645
|
+
const [doId] = parseActorId(actorId);
|
|
646
|
+
return this.#globalState.getDOState(doId).ctx;
|
|
647
|
+
}
|
|
648
|
+
async loadActor(actorId) {
|
|
649
|
+
var _a;
|
|
650
|
+
const [doId, expectedGeneration] = parseActorId(actorId);
|
|
651
|
+
const doState = this.#globalState.getDOState(doId);
|
|
652
|
+
let actorState = this.#globalState.getActorState(doState.ctx);
|
|
653
|
+
if (actorState == null ? void 0 : actorState.actorInstance) {
|
|
654
|
+
return actorState.actorInstance;
|
|
655
|
+
}
|
|
656
|
+
if (!actorState) {
|
|
657
|
+
actorState = new ActorGlobalState();
|
|
658
|
+
actorState.actorPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled actor promise rejection", reason }));
|
|
659
|
+
this.#globalState.setActorState(doState.ctx, actorState);
|
|
660
|
+
} else if (actorState.actorPromise) {
|
|
661
|
+
await actorState.actorPromise.promise;
|
|
662
|
+
if (!actorState.actorInstance) {
|
|
663
|
+
throw new Error(
|
|
664
|
+
`Actor ${actorId} failed to load in concurrent request`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return actorState.actorInstance;
|
|
668
|
+
}
|
|
669
|
+
const sql = doState.ctx.storage.sql;
|
|
670
|
+
const cursor = sql.exec(
|
|
671
|
+
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata LIMIT 1"
|
|
672
|
+
);
|
|
673
|
+
const result = cursor.raw().next();
|
|
674
|
+
if (result.done || !result.value) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
`Actor ${actorId} is not initialized - missing metadata`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
const name = result.value[0];
|
|
680
|
+
const key = JSON.parse(result.value[1]);
|
|
681
|
+
const destroyed = result.value[2];
|
|
682
|
+
const generation = result.value[3];
|
|
683
|
+
if (destroyed) {
|
|
684
|
+
throw new Error(`Actor ${actorId} is destroyed`);
|
|
685
|
+
}
|
|
686
|
+
if (generation !== expectedGeneration) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
`Actor ${actorId} generation mismatch - expected ${expectedGeneration}, got ${generation}`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const definition = lookupInRegistry(this.#registryConfig, name);
|
|
692
|
+
actorState.actorInstance = definition.instantiate();
|
|
693
|
+
await actorState.actorInstance.start(
|
|
694
|
+
this,
|
|
695
|
+
this.#inlineClient,
|
|
696
|
+
actorId,
|
|
697
|
+
name,
|
|
698
|
+
key,
|
|
699
|
+
"unknown"
|
|
700
|
+
// TODO: Support regions in Cloudflare
|
|
701
|
+
);
|
|
702
|
+
(_a = actorState.actorPromise) == null ? void 0 : _a.resolve();
|
|
703
|
+
actorState.actorPromise = void 0;
|
|
704
|
+
return actorState.actorInstance;
|
|
705
|
+
}
|
|
706
|
+
getContext(actorId) {
|
|
707
|
+
const [doId] = parseActorId(actorId);
|
|
708
|
+
const state = this.#globalState.getDOState(doId);
|
|
709
|
+
return { state: state.ctx };
|
|
710
|
+
}
|
|
711
|
+
async setAlarm(actor, timestamp) {
|
|
712
|
+
await this.#getDOCtx(actor.id).storage.setAlarm(timestamp);
|
|
713
|
+
}
|
|
714
|
+
async getDatabase(actorId) {
|
|
715
|
+
return this.#getDOCtx(actorId).storage.sql;
|
|
716
|
+
}
|
|
717
|
+
// Batch KV operations
|
|
718
|
+
async kvBatchPut(actorId, entries) {
|
|
719
|
+
const sql = this.#getDOCtx(actorId).storage.sql;
|
|
720
|
+
for (const [key, value] of entries) {
|
|
721
|
+
kvPut(sql, key, value);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async kvBatchGet(actorId, keys) {
|
|
725
|
+
const sql = this.#getDOCtx(actorId).storage.sql;
|
|
726
|
+
const results = [];
|
|
727
|
+
for (const key of keys) {
|
|
728
|
+
results.push(kvGet(sql, key));
|
|
729
|
+
}
|
|
730
|
+
return results;
|
|
731
|
+
}
|
|
732
|
+
async kvBatchDelete(actorId, keys) {
|
|
733
|
+
const sql = this.#getDOCtx(actorId).storage.sql;
|
|
734
|
+
for (const key of keys) {
|
|
735
|
+
kvDelete(sql, key);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async kvListPrefix(actorId, prefix) {
|
|
739
|
+
const sql = this.#getDOCtx(actorId).storage.sql;
|
|
740
|
+
return kvListPrefix(sql, prefix);
|
|
741
|
+
}
|
|
742
|
+
startDestroy(actorId) {
|
|
743
|
+
const [doId, generation] = parseActorId(actorId);
|
|
744
|
+
const doState = this.#globalState.getDOState(doId);
|
|
745
|
+
const actorState = this.#globalState.getActorState(doState.ctx);
|
|
746
|
+
if (!(actorState == null ? void 0 : actorState.actorInstance)) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (actorState.destroying) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
actorState.destroying = true;
|
|
753
|
+
this.#callOnStopAsync(actorId, doId, actorState.actorInstance);
|
|
754
|
+
}
|
|
755
|
+
async #callOnStopAsync(actorId, doId, actor) {
|
|
756
|
+
await actor.onStop("destroy");
|
|
757
|
+
const doState = this.#globalState.getDOState(doId);
|
|
758
|
+
const sql = doState.ctx.storage.sql;
|
|
759
|
+
sql.exec("UPDATE _rivetkit_metadata SET destroyed = 1 WHERE 1=1");
|
|
760
|
+
sql.exec("DELETE FROM _rivetkit_kv_storage");
|
|
761
|
+
await doState.ctx.storage.deleteAlarm();
|
|
762
|
+
const env3 = getCloudflareAmbientEnv();
|
|
763
|
+
doState.ctx.waitUntil(
|
|
764
|
+
env3.ACTOR_KV.delete(GLOBAL_KV_KEYS.actorMetadata(actorId))
|
|
765
|
+
);
|
|
766
|
+
const actorHandle = this.#globalState.getActorState(doState.ctx);
|
|
767
|
+
actorHandle == null ? void 0 : actorHandle.reset();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
function createCloudflareActorsActorDriverBuilder(globalState) {
|
|
771
|
+
return (config, managerDriver, inlineClient) => {
|
|
772
|
+
return new CloudflareActorsActorDriver(
|
|
773
|
+
config,
|
|
774
|
+
managerDriver,
|
|
775
|
+
inlineClient,
|
|
776
|
+
globalState
|
|
777
|
+
);
|
|
595
778
|
};
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/actor-handler-do.ts
|
|
782
|
+
function createActorDurableObject(registry, getUpgradeWebSocket) {
|
|
783
|
+
const globalState = new CloudflareDurableObjectGlobalState();
|
|
784
|
+
const parsedConfig = registry.parseConfig();
|
|
785
|
+
return class ActorHandler extends DurableObject {
|
|
786
|
+
/**
|
|
787
|
+
* This holds a strong reference to ActorGlobalState.
|
|
788
|
+
* CloudflareDurableObjectGlobalState holds a weak reference so we can
|
|
789
|
+
* access it elsewhere.
|
|
790
|
+
**/
|
|
791
|
+
#state;
|
|
792
|
+
constructor(...args) {
|
|
793
|
+
super(...args);
|
|
794
|
+
this.ctx.storage.sql.exec(`
|
|
795
|
+
CREATE TABLE IF NOT EXISTS _rivetkit_kv_storage(
|
|
796
|
+
key BLOB PRIMARY KEY,
|
|
797
|
+
value BLOB
|
|
798
|
+
);
|
|
799
|
+
`);
|
|
800
|
+
this.ctx.storage.sql.exec(`
|
|
801
|
+
CREATE TABLE IF NOT EXISTS _rivetkit_metadata(
|
|
802
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
803
|
+
name TEXT NOT NULL,
|
|
804
|
+
key TEXT NOT NULL,
|
|
805
|
+
destroyed INTEGER DEFAULT 0,
|
|
806
|
+
generation INTEGER DEFAULT 0
|
|
807
|
+
);
|
|
808
|
+
`);
|
|
809
|
+
const state = globalState.getActorState(this.ctx);
|
|
810
|
+
if (state) {
|
|
811
|
+
this.#state = state;
|
|
812
|
+
} else {
|
|
813
|
+
this.#state = new ActorGlobalState();
|
|
814
|
+
globalState.setActorState(this.ctx, this.#state);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
async #loadActor() {
|
|
818
|
+
var _a, _b;
|
|
819
|
+
invariant2(this.#state, "State should be initialized");
|
|
820
|
+
if (!this.#state.initialized) {
|
|
821
|
+
const cursor = this.ctx.storage.sql.exec(
|
|
822
|
+
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
|
|
823
|
+
);
|
|
824
|
+
const result = cursor.raw().next();
|
|
825
|
+
if (!result.done && result.value) {
|
|
826
|
+
const name = result.value[0];
|
|
827
|
+
const key = JSON.parse(
|
|
828
|
+
result.value[1]
|
|
829
|
+
);
|
|
830
|
+
const destroyed = result.value[2];
|
|
831
|
+
const generation = result.value[3];
|
|
832
|
+
if (!destroyed) {
|
|
833
|
+
logger().debug({
|
|
834
|
+
msg: "already initialized",
|
|
835
|
+
name,
|
|
836
|
+
key,
|
|
837
|
+
generation
|
|
838
|
+
});
|
|
839
|
+
this.#state.initialized = { name, key, generation };
|
|
840
|
+
} else {
|
|
841
|
+
logger().debug("actor is destroyed, cannot load");
|
|
842
|
+
throw new Error("Actor is destroyed");
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
logger().debug("not initialized");
|
|
846
|
+
throw new Error("Actor is not initialized");
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (this.#state.actor) {
|
|
850
|
+
invariant2(
|
|
851
|
+
!this.#state.initialized || this.#state.actor.generation === this.#state.initialized.generation,
|
|
852
|
+
`Stale actor cached: actor generation ${this.#state.actor.generation} != initialized generation ${(_a = this.#state.initialized) == null ? void 0 : _a.generation}. This should not happen.`
|
|
853
|
+
);
|
|
854
|
+
return this.#state.actor;
|
|
604
855
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
856
|
+
if (!this.#state.initialized) throw new Error("Not initialized");
|
|
857
|
+
const actorId = this.ctx.id.toString();
|
|
858
|
+
globalState.setDOState(actorId, { ctx: this.ctx, env: env2 });
|
|
859
|
+
const managerDriver = new CloudflareActorsManagerDriver();
|
|
860
|
+
const inlineClient = createClientWithDriver2(managerDriver);
|
|
861
|
+
const actorDriverBuilder = createCloudflareActorsActorDriverBuilder(globalState);
|
|
862
|
+
const actorDriver = actorDriverBuilder(
|
|
863
|
+
parsedConfig,
|
|
864
|
+
managerDriver,
|
|
865
|
+
inlineClient
|
|
866
|
+
);
|
|
867
|
+
const actorRouter = createActorRouter(
|
|
868
|
+
parsedConfig,
|
|
869
|
+
actorDriver,
|
|
870
|
+
getUpgradeWebSocket,
|
|
871
|
+
((_b = registry.config.test) == null ? void 0 : _b.enabled) ?? false
|
|
872
|
+
);
|
|
873
|
+
this.#state.actor = {
|
|
874
|
+
actorRouter,
|
|
875
|
+
actorDriver,
|
|
876
|
+
generation: this.#state.initialized.generation
|
|
877
|
+
};
|
|
878
|
+
const actorIdWithGen = buildActorId(
|
|
879
|
+
actorId,
|
|
880
|
+
this.#state.initialized.generation
|
|
881
|
+
);
|
|
882
|
+
await actorDriver.loadActor(actorIdWithGen);
|
|
883
|
+
return this.#state.actor;
|
|
884
|
+
}
|
|
885
|
+
/** RPC called to get actor metadata without creating it */
|
|
886
|
+
async getMetadata() {
|
|
887
|
+
var _a;
|
|
888
|
+
const cursor = this.ctx.storage.sql.exec(
|
|
889
|
+
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
|
|
890
|
+
);
|
|
891
|
+
const result = cursor.raw().next();
|
|
892
|
+
if (!result.done && result.value) {
|
|
893
|
+
const name = result.value[0];
|
|
894
|
+
const key = JSON.parse(result.value[1]);
|
|
895
|
+
const destroyed = result.value[2];
|
|
896
|
+
const generation = result.value[3];
|
|
897
|
+
if (destroyed) {
|
|
898
|
+
logger().debug({
|
|
899
|
+
msg: "getMetadata: actor is destroyed",
|
|
900
|
+
name,
|
|
901
|
+
key,
|
|
902
|
+
generation
|
|
903
|
+
});
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
const doId = this.ctx.id.toString();
|
|
907
|
+
const actorId = buildActorId(doId, generation);
|
|
908
|
+
const destroying = ((_a = globalState.getActorState(this.ctx)) == null ? void 0 : _a.destroying) ?? false;
|
|
909
|
+
logger().debug({
|
|
910
|
+
msg: "getMetadata: found actor metadata",
|
|
911
|
+
actorId,
|
|
912
|
+
name,
|
|
913
|
+
key,
|
|
914
|
+
generation,
|
|
915
|
+
destroying
|
|
916
|
+
});
|
|
917
|
+
return { actorId, name, key, destroying };
|
|
918
|
+
}
|
|
919
|
+
logger().debug({
|
|
920
|
+
msg: "getMetadata: no metadata found"
|
|
921
|
+
});
|
|
922
|
+
return void 0;
|
|
923
|
+
}
|
|
924
|
+
/** RPC called by ManagerDriver.kvGet to read from KV. */
|
|
925
|
+
async managerKvGet(key) {
|
|
926
|
+
return kvGet(this.ctx.storage.sql, key);
|
|
927
|
+
}
|
|
928
|
+
/** RPC called by the manager to create a DO. Can optionally allow existing actors. */
|
|
929
|
+
async create(req) {
|
|
930
|
+
const checkCursor = this.ctx.storage.sql.exec(
|
|
931
|
+
"SELECT destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
|
|
932
|
+
);
|
|
933
|
+
const checkResult = checkCursor.raw().next();
|
|
934
|
+
let created = false;
|
|
935
|
+
let generation = 0;
|
|
936
|
+
if (!checkResult.done && checkResult.value) {
|
|
937
|
+
const destroyed = checkResult.value[0];
|
|
938
|
+
generation = checkResult.value[1];
|
|
939
|
+
if (!destroyed) {
|
|
940
|
+
if (!req.allowExisting) {
|
|
941
|
+
logger().debug({
|
|
942
|
+
msg: "create failed: actor already exists",
|
|
943
|
+
name: req.name,
|
|
944
|
+
key: req.key,
|
|
945
|
+
generation
|
|
946
|
+
});
|
|
947
|
+
return { error: { actorAlreadyExists: true } };
|
|
948
|
+
}
|
|
949
|
+
logger().debug({
|
|
950
|
+
msg: "actor already exists",
|
|
951
|
+
key: req.key,
|
|
952
|
+
generation
|
|
953
|
+
});
|
|
954
|
+
const doId2 = this.ctx.id.toString();
|
|
955
|
+
const actorId2 = buildActorId(doId2, generation);
|
|
956
|
+
return { success: { actorId: actorId2, created: false } };
|
|
608
957
|
}
|
|
958
|
+
generation = generation + 1;
|
|
959
|
+
created = true;
|
|
960
|
+
if (this.#state) {
|
|
961
|
+
this.#state.actor = void 0;
|
|
962
|
+
}
|
|
963
|
+
logger().debug({
|
|
964
|
+
msg: "resurrecting destroyed actor",
|
|
965
|
+
key: req.key,
|
|
966
|
+
oldGeneration: generation - 1,
|
|
967
|
+
newGeneration: generation
|
|
968
|
+
});
|
|
969
|
+
} else {
|
|
970
|
+
generation = 0;
|
|
971
|
+
created = true;
|
|
972
|
+
logger().debug({
|
|
973
|
+
msg: "creating new actor",
|
|
974
|
+
key: req.key,
|
|
975
|
+
generation
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
this.ctx.storage.sql.exec(
|
|
979
|
+
`INSERT INTO _rivetkit_metadata (id, name, key, destroyed, generation)
|
|
980
|
+
VALUES (1, ?, ?, 0, ?)
|
|
981
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
982
|
+
name = excluded.name,
|
|
983
|
+
key = excluded.key,
|
|
984
|
+
destroyed = 0,
|
|
985
|
+
generation = excluded.generation`,
|
|
986
|
+
req.name,
|
|
987
|
+
JSON.stringify(req.key),
|
|
988
|
+
generation
|
|
989
|
+
);
|
|
990
|
+
this.#state.initialized = {
|
|
991
|
+
name: req.name,
|
|
992
|
+
key: req.key,
|
|
993
|
+
generation
|
|
609
994
|
};
|
|
610
|
-
|
|
995
|
+
const doId = this.ctx.id.toString();
|
|
996
|
+
const actorId = buildActorId(doId, generation);
|
|
997
|
+
if (created) {
|
|
998
|
+
initializeActorKvStorage(this.ctx.storage.sql, req.input);
|
|
999
|
+
const env3 = getCloudflareAmbientEnv();
|
|
1000
|
+
const actorData = { name: req.name, key: req.key, generation };
|
|
1001
|
+
this.ctx.waitUntil(
|
|
1002
|
+
env3.ACTOR_KV.put(
|
|
1003
|
+
GLOBAL_KV_KEYS.actorMetadata(actorId),
|
|
1004
|
+
JSON.stringify(actorData)
|
|
1005
|
+
)
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
await this.#loadActor();
|
|
1009
|
+
logger().debug({
|
|
1010
|
+
msg: created ? "actor created/resurrected" : "returning existing actor",
|
|
1011
|
+
actorId,
|
|
1012
|
+
created,
|
|
1013
|
+
generation
|
|
1014
|
+
});
|
|
1015
|
+
return { success: { actorId, created } };
|
|
1016
|
+
}
|
|
1017
|
+
async fetch(request) {
|
|
1018
|
+
const { actorRouter, generation } = await this.#loadActor();
|
|
1019
|
+
const doId = this.ctx.id.toString();
|
|
1020
|
+
const actorId = buildActorId(doId, generation);
|
|
1021
|
+
return await actorRouter.fetch(request, {
|
|
1022
|
+
actorId
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
async alarm() {
|
|
1026
|
+
const { actorDriver, generation } = await this.#loadActor();
|
|
1027
|
+
const doId = this.ctx.id.toString();
|
|
1028
|
+
const actorId = buildActorId(doId, generation);
|
|
1029
|
+
const actor = await actorDriver.loadActor(actorId);
|
|
1030
|
+
await actor.onAlarm();
|
|
611
1031
|
}
|
|
612
1032
|
};
|
|
613
1033
|
}
|
|
1034
|
+
function initializeActorKvStorage(sql, input) {
|
|
1035
|
+
const initialKvState = getInitialActorKvState(input);
|
|
1036
|
+
for (const [key, value] of initialKvState) {
|
|
1037
|
+
kvPut(sql, key, value);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
614
1040
|
export {
|
|
615
|
-
|
|
616
|
-
|
|
1041
|
+
createActorDurableObject,
|
|
1042
|
+
createHandler,
|
|
1043
|
+
createInlineClient
|
|
617
1044
|
};
|
|
618
1045
|
//# sourceMappingURL=mod.js.map
|