@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/src/manager-driver.ts
CHANGED
|
@@ -1,42 +1,31 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Hono, Context as HonoContext } from "hono";
|
|
2
|
+
import type { Encoding, RegistryConfig, UniversalWebSocket } from "rivetkit";
|
|
2
3
|
import {
|
|
3
4
|
type ActorOutput,
|
|
4
5
|
type CreateInput,
|
|
5
6
|
type GetForIdInput,
|
|
6
7
|
type GetOrCreateWithKeyInput,
|
|
7
8
|
type GetWithKeyInput,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
HEADER_ENCODING,
|
|
11
|
-
HEADER_EXPOSE_INTERNAL_ERROR,
|
|
9
|
+
type ListActorsInput,
|
|
10
|
+
type ManagerDisplayInformation,
|
|
12
11
|
type ManagerDriver,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
WS_PROTOCOL_ACTOR,
|
|
13
|
+
WS_PROTOCOL_CONN_PARAMS,
|
|
14
|
+
WS_PROTOCOL_ENCODING,
|
|
15
|
+
WS_PROTOCOL_STANDARD,
|
|
16
|
+
WS_PROTOCOL_TARGET,
|
|
17
|
+
} from "rivetkit/driver-helpers";
|
|
18
|
+
import {
|
|
19
|
+
ActorDuplicateKey,
|
|
20
|
+
ActorNotFound,
|
|
21
|
+
InternalError,
|
|
22
|
+
} from "rivetkit/errors";
|
|
23
|
+
import { assertUnreachable } from "rivetkit/utils";
|
|
24
|
+
import { parseActorId } from "./actor-id";
|
|
16
25
|
import { getCloudflareAmbientEnv } from "./handler";
|
|
17
26
|
import { logger } from "./log";
|
|
18
27
|
import type { Bindings } from "./mod";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
// Actor metadata structure
|
|
22
|
-
interface ActorData {
|
|
23
|
-
name: string;
|
|
24
|
-
key: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Key constants similar to Redis implementation
|
|
28
|
-
const KEYS = {
|
|
29
|
-
ACTOR: {
|
|
30
|
-
// Combined key for actor metadata (name and key)
|
|
31
|
-
metadata: (actorId: string) => `actor:${actorId}:metadata`,
|
|
32
|
-
|
|
33
|
-
// Key index function for actor lookup
|
|
34
|
-
keyIndex: (name: string, key: string[] = []) => {
|
|
35
|
-
// Use serializeKey for consistent handling of all keys
|
|
36
|
-
return `actor_key:${serializeKey(key)}`;
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
};
|
|
28
|
+
import { serializeNameAndKey } from "./util";
|
|
40
29
|
|
|
41
30
|
const STANDARD_WEBSOCKET_HEADERS = [
|
|
42
31
|
"connection",
|
|
@@ -48,16 +37,24 @@ const STANDARD_WEBSOCKET_HEADERS = [
|
|
|
48
37
|
];
|
|
49
38
|
|
|
50
39
|
export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
51
|
-
async sendRequest(
|
|
40
|
+
async sendRequest(
|
|
41
|
+
actorId: string,
|
|
42
|
+
actorRequest: Request,
|
|
43
|
+
): Promise<Response> {
|
|
52
44
|
const env = getCloudflareAmbientEnv();
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
// Parse actor ID to get DO ID
|
|
47
|
+
const [doId] = parseActorId(actorId);
|
|
48
|
+
|
|
49
|
+
logger().debug({
|
|
50
|
+
msg: "sending request to durable object",
|
|
55
51
|
actorId,
|
|
52
|
+
doId,
|
|
56
53
|
method: actorRequest.method,
|
|
57
54
|
url: actorRequest.url,
|
|
58
55
|
});
|
|
59
56
|
|
|
60
|
-
const id = env.ACTOR_DO.idFromString(
|
|
57
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
61
58
|
const stub = env.ACTOR_DO.get(id);
|
|
62
59
|
|
|
63
60
|
return await stub.fetch(actorRequest);
|
|
@@ -68,34 +65,45 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
68
65
|
actorId: string,
|
|
69
66
|
encoding: Encoding,
|
|
70
67
|
params: unknown,
|
|
71
|
-
): Promise<
|
|
68
|
+
): Promise<UniversalWebSocket> {
|
|
72
69
|
const env = getCloudflareAmbientEnv();
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
// Parse actor ID to get DO ID
|
|
72
|
+
const [doId] = parseActorId(actorId);
|
|
73
|
+
|
|
74
|
+
logger().debug({
|
|
75
|
+
msg: "opening websocket to durable object",
|
|
76
|
+
actorId,
|
|
77
|
+
doId,
|
|
78
|
+
path,
|
|
79
|
+
});
|
|
75
80
|
|
|
76
81
|
// Make a fetch request to the Durable Object with WebSocket upgrade
|
|
77
|
-
const id = env.ACTOR_DO.idFromString(
|
|
82
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
78
83
|
const stub = env.ACTOR_DO.get(id);
|
|
79
84
|
|
|
85
|
+
const protocols: string[] = [];
|
|
86
|
+
protocols.push(WS_PROTOCOL_STANDARD);
|
|
87
|
+
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
88
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
89
|
+
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
90
|
+
if (params) {
|
|
91
|
+
protocols.push(
|
|
92
|
+
`${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
const headers: Record<string, string> = {
|
|
81
97
|
Upgrade: "websocket",
|
|
82
98
|
Connection: "Upgrade",
|
|
83
|
-
|
|
84
|
-
[HEADER_ENCODING]: encoding,
|
|
99
|
+
"sec-websocket-protocol": protocols.join(", "),
|
|
85
100
|
};
|
|
86
|
-
if (params) {
|
|
87
|
-
headers[HEADER_CONN_PARAMS] = JSON.stringify(params);
|
|
88
|
-
}
|
|
89
|
-
// HACK: See packages/drivers/cloudflare-workers/src/websocket.ts
|
|
90
|
-
headers["sec-websocket-protocol"] = "rivetkit";
|
|
91
101
|
|
|
92
102
|
// Use the path parameter to determine the URL
|
|
93
|
-
const
|
|
103
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
104
|
+
const url = `http://actor${normalizedPath}`;
|
|
94
105
|
|
|
95
|
-
logger().debug("rewriting websocket url",
|
|
96
|
-
from: path,
|
|
97
|
-
to: url,
|
|
98
|
-
});
|
|
106
|
+
logger().debug({ msg: "rewriting websocket url", from: path, to: url });
|
|
99
107
|
|
|
100
108
|
const response = await stub.fetch(url, {
|
|
101
109
|
headers,
|
|
@@ -104,11 +112,12 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
104
112
|
|
|
105
113
|
if (!webSocket) {
|
|
106
114
|
throw new InternalError(
|
|
107
|
-
|
|
115
|
+
`missing websocket connection in response from DO\n\nStatus: ${response.status}\nResponse: ${await response.text()}`,
|
|
108
116
|
);
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
logger().debug(
|
|
119
|
+
logger().debug({
|
|
120
|
+
msg: "durable object websocket connection open",
|
|
112
121
|
actorId,
|
|
113
122
|
});
|
|
114
123
|
|
|
@@ -123,7 +132,11 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
123
132
|
(webSocket as any).dispatchEvent(event);
|
|
124
133
|
}, 0);
|
|
125
134
|
|
|
126
|
-
return webSocket as unknown as
|
|
135
|
+
return webSocket as unknown as UniversalWebSocket;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async buildGatewayUrl(actorId: string): Promise<string> {
|
|
139
|
+
return `http://actor/gateway/${encodeURIComponent(actorId)}`;
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
async proxyRequest(
|
|
@@ -131,14 +144,21 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
131
144
|
actorRequest: Request,
|
|
132
145
|
actorId: string,
|
|
133
146
|
): Promise<Response> {
|
|
134
|
-
|
|
147
|
+
const env = getCloudflareAmbientEnv();
|
|
148
|
+
|
|
149
|
+
// Parse actor ID to get DO ID
|
|
150
|
+
const [doId] = parseActorId(actorId);
|
|
151
|
+
|
|
152
|
+
logger().debug({
|
|
153
|
+
msg: "forwarding request to durable object",
|
|
135
154
|
actorId,
|
|
155
|
+
doId,
|
|
136
156
|
method: actorRequest.method,
|
|
137
157
|
url: actorRequest.url,
|
|
138
158
|
});
|
|
139
159
|
|
|
140
|
-
const id =
|
|
141
|
-
const stub =
|
|
160
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
161
|
+
const stub = env.ACTOR_DO.get(id);
|
|
142
162
|
|
|
143
163
|
return await stub.fetch(actorRequest);
|
|
144
164
|
}
|
|
@@ -149,9 +169,9 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
149
169
|
actorId: string,
|
|
150
170
|
encoding: Encoding,
|
|
151
171
|
params: unknown,
|
|
152
|
-
authData: unknown,
|
|
153
172
|
): Promise<Response> {
|
|
154
|
-
logger().debug(
|
|
173
|
+
logger().debug({
|
|
174
|
+
msg: "forwarding websocket to durable object",
|
|
155
175
|
actorId,
|
|
156
176
|
path,
|
|
157
177
|
});
|
|
@@ -167,7 +187,8 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
167
187
|
const newUrl = new URL(`http://actor${path}`);
|
|
168
188
|
const actorRequest = new Request(newUrl, c.req.raw);
|
|
169
189
|
|
|
170
|
-
logger().debug(
|
|
190
|
+
logger().debug({
|
|
191
|
+
msg: "rewriting websocket url",
|
|
171
192
|
from: c.req.url,
|
|
172
193
|
to: actorRequest.url,
|
|
173
194
|
});
|
|
@@ -176,49 +197,85 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
176
197
|
// HACK: Since we can't build a new request, we need to remove
|
|
177
198
|
// non-standard headers manually
|
|
178
199
|
const headerKeys: string[] = [];
|
|
179
|
-
actorRequest.headers.forEach((v, k) =>
|
|
200
|
+
actorRequest.headers.forEach((v, k) => {
|
|
201
|
+
headerKeys.push(k);
|
|
202
|
+
});
|
|
180
203
|
for (const k of headerKeys) {
|
|
181
204
|
if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) {
|
|
182
205
|
actorRequest.headers.delete(k);
|
|
183
206
|
}
|
|
184
207
|
}
|
|
185
208
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
// Build protocols for WebSocket connection
|
|
210
|
+
const protocols: string[] = [];
|
|
211
|
+
protocols.push(WS_PROTOCOL_STANDARD);
|
|
212
|
+
protocols.push(`${WS_PROTOCOL_TARGET}actor`);
|
|
213
|
+
protocols.push(`${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
|
|
214
|
+
protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
|
|
189
215
|
if (params) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
actorRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData));
|
|
216
|
+
protocols.push(
|
|
217
|
+
`${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`,
|
|
218
|
+
);
|
|
194
219
|
}
|
|
220
|
+
actorRequest.headers.set(
|
|
221
|
+
"sec-websocket-protocol",
|
|
222
|
+
protocols.join(", "),
|
|
223
|
+
);
|
|
195
224
|
|
|
196
|
-
|
|
197
|
-
const
|
|
225
|
+
// Parse actor ID to get DO ID
|
|
226
|
+
const env = getCloudflareAmbientEnv();
|
|
227
|
+
const [doId] = parseActorId(actorId);
|
|
228
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
229
|
+
const stub = env.ACTOR_DO.get(id);
|
|
198
230
|
|
|
199
231
|
return await stub.fetch(actorRequest);
|
|
200
232
|
}
|
|
201
233
|
|
|
202
234
|
async getForId({
|
|
203
235
|
c,
|
|
236
|
+
name,
|
|
204
237
|
actorId,
|
|
205
|
-
}: GetForIdInput<{ Bindings: Bindings }>): Promise<
|
|
238
|
+
}: GetForIdInput<{ Bindings: Bindings }>): Promise<
|
|
239
|
+
ActorOutput | undefined
|
|
240
|
+
> {
|
|
206
241
|
const env = getCloudflareAmbientEnv();
|
|
207
242
|
|
|
208
|
-
//
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
243
|
+
// Parse actor ID to get DO ID and expected generation
|
|
244
|
+
const [doId, expectedGeneration] = parseActorId(actorId);
|
|
245
|
+
|
|
246
|
+
// Get the Durable Object stub
|
|
247
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
248
|
+
const stub = env.ACTOR_DO.get(id);
|
|
249
|
+
|
|
250
|
+
// Call the DO's getMetadata method
|
|
251
|
+
const result = await stub.getMetadata();
|
|
252
|
+
|
|
253
|
+
if (!result) {
|
|
254
|
+
logger().debug({
|
|
255
|
+
msg: "getForId: actor not found",
|
|
256
|
+
actorId,
|
|
257
|
+
});
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
212
260
|
|
|
213
|
-
//
|
|
214
|
-
if (
|
|
261
|
+
// Check if the actor IDs match in order to check if the generation matches
|
|
262
|
+
if (result.actorId !== actorId) {
|
|
263
|
+
logger().debug({
|
|
264
|
+
msg: "getForId: generation mismatch",
|
|
265
|
+
requestedActorId: actorId,
|
|
266
|
+
actualActorId: result.actorId,
|
|
267
|
+
});
|
|
215
268
|
return undefined;
|
|
216
269
|
}
|
|
217
270
|
|
|
271
|
+
if (result.destroying) {
|
|
272
|
+
throw new ActorNotFound(actorId);
|
|
273
|
+
}
|
|
274
|
+
|
|
218
275
|
return {
|
|
219
|
-
actorId,
|
|
220
|
-
name:
|
|
221
|
-
key:
|
|
276
|
+
actorId: result.actorId,
|
|
277
|
+
name: result.name,
|
|
278
|
+
key: result.key,
|
|
222
279
|
};
|
|
223
280
|
}
|
|
224
281
|
|
|
@@ -231,44 +288,82 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
231
288
|
> {
|
|
232
289
|
const env = getCloudflareAmbientEnv();
|
|
233
290
|
|
|
234
|
-
logger().debug("getWithKey: searching for actor",
|
|
291
|
+
logger().debug({ msg: "getWithKey: searching for actor", name, key });
|
|
235
292
|
|
|
236
293
|
// Generate deterministic ID from the name and key
|
|
237
|
-
// This is aligned with how createActor generates IDs
|
|
238
294
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
239
|
-
const
|
|
295
|
+
const doId = env.ACTOR_DO.idFromName(nameKeyString).toString();
|
|
240
296
|
|
|
241
|
-
//
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
});
|
|
297
|
+
// Try to get the Durable Object to see if it exists
|
|
298
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
299
|
+
const stub = env.ACTOR_DO.get(id);
|
|
245
300
|
|
|
246
|
-
if
|
|
247
|
-
|
|
301
|
+
// Check if actor exists without creating it
|
|
302
|
+
const result = await stub.getMetadata();
|
|
303
|
+
|
|
304
|
+
if (result) {
|
|
305
|
+
logger().debug({
|
|
306
|
+
msg: "getWithKey: found actor with matching name and key",
|
|
307
|
+
actorId: result.actorId,
|
|
308
|
+
name: result.name,
|
|
309
|
+
key: result.key,
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
actorId: result.actorId,
|
|
313
|
+
name: result.name,
|
|
314
|
+
key: result.key,
|
|
315
|
+
};
|
|
316
|
+
} else {
|
|
317
|
+
logger().debug({
|
|
318
|
+
msg: "getWithKey: no actor found with matching name and key",
|
|
248
319
|
name,
|
|
249
320
|
key,
|
|
250
|
-
|
|
321
|
+
doId,
|
|
251
322
|
});
|
|
252
323
|
return undefined;
|
|
253
324
|
}
|
|
325
|
+
}
|
|
254
326
|
|
|
255
|
-
|
|
256
|
-
|
|
327
|
+
async getOrCreateWithKey({
|
|
328
|
+
c,
|
|
329
|
+
name,
|
|
330
|
+
key,
|
|
331
|
+
input,
|
|
332
|
+
}: GetOrCreateWithKeyInput<{ Bindings: Bindings }>): Promise<ActorOutput> {
|
|
333
|
+
const env = getCloudflareAmbientEnv();
|
|
334
|
+
|
|
335
|
+
// Create a deterministic ID from the actor name and key
|
|
336
|
+
// This ensures that actors with the same name and key will have the same ID
|
|
337
|
+
const nameKeyString = serializeNameAndKey(name, key);
|
|
338
|
+
const doId = env.ACTOR_DO.idFromName(nameKeyString);
|
|
339
|
+
|
|
340
|
+
// Get or create actor using the Durable Object's method
|
|
341
|
+
const actor = env.ACTOR_DO.get(doId);
|
|
342
|
+
const result = await actor.create({
|
|
257
343
|
name,
|
|
258
344
|
key,
|
|
345
|
+
input,
|
|
346
|
+
allowExisting: true,
|
|
259
347
|
});
|
|
260
|
-
|
|
261
|
-
|
|
348
|
+
if ("success" in result) {
|
|
349
|
+
const { actorId, created } = result.success;
|
|
350
|
+
logger().debug({
|
|
351
|
+
msg: "getOrCreateWithKey result",
|
|
352
|
+
actorId,
|
|
353
|
+
name,
|
|
354
|
+
key,
|
|
355
|
+
created,
|
|
356
|
+
});
|
|
262
357
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
358
|
+
return {
|
|
359
|
+
actorId,
|
|
360
|
+
name,
|
|
361
|
+
key,
|
|
362
|
+
};
|
|
363
|
+
} else if ("error" in result) {
|
|
364
|
+
throw new Error(`Error: ${JSON.stringify(result.error)}`);
|
|
270
365
|
} else {
|
|
271
|
-
|
|
366
|
+
assertUnreachable(result);
|
|
272
367
|
}
|
|
273
368
|
}
|
|
274
369
|
|
|
@@ -280,62 +375,70 @@ export class CloudflareActorsManagerDriver implements ManagerDriver {
|
|
|
280
375
|
}: CreateInput<{ Bindings: Bindings }>): Promise<ActorOutput> {
|
|
281
376
|
const env = getCloudflareAmbientEnv();
|
|
282
377
|
|
|
283
|
-
// Check if actor with the same name and key already exists
|
|
284
|
-
const existingActor = await this.getWithKey({ c, name, key });
|
|
285
|
-
if (existingActor) {
|
|
286
|
-
throw new ActorAlreadyExists(name, key);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
378
|
// Create a deterministic ID from the actor name and key
|
|
290
379
|
// This ensures that actors with the same name and key will have the same ID
|
|
291
380
|
const nameKeyString = serializeNameAndKey(name, key);
|
|
292
381
|
const doId = env.ACTOR_DO.idFromName(nameKeyString);
|
|
293
|
-
const actorId = doId.toString();
|
|
294
382
|
|
|
295
|
-
//
|
|
383
|
+
// Create actor - this will fail if it already exists
|
|
296
384
|
const actor = env.ACTOR_DO.get(doId);
|
|
297
|
-
await actor.
|
|
385
|
+
const result = await actor.create({
|
|
298
386
|
name,
|
|
299
387
|
key,
|
|
300
388
|
input,
|
|
389
|
+
allowExisting: false,
|
|
301
390
|
});
|
|
302
391
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
392
|
+
if ("success" in result) {
|
|
393
|
+
const { actorId } = result.success;
|
|
394
|
+
return {
|
|
395
|
+
actorId,
|
|
396
|
+
name,
|
|
397
|
+
key,
|
|
398
|
+
};
|
|
399
|
+
} else if ("error" in result) {
|
|
400
|
+
if (result.error.actorAlreadyExists) {
|
|
401
|
+
throw new ActorDuplicateKey(name, key);
|
|
402
|
+
}
|
|
309
403
|
|
|
310
|
-
|
|
311
|
-
|
|
404
|
+
throw new InternalError(
|
|
405
|
+
`Unknown error creating actor: ${JSON.stringify(result.error)}`,
|
|
406
|
+
);
|
|
407
|
+
} else {
|
|
408
|
+
assertUnreachable(result);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
312
411
|
|
|
313
|
-
|
|
314
|
-
|
|
412
|
+
async listActors({ c, name }: ListActorsInput): Promise<ActorOutput[]> {
|
|
413
|
+
logger().warn({
|
|
414
|
+
msg: "listActors not fully implemented for Cloudflare Workers",
|
|
315
415
|
name,
|
|
316
|
-
|
|
416
|
+
});
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
displayInformation(): ManagerDisplayInformation {
|
|
421
|
+
return {
|
|
422
|
+
properties: {
|
|
423
|
+
Driver: "Cloudflare Workers",
|
|
424
|
+
},
|
|
317
425
|
};
|
|
318
426
|
}
|
|
319
427
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
): Promise<
|
|
428
|
+
setGetUpgradeWebSocket(): void {
|
|
429
|
+
// No-op for Cloudflare Workers - WebSocket upgrades are handled by the DO
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async kvGet(actorId: string, key: Uint8Array): Promise<string | null> {
|
|
325
433
|
const env = getCloudflareAmbientEnv();
|
|
326
434
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
})) as ActorData | null;
|
|
435
|
+
// Parse actor ID to get DO ID
|
|
436
|
+
const [doId] = parseActorId(actorId);
|
|
330
437
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
438
|
+
const id = env.ACTOR_DO.idFromString(doId);
|
|
439
|
+
const stub = env.ACTOR_DO.get(id);
|
|
334
440
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
name: actorData.name,
|
|
338
|
-
key: actorData.key,
|
|
339
|
-
};
|
|
441
|
+
const value = await stub.managerKvGet(key);
|
|
442
|
+
return value !== null ? new TextDecoder().decode(value) : null;
|
|
340
443
|
}
|
|
341
444
|
}
|
package/src/mod.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export type { Client } from "rivetkit";
|
|
1
2
|
export type { DriverContext } from "./actor-driver";
|
|
3
|
+
export { createActorDurableObject } from "./actor-handler-do";
|
|
2
4
|
export type { InputConfig as Config } from "./config";
|
|
3
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
type Bindings,
|
|
7
|
+
createHandler,
|
|
8
|
+
createInlineClient,
|
|
9
|
+
HandlerOutput,
|
|
10
|
+
InlineOutput,
|
|
11
|
+
} from "./handler";
|
package/src/websocket.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { UpgradeWebSocket, WSEvents, WSReadyState } from "hono/ws";
|
|
6
6
|
import { defineWebSocketHelper, WSContext } from "hono/ws";
|
|
7
|
+
import { WS_PROTOCOL_STANDARD } from "rivetkit/driver-helpers";
|
|
7
8
|
|
|
8
9
|
// Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332
|
|
9
10
|
export const upgradeWebSocket: UpgradeWebSocket<
|
|
@@ -57,14 +58,24 @@ export const upgradeWebSocket: UpgradeWebSocket<
|
|
|
57
58
|
// we have to do this after `server.accept() is called`
|
|
58
59
|
events.onOpen?.(new Event("open"), wsContext);
|
|
59
60
|
|
|
61
|
+
// Build response headers
|
|
62
|
+
const headers: Record<string, string> = {};
|
|
63
|
+
|
|
64
|
+
// Set Sec-WebSocket-Protocol if does not exist
|
|
65
|
+
const protocols = c.req.header("Sec-WebSocket-Protocol");
|
|
66
|
+
if (
|
|
67
|
+
typeof protocols === "string" &&
|
|
68
|
+
protocols
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((x) => x.trim())
|
|
71
|
+
.includes(WS_PROTOCOL_STANDARD)
|
|
72
|
+
) {
|
|
73
|
+
headers["Sec-WebSocket-Protocol"] = WS_PROTOCOL_STANDARD;
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
return new Response(null, {
|
|
61
77
|
status: 101,
|
|
62
|
-
headers
|
|
63
|
-
// HACK: Required in order for Cloudflare to not error with "Network connection lost"
|
|
64
|
-
//
|
|
65
|
-
// This bug undocumented. Cannot easily reproduce outside of RivetKit.
|
|
66
|
-
"Sec-WebSocket-Protocol": "rivetkit",
|
|
67
|
-
},
|
|
78
|
+
headers,
|
|
68
79
|
webSocket: client,
|
|
69
80
|
});
|
|
70
81
|
});
|