durable-objects

Créer et examiner les Cloudflare Durable Objects. À utiliser lors de la création de coordination avec état (salons de discussion, jeux multijoueurs, systèmes de réservation), de l'implémentation de méthodes RPC, du stockage SQLite, des alarmes, des WebSockets, ou de l'examen du code DO pour les bonnes pratiques. Couvre l'intégration Workers, la configuration wrangler et les tests avec Vitest. Privilégie la récupération à partir de la documentation Cloudflare par rapport aux connaissances pré-entraînées.

npx skills add https://github.com/cloudflare/skills --skill durable-objects

Objets Durables

Construisez des applications avec état et coordonnées sur le réseau périphérique de Cloudflare en utilisant les Objets Durables.

Sources de Récupération

Vos connaissances des APIs et de la configuration des Objets Durables peuvent être obsolètes. Préférez la récupération à la pré-formation pour toute tâche d'Objets Durables.

Ressource URL
Docs https://developers.cloudflare.com/durable-objects/
API Reference https://developers.cloudflare.com/durable-objects/api/
Best Practices https://developers.cloudflare.com/durable-objects/best-practices/
Examples https://developers.cloudflare.com/durable-objects/examples/

Récupérez la page de documentation pertinente lors de l'implémentation de fonctionnalités.

Quand les Utiliser

  • Créer de nouvelles classes d'Objets Durables pour la coordination avec état
  • Implémenter des méthodes RPC, des alarmes ou des gestionnaires WebSocket
  • Examiner le code DO existant pour les meilleures pratiques
  • Configurer wrangler.jsonc/toml pour les liaisons DO et les migrations
  • Écrire des tests avec @cloudflare/vitest-pool-workers
  • Concevoir des stratégies de sharding et des relations parent-enfant

Documentation de Référence

  • ./references/rules.md - Règles principales, stockage, concurrence, RPC, alarmes
  • ./references/testing.md - Configuration Vitest, tests unitaires/intégration, test des alarmes
  • ./references/workers.md - Gestionnaires Workers, types, configuration wrangler, observabilité

Recherche : blockConcurrencyWhile, idFromName, getByName, setAlarm, sql.exec

Principes Fondamentaux

Utiliser les Objets Durables Pour

Besoin Exemple
Coordination Salons de chat, jeux multijoueurs, documents collaboratifs
Cohérence forte Inventaire, systèmes de réservation, jeux au tour par tour
Stockage par entité SaaS multi-tenant, données par utilisateur
Connexions persistantes WebSockets, notifications en temps réel
Travail planifié par entité Renouvellement d'abonnements, délais d'expiration de jeux

NE PAS Utiliser Pour

  • Gestion de requêtes sans état (utiliser les Workers ordinaires)
  • Besoins de distribution mondiale maximale
  • Requêtes indépendantes à grand nombre de branches

Référence Rapide

Configuration Wrangler

// wrangler.jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}

Modèle d'Objet Durable Basique

import { DurableObject } from "cloudflare:workers";

export interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
}

export class MyDurableObject extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    ctx.blockConcurrencyWhile(async () => {
      this.ctx.storage.sql.exec(`
        CREATE TABLE IF NOT EXISTS items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          data TEXT NOT NULL
        )
      `);
    });
  }

  async addItem(data: string): Promise<number> {
    const result = this.ctx.storage.sql.exec<{ id: number }>(
      "INSERT INTO items (data) VALUES (?) RETURNING id",
      data
    );
    return result.one().id;
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const stub = env.MY_DO.getByName("my-instance");
    const id = await stub.addItem("hello");
    return Response.json({ id });
  },
};

Règles Critiques

  1. Modélisez autour d'atomes de coordination - Un DO par salon de chat/jeu/utilisateur, pas un DO global unique
  2. Utilisez getByName() pour le routage déterministe - Même entrée = même instance DO
  3. Utilisez le stockage SQLite - Configurez new_sqlite_classes dans les migrations
  4. Initialisez dans le constructeur - Utilisez blockConcurrencyWhile() pour la configuration du schéma uniquement
  5. Utilisez les méthodes RPC - Pas le gestionnaire fetch() (date de compatibilité >= 2024-04-03)
  6. Persistez d'abord, mettez en cache ensuite - Écrivez toujours dans le stockage avant de mettre à jour l'état en mémoire
  7. Une alarme par DO - setAlarm() remplace toute alarme existante

Anti-Modèles (JAMAIS)

  • Un DO global unique traitant toutes les requêtes (goulot d'étranglement)
  • Utiliser blockConcurrencyWhile() à chaque requête (tue le débit)
  • Stocker l'état critique uniquement en mémoire (perdu lors de l'éviction/plantage)
  • Utiliser await entre les écritures de stockage connexes (brise l'atomicité)
  • Maintenir blockConcurrencyWhile() à travers fetch() ou les E/S externes

Création de Stub

// Déterministe - préféré pour la plupart des cas
const stub = env.MY_DO.getByName("room-123");

// À partir d'une chaîne ID existante
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);

// Nouvel ID unique - stocker le mappage en externe
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);

Opérations de Stockage

// SQL (synchrone, recommandé)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();

// KV (asynchrone)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");

Alarmes

// Planifier (remplace existante)
await this.ctx.storage.setAlarm(Date.now() + 60_000);

// Gestionnaire
async alarm(): Promise<void> {
  // Traiter le travail planifié
  // Optionnellement reprogrammer : await this.ctx.storage.setAlarm(...)
}

// Annuler
await this.ctx.storage.deleteAlarm();

Démarrage Rapide des Tests

import { env } from "cloudflare:test";
import { describe, it, expect } from "vitest";

describe("MyDO", () => {
  it("should work", async () => {
    const stub = env.MY_DO.getByName("test");
    const result = await stub.addItem("test");
    expect(result).toBe(1);
  });
});