feat: support migration (#2852)

This commit is contained in:
Alex Yang
2023-06-26 15:55:44 +08:00
committed by GitHub
parent 002e64c819
commit 8e82d1e02c
14 changed files with 395 additions and 119 deletions
+2 -1
View File
@@ -5,7 +5,7 @@ import 'fake-indexeddb/auto';
import { initEmptyPage } from '@affine/env/blocksuite';
import type { LocalIndexedDBBackgroundProvider } from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
import {
rootCurrentWorkspaceIdAtom,
rootWorkspacesMetadataAtom,
@@ -96,6 +96,7 @@ describe('currentWorkspace atom', () => {
{
id: workspaceId,
flavour: WorkspaceFlavour.LOCAL,
version: WorkspaceVersion.SubDoc,
},
]);
_cleanupBlockSuiteWorkspaceCache();
+26 -22
View File
@@ -1,6 +1,6 @@
import { DebugLogger } from '@affine/debug';
import { WorkspaceFlavour } from '@affine/env/workspace';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
import type { RootWorkspaceMetadataV2 } from '@affine/workspace/atom';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { atom } from 'jotai';
import { atomFamily, atomWithStorage } from 'jotai/utils';
@@ -13,7 +13,7 @@ const logger = new DebugLogger('web:atoms');
// workspace necessary atoms
// todo(himself65): move this to the workspace package
rootWorkspacesMetadataAtom.onMount = setAtom => {
function createFirst(): RootWorkspaceMetadata[] {
function createFirst(): RootWorkspaceMetadataV2[] {
const Plugins = Object.values(WorkspaceAdapters).sort(
(a, b) => a.loadPriority - b.loadPriority
);
@@ -24,29 +24,33 @@ rootWorkspacesMetadataAtom.onMount = setAtom => {
({
id,
flavour: Plugin.flavour,
} satisfies RootWorkspaceMetadata)
// new workspace should all support sub-doc feature
version: WorkspaceVersion.SubDoc,
} satisfies RootWorkspaceMetadataV2)
);
}).filter((ids): ids is RootWorkspaceMetadata => !!ids);
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
}
const abortController = new AbortController();
// next tick to make sure the hydration is correct
const id = setTimeout(() => {
setAtom(metadata => {
if (abortController.signal.aborted) return metadata;
if (
metadata.length === 0 &&
localStorage.getItem('is-first-open') === null
) {
localStorage.setItem('is-first-open', 'false');
const newMetadata = createFirst();
logger.info('create first workspace', newMetadata);
return newMetadata;
}
return metadata;
});
}, 0);
if (!environment.isServer) {
// next tick to make sure the hydration is correct
setTimeout(() => {
setAtom(metadata => {
if (abortController.signal.aborted) return metadata;
if (
metadata.length === 0 &&
localStorage.getItem('is-first-open') === null
) {
localStorage.setItem('is-first-open', 'false');
const newMetadata = createFirst();
logger.info('create first workspace', newMetadata);
return newMetadata;
}
return metadata;
});
}, 0);
}
if (environment.isDesktop) {
window.apis?.workspace
@@ -56,6 +60,7 @@ rootWorkspacesMetadataAtom.onMount = setAtom => {
const newMetadata = workspaceIDs.map(w => ({
id: w[0],
flavour: WorkspaceFlavour.LOCAL,
version: undefined,
}));
setAtom(metadata => {
return [
@@ -70,7 +75,6 @@ rootWorkspacesMetadataAtom.onMount = setAtom => {
}
return () => {
clearTimeout(id);
abortController.abort();
};
};
+75 -51
View File
@@ -18,60 +18,73 @@ const logger = new DebugLogger('web:atoms:root');
/**
* Fetch all workspaces from the Plugin CRUD
*/
export const workspacesAtom = atom<Promise<AllWorkspace[]>>(async get => {
const { WorkspaceAdapters } = await import('../adapters/workspace');
const flavours: string[] = Object.values(WorkspaceAdapters).map(
plugin => plugin.flavour
);
const jotaiWorkspaces = get(rootWorkspacesMetadataAtom)
.filter(
workspace => flavours.includes(workspace.flavour)
// TODO: remove this when we remove the legacy cloud
)
.filter(workspace =>
!config.enableLegacyCloud
? workspace.flavour !== WorkspaceFlavour.AFFINE
: true
export const workspacesAtom = atom<Promise<AllWorkspace[]>>(
async (get, { signal }) => {
const { WorkspaceAdapters } = await import('../adapters/workspace');
const flavours: string[] = Object.values(WorkspaceAdapters).map(
plugin => plugin.flavour
);
const workspaces = await Promise.all(
jotaiWorkspaces.map(workspace => {
const plugin =
WorkspaceAdapters[workspace.flavour as keyof typeof WorkspaceAdapters];
assertExists(plugin);
const { CRUD } = plugin;
return CRUD.get(workspace.id).then(workspace => {
if (workspace === null) {
console.warn(
'workspace is null. this should not happen. If you see this error, please report it to the developer.'
);
}
return workspace;
const jotaiWorkspaces = get(rootWorkspacesMetadataAtom)
.filter(
workspace => flavours.includes(workspace.flavour)
// TODO: remove this when we remove the legacy cloud
)
.filter(workspace =>
!config.enableLegacyCloud
? workspace.flavour !== WorkspaceFlavour.AFFINE
: true
);
if (jotaiWorkspaces.some(meta => meta.version === undefined)) {
// wait until all workspaces have migrated to v2
await new Promise((resolve, reject) => {
signal.addEventListener('abort', reject);
setTimeout(resolve, 1000);
}).catch(() => {
// do nothing
});
})
).then(workspaces =>
workspaces.filter(
(workspace): workspace is WorkspaceRegistry['affine' | 'local'] =>
workspace !== null
)
);
const workspaceProviders = workspaces.map(workspace =>
workspace.blockSuiteWorkspace.providers.filter(
(provider): provider is ActiveDocProvider =>
'active' in provider && provider.active
)
);
const promises: Promise<void>[] = [];
for (const providers of workspaceProviders) {
for (const provider of providers) {
provider.sync();
promises.push(provider.whenReady);
}
const workspaces = await Promise.all(
jotaiWorkspaces.map(workspace => {
const plugin =
WorkspaceAdapters[
workspace.flavour as keyof typeof WorkspaceAdapters
];
assertExists(plugin);
const { CRUD } = plugin;
return CRUD.get(workspace.id).then(workspace => {
if (workspace === null) {
console.warn(
'workspace is null. this should not happen. If you see this error, please report it to the developer.'
);
}
return workspace;
});
})
).then(workspaces =>
workspaces.filter(
(workspace): workspace is WorkspaceRegistry['affine' | 'local'] =>
workspace !== null
)
);
const workspaceProviders = workspaces.map(workspace =>
workspace.blockSuiteWorkspace.providers.filter(
(provider): provider is ActiveDocProvider =>
'active' in provider && provider.active
)
);
const promises: Promise<void>[] = [];
for (const providers of workspaceProviders) {
for (const provider of providers) {
provider.sync();
promises.push(provider.whenReady);
}
}
// we will wait for all the necessary providers to be ready
await Promise.all(promises);
logger.info('workspaces', workspaces);
return workspaces;
}
// we will wait for all the necessary providers to be ready
await Promise.all(promises);
logger.info('workspaces', workspaces);
return workspaces;
});
);
/**
* This will throw an error if the workspace is not found,
@@ -79,7 +92,7 @@ export const workspacesAtom = atom<Promise<AllWorkspace[]>>(async get => {
* use `rootCurrentWorkspaceIdAtom` instead
*/
export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>(
async get => {
async (get, { signal }) => {
const { WorkspaceAdapters } = await import('../adapters/workspace');
const metadata = get(rootWorkspacesMetadataAtom);
const targetId = get(rootCurrentWorkspaceIdAtom);
@@ -92,6 +105,17 @@ export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>(
if (!targetWorkspace) {
throw new Error(`cannot find the workspace with id ${targetId}.`);
}
if (!targetWorkspace.version) {
// wait until the workspace has migrated to v2
await new Promise((resolve, reject) => {
signal.addEventListener('abort', reject);
setTimeout(resolve, 1000);
}).catch(() => {
// do nothing
});
}
const workspace = await WorkspaceAdapters[targetWorkspace.flavour].CRUD.get(
targetWorkspace.id
);