Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Commit ed04f22

Browse files
committed
fix(studio): allow replacing the whole state, catch unhandled promises (#1122)
### TL;DR Removed database dependencies from the counter example and simplified the actor state structure. ### What changed? - Removed database integration from the counter example: - Deleted `dizzle.config.ts` and `schema.ts` files - Removed database-related dependencies from `package.json` (drizzle-orm, better-sqlite3, drizzle-kit) - Removed `@rivetkit/db` dependency - Simplified the counter example's actor state to only include the essential `count` property - Removed the database initialization and unused code in the registry - Updated the file system global state implementation to improve actor state loading - Enhanced the inspector API to support both patching and replacing state - Updated Zod import to use v4 - Added error handling for actor fetching in the manager inspector ### How to test? 1. Run the counter example to verify it works without database dependencies: ``` cd examples/counter pnpm install pnpm start ``` 2. Test the inspector API's new state replacement functionality by making a PATCH request with a `replace` property 3. Verify that actor state loading works correctly with the updated implementation ### Why make this change? This change simplifies the counter example by removing unnecessary database dependencies, making it more focused on demonstrating the core actor functionality. It also improves the state management implementation and enhances the inspector API with better error handling and the ability to completely replace state, providing more flexibility for debugging and development.
1 parent f507767 commit ed04f22

File tree

9 files changed

+54
-166
lines changed

9 files changed

+54
-166
lines changed

examples/counter/dizzle.config.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

examples/counter/package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,10 @@
1010
},
1111
"devDependencies": {
1212
"@rivetkit/actor": "workspace:*",
13-
"@rivetkit/db": "workspace:*",
1413
"@types/node": "^22.13.9",
1514
"tsx": "^3.12.7",
1615
"typescript": "^5.7.3",
1716
"vitest": "^3.1.1"
1817
},
19-
"stableVersion": "0.8.0",
20-
"dependencies": {
21-
"better-sqlite3": "^11.10.0",
22-
"drizzle-kit": "^0.31.2",
23-
"drizzle-orm": "^0.44.2"
24-
}
18+
"stableVersion": "0.8.0"
2519
}

examples/counter/src/registry.ts

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,14 @@
11
import { actor, setup } from "@rivetkit/actor";
2-
import { db } from "@rivetkit/db/drizzle";
3-
import * as schema from "./schema";
42

