Lifecycle & Config

Lifecycle

Actors follow a well-defined lifecycle with hooks at each stage. Understanding these hooks is essential for proper initialization, state management, and cleanup.

Lifecycle

Actors transition through several states during their lifetime. Each transition triggers specific hooks that let you initialize resources, manage connections, and clean up state.

On Create (runs once per actor)

  1. createState
  2. onCreate
  3. createVars
  4. onWake

On Destroy

  1. onDestroy

On Wake (after sleep, restart, or crash)

  1. createVars
  2. onWake

On Sleep (after idle period)

  1. onSleep

On Connect (per client)

  1. onBeforeConnect
  2. createConnState
  3. onConnect

On Disconnect (per client)

  1. onDisconnect

Lifecycle Hooks

Actor lifecycle hooks are defined as functions in the actor configuration.

state

The state constant defines the initial state of the actor. See state documentation for more information.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  actions: { /* ... */ }
});
TypeScript

createState

API Reference

The createState function dynamically initializes state based on input. Called only once when the actor is first created. Can be async. See state documentation for more information.

import { actor } from "rivetkit";

const counter = actor({
  createState: (c, input: { initialCount: number }) => ({
    count: input.initialCount
  }),
  actions: { /* ... */ }
});
TypeScript

vars

The vars constant defines ephemeral variables for the actor. These variables are not persisted and are useful for storing runtime-only data. The value for vars must be clonable via structuredClone. See ephemeral variables documentation for more information.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  vars: { lastAccessTime: 0 },
  actions: { /* ... */ }
});
TypeScript

createVars

API Reference

The createVars function dynamically initializes ephemeral variables. Can be async. Use this when you need to initialize values at runtime. The driverCtx parameter provides driver-specific context. See ephemeral variables documentation for more information.

import { actor } from "rivetkit";

interface CounterVars {
  lastAccessTime: number;
  emitter: EventTarget;
}

const counter = actor({
  state: { count: 0 },
  createVars: (c, driverCtx): CounterVars => ({
    lastAccessTime: Date.now(),
    emitter: new EventTarget()
  }),
  actions: { /* ... */ }
});
TypeScript

onCreate

API Reference

The onCreate hook is called when the actor is first created. Can be async. Use this hook for initialization logic that doesn't affect the initial state.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },

  onCreate: (c, input: { initialCount: number }) => {
    console.log("Actor created with initial count:", input.initialCount);
  },

  actions: { /* ... */ }
});
TypeScript

onDestroy

API Reference

The onDestroy hook is called when the actor is being permanently destroyed. Can be async. Use this for final cleanup operations like closing external connections, releasing resources, or performing any last-minute state persistence.

import { actor } from "rivetkit";

const gameSession = actor({
  onDestroy: (c) => {
    // Clean up any external resources
  },
  actions: { /* ... */ }
});
TypeScript

onWake

API Reference

This hook is called any time the actor is started (e.g. after restarting, upgrading code, or crashing). Can be async.

This is called after the actor has been initialized but before any connections are accepted.

Use this hook to set up any resources or start any background tasks, such as setInterval.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  vars: { intervalId: null as NodeJS.Timeout | null },

  onWake: (c) => {
    console.log('Actor started with count:', c.state.count);

    // Set up interval for automatic counting
    const intervalId = setInterval(() => {
      c.state.count++;
      c.broadcast("countChanged", c.state.count);
      console.log('Auto-increment:', c.state.count);
    }, 10000);

    // Store interval ID in vars to clean up later if needed
    c.vars.intervalId = intervalId;
  },

  actions: {
    stop: (c) => {
      if (c.vars.intervalId) {
        clearInterval(c.vars.intervalId);
        c.vars.intervalId = null;
      }
    }
  }
});
TypeScript

onSleep

API Reference

This hook is called when the actor is going to sleep. Can be async. Use this to clean up resources, close connections, or perform any shutdown operations.

This hook may not always be called in situations like crashes or forced terminations. Don't rely on it for critical cleanup operations.

Not supported on Cloudflare Workers.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  vars: { intervalId: null as NodeJS.Timeout | null },

  onWake: (c) => {
    // Set up interval when actor wakes
    c.vars.intervalId = setInterval(() => {
      c.state.count++;
      console.log('Auto-increment:', c.state.count);
    }, 10000);
  },

  onSleep: (c) => {
    console.log('Actor going to sleep, cleaning up...');

    // Clean up interval before sleeping
    if (c.vars.intervalId) {
      clearInterval(c.vars.intervalId);
      c.vars.intervalId = null;
    }

    // Perform any other cleanup
    console.log('Final count:', c.state.count);
  },

  actions: { /* ... */ }
});
TypeScript

