Initial commit
This commit is contained in:
378
skills/durable-objects-pattern-checker/SKILL.md
Normal file
378
skills/durable-objects-pattern-checker/SKILL.md
Normal file
@@ -0,0 +1,378 @@
|
||||
---
|
||||
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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// ❌ 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
|
||||
```typescript
|
||||
// ❌ 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
|
||||
```typescript
|
||||
// ❌ 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
|
||||
```typescript
|
||||
// ❌ 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
|
||||
```typescript
|
||||
// ❌ 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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.
|
||||
Reference in New Issue
Block a user