Skip to content

Commit 6312fc5

Browse files
committed
fix: repair scene tracks broken by v23-to-v24 migration
1 parent 2da93aa commit 6312fc5

File tree

4 files changed

+161
-5
lines changed

4 files changed

+161
-5
lines changed

apps/web/src/services/storage/migrations/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import { V21toV22Migration } from "./v21-to-v22";
2424
import { V22toV23Migration } from "./v22-to-v23";
2525
import { V23toV24Migration } from "./v23-to-v24";
2626
import { V24toV25Migration } from "./v24-to-v25";
27+
import { V25toV26Migration } from "./v25-to-v26";
2728
export { runStorageMigrations } from "./runner";
2829
export type { MigrationProgress } from "./runner";
2930

30-
export const CURRENT_PROJECT_VERSION = 25;
31+
export const CURRENT_PROJECT_VERSION = 26;
3132

3233
export const migrations = [
3334
new V0toV1Migration(),
@@ -55,4 +56,5 @@ export const migrations = [
5556
new V22toV23Migration(),
5657
new V23toV24Migration(),
5758
new V24toV25Migration(),
59+
new V25toV26Migration(),
5860
];

apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
5151
}
5252

5353
const mainTrack = findMainTrack({ tracks: scene.tracks });
54-
if (!mainTrack) {
55-
return scene;
56-
}
54+
const finalMainTrack = mainTrack ?? buildEmptyMainTrack();
5755

5856
return {
5957
...scene,
@@ -65,7 +63,7 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
6563
(track): track is ProjectRecord =>
6664
isRecord(track) && track.type !== "audio",
6765
),
68-
main: migrateTrack({ track: mainTrack }),
66+
main: migrateTrack({ track: finalMainTrack }),
6967
audio: scene.tracks
7068
.map((track) => migrateTrack({ track }))
7169
.filter(
@@ -76,6 +74,17 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
7674
};
7775
}
7876

77+
function buildEmptyMainTrack(): ProjectRecord {
78+
return {
79+
id: crypto.randomUUID(),
80+
name: "Main",
81+
type: "video",
82+
elements: [],
83+
muted: false,
84+
hidden: false,
85+
};
86+
}
87+
7988
function findMainTrack({
8089
tracks,
8190
}: {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import type { MigrationResult, ProjectRecord } from "./types";
2+
import { getProjectId, isRecord } from "./utils";
3+
4+
export function transformProjectV25ToV26({
5+
project,
6+
}: {
7+
project: ProjectRecord;
8+
}): MigrationResult<ProjectRecord> {
9+
if (!getProjectId({ project })) {
10+
return { project, skipped: true, reason: "no project id" };
11+
}
12+
13+
const version = project.version;
14+
if (typeof version !== "number") {
15+
return { project, skipped: true, reason: "invalid version" };
16+
}
17+
if (version >= 26) {
18+
return { project, skipped: true, reason: "already v26" };
19+
}
20+
if (version !== 25) {
21+
return { project, skipped: true, reason: "not v25" };
22+
}
23+
24+
return {
25+
project: {
26+
...migrateProject({ project }),
27+
version: 26,
28+
},
29+
skipped: false,
30+
};
31+
}
32+
33+
function migrateProject({
34+
project,
35+
}: {
36+
project: ProjectRecord;
37+
}): ProjectRecord {
38+
if (!Array.isArray(project.scenes)) {
39+
return project;
40+
}
41+
42+
return {
43+
...project,
44+
scenes: project.scenes.map((scene) => migrateScene({ scene })),
45+
};
46+
}
47+
48+
function migrateScene({ scene }: { scene: unknown }): unknown {
49+
if (!isRecord(scene)) {
50+
return scene;
51+
}
52+
53+
const tracks = scene.tracks;
54+
55+
// Already in the correct shape — nothing to repair
56+
if (isRecord(tracks) && Array.isArray(tracks.overlay) && Array.isArray(tracks.audio)) {
57+
return scene;
58+
}
59+
60+
// Reconstruct the flat track array from whatever broken state it's in.
61+
// v24-to-v25 spread a flat array into a numeric-keyed object, so
62+
// Object.values recovers the original elements. A true flat array is
63+
// also handled for safety.
64+
let trackArray: unknown[];
65+
if (Array.isArray(tracks)) {
66+
trackArray = tracks;
67+
} else if (isRecord(tracks)) {
68+
trackArray = Object.values(tracks);
69+
} else {
70+
trackArray = [];
71+
}
72+
73+
const mainTrack = findMainTrack({ tracks: trackArray });
74+
const finalMainTrack = mainTrack ?? buildEmptyMainTrack();
75+
76+
return {
77+
...scene,
78+
tracks: {
79+
main: migrateTrack({ track: finalMainTrack }),
80+
overlay: trackArray
81+
.filter((track) => track !== mainTrack)
82+
.map((track) => migrateTrack({ track }))
83+
.filter(
84+
(track): track is ProjectRecord =>
85+
isRecord(track) && track.type !== "audio",
86+
),
87+
audio: trackArray
88+
.map((track) => migrateTrack({ track }))
89+
.filter(
90+
(track): track is ProjectRecord =>
91+
isRecord(track) && track.type === "audio",
92+
),
93+
},
94+
};
95+
}
96+
97+
function findMainTrack({
98+
tracks,
99+
}: {
100+
tracks: unknown[];
101+
}): ProjectRecord | null {
102+
for (const track of tracks) {
103+
if (!isRecord(track)) continue;
104+
if (track.type === "video" && track.isMain === true) return track;
105+
}
106+
for (const track of tracks) {
107+
if (!isRecord(track)) continue;
108+
if (track.type === "video") return track;
109+
}
110+
return null;
111+
}
112+
113+
function migrateTrack({ track }: { track: unknown }): unknown {
114+
if (!isRecord(track)) return track;
115+
const nextTrack = { ...track };
116+
delete nextTrack.isMain;
117+
return nextTrack;
118+
}
119+
120+
function buildEmptyMainTrack(): ProjectRecord {
121+
return {
122+
id: crypto.randomUUID(),
123+
name: "Main",
124+
type: "video",
125+
elements: [],
126+
muted: false,
127+
hidden: false,
128+
};
129+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { StorageMigration } from "./base";
2+
import type { ProjectRecord } from "./transformers/types";
3+
import { transformProjectV25ToV26 } from "./transformers/v25-to-v26";
4+
5+
export class V25toV26Migration extends StorageMigration {
6+
from = 25;
7+
to = 26;
8+
9+
async transform(project: ProjectRecord): Promise<{
10+
project: ProjectRecord;
11+
skipped: boolean;
12+
reason?: string;
13+
}> {
14+
return transformProjectV25ToV26({ project });
15+
}
16+
}

0 commit comments

Comments
 (0)