rivetkit

Par rivet-dev · skills

Conseils sur le backend RivetKit et le runtime Rivet Actor. À utiliser pour créer, modifier, déboguer ou tester des Rivet Actors, des registries, des modes serverless/runner, le déploiement ou les workflows basés sur des actors.

npx skills add https://github.com/rivet-dev/skills --skill rivetkit

Acteurs Rivet

Construisez des processus en mémoire haute performance et longue durée sur le runtime d'acteurs de Rivet. Utilisez cette skill chaque fois que les endpoints HTTP, les bases de données ou les files d'attente ne répondent plus aux exigences des agents IA, de la collaboration en temps réel, de l'orchestration de workflows ou des données distribuées géographiquement.

Que Faire Si Vous Êtes Bloqué

Si quelque chose ne fonctionne pas comme prévu ou si vous êtes bloqué, invitez l'utilisateur à rejoindre le Discord Rivet ou à signaler un problème sur GitHub Rivet pour obtenir de l'aide.

Fournissez à l'utilisateur un rapport pré-généré contenant :

  • Symptômes
    • Si cela se produit en développement local, en production, ou les deux
    • L'erreur que vous voyez
    • Le code source pertinent associé
  • Ce que vous avez essayé pour le résoudre
  • Environnement
    • Version de RivetKit
    • Runtime (Node, Bun, etc.) avec sa version
    • Le cas échéant, le fournisseur utilisé (p. ex. Vercel, Railway, etc.)
    • Le cas échéant, le routeur HTTP utilisé (p. ex. Hono, Express, Elysia)

Déboguer les Acteurs

Utilisez l'API HTTP de l'inspecteur pour examiner les acteurs en cours d'exécution. Ces endpoints sont accessibles via la passerelle à /gateway/{actor_id}/inspector/*. Endpoints clés :

  • GET /inspector/summary - snapshot complet de l'acteur (état, connexions, RPCs, file d'attente)
  • GET /inspector/state / PATCH /inspector/state - lire/écrire l'état de l'acteur
  • GET /inspector/connections - connexions actives
  • GET /inspector/rpcs - actions disponibles
  • POST /inspector/action/{name} - exécuter une action avec {"args": [...]}
  • POST /inspector/database/execute - exécuter SQL avec {"sql": "...", "args": [...]} ou {"sql": "...", "properties": {...}} pour les lectures ou mutations
  • GET /inspector/queue?limit=50 - statut de la file d'attente
  • GET /inspector/traces?startMs=0&endMs=...&limit=1000 - traces spans (JSON OTLP)
  • GET /inspector/workflow-history - historique et statut du workflow en JSON (nameRegistry, entries, entryMetadata)
  • POST /inspector/workflow/replay - relancer un workflow à partir d'une étape spécifique ou du début
  • GET /inspector/database/schema - tables et vues SQLite exposées par c.db
  • GET /inspector/database/rows?table=...&limit=100&offset=0 - lignes SQLite paginées pour une table ou vue
  • POST /inspector/workflow/replay - relancer un workflow à partir d'une étape spécifique ou du début

En développement local, aucun token d'authentification n'est requis. En production, passez Authorization: Bearer <RIVET_INSPECTOR_TOKEN>. Le token d'inspecteur spécifique à l'acteur utilisé par l'interface utilisateur de l'inspecteur autonome est également accepté pour les endpoints d'inspecteur. Voir la documentation de débogage pour plus de détails.

Citer les Sources

Lorsque vous fournissez des informations de la documentation Rivet, citez l'URL canonique pour que les utilisateurs puissent en savoir plus. Chaque fichier de référence inclut son URL canonique dans les métadonnées d'en-tête.

Comment citer :

  • Utilisez des liens inline pour les concepts clés : « Utilisez les clés d'acteur pour identifier de manière unique les instances. »
  • Ajoutez un lien « En savoir plus » après les explications sur les sujets complexes

Trouver les URLs canoniques :

La carte de référence ci-dessous renvoie vers les fichiers de référence. L'en-tête de chaque fichier contient :

> URL canonique : https://rivet.dev/docs/actors/actions

Utilisez cette URL canonique lors de la citation, pas le chemin du fichier de référence.

