summaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts172
-rw-r--r--apps/web/src/services/storage/migrations/base.ts25
-rw-r--r--apps/web/src/services/storage/migrations/runner.ts315
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts313
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts136
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts218
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts3
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts10
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts45
-rw-r--r--apps/web/src/services/storage/migrations/v0-to-v1.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v1-to-v2.ts269
-rw-r--r--apps/web/src/services/storage/migrations/v10-to-v11.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v11-to-v12.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v12-to-v13.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v13-to-v14.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v14-to-v15.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v15-to-v16.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v16-to-v17.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v17-to-v18.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v18-to-v19.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v19-to-v20.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v2-to-v3.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v20-to-v21.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v21-to-v22.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v22-to-v23.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v23-to-v24.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v24-to-v25.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v25-to-v26.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v26-to-v27.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v3-to-v4.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v4-to-v5.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v5-to-v6.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v6-to-v7.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v7-to-v8.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v8-to-v9.ts12
-rw-r--r--apps/web/src/services/storage/migrations/v9-to-v10.ts12
36 files changed, 990 insertions, 828 deletions
diff --git a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts
index 6d0c37fb..0840ac76 100644
--- a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts
+++ b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts
@@ -1,10 +1,9 @@
import { describe, expect, test } from "bun:test";
-import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur";
-import { DEFAULT_BACKGROUND_COLOR } from "@/background/color";
-import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes";
-const DEFAULT_FPS = 30;
-import type { MediaAssetData } from "@/services/storage/types";
-import { getProjectId, transformProjectV1ToV2 } from "../transformers/v1-to-v2";
+import {
+ getProjectId,
+ transformProjectV1ToV2,
+ type V1ToV2Context,
+} from "../transformers/v1-to-v2";
import {
projectWithNoId,
projectWithNullValues,
@@ -13,10 +12,15 @@ import {
v2Project,
} from "./fixtures";
+const DEFAULT_FPS = 30;
+const DEFAULT_BACKGROUND_BLUR_INTENSITY = 10;
+const DEFAULT_BACKGROUND_COLOR = "#000000";
+const DEFAULT_CANVAS_SIZE = { width: 1920, height: 1080 };
+
describe("V1 to V2 Migration", () => {
describe("transformProjectV1ToV2", () => {
- test("creates metadata object from flat properties", async () => {
- const result = await transformProjectV1ToV2({ project: v1Project });
+ test("creates metadata object from flat properties", () => {
+ const result = transformProjectV1ToV2({ project: v1Project });
expect(result.skipped).toBe(false);
expect(result.project.version).toBe(2);
@@ -28,8 +32,8 @@ describe("V1 to V2 Migration", () => {
expect(typeof metadata.updatedAt).toBe("string");
});
- test("creates settings object from flat properties", async () => {
- const result = await transformProjectV1ToV2({ project: v1Project });
+ test("creates settings object from flat properties", () => {
+ const result = transformProjectV1ToV2({ project: v1Project });
const settings = result.project.settings as Record<string, unknown>;
expect(settings.fps).toBe(v1Project.fps);
@@ -37,8 +41,8 @@ describe("V1 to V2 Migration", () => {
expect(settings.originalCanvasSize).toBe(null);
});
- test("converts color background correctly", async () => {
- const result = await transformProjectV1ToV2({ project: v1Project });
+ test("converts color background correctly", () => {
+ const result = transformProjectV1ToV2({ project: v1Project });
const settings = result.project.settings as Record<string, unknown>;
const background = settings.background as Record<string, unknown>;
@@ -46,13 +50,13 @@ describe("V1 to V2 Migration", () => {
expect(background.color).toBe(v1Project.backgroundColor);
});
- test("converts blur background correctly", async () => {
+ test("converts blur background correctly", () => {
const projectWithBlur = {
...v1Project,
backgroundType: "blur",
blurIntensity: 30,
};
- const result = await transformProjectV1ToV2({ project: projectWithBlur });
+ const result = transformProjectV1ToV2({ project: projectWithBlur });
const settings = result.project.settings as Record<string, unknown>;
const background = settings.background as Record<string, unknown>;
@@ -60,16 +64,16 @@ describe("V1 to V2 Migration", () => {
expect(background.blurIntensity).toBe(30);
});
- test("applies legacy bookmarks to main scene", async () => {
- const result = await transformProjectV1ToV2({ project: v1Project });
+ test("applies legacy bookmarks to main scene", () => {
+ const result = transformProjectV1ToV2({ project: v1Project });
const scenes = result.project.scenes as Array<Record<string, unknown>>;
const mainScene = scenes.find((s) => s.isMain === true);
expect(mainScene?.bookmarks).toEqual(v1Project.bookmarks);
});
- test("preserves existing scene bookmarks", async () => {
- const result = await transformProjectV1ToV2({
+ test("preserves existing scene bookmarks", () => {
+ const result = transformProjectV1ToV2({
project: v1ProjectWithMultipleScenes,
});
@@ -78,22 +82,22 @@ describe("V1 to V2 Migration", () => {
expect(introScene?.bookmarks).toEqual([1.0]);
});
- test("skips project that already has v2 structure", async () => {
- const result = await transformProjectV1ToV2({ project: v2Project });
+ test("skips project that already has v2 structure", () => {
+ const result = transformProjectV1ToV2({ project: v2Project });
expect(result.skipped).toBe(true);
expect(result.reason).toBe("already v2");
});
- test("skips project with no id", async () => {
- const result = await transformProjectV1ToV2({ project: projectWithNoId });
+ test("skips project with no id", () => {
+ const result = transformProjectV1ToV2({ project: projectWithNoId });
expect(result.skipped).toBe(true);
expect(result.reason).toBe("no project id");
});
- test("handles null values gracefully", async () => {
- const result = await transformProjectV1ToV2({
+ test("handles null values gracefully", () => {
+ const result = transformProjectV1ToV2({
project: projectWithNullValues,
});
@@ -103,13 +107,13 @@ describe("V1 to V2 Migration", () => {
expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE);
});
- test("uses default values for missing properties", async () => {
+ test("uses default values for missing properties", () => {
const minimalProject = {
id: "minimal",
version: 1,
scenes: [],
};
- const result = await transformProjectV1ToV2({ project: minimalProject });
+ const result = transformProjectV1ToV2({ project: minimalProject });
const settings = result.project.settings as Record<string, unknown>;
expect(settings.fps).toBe(DEFAULT_FPS);
@@ -120,14 +124,14 @@ describe("V1 to V2 Migration", () => {
expect(background.color).toBe(DEFAULT_BACKGROUND_COLOR);
});
- test("uses default blur intensity when missing", async () => {
+ test("uses default blur intensity when missing", () => {
const projectWithBlurNoIntensity = {
id: "blur-no-intensity",
version: 1,
backgroundType: "blur",
scenes: [],
};
- const result = await transformProjectV1ToV2({
+ const result = transformProjectV1ToV2({
project: projectWithBlurNoIntensity,
});
@@ -136,23 +140,23 @@ describe("V1 to V2 Migration", () => {
expect(background.blurIntensity).toBe(DEFAULT_BACKGROUND_BLUR_INTENSITY);
});
- test("preserves currentSceneId", async () => {
- const result = await transformProjectV1ToV2({ project: v1Project });
+ test("preserves currentSceneId", () => {
+ const result = transformProjectV1ToV2({ project: v1Project });
expect(result.project.currentSceneId).toBe(v1Project.currentSceneId);
});
- test("finds main scene id when currentSceneId missing", async () => {
+ test("finds main scene id when currentSceneId missing", () => {
const projectWithoutCurrentScene = {
...v1Project,
currentSceneId: undefined,
};
- const result = await transformProjectV1ToV2({
+ const result = transformProjectV1ToV2({
project: projectWithoutCurrentScene,
});
expect(result.project.currentSceneId).toBe("scene-main");
});
- test("skips loading tracks if scene already has tracks", async () => {
+ test("skips loading tracks if scene already has tracks", () => {
const projectWithTracks = {
...v1Project,
scenes: [
@@ -175,7 +179,7 @@ describe("V1 to V2 Migration", () => {
],
};
- const result = await transformProjectV1ToV2({
+ const result = transformProjectV1ToV2({
project: projectWithTracks,
});
@@ -188,22 +192,32 @@ describe("V1 to V2 Migration", () => {
});
describe("Track Loading and Transformation", () => {
- test("loads tracks from legacy DB and transforms media track to video track", async () => {
- const mockLoadMediaAsset = async ({
- mediaId,
- }: {
- mediaId: string;
- }): Promise<MediaAssetData | null> => {
- if (mediaId === "media-1") {
- return {
- id: "media-1",
- name: "Test Video",
- type: "video",
- size: 1000,
- lastModified: Date.now(),
- };
- }
- return null;
+ test("loads tracks from legacy DB and transforms media track to video track", () => {
+ const context: V1ToV2Context = {
+ legacyTracksBySceneId: {
+ "scene-main": [
+ {
+ id: "legacy-track-1",
+ type: "media",
+ name: "Legacy media track",
+ elements: [
+ {
+ id: "media-element-1",
+ name: "Test video clip",
+ type: "media",
+ mediaId: "media-1",
+ duration: 120,
+ startTime: 0,
+ trimStart: 0,
+ trimEnd: 0,
+ },
+ ],
+ },
+ ],
+ },
+ mediaTypesById: {
+ "media-1": "video",
+ },
};
const projectWithLegacyTracks = {
@@ -221,19 +235,50 @@ describe("V1 to V2 Migration", () => {
],
};
- // mock IndexedDB for this test would require setting up a test environment
- // for now, we test that the transformer handles empty tracks gracefully
- const result = await transformProjectV1ToV2({
+ const result = transformProjectV1ToV2({
project: projectWithLegacyTracks,
- options: { loadMediaAsset: mockLoadMediaAsset },
+ context,
});
const scenes = result.project.scenes as Array<Record<string, unknown>>;
const mainScene = scenes[0];
- expect(Array.isArray(mainScene.tracks)).toBe(true);
+ const tracks = mainScene.tracks as Array<Record<string, unknown>>;
+ expect(Array.isArray(tracks)).toBe(true);
+ expect(tracks).toHaveLength(1);
+ expect(tracks[0].type).toBe("video");
+ expect(tracks[0].isMain).toBe(true);
});
- test("transforms text element preserving opacity and migrating position", async () => {
+ test("transforms text element preserving opacity and migrating position", () => {
+ const context: V1ToV2Context = {
+ legacyTracksBySceneId: {
+ "scene-1": [
+ {
+ id: "legacy-text-track",
+ type: "text",
+ name: "Text",
+ elements: [
+ {
+ id: "text-element-1",
+ name: "Title",
+ type: "text",
+ content: "Hello",
+ x: 120,
+ y: 240,
+ rotation: 15,
+ opacity: 0.5,
+ duration: 90,
+ startTime: 10,
+ trimStart: 0,
+ trimEnd: 0,
+ },
+ ],
+ },
+ ],
+ },
+ mediaTypesById: {},
+ };
+
const projectWithTextTrack = {
id: "project-text",
version: 1,
@@ -251,15 +296,22 @@ describe("V1 to V2 Migration", () => {
],
};
- // since tracks are empty, transformation won't happen
- // but we verify the structure is correct
- const result = await transformProjectV1ToV2({
+ const result = transformProjectV1ToV2({
project: projectWithTextTrack,
+ context,
});
expect(result.skipped).toBe(false);
const scenes = result.project.scenes as Array<Record<string, unknown>>;
- expect(scenes.length).toBe(1);
+ const tracks = scenes[0].tracks as Array<Record<string, unknown>>;
+ const elements = tracks[0].elements as Array<Record<string, unknown>>;
+ const textElement = elements[0];
+ expect(textElement.opacity).toBe(0.5);
+ expect(textElement.transform).toEqual({
+ scale: 1,
+ position: { x: 120, y: 240 },
+ rotate: 15,
+ });
});
});
diff --git a/apps/web/src/services/storage/migrations/base.ts b/apps/web/src/services/storage/migrations/base.ts
index 13418f98..0130c95e 100644
--- a/apps/web/src/services/storage/migrations/base.ts
+++ b/apps/web/src/services/storage/migrations/base.ts
@@ -1,9 +1,16 @@
-import type { MigrationResult, ProjectRecord } from "./transformers/types";
-
-export abstract class StorageMigration {
- abstract from: number;
- abstract to: number;
- abstract transform(
- project: ProjectRecord,
- ): Promise<MigrationResult<ProjectRecord>>;
-}
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
+
+export interface StorageMigrationRunArgs {
+ projectId: string;
+ project: ProjectRecord;
+}
+
+export abstract class StorageMigration {
+ abstract from: number;
+ abstract to: number;
+
+ abstract run({
+ projectId,
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>>;
+}
diff --git a/apps/web/src/services/storage/migrations/runner.ts b/apps/web/src/services/storage/migrations/runner.ts
index f63fe983..eed8071f 100644
--- a/apps/web/src/services/storage/migrations/runner.ts
+++ b/apps/web/src/services/storage/migrations/runner.ts
@@ -1,156 +1,159 @@
-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;
+ const projectId = getProjectId({ project: projectRecord });
+ if (!projectId) {
+ continue;
+ }
+
+ 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.run({
+ projectId,
+ project: projectRecord,
+ });
+
+ if (result.skipped) {
+ 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;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
index c4b64e50..065a6b34 100644
--- a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
@@ -1,17 +1,24 @@
-import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur";
-import { DEFAULT_BACKGROUND_COLOR } from "@/background/color";
-import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes";
-const DEFAULT_FPS = 30;
-import { IndexedDBAdapter } from "@/services/storage/indexeddb-adapter";
-import type { MediaAssetData } from "@/services/storage/types";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
-interface LegacyTimelineData {
- tracks: unknown[];
- lastModified: string;
+// Frozen snapshots of v2-era defaults. See ./README.md.
+const DEFAULT_BACKGROUND_BLUR_INTENSITY = 10;
+const DEFAULT_BACKGROUND_COLOR = "#000000";
+const DEFAULT_CANVAS_SIZE = { width: 1920, height: 1080 };
+const DEFAULT_FPS = 30;
+
+type LegacyMediaType = "image" | "video" | "audio";
+
+export interface V1ToV2Context {
+ legacyTracksBySceneId: Record<string, unknown[]>;
+ mediaTypesById: Record<string, LegacyMediaType>;
}
+const EMPTY_V1_TO_V2_CONTEXT: V1ToV2Context = {
+ legacyTracksBySceneId: {},
+ mediaTypesById: {},
+};
+
interface LegacyMediaElement {
type: "media";
mediaId: string;
@@ -146,21 +153,13 @@ interface V2AudioTrack {
type V2TimelineTrack = V2VideoTrack | V2TextTrack | V2AudioTrack;
-export interface TransformV1ToV2Options {
- loadMediaAsset?: ({
- mediaId,
- }: {
- mediaId: string;
- }) => Promise<MediaAssetData | null>;
-}
-
-export async function transformProjectV1ToV2({
+export function transformProjectV1ToV2({
project,
- options = {},
+ context = EMPTY_V1_TO_V2_CONTEXT,
}: {
project: ProjectRecord;
- options?: TransformV1ToV2Options;
-}): Promise<MigrationResult<ProjectRecord>> {
+ context?: V1ToV2Context;
+}): MigrationResult<ProjectRecord> {
const projectId = getProjectId({ project });
if (!projectId) {
return { project, skipped: true, reason: "no project id" };
@@ -170,27 +169,23 @@ export async function transformProjectV1ToV2({
return { project, skipped: true, reason: "already v2" };
}
- const migratedProject = await migrateProject({
+ const migratedProject = migrateProject({
project,
projectId,
- loadMediaAsset: options.loadMediaAsset,
+ context,
});
return { project: migratedProject, skipped: false };
}
-async function migrateProject({
+function migrateProject({
project,
projectId,
- loadMediaAsset,
+ context,
}: {
project: ProjectRecord;
projectId: string;
- loadMediaAsset?: ({
- mediaId,
- }: {
- mediaId: string;
- }) => Promise<MediaAssetData | null>;
-}): Promise<ProjectRecord> {
+ context: V1ToV2Context;
+}): ProjectRecord {
const createdAt = normalizeDateString({ value: project.createdAt });
const updatedAt = normalizeDateString({ value: project.updatedAt });
const metadataValue = project.metadata;
@@ -217,42 +212,35 @@ async function migrateProject({
? project.bookmarks
: null;
- const migratedScenes = await Promise.all(
- scenes.map(async (scene) => {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const sceneId = getStringValue({ value: scene.id });
- if (!sceneId) {
- return scene;
- }
+ const migratedScenes = scenes.map((scene) => {
+ if (!isRecord(scene)) {
+ return scene;
+ }
- const existingTracks = scene.tracks;
- const shouldLoadTracks =
- !Array.isArray(existingTracks) || existingTracks.length === 0;
+ const sceneId = getStringValue({ value: scene.id });
+ if (!sceneId) {
+ return scene;
+ }
- if (!shouldLoadTracks) {
- return scene;
- }
+ const existingTracks = scene.tracks;
+ const shouldLoadTracks =
+ !Array.isArray(existingTracks) || existingTracks.length === 0;
- const tracks = await loadTracksFromLegacyDB({
- projectId,
- sceneId,
- isMain: scene.isMain === true,
- });
+ if (!shouldLoadTracks) {
+ return scene;
+ }
- const transformedTracks = await transformTracks({
- tracks,
- loadMediaAsset,
- });
+ const tracks = context.legacyTracksBySceneId[sceneId] ?? [];
+ const transformedTracks = transformTracks({
+ tracks,
+ context,
+ });
- return {
- ...scene,
- tracks: transformedTracks,
- };
- }),
- );
+ return {
+ ...scene,
+ tracks: transformedTracks,
+ };
+ });
const normalizedScenes = applyLegacyBookmarks({
scenes: migratedScenes,
@@ -305,183 +293,126 @@ async function migrateProject({
};
}
-async function loadTracksFromLegacyDB({
- projectId,
- sceneId,
- isMain,
-}: {
- projectId: string;
- sceneId: string;
- isMain: boolean;
-}): Promise<unknown[]> {
- if (typeof indexedDB === "undefined") {
- return [];
- }
-
- const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
- const projectDbName = `video-editor-timelines-${projectId}`;
-
- const adapter = new IndexedDBAdapter<LegacyTimelineData>(
- sceneDbName,
- "timeline",
- 1,
- );
-
- let data = await adapter.get("timeline");
-
- if (!data && isMain) {
- const projectAdapter = new IndexedDBAdapter<LegacyTimelineData>(
- projectDbName,
- "timeline",
- 1,
- );
- data = await projectAdapter.get("timeline");
- }
-
- if (!data || !Array.isArray(data.tracks)) {
- return [];
- }
-
- return data.tracks;
-}
-
-async function transformTracks({
+function transformTracks({
tracks,
- loadMediaAsset,
+ context,
}: {
tracks: unknown[];
- loadMediaAsset?: ({
- mediaId,
- }: {
- mediaId: string;
- }) => Promise<MediaAssetData | null>;
-}): Promise<V2TimelineTrack[]> {
+ context: V1ToV2Context;
+}): V2TimelineTrack[] {
if (!Array.isArray(tracks)) {
return [];
}
let isFirstVideoTrackFound = false;
- const transformedTracks: (V2TimelineTrack | null)[] = [];
-
- for (const track of tracks) {
+ const transformedTracks = tracks.map((track): V2TimelineTrack | null => {
if (!isRecord(track)) {
- transformedTracks.push(null);
- continue;
+ return null;
}
const trackType = track.type;
if (trackType === "media") {
- const videoTrack = await transformMediaTrack({
+ const isMain = !isFirstVideoTrackFound;
+ isFirstVideoTrackFound = true;
+ const videoTrack = transformMediaTrack({
track: track as LegacyMediaTrack,
- loadMediaAsset,
- isMain: !isFirstVideoTrackFound,
+ context,
+ isMain,
});
- isFirstVideoTrackFound = true;
- transformedTracks.push(videoTrack);
- continue;
+ return videoTrack;
}
if (trackType === "text") {
- transformedTracks.push(transformTextTrack({ track }));
- continue;
+ return transformTextTrack({ track });
}
if (trackType === "audio") {
- transformedTracks.push(transformAudioTrack({ track }));
- continue;
+ return transformAudioTrack({ track });
}
- transformedTracks.push(null);
- }
+ return null;
+ });
return transformedTracks.filter(
(track): track is V2TimelineTrack => track !== null,
);
}
-async function transformMediaTrack({
+function transformMediaTrack({
track,
- loadMediaAsset,
+ context,
isMain,
}: {
track: LegacyMediaTrack;
- loadMediaAsset?: ({
- mediaId,
- }: {
- mediaId: string;
- }) => Promise<MediaAssetData | null>;
+ context: V1ToV2Context;
isMain: boolean;
-}): Promise<V2VideoTrack> {
+}): V2VideoTrack {
const elements = Array.isArray(track.elements) ? track.elements : [];
- const transformedElements = await Promise.all(
- elements.map(async (element) => {
- if (!isRecord(element) || element.type !== "media") {
- return null;
- }
-
- const mediaElement = element as LegacyMediaElement;
- const mediaId = getStringValue({ value: mediaElement.mediaId });
- if (!mediaId) {
- return null;
- }
+ const transformedElements = elements.map((element) => {
+ if (!isRecord(element) || element.type !== "media") {
+ return null;
+ }
- let mediaType: "video" | "image" = "video";
- if (loadMediaAsset) {
- const mediaAsset = await loadMediaAsset({ mediaId });
- if (mediaAsset) {
- mediaType = mediaAsset.type === "image" ? "image" : "video";
- }
- }
+ const mediaElement = element as LegacyMediaElement;
+ const mediaId = getStringValue({ value: mediaElement.mediaId });
+ if (!mediaId) {
+ return null;
+ }
- const defaultTransform: V2Transform = {
- scale: 1,
- position: { x: 0, y: 0 },
- rotate: 0,
- };
+ let mediaType: "video" | "image" = "video";
+ const storedMediaType = context.mediaTypesById[mediaId];
+ if (storedMediaType) {
+ mediaType = storedMediaType === "image" ? "image" : "video";
+ }
- const muted = mediaElement.muted === true;
+ const defaultTransform: V2Transform = {
+ scale: 1,
+ position: { x: 0, y: 0 },
+ rotate: 0,
+ };
- if (mediaType === "image") {
- const imageElement: V2ImageElement = {
- id: getStringValue({ value: element.id, fallback: "" }),
- name: getStringValue({ value: element.name, fallback: "" }),
- type: "image",
- mediaId,
- duration: getNumberValue({ value: element.duration, fallback: 0 }),
- startTime: getNumberValue({
- value: element.startTime,
- fallback: 0,
- }),
- trimStart: getNumberValue({
- value: element.trimStart,
- fallback: 0,
- }),
- trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
- hidden: false,
- transform: defaultTransform,
- opacity: 1,
- };
- return imageElement;
- }
+ const muted = mediaElement.muted === true;
- const videoElement: V2VideoElement = {
+ if (mediaType === "image") {
+ const imageElement: V2ImageElement = {
id: getStringValue({ value: element.id, fallback: "" }),
name: getStringValue({ value: element.name, fallback: "" }),
- type: "video",
+ type: "image",
mediaId,
- muted,
+ duration: getNumberValue({ value: element.duration, fallback: 0 }),
+ startTime: getNumberValue({
+ value: element.startTime,
+ fallback: 0,
+ }),
+ trimStart: getNumberValue({
+ value: element.trimStart,
+ fallback: 0,
+ }),
+ trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
hidden: false,
transform: defaultTransform,
opacity: 1,
- duration: getNumberValue({ value: element.duration, fallback: 0 }),
- startTime: getNumberValue({ value: element.startTime, fallback: 0 }),
- trimStart: getNumberValue({ value: element.trimStart, fallback: 0 }),
- trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
};
- return videoElement;
- }),
- );
+ return imageElement;
+ }
+
+ const videoElement: V2VideoElement = {
+ id: getStringValue({ value: element.id, fallback: "" }),
+ name: getStringValue({ value: element.name, fallback: "" }),
+ type: "video",
+ mediaId,
+ muted,
+ hidden: false,
+ transform: defaultTransform,
+ opacity: 1,
+ duration: getNumberValue({ value: element.duration, fallback: 0 }),
+ startTime: getNumberValue({ value: element.startTime, fallback: 0 }),
+ trimStart: getNumberValue({ value: element.trimStart, fallback: 0 }),
+ trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
+ };
+ return videoElement;
+ });
const validElements = transformedElements.filter(
(element): element is V2VideoElement | V2ImageElement => element !== null,
diff --git a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
index 5f3c01e9..84b2704a 100644
--- a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
@@ -1,67 +1,69 @@
-import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size";
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV14ToV15({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 15) {
- return { project, skipped: true, reason: "already v15" };
- }
-
- const migratedProject = backfillStickerIntrinsicDimensions({ project });
-
- return {
- project: { ...migratedProject, version: 15 },
- skipped: false,
- };
-}
-
-function backfillStickerIntrinsicDimensions({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene)) return scene;
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) => {
- if (!isRecord(track)) return track;
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) => {
- if (!isRecord(element)) return element;
- if (element.type !== "sticker") return element;
- if (
- typeof element.intrinsicWidth === "number" &&
- typeof element.intrinsicHeight === "number"
- ) {
- return element;
- }
- return {
- ...element,
- intrinsicWidth: STICKER_INTRINSIC_SIZE_FALLBACK,
- intrinsicHeight: STICKER_INTRINSIC_SIZE_FALLBACK,
- };
- });
-
- return { ...track, elements: migratedElements };
- });
-
- return { ...scene, tracks: migratedTracks };
- });
-
- return { ...project, scenes: migratedScenes };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+// Frozen snapshot of the v15-era fallback. See ./README.md.
+const STICKER_INTRINSIC_SIZE_FALLBACK = 200;
+
+export function transformProjectV14ToV15({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 15) {
+ return { project, skipped: true, reason: "already v15" };
+ }
+
+ const migratedProject = backfillStickerIntrinsicDimensions({ project });
+
+ return {
+ project: { ...migratedProject, version: 15 },
+ skipped: false,
+ };
+}
+
+function backfillStickerIntrinsicDimensions({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene)) return scene;
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) => {
+ if (!isRecord(track)) return track;
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) => {
+ if (!isRecord(element)) return element;
+ if (element.type !== "sticker") return element;
+ if (
+ typeof element.intrinsicWidth === "number" &&
+ typeof element.intrinsicHeight === "number"
+ ) {
+ return element;
+ }
+ return {
+ ...element,
+ intrinsicWidth: STICKER_INTRINSIC_SIZE_FALLBACK,
+ intrinsicHeight: STICKER_INTRINSIC_SIZE_FALLBACK,
+ };
+ });
+
+ return { ...track, elements: migratedElements };
+ });
+
+ return { ...scene, tracks: migratedTracks };
+ });
+
+ return { ...project, scenes: migratedScenes };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
index 3ef6cde9..660d91ee 100644
--- a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
@@ -1,105 +1,113 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { VOLUME_DB_MIN } from "@/timeline/audio-constants";
-import { clampDb } from "@/timeline/audio-state";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV17ToV18({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 18) {
- return { project, skipped: true, reason: "already v18" };
- }
-
- return {
- project: {
- ...migrateElementVolumes({ project }),
- version: 18,
- },
- skipped: false,
- };
-}
-
-function migrateElementVolumes({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) {
- return scene;
- }
-
- const migratedTracks = tracksValue.map((track) => {
- if (!isRecord(track)) {
- return track;
- }
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) {
- return track;
- }
-
- const migratedElements = elementsValue.map((element) => {
- if (!isRecord(element)) {
- return element;
- }
-
- if (element.type !== "audio" && element.type !== "video") {
- return element;
- }
-
- const legacyVolume = element.volume;
- return {
- ...element,
- volume:
- typeof legacyVolume === "number"
- ? linearGainToDb(legacyVolume)
- : 0,
- };
- });
-
- return {
- ...track,
- elements: migratedElements,
- };
- });
-
- return {
- ...scene,
- tracks: migratedTracks,
- };
- });
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
-
-function linearGainToDb(gain: number): number {
- if (!Number.isFinite(gain)) {
- return 0;
- }
-
- if (gain <= 0) {
- return VOLUME_DB_MIN;
- }
-
- return clampDb(20 * Math.log10(gain));
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+// Frozen snapshots of v18-era volume bounds. See ./README.md.
+const VOLUME_DB_MIN = -60;
+const VOLUME_DB_MAX = 20;
+
+export function transformProjectV17ToV18({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 18) {
+ return { project, skipped: true, reason: "already v18" };
+ }
+
+ return {
+ project: {
+ ...migrateElementVolumes({ project }),
+ version: 18,
+ },
+ skipped: false,
+ };
+}
+
+function migrateElementVolumes({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) {
+ return scene;
+ }
+
+ const migratedTracks = tracksValue.map((track) => {
+ if (!isRecord(track)) {
+ return track;
+ }
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) {
+ return track;
+ }
+
+ const migratedElements = elementsValue.map((element) => {
+ if (!isRecord(element)) {
+ return element;
+ }
+
+ if (element.type !== "audio" && element.type !== "video") {
+ return element;
+ }
+
+ const legacyVolume = element.volume;
+ return {
+ ...element,
+ volume:
+ typeof legacyVolume === "number" ? linearGainToDb(legacyVolume) : 0,
+ };
+ });
+
+ return {
+ ...track,
+ elements: migratedElements,
+ };
+ });
+
+ return {
+ ...scene,
+ tracks: migratedTracks,
+ };
+ });
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
+
+function linearGainToDb(gain: number): number {
+ if (!Number.isFinite(gain)) {
+ return 0;
+ }
+
+ if (gain <= 0) {
+ return VOLUME_DB_MIN;
+ }
+
+ return clampDb(20 * Math.log10(gain));
+}
+
+function clampDb(value: number): number {
+ if (!Number.isFinite(value)) {
+ return 0;
+ }
+
+ return Math.min(VOLUME_DB_MAX, Math.max(VOLUME_DB_MIN, value));
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts
index 6c30e5c8..14b7a092 100644
--- a/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts
@@ -1,7 +1,8 @@
-import { INTENSITY_TO_SIGMA_DIVISOR } from "@/effects/definitions/blur";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
+// Frozen snapshot of the v21-era divisor. See ./README.md.
+const INTENSITY_TO_SIGMA_DIVISOR = 5;
const LEGACY_DEFAULT_BACKGROUND_BLUR_INTENSITY = 50;
export function transformProjectV20ToV21({
diff --git a/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts b/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts
index 5e3adc33..24cc86e2 100644
--- a/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v22-to-v23.ts
@@ -1,7 +1,8 @@
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
-import { TICKS_PER_SECOND } from "@/wasm";
+// Frozen snapshot of the v23-era tick rate. See ./README.md.
+const TICKS_PER_SECOND = 120_000;
const ARBITRARY_FPS_DENOMINATOR = 1_000_000;
const STANDARD_FRAME_RATES = [
{ value: 24_000 / 1_001, numerator: 24_000, denominator: 1_001 },
@@ -193,7 +194,9 @@ function migrateAnimationChannel({ channel }: { channel: unknown }): unknown {
return {
...channel,
- keys: channel.keys.map((keyframe) => migrateAnimationKeyframe({ keyframe })),
+ keys: channel.keys.map((keyframe) =>
+ migrateAnimationKeyframe({ keyframe }),
+ ),
};
}
@@ -290,7 +293,8 @@ function migrateFrameRate({ fps }: { fps: unknown }): unknown {
}
const standardFrameRate = STANDARD_FRAME_RATES.find(
- (candidate) => Math.abs(fps - candidate.value) <= STANDARD_FRAME_RATE_TOLERANCE,
+ (candidate) =>
+ Math.abs(fps - candidate.value) <= STANDARD_FRAME_RATE_TOLERANCE,
);
if (standardFrameRate) {
return {
diff --git a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts
index 820b0087..3b118ee7 100644
--- a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts
@@ -1,7 +1,50 @@
-import { parseCustomMaskPath } from "@/masks/custom-path";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
+interface CustomMaskPathPoint {
+ id: string;
+ x: number;
+ y: number;
+ inX: number;
+ inY: number;
+ outX: number;
+ outY: number;
+}
+
+function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint {
+ if (!value || typeof value !== "object") {
+ return false;
+ }
+
+ const candidate = value as Record<string, unknown>;
+ return (
+ typeof candidate.id === "string" &&
+ typeof candidate.x === "number" &&
+ typeof candidate.y === "number" &&
+ typeof candidate.inX === "number" &&
+ typeof candidate.inY === "number" &&
+ typeof candidate.outX === "number" &&
+ typeof candidate.outY === "number"
+ );
+}
+
+function parseCustomMaskPath({
+ path,
+}: {
+ path: string;
+}): CustomMaskPathPoint[] {
+ if (!path) {
+ return [];
+ }
+
+ try {
+ const parsed = JSON.parse(path);
+ return Array.isArray(parsed) ? parsed.filter(isCustomMaskPathPoint) : [];
+ } catch {
+ return [];
+ }
+}
+
export function transformProjectV26ToV27({
project,
}: {
diff --git a/apps/web/src/services/storage/migrations/v0-to-v1.ts b/apps/web/src/services/storage/migrations/v0-to-v1.ts
index 8868e3f2..bebd0103 100644
--- a/apps/web/src/services/storage/migrations/v0-to-v1.ts
+++ b/apps/web/src/services/storage/migrations/v0-to-v1.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV0ToV1 } from "./transformers/v0-to-v1";
export class V0toV1Migration extends StorageMigration {
from = 0;
to = 1;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV0ToV1({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v1-to-v2.ts b/apps/web/src/services/storage/migrations/v1-to-v2.ts
index ef381541..a87b4a86 100644
--- a/apps/web/src/services/storage/migrations/v1-to-v2.ts
+++ b/apps/web/src/services/storage/migrations/v1-to-v2.ts
@@ -3,97 +3,260 @@ import {
deleteDatabase,
} from "@/services/storage/indexeddb-adapter";
import type { MediaAssetData } from "@/services/storage/types";
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import {
- getProjectId,
transformProjectV1ToV2,
- type TransformV1ToV2Options,
+ type V1ToV2Context,
} from "./transformers/v1-to-v2";
+import { isRecord } from "./transformers/utils";
+
+interface LegacyTimelineData {
+ tracks: unknown[];
+ lastModified: string;
+}
export class V1toV2Migration extends StorageMigration {
from = 1;
to = 2;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- const loadMediaAsset = createMediaAssetLoader({ projectId });
-
- const result = await transformProjectV1ToV2({
- project,
- options: { loadMediaAsset },
- });
+ async run({
+ projectId,
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
+ const context = await loadV1ToV2Context({ projectId, project });
+ const result = transformProjectV1ToV2({ project, context });
if (!result.skipped) {
- void cleanupLegacyTimelineDBs({
- projectId,
- project: result.project,
- });
+ try {
+ await deleteLegacyTimelineDbs({ projectId, project: result.project });
+ } catch (error) {
+ console.error(
+ `V1→V2 migration cleanup failed for project ${projectId}:`,
+ error,
+ );
+ }
}
return result;
}
}
-function createMediaAssetLoader({
+async function loadV1ToV2Context({
projectId,
+ project,
}: {
projectId: string;
-}): TransformV1ToV2Options["loadMediaAsset"] {
- return async ({ mediaId }: { mediaId: string }) => {
- const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(
- `video-editor-media-${projectId}`,
- "media-metadata",
- 1,
- );
+ project: ProjectRecord;
+}): Promise<V1ToV2Context> {
+ const legacyTracksBySceneId = await loadLegacyTracksBySceneId({
+ projectId,
+ project,
+ });
+ const mediaTypesById = await loadMediaTypesById({
+ projectId,
+ legacyTracksBySceneId,
+ });
- return mediaMetadataAdapter.get(mediaId);
+ return {
+ legacyTracksBySceneId,
+ mediaTypesById,
};
}
-function cleanupLegacyTimelineDBs({
+async function loadLegacyTracksBySceneId({
projectId,
project,
}: {
projectId: string;
project: ProjectRecord;
-}): void {
+}): Promise<V1ToV2Context["legacyTracksBySceneId"]> {
const scenes = project.scenes;
if (!Array.isArray(scenes)) {
- return;
+ return {};
+ }
+
+ const sceneEntries = await Promise.all(
+ scenes.map(async (scene) => {
+ if (!isRecord(scene)) {
+ return null;
+ }
+
+ const sceneId = scene.id;
+ if (typeof sceneId !== "string") {
+ return null;
+ }
+
+ const tracks = await loadLegacyTracksForScene({
+ projectId,
+ sceneId,
+ isMain: scene.isMain === true,
+ });
+
+ return [sceneId, tracks] as const;
+ }),
+ );
+
+ return Object.fromEntries(
+ sceneEntries.filter(
+ (
+ sceneEntry,
+ ): sceneEntry is readonly [
+ string,
+ V1ToV2Context["legacyTracksBySceneId"][string],
+ ] => sceneEntry !== null,
+ ),
+ );
+}
+
+async function loadLegacyTracksForScene({
+ projectId,
+ sceneId,
+ isMain,
+}: {
+ projectId: string;
+ sceneId: string;
+ isMain: boolean;
+}): Promise<unknown[]> {
+ const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
+ const projectDbName = `video-editor-timelines-${projectId}`;
+
+ const adapter = new IndexedDBAdapter<LegacyTimelineData>(
+ sceneDbName,
+ "timeline",
+ 1,
+ );
+
+ let data = await adapter.get("timeline");
+
+ if (!data && isMain) {
+ const projectAdapter = new IndexedDBAdapter<LegacyTimelineData>(
+ projectDbName,
+ "timeline",
+ 1,
+ );
+ data = await projectAdapter.get("timeline");
+ }
+
+ if (!data || !Array.isArray(data.tracks)) {
+ return [];
+ }
+
+ return data.tracks;
+}
+
+async function loadMediaTypesById({
+ projectId,
+ legacyTracksBySceneId,
+}: {
+ projectId: string;
+ legacyTracksBySceneId: V1ToV2Context["legacyTracksBySceneId"];
+}): Promise<V1ToV2Context["mediaTypesById"]> {
+ const mediaIds = collectLegacyMediaIds({ legacyTracksBySceneId });
+ if (mediaIds.length === 0) {
+ return {};
}
- const dbNamesToDelete: string[] = [];
+ const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(
+ `video-editor-media-${projectId}`,
+ "media-metadata",
+ 1,
+ );
+
+ const mediaEntries = await Promise.all(
+ mediaIds.map(async (mediaId) => {
+ const mediaMetadata = await mediaMetadataAdapter.get(mediaId);
+ if (!mediaMetadata) {
+ return null;
+ }
+
+ return [mediaId, mediaMetadata.type] as const;
+ }),
+ );
+
+ return Object.fromEntries(
+ mediaEntries.filter(
+ (
+ mediaEntry,
+ ): mediaEntry is readonly [
+ string,
+ V1ToV2Context["mediaTypesById"][string],
+ ] => mediaEntry !== null,
+ ),
+ );
+}
+
+function collectLegacyMediaIds({
+ legacyTracksBySceneId,
+}: {
+ legacyTracksBySceneId: V1ToV2Context["legacyTracksBySceneId"];
+}): string[] {
+ const mediaIds = new Set<string>();
- for (const scene of scenes) {
- if (typeof scene !== "object" || scene === null) {
+ for (const tracks of Object.values(legacyTracksBySceneId)) {
+ if (!Array.isArray(tracks)) {
continue;
}
- const sceneId = scene.id;
- if (typeof sceneId === "string") {
- const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
- dbNamesToDelete.push(sceneDbName);
+ for (const track of tracks) {
+ if (!isRecord(track) || track.type !== "media") {
+ continue;
+ }
+
+ const elements = track.elements;
+ if (!Array.isArray(elements)) {
+ continue;
+ }
+
+ for (const element of elements) {
+ if (!isRecord(element) || element.type !== "media") {
+ continue;
+ }
+
+ if (typeof element.mediaId !== "string") {
+ continue;
+ }
+
+ mediaIds.add(element.mediaId);
+ }
}
}
- const projectDbName = `video-editor-timelines-${projectId}`;
- dbNamesToDelete.push(projectDbName);
+ return Array.from(mediaIds);
+}
- // Fire-and-forget: delete in parallel, don't block migration
- void Promise.all(
- dbNamesToDelete.map((dbName) =>
- deleteDatabase({ dbName }).catch(() => {
- // ignore errors, DB might not exist or already deleted
- }),
- ),
- );
+async function deleteLegacyTimelineDbs({
+ projectId,
+ project,
+}: {
+ projectId: string;
+ project: ProjectRecord;
+}): Promise<void> {
+ const dbNames = getLegacyTimelineDbNames({ projectId, project });
+ await Promise.all(dbNames.map((dbName) => deleteDatabase({ dbName })));
+}
+
+function getLegacyTimelineDbNames({
+ projectId,
+ project,
+}: {
+ projectId: string;
+ project: ProjectRecord;
+}): string[] {
+ const scenes = project.scenes;
+ if (!Array.isArray(scenes)) {
+ return [`video-editor-timelines-${projectId}`];
+ }
+
+ const sceneDbNames = scenes.flatMap((scene) => {
+ if (!isRecord(scene)) {
+ return [];
+ }
+
+ return typeof scene.id === "string"
+ ? [`video-editor-timelines-${projectId}-${scene.id}`]
+ : [];
+ });
+
+ return [...sceneDbNames, `video-editor-timelines-${projectId}`];
}
diff --git a/apps/web/src/services/storage/migrations/v10-to-v11.ts b/apps/web/src/services/storage/migrations/v10-to-v11.ts
index 345dbeb3..53ce5bc5 100644
--- a/apps/web/src/services/storage/migrations/v10-to-v11.ts
+++ b/apps/web/src/services/storage/migrations/v10-to-v11.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV10ToV11 } from "./transformers/v10-to-v11";
export class V10toV11Migration extends StorageMigration {
from = 10;
to = 11;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV10ToV11({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v11-to-v12.ts b/apps/web/src/services/storage/migrations/v11-to-v12.ts
index b3154ca8..df626f2f 100644
--- a/apps/web/src/services/storage/migrations/v11-to-v12.ts
+++ b/apps/web/src/services/storage/migrations/v11-to-v12.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV11ToV12 } from "./transformers/v11-to-v12";
export class V11toV12Migration extends StorageMigration {
from = 11;
to = 12;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV11ToV12({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v12-to-v13.ts b/apps/web/src/services/storage/migrations/v12-to-v13.ts
index 34b8de60..466efb5f 100644
--- a/apps/web/src/services/storage/migrations/v12-to-v13.ts
+++ b/apps/web/src/services/storage/migrations/v12-to-v13.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV12ToV13 } from "./transformers/v12-to-v13";
export class V12toV13Migration extends StorageMigration {
from = 12;
to = 13;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV12ToV13({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v13-to-v14.ts b/apps/web/src/services/storage/migrations/v13-to-v14.ts
index b8bde59c..5b26f59a 100644
--- a/apps/web/src/services/storage/migrations/v13-to-v14.ts
+++ b/apps/web/src/services/storage/migrations/v13-to-v14.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV13ToV14 } from "./transformers/v13-to-v14";
export class V13toV14Migration extends StorageMigration {
from = 13;
to = 14;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV13ToV14({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v14-to-v15.ts b/apps/web/src/services/storage/migrations/v14-to-v15.ts
index 5c3a60cf..d2b58ab8 100644
--- a/apps/web/src/services/storage/migrations/v14-to-v15.ts
+++ b/apps/web/src/services/storage/migrations/v14-to-v15.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV14ToV15 } from "./transformers/v14-to-v15";
export class V14toV15Migration extends StorageMigration {
from = 14;
to = 15;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV14ToV15({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v15-to-v16.ts b/apps/web/src/services/storage/migrations/v15-to-v16.ts
index 07afe148..221bf278 100644
--- a/apps/web/src/services/storage/migrations/v15-to-v16.ts
+++ b/apps/web/src/services/storage/migrations/v15-to-v16.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV15ToV16 } from "./transformers/v15-to-v16";
export class V15toV16Migration extends StorageMigration {
from = 15;
to = 16;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV15ToV16({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v16-to-v17.ts b/apps/web/src/services/storage/migrations/v16-to-v17.ts
index 053e8d07..367fa3a5 100644
--- a/apps/web/src/services/storage/migrations/v16-to-v17.ts
+++ b/apps/web/src/services/storage/migrations/v16-to-v17.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV16ToV17 } from "./transformers/v16-to-v17";
export class V16toV17Migration extends StorageMigration {
from = 16;
to = 17;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV16ToV17({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v17-to-v18.ts b/apps/web/src/services/storage/migrations/v17-to-v18.ts
index b1591907..142c7836 100644
--- a/apps/web/src/services/storage/migrations/v17-to-v18.ts
+++ b/apps/web/src/services/storage/migrations/v17-to-v18.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV17ToV18 } from "./transformers/v17-to-v18";
export class V17toV18Migration extends StorageMigration {
from = 17;
to = 18;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV17ToV18({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v18-to-v19.ts b/apps/web/src/services/storage/migrations/v18-to-v19.ts
index d8fde783..fdde8295 100644
--- a/apps/web/src/services/storage/migrations/v18-to-v19.ts
+++ b/apps/web/src/services/storage/migrations/v18-to-v19.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV18ToV19 } from "./transformers/v18-to-v19";
export class V18toV19Migration extends StorageMigration {
from = 18;
to = 19;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV18ToV19({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v19-to-v20.ts b/apps/web/src/services/storage/migrations/v19-to-v20.ts
index c6809067..c423e00b 100644
--- a/apps/web/src/services/storage/migrations/v19-to-v20.ts
+++ b/apps/web/src/services/storage/migrations/v19-to-v20.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV19ToV20 } from "./transformers/v19-to-v20";
export class V19toV20Migration extends StorageMigration {
from = 19;
to = 20;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV19ToV20({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v2-to-v3.ts b/apps/web/src/services/storage/migrations/v2-to-v3.ts
index 70eb9aad..b825bfc1 100644
--- a/apps/web/src/services/storage/migrations/v2-to-v3.ts
+++ b/apps/web/src/services/storage/migrations/v2-to-v3.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV2ToV3 } from "./transformers/v2-to-v3";
export class V2toV3Migration extends StorageMigration {
from = 2;
to = 3;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV2ToV3({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v20-to-v21.ts b/apps/web/src/services/storage/migrations/v20-to-v21.ts
index f04ad35f..f28837f1 100644
--- a/apps/web/src/services/storage/migrations/v20-to-v21.ts
+++ b/apps/web/src/services/storage/migrations/v20-to-v21.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV20ToV21 } from "./transformers/v20-to-v21";
export class V20toV21Migration extends StorageMigration {
from = 20;
to = 21;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV20ToV21({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v21-to-v22.ts b/apps/web/src/services/storage/migrations/v21-to-v22.ts
index 212109a5..667e14e2 100644
--- a/apps/web/src/services/storage/migrations/v21-to-v22.ts
+++ b/apps/web/src/services/storage/migrations/v21-to-v22.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV21ToV22 } from "./transformers/v21-to-v22";
export class V21toV22Migration extends StorageMigration {
from = 21;
to = 22;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV21ToV22({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v22-to-v23.ts b/apps/web/src/services/storage/migrations/v22-to-v23.ts
index cd352e45..36b335ff 100644
--- a/apps/web/src/services/storage/migrations/v22-to-v23.ts
+++ b/apps/web/src/services/storage/migrations/v22-to-v23.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV22ToV23 } from "./transformers/v22-to-v23";
export class V22toV23Migration extends StorageMigration {
from = 22;
to = 23;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV22ToV23({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v23-to-v24.ts b/apps/web/src/services/storage/migrations/v23-to-v24.ts
index fe952bb1..3825a9eb 100644
--- a/apps/web/src/services/storage/migrations/v23-to-v24.ts
+++ b/apps/web/src/services/storage/migrations/v23-to-v24.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV23ToV24 } from "./transformers/v23-to-v24";
export class V23toV24Migration extends StorageMigration {
from = 23;
to = 24;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV23ToV24({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v24-to-v25.ts b/apps/web/src/services/storage/migrations/v24-to-v25.ts
index 2e2a6935..5190b129 100644
--- a/apps/web/src/services/storage/migrations/v24-to-v25.ts
+++ b/apps/web/src/services/storage/migrations/v24-to-v25.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV24ToV25 } from "./transformers/v24-to-v25";
export class V24toV25Migration extends StorageMigration {
from = 24;
to = 25;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV24ToV25({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v25-to-v26.ts b/apps/web/src/services/storage/migrations/v25-to-v26.ts
index b5a8f310..8674d84b 100644
--- a/apps/web/src/services/storage/migrations/v25-to-v26.ts
+++ b/apps/web/src/services/storage/migrations/v25-to-v26.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV25ToV26 } from "./transformers/v25-to-v26";
export class V25toV26Migration extends StorageMigration {
from = 25;
to = 26;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV25ToV26({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v26-to-v27.ts b/apps/web/src/services/storage/migrations/v26-to-v27.ts
index 2df5329d..92ef18c0 100644
--- a/apps/web/src/services/storage/migrations/v26-to-v27.ts
+++ b/apps/web/src/services/storage/migrations/v26-to-v27.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV26ToV27 } from "./transformers/v26-to-v27";
export class V26toV27Migration extends StorageMigration {
from = 26;
to = 27;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV26ToV27({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v3-to-v4.ts b/apps/web/src/services/storage/migrations/v3-to-v4.ts
index a3738076..c8054f25 100644
--- a/apps/web/src/services/storage/migrations/v3-to-v4.ts
+++ b/apps/web/src/services/storage/migrations/v3-to-v4.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV3ToV4 } from "./transformers/v3-to-v4";
export class V3toV4Migration extends StorageMigration {
from = 3;
to = 4;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV3ToV4({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v4-to-v5.ts b/apps/web/src/services/storage/migrations/v4-to-v5.ts
index 394406d1..e8f27657 100644
--- a/apps/web/src/services/storage/migrations/v4-to-v5.ts
+++ b/apps/web/src/services/storage/migrations/v4-to-v5.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV4ToV5 } from "./transformers/v4-to-v5";
export class V4toV5Migration extends StorageMigration {
from = 4;
to = 5;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV4ToV5({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v5-to-v6.ts b/apps/web/src/services/storage/migrations/v5-to-v6.ts
index e6be4f9b..71061855 100644
--- a/apps/web/src/services/storage/migrations/v5-to-v6.ts
+++ b/apps/web/src/services/storage/migrations/v5-to-v6.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV5ToV6 } from "./transformers/v5-to-v6";
export class V5toV6Migration extends StorageMigration {
from = 5;
to = 6;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV5ToV6({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v6-to-v7.ts b/apps/web/src/services/storage/migrations/v6-to-v7.ts
index 3f63770b..4c917ed9 100644
--- a/apps/web/src/services/storage/migrations/v6-to-v7.ts
+++ b/apps/web/src/services/storage/migrations/v6-to-v7.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV6ToV7 } from "./transformers/v6-to-v7";
export class V6toV7Migration extends StorageMigration {
from = 6;
to = 7;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV6ToV7({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v7-to-v8.ts b/apps/web/src/services/storage/migrations/v7-to-v8.ts
index 5a3302f7..24de80c6 100644
--- a/apps/web/src/services/storage/migrations/v7-to-v8.ts
+++ b/apps/web/src/services/storage/migrations/v7-to-v8.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV7ToV8 } from "./transformers/v7-to-v8";
export class V7toV8Migration extends StorageMigration {
from = 7;
to = 8;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV7ToV8({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v8-to-v9.ts b/apps/web/src/services/storage/migrations/v8-to-v9.ts
index bdbed910..b8baee60 100644
--- a/apps/web/src/services/storage/migrations/v8-to-v9.ts
+++ b/apps/web/src/services/storage/migrations/v8-to-v9.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV8ToV9 } from "./transformers/v8-to-v9";
export class V8toV9Migration extends StorageMigration {
from = 8;
to = 9;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV8ToV9({ project });
}
}
diff --git a/apps/web/src/services/storage/migrations/v9-to-v10.ts b/apps/web/src/services/storage/migrations/v9-to-v10.ts
index d61116f0..e156add9 100644
--- a/apps/web/src/services/storage/migrations/v9-to-v10.ts
+++ b/apps/web/src/services/storage/migrations/v9-to-v10.ts
@@ -1,16 +1,14 @@
-import { StorageMigration } from "./base";
-import type { ProjectRecord } from "./transformers/types";
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
import { transformProjectV9ToV10 } from "./transformers/v9-to-v10";
export class V9toV10Migration extends StorageMigration {
from = 9;
to = 10;
- async transform(project: ProjectRecord): Promise<{
- project: ProjectRecord;
- skipped: boolean;
- reason?: string;
- }> {
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
return transformProjectV9ToV10({ project });
}
}