@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.
@@ -1,57 +1,59 @@
1
1
  import { DurableObject, env } from "cloudflare:workers";
2
2
  import type { ExecutionContext } from "hono";
3
3
  import invariant from "invariant";
4
- import type { ActorKey, ActorRouter, Registry, RunConfig } from "rivetkit";
5
- import {
6
- createActorRouter,
7
- createClientWithDriver,
8
- createInlineClientDriver,
9
- } from "rivetkit";
10
- import { serializeEmptyPersistData } from "rivetkit/driver-helpers";
4
+ import type { ActorKey, ActorRouter, Registry, RegistryConfig } from "rivetkit";
5
+ import { createActorRouter, createClientWithDriver } from "rivetkit";
6
+ import type { ActorDriver, ManagerDriver } from "rivetkit/driver-helpers";
7
+ import { getInitialActorKvState } from "rivetkit/driver-helpers";
8
+ import type { GetUpgradeWebSocket } from "rivetkit/utils";
9
+ import { stringifyError } from "rivetkit/utils";
11
10
  import {
11
+ ActorGlobalState,
12
12
  CloudflareDurableObjectGlobalState,
13
13
  createCloudflareActorsActorDriverBuilder,
14
14
  } from "./actor-driver";
15
+ import { buildActorId, parseActorId } from "./actor-id";
16
+ import { kvGet, kvPut } from "./actor-kv";
17
+ import { GLOBAL_KV_KEYS } from "./global-kv";
15
18
  import type { Bindings } from "./handler";
19
+ import { getCloudflareAmbientEnv } from "./handler";
16
20
  import { logger } from "./log";
17
-
18
- export const KEYS = {
19
- NAME: "rivetkit:name",
20
- KEY: "rivetkit:key",
21
- PERSIST_DATA: "rivetkit:data",
22
- };
21
+ import { CloudflareActorsManagerDriver } from "./manager-driver";
23
22
 
24
23
  export interface ActorHandlerInterface extends DurableObject {
25
- initialize(req: ActorInitRequest): Promise<void>;
24
+ create(req: ActorInitRequest): Promise<ActorInitResponse>;
25
+ getMetadata(): Promise<
26
+ | {
27
+ actorId: string;
28
+ name: string;
29
+ key: ActorKey;
30
+ destroying: boolean;
31
+ }
32
+ | undefined
33
+ >;
34
+ managerKvGet(key: Uint8Array): Promise<Uint8Array | null>;
26
35
  }
27
36
 
28
37
  export interface ActorInitRequest {
29
38
  name: string;
30
39
  key: ActorKey;
31
40
  input?: unknown;
41
+ allowExisting: boolean;
32
42
  }
33
-
34
- interface InitializedData {
35
- name: string;
36
- key: ActorKey;
37
- }
43
+ export type ActorInitResponse =
44
+ | { success: { actorId: string; created: boolean } }
45
+ | { error: { actorAlreadyExists: true } };
38
46
 
39
47
  export type DurableObjectConstructor = new (
40
48
  ...args: ConstructorParameters<typeof DurableObject<Bindings>>
41
49
  ) => DurableObject<Bindings>;
42
50
 
