Back to Rivet

Ephemeral Variables

website/src/content/docs/actors/ephemeral-variables.mdx

2.2.14.3 KB
Original Source

vars is designed to complement state, not replace it. Most actors should use both: state for critical business data and vars for ephemeral or non-serializable data.

Initializing Variables

There are two ways to define an actor's initial vars:

<Tabs> <Tab title="Static Initial Variables">

Define an actor vars as a constant value:

typescript
import { actor } from "rivetkit";

// Mock event emitter for demonstration
interface EventEmitter {
  on: (event: string, callback: (data: unknown) => void) => void;
  emit: (event: string, data: unknown) => void;
}

function createEventEmitter(): EventEmitter {
  const listeners: Record<string, ((data: unknown) => void)[]> = {};
  return {
    on: (event, callback) => {
      listeners[event] = listeners[event] || [];
      listeners[event].push(callback);
    },
    emit: (event, data) => {
      listeners[event]?.forEach(cb => cb(data));
    }
  };
}

// Define vars as a constant
const counter = actor({
  state: { count: 0 },

  // Define ephemeral variables
  vars: {
    lastAccessTime: 0,
    emitter: createEventEmitter()
  },

  actions: {
    increment: (c) => ++c.state.count
  }
});

This value will be cloned for every new actor using structuredClone.

</Tab> <Tab title="Dynamic Initial Variables">

Create actor state dynamically on each actors' start:

typescript
import { actor } from "rivetkit";

// Mock event emitter for demonstration
interface EventEmitter {
  on: (event: string, callback: (data: unknown) => void) => void;
  emit: (event: string, data: unknown) => void;
}

function createEventEmitter(): EventEmitter {
  const listeners: Record<string, ((data: unknown) => void)[]> = {};
  return {
    on: (event, callback) => {
      listeners[event] = listeners[event] || [];
      listeners[event].push(callback);
    },
    emit: (event, data) => {
      listeners[event]?.forEach(cb => cb(data));
    }
  };
}

// Define vars with initialization logic
const counter = actor({
  state: { count: 0 },

  // Define vars using a creation function
  createVars: () => {
    return {
      lastAccessTime: Date.now(),
      emitter: createEventEmitter()
    };
  },

  actions: {
    increment: (c) => ++c.state.count
  }
});
<Note> If accepting arguments to `createVars`, you **must** define the types: `createVars(c: CreateVarsContext, driver: any)`

Otherwise, the return type will not be inferred and c.vars will be of type unknown. </Note>

</Tab> </Tabs>

Using Variables

Vars can be accessed and modified through the context object with c.vars:

typescript
import { actor } from "rivetkit";

// Mock event emitter for demonstration
interface EventEmitter {
  on: (event: string, callback: (data: number) => void) => void;
  emit: (event: string, data: number) => void;
}

function createEventEmitter(): EventEmitter {
  const listeners: Record<string, ((data: number) => void)[]> = {};
  return {
    on: (event, callback) => {
      listeners[event] = listeners[event] || [];
      listeners[event].push(callback);
    },
    emit: (event, data) => {
      listeners[event]?.forEach(cb => cb(data));
    }
  };
}

const counter = actor({
  // Persistent state - saved to storage
  state: { count: 0 },

  // Create ephemeral objects that won't be serialized
  createVars: () => {
    // Create an event emitter (can't be serialized)
    const emitter = createEventEmitter();

    // Set up event listener directly in createVars
    emitter.on('count-changed', (newCount) => {
      console.log(`Count changed to: ${newCount}`);
    });

    return { emitter };
  },

  actions: {
    increment: (c) => {
      // Update persistent state
      c.state.count += 1;

      // Use non-serializable emitter
      c.vars.emitter.emit('count-changed', c.state.count);

      return c.state.count;
    }
  }
});

When to Use vars vs state

In practice, most actors will use both: state for critical business data and vars for ephemeral or non-serializable data.

Use vars when:

  • You need to store temporary data that doesn't need to survive restarts
  • You need to maintain runtime-only references that can't be serialized (database connections, event emitters, class instances, etc.)

Use state when:

  • The data must be preserved across actor sleeps, restarts, updates, or crashes
  • The information is essential to the actor's core functionality and business logic