@rivetkit/cloudflare-workers 2.2.2-rc.1 → 2.3.0-rc.14
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 +44 -6
- package/dist/mod.d.mts +44 -0
- package/dist/mod.d.ts +37 -81
- package/dist/mod.js +123 -1064
- package/dist/mod.js.map +1 -1
- package/dist/mod.mjs +152 -0
- package/dist/mod.mjs.map +1 -0
- package/package.json +52 -56
- package/LICENSE +0 -203
- package/dist/mod.cjs +0 -1093
- package/dist/mod.cjs.map +0 -1
- package/dist/mod.d.cts +0 -88
- package/src/actor-driver.ts +0 -373
- package/src/actor-handler-do.ts +0 -440
- package/src/actor-id.ts +0 -38
- package/src/actor-kv.ts +0 -120
- package/src/config.ts +0 -22
- package/src/global-kv.ts +0 -6
- package/src/handler.ts +0 -150
- package/src/log.ts +0 -5
- package/src/manager-driver.ts +0 -444
- package/src/mod.ts +0 -11
- package/src/util.ts +0 -104
- package/src/websocket.ts +0 -81
package/src/actor-handler-do.ts
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import { DurableObject, env } from "cloudflare:workers";
|
|
2
|
-
import type { ExecutionContext } from "hono";
|
|
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";
|
|
10
|
-
import {
|
|
11
|
-
ActorGlobalState,
|
|
12
|
-
CloudflareDurableObjectGlobalState,
|
|
13
|
-
createCloudflareActorsActorDriverBuilder,
|
|
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";
|
|
18
|
-
import type { Bindings } from "./handler";
|
|
19
|
-
import { getCloudflareAmbientEnv } from "./handler";
|
|
20
|
-
import { logger } from "./log";
|
|
21
|
-
import { CloudflareActorsManagerDriver } from "./manager-driver";
|
|
22
|
-
|
|
23
|
-
export interface ActorHandlerInterface extends DurableObject {
|
|
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>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ActorInitRequest {
|
|
38
|
-
name: string;
|
|
39
|
-
key: ActorKey;
|
|
40
|
-
input?: unknown;
|
|
41
|
-
allowExisting: boolean;
|
|
42
|
-
}
|
|
43
|
-
export type ActorInitResponse =
|
|
44
|
-
| { success: { actorId: string; created: boolean } }
|
|
45
|
-
| { error: { actorAlreadyExists: true } };
|
|
46
|
-
|
|
47
|
-
export type DurableObjectConstructor = new (
|
|
48
|
-
...args: ConstructorParameters<typeof DurableObject<Bindings>>
|
|
49
|
-
) => DurableObject<Bindings>;
|
|
50
|
-
|
|
51
|
-
export function createActorDurableObject(
|
|
52
|
-
registry: Registry<any>,
|
|
53
|
-
getUpgradeWebSocket: GetUpgradeWebSocket,
|
|
54
|
-
): DurableObjectConstructor {
|
|
55
|
-
const globalState = new CloudflareDurableObjectGlobalState();
|
|
56
|
-
const parsedConfig = registry.parseConfig();
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Startup steps:
|
|
60
|
-
* 1. If not already created call `initialize`, otherwise check KV to ensure it's initialized
|
|
61
|
-
* 2. Load actor
|
|
62
|
-
* 3. Start service requests
|
|
63
|
-
*/
|
|
64
|
-
return class ActorHandler
|
|
65
|
-
extends DurableObject<Bindings>
|
|
66
|
-
implements ActorHandlerInterface
|
|
67
|
-
{
|
|
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
|
-
}
|
|
112
|
-
|
|
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 };
|
|
142
|
-
} else {
|
|
143
|
-
logger().debug("actor is destroyed, cannot load");
|
|
144
|
-
throw new Error("Actor is destroyed");
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
logger().debug("not initialized");
|
|
148
|
-
throw new Error("Actor is not initialized");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Check if already loaded
|
|
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;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (!this.#state.initialized) throw new Error("Not initialized");
|
|
166
|
-
|
|
167
|
-
// Register DO with global state first
|
|
168
|
-
// HACK: This leaks the DO context, but DO does not provide a native way
|
|
169
|
-
// of knowing when the DO shuts down. We're making a broad assumption
|
|
170
|
-
// that DO will boot a new isolate frequenlty enough that this is not an issue.
|
|
171
|
-
const actorId = this.ctx.id.toString();
|
|
172
|
-
globalState.setDOState(actorId, { ctx: this.ctx, env: env });
|
|
173
|
-
|
|
174
|
-
// Create manager driver
|
|
175
|
-
const managerDriver = new CloudflareActorsManagerDriver();
|
|
176
|
-
|
|
177
|
-
// Create inline client
|
|
178
|
-
// Avoid expensive type expansion in downstream DTS generation.
|
|
179
|
-
const inlineClient: any = (createClientWithDriver as any)(
|
|
180
|
-
managerDriver,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
// Create actor driver builder
|
|
184
|
-
const actorDriverBuilder =
|
|
185
|
-
createCloudflareActorsActorDriverBuilder(globalState);
|
|
186
|
-
|
|
187
|
-
// Create actor driver
|
|
188
|
-
const actorDriver = actorDriverBuilder(
|
|
189
|
-
parsedConfig,
|
|
190
|
-
managerDriver,
|
|
191
|
-
inlineClient,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
// Create actor router
|
|
195
|
-
const actorRouter = createActorRouter(
|
|
196
|
-
parsedConfig,
|
|
197
|
-
actorDriver,
|
|
198
|
-
getUpgradeWebSocket,
|
|
199
|
-
registry.config.test?.enabled ?? false,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
// Save actor with generation
|
|
203
|
-
this.#state.actor = {
|
|
204
|
-
actorRouter,
|
|
205
|
-
actorDriver,
|
|
206
|
-
generation: this.#state.initialized.generation,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
// Build actor ID with generation for loading
|
|
210
|
-
const actorIdWithGen = buildActorId(
|
|
211
|
-
actorId,
|
|
212
|
-
this.#state.initialized.generation,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Initialize the actor instance with proper metadata
|
|
216
|
-
// This ensures the actor driver knows about this actor
|
|
217
|
-
await actorDriver.loadActor(actorIdWithGen);
|
|
218
|
-
|
|
219
|
-
return this.#state.actor;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** RPC called to get actor metadata without creating it */
|
|
223
|
-
async getMetadata(): Promise<
|
|
224
|
-
| {
|
|
225
|
-
actorId: string;
|
|
226
|
-
name: string;
|
|
227
|
-
key: ActorKey;
|
|
228
|
-
destroying: boolean;
|
|
229
|
-
}
|
|
230
|
-
| undefined
|
|
231
|
-
> {
|
|
232
|
-
// Query the metadata
|
|
233
|
-
const cursor = this.ctx.storage.sql.exec(
|
|
234
|
-
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1",
|
|
235
|
-
);
|
|
236
|
-
const result = cursor.raw().next();
|
|
237
|
-
|
|
238
|
-
if (!result.done && result.value) {
|
|
239
|
-
const name = result.value[0] as string;
|
|
240
|
-
const key = JSON.parse(result.value[1] as string) as ActorKey;
|
|
241
|
-
const destroyed = result.value[2] as number;
|
|
242
|
-
const generation = result.value[3] as number;
|
|
243
|
-
|
|
244
|
-
// Check if destroyed
|
|
245
|
-
if (destroyed) {
|
|
246
|
-
logger().debug({
|
|
247
|
-
msg: "getMetadata: actor is destroyed",
|
|
248
|
-
name,
|
|
249
|
-
key,
|
|
250
|
-
generation,
|
|
251
|
-
});
|
|
252
|
-
return undefined;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Build actor ID with generation
|
|
256
|
-
const doId = this.ctx.id.toString();
|
|
257
|
-
const actorId = buildActorId(doId, generation);
|
|
258
|
-
const destroying =
|
|
259
|
-
globalState.getActorState(this.ctx)?.destroying ?? false;
|
|
260
|
-
|
|
261
|
-
logger().debug({
|
|
262
|
-
msg: "getMetadata: found actor metadata",
|
|
263
|
-
actorId,
|
|
264
|
-
name,
|
|
265
|
-
key,
|
|
266
|
-
generation,
|
|
267
|
-
destroying,
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
return { actorId, name, key, destroying };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
logger().debug({
|
|
274
|
-
msg: "getMetadata: no metadata found",
|
|
275
|
-
});
|
|
276
|
-
return undefined;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** RPC called by ManagerDriver.kvGet to read from KV. */
|
|
280
|
-
async managerKvGet(key: Uint8Array): Promise<Uint8Array | null> {
|
|
281
|
-
return kvGet(this.ctx.storage.sql, key);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/** RPC called by the manager to create a DO. Can optionally allow existing actors. */
|
|
285
|
-
async create(req: ActorInitRequest): Promise<ActorInitResponse> {
|
|
286
|
-
// Check if actor exists
|
|
287
|
-
const checkCursor = this.ctx.storage.sql.exec(
|
|
288
|
-
"SELECT destroyed, generation FROM _rivetkit_metadata WHERE id = 1",
|
|
289
|
-
);
|
|
290
|
-
const checkResult = checkCursor.raw().next();
|
|
291
|
-
|
|
292
|
-
let created = false;
|
|
293
|
-
let generation = 0;
|
|
294
|
-
|
|
295
|
-
if (!checkResult.done && checkResult.value) {
|
|
296
|
-
const destroyed = checkResult.value[0] as number;
|
|
297
|
-
generation = checkResult.value[1] as number;
|
|
298
|
-
|
|
299
|
-
if (!destroyed) {
|
|
300
|
-
// Actor exists and is not destroyed
|
|
301
|
-
if (!req.allowExisting) {
|
|
302
|
-
// Fail if not allowing existing actors
|
|
303
|
-
logger().debug({
|
|
304
|
-
msg: "create failed: actor already exists",
|
|
305
|
-
name: req.name,
|
|
306
|
-
key: req.key,
|
|
307
|
-
generation,
|
|
308
|
-
});
|
|
309
|
-
return { error: { actorAlreadyExists: true } };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Return existing actor
|
|
313
|
-
logger().debug({
|
|
314
|
-
msg: "actor already exists",
|
|
315
|
-
key: req.key,
|
|
316
|
-
generation,
|
|
317
|
-
});
|
|
318
|
-
const doId = this.ctx.id.toString();
|
|
319
|
-
const actorId = buildActorId(doId, generation);
|
|
320
|
-
return { success: { actorId, created: false } };
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Actor exists but is destroyed - resurrect with incremented generation
|
|
324
|
-
generation = generation + 1;
|
|
325
|
-
created = true;
|
|
326
|
-
|
|
327
|
-
// Clear stale actor from previous generation
|
|
328
|
-
// This is necessary because the DO instance may still be in memory
|
|
329
|
-
// with the old #state.actor field from before the destroy
|
|
330
|
-
if (this.#state) {
|
|
331
|
-
this.#state.actor = undefined;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
logger().debug({
|
|
335
|
-
msg: "resurrecting destroyed actor",
|
|
336
|
-
key: req.key,
|
|
337
|
-
oldGeneration: generation - 1,
|
|
338
|
-
newGeneration: generation,
|
|
339
|
-
});
|
|
340
|
-
} else {
|
|
341
|
-
// No actor exists - will create with generation 0
|
|
342
|
-
generation = 0;
|
|
343
|
-
created = true;
|
|
344
|
-
logger().debug({
|
|
345
|
-
msg: "creating new actor",
|
|
346
|
-
key: req.key,
|
|
347
|
-
generation,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Perform upsert - either inserts new or updates destroyed actor
|
|
352
|
-
this.ctx.storage.sql.exec(
|
|
353
|
-
`INSERT INTO _rivetkit_metadata (id, name, key, destroyed, generation)
|
|
354
|
-
VALUES (1, ?, ?, 0, ?)
|
|
355
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
356
|
-
name = excluded.name,
|
|
357
|
-
key = excluded.key,
|
|
358
|
-
destroyed = 0,
|
|
359
|
-
generation = excluded.generation`,
|
|
360
|
-
req.name,
|
|
361
|
-
JSON.stringify(req.key),
|
|
362
|
-
generation,
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
this.#state.initialized = {
|
|
366
|
-
name: req.name,
|
|
367
|
-
key: req.key,
|
|
368
|
-
generation,
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// Build actor ID with generation
|
|
372
|
-
const doId = this.ctx.id.toString();
|
|
373
|
-
const actorId = buildActorId(doId, generation);
|
|
374
|
-
|
|
375
|
-
// Initialize storage and update KV when created or resurrected
|
|
376
|
-
if (created) {
|
|
377
|
-
// Initialize persist data in KV storage
|
|
378
|
-
initializeActorKvStorage(this.ctx.storage.sql, req.input);
|
|
379
|
-
|
|
380
|
-
// Update metadata in the background
|
|
381
|
-
const env = getCloudflareAmbientEnv();
|
|
382
|
-
const actorData = { name: req.name, key: req.key, generation };
|
|
383
|
-
this.ctx.waitUntil(
|
|
384
|
-
env.ACTOR_KV.put(
|
|
385
|
-
GLOBAL_KV_KEYS.actorMetadata(actorId),
|
|
386
|
-
JSON.stringify(actorData),
|
|
387
|
-
),
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Preemptively load actor so the lifecycle hooks are called
|
|
392
|
-
await this.#loadActor();
|
|
393
|
-
|
|
394
|
-
logger().debug({
|
|
395
|
-
msg: created
|
|
396
|
-
? "actor created/resurrected"
|
|
397
|
-
: "returning existing actor",
|
|
398
|
-
actorId,
|
|
399
|
-
created,
|
|
400
|
-
generation,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return { success: { actorId, created } };
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async fetch(request: Request): Promise<Response> {
|
|
407
|
-
const { actorRouter, generation } = await this.#loadActor();
|
|
408
|
-
|
|
409
|
-
// Build actor ID with generation
|
|
410
|
-
const doId = this.ctx.id.toString();
|
|
411
|
-
const actorId = buildActorId(doId, generation);
|
|
412
|
-
|
|
413
|
-
return await actorRouter.fetch(request, {
|
|
414
|
-
actorId,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async alarm(): Promise<void> {
|
|
419
|
-
const { actorDriver, generation } = await this.#loadActor();
|
|
420
|
-
|
|
421
|
-
// Build actor ID with generation
|
|
422
|
-
const doId = this.ctx.id.toString();
|
|
423
|
-
const actorId = buildActorId(doId, generation);
|
|
424
|
-
|
|
425
|
-
// Load the actor instance and trigger alarm
|
|
426
|
-
const actor = await actorDriver.loadActor(actorId);
|
|
427
|
-
await actor.onAlarm();
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function initializeActorKvStorage(
|
|
433
|
-
sql: SqlStorage,
|
|
434
|
-
input: unknown | undefined,
|
|
435
|
-
): void {
|
|
436
|
-
const initialKvState = getInitialActorKvState(input);
|
|
437
|
-
for (const [key, value] of initialKvState) {
|
|
438
|
-
kvPut(sql, key, value);
|
|
439
|
-
}
|
|
440
|
-
}
|
package/src/actor-id.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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
|
-
}
|
package/src/actor-kv.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
const DEFAULT_LIST_LIMIT = 16_384;
|
|
2
|
-
|
|
3
|
-
export function kvGet(sql: SqlStorage, key: Uint8Array): Uint8Array | null {
|
|
4
|
-
const cursor = sql.exec(
|
|
5
|
-
"SELECT value FROM _rivetkit_kv_storage WHERE key = ?",
|
|
6
|
-
key,
|
|
7
|
-
);
|
|
8
|
-
const result = cursor.raw().next();
|
|
9
|
-
|
|
10
|
-
if (!result.done && result.value) {
|
|
11
|
-
return toUint8Array(result.value[0]);
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function kvPut(
|
|
17
|
-
sql: SqlStorage,
|
|
18
|
-
key: Uint8Array,
|
|
19
|
-
value: Uint8Array,
|
|
20
|
-
): void {
|
|
21
|
-
sql.exec(
|
|
22
|
-
"INSERT OR REPLACE INTO _rivetkit_kv_storage (key, value) VALUES (?, ?)",
|
|
23
|
-
key,
|
|
24
|
-
value,
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function kvDelete(sql: SqlStorage, key: Uint8Array): void {
|
|
29
|
-
sql.exec("DELETE FROM _rivetkit_kv_storage WHERE key = ?", key);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function kvDeleteRange(
|
|
33
|
-
sql: SqlStorage,
|
|
34
|
-
start: Uint8Array,
|
|
35
|
-
end: Uint8Array,
|
|
36
|
-
): void {
|
|
37
|
-
sql.exec(
|
|
38
|
-
"DELETE FROM _rivetkit_kv_storage WHERE key >= ? AND key < ?",
|
|
39
|
-
start,
|
|
40
|
-
end,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function kvListPrefix(
|
|
45
|
-
sql: SqlStorage,
|
|
46
|
-
prefix: Uint8Array,
|
|
47
|
-
options?: {
|
|
48
|
-
reverse?: boolean;
|
|
49
|
-
limit?: number;
|
|
50
|
-
},
|
|
51
|
-
): [Uint8Array, Uint8Array][] {
|
|
52
|
-
const upperBound = computePrefixUpperBound(prefix);
|
|
53
|
-
if (upperBound) {
|
|
54
|
-
return kvListRange(sql, prefix, upperBound, options);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const direction = options?.reverse ? "DESC" : "ASC";
|
|
58
|
-
const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
|
|
59
|
-
const cursor = sql.exec(
|
|
60
|
-
`SELECT key, value FROM _rivetkit_kv_storage WHERE key >= ? ORDER BY key ${direction} LIMIT ?`,
|
|
61
|
-
prefix,
|
|
62
|
-
limit,
|
|
63
|
-
);
|
|
64
|
-
return readEntries(cursor);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function kvListRange(
|
|
68
|
-
sql: SqlStorage,
|
|
69
|
-
start: Uint8Array,
|
|
70
|
-
end: Uint8Array,
|
|
71
|
-
options?: {
|
|
72
|
-
reverse?: boolean;
|
|
73
|
-
limit?: number;
|
|
74
|
-
},
|
|
75
|
-
): [Uint8Array, Uint8Array][] {
|
|
76
|
-
const direction = options?.reverse ? "DESC" : "ASC";
|
|
77
|
-
const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
|
|
78
|
-
const cursor = sql.exec(
|
|
79
|
-
`SELECT key, value FROM _rivetkit_kv_storage WHERE key >= ? AND key < ? ORDER BY key ${direction} LIMIT ?`,
|
|
80
|
-
start,
|
|
81
|
-
end,
|
|
82
|
-
limit,
|
|
83
|
-
);
|
|
84
|
-
return readEntries(cursor);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function toUint8Array(
|
|
88
|
-
value: string | number | ArrayBuffer | Uint8Array | null,
|
|
89
|
-
): Uint8Array {
|
|
90
|
-
if (value instanceof Uint8Array) {
|
|
91
|
-
return value;
|
|
92
|
-
}
|
|
93
|
-
if (value instanceof ArrayBuffer) {
|
|
94
|
-
return new Uint8Array(value);
|
|
95
|
-
}
|
|
96
|
-
throw new Error(
|
|
97
|
-
`Unexpected SQL value type: ${typeof value} (${value?.constructor?.name})`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function readEntries(
|
|
102
|
-
cursor: ReturnType<SqlStorage["exec"]>,
|
|
103
|
-
): [Uint8Array, Uint8Array][] {
|
|
104
|
-
const entries: [Uint8Array, Uint8Array][] = [];
|
|
105
|
-
for (const row of cursor.raw()) {
|
|
106
|
-
entries.push([toUint8Array(row[0]), toUint8Array(row[1])]);
|
|
107
|
-
}
|
|
108
|
-
return entries;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function computePrefixUpperBound(prefix: Uint8Array): Uint8Array | null {
|
|
112
|
-
const upperBound = prefix.slice();
|
|
113
|
-
for (let i = upperBound.length - 1; i >= 0; i--) {
|
|
114
|
-
if (upperBound[i] !== 0xff) {
|
|
115
|
-
upperBound[i]++;
|
|
116
|
-
return upperBound.slice(0, i + 1);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
/** Deprecated. 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<ExportedHandlerFetchHandler<{ RIVET: Client<any> }, unknown>>()
|
|
16
|
-
.optional(),
|
|
17
|
-
});
|
|
18
|
-
export const ConfigSchema = ConfigSchemaBase.default(() =>
|
|
19
|
-
ConfigSchemaBase.parse({}),
|
|
20
|
-
);
|
|
21
|
-
export type InputConfig = z.input<typeof ConfigSchema>;
|
|
22
|
-
export type Config = z.infer<typeof ConfigSchema>;
|