@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.
@@ -1,62 +1,59 @@
1
1
  import { DurableObject, env } from "cloudflare:workers";
2
- import type {
3
- ActorKey,
4
- ActorRouter,
5
- Registry,
6
- RunConfig,
7
- } from "@rivetkit/core";
8
- import {
9
- createActorRouter,
10
- createClientWithDriver,
11
- createInlineClientDriver,
12
- } from "@rivetkit/core";
13
- import { serializeEmptyPersistData } from "@rivetkit/core/driver-helpers";
14
2
  import type { ExecutionContext } from "hono";
15
3
  import invariant from "invariant";
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";
16
10
  import {
11
+ ActorGlobalState,
17
12
  CloudflareDurableObjectGlobalState,
18
13
  createCloudflareActorsActorDriverBuilder,
19
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";
20
18
  import type { Bindings } from "./handler";
19
+ import { getCloudflareAmbientEnv } from "./handler";
21
20
  import { logger } from "./log";
22
-
23
- export const KEYS = {
24
- NAME: "rivetkit:name",
25
- KEY: "rivetkit:key",
26
- PERSIST_DATA: "rivetkit:data",
27
- };
21
+ import { CloudflareActorsManagerDriver } from "./manager-driver";
28
22
 
29
23
  export interface ActorHandlerInterface extends DurableObject {
30
- 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>;
31
35
  }
32
36
 
33
37
  export interface ActorInitRequest {
34
38
  name: string;
35
39
  key: ActorKey;
36
40
  input?: unknown;
41
+ allowExisting: boolean;
37
42
  }
38
-
39
- interface InitializedData {
40
- name: string;
41
- key: ActorKey;
42
- }
43
+ export type ActorInitResponse =
44
+ | { success: { actorId: string; created: boolean } }
45
+ | { error: { actorAlreadyExists: true } };
43
46
 
44
47
  export type DurableObjectConstructor = new (
45
48
  ...args: ConstructorParameters<typeof DurableObject<Bindings>>
46
49
  ) => DurableObject<Bindings>;
47
50
 
48
- interface LoadedActor {
49
- actorRouter: ActorRouter;
50
- }
51
-
52
51
  export function createActorDurableObject(
53
52
  registry: Registry<any>,
54
- rootRunConfig: RunConfig,
53
+ getUpgradeWebSocket: GetUpgradeWebSocket,
55
54
  ): DurableObjectConstructor {
56
55
  const globalState = new CloudflareDurableObjectGlobalState();
57
-
58
- // Configure to use the runner role instead of server role
59
- const runConfig = Object.assign({}, rootRunConfig, { role: "runner" });
56
+ const parsedConfig = registry.parseConfig();
60
57
 
61
58
  /**
62
59
  * Startup steps:
@@ -68,46 +65,104 @@ export function createActorDurableObject(
68
65
  extends DurableObject<Bindings>
69
66
  implements ActorHandlerInterface
70
67
  {
71
- #initialized?: InitializedData;
72
- #initializedPromise?: PromiseWithResolvers<void>;
73
-
74
- #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
+ }
75
112
 
76
- async #loadActor(): Promise<LoadedActor> {
77
- // Wait for init
78
- if (!this.#initialized) {
79
- // Wait for init
80
- if (this.#initializedPromise) {
81
- await this.#initializedPromise.promise;
82
- } else {
83
- this.#initializedPromise = Promise.withResolvers();
84
- const res = await this.ctx.storage.get([
85
- KEYS.NAME,
86
- KEYS.KEY,
87
- KEYS.PERSIST_DATA,
88
- ]);
89
- if (res.get(KEYS.PERSIST_DATA)) {
90
- const name = res.get(KEYS.NAME) as string;
91
- if (!name) throw new Error("missing actor name");
92
- const key = res.get(KEYS.KEY) as ActorKey;
93
- if (!key) throw new Error("missing actor key");
94
-
95
- logger().debug("already initialized", { name, key });
96
-
97
- this.#initialized = { name, key };
98
- 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 };
99
142
  } else {
100
- logger().debug("waiting to initialize");
143
+ logger().debug("actor is destroyed, cannot load");
144
+ throw new Error("Actor is destroyed");
101
145
  }
146
+ } else {
147
+ logger().debug("not initialized");
148
+ throw new Error("Actor is not initialized");
102
149
  }
103
150
  }
104
151
 
105
152
  // Check if already loaded
106
- if (this.#actor) {
107
- 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;
108
163
  }
109
164
 
110
- if (!this.#initialized) throw new Error("Not initialized");
165
+ if (!this.#state.initialized) throw new Error("Not initialized");
111
166
 
112
167
  // Register DO with global state first
113
168
  // HACK: This leaks the DO context, but DO does not provide a native way
@@ -116,93 +171,253 @@ export function createActorDurableObject(
116
171
  const actorId = this.ctx.id.toString();
117
172
  globalState.setDOState(actorId, { ctx: this.ctx, env: env });
118
173
 
119
- // Configure actor driver
120
- invariant(runConfig.driver, "runConfig.driver");
121
- runConfig.driver.actor =
122
- createCloudflareActorsActorDriverBuilder(globalState);
123
-
124
- // Create manager driver (we need this for the actor router)
125
- const managerDriver = runConfig.driver.manager(
126
- registry.config,
127
- runConfig,
128
- );
174
+ // Create manager driver
175
+ const managerDriver = new CloudflareActorsManagerDriver();
129
176
 
130
177
  // Create inline client
131
- const inlineClient = createClientWithDriver(
132
- createInlineClientDriver(managerDriver),
133
- );
178
+ const inlineClient = createClientWithDriver(managerDriver);
179
+
180
+ // Create actor driver builder
181
+ const actorDriverBuilder =
182
+ createCloudflareActorsActorDriverBuilder(globalState);
134
183
 
135
184
  // Create actor driver
136
- const actorDriver = runConfig.driver.actor(
137
- registry.config,
138
- runConfig,
185
+ const actorDriver = actorDriverBuilder(
186
+ parsedConfig,
139
187
  managerDriver,
140
188
  inlineClient,
141
189
  );
142
190
 
143
191
  // Create actor router
144
- const actorRouter = createActorRouter(runConfig, actorDriver);
192
+ const actorRouter = createActorRouter(
193
+ parsedConfig,
194
+ actorDriver,
195
+ getUpgradeWebSocket,
196
+ registry.config.test?.enabled ?? false,
197
+ );
145
198
 
146
- // Save actor
147
- this.#actor = {
199
+ // Save actor with generation
200
+ this.#state.actor = {
148
201
  actorRouter,
202
+ actorDriver,
203
+ generation: this.#state.initialized.generation,
149
204
  };
150
205
 
206
+ // Build actor ID with generation for loading
207
+ const actorIdWithGen = buildActorId(
208
+ actorId,
209
+ this.#state.initialized.generation,
210
+ );
211
+
151
212
  // Initialize the actor instance with proper metadata
152
213
  // This ensures the actor driver knows about this actor
153
- await actorDriver.loadActor(actorId);
214
+ await actorDriver.loadActor(actorIdWithGen);
154
215
 
155
- return this.#actor;
216
+ return this.#state.actor;
156
217
  }
157
218
 
158
- /** RPC called by the service that creates the DO to initialize it. */
159
- async initialize(req: ActorInitRequest) {
160
- // 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
+ }
161
251
 
162
- await this.ctx.storage.put({
163
- [KEYS.NAME]: req.name,
164
- [KEYS.KEY]: req.key,
165
- [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",
166
272
  });
167
- 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 = {
168
363
  name: req.name,
169
364
  key: req.key,
365
+ generation,
170
366
  };
171
367
 
172
- 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
+ }
173
387
 
174
- // Preemptively actor so the lifecycle hooks are called
388
+ // Preemptively load actor so the lifecycle hooks are called
175
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 } };
176
401
  }
