run
Replace a plain run handler with a background task started in onWake.
Passing a plain function to run was deprecated in 3.0 and will be removed in a future version. It still works today and logs a warning at startup. run: workflow(...) is not deprecated.
What is deprecated
A plain run function starts a long-running loop alongside the actor. Only that form is deprecated. Workflows keep using run: workflow(...).
import { actor } from "rivetkit";
// An actor that still uses a plain `run` function
const poller = actor({
createVars: () => ({ lastPrice: 0 }),
run: async (c) => {
while (!c.aborted) {
c.vars.lastPrice = await fetchPrice();
await new Promise((resolve) => setTimeout(resolve, 5_000));
}
},
actions: {
price: (c) => c.vars.lastPrice,
},
});
async function fetchPrice(): Promise<number> {
return 42;
}
Start a background task in onWake
Start the same loop yourself from onWake, which runs on every start, and keep a handle to it in c.vars:
onWakestarts the loop and stores its promise inc.vars.- The loop checks
c.abortedand passesc.abortSignalto anything it waits on, so it exits when the actor starts shutting down. c.keepAwakewraps only the work that must finish. Between iterations the actor can still sleep, andonWakerestarts the loop when it wakes.onSleepwaits for the loop to exit.c.abortSignalfires beforeonSleepruns, so the loop is already stopping.
import { actor } from "rivetkit";
interface PollerVars {
lastPrice: number;
// The background loop started in `onWake`, so `onSleep` can wait for it
task: Promise<void> | null;
}
interface PollerContext {
vars: PollerVars;
aborted: boolean;
abortSignal: AbortSignal;
keepAwake<T>(promise: Promise<T>): Promise<T>;
log: { error(fields: Record<string, unknown>): void };
}
async function pollPrices(c: PollerContext): Promise<void> {
while (!c.aborted) {
// Keep the actor awake only while a fetch is in flight. Between polls
// the actor may sleep, and `onWake` restarts the loop.
c.vars.lastPrice = await c.keepAwake(fetchPrice());
await delay(5_000, c.abortSignal);
}
}
const poller = actor({
createVars: (): PollerVars => ({ lastPrice: 0, task: null }),
// `onWake` runs on every start, so the loop is restarted after sleep
onWake: (c) => {
c.vars.task = pollPrices(c).catch((error) => {
c.log.error({ msg: "price poller failed", error });
});
},
// `c.abortSignal` fires on shutdown, so the loop exits on its own
onSleep: async (c) => {
await c.vars.task;
},
actions: {
price: (c) => c.vars.lastPrice,
},
});
function delay(ms: number, signal: AbortSignal): Promise<void> {
return new Promise((resolve) => {
const timer = setTimeout(resolve, ms);
signal.addEventListener("abort", () => {
clearTimeout(timer);
resolve();
});
});
}
async function fetchPrice(): Promise<number> {
return 42;
}
Wrapping the whole loop in c.keepAwake keeps the actor awake until it is destroyed. Only do that for an actor that must never sleep.
For work that must survive a crash or resume where it left off, store progress in SQLite or use a workflow. See Build a durable job queue for a complete example.