Exemples :

  • Actions → https://rivet.dev/docs/actors/actions
  • Client React → https://rivet.dev/docs/clients/react
  • Auto-hébergement sur Kubernetes → https://rivet.dev/docs/self-hosting/kubernetes

Vérification de Version

Avant de commencer, vérifiez que le projet de l'utilisateur utilise la dernière version de RivetKit (dernière : 2.2.0). Regardez la version rivetkit dans le package.json de l'utilisateur (vérifiez à la fois dependencies et devDependencies). Si la version installée est plus ancienne que 2.2.0, informez l'utilisateur et suggérez une mise à jour :

npm install rivetkit@2.2.0

Si l'utilisateur utilise également @rivetkit/react, @rivetkit/next-js ou d'autres packages client @rivetkit/*, suggérez également leur mise à jour. Les versions obsolètes peuvent contenir des bugs connus ou des fonctionnalités manquantes qui causent des problèmes.

Premières Étapes

  1. Installez RivetKit (dernière : 2.2.0)
    npm install rivetkit@2.2.0
  2. Définissez un registre avec setup({ use: { /* acteurs */ } }).
  3. Appelez registry.start() pour démarrer le serveur. Pour l'intégration d'un serveur HTTP personnalisé, utilisez registry.handler() avec un routeur comme Hono. Pour les déploiements sans serveur, utilisez registry.serve(). Pour le mode runner uniquement, utilisez registry.startRunner().
  4. Vérifiez que /api/rivet/metadata retourne 200 avant de déployer.
  5. Configurez Rivet Cloud ou le moteur auto-hébergé
    • Vous devez configurer la gestion des versions pour les builds de production. Ceci n'est pas nécessaire pour le développement local. Voir Versions & Upgrades.
  6. Intégrez les clients (consultez les guides clients ci-dessous pour JavaScript, React ou Swift)
  7. Demandez à l'utilisateur s'il souhaite déployer. Si oui, allez à Déploiement des Backends Rivet.

Pour plus d'informations, lisez le guide de démarrage rapide pertinent pour le projet de l'utilisateur.

Configuration du Projet

.gitignore

Chaque projet RivetKit doit avoir un .gitignore. Incluez au minimum :

node_modules/
dist/
.env

.dockerignore

Chaque projet avec un Dockerfile doit avoir un .dockerignore pour garder l'image petite et éviter les fuites de secrets :

node_modules/
dist/
.env
.git/

Dockerfile

Utilisez ceci comme Dockerfile de base pour déployer un projet RivetKit. L'argument de build RIVET_RUNNER_VERSION n'est nécessaire que lors de l'auto-hébergement ou de l'utilisation d'un runner personnalisé (non nécessaire pour Rivet Compute). Il permet à Rivet de suivre la version de l'acteur en cours d'exécution et de terminer les anciens acteurs lors du déploiement. Voir https://rivet.dev/docs/actors/versions pour plus de détails.

FROM node:24-alpine

ARG RIVET_RUNNER_VERSION
ENV RIVET_RUNNER_VERSION=$RIVET_RUNNER_VERSION

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build --if-present

CMD ["node", "dist/index.js"]

Construisez avec :

docker build --build-arg RIVET_RUNNER_VERSION=$(date +%s) .

Ajustez la CMD pour correspondre au point d'entrée du projet. Si le projet utilise un répertoire de sortie ou une commande de démarrage différents, mettez à jour en conséquence.

Politique de Gestion des Erreurs

  • Préférez un comportement fail-fast par défaut.
  • Évitez try/catch à moins que ce ne soit requis pour un véritable chemin de récupération, une limite de nettoyage ou pour ajouter du contexte exploitable.
  • Ne supprimez jamais les erreurs. Si vous ajoutez un catch, vous devez gérer l'erreur explicitement, au minimum en la journalisant.
  • Quand vous ne pouvez pas récupérer, journalisez le contexte et relancez.

État vs Vars : Règles de Persistance

