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

@@ -24,13 +24,13 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/blocks": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/editor": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/global": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/block-std": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/blocks": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/editor": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/global": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/icons": "^2.1.32",
"@blocksuite/lit": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/store": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/lit": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/store": "0.0.0-20230905170607-94acf22c-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",

View File

@@ -15,7 +15,10 @@ import {
CRUD,
saveWorkspaceToLocalStorage,
} from '@affine/workspace/local/crud';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import {
getOrCreateWorkspace,
globalBlockSuiteSchema,
} from '@affine/workspace/manager';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { nanoid } from '@blocksuite/store';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
@@ -49,6 +52,7 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
if (runtimeConfig.enablePreloading) {
buildShowcaseWorkspace(blockSuiteWorkspace, {
schema: globalBlockSuiteSchema,
store: getCurrentStore(),
atoms: {
pageMode: setPageModeAtom,

View File

@@ -18,10 +18,9 @@ import {
migrateWorkspace,
WorkspaceVersion,
} from '@toeverything/infra/blocksuite';
import { downloadBinary } from '@toeverything/y-indexeddb';
import { downloadBinary, overwriteBinary } from '@toeverything/y-indexeddb';
import type { createStore } from 'jotai/vanilla';
import { Doc } from 'yjs';
import { applyUpdate } from 'yjs';
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
import { WorkspaceAdapters } from '../adapters/workspace';
@@ -34,37 +33,39 @@ async function tryMigration() {
const newMetadata = [...metadata];
metadata.forEach(oldMeta => {
if (oldMeta.flavour === WorkspaceFlavour.LOCAL) {
let doc: YDoc;
const options = {
getCurrentRootDoc: async () => {
doc = new YDoc({
guid: oldMeta.id,
});
const downloadWorkspace = async (doc: YDoc): Promise<void> => {
const binary = await downloadBinary(doc.guid);
if (binary) {
applyUpdate(doc, binary);
}
await Promise.all(
[...doc.subdocs.values()].map(subdoc =>
downloadWorkspace(subdoc)
)
);
};
await downloadWorkspace(doc);
return doc;
},
createWorkspace: async () =>
getOrCreateWorkspace(nanoid(), WorkspaceFlavour.LOCAL),
getSchema: () => globalBlockSuiteSchema,
};
promises.push(
migrateWorkspace(
'version' in oldMeta ? oldMeta.version : undefined,
{
getCurrentRootDoc: async () => {
const doc = new Doc({
guid: oldMeta.id,
});
const downloadWorkspace = async (doc: Doc): Promise<void> => {
const binary = await downloadBinary(doc.guid);
if (binary) {
applyUpdate(doc, binary);
}
return Promise.all(
[...doc.subdocs.values()].map(subdoc =>
downloadWorkspace(subdoc)
)
).then();
};
await downloadWorkspace(doc);
return doc;
},
createWorkspace: async () =>
getOrCreateWorkspace(nanoid(), WorkspaceFlavour.LOCAL),
getSchema: () => globalBlockSuiteSchema,
}
).then(async workspace => {
if (typeof workspace !== 'boolean') {
options
).then(async status => {
if (typeof status !== 'boolean') {
const adapter = WorkspaceAdapters[oldMeta.flavour];
const oldWorkspace = await adapter.CRUD.get(oldMeta.id);
const newId = await adapter.CRUD.create(workspace);
const newId = await adapter.CRUD.create(status);
assertExists(
oldWorkspace,
'workspace should exist after migrate'
@@ -76,11 +77,25 @@ async function tryMigration() {
newMetadata[index] = {
...oldMeta,
id: newId,
version: WorkspaceVersion.DatabaseV3,
version: WorkspaceVersion.Surface,
};
await migrateLocalBlobStorage(workspace.id, newId);
await migrateLocalBlobStorage(status.id, newId);
console.log('workspace migrated', oldMeta.id, newId);
} else if (workspace) {
} else if (status) {
const index = newMetadata.findIndex(
meta => meta.id === oldMeta.id
);
newMetadata[index] = {
...oldMeta,
version: WorkspaceVersion.Surface,
};
const overWrite = async (doc: YDoc): Promise<void> => {
await overwriteBinary(doc.guid, encodeStateAsUpdate(doc));
return Promise.all(
[...doc.subdocs.values()].map(subdoc => overWrite(subdoc))
).then();
};
await overWrite(doc);
console.log('workspace migrated', oldMeta.id);
}
})
@@ -106,7 +121,7 @@ async function tryMigration() {
}
}
function createFirstAppData(store: ReturnType<typeof createStore>) {
export function createFirstAppData(store: ReturnType<typeof createStore>) {
const createFirst = (): RootWorkspaceMetadataV2[] => {
const Plugins = Object.values(WorkspaceAdapters).sort(
(a, b) => a.loadPriority - b.loadPriority
@@ -144,8 +159,8 @@ export async function setup(store: ReturnType<typeof createStore>) {
console.log('setup global');
setupGlobal();
createFirstAppData(store);
await tryMigration();
// do not read `rootWorkspacesMetadataAtom` before migration
await store.get(rootWorkspacesMetadataAtom);
console.log('setup done');
}

View File

@@ -0,0 +1,111 @@
import type {
AffineSocketIOProvider,
LocalIndexedDBBackgroundProvider,
SQLiteProvider,
} from '@affine/env/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { Button } from '@toeverything/components/button';
import { forceUpgradePages } from '@toeverything/infra/blocksuite';
import { useCallback, useMemo, useState } from 'react';
import type { Doc as YDoc } from 'yjs';
import { applyUpdate, encodeStateAsUpdate, encodeStateVector } from 'yjs';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
export const MigrationFallback = function MigrationFallback() {
const [done, setDone] = useState(false);
const [workspace] = useCurrentWorkspace();
const providers = workspace.blockSuiteWorkspace.providers;
const remoteProvider: AffineSocketIOProvider | undefined = useMemo(() => {
return providers.find(
(provider): provider is AffineSocketIOProvider =>
provider.flavour === 'affine-socket-io'
);
}, [providers]);
const localProvider = useMemo(() => {
const sqliteProvider = providers.find(
(provider): provider is SQLiteProvider => provider.flavour === 'sqlite'
);
const indexedDbProvider = providers.find(
(provider): provider is LocalIndexedDBBackgroundProvider =>
provider.flavour === 'local-indexeddb-background'
);
const provider = sqliteProvider || indexedDbProvider;
assertExists(provider, 'no local provider');
return provider;
}, [providers]);
const handleClick = useCallback(async () => {
setDone(false);
const downloadRecursively = async (doc: YDoc) => {
{
const docState = await localProvider.datasource.queryDocState(
doc.guid,
{
stateVector: encodeStateVector(doc),
}
);
console.log('download indexeddb', doc.guid);
if (docState) {
applyUpdate(doc, docState.missing, 'migration');
}
}
if (remoteProvider) {
{
const docState = await remoteProvider.datasource.queryDocState(
doc.guid,
{
stateVector: encodeStateVector(doc),
}
);
console.log('download remote', doc.guid);
if (docState) {
applyUpdate(doc, docState.missing, 'migration');
}
}
}
await Promise.all(
[...doc.subdocs].map(async subdoc => {
await downloadRecursively(subdoc);
})
);
{
await localProvider.datasource.sendDocUpdate(
doc.guid,
encodeStateAsUpdate(doc)
);
console.log('upload indexeddb', doc.guid);
if (remoteProvider) {
await remoteProvider.datasource.sendDocUpdate(
doc.guid,
encodeStateAsUpdate(doc)
);
console.log('upload remote', doc.guid);
}
}
};
await downloadRecursively(workspace.blockSuiteWorkspace.doc);
console.log('download done');
console.log('start migration');
await forceUpgradePages({
getCurrentRootDoc: async () => workspace.blockSuiteWorkspace.doc,
getSchema: () => workspace.blockSuiteWorkspace.schema,
});
console.log('migration done');
setDone(true);
}, [
localProvider.datasource,
remoteProvider,
workspace.blockSuiteWorkspace.doc,
workspace.blockSuiteWorkspace.schema,
]);
if (done) {
return <div>Done, please refresh the page.</div>;
}
return (
<Button data-testid="upgrade-workspace" onClick={handleClick}>
Upgrade Workspace
</Button>
);
};

View File

@@ -2,7 +2,10 @@ import { DebugLogger } from '@affine/debug';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import {
getOrCreateWorkspace,
globalBlockSuiteSchema,
} from '@affine/workspace/manager';
import { nanoid } from '@blocksuite/store';
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
import { getCurrentStore } from '@toeverything/infra/atom';
@@ -73,6 +76,7 @@ export function useAppHelper() {
WorkspaceFlavour.LOCAL
);
await buildShowcaseWorkspace(blockSuiteWorkspace, {
schema: globalBlockSuiteSchema,
store: getCurrentStore(),
atoms: {
pageMode: setPageModeAtom,

View File

@@ -47,6 +47,7 @@ import { useAppSetting } from '../atoms/settings';
import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper';
import { AppContainer } from '../components/affine/app-container';
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
import { MigrationFallback } from '../components/migration-fallback';
import type { IslandItemNames } from '../components/pure/help-island';
import { HelpIsland } from '../components/pure/help-island';
import { processCollectionsDrag } from '../components/pure/workspace-slider-bar/collections';
@@ -112,9 +113,14 @@ export const CurrentWorkspaceContext = ({
return <>{children}</>;
};
type WorkspaceLayoutProps = {
incompatible?: boolean;
};
export const WorkspaceLayout = function WorkspacesSuspense({
children,
}: PropsWithChildren) {
incompatible = false,
}: PropsWithChildren<WorkspaceLayoutProps>) {
return (
<AdapterProviderWrapper>
<CurrentWorkspaceContext>
@@ -124,14 +130,19 @@ export const WorkspaceLayout = function WorkspacesSuspense({
<CurrentWorkspaceModals />
</Suspense>
<Suspense fallback={<WorkspaceFallback />}>
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
<WorkspaceLayoutInner incompatible={incompatible}>
{children}
</WorkspaceLayoutInner>
</Suspense>
</CurrentWorkspaceContext>
</AdapterProviderWrapper>
);
};
export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
export const WorkspaceLayoutInner = ({
children,
incompatible = false,
}: PropsWithChildren<WorkspaceLayoutProps>) => {
const [currentWorkspace] = useCurrentWorkspace();
const { openPage } = useNavigateHelper();
@@ -263,7 +274,7 @@ export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
ref={setMainContainer}
padding={appSetting.clientBorder}
>
{children}
{incompatible ? <MigrationFallback /> : children}
<ToolContainer>
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
<HelpIsland showList={pageId ? undefined : showList} />

View File

@@ -17,6 +17,8 @@ const logger = new DebugLogger('index-page');
export const loader: LoaderFunction = async () => {
const rootStore = getCurrentStore();
const { createFirstAppData } = await import('../bootstrap/setup');
createFirstAppData(rootStore);
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
const lastId = localStorage.getItem('last_workspace_id');
const lastPageId = localStorage.getItem('last_page_id');
@@ -52,9 +54,5 @@ export const loader: LoaderFunction = async () => {
};
export const Component = () => {
return (
<>
<AllWorkspaceModals />
</>
);
return <AllWorkspaceModals />;
};

View File

@@ -1,18 +1,27 @@
import { WorkspaceFlavour } from '@affine/env/workspace';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
getCurrentStore,
} from '@toeverything/infra/atom';
import type { ReactElement } from 'react';
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
import {
type LoaderFunction,
Outlet,
redirect,
useLoaderData,
} from 'react-router-dom';
import { WorkspaceLayout } from '../../layouts/workspace-layout';
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
if (!meta.some(({ id }) => id === args.params.workspaceId)) {
const currentMetadata = meta.find(({ id }) => id === args.params.workspaceId);
if (!currentMetadata) {
return redirect('/404');
}
if (args.params.workspaceId) {
@@ -22,12 +31,27 @@ export const loader: LoaderFunction = async args => {
if (!args.params.pageId) {
rootStore.set(currentPageIdAtom, null);
}
if (currentMetadata.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(currentMetadata.id);
const workspace = await rootStore.get(workspaceAtom);
return (() => {
const blockVersions = workspace.meta.blockVersions;
assertExists(blockVersions, 'blockVersions should not be null');
for (const [flavour, schema] of workspace.schema.flavourSchemaMap) {
if (blockVersions[flavour] !== schema.version) {
return true;
}
}
return false;
})();
}
return null;
};
export const Component = (): ReactElement => {
const incompatible = useLoaderData();
return (
<WorkspaceLayout>
<WorkspaceLayout incompatible={!!incompatible}>
<Outlet />
</WorkspaceLayout>
);

View File

@@ -10,12 +10,12 @@
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/blocks": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/editor": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/global": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/lit": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/store": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/block-std": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/blocks": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/editor": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/global": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/lit": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/store": "0.0.0-20230905170607-94acf22c-nightly",
"express": "^4.18.2",
"jotai": "^2.4.1",
"react": "18.3.0-canary-7118f5dd7-20230705",

View File

@@ -29,10 +29,10 @@
"@affine/env": "workspace:*",
"@affine/native": "workspace:*",
"@affine/sdk": "workspace:*",
"@blocksuite/blocks": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/editor": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/lit": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/store": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/blocks": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/editor": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/lit": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/store": "0.0.0-20230905170607-94acf22c-nightly",
"@electron-forge/cli": "^6.4.1",
"@electron-forge/core": "^6.4.1",
"@electron-forge/core-utils": "^6.4.1",

View File

@@ -3,8 +3,9 @@ import {
SqliteConnection,
ValidationResult,
} from '@affine/native';
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
import { migrateToLatestDatabase } from '../db/migration';
import { migrateToLatest } from '../db/migration';
import { logger } from '../logger';
/**
@@ -20,10 +21,14 @@ export abstract class BaseSQLiteAdapter {
if (!this.db) {
const validation = await SqliteConnection.validate(this.path);
if (validation === ValidationResult.MissingVersionColumn) {
await migrateToLatestDatabase(this.path);
await migrateToLatest(this.path, WorkspaceVersion.SubDoc);
}
this.db = new SqliteConnection(this.path);
await this.db.connect();
const maxVersion = await this.db.getMaxVersion();
if (maxVersion !== WorkspaceVersion.Surface) {
await migrateToLatest(this.path, WorkspaceVersion.Surface);
}
logger.info(`[SQLiteAdapter:${this.role}]`, 'connected:', this.path);
}
return this.db;

View File

@@ -2,7 +2,10 @@ import { equal } from 'node:assert';
import { resolve } from 'node:path';
import { SqliteConnection } from '@affine/native';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { Schema } from '@blocksuite/store';
import {
forceUpgradePages,
migrateToSubdoc,
WorkspaceVersion,
} from '@toeverything/infra/blocksuite';
@@ -34,15 +37,19 @@ export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
await db.close();
};
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { Schema, Workspace } from '@blocksuite/store';
import { migrateWorkspace } from '@toeverything/infra/blocksuite';
// v1 v2 -> v3
export const migrateToLatestDatabase = async (path: string) => {
// v3 -> v4
export const migrateToLatest = async (
path: string,
version: WorkspaceVersion
) => {
const connection = new SqliteConnection(path);
await connection.connect();
await connection.initVersion();
if (version === WorkspaceVersion.SubDoc) {
await connection.initVersion();
} else {
await connection.setVersion(version);
}
const schema = new Schema();
schema.register(AffineSchemas).register(__unstableSchemas);
const rootDoc = new YDoc();
@@ -69,24 +76,11 @@ export const migrateToLatestDatabase = async (path: string) => {
);
};
await downloadBinary(rootDoc, true);
const result = await migrateWorkspace(WorkspaceVersion.SubDoc, {
const result = await forceUpgradePages({
getSchema: () => schema,
getCurrentRootDoc: () => Promise.resolve(rootDoc),
createWorkspace: () =>
Promise.resolve(
new Workspace({
id: nanoid(10),
schema,
blobStorages: [],
providerCreators: [],
})
),
});
equal(
typeof result,
'boolean',
'migrateWorkspace should return boolean value'
);
equal(result, true, 'migrateWorkspace should return boolean value');
const uploadBinary = async (doc: YDoc, isRoot: boolean) => {
await connection.replaceUpdates(doc.guid, [
{ docId: isRoot ? undefined : doc.guid, data: encodeStateAsUpdate(doc) },

View File

@@ -1,6 +1,7 @@
import path from 'node:path';
import { ValidationResult } from '@affine/native';
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
import type {
FakeDialogResult,
LoadDBFileResult,
@@ -14,7 +15,7 @@ import { nanoid } from 'nanoid';
import { ensureSQLiteDB } from '../db/ensure-db';
import {
copyToTemp,
migrateToLatestDatabase,
migrateToLatest,
migrateToSubdocAndReplaceDatabase,
} from '../db/migration';
import type { WorkspaceSQLiteDB } from '../db/workspace-db-adapter';
@@ -204,7 +205,7 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
if (validationResult === ValidationResult.MissingVersionColumn) {
try {
const tmpDBPath = await copyToTemp(originalPath);
await migrateToLatestDatabase(tmpDBPath);
await migrateToLatest(tmpDBPath, WorkspaceVersion.SubDoc);
originalPath = tmpDBPath;
} catch (error) {
logger.warn(
@@ -223,6 +224,24 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
return { error: 'DB_FILE_INVALID' }; // invalid db file
}
const db = new SqliteConnection(originalPath);
try {
await db.connect();
if ((await db.getMaxVersion()) === WorkspaceVersion.DatabaseV3) {
const tmpDBPath = await copyToTemp(originalPath);
await migrateToLatest(tmpDBPath, WorkspaceVersion.SubDoc);
originalPath = tmpDBPath;
}
} catch (error) {
logger.warn(
`loadDBFile, migration version column failed: ${originalPath}`,
error
);
return { error: 'DB_FILE_MIGRATION_FAILED' };
} finally {
await db.close();
}
// copy the db file to a new workspace id
const workspaceId = nanoid(10);
const internalFilePath = await getWorkspaceDBPath(workspaceId);

View File

@@ -18,13 +18,13 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/blocks": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/editor": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/global": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/block-std": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/blocks": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/editor": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/global": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/icons": "^2.1.32",
"@blocksuite/lit": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/store": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/lit": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/store": "0.0.0-20230905170607-94acf22c-nightly",
"@toeverything/hooks": "workspace:*",
"@toeverything/y-indexeddb": "workspace:*",
"react": "^18.2.0",

View File

@@ -31,13 +31,13 @@
"wait-on": "^7.0.1"
},
"devDependencies": {
"@blocksuite/block-std": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/blocks": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/editor": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/global": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/block-std": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/blocks": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/editor": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/global": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/icons": "^2.1.32",
"@blocksuite/lit": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/store": "0.0.0-20230829150056-df43987c-nightly",
"@blocksuite/lit": "0.0.0-20230905170607-94acf22c-nightly",
"@blocksuite/store": "0.0.0-20230905170607-94acf22c-nightly",
"@tomfreudenberg/next-auth-mock": "^0.5.6",
"chromatic": "^6.24.1",
"react": "18.2.0",