Marketplace

durable-objects-pattern-checker

Automatically validates Cloudflare Durable Objects usage patterns, ensuring correct state management, hibernation, and strong consistency practices

$ Installer

git clone https://github.com/hirefrank/hirefrank-marketplace /tmp/hirefrank-marketplace && cp -r /tmp/hirefrank-marketplace/plugins/edge-stack/skills/durable-objects-pattern-checker ~/.claude/skills/hirefrank-marketplace

// tip: Run this command in your terminal to install the skill


name: durable-objects-pattern-checker description: Automatically validates Cloudflare Durable Objects usage patterns, ensuring correct state management, hibernation, and strong consistency practices triggers: ["Durable Object imports", "DO stub usage", "state management patterns", "DO ID generation"]

Durable Objects Pattern Checker SKILL

Activation Patterns

This SKILL automatically activates when:

  • Durable Object imports or exports are detected
  • DO stub creation and usage patterns
  • State management in Durable Objects
  • ID generation patterns (idFromName, newUniqueId)
  • Hibernation and lifecycle patterns
  • WebSocket or real-time features with DOs

Expertise Provided

Durable Objects Best Practices

  • State Management: Ensures proper state persistence and consistency
  • ID Generation: Validates correct ID patterns for different use cases
  • Hibernation: Checks for proper hibernation implementation
  • Lifecycle Management: Validates constructor, fetch, and alarm handling
  • Strong Consistency: Ensures DOs are used when strong consistency is needed
  • Performance: Identifies DO performance anti-patterns

Specific Checks Performed

❌ Durable Objects Anti-Patterns

// These patterns trigger immediate alerts:
// Using DOs for stateless operations
export default {
  async fetch(request: Request, env: Env) {
    const id = env.COUNTER.newUniqueId();  // New DO every request!
    const stub = env.COUNTER.get(id);
    return stub.fetch(request);  // Overkill for simple counter
  }
}

// Missing hibernation for long-lived DOs
export class ChatRoom {
  constructor(state, env) {
    this.state = state;
    // Missing this.state.storage.setAlarm() for hibernation
  }
}

✅ Durable Objects Best Practices

// These patterns are validated as correct:
// Reuse DO instances for stateful coordination
export default {
  async fetch(request: Request, env: Env) {
    const ip = request.headers.get('CF-Connecting-IP');
    const id = env.RATE_LIMITER.idFromName(ip);  // Reuse same DO
    const stub = env.RATE_LIMITER.get(id);
    return stub.fetch(request);
  }
}

// Proper hibernation implementation
export class ChatRoom {
  constructor(state, env) {
    this.state = state;
    this.env = env;
    
    // Set alarm for hibernation after inactivity
    this.state.storage.setAlarm(Date.now() + 30000); // 30 seconds
  }
  
  alarm() {
    // DO will hibernate after alarm
  }
}

Integration Points

Complementary to Existing Components

  • cloudflare-architecture-strategist agent: Handles complex DO architecture, SKILL provides immediate pattern validation
  • edge-performance-oracle agent: Handles DO performance analysis, SKILL ensures correct usage patterns
  • workers-binding-validator SKILL: Ensures DO bindings are correct, SKILL validates usage patterns

Escalation Triggers

  • Complex DO architecture questions → cloudflare-architecture-strategist agent
  • DO performance troubleshooting → edge-performance-oracle agent
  • DO migration strategies → cloudflare-architecture-strategist agent

Validation Rules

P1 - Critical (Will Cause Issues)

  • New Unique ID Per Request: Creating new DO for every request
  • Missing Hibernation: Long-lived DOs without hibernation
  • State Leaks: State not properly persisted to storage
  • Blocking Operations: Synchronous operations in DO fetch

P2 - High (Performance/Correctness Issues)

  • Wrong ID Pattern: Using newUniqueId when idFromName is appropriate
  • Stateless DOs: Using DOs for operations that don't need state
  • Missing Error Handling: DO operations without proper error handling
  • Alarm Misuse: Incorrect alarm patterns for hibernation

P3 - Medium (Best Practices)

  • State Size: Large state objects that impact performance
  • Concurrency: Missing concurrency control for shared state
  • Cleanup: Missing cleanup in DO lifecycle

Remediation Examples

Fixing New Unique ID Per Request

// ❌ Critical: New DO for every request (expensive and wrong)
export default {
  async fetch(request: Request, env: Env) {
    const userId = getUserId(request);
    
    // Creates new DO instance for every request!
    const id = env.USER_SESSION.newUniqueId();
    const stub = env.USER_SESSION.get(id);
    
    return stub.fetch(request);
  }
}

// ✅ Correct: Reuse DO for same entity
export default {
  async fetch(request: Request, env: Env) {
    const userId = getUserId(request);
    
    // Reuse same DO for this user
    const id = env.USER_SESSION.idFromName(userId);
    const stub = env.USER_SESSION.get(id);
    
    return stub.fetch(request);
  }
}

Fixing Missing Hibernation

// ❌ High: DO never hibernates (wastes resources)
export class ChatRoom {
  constructor(state, env) {
    this.state = state;
    this.env = env;
    this.messages = [];
  }
  
  async fetch(request) {
    // Handle chat messages...
    // But never hibernates - stays in memory forever!
  }
}

// ✅ Correct: Implement hibernation
export class ChatRoom {
  constructor(state, env) {
    this.state = state;
    this.env = env;
    
    // Load persisted state
    this.loadState();
    
    // Set alarm for hibernation after inactivity
    this.resetHibernationTimer();
  }
  
  async loadState() {
    const messages = await this.state.storage.get('messages');
    this.messages = messages || [];
  }
  
