Back to Rivet

Actor Keys

website/src/content/docs/actors/keys.mdx

2.2.17.5 KB
Original Source

Key Format

Actor keys can be either a string or an array of strings:

typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const counter = actor({
  state: { count: 0 },
  actions: { increment: (c) => c.state.count++ }
});

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {}
});

const registry = setup({ use: { counter, chatRoom } });
const client = createClient<typeof registry>("http://localhost:6420");

// String key
const counterHandle = client.counter.getOrCreate(["my-counter"]);

// Array key (compound key)
const chatRoomHandle = client.chatRoom.getOrCreate(["room", "general"]);

Compound Keys & User Data

Array keys are useful when you need compound keys with user-provided data. Using arrays makes adding user data safe by preventing key injection attacks:

typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({ state: { messages: [] as string[] }, actions: {} });
const gameRoom = actor({ state: { players: [] as string[] }, actions: {} });
const workspace = actor({ state: { data: {} }, actions: {} });

const registry = setup({ use: { chatRoom, gameRoom, workspace } });
const client = createClient<typeof registry>("http://localhost:6420");

// Example user data
const userId = "user-123";
const gameId = "game-456";
const tenantId = "tenant-789";
const workspaceId = "workspace-abc";

// User-specific chat rooms
const userRoomHandle = client.chatRoom.getOrCreate(["user", userId, "private"]);

// Game rooms by region and difficulty
const gameRoomHandle = client.gameRoom.getOrCreate(["us-west", "hard", gameId]);

// Multi-tenant resources
const workspaceHandle = client.workspace.getOrCreate(["tenant", tenantId, workspaceId]);

This allows you to create hierarchical addressing schemes and organize actors by multiple dimensions.

<Warning> Don't build keys using string interpolation like `"foo:${userId}:bar"` when `userId` contains user data. If a user provides a value containing the delimiter (`:` in this example), it can break your key structure and cause key injection attacks. </Warning>

Omitting Keys

You can create actors without specifying a key in situations where there is a singleton actor (i.e. only one actor of a given type). For example:

typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const globalActor = actor({
  state: { config: {} },
  actions: {}
});

const registry = setup({ use: { globalActor } });
const client = createClient<typeof registry>("http://localhost:6420");

// Get the singleton session
const globalActorHandle = client.globalActor.getOrCreate();

This pattern should be avoided, since a singleton actor usually means you have a single actor serving all traffic & your application will not scale. See scaling documentation for more information.

Key Uniqueness

Keys are unique within each actor name. Different actor types can use the same key:

typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({ state: { messages: [] as string[] }, actions: {} });
const userProfile = actor({ state: { name: "" }, actions: {} });

const registry = setup({ use: { chatRoom, userProfile } });
const client = createClient<typeof registry>("http://localhost:6420");

// These are different actors, same key is fine
const userChat = client.chatRoom.getOrCreate(["user-123"]);
const userProfileHandle = client.userProfile.getOrCreate(["user-123"]);

Accessing Keys in Metadata

Access the actor's key within the actor using the metadata API:

<CodeGroup>
typescript
import { actor, setup } from "rivetkit";

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {
    getRoomName: (c) => {
      // Access the key from metadata
      const key = c.key;
      return key[1]; // Get "general" from ["room", "general"]
    }
  }
});

export const registry = setup({
  use: { chatRoom }
});
typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: { getRoomName: (c) => c.key[1] }
});

const registry = setup({ use: { chatRoom } });
const client = createClient<typeof registry>("http://localhost:6420");

async function connectToRoom(roomName: string) {
  // Connect to a chat room
  const chatRoomHandle = client.chatRoom.getOrCreate(["room", roomName]);

  // Get the room name from the key
  const retrievedRoomName = await chatRoomHandle.getRoomName();
  console.log("Room name:", retrievedRoomName); // e.g., "general"

  return chatRoomHandle;
}

// Usage example
const generalRoom = await connectToRoom("general");
</CodeGroup>

Configuration Examples

Simple Configuration with Keys

Use keys to provide basic actor configuration:

<CodeGroup>
typescript
import { actor, setup } from "rivetkit";

interface UserSessionState {
  userId: string;
  loginTime: number;
  preferences: Record<string, unknown>;
}

const userSession = actor({
  state: { userId: "", loginTime: 0, preferences: {} } as UserSessionState,
  createState: (c): UserSessionState => ({
    userId: c.key[0], // Extract user ID from key
    loginTime: Date.now(),
    preferences: {}
  }),

  actions: {
    getUserId: (c) => c.state.userId
  }
});

export const registry = setup({
  use: { userSession }
});
typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const userSession = actor({
  state: { userId: "", loginTime: 0, preferences: {} },
  actions: { getUserId: (c) => c.state.userId }
});

const registry = setup({ use: { userSession } });
const client = createClient<typeof registry>("http://localhost:6420");

// Pass user ID in the key for user-specific actors
const userId = "user-123";
const userSessionHandle = client.userSession.getOrCreate([userId]);
</CodeGroup>

Complex Configuration with Input

For more complex configuration, use input parameters:

typescript
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

interface ChatRoomInput {
  maxUsers: number;
  isPrivate: boolean;
  moderators: string[];
  settings: { allowImages: boolean; slowMode: boolean };
}

const chatRoom = actor({
  state: { maxUsers: 0, isPrivate: false, moderators: [] as string[], settings: { allowImages: true, slowMode: false } },
  createState: (c, input: ChatRoomInput) => ({
    maxUsers: input.maxUsers,
    isPrivate: input.isPrivate,
    moderators: input.moderators,
    settings: input.settings,
  }),
  actions: {}
});

const registry = setup({ use: { chatRoom } });
const client = createClient<typeof registry>("http://localhost:6420");
const roomName = "general";

// Create with both key and input
const chatRoomHandle = await client.chatRoom.create(["room", roomName], {
  input: {
    maxUsers: 100,
    isPrivate: false,
    moderators: ["admin1", "admin2"],
    settings: {
      allowImages: true,
      slowMode: false
    }
  }
});

API Reference