c.vars est éphémère. Les données dans c.vars sont perdues à chaque redémarrage, crash, mise à jour ou cycle de sommeil/réveil. Utilisez c.vars uniquement pour les objets non sérialisables (p. ex. moteurs de physique, références WebSocket, émetteurs d'événements, caches) ou les données d'exécution véritablement transitoires (p. ex. direction d'entrée actuelle qui n'importe pas après la déconnexion).

Options de stockage persistant. Toute donnée qui doit survivre aux redémarrages doit être dans l'une de celles-ci, PAS dans c.vars :

  • c.state — données sérialisables CBOR pour les petits ensembles de données limités. Idéal pour la configuration, les compteurs, les petites listes de joueurs, les drapeaux de phase, etc. Restez sous 128 KB. Ne stockez pas de données non limitées ou croissantes ici (p. ex. journaux de chat, historiques d'événements, listes d'entités générées qui se développent sans limite). L'état est lu/écrit en tant que blob unique à chaque cycle de persistance.
  • c.kv — magasin clé-valeur pour les données non limitées. C'est ce que c.state utilise en dessous. Supporte les valeurs binaires. Utilisez pour les données plus grandes ou de taille variable comme les inventaires utilisateurs, les chunks du monde, les blobs de fichiers ou tout ensemble qui peut se développer avec le temps. Les clés sont scoped à l'instance d'acteur.
  • c.db — base de données SQLite pour les données structurées ou complexes. Utilisez quand vous avez besoin de requêtes, d'index, de jointures, d'agrégations ou de modélisation relationnelle. Idéal pour les classements, les historiques de matchs, les pools de joueurs ou toute donnée qui bénéficie de SQL.

Erreur courante : stocker des données significatives de jeu/application dans c.vars au lieu de les persister. Par exemple, si les utilisateurs peuvent générer des objets dans une simulation de physique, les définitions de génération (position, taille, type) doivent être persistées dans c.state (ou c.kv si non limitées), même si le moteur de physique gère le temps réel (non sérialisable) dans c.vars. Au redémarrage, run() doit recréer les objets runtime à partir des données persistées.

Déploiement des Backends Rivet

Supposez que l'utilisateur déploie sur Rivet Cloud, sauf indication contraire. Si l'utilisateur effectue l'auto-hébergement, consultez les guides d'auto-hébergement ci-dessous.

  1. Vérifiez que les Acteurs Rivet fonctionnent en développement local
  2. Invitez l'utilisateur à choisir un fournisseur pour déployer (voir Connect pour une liste des fournisseurs, tels que Vercel, Railway, etc.)
  3. Suivez le guide de déploiement pour ce fournisseur donné. Vous devrez instruire l'utilisateur quand vous avez besoin d'une intervention manuelle.

Référence API

La spécification OpenAPI de RivetKit est disponible dans le répertoire de la skill à openapi.json. Ce fichier documente tous les endpoints HTTP pour gérer les acteurs.

Notes Diverses

  • Le domaine Rivet est rivet.dev, pas rivet.gg

Mise en Garde TypeScript : Inférence de Client Acteur

  • Dans les projets TypeScript multi-fichiers, les appels bidirectionnels d'acteurs peuvent créer une dépendance circulaire de type quand les deux acteurs utilisent c.client<typeof registry>().
  • Les symptômes incluent généralement c.state devenant unknown, les méthodes d'acteur devenant possiblement undefined, ou des erreurs TS2322 / TS2722 après le premier appel d'acteur croisé.
  • Si une action retourne le résultat d'un appel d'acteur autre, préférez une annotation de type de retour explicite sur cette action au lieu de vous fier à l'inférence via c.client<typeof registry>().
  • Si les types de retour explicites ne suffisent pas, utilisez un type de client ou de registre plus étroit pour uniquement les acteurs que l'action nécessite.
  • En dernier recours, passez unknown pour le type de registre et soyez explicite que cela abandonne la sécurité des types à ce site d'appel.

Fonctionnalités

  • Calcul Longue Durée et Stateful : Chaque unité de calcul est comme un petit serveur qui se souvient des choses entre les demandes – pas besoin de re-récupérer les données d'une base de données ou de se soucier des délais d'expiration. Comme AWS Lambda, mais avec mémoire et sans délais d'expiration.
  • Lectures & Écritures Blazing-Fast : L'état est stocké sur la même machine que votre calcul, donc les lectures et écritures sont ultra-rapides. Pas de requêtes de base de données, pas de pics de latence. L'état est persisté à Rivet pour le stockage à long terme, donc il survit aux redémarrages du serveur.
  • Temps Réel : Mettez à jour l'état et diffusez les changements en temps réel avec WebSockets. Pas de systèmes pub/sub externes, pas de polling – juste les événements à faible latence intégrés.
  • Infiniment Scalable : Mettez à l'échelle automatiquement de zéro à des millions d'acteurs concurrents. Payez uniquement pour ce que vous utilisez avec la mise à l'échelle instantanée et sans démarrages à froid.
  • Tolérant aux Pannes : Gestion des erreurs et récupération intégrées. Les acteurs redémarrent automatiquement en cas de défaillance tout en préservant l'intégrité de l'état et en continuant les opérations.

Quand Utiliser les Acteurs Rivet

  • Agents IA & sandboxes : chaînes multi-étapes, mémoire de conversation, orchestration de sandbox.
  • Apps multiplayers ou collaboratives : docs CRDT, curseurs partagés, tableaux de bord en temps réel, chat.
  • Automatisation des workflows : travaux de fond, cron, limiteurs de débit, files d'attente durables, contrôle de la contrepression.
  • Backends intensifs en données : bases de données distribuées géographiquement ou par tenant, caches en mémoire, SQL shardé.
  • Charges de travail réseau : serveurs WebSocket, protocoles personnalisés, sync local-first, fanout edge.

Projet Minimal

Backend

index.ts

import { actor, event, setup } from "rivetkit";

const counter = actor({
    state: { count: 0 },
    events: {
        count: event<number>(),
    },
    actions: {
        increment: (c, amount: number) => {
            c.state.count += amount;
            c.broadcast("count", c.state.count);
            return c.state.count;
        },
    },
});

export const registry = setup({
    use: { counter },
});

registry.start();

Docs Client

Utilisez le SDK client qui correspond à votre app :

Référence Rapide Acteur

État En Mémoire

Données persistantes qui survivent aux redémarrages, crashes et déploiements. L'état est persisté sur Rivet Cloud ou Rivet auto-hébergé, donc il survit aux redémarrages si le processus actuel crash ou s'arrête.

État Initial Statique

import { actor } from "rivetkit";

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

État Initial Dynamique

import { actor } from "rivetkit";

interface CounterState {
  count: number;
}

const counter = actor({
  state: { count: 0 } as CounterState,
  createState: (c, input: { start?: number }): CounterState => ({
    count: input.start ?? 0,
  }),
  actions: {
    increment: (c) => c.state.count += 1,
  },
});

Documentation

Clés

Les clés identifient de manière unique les instances d'acteur. Utilisez des clés composées (tableaux) pour l'adressage hiérarchique :

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

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

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

// Clé composée : [org, room]
client.chatRoom.getOrCreate(["org-acme", "general"]);

// Accédez à la clé dans l'acteur via c.key

Ne construisez pas les clés avec interpolation de chaîne comme "org:${userId}" quand userId contient des données utilisateur. Utilisez plutôt des tableaux pour éviter les attaques par injection de clé.

Documentation

Input

Passez les données d'initialisation lors de la création d'acteurs. L'input n'est disponible que dans createState et onCreate, alors stockez-le dans l'état si vous en avez besoin plus tard.

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

const game = actor({
    state: { mode: "" },
    createState: (c, input: { mode: string }) => ({
        mode: input.mode, // Stockez l'input dans l'état pour un accès ultérieur
    }),
    actions: {
        getMode: (c) => c.state.mode,
    },
});

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

// Utilisation du client
const gameHandle = client.game.getOrCreate(["game-1"], {
    createWithInput: { mode: "ranked" },
});

Documentation

Variables Temporaires

Données temporaires qui ne survivent pas aux redémarrages. Utilisez pour les objets non sérialisables (émetteurs d'événements, connexions, etc.).

Vars Initial Statique

import { actor } from "rivetkit";

const counter = actor({
state: { count: 0 },
vars: { lastAccess: 0 },
actions: {
increment: (c) => {
c.vars.lastAccess = Date.now();
return c.state.count += 1;
},
},
});

Vars Initial Dynamique

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  createVars: () => ({
    emitter: new EventTarget(),
  }),
  actions: {
    increment: (c) => {
      c.vars.emitter.dispatchEvent(new Event("change"));
      return c.state.count += 1;
    },
  },
});