43
- interface LoadedActor {
44
- actorRouter: ActorRouter;
45
- }
46
-
47
51
  export function createActorDurableObject(
48
52
  registry: Registry<any>,
49
- rootRunConfig: RunConfig,
53
+ getUpgradeWebSocket: GetUpgradeWebSocket,
50
54
  ): DurableObjectConstructor {
51
55
  const globalState = new CloudflareDurableObjectGlobalState();
52
-
53
- // Configure to use the runner role instead of server role
54
- const runConfig = Object.assign({}, rootRunConfig, { role: "runner" });
56
+ const parsedConfig = registry.parseConfig();
55
57
 
56
58
  /**
57
59
  * Startup steps:
@@ -63,46 +65,104 @@ export function createActorDurableObject(
63
65
  extends DurableObject<Bindings>
64
66
  implements ActorHandlerInterface
65
67
  {
66
- #initialized?: InitializedData;
67
- #initializedPromise?: PromiseWithResolvers<void>;
68
-
69
- #actor?: LoadedActor;
68
+ /**
69
+ * This holds a strong reference to ActorGlobalState.
70
+ * CloudflareDurableObjectGlobalState holds a weak reference so we can
71
+ * access it elsewhere.
72
+ **/
73
+ #state: ActorGlobalState;
74
+
75
+ constructor(
76
+ ...args: ConstructorParameters<typeof DurableObject<Bindings>>
77
+ ) {
78
+ super(...args);
79
+
80
+ // Initialize SQL table for key-value storage
81
+ //
82
+ // We do this instead of using the native KV storage so we can store blob keys. The native CF KV API only supports string keys.
83
+ this.ctx.storage.sql.exec(`
84
+ CREATE TABLE IF NOT EXISTS _rivetkit_kv_storage(
85
+ key BLOB PRIMARY KEY,
86
+ value BLOB
87
+ );
88
+ `);
89
+
90
+ // Initialize SQL table for actor metadata
91
+ //
92
+ // id always equals 1 in order to ensure that there's always exactly 1 row in this table
93
+ this.ctx.storage.sql.exec(`
94
+ CREATE TABLE IF NOT EXISTS _rivetkit_metadata(
95
+ id INTEGER PRIMARY KEY CHECK (id = 1),
96
+ name TEXT NOT NULL,
97
+ key TEXT NOT NULL,
98
+ destroyed INTEGER DEFAULT 0,
99
+ generation INTEGER DEFAULT 0
100
+ );
101
+ `);
102
+
103
+ // Get or create the actor state from the global WeakMap
104
+ const state = globalState.getActorState(this.ctx);
105
+ if (state) {
106
+ this.#state = state;
107
+ } else {
108
+ this.#state = new ActorGlobalState();
109
+ globalState.setActorState(this.ctx, this.#state);
110
+ }
111
+ }
70
112
 
71
- async #loadActor(): Promise<LoadedActor> {
72
- // Wait for init
73
- if (!this.#initialized) {
74
- // Wait for init
75
- if (this.#initializedPromise) {
76
- await this.#initializedPromise.promise;
77
- } else {
78
- this.#initializedPromise = Promise.withResolvers();
79
- const res = await this.ctx.storage.get([
80
- KEYS.NAME,
81
- KEYS.KEY,
82
- KEYS.PERSIST_DATA,
83
- ]);
84
- if (res.get(KEYS.PERSIST_DATA)) {
85
- const name = res.get(KEYS.NAME) as string;
86
- if (!name) throw new Error("missing actor name");
87
- const key = res.get(KEYS.KEY) as ActorKey;
88
- if (!key) throw new Error("missing actor key");
89
-
90
- logger().debug("already initialized", { name, key });
91
-
92
- this.#initialized = { name, key };
93
- this.#initializedPromise.resolve();
113
+ async #loadActor() {
114
+ invariant(this.#state, "State should be initialized");
115
+
116
+ // Check if initialized
117
+ if (!this.#state.initialized) {
118
+ // Query SQL for initialization data
119
+ const cursor = this.ctx.storage.sql.exec(
120
+ "SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1",
121
+ );
122
+ const result = cursor.raw().next();
123
+
124
+ if (!result.done && result.value) {
125
+ const name = result.value[0] as string;
126
+ const key = JSON.parse(
127
+ result.value[1] as string,
128
+ ) as ActorKey;
129
+ const destroyed = result.value[2] as number;
130
+ const generation = result.value[3] as number;
131
+
132
+ // Only initialize if not destroyed
133
+ if (!destroyed) {
134
+ logger().debug({
135
+ msg: "already initialized",
136
+ name,
137
+ key,
138
+ generation,
139
+ });
140
+
141
+ this.#state.initialized = { name, key, generation };
94
142
  } else {
95
- logger().debug("waiting to initialize");
143
+ logger().debug("actor is destroyed, cannot load");
144
+ throw new Error("Actor is destroyed");
96
145
  }
146
+ } else {
147
+ logger().debug("not initialized");
148
+ throw new Error("Actor is not initialized");
97
149
  }
98
150
  }
99
151
 
100
152
  // Check if already loaded
101
- if (this.#actor) {
102
- return this.#actor;
153
+ if (this.#state.actor) {
154
+ // Assert that the cached actor has the correct generation
155
+ // This will catch any cases where #state.actor has a stale generation
156
+ invariant(
157
+ !this.#state.initialized ||
158
+ this.#state.actor.generation ===
159
+ this.#state.initialized.generation,
160
+ `Stale actor cached: actor generation ${this.#state.actor.generation} != initialized generation ${this.#state.initialized?.generation}. This should not happen.`,
161
+ );
162
+ return this.#state.actor;
103
163
  }
104
164
 
105
- if (!this.#initialized) throw new Error("Not initialized");
165
+ if (!this.#state.initialized) throw new Error("Not initialized");
106
166
 
107
167
  // Register DO with global state first
108
168
  // HACK: This leaks the DO context, but DO does not provide a native way
@@ -111,97 +171,267 @@ export function createActorDurableObject(
111
171
  const actorId = this.ctx.id.toString();
112
172
  globalState.setDOState(actorId, { ctx: this.ctx, env: env });
113
173
 
114
- // Configure actor driver
115
- invariant(runConfig.driver, "runConfig.driver");
116
- runConfig.driver.actor =
117
- createCloudflareActorsActorDriverBuilder(globalState);
118
-
119
- // Create manager driver (we need this for the actor router)
120
- const managerDriver = runConfig.driver.manager(
121
- registry.config,
122
- runConfig,
123
- );
174
+ // Create manager driver
175
+ const managerDriver = new CloudflareActorsManagerDriver();
124
176
 
125
177
  // Create inline client
126
- const inlineClient = createClientWithDriver(
127
- createInlineClientDriver(managerDriver),
128
- );
178
+ const inlineClient = createClientWithDriver(managerDriver);
179
+
180
+ // Create actor driver builder
181
+ const actorDriverBuilder =
182
+ createCloudflareActorsActorDriverBuilder(globalState);
129
183
 
130
184
  // Create actor driver
131
- const actorDriver = runConfig.driver.actor(
132
- registry.config,
133
- runConfig,
185
+ const actorDriver = actorDriverBuilder(
186
+ parsedConfig,
134
187
  managerDriver,
135
188
  inlineClient,
136
189
  );
137
190
 
138
191
  // Create actor router
139
- const actorRouter = createActorRouter(runConfig, actorDriver);
192
+ const actorRouter = createActorRouter(
193
+ parsedConfig,
194
+ actorDriver,
195
+ getUpgradeWebSocket,
196
+ registry.config.test?.enabled ?? false,
197
+ );
140
198
 
141
- // Save actor
142
- this.#actor = {
199
+ // Save actor with generation
200
+ this.#state.actor = {
143
201
  actorRouter,
202
+ actorDriver,
203
+ generation: this.#state.initialized.generation,
144
204
  };
145
205
 
206
+ // Build actor ID with generation for loading
207
+ const actorIdWithGen = buildActorId(
208
+ actorId,
209
+ this.#state.initialized.generation,
210
+ );
211
+
146
212
  // Initialize the actor instance with proper metadata
147
213
  // This ensures the actor driver knows about this actor
148
- await actorDriver.loadActor(actorId);
214
+ await actorDriver.loadActor(actorIdWithGen);
149
215
 
150
- return this.#actor;
216
+ return this.#state.actor;
151
217
  }
152
218
 
153
- /** RPC called by the service that creates the DO to initialize it. */
154
- async initialize(req: ActorInitRequest) {
155
- // TODO: Need to add this to a core promise that needs to be resolved before start
219
+ /** RPC called to get actor metadata without creating it */
220
+ async getMetadata(): Promise<
221
+ | {
222
+ actorId: string;
223
+ name: string;
224
+ key: ActorKey;
225
+ destroying: boolean;
226
+ }
227
+ | undefined
228
+ > {
229
+ // Query the metadata
230
+ const cursor = this.ctx.storage.sql.exec(
231
+ "SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1",
232
+ );
233
+ const result = cursor.raw().next();
234
+
235
+ if (!result.done && result.value) {
236
+ const name = result.value[0] as string;
237
+ const key = JSON.parse(result.value[1] as string) as ActorKey;
238
+ const destroyed = result.value[2] as number;
239
+ const generation = result.value[3] as number;
240
+
241
+ // Check if destroyed
242
+ if (destroyed) {
243
+ logger().debug({
244
+ msg: "getMetadata: actor is destroyed",
245
+ name,
246
+ key,
247
+ generation,
248
+ });
249
+ return undefined;
250
+ }
156
251
 
157
- await this.ctx.storage.put({
158
- [KEYS.NAME]: req.name,
159
- [KEYS.KEY]: req.key,
160
- [KEYS.PERSIST_DATA]: serializeEmptyPersistData(req.input),
252
+ // Build actor ID with generation
253
+ const doId = this.ctx.id.toString();
254
+ const actorId = buildActorId(doId, generation);
255
+ const destroying =
256
+ globalState.getActorState(this.ctx)?.destroying ?? false;
257
+
258
+ logger().debug({
259
+ msg: "getMetadata: found actor metadata",
260
+ actorId,
261
+ name,
262
+ key,
263
+ generation,
264
+ destroying,
265
+ });
266
+
267
+ return { actorId, name, key, destroying };
268
+ }
269
+
270
+ logger().debug({
271
+ msg: "getMetadata: no metadata found",
161
272
  });
162
- this.#initialized = {
273
+ return undefined;
274
+ }
275
+
276
+ /** RPC called by ManagerDriver.kvGet to read from KV. */
277
+ async managerKvGet(key: Uint8Array): Promise<Uint8Array | null> {
278
+ return kvGet(this.ctx.storage.sql, key);
279
+ }
280
+
281
+ /** RPC called by the manager to create a DO. Can optionally allow existing actors. */
282
+ async create(req: ActorInitRequest): Promise<ActorInitResponse> {
283
+ // Check if actor exists
284
+ const checkCursor = this.ctx.storage.sql.exec(
285
+ "SELECT destroyed, generation FROM _rivetkit_metadata WHERE id = 1",
286
+ );
287
+ const checkResult = checkCursor.raw().next();
288
+
289
+ let created = false;
290
+ let generation = 0;
291
+
292
+ if (!checkResult.done && checkResult.value) {
293
+ const destroyed = checkResult.value[0] as number;
294
+ generation = checkResult.value[1] as number;
295
+
296
+ if (!destroyed) {
297
+ // Actor exists and is not destroyed
298
+ if (!req.allowExisting) {
299
+ // Fail if not allowing existing actors
300
+ logger().debug({
301
+ msg: "create failed: actor already exists",
302
+ name: req.name,
303
+ key: req.key,
304
+ generation,
305
+ });
306
+ return { error: { actorAlreadyExists: true } };
307
+ }
308
+
309
+ // Return existing actor
310
+ logger().debug({
311
+ msg: "actor already exists",
312
+ key: req.key,
313
+ generation,
314
+ });
315
+ const doId = this.ctx.id.toString();
316
+ const actorId = buildActorId(doId, generation);
317
+ return { success: { actorId, created: false } };
318
+ }
319
+
320
+ // Actor exists but is destroyed - resurrect with incremented generation
321
+ generation = generation + 1;
322
+ created = true;
323
+
324
+ // Clear stale actor from previous generation
325
+ // This is necessary because the DO instance may still be in memory
326
+ // with the old #state.actor field from before the destroy
327
+ if (this.#state) {
328
+ this.#state.actor = undefined;
329
+ }
330
+
331
+ logger().debug({
332
+ msg: "resurrecting destroyed actor",
333
+ key: req.key,
334
+ oldGeneration: generation - 1,
335
+ newGeneration: generation,
336
+ });
337
+ } else {
338
+ // No actor exists - will create with generation 0
339
+ generation = 0;
340
+ created = true;
341
+ logger().debug({
342
+ msg: "creating new actor",
343
+ key: req.key,
344
+ generation,
345
+ });
346
+ }
347
+
348
+ // Perform upsert - either inserts new or updates destroyed actor
349
+ this.ctx.storage.sql.exec(
350
+ `INSERT INTO _rivetkit_metadata (id, name, key, destroyed, generation)
351
+ VALUES (1, ?, ?, 0, ?)
352
+ ON CONFLICT(id) DO UPDATE SET
353
+ name = excluded.name,
354
+ key = excluded.key,
355
+ destroyed = 0,
356
+ generation = excluded.generation`,
357
+ req.name,
358
+ JSON.stringify(req.key),
359
+ generation,
360
+ );
361
+
362
+ this.#state.initialized = {
163
363
  name: req.name,
164
364
  key: req.key,
365
+ generation,
165
366
  };
166
367
 
167
- logger().debug("initialized actor", { key: req.key });
368
+ // Build actor ID with generation
369
+ const doId = this.ctx.id.toString();
370
+ const actorId = buildActorId(doId, generation);
371
+
372
+ // Initialize storage and update KV when created or resurrected
373
+ if (created) {
374
+ // Initialize persist data in KV storage
375
+ initializeActorKvStorage(this.ctx.storage.sql, req.input);
376
+
377
+ // Update metadata in the background
378
+ const env = getCloudflareAmbientEnv();
379
+ const actorData = { name: req.name, key: req.key, generation };
380
+ this.ctx.waitUntil(
381
+ env.ACTOR_KV.put(
382
+ GLOBAL_KV_KEYS.actorMetadata(actorId),
383
+ JSON.stringify(actorData),
384
+ ),
385
+ );
386
+ }
168
387
 
169
- // Preemptively actor so the lifecycle hooks are called
388
+ // Preemptively load actor so the lifecycle hooks are called
170
389
  await this.#loadActor();
390
+
391
+ logger().debug({
392
+ msg: created
393
+ ? "actor created/resurrected"
394
+ : "returning existing actor",
395
+ actorId,
396
+ created,
397
+ generation,
398
+ });
399
+
400
+ return { success: { actorId, created } };
171
401
  }
172
402
 
173
403
  async fetch(request: Request): Promise<Response> {
174
- const { actorRouter } = await this.#loadActor();
404
+ const { actorRouter, generation } = await this.#loadActor();
405
+
406
+ // Build actor ID with generation
407
+ const doId = this.ctx.id.toString();
408
+ const actorId = buildActorId(doId, generation);
175
409
 
176
- const actorId = this.ctx.id.toString();
177
410
  return await actorRouter.fetch(request, {
178
411
  actorId,
179
412
  });
180
413
  }
181
414
 
182
415
  async alarm(): Promise<void> {
183
- await this.#loadActor();
184
- const actorId = this.ctx.id.toString();
416
+ const { actorDriver, generation } = await this.#loadActor();
185
417
 
186
- // Get the actor driver
187
- invariant(runConfig.driver, "runConfig.driver");
188
- const managerDriver = runConfig.driver.manager(
189
- registry.config,
190
- runConfig,
191
- );
192
- const inlineClient = createClientWithDriver(
193
- createInlineClientDriver(managerDriver),
194
- );
195
- const actorDriver = runConfig.driver.actor(
196
- registry.config,
197
- runConfig,
198
- managerDriver,
199
- inlineClient,
200
- );
418
+ // Build actor ID with generation
419
+ const doId = this.ctx.id.toString();
420
+ const actorId = buildActorId(doId, generation);
201
421
 
202
422
  // Load the actor instance and trigger alarm
203
423
  const actor = await actorDriver.loadActor(actorId);
204
- await actor._onAlarm();
424
+ await actor.onAlarm();
205
425
  }
206
426
  };
207
427
  }
428
+
429
+ function initializeActorKvStorage(
430
+ sql: SqlStorage,
431
+ input: unknown | undefined,
432
+ ): void {
433
+ const initialKvState = getInitialActorKvState(input);
434
+ for (const [key, value] of initialKvState) {
435
+ kvPut(sql, key, value);
436
+ }
437
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Actor ID utilities for managing actor IDs with generation tracking.
3
+ *
4
+ * Actor IDs are formatted as: `{doId}:{generation}`
5
+ * This allows tracking actor resurrection and preventing stale references.
6
+ */
7
+
8
+ /**
9
+ * Build an actor ID from a Durable Object ID and generation number.
10
+ * @param doId The Durable Object ID
11
+ * @param generation The generation number (increments on resurrection)
12
+ * @returns The formatted actor ID
13
+ */
14
+ export function buildActorId(doId: string, generation: number): string {
15
+ return `${doId}:${generation}`;
16
+ }
17
+
18
+ /**
19
+ * Parse an actor ID into its components.
20
+ * @param actorId The actor ID to parse
21
+ * @returns A tuple of [doId, generation]
22
+ * @throws Error if the actor ID format is invalid
23
+ */
24
+ export function parseActorId(actorId: string): [string, number] {
25
+ const parts = actorId.split(":");
26
+ if (parts.length !== 2) {
27
+ throw new Error(`Invalid actor ID format: ${actorId}`);
28
+ }
29
+
30
+ const [doId, generationStr] = parts;
31
+ const generation = parseInt(generationStr, 10);
32
+
33
+ if (Number.isNaN(generation)) {
34
+ throw new Error(`Invalid generation number in actor ID: ${actorId}`);
35
+ }
36
+
37
+ return [doId, generation];
38
+ }
@@ -0,0 +1,71 @@
1
+ export function kvGet(sql: SqlStorage, key: Uint8Array): Uint8Array | null {
2
+ const cursor = sql.exec(
3
+ "SELECT value FROM _rivetkit_kv_storage WHERE key = ?",
4
+ key,
5
+ );
6
+ const result = cursor.raw().next();
7
+
8
+ if (!result.done && result.value) {
9
+ return toUint8Array(result.value[0]);
10
+ }
11
+ return null;
12
+ }
13
+
14
+ export function kvPut(
15
+ sql: SqlStorage,
16
+ key: Uint8Array,
17
+ value: Uint8Array,
18
+ ): void {
19
+ sql.exec(
20
+ "INSERT OR REPLACE INTO _rivetkit_kv_storage (key, value) VALUES (?, ?)",
21
+ key,
22
+ value,
23
+ );
24
+ }
25
+
26
+ export function kvDelete(sql: SqlStorage, key: Uint8Array): void {
27
+ sql.exec("DELETE FROM _rivetkit_kv_storage WHERE key = ?", key);
28
+ }
29
+
30
+ export function kvListPrefix(
31
+ sql: SqlStorage,
32
+ prefix: Uint8Array,
33
+ ): [Uint8Array, Uint8Array][] {
34
+ const cursor = sql.exec("SELECT key, value FROM _rivetkit_kv_storage");
35
+ const entries: [Uint8Array, Uint8Array][] = [];
36
+
37
+ for (const row of cursor.raw()) {
38
+ const key = toUint8Array(row[0]);
39
+ const value = toUint8Array(row[1]);
40
+
41
+ // Check if key starts with prefix
42
+ if (hasPrefix(key, prefix)) {
43
+ entries.push([key, value]);
44
+ }
45
+ }
46
+
47
+ return entries;
48
+ }
49
+
50
+ // Helper function to convert SqlStorageValue to Uint8Array
51
+ function toUint8Array(
52
+ value: string | number | ArrayBuffer | Uint8Array | null,
53
+ ): Uint8Array {
54
+ if (value instanceof Uint8Array) {
55
+ return value;
56
+ }
57
+ if (value instanceof ArrayBuffer) {
58
+ return new Uint8Array(value);
59
+ }
60
+ throw new Error(
61
+ `Unexpected SQL value type: ${typeof value} (${value?.constructor?.name})`,
62
+ );
63
+ }
64
+
65
+ function hasPrefix(arr: Uint8Array, prefix: Uint8Array): boolean {
66
+ if (prefix.length > arr.length) return false;
67
+ for (let i = 0; i < prefix.length; i++) {
68
+ if (arr[i] !== prefix[i]) return false;
69
+ }
70
+ return true;
71
+ }
package/src/config.ts CHANGED
@@ -1,12 +1,24 @@
1
- import type { Hono } from "hono";
2
- import { RunConfigSchema } from "rivetkit/driver-helpers";
3
- import { z } from "zod";
4
-
5
- export const ConfigSchema = RunConfigSchema.removeDefault()
6
- .omit({ driver: true, getUpgradeWebSocket: true })
7
- .extend({
8
- app: z.custom<Hono>().optional(),
9
- })
10
- .default({});
1
+ import type { Client } from "rivetkit";
2
+ import { z } from "zod/v4";
3
+
4
+ const ConfigSchemaBase = z.object({
5
+ /** Path that the Rivet manager API will be mounted. */
6
+ managerPath: z.string().optional().default("/api/rivet"),
7
+
8
+ /** Runner key for authentication. */
9
+ runnerKey: z.string().optional(),
10
+
11
+ /** Disable the welcome message. */
12
+ noWelcome: z.boolean().optional().default(false),
13
+
14
+ fetch: z
15
+ .custom<
16
+ ExportedHandlerFetchHandler<{ RIVET: Client<any> }, unknown>
17
+ >()
18
+ .optional(),
19
+ });
20
+ export const ConfigSchema = ConfigSchemaBase.default(() =>
21
+ ConfigSchemaBase.parse({}),
22
+ );
11
23
  export type InputConfig = z.input<typeof ConfigSchema>;
12
24
  export type Config = z.infer<typeof ConfigSchema>;
@@ -0,0 +1,6 @@
1
+ /** KV keys for using Workers KV to store actor metadata globally. */
2
+ export const GLOBAL_KV_KEYS = {
3
+ actorMetadata: (actorId: string): string => {
4
+ return `actor:${actorId}:metadata`;
5
+ },
6
+ };