@rivetkit/cloudflare-workers 2.0.23 → 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/mod.cjs +669 -303
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +67 -27
- package/dist/mod.d.ts +67 -27
- package/dist/mod.js +675 -309
- package/dist/mod.js.map +1 -1
- package/package.json +2 -2
- package/src/actor-driver.ts +211 -47
- package/src/actor-handler-do.ts +306 -71
- package/src/actor-id.ts +38 -0
- package/src/actor-kv.ts +71 -0
- package/src/global-kv.ts +6 -0
- package/src/handler.ts +48 -9
- package/src/manager-driver.ts +143 -116
- package/src/mod.ts +8 -1
package/src/manager-driver.ts
CHANGED
|
@@ -7,40 +7,26 @@ import {
|
|
|
7
7
|
type GetOrCreateWithKeyInput,
|
|
8
8
|
type GetWithKeyInput,
|
|
9
9
|
generateRandomString,
|
|
10
|
+
type ListActorsInput,
|
|
10
11
|
type ManagerDisplayInformation,
|
|
11
12
|
type ManagerDriver,
|
|
12
13
|
WS_PROTOCOL_ACTOR,
|
|
13
|
-
WS_PROTOCOL_CONN_ID,
|
|
14
14
|
WS_PROTOCOL_CONN_PARAMS,
|
|
15
|
-
WS_PROTOCOL_CONN_TOKEN,
|
|
16
15
|
WS_PROTOCOL_ENCODING,
|
|
17
16
|
WS_PROTOCOL_STANDARD,
|
|
18
17
|
WS_PROTOCOL_TARGET,
|
|
19
18
|
} from "rivetkit/driver-helpers";
|
|
20
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
ActorDuplicateKey,
|
|
21
|
+
ActorNotFound,
|
|
22
|
+
InternalError,
|
|
23
|
+
} from "rivetkit/errors";
|
|
24
|
+
import { assertUnreachable } from "rivetkit/utils";
|
|
25
|
+
import { parseActorId } from "./actor-id";
|
|
21
26
|
import { getCloudflareAmbientEnv } from "./handler";
|
|
22
27
|
import { logger } from "./log";
|
|
23
28
|
import type { Bindings } from "./mod";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
// Actor metadata structure
|
|
27
|
-
interface ActorData {
|
|
28
|
-
name: string;
|
|
29
|
-
key: string[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const KEYS = {
|
|
33
|
-
ACTOR: {
|
|
34
|
-
// Combined key for actor metadata (name and key)
|
|
35
|
-
metadata: (actorId: string) => `actor:${actorId}:metadata`,
|
|
36
|
-
|
|
37
|
-
// Key index function for actor lookup
|
|
38
|
-
keyIndex: (name: string, key: string[] = []) => {
|
|
39
|
-
// Use serializeKey for consistent handling of all keys
|
|
40
|
-
return `actor_key:${serializeKey(key)}`;
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
29
|
+
import { serializeNameAndKey } from "./util";
|
|
44
30
|
|
|
45
31
|
const STANDARD_WEBSOCKET_HEADERS = [
|
|
46
32
|
"connection",
|
|
@@ -58,14 +44,18 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
58
44
|
): Promise<Response> {
|
|
59
45
|
const env = getCloudflareAmbientEnv();
|
|
60
46
|
|
|
47
|
+
// Parse actor ID to get DO ID
|
|
48
|
+
const [doId] = parseActorId(actorId);
|
|
49
|
+
|
|
61
50
|
logger().debug({
|
|
62
51
|
msg: "sending request to durable object",
|
|
63
52
|
actorId,
|
|
53
|
+
doId,
|
|
64
54
|
method: actorRequest.method,
|
|
65
55
|
url: actorRequest.url,
|
|
66
56
|
});
|
|
67
57
|
|
|
68
|
-
const id = env.ACTOR_DO.idFromString(
|
|
58
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
69
59
|
const stub = env.ACTOR_DO.get(id);
|
|
70
60
|
|
|
71
61
|
return await stub.fetch(actorRequest);
|
|
@@ -76,37 +66,33 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
76
66
|
actorId: string,
|
|
77
67
|
encoding: Encoding,
|
|
78
68
|
params: unknown,
|
|
79
|
-
connId?: string,
|
|
80
|
-
connToken?: string,
|
|
81
69
|
): Promise<UniversalWebSocket> {
|
|
82
70
|
const env = getCloudflareAmbientEnv();
|
|
83
71
|
|
|
72
|
+
// Parse actor ID to get DO ID
|
|
73
|
+
const [doId] = parseActorId(actorId);
|
|
74
|
+
|
|
84
75
|
logger().debug({
|
|
85
76
|
msg: "opening websocket to durable object",
|
|
86
77
|
actorId,
|
|
78
|
+
doId,
|
|
87
79
|
path,
|
|
88
80
|
});
|
|
89
81
|
|
|
90
82
|
// Make a fetch request to the Durable Object with WebSocket upgrade
|
|
91
|
-
const id = env.ACTOR_DO.idFromString(
|
|
83
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
92
84
|
const stub = env.ACTOR_DO.get(id);
|
|
93
85
|
|
|
94
86
|
const protocols: string[] = [];
|
|
95
87
|
protocols.push(WS_PROTOCOL_STANDARD);
|
|
96
88
|
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
97
|
-
protocols.push(`${WS_PROTOCOL_ACTOR}${actorId}`);
|
|
89
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
98
90
|
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
99
91
|
if (params) {
|
|
100
92
|
protocols.push(
|
|
101
93
|
`${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`,
|
|
102
94
|
);
|
|
103
95
|
}
|
|
104
|
-
if (connId) {
|
|
105
|
-
protocols.push(`${WS_PROTOCOL_CONN_ID}${connId}`);
|
|
106
|
-
}
|
|
107
|
-
if (connToken) {
|
|
108
|
-
protocols.push(`${WS_PROTOCOL_CONN_TOKEN}${connToken}`);
|
|
109
|
-
}
|
|
110
96
|
|
|
111
97
|
const headers: Record<string, string> = {
|
|
112
98
|
Upgrade: "websocket",
|
|
@@ -155,14 +141,18 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
155
141
|
actorRequest: Request,
|
|
156
142
|
actorId: string,
|
|
157
143
|
): Promise<Response> {
|
|
144
|
+
// Parse actor ID to get DO ID
|
|
145
|
+
const [doId] = parseActorId(actorId);
|
|
146
|
+
|
|
158
147
|
logger().debug({
|
|
159
148
|
msg: "forwarding request to durable object",
|
|
160
149
|
actorId,
|
|
150
|
+
doId,
|
|
161
151
|
method: actorRequest.method,
|
|
162
152
|
url: actorRequest.url,
|
|
163
153
|
});
|
|
164
154
|
|
|
165
|
-
const id = c.env.ACTOR_DO.idFromString(
|
|
155
|
+
const id = c.env.ACTOR_DO.idFromString(doId);
|
|
166
156
|
const stub = c.env.ACTOR_DO.get(id);
|
|
167
157
|
|
|
168
158
|
return await stub.fetch(actorRequest);
|
|
@@ -215,7 +205,7 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
215
205
|
const protocols: string[] = [];
|
|
216
206
|
protocols.push(WS_PROTOCOL_STANDARD);
|
|
217
207
|
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
218
|
-
protocols.push(`${WS_PROTOCOL_ACTOR}${actorId}`);
|
|
208
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
219
209
|
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
220
210
|
if (params) {
|
|
221
211
|
protocols.push(
|
|
@@ -227,7 +217,9 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
227
217
|
protocols.join(", "),
|
|
228
218
|
);
|
|
229
219
|
|
|
230
|
-
|
|
220
|
+
// Parse actor ID to get DO ID
|
|
221
|
+
const [doId] = parseActorId(actorId);
|
|
222
|
+
const id = c.env.ACTOR_DO.idFromString(doId);
|
|
231
223
|
const stub = c.env.ACTOR_DO.get(id);
|
|
232
224
|
|
|
233
225
|
return await stub.fetch(actorRequest);
|
|
@@ -241,23 +233,42 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
241
233
|
> {
|
|
242
234
|
const env = getCloudflareAmbientEnv();
|
|
243
235
|
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
236
|
+
// Parse actor ID to get DO ID and expected generation
|
|
237
|
+
const [doId, expectedGeneration] = parseActorId(actorId);
|
|
238
|
+
|
|
239
|
+
// Get the Durable Object stub
|
|
240
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
241
|
+
const stub = env.ACTOR_DO.get(id);
|
|
242
|
+
|
|
243
|
+
// Call the DO's getMetadata method
|
|
244
|
+
const result = await stub.getMetadata();
|
|
251
245
|
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
if (!result) {
|
|
247
|
+
logger().debug({
|
|
248
|
+
msg: "getForId: actor not found",
|
|
249
|
+
actorId,
|
|
250
|
+
});
|
|
254
251
|
return undefined;
|
|
255
252
|
}
|
|
256
253
|
|
|
254
|
+
// Check if the actor IDs match in order to check if the generation matches
|
|
255
|
+
if (result.actorId !== actorId) {
|
|
256
|
+
logger().debug({
|
|
257
|
+
msg: "getForId: generation mismatch",
|
|
258
|
+
requestedActorId: actorId,
|
|
259
|
+
actualActorId: result.actorId,
|
|
260
|
+
});
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (result.destroying) {
|
|
265
|
+
throw new ActorNotFound(actorId);
|
|
266
|
+
}
|
|
267
|
+
|
|
257
268
|
return {
|
|
258
|
-
actorId,
|
|
259
|
-
name:
|
|
260
|
-
key:
|
|
269
|
+
actorId: result.actorId,
|
|
270
|
+
name: result.name,
|
|
271
|
+
key: result.key,
|
|
261
272
|
};
|
|
262
273
|
}
|
|
263
274
|
|
|
@@ -273,43 +284,79 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
273
284
|
logger().debug({ msg: "getWithKey: searching for actor", name, key });
|
|
274
285
|
|
|
275
286
|
// Generate deterministic ID from the name and key
|
|
276
|
-
// This is aligned with how createActor generates IDs
|
|
277
287
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
278
|
-
const
|
|
288
|
+
const doId = env.ACTOR_DO.idFromName(nameKeyString).toString();
|
|
279
289
|
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
// Try to get the Durable Object to see if it exists
|
|
291
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
292
|
+
const stub = env.ACTOR_DO.get(id);
|
|
293
|
+
|
|
294
|
+
// Check if actor exists without creating it
|
|
295
|
+
const result = await stub.getMetadata();
|
|
284
296
|
|
|
285
|
-
if (
|
|
297
|
+
if (result) {
|
|
298
|
+
logger().debug({
|
|
299
|
+
msg: "getWithKey: found actor with matching name and key",
|
|
300
|
+
actorId: result.actorId,
|
|
301
|
+
name: result.name,
|
|
302
|
+
key: result.key,
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
actorId: result.actorId,
|
|
306
|
+
name: result.name,
|
|
307
|
+
key: result.key,
|
|
308
|
+
};
|
|
309
|
+
} else {
|
|
286
310
|
logger().debug({
|
|
287
311
|
msg: "getWithKey: no actor found with matching name and key",
|
|
288
312
|
name,
|
|
289
313
|
key,
|
|
290
|
-
|
|
314
|
+
doId,
|
|
291
315
|
});
|
|
292
316
|
return undefined;
|
|
293
317
|
}
|
|
318
|
+
}
|
|
294
319
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
320
|
+
async getOrCreateWithKey({
|
|
321
|
+
c,
|
|
322
|
+
name,
|
|
323
|
+
key,
|
|
324
|
+
input,
|
|
325
|
+
}: GetOrCreateWithKeyInput<{ Bindings: Bindings }>): Promise<ActorOutput> {
|
|
326
|
+
const env = getCloudflareAmbientEnv();
|
|
327
|
+
|
|
328
|
+
// Create a deterministic ID from the actor name and key
|
|
329
|
+
// This ensures that actors with the same name and key will have the same ID
|
|
330
|
+
const nameKeyString = serializeNameAndKey(name, key);
|
|
331
|
+
const doId = env.ACTOR_DO.idFromName(nameKeyString);
|
|
332
|
+
|
|
333
|
+
// Get or create actor using the Durable Object's method
|
|
334
|
+
const actor = env.ACTOR_DO.get(doId);
|
|
335
|
+
const result = await actor.create({
|
|
298
336
|
name,
|
|
299
337
|
key,
|
|
338
|
+
input,
|
|
339
|
+
allowExisting: true,
|
|
300
340
|
});
|
|
301
|
-
|
|
302
|
-
|
|
341
|
+
if ("success" in result) {
|
|
342
|
+
const { actorId, created } = result.success;
|
|
343
|
+
logger().debug({
|
|
344
|
+
msg: "getOrCreateWithKey result",
|
|
345
|
+
actorId,
|
|
346
|
+
name,
|
|
347
|
+
key,
|
|
348
|
+
created,
|
|
349
|
+
});
|
|
303
350
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
351
|
+
return {
|
|
352
|
+
actorId,
|
|
353
|
+
name,
|
|
354
|
+
key,
|
|
355
|
+
};
|
|
356
|
+
} else if ("error" in result) {
|
|
357
|
+
throw new Error(`Error: ${JSON.stringify(result.error)}`);
|
|
311
358
|
} else {
|
|
312
|
-
|
|
359
|
+
assertUnreachable(result);
|
|
313
360
|
}
|
|
314
361
|
}
|
|
315
362
|
|
|
@@ -321,66 +368,46 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
321
368
|
}: CreateInput<{ Bindings: Bindings }>): Promise<ActorOutput> {
|
|
322
369
|
const env = getCloudflareAmbientEnv();
|
|
323
370
|
|
|
324
|
-
// Check if actor with the same name and key already exists
|
|
325
|
-
const existingActor = await this.getWithKey({ c, name, key });
|
|
326
|
-
if (existingActor) {
|
|
327
|
-
throw new ActorAlreadyExists(name, key);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
371
|
// Create a deterministic ID from the actor name and key
|
|
331
372
|
// This ensures that actors with the same name and key will have the same ID
|
|
332
373
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
333
374
|
const doId = env.ACTOR_DO.idFromName(nameKeyString);
|
|
334
|
-
const actorId = doId.toString();
|
|
335
375
|
|
|
336
|
-
//
|
|
376
|
+
// Create actor - this will fail if it already exists
|
|
337
377
|
const actor = env.ACTOR_DO.get(doId);
|
|
338
|
-
await actor.
|
|
378
|
+
const result = await actor.create({
|
|
339
379
|
name,
|
|
340
380
|
key,
|
|
341
381
|
input,
|
|
382
|
+
allowExisting: false,
|
|
342
383
|
});
|
|
343
384
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
actorId,
|
|
356
|
-
name,
|
|
357
|
-
key,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Helper method to build actor output from an ID
|
|
362
|
-
async #buildActorOutput(
|
|
363
|
-
c: any,
|
|
364
|
-
actorId: string,
|
|
365
|
-
): Promise<ActorOutput | undefined> {
|
|
366
|
-
const env = getCloudflareAmbientEnv();
|
|
367
|
-
|
|
368
|
-
const actorData = (await env.ACTOR_KV.get(
|
|
369
|
-
KEYS.ACTOR.metadata(actorId),
|
|
370
|
-
{
|
|
371
|
-
type: "json",
|
|
372
|
-
},
|
|
373
|
-
)) as ActorData | null;
|
|
385
|
+
if ("success" in result) {
|
|
386
|
+
const { actorId } = result.success;
|
|
387
|
+
return {
|
|
388
|
+
actorId,
|
|
389
|
+
name,
|
|
390
|
+
key,
|
|
391
|
+
};
|
|
392
|
+
} else if ("error" in result) {
|
|
393
|
+
if (result.error.actorAlreadyExists) {
|
|
394
|
+
throw new ActorDuplicateKey(name, key);
|
|
395
|
+
}
|
|
374
396
|
|
|
375
|
-
|
|
376
|
-
|
|
397
|
+
throw new InternalError(
|
|
398
|
+
`Unknown error creating actor: ${JSON.stringify(result.error)}`,
|
|
399
|
+
);
|
|
400
|
+
} else {
|
|
401
|
+
assertUnreachable(result);
|
|
377
402
|
}
|
|
403
|
+
}
|
|
378
404
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
};
|
|
405
|
+
async listActors({ c, name }: ListActorsInput): Promise<ActorOutput[]> {
|
|
406
|
+
logger().warn({
|
|
407
|
+
msg: "listActors not fully implemented for Cloudflare Workers",
|
|
408
|
+
name,
|
|
409
|
+
});
|
|
410
|
+
return [];
|
|
384
411
|
}
|
|
385
412
|
|
|
386
413
|
displayInformation(): ManagerDisplayInformation {
|
package/src/mod.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export type { Client } from "rivetkit";
|
|
2
2
|
export type { DriverContext } from "./actor-driver";
|
|
3
|
+
export { createActorDurableObject } from "./actor-handler-do";
|
|
3
4
|
export type { InputConfig as Config } from "./config";
|
|
4
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
type Bindings,
|
|
7
|
+
createHandler,
|
|
8
|
+
createInlineClient,
|
|
9
|
+
HandlerOutput,
|
|
10
|
+
InlineOutput,
|
|
11
|
+
} from "./handler";
|