|
| 1 | +--- |
| 2 | +description: Enforce storage migration scripts when project save format changes |
| 3 | +alwaysApply: true |
| 4 | +--- |
| 5 | + |
| 6 | +# Storage Migration |
| 7 | + |
| 8 | +When a code change modifies the persisted project data shape (fields in `TProject`, `TScene`, `TProjectMetadata`, `TProjectSettings`, `TimelineTrack`, or `TimelineElement` types), you **must** also implement a storage migration so existing saved projects upgrade automatically. |
| 9 | + |
| 10 | +## When a Migration is Required |
| 11 | + |
| 12 | +- Adding, removing, or renaming a field on any persisted type |
| 13 | +- Changing the semantics or default of an existing persisted field |
| 14 | +- Restructuring nested data (e.g. moving a field from root to `metadata`) |
| 15 | + |
| 16 | +## Checklist |
| 17 | + |
| 18 | +1. **Bump version** — increment `CURRENT_PROJECT_VERSION` in `services/storage/migrations/index.ts` |
| 19 | +2. **Transformer** — create `transformers/vN-to-vM.ts` with pure transform logic |
| 20 | +3. **Migration class** — create `vN-to-vM.ts` extending `StorageMigration` (`from = N`, `to = M`) |
| 21 | +4. **Register** — add the new migration instance to the `migrations` array in `index.ts` |
| 22 | +5. **Tests** — add `__tests__/vN-to-vM.test.ts` with fixture data covering normal, edge-case, and already-migrated scenarios |
| 23 | + |
| 24 | +## File Structure |
| 25 | + |
| 26 | +``` |
| 27 | +services/storage/migrations/ |
| 28 | +├── index.ts # CURRENT_PROJECT_VERSION + migrations array |
| 29 | +├── base.ts # StorageMigration abstract class |
| 30 | +├── runner.ts # Migration executor |
| 31 | +├── vN-to-vM.ts # Migration class |
| 32 | +├── transformers/ |
| 33 | +│ ├── vN-to-vM.ts # Pure transform function |
| 34 | +│ ├── types.ts # ProjectRecord, MigrationResult |
| 35 | +│ └── utils.ts # getProjectId, isRecord, etc. |
| 36 | +└── __tests__/ |
| 37 | + ├── vN-to-vM.test.ts |
| 38 | + └── fixtures/ |
| 39 | + └── vN.ts # Fixture data for version N |
| 40 | +``` |
| 41 | + |
| 42 | +## Migration Pattern |
| 43 | + |
| 44 | +```typescript |
| 45 | +// transformers/vN-to-vM.ts |
| 46 | +export function transformProjectVNToVM({ |
| 47 | + project, |
| 48 | +}: { |
| 49 | + project: ProjectRecord; |
| 50 | +}): MigrationResult<ProjectRecord> { |
| 51 | + const projectId = getProjectId({ project }); |
| 52 | + if (!projectId) { |
| 53 | + return { project, skipped: true, reason: "no project id" }; |
| 54 | + } |
| 55 | + |
| 56 | + if (isAlreadyMigrated({ project })) { |
| 57 | + return { project, skipped: true, reason: "already vM" }; |
| 58 | + } |
| 59 | + |
| 60 | + const migratedProject = { |
| 61 | + ...project, |
| 62 | + /* apply changes */ |
| 63 | + version: M, |
| 64 | + }; |
| 65 | + |
| 66 | + return { project: migratedProject, skipped: false }; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +## Key Rules |
| 71 | + |
| 72 | +- Transformers must be **pure functions** — no side-effects, no DB access |
| 73 | +- Always guard with an `isAlreadyMigrated` check so re-runs are safe |
| 74 | +- Never delete data without first copying it to the new location |
| 75 | +- Keep each migration small and single-purpose |
0 commit comments