summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/services/storage/migrations')
-rw-r--r--apps/web/src/services/storage/migrations/__tests__/v31-to-v32.test.ts65
-rw-r--r--apps/web/src/services/storage/migrations/index.ts4
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v31-to-v32.ts31
-rw-r--r--apps/web/src/services/storage/migrations/v31-to-v32.ts14
4 files changed, 113 insertions, 1 deletions
diff --git a/apps/web/src/services/storage/migrations/__tests__/v31-to-v32.test.ts b/apps/web/src/services/storage/migrations/__tests__/v31-to-v32.test.ts
new file mode 100644
index 00000000..e3f1af8c
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/__tests__/v31-to-v32.test.ts
@@ -0,0 +1,65 @@
+import { describe, expect, test } from "bun:test";
+import { transformProjectV31ToV32 } from "../transformers/v31-to-v32";
+import { asRecord, asRecordArray } from "./helpers";
+
+describe("V31 to V32 Migration", () => {
+ test("stamps a v31 project as v32 without changing its content", () => {
+ const result = transformProjectV31ToV32({
+ project: {
+ id: "project-v31",
+ version: 31,
+ metadata: { id: "project-v31", name: "My project" },
+ scenes: [
+ {
+ id: "scene-1",
+ tracks: {
+ main: {
+ id: "track-1",
+ type: "video",
+ elements: [{ id: "elem-1", type: "video", mediaId: "media-1" }],
+ },
+ overlay: [],
+ audio: [],
+ },
+ },
+ ],
+ },
+ });
+
+ expect(result.skipped).toBe(false);
+ expect(result.project.version).toBe(32);
+ const scene = asRecordArray(result.project.scenes)[0];
+ const main = asRecord(asRecord(scene.tracks).main);
+ const element = asRecordArray(main.elements)[0];
+ expect(element).toMatchObject({
+ id: "elem-1",
+ type: "video",
+ mediaId: "media-1",
+ });
+ expect(asRecord(result.project.metadata).name).toBe("My project");
+ });
+
+ test("skips a project that is already v32", () => {
+ const project = { id: "p1", version: 32, scenes: [] };
+ const result = transformProjectV31ToV32({ project });
+ expect(result.skipped).toBe(true);
+ expect(result.reason).toBe("already v32");
+ expect(result.project).toBe(project);
+ });
+
+ test("skips a project that is not v31", () => {
+ const project = { id: "p1", version: 30, scenes: [] };
+ const result = transformProjectV31ToV32({ project });
+ expect(result.skipped).toBe(true);
+ expect(result.reason).toBe("not v31");
+ expect(result.project).toBe(project);
+ });
+
+ test("skips a project without an id", () => {
+ const project = { version: 31, scenes: [] };
+ const result = transformProjectV31ToV32({ project });
+ expect(result.skipped).toBe(true);
+ expect(result.reason).toBe("no project id");
+ expect(result.project).toBe(project);
+ });
+});
diff --git a/apps/web/src/services/storage/migrations/index.ts b/apps/web/src/services/storage/migrations/index.ts
index cbc35bb3..f3f2a22c 100644
--- a/apps/web/src/services/storage/migrations/index.ts
+++ b/apps/web/src/services/storage/migrations/index.ts
@@ -30,10 +30,11 @@ import { V27toV28Migration } from "./v27-to-v28";
import { V28toV29Migration } from "./v28-to-v29";
import { V29toV30Migration } from "./v29-to-v30";
import { V30toV31Migration } from "./v30-to-v31";
+import { V31toV32Migration } from "./v31-to-v32";
export { runStorageMigrations } from "./runner";
export type { MigrationProgress } from "./runner";
-export const CURRENT_PROJECT_VERSION = 31;
+export const CURRENT_PROJECT_VERSION = 32;
export const migrations = [
new V0toV1Migration(),
@@ -67,4 +68,5 @@ export const migrations = [
new V28toV29Migration(),
new V29toV30Migration(),
new V30toV31Migration(),
+ new V31toV32Migration(),
];
diff --git a/apps/web/src/services/storage/migrations/transformers/v31-to-v32.ts b/apps/web/src/services/storage/migrations/transformers/v31-to-v32.ts
new file mode 100644
index 00000000..bedb4784
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/transformers/v31-to-v32.ts
@@ -0,0 +1,31 @@
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId } from "./utils";
+
+/**
+ * v31 → v32 introduces the `projectVersions` store (named checkpoints +
+ * auto-checkpoints; see notes/project-versioning.md). The store lives in its
+ * own IndexedDB database created lazily by the versions adapter, so project
+ * records need no shape change — this migration only stamps the new version.
+ */
+export function transformProjectV31ToV32({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ const version = project.version;
+ if (typeof version !== "number") {
+ return { project, skipped: true, reason: "invalid version" };
+ }
+ if (version >= 32) {
+ return { project, skipped: true, reason: "already v32" };
+ }
+ if (version !== 31) {
+ return { project, skipped: true, reason: "not v31" };
+ }
+
+ return { project: { ...project, version: 32 }, skipped: false };
+}
diff --git a/apps/web/src/services/storage/migrations/v31-to-v32.ts b/apps/web/src/services/storage/migrations/v31-to-v32.ts
new file mode 100644
index 00000000..0b6da324
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/v31-to-v32.ts
@@ -0,0 +1,14 @@
+import { StorageMigration, type StorageMigrationRunArgs } from "./base";
+import type { MigrationResult, ProjectRecord } from "./transformers/types";
+import { transformProjectV31ToV32 } from "./transformers/v31-to-v32";
+
+export class V31toV32Migration extends StorageMigration {
+ from = 31;
+ to = 32;
+
+ async run({
+ project,
+ }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
+ return transformProjectV31ToV32({ project });
+ }
+}