53
const counter = actor({
6-
db: db({ schema }),
74
state: {
85
count: 0,
9-
boolean: false,
10-
string: "",
11-
number: 0,
12-
array: [{ id: 1, value: "value", object: { key: "value" } }],
13-
object: { key: "value" },
14-
nestedObject: { nestedKey: "nestedValue" },
15-
nestedArray: [{ id: 1, value: "value", object: { key: "value" } }],
16-
nestedObjectArray: [{ id: 1, value: "value", object: { key: "value" } }],
17-
nestedArrayObject: [{ id: 1, value: "value", object: { key: "value" } }],
18-
nestedArrays: [
19-
[
20-
{
21-
id: 1,
22-
value: "value",
23-
object: { key: "value" },
24-
array: [{ key: "value", array: [{ key: "value" }] }],
25-
},
26-
],
27-
[
28-
{
29-
id: 2,
30-
value: "value2",
31-
object: { key: "value2" },
32-
array: [{ key: "value", array: [{ key: "value" }] }],
33-
},
34-
],
35-
],
36-
undefined: undefined,
37-
date: new Date(),
38-
dateArray: [new Date(), new Date()],
39-
dateObject: { date: new Date() },
40-
null: null,
416
},
427
onAuth: () => {
438
return true;
449
},
45-
onStart: (c) => {
46-
c.schedule.after(1000, "increment", 1);
47-
48-
c.state.count = 0;
49-
},
5010
actions: {
5111
increment: (c, x: number) => {
52-
// c.db.insert(schema.usersTable).values({
53-
// name: "John Doe",
54-
// age: 30,
55-
// email: "john.doe@example.com",
56-
// });
5712
c.state.count += x;
5813
c.broadcast("newCount", c.state.count);
5914
return c.state.count;

examples/counter/src/schema.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

packages/core/src/drivers/file-system/global-state.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ interface ActorEntry {
3737

3838
state?: ActorState;
3939
/** Promise for loading the actor state. */
40-
loadPromise?: PromiseWithResolvers<void>;
40+
loadPromise?: Promise<ActorEntry>;
4141

4242
actor?: AnyActorInstance;
4343
/** Promise for starting the actor. */
@@ -202,24 +202,18 @@ export class FileSystemGlobalState {
202202

203203
// If state is currently being loaded, wait for it
204204
if (entry.loadPromise) {
205-
await entry.loadPromise.promise;
205+
await entry.loadPromise;
206206
return entry;
207207
}
208208

209209
// Start loading state
210-
entry.loadPromise = Promise.withResolvers();
210+
entry.loadPromise = this.loadActorState(entry);
211+
return entry.loadPromise;
212+
}
211213

214+
private async loadActorState(entry: ActorEntry) {
212215
const stateFilePath = getActorDataPath(this.#storagePath, entry.id);
213216

214-
// Check if file exists
215-
try {
216-
await fs.access(stateFilePath);
217-
} catch {
218-
// Actor does not exist
219-
entry.loadPromise.resolve(undefined);
220-
return entry;
221-
}
222-
223217
// Read & parse file
224218
try {
225219
const stateData = await fs.readFile(stateFilePath);
@@ -230,15 +224,11 @@ export class FileSystemGlobalState {
230224

231225
// Cache the loaded state in handler
232226
entry.state = state;
233-
entry.loadPromise.resolve();
234-
entry.loadPromise = undefined;
235227

236228
return entry;
237229
} catch (innerError) {
238230
// Failed to read actor, so reset promise to retry next time
239231
const error = new Error(`Failed to load actor state: ${innerError}`);
240-
entry.loadPromise?.reject(error);
241-
entry.loadPromise = undefined;
242232
throw error;
243233
}
244234
}

packages/core/src/drivers/file-system/manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class FileSystemManagerDriver implements ManagerDriver {
7979
getAllActors: async ({ cursor, limit }) => {
8080
const itr = this.#state.getActorsIterator({ cursor });
8181
const actors: Actor[] = [];
82+
8283
for await (const actor of itr) {
8384
actors.push(transformActor(actor));
8485
if (limit && actors.length >= limit) {

packages/core/src/inspector/actor.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import jsonPatch from "fast-json-patch";
33
import { Hono } from "hono";
44
import { streamSSE } from "hono/streaming";
55
import { createNanoEvents, type Unsubscribe } from "nanoevents";
6-
import z from "zod";
6+
import z from "zod/v4";
77
import type {
88
AnyDatabaseProvider,
99
InferDatabaseClient,
@@ -45,22 +45,42 @@ export function createActorInspectorRouter() {
4545
}
4646
return c.json({ enabled: false, state: null }, 200);
4747
})
48-
.patch("/state", sValidator("json", PatchSchema), async (c) => {
49-
if (!(await c.var.inspector.accessors.isStateEnabled())) {
50-
return c.json({ enabled: false }, 200);
51-
}
48+
.patch(
49+
"/state",
50+
sValidator(
51+
"json",
52+
z.object({ patch: PatchSchema }).or(z.object({ replace: z.any() })),
53+
),
54+
async (c) => {
55+
if (!(await c.var.inspector.accessors.isStateEnabled())) {
56+
return c.json({ enabled: false }, 200);
57+
}
5258

53-
const patch = c.req.valid("json");
54-
const state = await c.var.inspector.accessors.getState();
59+
const body = c.req.valid("json");
60+
if ("replace" in body) {
61+
await c.var.inspector.accessors.setState(body.replace);
62+
return c.json(
63+
{
64+
enabled: true,
65+
state: await c.var.inspector.accessors.getState(),
66+
},
67+
200,
68+
);
69+
}
70+
const state = await c.var.inspector.accessors.getState();
5571

56-
const { newDocument: newState } = jsonPatch.applyPatch(state, patch);
57-
await c.var.inspector.accessors.setState(newState);
72+
const { newDocument: newState } = jsonPatch.applyPatch(
73+
state,
74+
body.patch,
75+
);
76+
await c.var.inspector.accessors.setState(newState);
5877

59-
return c.json(
60-
{ enabled: true, state: await c.var.inspector.accessors.getState() },
61-
200,
62-
);
63-
})
78+
return c.json(
79+
{ enabled: true, state: await c.var.inspector.accessors.getState() },
80+
200,
81+
);
82+
},
83+
)
6484
.get("/state/stream", async (c) => {
6585
if (!(await c.var.inspector.accessors.isStateEnabled())) {
6686
return c.json({ enabled: false }, 200);
@@ -71,9 +91,9 @@ export function createActorInspectorRouter() {
7191
return streamSSE(
7292
c,
7393
async (stream) => {
74-
unsub = c.var.inspector.emitter.on("stateUpdated", (state) => {
94+
unsub = c.var.inspector.emitter.on("stateUpdated", async (state) => {
7595
stream.writeSSE({
76-
data: JSON.stringify(state),
96+
data: JSON.stringify(state) || "",
7797
event: "state-update",
7898
id: String(id++),
7999
});

packages/core/src/inspector/manager.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@ export function createManagerInspectorRouter() {
2626

2727
invariant(limit && limit > 0, "Limit must be a positive integer");
2828

29-
const actors = await c.var.inspector.accessors.getAllActors({
30-
limit,
31-
cursor,
32-
});
33-
return c.json(actors, 200);
29+
try {
30+
const actors = await c.var.inspector.accessors.getAllActors({
31+
limit,
32+
cursor,
33+
});
34+
return c.json(actors, 200);
35+
} catch (error) {
36+
inspectorLogger().error("Failed to fetch actors", error);
37+
return c.json("Failed to fetch actors", 500);
38+
}
3439
})
3540

3641
.post("/actors", sValidator("json", CreateActorSchema), async (c) => {

pnpm-lock.yaml

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)