Base de données par locataire
IMPORTANT : Avant de faire quoi que ce soit, vous DEVEZ lire BASE_SKILL.md dans le répertoire de cette compétence. Il contient des conseils essentiels sur le débogage, la gestion des erreurs, la gestion d'état, le déploiement et la configuration du projet. Ces règles et modèles s'appliquent à tous les travaux RivetKit. Tout ce qui suit suppose que vous avez déjà lu et compris cela.
Exemples fonctionnels
Si vous avez besoin d'une implémentation de référence, lisez le code d'exemple brut et fonctionnel dans ces modèles :
Modèles pour les architectures base de données par locataire avec RivetKit. Au lieu d'une seule base de données partagée avec une colonne tenant_id sur chaque table, chaque locataire obtient son propre Rivet Actor, et cet actor possède l'ensemble du dataset du locataire.
Code de démarrage
Commencez par l'exemple fonctionnel sur GitHub et adaptez-le. L'exemple stocke le dataset de chaque locataire dans l'état de l'actor JSON et sert un tableau de bord React avec des mises à jour d'événements en direct.
| Sujet | Résumé |
|---|---|
| Isolation | Un actor companyDatabase par locataire, indexé par le nom de l'entreprise. Le changement de locataires remplace l'ensemble du dataset. |
| État | État de l'actor JSON contenant les tableaux employees et projects plus les timestamps. Pas de SQLite, pas de files d'attente, pas de planification. |
| Temps réel | Chaque action d'écriture mute l'état, puis diffuse un événement typé (employeeAdded, projectAdded) à tous les clients connectés de ce locataire. |
| Auth | Aucune. L'écran de connexion est cosmétique. Les conseils de production figurent dans la liste de contrôle de sécurité. |
Le modèle d'isolation
La clé de l'actor est l'identifiant du locataire. Le client se connecte avec useActor({ name: "companyDatabase", key: [companyName] }) et l'actor lit c.key[0] dans createState pour ensemencer le dataset de ce locataire. Cela vous donne :
- Un actor par locataire :
companyDatabase[tenantId]adresse exactement une instance d'actor. Deux locataires ne peuvent jamais partager un actor. - Un dataset par locataire : Toutes les lectures et écritures passent par l'état de cet actor, donc il n'y a pas de table partagée avec une colonne
tenant_idà filtrer incorrectement. Les fuites entre locataires nécessitent de construire la mauvaise clé, pas d'oublier une clauseWHERE. - Aucune injection de clé : Les clés sont des tableaux, pas des chaînes interpolées.
key: [tenantId]ne peut pas être échappé de la manière dont"tenant:" + tenantIdla concaténation de chaînes peut l'être. Voir Keys.
Le test de l'exemple (tests/per-tenant-database.test.ts) prouve l'isolation : les données écrites dans companyDatabase["Alpha Co"] n'apparaissent jamais dans companyDatabase["Beta Co"].
Choix d'un backend d'état
L'exemple utilise l'état de l'actor JSON brut. Le même modèle clé-égal-locataire fonctionne avec n'importe quel backend d'état de l'actor.
| Backend | Utiliser quand | Docs | Code fonctionnel |
|---|---|---|---|
| État de l'actor JSON | Petits datasets, lectures simples, l'ensemble du dataset tient confortablement en mémoire. Ce que l'exemple utilise. | State | GitHub |
SQLite de l'actor (rivetkit/db) |
Tables, index, requêtes SQL, données plus grandes que la mémoire, schéma relationnel par locataire. | SQLite | GitHub |
| SQLite + Drizzle | Schéma typé, générateur de requêtes et fichiers de migration générés au-dessus de SQLite de l'actor. | SQLite + Drizzle | GitHub |
Avec l'une ou l'autre option SQLite, chaque locataire obtient sa propre base de données SQLite intégrée, puisque la base de données est limitée à l'actor et l'actor est limité au locataire.
Migrations
L'exemple par locataire n'a pas de migrations car l'état JSON n'a pas de schéma. Lorsque vous adoptez SQLite, les migrations s'exécutent par base de données de locataire :
- SQL brut :
db({ onMigrate })exécute votre SQL de migration à l'intérieur d'un savepoint SQLite avant que l'actor ne serve le trafic. SionMigratelève une exception, tout le SQL de migration est annulé de manière atomique et l'actor ne démarre pas. Voir SQLite. - Drizzle :
drizzle-kitgénère des fichiers de migration à partir de votre schéma typé, etdb({ schema, migrations })les applique au moment où l'actor se réveille. Voir SQLite + Drizzle.
Parce que chaque locataire a sa propre base de données, les migrations se déploient par actor à mesure que l'actor de chaque locataire se réveille, plutôt que comme une grande migration contre une base de données partagée.
L'identifiant du locataire doit provenir de l'authentification
La connexion de l'exemple est cosmétique : le client choisit n'importe quelle chaîne d'entreprise et cette chaîne devient la clé de l'actor, donc n'importe quel visiteur peut lire et écrire les données de n'importe quel locataire. Ne publiez pas cela. En tant qu'extension de production requise (non implémentée par l'exemple) :
- Dérivez l'identifiant du locataire à partir d'une credential vérifiée, telle qu'une revendication JWT, jamais à partir d'une entrée utilisateur.
- Validez la credential par rapport à
c.keydansonBeforeConnect(réussite/échec) oucreateConnState(stockez l'utilisateur vérifié sur l'état de la connexion). Voir Authentication et Connections. - Ajoutez des vérifications de permission par action en plus de l'authentification au niveau de la connexion. Voir Access Control.
Actors
- Clé :
companyDatabase[companyName](clé tableau à un seul élément ;c.key[0]est le nom de l'entreprise) - Responsabilité : Un actor par locataire. Contient les employés et projets de cette entreprise dans un état persistant, sert les lectures et écritures via des actions, et diffuse les mutations aux clients connectés.
- Actions
addEmployeelistEmployeesaddProjectlistProjectsgetStats
- Files d'attente
- Aucune
- Événements
employeeAddedprojectAdded
- État
- JSON
company_nameemployeesprojectscreated_atupdated_at
Chaque action d'écriture suit la même forme mutate-puis-diffuse : poussez l'enregistrement dans c.state, augmentez updated_at, diffusez l'événement typé, retournez l'enregistrement. Voir Actions et Events.
Cycle de vie
sequenceDiagram
participant A as Client du locataire A
participant DA as companyDatabase A
participant B as Client du locataire B
participant DB as companyDatabase B
Note over A: authentifier et dériver l'identifiant du locataire
A->>DA: se connecter avec la clé [tenantA]
Note over DA: createState ensemence company_name, employees, projects
A->>DA: listEmployees() + listProjects() + getStats()
A->>DA: addEmployee(name, role)
DA-->>A: événement employeeAdded
B->>DB: se connecter avec la clé [tenantB]
Note over DB: actor séparé, dataset séparé
B->>DB: listEmployees()
DB-->>B: données du locataire B uniquement
Dans l'exemple, l'étape « authentifier » est un sélecteur d'entreprise en texte libre. Le reste du flux correspond au diagramme : createState ensemence le dataset à la première création, le tableau de bord se charge avec listEmployees, listProjects et getStats, et chaque client connecté du même locataire reçoit les événements employeeAdded et projectAdded.
Liste de contrôle de sécurité
L'exemple n'est livré avec aucun de ceux-ci. Appliquez tous ceux-ci avant la production.
- Identité du locataire : Dérivez l'identifiant du locataire à partir d'une revendication JWT vérifiée, jamais à partir d'une chaîne fournie par le client.
- Validation de la connexion : Dans
onBeforeConnectoucreateConnState, vérifiez que la revendication de locataire de la credential correspond àc.keyet rejetez les désaccords. - Autorisation par action : Vérifiez le rôle de l'appelant avant les actions mutantes (
addEmployee,addProject), pas seulement à la connexion. Voir Access Control. - Validation d'entrée : Limitez les longueurs de nom et de rôle et validez les énumérations. L'exemple ne fait que réduire l'entrée et substituer les valeurs par défaut de secours.
- Construction de clé : Passez toujours l'identifiant du locataire comme élément de tableau (
key: [tenantId]). Ne jamais interpoler les identifiants de locataire dans les chaînes de clé, et ne jamais construire des clés à partir de l'entrée d'un locataire pour adresser l'actor d'un autre locataire. - Limites de croissance : En tant qu'extension recommandée, limitez ou paginéz les tableaux
employeesetprojects. L'exemple les laisse croître sans limites dans l'état JSON ; passez à SQLite lorsque le dataset dépasse la mémoire.
Carte de référence
Actors
- Access Control
- Actions
- Actor Keys
- Actor Scheduling
- Actor Statuses
- AI and User-Generated Rivet Actors
- Authentication
- Communicating Between Actors
- Connections
- Custom Inspector Tabs
- Debugging
- Design Patterns
- Destroying Actors
- Errors
- Fetch and WebSocket Handler
- Helper Types
- Icons & Names
- Input Parameters
- Lifecycle
- Limits
- Low-Level HTTP Request Handler
- Low-Level KV Storage
- Low-Level WebSocket Handler
- Metadata
- Next.js Quickstart
- Node.js & Bun Quickstart
- Queues & Run Loops
- React Quickstart
- Realtime
- Rust Quickstart (Preview)
- Sandbox Actor
- Scaling & Concurrency
- Sharing and Joining State
- SQLite
- SQLite + Drizzle
- State & Storage
- Testing
- Troubleshooting
- Types
- Vanilla HTTP API
- Versions & Upgrades
- Workflows
Agent Os
- Agent-to-Agent Communication
- agentOS vs Sandbox
- Authentication
- Benchmarks
- Configuration
- Core Package
- Cron Jobs
- Deployment
- Embedded LLM Gateway
- Events
- Filesystem
- Limitations
- LLM Credentials
- Multiplayer
- Networking & Previews
- Overview
- Permissions
- Persistence & Sleep
- Pi
- Processes & Shell
- Queues
- Quickstart
- Sandbox Mounting
- Security & Auth
- Security Model
- Sessions
- Software
- SQLite
- System Prompt
- Tools
- Webhooks
- Workflow Automation
Clients
Connect
- Deploy To Amazon Web Services Lambda
- Deploying to AWS ECS
- Deploying to Cloudflare Workers
- Deploying to Freestyle
- Deploying to Google Cloud Run
- Deploying to Hetzner
- Deploying to Kubernetes
- Deploying to Railway
- Deploying to Rivet Compute
- Deploying to Supabase Functions
- Deploying to Vercel
- Deploying to VMs & Bare Metal
Cookbook
- AI Agent
- AI Agent Workspaces
- Chat Room
- Collaborative Text Editor
- Cron Jobs and Scheduled Tasks
- Database per Tenant
- Deploying Rivet in a VPC or Air-Gapped Network
- Live Cursors and Presence
- Multiplayer Game
Général
- Actor Configuration
- Architecture
- Cross-Origin Resource Sharing
- Documentation for LLMs & AI
- Edge Networking
- Endpoints
- Environment Variables
- HTTP Server
- Logging
- Pool Configuration
- Production Checklist
- Registry Configuration
- Runtime Modes