onStateChange

API Reference

Called whenever the actor's state changes. Cannot be async. This is often used to broadcast state updates.

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },

  onStateChange: (c, newState) => {
    // Broadcast the new count to all connected clients
    c.broadcast('countUpdated', {
      count: newState.count
    });
  },
  
  actions: {
    increment: (c) => {
      c.state.count++;
      return c.state.count;
    }
  }
});
TypeScript

createConnState and connState

API Reference

There are two ways to define the initial state for connections:

  1. connState: Define a constant object that will be used as the initial state for all connections
  2. createConnState: A function that dynamically creates initial connection state based on connection parameters. Can be async.

onBeforeConnect

API Reference

The onBeforeConnect hook is called whenever a new client connects to the actor. Can be async. Clients can pass parameters when connecting, accessible via params. This hook is used for connection validation and can throw errors to reject connections.

The onBeforeConnect hook does NOT return connection state - it's used solely for validation.

import { actor } from "rivetkit";

const chatRoom = actor({
  state: { messages: [] },

  // Method 1: Use a static default connection state
  connState: {
    role: "guest",
    joinTime: 0,
  },

  // Method 2: Dynamically create connection state
  createConnState: (c, params: { userId?: string, role?: string }) => {
    return {
      userId: params.userId || "anonymous",
      role: params.role || "guest",
      joinTime: Date.now()
    };
  },

  // Validate connections before accepting them
  onBeforeConnect: (c, params: { authToken?: string }) => {
    // Validate authentication
    const authToken = params.authToken;
    if (!authToken || !validateToken(authToken)) {
      throw new Error("Invalid authentication");
    }

    // Authentication is valid, connection will proceed
    // The actual connection state will come from connState or createConnState
  },

  actions: { /* ... */ }
});
TypeScript

Connections cannot interact with the actor until this method completes successfully. Throwing an error will abort the connection. This can be used for authentication - see Authentication for details.

onConnect

API Reference

Executed after the client has successfully connected. Can be async. Receives the connection object as a second parameter.

import { actor } from "rivetkit";

const chatRoom = actor({
  state: { users: {}, messages: [] },

  onConnect: (c, conn) => {
    // Add user to the room's user list using connection state
    const userId = conn.state.userId;
    c.state.users[userId] = {
      online: true,
      lastSeen: Date.now()
    };

    // Broadcast that a user joined
    c.broadcast("userJoined", { userId, timestamp: Date.now() });

    console.log(`User ${userId} connected`);
  },

  actions: { /* ... */ }
});
TypeScript

Messages will not be processed for this actor until this hook succeeds. Errors thrown from this hook will cause the client to disconnect.

onDisconnect

API Reference

Called when a client disconnects from the actor. Can be async. Receives the connection object as a second parameter. Use this to clean up any connection-specific resources.

import { actor } from "rivetkit";

const chatRoom = actor({
  state: { users: {}, messages: [] },

  onDisconnect: (c, conn) => {
    // Update user status when they disconnect
    const userId = conn.state.userId;
    if (c.state.users[userId]) {
      c.state.users[userId].online = false;
      c.state.users[userId].lastSeen = Date.now();
    }

    // Broadcast that a user left
    c.broadcast("userLeft", { userId, timestamp: Date.now() });

    console.log(`User ${userId} disconnected`);
  },

  actions: { /* ... */ }
});
TypeScript

onRequest

API Reference

The onRequest hook handles HTTP requests sent to your actor at /actors/{actorName}/http/* endpoints. Can be async. It receives the request context and a standard Request object, and should return a Response object or void to continue default routing.

See Request Handler for more details.

import { actor } from "rivetkit";

const apiActor = actor({
  state: { requestCount: 0 },

  onRequest: (c, request) => {
    const url = new URL(request.url);
    c.state.requestCount++;

    if (url.pathname === "/api/status") {
      return new Response(JSON.stringify({
        status: "ok",
        requestCount: c.state.requestCount
      }), {
        headers: { "Content-Type": "application/json" }
      });
    }

    // Return void to continue to default routing
    return;
  },

  actions: { /* ... */ }
});
TypeScript

onWebSocket

API Reference

The onWebSocket hook handles WebSocket connections to your actor. Can be async. It receives the actor context and a WebSocket object. Use this to set up WebSocket event listeners and handle real-time communication.

See WebSocket Handler for more details.

import { actor } from "rivetkit";

