refactor: add schema migration helper APIs

This commit is contained in:
Ryan Di
2026-03-20 18:15:03 +11:00
parent 6c9914049e
commit 67967c05fa
6 changed files with 96 additions and 69 deletions
+16 -25
View File
@@ -54,7 +54,7 @@ import {
restoreElements, restoreElements,
} from "@excalidraw/excalidraw/data/restore"; } from "@excalidraw/excalidraw/data/restore";
import { import {
migrateElementsBySchema, migrateSceneElements,
resolveSchemaVersion, resolveSchemaVersion,
SCHEMA_VERSIONS, SCHEMA_VERSIONS,
} from "@excalidraw/excalidraw/data/schema"; } from "@excalidraw/excalidraw/data/schema";
@@ -242,15 +242,12 @@ const initializeScene = async (opts: {
scrollToContent?: boolean; scrollToContent?: boolean;
} = { } = {
elements: restoreElements( elements: restoreElements(
migrateElementsBySchema( migrateSceneElements(
localDataState?.elements, localDataState?.elements,
{ resolveSchemaVersion(
schemaVersion: resolveSchemaVersion( localDataState?.schemaVersion,
localDataState?.schemaVersion, SCHEMA_VERSIONS.initial,
SCHEMA_VERSIONS.initial, ),
),
scope: "scene",
},
), ),
null, null,
{ {
@@ -281,15 +278,12 @@ const initializeScene = async (opts: {
scene = { scene = {
elements: bumpElementVersions( elements: bumpElementVersions(
restoreElements( restoreElements(
migrateElementsBySchema( migrateSceneElements(
imported.elements, imported.elements,
{ resolveSchemaVersion(
schemaVersion: resolveSchemaVersion( imported.schemaVersion,
imported.schemaVersion, SCHEMA_VERSIONS.initial,
SCHEMA_VERSIONS.initial, ),
),
scope: "scene",
},
), ),
null, null,
{ {
@@ -580,15 +574,12 @@ const ExcalidrawWrapper = () => {
if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_DATA_STATE)) { if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_DATA_STATE)) {
const localDataState = importFromLocalStorage(); const localDataState = importFromLocalStorage();
const username = importUsernameFromLocalStorage(); const username = importUsernameFromLocalStorage();
const migratedElements = migrateElementsBySchema( const migratedElements = migrateSceneElements(
localDataState?.elements, localDataState?.elements,
{ resolveSchemaVersion(
schemaVersion: resolveSchemaVersion( localDataState?.schemaVersion,
localDataState?.schemaVersion, SCHEMA_VERSIONS.initial,
SCHEMA_VERSIONS.initial, ),
),
scope: "scene",
},
); );
setLangCode(getPreferredLanguage()); setLangCode(getPreferredLanguage());
excalidrawAPI.updateScene({ excalidrawAPI.updateScene({
+21 -19
View File
@@ -356,7 +356,10 @@ import { exportCanvas, loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library"; import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
import { restoreAppState, restoreElements } from "../data/restore"; import { restoreAppState, restoreElements } from "../data/restore";
import { import {
migrateElementsBySchema, migrateAPIElements,
migrateClipboardElements,
migrateLibraryElements,
migrateSceneElements,
resolveSchemaVersion, resolveSchemaVersion,
SCHEMA_VERSIONS, SCHEMA_VERSIONS,
type SchemaMigrationScope, type SchemaMigrationScope,
@@ -2819,15 +2822,12 @@ class App extends React.Component<AppProps, AppState> {
? SCHEMA_VERSIONS.initial ? SCHEMA_VERSIONS.initial
: SCHEMA_VERSIONS.latest; : SCHEMA_VERSIONS.latest;
const restoredElements = restoreElements( const restoredElements = restoreElements(
migrateElementsBySchema( migrateSceneElements(
initialData?.elements, initialData?.elements,
{ resolveSchemaVersion(
schemaVersion: resolveSchemaVersion( initialData?.schemaVersion,
initialData?.schemaVersion, initialDataSchemaFallback,
initialDataSchemaFallback, ),
),
scope: "scene",
},
), ),
null, null,
{ {
@@ -3754,17 +3754,19 @@ class App extends React.Component<AppProps, AppState> {
retainSeed?: boolean; retainSeed?: boolean;
fitToContent?: boolean; fitToContent?: boolean;
}) => { }) => {
const schemaVersion = resolveSchemaVersion(
opts.schemaVersion,
SCHEMA_VERSIONS.latest,
);
const migratedElements =
opts.migrationScope === "clipboard"
? migrateClipboardElements(opts.elements, schemaVersion)
: opts.migrationScope === "library"
? migrateLibraryElements(opts.elements, schemaVersion)
: migrateAPIElements(opts.elements, schemaVersion);
const elements = restoreElements( const elements = restoreElements(
migrateElementsBySchema( migratedElements,
opts.elements,
{
schemaVersion: resolveSchemaVersion(
opts.schemaVersion,
SCHEMA_VERSIONS.latest,
),
scope: opts.migrationScope,
},
),
null, null,
{ {
deleteInvisibleElements: true, deleteInvisibleElements: true,
+3 -6
View File
@@ -25,7 +25,7 @@ import {
restoreLibraryItems, restoreLibraryItems,
} from "./restore"; } from "./restore";
import { import {
migrateElementsBySchema, migrateSceneElements,
resolveSchemaVersion, resolveSchemaVersion,
SCHEMA_VERSIONS, SCHEMA_VERSIONS,
} from "./schema"; } from "./schema";
@@ -166,12 +166,9 @@ export const loadSceneOrLibraryFromBlob = async (
data.schemaVersion, data.schemaVersion,
SCHEMA_VERSIONS.initial, SCHEMA_VERSIONS.initial,
); );
const migratedElements = migrateElementsBySchema( const migratedElements = migrateSceneElements(
data.elements, data.elements,
{ schemaVersion,
schemaVersion,
scope: "scene",
},
); );
return { return {
type: MIME_TYPES.excalidraw, type: MIME_TYPES.excalidraw,
+3 -6
View File
@@ -81,7 +81,7 @@ import {
getNormalizedGridStep, getNormalizedGridStep,
getNormalizedZoom, getNormalizedZoom,
} from "../scene"; } from "../scene";
import { migrateElementsBySchema, SCHEMA_VERSIONS } from "./schema"; import { migrateLibraryElements, SCHEMA_VERSIONS } from "./schema";
import type { import type {
AppState, AppState,
@@ -962,12 +962,9 @@ const restoreLibraryItem = (
schemaVersion: number, schemaVersion: number,
) => { ) => {
const elements = restoreElements( const elements = restoreElements(
migrateElementsBySchema( migrateLibraryElements(
getNonDeletedElements(libraryItem.elements), getNonDeletedElements(libraryItem.elements),
{ schemaVersion,
schemaVersion,
scope: "library",
},
), ),
null, null,
); );
+17 -13
View File
@@ -4,7 +4,10 @@ import { API } from "../tests/helpers/api";
import { import {
ALL_SCOPES, ALL_SCOPES,
type SchemaMigration, type SchemaMigration,
migrateElementsBySchema, migrateAPIElements,
migrateClipboardElements,
migrateLibraryElements,
migrateSceneElements,
resolveSchemaVersion, resolveSchemaVersion,
SCHEMA_MIGRATIONS, SCHEMA_MIGRATIONS,
SCHEMA_VERSIONS, SCHEMA_VERSIONS,
@@ -18,10 +21,7 @@ describe("schema migration", () => {
backgroundColor: "#ffc9c9", backgroundColor: "#ffc9c9",
}); });
const migrated = migrateElementsBySchema([frame], { const migrated = migrateSceneElements([frame], SCHEMA_VERSIONS.initial)!;
schemaVersion: SCHEMA_VERSIONS.initial,
scope: "scene",
})!;
expect(migrated[0].backgroundColor).toBe( expect(migrated[0].backgroundColor).toBe(
DEFAULT_ELEMENT_PROPS.backgroundColor, DEFAULT_ELEMENT_PROPS.backgroundColor,
@@ -34,10 +34,7 @@ describe("schema migration", () => {
backgroundColor: "#ffc9c9", backgroundColor: "#ffc9c9",
}); });
const migrated = migrateElementsBySchema([frame], { const migrated = migrateSceneElements([frame], SCHEMA_VERSIONS.latest)!;
schemaVersion: SCHEMA_VERSIONS.latest,
scope: "scene",
})!;
expect(migrated[0].backgroundColor).toBe("#ffc9c9"); expect(migrated[0].backgroundColor).toBe("#ffc9c9");
}); });
@@ -48,11 +45,18 @@ describe("schema migration", () => {
backgroundColor: "#a5d8ff", backgroundColor: "#a5d8ff",
}); });
const migrationsByScope = {
scene: migrateSceneElements,
library: migrateLibraryElements,
clipboard: migrateClipboardElements,
api: migrateAPIElements,
} as const;
for (const scope of ALL_SCOPES) { for (const scope of ALL_SCOPES) {
const migrated = migrateElementsBySchema([frame], { const migrated = migrationsByScope[scope](
schemaVersion: SCHEMA_VERSIONS.initial, [frame],
scope, SCHEMA_VERSIONS.initial,
})!; )!;
expect(migrated[0].backgroundColor).toBe( expect(migrated[0].backgroundColor).toBe(
DEFAULT_ELEMENT_PROPS.backgroundColor, DEFAULT_ELEMENT_PROPS.backgroundColor,
); );
+36
View File
@@ -154,3 +154,39 @@ export const migrateElementsBySchema = (
elements, elements,
); );
}; };
export const migrateSceneElements = (
elements: readonly ExcalidrawElement[] | null | undefined,
schemaVersion: number,
) =>
migrateElementsBySchema(elements, {
schemaVersion,
scope: "scene",
});
export const migrateLibraryElements = (
elements: readonly ExcalidrawElement[] | null | undefined,
schemaVersion: number,
) =>
migrateElementsBySchema(elements, {
schemaVersion,
scope: "library",
});
export const migrateClipboardElements = (
elements: readonly ExcalidrawElement[] | null | undefined,
schemaVersion: number,
) =>
migrateElementsBySchema(elements, {
schemaVersion,
scope: "clipboard",
});
export const migrateAPIElements = (
elements: readonly ExcalidrawElement[] | null | undefined,
schemaVersion: number,
) =>
migrateElementsBySchema(elements, {
schemaVersion,
scope: "api",
});