Documentation

Actions

Les actions sont le principal moyen pour les clients et les autres acteurs de communiquer avec un acteur.

import { actor } from "rivetkit";

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

Documentation

Événements & Broadcasts

Les événements permettent la communication en temps réel des acteurs aux clients connectés.

import { actor, event } from "rivetkit";

const chatRoom = actor({
    state: { messages: [] as string[] },
    events: {
        newMessage: event<{ text: string }>(),
    },
    actions: {
        sendMessage: (c, text: string) => {
            // Diffusez à TOUS les clients connectés
            c.broadcast("newMessage", { text });
        },
    },
});

Documentation

Connexions

Accédez à la connexion actuelle via c.conn ou à tous les clients connectés via c.conns. Utilisez c.conn.id ou c.conn.state pour identifier de manière sécurisée qui appelle une action. L'état de connexion est initialisé via connState ou createConnState, qui reçoit les paramètres passés par le client à la connexion.

État Initial de Connexion Statique

import { actor } from "rivetkit";

const chatRoom = actor({
state: {},
connState: { visitorId: 0 },
onConnect: (c, conn) => {
conn.state.visitorId = Math.random();
},
actions: {
whoAmI: (c) => c.conn.state.visitorId,
},
});

État Initial de Connexion Dynamique

import { actor } from "rivetkit";