const realtimeActor = actor({
  state: { connectionCount: 0 },

  onWebSocket: (c, websocket) => {
    c.state.connectionCount++;
    
    // Send welcome message
    websocket.send(JSON.stringify({
      type: "welcome",
      connectionCount: c.state.connectionCount
    }));
    
    // Handle incoming messages
    websocket.addEventListener("message", (event) => {
      const data = JSON.parse(event.data);
      
      if (data.type === "ping") {
        websocket.send(JSON.stringify({
          type: "pong",
          timestamp: Date.now()
        }));
      }
    });
    
    // Handle connection close
    websocket.addEventListener("close", () => {
      c.state.connectionCount--;
    });
  },
  
  actions: { /* ... */ }
});
TypeScript

onBeforeActionResponse

API Reference

The onBeforeActionResponse hook is called before sending an action response to the client. Can be async. Use this hook to modify or transform the output of an action before it's sent to the client. This is useful for formatting responses, adding metadata, or applying transformations to the output.

import { actor } from "rivetkit";

const loggingActor = actor({
  state: { requestCount: 0 },

  onBeforeActionResponse: (c, actionName, args, output) => {
    // Log action calls
    console.log(`Action ${actionName} called with args:`, args);
    console.log(`Action ${actionName} returned:`, output);

    // Add metadata to all responses
    return {
      data: output,
      metadata: {
        actionName,
        timestamp: Date.now(),
        requestId: crypto.randomUUID(),
        userId: c.conn.state?.userId
      }
    };
  },
  
  actions: {
    getUserData: (c, userId: string) => {
      c.state.requestCount++;
      
      // This will be wrapped with metadata by onBeforeActionResponse
      return {
        userId,
        profile: { name: "John Doe", email: "john@example.com" },
        lastActive: Date.now()
      };
    },
    
    getStats: (c) => {
      // This will also be wrapped with metadata
      return {
        requestCount: c.state.requestCount,
        uptime: process.uptime()
      };
    }
  }
});
TypeScript

Options

The options object allows you to configure various timeouts and behaviors for your actor.

import { actor } from "rivetkit";

const myActor = actor({
  state: { count: 0 },

  options: {
    // Timeout for createVars function (default: 5000ms)
    createVarsTimeout: 5000,

    // Timeout for createConnState function (default: 5000ms)
    createConnStateTimeout: 5000,

    // Timeout for onConnect hook (default: 5000ms)
    onConnectTimeout: 5000,

    // Timeout for onSleep hook (default: 5000ms)
    onSleepTimeout: 5000,

    // Timeout for onDestroy hook (default: 5000ms)
    onDestroyTimeout: 5000,

    // Interval for saving state (default: 10000ms)
    stateSaveInterval: 10_000,

    // Timeout for action execution (default: 60000ms)
    actionTimeout: 60_000,

    // Max time to wait for background promises during shutdown (default: 15000ms)
    waitUntilTimeout: 15_000,

    // Timeout for connection liveness check (default: 2500ms)
    connectionLivenessTimeout: 2500,

    // Interval for connection liveness check (default: 5000ms)
    connectionLivenessInterval: 5000,

    // Prevent actor from sleeping (default: false)
    noSleep: false,

    // Time before actor sleeps due to inactivity (default: 30000ms)
    sleepTimeout: 30_000,

    // Whether WebSockets can hibernate for onWebSocket (default: false)
    // Can be a boolean or a function that takes a Request and returns a boolean
    canHibernateWebSocket: false,
  },

  actions: { /* ... */ }
});
TypeScript
OptionDefaultDescription
createVarsTimeout5000msTimeout for createVars function
createConnStateTimeout5000msTimeout for createConnState function
onConnectTimeout5000msTimeout for onConnect hook
onSleepTimeout5000msTimeout for onSleep hook
onDestroyTimeout5000msTimeout for onDestroy hook
stateSaveInterval10000msInterval for persisting state
actionTimeout60000msTimeout for action execution
waitUntilTimeout15000msMax time to wait for background promises during shutdown
connectionLivenessTimeout2500msTimeout for connection liveness check
connectionLivenessInterval5000msInterval for connection liveness check
noSleepfalsePrevent actor from sleeping
sleepTimeout30000msTime before actor sleeps due to inactivity
canHibernateWebSocketfalseWhether WebSockets can hibernate (experimental)

Advanced

Running Background Tasks

The c.runInBackground method allows you to execute promises asynchronously without blocking the actor's main execution flow. The actor is prevented from sleeping while the promise passed to runInBackground is still active. This is useful for fire-and-forget operations where you don't need to wait for completion.