177
402
 
178
403
  async fetch(request: Request): Promise<Response> {
179
- 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);
180
409
 
181
- const actorId = this.ctx.id.toString();
182
410
  return await actorRouter.fetch(request, {
183
411
  actorId,
184
412
  });
185
413
  }
186
414
 
187
415
  async alarm(): Promise<void> {
188
- await this.#loadActor();
189
- const actorId = this.ctx.id.toString();
416
+ const { actorDriver, generation } = await this.#loadActor();
190
417
 
191
- // Get the actor driver
192
- invariant(runConfig.driver, "runConfig.driver");
193
- const managerDriver = runConfig.driver.manager(
194
- registry.config,
195
- runConfig,
196
- );
197
- const inlineClient = createClientWithDriver(
198
- createInlineClientDriver(managerDriver),
199
- );
200
- const actorDriver = runConfig.driver.actor(
201
- registry.config,
202
- runConfig,
203
- managerDriver,
204
- inlineClient,
205
- );
418
+ // Build actor ID with generation
419
+ const doId = this.ctx.id.toString();
420
+ const actorId = buildActorId(doId, generation);
206
421
 
207
422
  // Load the actor instance and trigger alarm
208
423
  const actor = await actorDriver.loadActor(actorId);
@@ -210,3 +425,13 @@ export function createActorDurableObject(
210
425
  }
211
426
  };
212
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 { RunConfigSchema } from "@rivetkit/core/driver-helpers";
2
- import type { Hono } from "hono";
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
+ };