const chatRoom = actor({
  state: {},
  // params passés par le client
  createConnState: (c, params: { userId: string }) => ({
    userId: params.userId,
  }),
  actions: {
    // Accédez à l'état et aux paramètres de la connexion actuelle
    whoAmI: (c) => ({
      state: c.conn.state,
      params: c.conn.params,
    }),
    // Itérez toutes les connexions avec c.conns
    notifyOthers: (c, text: string) => {
      for (const conn of c.conns.values()) {
        if (conn !== c.conn) conn.send("notification", { text });
      }
    },
  },
});

Documentation

Files d'Attente

Utilisez les files d'attente pour traiter les messages durables dans l'ordre à l'intérieur d'une boucle run.

import { actor, queue } from "rivetkit";

const counter = actor({
    state: { value: 0 },
    queues: {
        increment: queue<{ amount: number }>(),
    },
    run: async (c) => {
        for await (const message of c.queue.iter()) {
            c.state.value += message.body.amount;
        }
    },
});

Documentation

Workflows

Utilisez les workflows quand votre logique run a besoin d'une exécution multi-étapes durable et rejouable.

import { actor, queue } from "rivetkit";
import { workflow } from "rivetkit/workflow";

const worker = actor({
    state: { processed: 0 },
    queues: {
        tasks: queue<{ url: string }>(),
    },
    run: workflow(async (ctx) => {
        await ctx.loop("task-loop", async (loopCtx) => {
            const message = await loopCtx.queue.next("wait-task");

            await loopCtx.step("process-task", async () => {
                await processTask(message.body.url);
                loopCtx.state.processed += 1;
            });
        });
    }),
});

async function processTask(url: string): Promise<void> {
    const res = await fetch(url, { method: "POST" });
    if (!res.ok) throw new Error(`Task failed: ${res.status}`);
}

Documentation

Communication Entre Acteurs

Les acteurs peuvent appeler d'autres acteurs en utilisant c.client().

import { actor, setup } from "rivetkit";

const inventory = actor({
    state: { stock: 100 },
    actions: {
        reserve: (c, amount: number) => {
            c.state.stock -= amount;
        },
    },
});

const order = actor({
    state: {},
    actions: {
        process: async (c) => {
            const client = c.client<typeof registry>();
            await client.inventory.getOrCreate(["main"]).reserve(1);
        },
    },
});

const registry = setup({ use: { inventory, order } });

Documentation

Planification

Planifiez l'exécution d'actions après un délai ou à un moment spécifique. Les planifications persistent à travers les redémarrages, mises à jour et crashes.

import { actor, event } from "rivetkit";

const reminder = actor({
    state: { message: "" },
    events: {
        reminder: event<{ message: string }>(),
    },
    actions: {
        // Planifiez une action pour s'exécuter après un délai (ms)
        setReminder: (c, message: string, delayMs: number) => {
            c.state.message = message;
            c.schedule.after(delayMs, "sendReminder");
        },
        // Planifiez une action pour s'exécuter à un timestamp spécifique
        setReminderAt: (c, message: string, timestamp: number) => {
            c.state.message = message;
            c.schedule.at(timestamp, "sendReminder");
        },
        sendReminder: (c) => {
            c.broadcast("reminder", { message: c.state.message });
        },
    },
});