Common use cases:

  • Analytics and logging: Send events to external services without delaying responses
  • State sync: Populate external databases or APIs with updates to actor state in the background
import { actor } from "rivetkit";

const gameRoom = actor({
  state: { players: {}, scores: {} },
  
  actions: {
    playerJoined: (c, playerId: string) => {
      c.state.players[playerId] = { joinedAt: Date.now() };
      
      // Send analytics event without blocking
      c.runInBackground(
        fetch('https://analytics.example.com/events', {
          method: 'POST',
          body: JSON.stringify({ 
            event: 'player_joined', 
            playerId,
            timestamp: Date.now() 
          })
        }).then(() => console.log('Analytics sent'))
      );
      
      return { success: true };
    },
  }
});
TypeScript

Actor Shutdown Abort Signal

The c.abortSignal provides an AbortSignal that fires when the actor is stopping. Use this to cancel ongoing operations when the actor sleeps or is destroyed.

import { actor } from "rivetkit";

const chatActor = actor({
  actions: {
    generate: async (c, prompt: string) => {
      const response = await fetch("https://api.example.com/generate", {
        method: "POST",
        body: JSON.stringify({ prompt }),
        signal: c.abortSignal
      });

      return await response.json();
    }
  }
});
TypeScript

See Canceling Long-Running Actions for manually canceling operations on-demand.

Using ActorContext Type Externally

When extracting logic from lifecycle hooks or actions into external functions, you'll often need to define the type of the context parameter. Rivet provides helper types that make it easy to extract and pass these context types to external functions.

import { actor, ActorContextOf } from "rivetkit";

const myActor = actor({
  state: { count: 0 },
  
  // Use external function in lifecycle hook
  onWake: (c) => logActorStarted(c)
});

// Simple external function with typed context
function logActorStarted(c: ActorContextOf<typeof myActor>) {
  console.log(`Actor started with count: ${c.state.count}`);
}
TypeScript

See Types for more details on using ActorContextOf.

Full Example

import { actor, CreateContext } from "rivetkit";

interface CounterInput {
  initialCount?: number;
  stepSize?: number;
  name?: string;
}

interface CounterState {
  count: number;
  stepSize: number;
  name: string;
  requestCount: number;
}

interface ConnParams {
  userId: string;
  role: string;
}

interface ConnState {
  userId: string;
  role: string;
  connectedAt: number;
}

const counter = actor({
  // Initialize state with input
  createState: (c: CreateContext, input: CounterInput): CounterState => ({
    count: input.initialCount ?? 0,
    stepSize: input.stepSize ?? 1,
    name: input.name ?? "Unnamed Counter",
    requestCount: 0,
  }),

  // Initialize actor (run setup that doesn't affect initial state)
  onCreate: (c, input: CounterInput) => {
    console.log(`Counter "${input.name}" initialized`);
    // Set up external resources, logging, etc.
  },

  // Dynamically create connection state from params
  createConnState: (c, params: ConnParams): ConnState => {
    return {
      userId: params.userId,
      role: params.role,
      connectedAt: Date.now()
    };
  },

  // Lifecycle hooks
  onWake: (c) => {
    console.log(`Counter "${c.state.name}" started with count:`, c.state.count);
  },

  onStateChange: (c, newState) => {
    c.broadcast('countUpdated', {
      count: newState.count,
      name: newState.name
    });
  },

  onBeforeConnect: (c, params: ConnParams) => {
    // Validate connection params
    if (!params.userId) {
      throw new Error("userId is required");
    }
    console.log(`User ${params.userId} attempting to connect`);
  },

  onConnect: (c, conn) => {
    console.log(`User ${conn.state.userId} connected to "${c.state.name}"`);
  },

  onDisconnect: (c, conn) => {
    console.log(`User ${conn.state.userId} disconnected from "${c.state.name}"`);
  },

  // Transform all action responses
  onBeforeActionResponse: (c, actionName, args, output) => {
    c.state.requestCount++;

    return {
      data: output,
      metadata: {
        action: actionName,
        timestamp: Date.now(),
        requestNumber: c.state.requestCount
      }
    };
  },

  // Define actions
  actions: {
    increment: (c, amount?: number) => {
      const step = amount ?? c.state.stepSize;
      c.state.count += step;
      return c.state.count;
    },

    getInfo: (c) => ({
      name: c.state.name,
      count: c.state.count,
      stepSize: c.state.stepSize,
      totalRequests: c.state.requestCount,
    }),
  }
});

export default counter;
TypeScript
Suggest changes to this page