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