feat!: unified migration logic in server electron, and browser (#4079)

Co-authored-by: Mirone <Saul-Mirone@outlook.com>
This commit is contained in:
Alex Yang
2023-09-06 00:44:53 -07:00
committed by GitHub
parent 925c18300f
commit 1b6a78cd00
61 changed files with 10776 additions and 10267 deletions

View File

@@ -1,11 +1,11 @@
import type { Workspace } from '@blocksuite/store';
import { type PassiveDocProvider } from '@blocksuite/store';
import { useAtomValue } from 'jotai/react';
import { useEffect } from 'react';
import {
disablePassiveProviders,
enablePassiveProviders,
getActiveBlockSuiteWorkspaceAtom,
workspacePassiveEffectWeakMap,
} from './workspace.js';
export function useStaticBlockSuiteWorkspace(id: string): Workspace {
@@ -14,22 +14,9 @@ export function useStaticBlockSuiteWorkspace(id: string): Workspace {
export function usePassiveWorkspaceEffect(workspace: Workspace) {
useEffect(() => {
if (workspacePassiveEffectWeakMap.get(workspace) === true) {
return;
}
const providers = workspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive === true
);
providers.forEach(provider => {
provider.connect();
});
workspacePassiveEffectWeakMap.set(workspace, true);
enablePassiveProviders(workspace);
return () => {
providers.forEach(provider => {
provider.disconnect();
});
workspacePassiveEffectWeakMap.delete(workspace);
disablePassiveProviders(workspace);
};
}, [workspace]);
}

View File

@@ -1,4 +1,5 @@
import type { ActiveDocProvider, Workspace } from '@blocksuite/store';
import type { PassiveDocProvider } from '@blocksuite/store';
import type { Atom } from 'jotai/vanilla';
import { atom } from 'jotai/vanilla';
@@ -8,16 +9,44 @@ import { atom } from 'jotai/vanilla';
*/
export const INTERNAL_BLOCKSUITE_HASH_MAP = new Map<string, Workspace>([]);
const workspacePassiveAtomWeakMap = new WeakMap<
const workspaceActiveAtomWeakMap = new WeakMap<
Workspace,
Atom<Promise<Workspace>>
>();
// Whether the workspace is active to use
export const workspaceActiveWeakMap = new WeakMap<Workspace, boolean>();
const workspaceActiveWeakMap = new WeakMap<Workspace, boolean>();
// Whether the workspace has been enabled the passive effect (background)
export const workspacePassiveEffectWeakMap = new WeakMap<Workspace, boolean>();
const workspacePassiveEffectWeakMap = new WeakMap<Workspace, boolean>();
export function enablePassiveProviders(workspace: Workspace) {
if (workspacePassiveEffectWeakMap.get(workspace) === true) {
return;
}
const providers = workspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive === true
);
providers.forEach(provider => {
provider.connect();
});
workspacePassiveEffectWeakMap.set(workspace, true);
}
export function disablePassiveProviders(workspace: Workspace) {
if (workspacePassiveEffectWeakMap.get(workspace) !== true) {
return;
}
const providers = workspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive === true
);
providers.forEach(provider => {
provider.disconnect();
});
workspacePassiveEffectWeakMap.delete(workspace);
}
export async function waitForWorkspace(workspace: Workspace) {
if (workspaceActiveWeakMap.get(workspace) !== true) {
@@ -48,12 +77,12 @@ export function getActiveBlockSuiteWorkspaceAtom(
throw new Error('Workspace not found');
}
const workspace = INTERNAL_BLOCKSUITE_HASH_MAP.get(id) as Workspace;
if (!workspacePassiveAtomWeakMap.has(workspace)) {
if (!workspaceActiveAtomWeakMap.has(workspace)) {
const baseAtom = atom(async () => {
await waitForWorkspace(workspace);
return workspace;
});
workspacePassiveAtomWeakMap.set(workspace, baseAtom);
workspaceActiveAtomWeakMap.set(workspace, baseAtom);
}
return workspacePassiveAtomWeakMap.get(workspace) as Atom<Promise<Workspace>>;
return workspaceActiveAtomWeakMap.get(workspace) as Atom<Promise<Workspace>>;
}

View File

@@ -1,6 +1,7 @@
import type { Page, PageMeta, Workspace } from '@blocksuite/store';
import { createIndexeddbStorage } from '@blocksuite/store';
import type { createStore, WritableAtom } from 'jotai/vanilla';
import type { Doc } from 'yjs';
import { Array as YArray, Doc as YDoc, Map as YMap } from 'yjs';
export async function initEmptyPage(page: Page, title?: string) {
@@ -24,6 +25,7 @@ export async function buildEmptyBlockSuite(workspace: Workspace) {
export async function buildShowcaseWorkspace(
workspace: Workspace,
options: {
schema: Schema;
atoms: {
pageMode: WritableAtom<
undefined,
@@ -196,7 +198,11 @@ export async function buildShowcaseWorkspace(
await Promise.all(
data.map(async ([id, promise]) => {
const { default: template } = await promise;
await workspace.importPageSnapshot(structuredClone(template), id);
await workspace
.importPageSnapshot(structuredClone(template), id)
.catch(error => {
console.error('error importing page', id, error);
});
workspace.setPageMeta(id, pageMetas[id]);
})
);
@@ -214,6 +220,16 @@ function deserializeXYWH(xywh: string): XYWH {
return JSON.parse(xywh) as XYWH;
}
const getLatestVersions = (schema: Schema): Record<string, number> => {
return [...schema.flavourSchemaMap.entries()].reduce(
(record, [flavour, schema]) => {
record[flavour] = schema.version;
return record;
},
{} as Record<string, number>
);
};
function migrateDatabase(data: YMap<unknown>) {
data.delete('prop:mode');
data.set('prop:views', new YArray());
@@ -450,30 +466,6 @@ export function migrateToSubdoc(oldDoc: YDoc): YDoc {
return newDoc;
}
export async function migrateDatabaseBlockTo3(rootDoc: YDoc, schema: Schema) {
const spaces = rootDoc.getMap('spaces') as YMap<any>;
spaces.forEach(space => {
schema.upgradePage(
{
'affine:note': 1,
'affine:bookmark': 1,
'affine:database': 2,
'affine:divider': 1,
'affine:image': 1,
'affine:list': 1,
'affine:code': 1,
'affine:page': 2,
'affine:paragraph': 1,
'affine:surface': 3,
},
space
);
});
const meta = rootDoc.getMap('meta') as YMap<unknown>;
const versions = meta.get('blockVersions') as YMap<number>;
versions.set('affine:database', 3);
}
export type UpgradeOptions = {
getCurrentRootDoc: () => Promise<YDoc>;
createWorkspace: () => Promise<Workspace>;
@@ -495,20 +487,42 @@ const upgradeV1ToV2 = async (options: UpgradeOptions) => {
return newWorkspace;
};
const upgradeV2ToV3 = async (options: UpgradeOptions): Promise<boolean> => {
/**
* Force upgrade block schema to the latest.
* Don't force to upgrade the pages without the check.
*
* Please note that this function will not upgrade the workspace version.
*
* @returns true if any schema is upgraded.
* @returns false if no schema is upgraded.
*/
export async function forceUpgradePages(
options: Omit<UpgradeOptions, 'createWorkspace'>
): Promise<boolean> {
const rootDoc = await options.getCurrentRootDoc();
const spaces = rootDoc.getMap('spaces') as YMap<any>;
const meta = rootDoc.getMap('meta') as YMap<unknown>;
const versions = meta.get('blockVersions') as YMap<number>;
if ('affine:database' in versions) {
if (versions['affine:database'] === 3) {
return false;
}
} else if (versions.get('affine:database') === 3) {
return false;
}
const schema = options.getSchema();
spaces.forEach(space => {
const oldVersions = versions.toJSON();
spaces.forEach((space: Doc) => {
schema.upgradePage(oldVersions, space);
});
const newVersions = getLatestVersions(schema);
meta.set('blockVersions', new YMap(Object.entries(newVersions)));
return Object.entries(oldVersions).some(
([flavour, version]) => newVersions[flavour] !== version
);
}
// database from 2 to 3
async function upgradeV2ToV3(options: UpgradeOptions): Promise<boolean> {
const rootDoc = await options.getCurrentRootDoc();
const spaces = rootDoc.getMap('spaces') as YMap<any>;
const meta = rootDoc.getMap('meta') as YMap<unknown>;
const versions = meta.get('blockVersions') as YMap<number>;
const schema = options.getSchema();
spaces.forEach((space: Doc) => {
schema.upgradePage(
{
'affine:note': 1,
@@ -526,18 +540,23 @@ const upgradeV2ToV3 = async (options: UpgradeOptions): Promise<boolean> => {
);
});
if ('affine:database' in versions) {
versions['affine:database'] = 3;
meta.set('blockVersions', new YMap(Object.entries(versions)));
meta.set(
'blockVersions',
new YMap(Object.entries(getLatestVersions(schema)))
);
} else {
versions.set('affine:database', 3);
Object.entries(getLatestVersions(schema)).map(([flavour, version]) =>
versions.set(flavour, version)
);
}
return true;
};
}
export enum WorkspaceVersion {
// v1 is treated as undefined
SubDoc = 2,
DatabaseV3 = 3,
Surface = 4,
}
/**
@@ -560,6 +579,9 @@ export async function migrateWorkspace(
}
if (currentVersion === WorkspaceVersion.SubDoc) {
return upgradeV2ToV3(options);
} else if (currentVersion === WorkspaceVersion.DatabaseV3) {
// surface from 3 to 5
return forceUpgradePages(options);
} else {
return false;
}