diff --git a/site/src/content/docs/actors/authentication.mdx b/site/src/content/docs/actors/authentication.mdx
index 1606a07652..6980946796 100644
--- a/site/src/content/docs/actors/authentication.mdx
+++ b/site/src/content/docs/actors/authentication.mdx
@@ -14,11 +14,10 @@ The `onAuth` hook runs on the HTTP server before clients can access actors. This
import { actor, UserError } from "@rivetkit/actor";
const chatRoom = actor({
- onAuth: async (opts) => {
- const { req, params, intents } = opts;
+ onAuth: async (params: { authToken?: string }, { request, intents }) => {
// Extract token from params or headers
- const token = params.authToken || req.headers.get("Authorization");
+ const token = params.authToken || request.headers.get("Authorization");
if (!token) {
throw new UserError("Authentication required");
@@ -83,8 +82,8 @@ const userProfileActor = actor({
}
},
- createConnState: (c, opts) => {
- return { userId: opts.params.userId };
+ createConnState: (c, opts, params: { userId: string }) => {
+ return { userId: params.userId };
},
actions: {
@@ -128,9 +127,7 @@ The `onAuth` hook receives an `intents` parameter indicating what the client wan
```typescript
const secureActor = actor({
- onAuth: async (opts) => {
- const { intents, params } = opts;
-
+ onAuth: async (params: { token: string }, { intents }) => {
// Different validation based on intent
if (intents.has("action")) {
// Requires higher privileges for actions
@@ -162,8 +159,8 @@ Use specific error types for different authentication failures:
import { UserError, Unauthorized, Forbidden } from "@rivetkit/actor/errors";
const protectedActor = actor({
- onAuth: async (opts) => {
- const token = opts.params.authToken;
+ onAuth: async (params: { authToken?: string }) => {
+ const token = params.authToken;
if (!token) {
throw new Unauthorized("Authentication token required");
@@ -226,9 +223,9 @@ import { actor, UserError } from "@rivetkit/actor";
import jwt from "jsonwebtoken";
const jwtActor = actor({
- onAuth: async (opts) => {
- const token = opts.params.jwt ||
- opts.req.headers.get("Authorization")?.replace("Bearer ", "");
+ onAuth: async (params: { jwt?: string }, { request }) => {
+ const token = params.jwt ||
+ request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) {
throw new UserError("JWT token required");
@@ -265,9 +262,9 @@ const jwtActor = actor({
```typescript
const apiActor = actor({
- onAuth: async (opts) => {
- const apiKey = opts.params.apiKey ||
- opts.req.headers.get("X-API-Key");
+ onAuth: async (params: { apiKey?: string }, { request }) => {
+ const apiKey = params.apiKey ||
+ request.headers.get("X-API-Key");
if (!apiKey) {
throw new UserError("API key required");
@@ -331,7 +328,7 @@ export function requirePermission(permission: string) {
// usage in actor
const forumActor = actor({
- onAuth: async (opts) => {
+ onAuth: async (params: { token: string }) => {
// ... authenticate and return user with role/permissions
},
diff --git a/site/src/content/docs/actors/connections.mdx b/site/src/content/docs/actors/connections.mdx
index 3feec6f9d8..3bbbba255d 100644
--- a/site/src/content/docs/actors/connections.mdx
+++ b/site/src/content/docs/actors/connections.mdx
@@ -27,7 +27,7 @@ const gameRoom = actor({
state: {},
// Handle connection setup
- createConnState: (c, { params }) => {
+ createConnState: (c, opts, params: { authToken: string }) => {
// Validate authentication token
const authToken = params.authToken;
diff --git a/site/src/content/docs/actors/ephemeral-variables.mdx b/site/src/content/docs/actors/ephemeral-variables.mdx
index ca249d58d2..20f9506a53 100644
--- a/site/src/content/docs/actors/ephemeral-variables.mdx
+++ b/site/src/content/docs/actors/ephemeral-variables.mdx
@@ -42,14 +42,14 @@ This value will be cloned for every new actor using `structuredClone`.
Create actor state dynamically on each actors' start:
```typescript
-import { actor } from "@rivetkit/actor";
+import { actor, ActorInitContext } from "@rivetkit/actor";
// Define vars with initialization logic
const counter = actor({
state: { count: 0 },
// Define vars using a creation function
- createVars: () => {
+ createVars: (c: ActorInitContext, driver: any) => {
return {
lastAccessTime: Date.now(),
emitter: createNanoEvents()
@@ -62,6 +62,12 @@ const counter = actor({
});
```
+
+If accepting arguments to `createVars`, you **must** define the types: `createVars(c: ActorInitContext, driver: any)`
+
+Otherwise, the return type will not be inferred and `c.vars` will be of type `unknown`.
+
+
diff --git a/site/src/content/docs/actors/events.mdx b/site/src/content/docs/actors/events.mdx
index 37d8aa1406..ed5b3a5335 100644
--- a/site/src/content/docs/actors/events.mdx
+++ b/site/src/content/docs/actors/events.mdx
@@ -50,7 +50,7 @@ const gameRoom = actor({
players: {} as Record
},
- createConnState: (c, { params }) => ({
+ createConnState: (c, opts, params: { playerId: string, role?: string }) => ({
playerId: params.playerId,
role: params.role || "player"
}),
@@ -86,7 +86,7 @@ const gameRoom = actor({
players: {} as Record
},
- createConnState: (c, { params }) => ({
+ createConnState: (c, opts, params: { playerId: string, role?: string }) => ({
playerId: params.playerId,
role: params.role || "player"
}),
diff --git a/site/src/content/docs/actors/external-sql.mdx b/site/src/content/docs/actors/external-sql.mdx
index 3b6d94ff69..56c8d5b16a 100644
--- a/site/src/content/docs/actors/external-sql.mdx
+++ b/site/src/content/docs/actors/external-sql.mdx
@@ -36,7 +36,7 @@ Here's a basic example of a user actor that creates a database record on start a
```typescript {{ "title": "registry.ts" }}
-import { actor, setup } from "@rivetkit/actor";
+import { actor, setup, ActorInitContext } from "@rivetkit/actor";
import { Pool } from "pg";
interface ActorInput {
@@ -55,10 +55,10 @@ const pool = new Pool({
// Create the user actor
export const userActor = actor({
- createState: (opts: { input: ActorInput }) => ({
+ createState: (c: ActorInitContext, input: ActorInput) => ({
requestCount: 0,
- username: opts.input.username,
- email: opts.input.email,
+ username: input.username,
+ email: input.email,
lastActive: Date.now()
}),
@@ -140,7 +140,7 @@ Here's the same user actor pattern using Drizzle ORM for more type-safe database
```typescript {{ "title": "registry.ts" }}
-import { actor, setup } from "@rivetkit/actor";
+import { actor, setup, ActorInitContext } from "@rivetkit/actor";
import { drizzle } from "drizzle-orm/node-postgres";
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { eq } from "drizzle-orm";
@@ -169,10 +169,10 @@ const db = drizzle(pool);
// Create the user actor
export const userActor = actor({
- createState: (opts: { input: ActorInput }) => ({
+ createState: (c: ActorInitContext, input: ActorInput) => ({
requestCount: 0,
- username: opts.input.username,
- email: opts.input.email,
+ username: input.username,
+ email: input.email,
lastActive: Date.now()
}),
diff --git a/site/src/content/docs/actors/helper-types.mdx b/site/src/content/docs/actors/helper-types.mdx
index 6708875091..3e53f37588 100644
--- a/site/src/content/docs/actors/helper-types.mdx
+++ b/site/src/content/docs/actors/helper-types.mdx
@@ -2,9 +2,10 @@
Rivet provides several TypeScript helper types to make it easier to work with actors in a type-safe way.
+
## `Context` Types
-When working with actors, you often need to access the context object. Rivet provides helper types to extract the context types from actor definitions.
+When working with actors, you often need to access the context object outside of the actor's handlers. Rivet provides helper types to extract the context types from actor definitions.
### `ActorContextOf`
@@ -57,4 +58,3 @@ function processCounterAction(context: ActionContextOf) {
context.state.count++;
}
```
-
diff --git a/site/src/content/docs/actors/input.mdx b/site/src/content/docs/actors/input.mdx
index b56c86ffe5..13fa613f89 100644
--- a/site/src/content/docs/actors/input.mdx
+++ b/site/src/content/docs/actors/input.mdx
@@ -32,21 +32,26 @@ const gameHandle = client.game.getOrCreate(["game-456"], {
Input is available in lifecycle hooks via the `opts.input` parameter:
```typescript
+interface ChatRoomInput {
+ roomName: string,
+ isPrivate: boolean,
+}
+
const chatRoom = actor({
- createState: (c, opts) => ({
- name: opts.input?.roomName ?? "Unnamed Room",
- isPrivate: opts.input?.isPrivate ?? false,
- maxUsers: opts.input?.maxUsers ?? 50,
+ createState: (c: ActorInitContext, input: ChatRoomInput) => ({
+ name: input?.roomName ?? "Unnamed Room",
+ isPrivate: input?.isPrivate ?? false,
+ maxUsers: input?.maxUsers ?? 50,
users: {},
messages: [],
}),
- onCreate: (c, opts) => {
- console.log(`Creating room: ${opts.input?.roomName}`);
+ onCreate: (c, opts, input: ChatRoomInput) => {
+ console.log(`Creating room: ${input.roomName}`);
// Setup external services based on input
- if (opts.input?.isPrivate) {
- setupPrivateRoomLogging(opts.input.roomName);
+ if (input.isPrivate) {
+ setupPrivateRoomLogging(input.roomName);
}
},
@@ -76,9 +81,9 @@ const GameInputSchema = z.object({
});
const game = actor({
- createState: (c, opts) => {
+ createState: (c: ActorInitContext, inputRaw: z.infer) => {
// Validate input
- const input = GameInputSchema.parse(opts.input);
+ const input = GameInputSchema.parse(inputRaw);
return {
gameMode: input.gameMode,
@@ -142,10 +147,10 @@ interface GameInput {
}
const game = actor({
- createState: (c, opts: { input?: GameInput }) => ({
- gameMode: opts.input?.gameMode ?? "casual",
- maxPlayers: opts.input?.maxPlayers ?? 4,
- difficulty: opts.input?.difficulty ?? "medium",
+ createState: (c: ActorInitContext, input: GameInput) => ({
+ gameMode: input.gameMode,
+ maxPlayers: input.maxPlayers,
+ difficulty: input.difficulty ?? "medium",
}),
actions: {
@@ -160,12 +165,12 @@ If you need to access input data in actions, store it in the actor's state:
```typescript
const game = actor({
- createState: (c, opts) => ({
+ createState: (c: ActorInitContext, input: GameInput) => ({
// Store input configuration in state
config: {
- gameMode: opts.input?.gameMode ?? "casual",
- maxPlayers: opts.input?.maxPlayers ?? 4,
- difficulty: opts.input?.difficulty ?? "medium",
+ gameMode: input.gameMode,
+ maxPlayers: input.maxPlayers,
+ difficulty: input?.difficulty ?? "medium",
},
// Runtime state
players: {},
diff --git a/site/src/content/docs/actors/keys.mdx b/site/src/content/docs/actors/keys.mdx
index 57e2c69a03..86c4db13c7 100644
--- a/site/src/content/docs/actors/keys.mdx
+++ b/site/src/content/docs/actors/keys.mdx
@@ -111,7 +111,7 @@ Use keys to provide basic actor configuration:
```typescript {{"title":"registry.ts"}}
const userSession = actor({
- createState: (c) => ({
+ createState: () => ({
userId: c.key[0], // Extract user ID from key
loginTime: Date.now(),
preferences: {}
diff --git a/site/src/content/docs/actors/lifecycle.mdx b/site/src/content/docs/actors/lifecycle.mdx
index b0bd85de1f..e0babd2b24 100644
--- a/site/src/content/docs/actors/lifecycle.mdx
+++ b/site/src/content/docs/actors/lifecycle.mdx
@@ -22,7 +22,9 @@ The `createVars` function or `vars` constant defines ephemeral variables for the
The `createVars` function can also receive driver-specific context as its second parameter, allowing access to driver capabilities like Rivet KV or Cloudflare Durable Object storage.
```typescript
-import { actor } from "@rivetkit/actor";
+import { actor, ActorInitContext } from "@rivetkit/actor";
+// In this example, assume we're using Redis
+import type { DriverContext } from "@rivetkit/redis";
// Using vars constant
const counter1 = actor({
@@ -48,7 +50,7 @@ const counter2 = actor({
const exampleActor = actor({
state: { count: 0 },
// Access driver context in createVars
- createVars: (c, driverCtx) => ({
+ createVars: (c: ActorInitContext, driverCtx: DriverContext) => ({
driverCtx,
}),
actions: {
@@ -87,10 +89,10 @@ const counter3 = actor({
state: { count: 0 },
// Run initialization logic (logging, external service setup, etc.)
- onCreate: (c, opts) => {
+ onCreate: (c, opts, input: { foo: string }) => {
console.log("Counter actor initialized");
// Access input parameters if provided
- console.log("Input:", opts.input);
+ console.log("Input:", input);
// Can perform async operations or setup
// No need to return anything
},
@@ -198,7 +200,7 @@ const chatRoom = actor({
},
// Method 2: Dynamically create connection state
- createConnState: (c, { params }) => {
+ createConnState: (c, opts, params: { userId?: string, role?: string }) => {
return {
userId: params.userId || "anonymous",
role: params.role || "guest",
@@ -207,7 +209,7 @@ const chatRoom = actor({
},
// Validate connections before accepting them
- onBeforeConnect: (c, { params }) => {
+ onBeforeConnect: (c, opts, params: { authToken?: string }) => {
// Validate authentication
const authToken = params.authToken;
if (!authToken || !validateToken(authToken)) {
@@ -369,8 +371,8 @@ import { actor } from "@rivetkit/actor";
const secureActor = actor({
// Authentication handler - runs on HTTP server
- onAuth: async (opts) => {
- const authHeader = opts.req.headers.get("authorization");
+ onAuth: async (params: undefined, { request }) => {
+ const authHeader = request.headers.get("authorization");
if (!authHeader?.startsWith("Bearer ")) {
throw new Error("Missing or invalid authorization header");
}
@@ -537,9 +539,15 @@ See [Helper Types](/docs/actors/helper-types) for more details on using `ActorCo
## Full Example
```typescript
-import { actor } from "@rivetkit/actor";
+import { actor, ActorInitContext } from "@rivetkit/actor";
import { z } from "zod";
+interface CounterInput {
+ initialCount?: number;
+ stepSize?: number;
+ name?: string;
+}
+
const counter = actor({
// Authentication handler (runs on HTTP server)
onAuth: async (opts) => {
@@ -561,16 +569,16 @@ const counter = actor({
},
// Initialize state with input
- createState: (c, opts) => ({
- count: opts.input?.initialCount ?? 0,
- stepSize: opts.input?.stepSize ?? 1,
- name: opts.input?.name ?? "Unnamed Counter",
+ createState: (c: ActorInitContext, input: CounterInput) => ({
+ 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, opts) => {
- console.log(`Counter "${opts.input?.name}" initialized`);
+ onCreate: (c, opts, input: { name: string }) => {
+ console.log(`Counter "${input.name}" initialized`);
// Set up external resources, logging, etc.
},
@@ -579,8 +587,7 @@ const counter = actor({
role: "guest"
},
- // Dynamically create connection state based on params
- createConnState: (c, { params }) => {
+ createConnState: (c, opts) => {
// Auth data is available from onAuth
return {
userId: c.conn.auth.userId,
diff --git a/site/src/content/docs/actors/state.mdx b/site/src/content/docs/actors/state.mdx
index 5dd9c4e5d4..33eac8b92c 100644
--- a/site/src/content/docs/actors/state.mdx
+++ b/site/src/content/docs/actors/state.mdx
@@ -55,6 +55,36 @@ const counter = actor({
});
```
+To accept a custom input parameters for the initial state, use:
+
+```typescript
+import { actor, ActorInitContext } from "@rivetkit/actor";
+
+interface CounterInput {
+ startingCount: number;
+}
+
+// State with initialization logic
+const counter = actor({
+ // Define state using a creation function
+ createState: (c: ActorInitContext, input: CounterInput) => {
+ return { count: input.startingCount };
+ },
+
+ actions: {
+ // ...
+ }
+});
+```
+
+Read more about [input parameters](/docs/actors/input) here.
+
+
+If accepting arguments to `createState`, you **must** define the types: `createSTate(c: ActorInitContext, input: MyType)`
+
+Otherwise, the return type will not be inferred and `c.vars` will be of type `unknown`.
+
+
@@ -141,13 +171,22 @@ In addition to persisted state, actors can store ephemeral data that is not save
For complete documentation on ephemeral variables, see [Ephemeral Variables](/docs/actors/ephemeral-variables).
-## Limitations
-
-State is currently constrained to the available memory on the machine.
-
-Only JSON-serializable types can be stored in state. In serverless runtimes that support it (Rivet, Cloudflare Workers), state is persisted under the hood in a compact, binary format. This is because JavaScript classes cannot be serialized & deserialized.
-
-
-SQLite in Rivet Actors (coming soon) will provide a way of dynamically querying data with in-memory performance without being constrained to memory limits.
-
+## Type Limitations
+
+State is currently constrained to the following types:
+
+- `null`
+- `undefined`
+- `boolean`
+- `string`
+- `number`
+- `BigInt`
+- `Date`
+- `RegExp`
+- `Error`
+- Typed arrays (`Uint8Array`, `Int8Array`, `Float32Array`, etc.)
+- `Map`
+- `Set`
+- `Array`
+- Plain objects
diff --git a/site/src/sitemap/mod.ts b/site/src/sitemap/mod.ts
index f8582c14a0..027ec08680 100644
--- a/site/src/sitemap/mod.ts
+++ b/site/src/sitemap/mod.ts
@@ -200,7 +200,7 @@ export const sitemap = [
collapsible: true,
pages: [
{
- title: "Variables",
+ title: "Ephemeral Variables",
href: "/docs/actors/ephemeral-variables",
//icon: faMemory,
},