Documentation

Destruction d'Acteurs

Supprimez définitivement un acteur et son état en utilisant c.destroy().

import { actor } from "rivetkit";

const userAccount = actor({
    state: { email: "", name: "" },
    onDestroy: (c) => {
        console.log(`Account ${c.state.email} deleted`);
    },
    actions: {
        deleteAccount: (c) => {
            c.destroy();
        },
    },
});

Documentation

Hooks de Cycle de Vie

Les acteurs supportent les hooks pour l'initialisation, le traitement de fond, les connexions, la mise en réseau et les changements d'état. Utilisez run pour les boucles de fond longues et quittez proprement au shutdown avec c.aborted ou c.abortSignal.

import { actor, event, queue } from "rivetkit";

interface RoomState {
    users: Record<string, boolean>;
    name?: string;
}

interface RoomInput {
    roomName: string;
}

interface ConnState {
    userId: string;
    joinedAt: number;
}

const chatRoom = actor({
    state: { users: {} } as RoomState,
    vars: { startTime: 0 },
    connState: { userId: "", joinedAt: 0 } as ConnState,
    events: {
        stateChanged: event<RoomState>(),
    },
    queues: {
        work: queue<{ task: string }>(),
    },

    // Initialisation de l'état & vars
    createState: (c, input: RoomInput): RoomState => ({
        users: {},
        name: input.roomName,
    }),
    createVars: () => ({ startTime: Date.now() }),

    // Cycle de vie de l'acteur
    onCreate: (c) => console.log("created", c.key),
    onDestroy: (c) => console.log("destroyed"),
    onWake: (c) => console.log("actor started"),
    onSleep: (c) => console.log("actor sleeping"),
    run: async (c) => {
        for await (const message of c.queue.iter()) {
            console.log("processing", message.body.task);
        }
    },
    onStateChange: (c, newState) => c.broadcast("stateChanged", newState),

    // Cycle de vie de la connexion
    createConnState: (c, params): ConnState => ({
        userId: (params as { userId: string }).userId,
        joinedAt: Date.now(),
    }),
    onBeforeConnect: (c, params) => {
        /* valider auth */
    },
    onConnect: (c, conn) => console.log("connected:", conn.state.userId),
    onDisconnect: (c, conn) => console.log("disconnected:", conn.state.userId),

    // Mise en réseau
    onRequest: (c, req) => new Response(JSON.stringify(c.state)),
    onWebSocket: (c, socket) => socket.addEventListener("message", console.log),

    // Transformation de réponse
    onBeforeActionResponse: <Out>(
        c: unknown,
        name: string,
        args: unknown[],
        output: Out,
    ): Out => output,

    actions: {},
});

Documentation

Types de Contexte

Lorsque vous écrivez des fonctions d'aide en dehors de la définition d'acteur, utilisez *ContextOf<typeof myActor> pour extraire le type de contexte correct. Ne définissez pas manuellement votre propre interface de contexte — dérivez-la toujours de la définition d'acteur.

import { actor, ActionContextOf } from "rivetkit";

const gameRoom = actor({
    state: { players: [] as string[], score: 0 },
    actions: {
        addPlayer: (c, playerId: string) => {
            validatePlayer(c, playerId);
            c.state.players.push(playerId);
        },
    },
});

// Bon : dérivez le type de contexte de la définition d'acteur
function validatePlayer(c: ActionContextOf<typeof gameRoom>, playerId: string) {
    if (c.state.players.includes(playerId)) {
        throw new Error("Player already in room");
    }
}

// Mauvais : ne définissez pas manuellement les types de contexte comme ceci
// type MyContext = { state: { players: string[] }; ... };

Documentation

Erreurs

Utilisez UserError pour lancer des erreurs qui sont retournées en toute sécurité aux clients. Passez metadata pour inclure les données structurées. Les autres erreurs sont converties en « erreur interne » générique pour la sécurité.

Acteur

import { actor, UserError } from "rivetkit";