  resetHibernationTimer() {
    // Reset alarm for 30 seconds from now
    this.state.storage.setAlarm(Date.now() + 30000);
  }
  
  async fetch(request) {
    // Reset timer on activity
    this.resetHibernationTimer();
    
    // Handle chat messages...
    return new Response('Message processed');
  }
  
  async alarm() {
    // Persist state before hibernation
    await this.state.storage.put('messages', this.messages);
    // DO will hibernate after this method returns
  }
}

Fixing Wrong ID Pattern

// ❌ High: Using newUniqueId for named resources
export default {
  async fetch(request: Request, env: Env) {
    const roomId = new URL(request.url).searchParams.get('room');
    
    // Wrong: Creates new DO for same room name
    const id = env.CHAT_ROOM.newUniqueId();
    const stub = env.CHAT_ROOM.get(id);
    
    return stub.fetch(request);
  }
}

// ✅ Correct: Use idFromName for named resources
export default {
  async fetch(request: Request, env: Env) {
    const roomId = new URL(request.url).searchParams.get('room');
    
    // Correct: Same DO for same room name
    const id = env.CHAT_ROOM.idFromName(roomId);
    const stub = env.CHAT_ROOM.get(id);
    
    return stub.fetch(request);
  }
}

Fixing State Persistence

// ❌ Critical: State not persisted (lost on hibernation)
export class Counter {
  constructor(state, env) {
    this.state = state;
    this.count = 0;  // Not persisted!
  }
  
  async fetch(request) {
    if (request.url.endsWith('/increment')) {
      this.count++;  // Lost when DO hibernates!
      return new Response(`Count: ${this.count}`);
    }
  }
}

// ✅ Correct: Persist state to storage
export class Counter {
  constructor(state, env) {
    this.state = state;
  }
  
  async fetch(request) {
    if (request.url.endsWith('/increment')) {
      // Persist to storage
      const currentCount = (await this.state.storage.get('count')) || 0;
      const newCount = currentCount + 1;
      await this.state.storage.put('count', newCount);
      
      return new Response(`Count: ${newCount}`);
    }
    
    if (request.url.endsWith('/get')) {
      const count = await this.state.storage.get('count') || 0;
      return new Response(`Count: ${count}`);
    }
  }
}

Fixing Stateless DO Usage

// ❌ High: Using DO for stateless operation (overkill)
export default {
  async fetch(request: Request, env: Env) {
    // Using DO for simple API call - unnecessary!
    const id = env.API_PROXY.newUniqueId();
    const stub = env.API_PROXY.get(id);
    return stub.fetch(request);
  }
}

// ✅ Correct: Handle stateless operations in Worker
export default {
  async fetch(request: Request, env: Env) {
    // Simple API call - handle directly in Worker
    const response = await fetch('https://api.example.com/data');
    return response;
  }
}

// ✅ Correct: Use DO for actual stateful coordination
export default {
  async fetch(request: Request, env: Env) {
    const ip = request.headers.get('CF-Connecting-IP');
    
    // Rate limiting needs state - perfect for DO
    const id = env.RATE_LIMITER.idFromName(ip);
    const stub = env.RATE_LIMITER.get(id);
    
    return stub.fetch(request);
  }
}

Durable Objects Use Cases

Use Durable Objects When:

  • Strong Consistency required (rate limiting, counters)
  • Stateful Coordination (chat rooms, game sessions)
  • Real-time Features (WebSockets, collaboration)
  • Distributed Locks (coordination between requests)
  • Long-running Operations (background processing)

Don't Use Durable Objects When:

  • Stateless Operations (simple API calls)
  • Read-heavy Caching (use KV instead)
  • Large File Storage (use R2 instead)
  • Simple Key-Value (use KV instead)

MCP Server Integration

When Cloudflare MCP server is available:

  • Query DO performance metrics and best practices
  • Get latest hibernation patterns and techniques
  • Check DO usage limits and quotas
  • Analyze DO performance in production

Benefits

Immediate Impact

  • Prevents Resource Waste: Catches DO anti-patterns that waste resources
  • Ensures Correctness: Validates state persistence and consistency
  • Improves Performance: Identifies performance issues in DO usage

Long-term Value

  • Consistent DO Patterns: Ensures all DO usage follows best practices
  • Better Resource Management: Proper hibernation and lifecycle management
  • Reduced Costs: Efficient DO usage reduces resource consumption

Usage Examples

During DO Creation

// Developer types: const id = env.MY_DO.newUniqueId();
// SKILL immediately activates: "⚠ HIGH: Using newUniqueId for every request. Consider idFromName for named resources or if this should be stateless."

During State Management

// Developer types: this.count = 0; in constructor
// SKILL immediately activates: "❌ CRITICAL: State not persisted to storage. Use this.state.storage.put() to persist data."

During Hibernation

// Developer types: DO without alarm() method
// SKILL immediately activates: "⚠ HIGH: Durable Object missing hibernation. Add alarm() method and setAlarm() for resource efficiency."

Performance Targets

DO Creation

  • Excellent: Reuse existing DOs (idFromName)
  • Good: Minimal new DO creation
  • Acceptable: Appropriate DO usage patterns
  • Needs Improvement: Creating new DOs per request

State Persistence

  • Excellent: All state persisted to storage
  • Good: Critical state persisted
  • Acceptable: Basic state management
  • Needs Improvement: State not persisted

Hibernation

  • Excellent: Proper hibernation implementation
  • Good: Basic hibernation setup
  • Acceptable: Some hibernation consideration
  • Needs Improvement: No hibernation (resource waste)

This SKILL ensures Durable Objects are used correctly by providing immediate, autonomous validation of DO patterns, preventing common mistakes and ensuring efficient state management.