Files
gh-jezweb-claude-skills-ski…/references/migrations-guide.md
2025-11-30 08:24:13 +08:00

385 lines
7.3 KiB
Markdown

# Durable Objects Migrations Guide
Complete guide to managing DO class lifecycles with migrations.
---
## Why Migrations?
Migrations tell Cloudflare Workers runtime about changes to Durable Object classes:
**Required for:**
- ✅ Creating new DO class
- ✅ Renaming DO class
- ✅ Deleting DO class
- ✅ Transferring DO class to another Worker
**NOT required for:**
- ❌ Code changes to existing DO class
- ❌ Storage schema changes within DO
---
## Migration Types
### 1. Create New DO Class
```jsonc
{
"durable_objects": {
"bindings": [
{
"name": "COUNTER",
"class_name": "Counter"
}
]
},
"migrations": [
{
"tag": "v1", // Unique migration identifier
"new_sqlite_classes": [ // SQLite backend (recommended)
"Counter"
]
}
]
}
```
**For KV backend (legacy):**
```jsonc
{
"migrations": [
{
"tag": "v1",
"new_classes": ["Counter"] // KV backend (128MB limit)
}
]
}
```
**CRITICAL:**
- ✅ Use `new_sqlite_classes` for new DOs (1GB storage, atomic operations)
-**Cannot** change KV backend to SQLite after deployment
---
### 2. Rename DO Class
```jsonc
{
"durable_objects": {
"bindings": [
{
"name": "MY_DO",
"class_name": "NewClassName" // Updated class name
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["OldClassName"]
},
{
"tag": "v2", // New migration tag
"renamed_classes": [
{
"from": "OldClassName",
"to": "NewClassName"
}
]
}
]
}
```
**What happens:**
- ✅ All existing DO instances keep their data
- ✅ Old bindings automatically forward to new class
-`idFromName('foo')` still routes to same instance
- ⚠️ **Must export new class** in Worker code
---
### 3. Delete DO Class
```jsonc
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter"]
},
{
"tag": "v2",
"deleted_classes": ["Counter"] // Mark for deletion
}
]
}
```
**What happens:**
- ✅ All DO instances **immediately deleted**
- ✅ All storage **permanently deleted**
- ⚠️ **CANNOT UNDO** - data is gone forever
**Before deleting:**
1. Export data if needed
2. Update Workers that reference this DO
3. Consider rename instead (if migrating)
---
### 4. Transfer DO Class to Another Worker
**Destination Worker:**
```jsonc
{
"durable_objects": {
"bindings": [
{
"name": "TRANSFERRED_DO",
"class_name": "TransferredClass"
}
]
},
"migrations": [
{
"tag": "v1",
"transferred_classes": [
{
"from": "OriginalClass",
"from_script": "original-worker", // Source Worker name
"to": "TransferredClass"
}
]
}
]
}
```
**What happens:**
- ✅ DO instances move to new Worker
- ✅ All storage is transferred
- ✅ Old bindings automatically forward to new Worker
- ⚠️ **Must export new class** in destination Worker
---
## Migration Rules
### Tags Must Be Unique
```jsonc
// ✅ CORRECT
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v2", "new_sqlite_classes": ["B"] },
{ "tag": "v3", "renamed_classes": [{ "from": "A", "to": "C" }] }
]
}
// ❌ WRONG: Duplicate tag
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v1", "new_sqlite_classes": ["B"] } // ERROR
]
}
```
### Tags Are Append-Only
```jsonc
// ✅ CORRECT: Add new tag
{
"migrations": [
{ "tag": "v1", ... },
{ "tag": "v2", ... } // Append
]
}
// ❌ WRONG: Remove or reorder
{
"migrations": [
{ "tag": "v2", ... } // Can't remove v1
]
}
```
### Migrations Are Atomic
⚠️ **Cannot use gradual deployments** with migrations
- All DO instances migrate at once when you deploy
- No partial rollout support
- Use canary releases at Worker level, not DO level
---
## Migration Gotchas
### Global Uniqueness
DO class names are **globally unique per account**.
```typescript
// Worker A
export class Counter extends DurableObject { }
// Worker B
export class Counter extends DurableObject { }
// ❌ ERROR: Class name "Counter" already exists in account
```
**Solution:** Use unique class names (e.g., prefix with Worker name)
```typescript
// Worker A
export class CounterA extends DurableObject { }
// Worker B
export class CounterB extends DurableObject { }
```
### Cannot Enable SQLite on Existing KV-backed DO
```jsonc
// Deployed with:
{ "tag": "v1", "new_classes": ["Counter"] } // KV backend
// ❌ WRONG: Cannot change to SQLite
{ "tag": "v2", "renamed_classes": [{ "from": "Counter", "to": "CounterSQLite" }] }
{ "tag": "v3", "new_sqlite_classes": ["CounterSQLite"] }
// ✅ CORRECT: Create new class instead
{ "tag": "v2", "new_sqlite_classes": ["CounterV2"] }
// Then migrate data from Counter to CounterV2
```
### Code Changes Don't Need Migrations
```typescript
// ✅ CORRECT: Just deploy code changes
export class Counter extends DurableObject {
async increment(): Promise<number> {
// Changed implementation
let value = await this.ctx.storage.get<number>('count') || 0;
value += 2; // Changed from += 1
await this.ctx.storage.put('count', value);
return value;
}
}
// No migration needed - deploy directly
```
Only schema changes (new/rename/delete/transfer) need migrations.
---
## Environment-Specific Migrations
You can define migrations per environment:
```jsonc
{
// Top-level (default) migrations
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] }
],
"env": {
"production": {
// Production-specific migrations override top-level
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] },
{ "tag": "v2", "new_sqlite_classes": ["Analytics"] }
]
}
}
}
```
**Rules:**
- If migration defined at environment level, it overrides top-level
- If NOT defined at environment level, inherits top-level
---
## Migration Workflow
### Example: Rename DO Class
**Step 1:** Current state (v1)
```jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "OldName" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["OldName"] }
]
}
```
**Step 2:** Update wrangler.jsonc
```jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "NewName" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["OldName"] },
{ "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]
}
```
**Step 3:** Update Worker code
```typescript
// Rename class
export class NewName extends DurableObject { }
export default NewName;
```
**Step 4:** Deploy
```bash
npx wrangler deploy
```
Migration applies atomically on deploy.
---
## Troubleshooting
### Error: "Migration tag already exists"
**Cause:** Trying to reuse a migration tag
**Solution:** Use a new, unique tag
### Error: "Class not found"
**Cause:** Class not exported from Worker
**Solution:** Ensure `export default MyDOClass;`
### Error: "Cannot enable SQLite on existing class"
**Cause:** Trying to migrate KV-backed DO to SQLite
**Solution:** Create new SQLite-backed class, migrate data manually
---
**Official Docs**: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/