const user = actor({
state: { username: "" },
actions: {
updateUsername: (c, username: string) => {
if (username.length < 3) {
throw new UserError("Username too short", {
code: "username_too_short",
metadata: { minLength: 3, actual: username.length },
});
}
c.state.username = username;
},
},
});

Client

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

const user = actor({
  state: { username: "" },
  actions: { updateUsername: (c, username: string) => { c.state.username = username; } }
});

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

try {
  await client.user.getOrCreate([]).updateUsername("ab");
} catch (error) {
  if (error instanceof ActorError) {
    console.log(error.code);     // "username_too_short"
    console.log(error.metadata); // { minLength: 3, actual: 2 }
  }
}

Documentation

Handlers HTTP & WebSocket Bas Niveau

Pour les protocoles personnalisés ou l'intégration de bibliothèques qui ont besoin d'accès direct aux connexions Request/Response ou WebSocket HTTP, utilisez onRequest et onWebSocket.

Documentation du Handler HTTP · Documentation du Handler WebSocket

Icônes & Noms

Personnalisez l'apparence des acteurs dans l'interface utilisateur avec les noms d'affichage et les icônes. Il est recommandé de toujours fournir un nom et une icône aux acteurs pour les rendre plus faciles à distinguer dans le tableau de bord.

import { actor } from "rivetkit";

const chatRoom = actor({
    options: {
        name: "Chat Room",
        icon: "💬", // ou FontAwesome : "comments", "chart-line", etc.
    },
    // ...
});

Documentation

Documentation Client

Trouvez les guides clients complets ici :

Modèles Courants

Les acteurs se mettent à l'échelle naturellement grâce à l'état isolé et au passage de messages. Structurez vos applications avec ces modèles :

Documentation

Un Acteur Par Entité

Créez un acteur par utilisateur, document ou room. Utilisez des clés composées pour scoper les entités :

import { createClient } from "rivetkit/client";
import type { registry } from "./index";

const client = createClient<typeof registry>("http://localhost:6420");

// Clé unique : un acteur par utilisateur
client.user.getOrCreate(["user-123"]);

// Clé composée : document scopé à une organisation
client.document.getOrCreate(["org-acme", "doc-456"]);
import { actor, setup } from "rivetkit";

export const user = actor({
  state: { name: "" },
  actions: {},
});

export const document = actor({
  state: { content: "" },
  actions: {},
});

export const registry = setup({ use: { user, document } });

registry.start();

Acteurs Coordinateur & Données

Les acteurs de données gèrent la logique centrale (chat rooms, sessions de jeu, données utilisateur). Les acteurs coordinateur suivent et gèrent les collections d'acteurs de données—pensez à eux comme un index.

import { actor, setup } from "rivetkit";

// Coordinateur : suit les chat rooms d'une organisation
export const chatRoomList = actor({
state: { rooms: [] as string[] },
actions: {
addRoom: async (c, name: string) => {
// Créez l'acteur chat room
const client = c.client<typeof registry>();
await client.chatRoom.create([c.key[0], name]);
c.state.rooms.push(name);
},
listRooms: (c) => c.state.rooms,
},
});

// Acteur de données : gère une seule chat room
export const chatRoom = actor({
state: { messages: [] as string[] },
actions: {
send: (c, msg: string) => { c.state.messages.push(msg); },
},
});

export const registry = setup({ use: { chatRoomList, chatRoom } });

registry.start();
import { createClient } from "rivetkit/client";
import type { registry } from "./index";

const client = createClient<typeof registry>("http://localhost:6420");

// Coordinateur par org
const coordinator = client.chatRoomList.getOrCreate(["org-acme"]);
await coordinator.addRoom("general");
await coordinator.addRoom("random");

// Accédez aux chat rooms créés par coordinateur
client.chatRoom.get(["org-acme", "general"]);

Boucle Run

Utilisez une boucle run pour le travail continu de fond à l'intérieur d'un acteur. Traitez les messages de file d'attente dans l'ordre, exécutez la logique par intervalles, streamez les réponses IA ou coordonnez les tâches longue durée.

import { actor, queue, setup } from "rivetkit";

const counterWorker = actor({
    state: { value: 0 },
    queues: {
        mutate: queue<{ delta: number }>(),
    },
    run: async (c) => {
        for await (const message of c.queue.iter()) {
            c.state.value += message.body.delta;
        }
    },
    actions: {
        getValue: (c) => c.state.value,
    },
});

