cloudflare / cloudflare/actors
@Persist state returns RpcStub objects when returned via RPC
- Dominant language
- TypeScript
- Stars
- 422
- Forks
- 28
- PR merge metrics
- No merged PRs in 30d
Description
## Problem
When an Actor method returns `@Persist` decorated state via RPC, the caller receives `RpcStub` objects instead of serialized data.
## Reproduction
https://github.com/dmmulroy/actor-bug
```bash
git clone https://github.com/dmmulroy/actor-bug
cd actor-bug
npm install
npm run dev
curl http://localhost:8787
```
### DataActor.ts
```ts
import { Actor, Persist } from "@cloudflare/actors";
type Item = { id: string; value: number };
export class DataActor extends Actor {
@Persist state: { items: Item[] } = { items: [] };
async getItems(): Promise {
return this.state.items; // Returns RpcStub objects to RPC callers
}
}
```
### CoordinatorActor.ts (RPC caller)
```ts
const dataActor = DataActor.get("data-1");
const items = await dataActor.getItems();
console.log(items); // [ RpcStub {}, RpcStub {} ]
console.log(typeof items[0]); // "function"
console.log(String(items[0])); // "[object JsRpcStub]"
```
## Expected
`getItems()` returns `[{ id: "item-1", value: 100 }, ...]`
## Actual
`getItems()` returns `[RpcStub {}, RpcStub {}]` which serialize to `[null, null]`
## Workarounds
### 1. Query storage directly (recommended)
```ts
async getItems(): Promise {
const result = this.ctx.storage.sql.exec(
"SELECT value FROM _actor_persist WHERE property = 'state'"
);
const row = [...result][0];
if (row) {
return JSON.parse(row.value as string).items ?? [];
}
return [];
}
```
### 2. JSON roundtrip
```ts
async getItems(): Promise {
return JSON.parse(JSON.stringify(this.state.items));
}
```
## Environment
- `@cloudflare/actors`: 0.0.1-beta.6
- `wrangler`: 4.53.0
Contributor guide
Assessment
This issue has not been assessed yet.