diff options
Diffstat (limited to 'apps/web/src/services/storage/migrations/runner.ts')
| -rw-r--r-- | apps/web/src/services/storage/migrations/runner.ts | 310 |
1 files changed, 155 insertions, 155 deletions
diff --git a/apps/web/src/services/storage/migrations/runner.ts b/apps/web/src/services/storage/migrations/runner.ts index e1c2d09a..409a8695 100644 --- a/apps/web/src/services/storage/migrations/runner.ts +++ b/apps/web/src/services/storage/migrations/runner.ts @@ -1,155 +1,155 @@ -import { - IndexedDBAdapter, - deleteDatabase, -} from "@/services/storage/indexeddb-adapter"; -import type { StorageMigration } from "./base"; -import type { ProjectRecord } from "./transformers/types"; -import { getProjectId, isRecord } from "./transformers/utils"; - -export interface StorageMigrationResult { - migratedCount: number; -} - -export interface MigrationProgress { - isMigrating: boolean; - fromVersion: number | null; - toVersion: number | null; - projectName: string | null; -} - -let hasCleanedUpMetaDb = false; - -const MIN_MIGRATION_DISPLAY_MS = 1000; - -export async function runStorageMigrations({ - migrations, - onProgress, -}: { - migrations: StorageMigration[]; - onProgress?: (progress: MigrationProgress) => void; -}): Promise<StorageMigrationResult> { - // One-time cleanup: delete the old global version database - if (!hasCleanedUpMetaDb) { - try { - await deleteDatabase({ dbName: "video-editor-meta" }); - } catch { - // Ignore errors - DB might not exist - } - hasCleanedUpMetaDb = true; - } - - const projectsAdapter = new IndexedDBAdapter<ProjectRecord>( - "video-editor-projects", - "projects", - 1, - ); - const projects = await projectsAdapter.getAll(); - - const orderedMigrations = [...migrations].sort((a, b) => a.from - b.from); - let migratedCount = 0; - let migrationStartTime: number | null = null; - - for (const project of projects) { - if (typeof project !== "object" || project === null) { - continue; - } - - let projectRecord = project as ProjectRecord; - let currentVersion = getProjectVersion({ project: projectRecord }); - const targetVersion = orderedMigrations.at(-1)?.to ?? currentVersion; - - if (currentVersion >= targetVersion) { - continue; - } - - // Track when we first showed the migration dialog - if (migrationStartTime === null) { - migrationStartTime = Date.now(); - } - - const projectName = getProjectName({ project: projectRecord }); - onProgress?.({ - isMigrating: true, - fromVersion: currentVersion, - toVersion: targetVersion, - projectName, - }); - - for (const migration of orderedMigrations) { - if (migration.from !== currentVersion) { - continue; - } - - const result = await migration.transform(projectRecord); - - if (result.skipped) { - break; - } - - const projectId = getProjectId({ project: result.project }); - if (!projectId) { - break; - } - - await projectsAdapter.set(projectId, result.project); - migratedCount++; - currentVersion = migration.to; - projectRecord = result.project; - } - } - - // Ensure dialog is visible for minimum time so users can see it - if (migrationStartTime !== null) { - const elapsed = Date.now() - migrationStartTime; - if (elapsed < MIN_MIGRATION_DISPLAY_MS) { - await new Promise((resolve) => - setTimeout(resolve, MIN_MIGRATION_DISPLAY_MS - elapsed), - ); - } - } - - onProgress?.({ - isMigrating: false, - fromVersion: null, - toVersion: null, - projectName: null, - }); - - return { migratedCount }; -} - -function getProjectVersion({ project }: { project: ProjectRecord }): number { - const versionValue = project.version; - - // v2 and up - has explicit version field - if (typeof versionValue === "number") { - return versionValue; - } - - // v1 - has scenes array - const scenesValue = project.scenes; - if (Array.isArray(scenesValue) && scenesValue.length > 0) { - return 1; - } - - // v0 - no scenes - return 0; -} - -function getProjectName({ - project, -}: { - project: ProjectRecord; -}): string | null { - const metadata = project.metadata; - if (isRecord(metadata) && typeof metadata.name === "string") { - return metadata.name; - } - - // v0 had name directly on project - if (typeof project.name === "string") { - return project.name; - } - - return null; -} +import {
+ IndexedDBAdapter,
+ deleteDatabase,
+} from "@/services/storage/indexeddb-adapter";
+import type { StorageMigration } from "./base";
+import type { ProjectRecord } from "./transformers/types";
+import { getProjectId, isRecord } from "./transformers/utils";
+
+export interface StorageMigrationResult {
+ migratedCount: number;
+}
+
+export interface MigrationProgress {
+ isMigrating: boolean;
+ fromVersion: number | null;
+ toVersion: number | null;
+ projectName: string | null;
+}
+
+let hasCleanedUpMetaDb = false;
+
+const MIN_MIGRATION_DISPLAY_MS = 1000;
+
+export async function runStorageMigrations({
+ migrations,
+ onProgress,
+}: {
+ migrations: StorageMigration[];
+ onProgress?: (progress: MigrationProgress) => void;
+}): Promise<StorageMigrationResult> {
+ // One-time cleanup: delete the old global version database
+ if (!hasCleanedUpMetaDb) {
+ try {
+ await deleteDatabase({ dbName: "video-editor-meta" });
+ } catch {
+ // Ignore errors - DB might not exist
+ }
+ hasCleanedUpMetaDb = true;
+ }
+
+ const projectsAdapter = new IndexedDBAdapter<ProjectRecord>(
+ "video-editor-projects",
+ "projects",
+ 1,
+ );
+ const projects = await projectsAdapter.getAll();
+
+ const orderedMigrations = [...migrations].sort((a, b) => a.from - b.from);
+ let migratedCount = 0;
+ let migrationStartTime: number | null = null;
+
+ for (const project of projects) {
+ if (typeof project !== "object" || project === null) {
+ continue;
+ }
+
+ let projectRecord = project as ProjectRecord;
+ let currentVersion = getProjectVersion({ project: projectRecord });
+ const targetVersion = orderedMigrations.at(-1)?.to ?? currentVersion;
+
+ if (currentVersion >= targetVersion) {
+ continue;
+ }
+
+ // Track when we first showed the migration dialog
+ if (migrationStartTime === null) {
+ migrationStartTime = Date.now();
+ }
+
+ const projectName = getProjectName({ project: projectRecord });
+ onProgress?.({
+ isMigrating: true,
+ fromVersion: currentVersion,
+ toVersion: targetVersion,
+ projectName,
+ });
+
+ for (const migration of orderedMigrations) {
+ if (migration.from !== currentVersion) {
+ continue;
+ }
+
+ const result = await migration.transform(projectRecord);
+
+ if (result.skipped) {
+ break;
+ }
+
+ const projectId = getProjectId({ project: result.project });
+ if (!projectId) {
+ break;
+ }
+
+ await projectsAdapter.set(projectId, result.project);
+ migratedCount++;
+ currentVersion = migration.to;
+ projectRecord = result.project;
+ }
+ }
+
+ // Ensure dialog is visible for minimum time so users can see it
+ if (migrationStartTime !== null) {
+ const elapsed = Date.now() - migrationStartTime;
+ if (elapsed < MIN_MIGRATION_DISPLAY_MS) {
+ await new Promise((resolve) =>
+ setTimeout(resolve, MIN_MIGRATION_DISPLAY_MS - elapsed),
+ );
+ }
+ }
+
+ onProgress?.({
+ isMigrating: false,
+ fromVersion: null,
+ toVersion: null,
+ projectName: null,
+ });
+
+ return { migratedCount };
+}
+
+function getProjectVersion({ project }: { project: ProjectRecord }): number {
+ const versionValue = project.version;
+
+ // v2 and up - has explicit version field
+ if (typeof versionValue === "number") {
+ return versionValue;
+ }
+
+ // v1 - has scenes array
+ const scenesValue = project.scenes;
+ if (Array.isArray(scenesValue) && scenesValue.length > 0) {
+ return 1;
+ }
+
+ // v0 - no scenes
+ return 0;
+}
+
+function getProjectName({
+ project,
+}: {
+ project: ProjectRecord;
+}): string | null {
+ const metadata = project.metadata;
+ if (isRecord(metadata) && typeof metadata.name === "string") {
+ return metadata.name;
+ }
+
+ // v0 had name directly on project
+ if (typeof project.name === "string") {
+ return project.name;
+ }
+
+ return null;
+}
|
