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