const registry = setup({ use: { counterWorker } });

Boucle Workflow

Utilisez ce modèle pour les workflows longue durée et durables qui initialisent les ressources, traitent les commandes dans une boucle, puis nettoient.

import { actor, queue, setup } from "rivetkit";
import { Loop, workflow } from "rivetkit/workflow";

type WorkMessage = { amount: number };
type ControlMessage = { type: "stop"; reason: string };

const worker = actor({
    state: {
        phase: "idle" as "idle" | "running" | "stopped",
        processed: 0,
        total: 0,
        stopReason: null as string | null,
    },
    queues: {
        work: queue<WorkMessage>(),
        control: queue<ControlMessage>(),
    },
    run: workflow(async (ctx) => {
        await ctx.step("setup", async () => {
            await fetch("https://api.example.com/workers/init", {
                method: "POST",
            });
            ctx.state.phase = "running";
            ctx.state.stopReason = null;
        });

        const stopReason = await ctx.loop("worker-loop", async (loopCtx) => {
            const message = await loopCtx.queue.next("wait-command", {
                names: ["work", "control"],
            });

            if (message.name === "work") {
                await loopCtx.step("apply-work", async () => {
                    await fetch("https://api.example.com/workers/process", {
                        method: "POST",
                        body: JSON.stringify({ amount: message.body.amount }),
                    });
                    loopCtx.state.processed += 1;
                    loopCtx.state.total += message.body.amount;
                });
                return;
            }

            return Loop.break((message.body as ControlMessage).reason);
        });

        await ctx.step("teardown", async () => {
            await fetch("https://api.example.com/workers/shutdown", {
                method: "POST",
            });
            ctx.state.phase = "stopped";
            ctx.state.stopReason = stopReason;
        });
    }),
});

const registry = setup({ use: { worker } });

Documentation

Actions vs Files d'Attente

  • Actions ne sont pas durables. Utilisez-les pour les lectures en temps réel, les données éphémères et la communication à faible latence comme l'entrée joueur.
  • Files d'Attente sont durables. Utilisez-les pour sérialiser les mutations via la boucle run, évitant les conditions de course avec SQLite et autres états locaux. Les appelants peuvent toujours attendre une réponse du travail en attente.

Authentification, Sécurité & CORS

  • Validez les identifiants dans onBeforeConnect ou createConnState et levez une erreur pour rejeter les connexions non autorisées.
  • Utilisez c.conn.state pour identifier de manière sécurisée les utilisateurs dans les actions plutôt que de faire confiance aux paramètres d'action.
  • Pour l'accès multi-origines, validez l'origine de la demande dans onBeforeConnect.

Documentation Authentification · Documentation CORS

Versions & Upgrades

Lors du déploiement d'un nouveau code, définissez un numéro de version afin que Rivet puisse acheminer les nouveaux acteurs vers le dernier runner et éventuellement drainer les anciens. Utilisez un timestamp de build, un nombre de commits git ou un numéro de build CI comme version. Il est très important de configurer la gestion des versions avant de déployer en production. Sans gestion des versions, les acteurs peuvent régresser en s'exécutant sur des versions de runner plus anciennes, et les acteurs existants ne seront jamais forcés de migrer vers de nouveaux runners. Ils continueront à s'exécuter indéfiniment sur les anciens runners jusqu'à leur sortie.

Documentation

Anti-Modèles

Ne construisez jamais un acteur « god »

Ne mettez pas toute votre logique dans un seul acteur. Un acteur god sérialise chaque opération via un goulot d'étranglement, tue le parallélisme et fait échouer le système entier comme une unité. Divisez en acteurs fokalisés par entité.

Ne créez jamais un acteur par demande

Les acteurs sont longue durée et maintiennent l'état à travers les demandes. Créer un nouvel acteur pour chaque demande entrante jette l'avantage principal du modèle et gaspille les ressources sur la création et la suppression d'acteurs. Utilisez les acteurs pour les entités persistantes et les fonctions régulières pour le travail sans état.

Carte de Référence

Acteurs

Agent OS

Clients

Connect

Cookbook

Général

Auto-Hébergement

Skills similaires