Skip to main content
Deprecated

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:

  1. onWake starts the loop and stores its promise in c.vars.
  2. The loop checks c.aborted and passes c.abortSignal to anything it waits on, so it exits when the actor starts shutting down.
  3. c.keepAwake wraps only the work that must finish. Between iterations the actor can still sleep, and onWake restarts the loop when it wakes.
  4. onSleep waits for the loop to exit. c.abortSignal fires before onSleep runs, 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.