refactor: move local workspace CRUD (#1778)

This commit is contained in:
Himself65
2023-04-02 01:34:57 -05:00
committed by GitHub
parent 5e56728dbc
commit 60324b8967
11 changed files with 224 additions and 153 deletions
+7 -17
View File
@@ -1,12 +1,13 @@
import { config } from '@affine/env';
import {
createIndexedDBProvider,
createLocalProviders,
} from '@affine/workspace/providers';
import { createBroadCastChannelProvider } from '@affine/workspace/providers';
import type { Provider } from '@affine/workspace/type';
import type { BlockSuiteWorkspace } from '../shared';
import {
createAffineWebSocketProvider,
createBroadCastChannelProvider,
createIndexedDBProvider,
} from './providers';
import { createAffineWebSocketProvider } from './providers';
import { createAffineDownloadProvider } from './providers/affine';
export const createAffineProviders = (
@@ -24,15 +25,4 @@ export const createAffineProviders = (
).filter(v => Boolean(v));
};
export const createLocalProviders = (
blockSuiteWorkspace: BlockSuiteWorkspace
): Provider[] => {
return (
[
config.enableBroadCastChannelProvider &&
createBroadCastChannelProvider(blockSuiteWorkspace),
config.enableIndexedDBProvider &&
createIndexedDBProvider(blockSuiteWorkspace),
] as any[]
).filter(v => Boolean(v));
};
export { createLocalProviders };
+2 -49
View File
@@ -1,15 +1,10 @@
import { KeckProvider } from '@affine/workspace/affine/keck';
import { getLoginStorage } from '@affine/workspace/affine/login';
import type {
AffineWebSocketProvider,
LocalIndexedDBProvider,
} from '@affine/workspace/type';
import type { AffineWebSocketProvider } from '@affine/workspace/type';
import { assertExists } from '@blocksuite/store';
import { IndexeddbPersistence } from 'y-indexeddb';
import type { BlockSuiteWorkspace } from '../../shared';
import { providerLogger } from '../logger';
import { createBroadCastChannelProvider } from './broad-cast-channel';
const createAffineWebSocketProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
@@ -52,46 +47,4 @@ const createAffineWebSocketProvider = (
};
};
const createIndexedDBProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): LocalIndexedDBProvider => {
let indexeddbProvider: IndexeddbPersistence | null = null;
const callbacks = new Set<() => void>();
return {
flavour: 'local-indexeddb',
callbacks,
// fixme: remove background long polling
background: true,
cleanup: () => {
assertExists(indexeddbProvider);
indexeddbProvider.clearData();
callbacks.clear();
indexeddbProvider = null;
},
connect: () => {
providerLogger.info('connect indexeddb provider', blockSuiteWorkspace.id);
indexeddbProvider = new IndexeddbPersistence(
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc
);
indexeddbProvider.whenSynced.then(() => {
callbacks.forEach(cb => cb());
});
},
disconnect: () => {
assertExists(indexeddbProvider);
providerLogger.info(
'disconnect indexeddb provider',
blockSuiteWorkspace.id
);
indexeddbProvider.destroy();
indexeddbProvider = null;
},
};
};
export {
createAffineWebSocketProvider,
createBroadCastChannelProvider,
createIndexedDBProvider,
};
export { createAffineWebSocketProvider };
+1 -1
View File
@@ -1,5 +1,6 @@
import { Button } from '@affine/component';
import { DebugLogger } from '@affine/debug';
import { createBroadCastChannelProvider } from '@affine/workspace/providers';
import type { BroadCastChannelProvider } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { nanoid } from '@blocksuite/store';
@@ -7,7 +8,6 @@ import { Typography } from '@mui/material';
import type React from 'react';
import { useEffect, useMemo, useState } from 'react';
import { createBroadCastChannelProvider } from '../../blocksuite/providers';
import PageList from '../../components/blocksuite/block-suite-page-list/page-list';
import { StyledPage, StyledWrapper } from '../../layouts/styles';
import { toast } from '../../utils';
+2 -81
View File
@@ -1,97 +1,18 @@
import type { LocalWorkspace } from '@affine/workspace/type';
import { CRUD } from '@affine/workspace/local/crud';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { nanoid } from '@blocksuite/store';
import { createJSONStorage } from 'jotai/utils';
import React from 'react';
import { IndexeddbPersistence } from 'y-indexeddb';
import { z } from 'zod';
import { createLocalProviders } from '../../blocksuite';
import { PageNotFoundError } from '../../components/affine/affine-error-eoundary';
import { WorkspaceSettingDetail } from '../../components/affine/workspace-setting-detail';
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
import { PageDetailEditor } from '../../components/page-detail-editor';
import { BlockSuiteWorkspace } from '../../shared';
import { initPage } from '../../utils';
import type { WorkspacePlugin } from '..';
const getStorage = () => createJSONStorage(() => localStorage);
export const kStoreKey = 'affine-local-workspace';
const schema = z.array(z.string());
export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
flavour: WorkspaceFlavour.LOCAL,
loadPriority: LoadPriority.LOW,
CRUD: {
get: async workspaceId => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const id = data.find(id => id === workspaceId);
if (!id) {
return null;
}
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
id,
(_: string) => undefined
);
const workspace: LocalWorkspace = {
id,
flavour: WorkspaceFlavour.LOCAL,
blockSuiteWorkspace: blockSuiteWorkspace,
providers: [...createLocalProviders(blockSuiteWorkspace)],
};
return workspace;
},
create: async ({ doc }) => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const binary = BlockSuiteWorkspace.Y.encodeStateAsUpdateV2(doc);
const id = nanoid();
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
id,
(_: string) => undefined
);
BlockSuiteWorkspace.Y.applyUpdateV2(blockSuiteWorkspace.doc, binary);
const persistence = new IndexeddbPersistence(id, blockSuiteWorkspace.doc);
await persistence.whenSynced.then(() => {
persistence.destroy();
});
storage.setItem(kStoreKey, [...data, id]);
console.log('create', id, storage.getItem(kStoreKey));
return id;
},
delete: async workspace => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const idx = data.findIndex(id => id === workspace.id);
if (idx === -1) {
throw new Error('workspace not found');
}
data.splice(idx, 1);
storage.setItem(kStoreKey, [...data]);
},
list: async () => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = (
await Promise.all(
(storage.getItem(kStoreKey) as z.infer<typeof schema>).map(id =>
LocalPlugin.CRUD.get(id)
)
)
).filter(item => item !== null) as LocalWorkspace[];
return data;
},
},
CRUD,
UI: {
PageDetail: ({ currentWorkspace, currentPageId }) => {
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
+3
View File
@@ -5,6 +5,8 @@
"./atom": "./src/atom.ts",
"./utils": "./src/utils.ts",
"./type": "./src/type.ts",
"./local/crud": "./src/local/crud.ts",
"./providers": "./src/providers/index.ts",
"./affine/*": "./src/affine/*.ts",
"./affine/api": "./src/affine/api/index.ts",
"./affine/sync": "./src/affine/sync.js",
@@ -24,6 +26,7 @@
"lib0": "^0.2.73",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"y-indexeddb": "^9.0.10",
"y-protocols": "^1.0.5",
"yjs": "^13.5.51",
"zod": "^3.21.4"
+82
View File
@@ -0,0 +1,82 @@
import { nanoid, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import { createJSONStorage } from 'jotai/utils';
import { IndexeddbPersistence } from 'y-indexeddb';
import { z } from 'zod';
import { createLocalProviders } from '../providers';
import type { LocalWorkspace, WorkspaceCRUD } from '../type';
import { WorkspaceFlavour } from '../type';
import { createEmptyBlockSuiteWorkspace } from '../utils';
const getStorage = () => createJSONStorage(() => localStorage);
const kStoreKey = 'affine-local-workspace';
const schema = z.array(z.string());
export const CRUD: WorkspaceCRUD<WorkspaceFlavour.LOCAL> = {
get: async workspaceId => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const id = data.find(id => id === workspaceId);
if (!id) {
return null;
}
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
id,
(_: string) => undefined
);
const workspace: LocalWorkspace = {
id,
flavour: WorkspaceFlavour.LOCAL,
blockSuiteWorkspace: blockSuiteWorkspace,
providers: [...createLocalProviders(blockSuiteWorkspace)],
};
return workspace;
},
create: async ({ doc }) => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const binary = BlockSuiteWorkspace.Y.encodeStateAsUpdateV2(doc);
const id = nanoid();
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
id,
(_: string) => undefined
);
BlockSuiteWorkspace.Y.applyUpdateV2(blockSuiteWorkspace.doc, binary);
const persistence = new IndexeddbPersistence(id, blockSuiteWorkspace.doc);
await persistence.whenSynced.then(() => {
persistence.destroy();
});
storage.setItem(kStoreKey, [...data, id]);
console.log('create', id, storage.getItem(kStoreKey));
return id;
},
delete: async workspace => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
const data = storage.getItem(kStoreKey) as z.infer<typeof schema>;
const idx = data.findIndex(id => id === workspace.id);
if (idx === -1) {
throw new Error('workspace not found');
}
data.splice(idx, 1);
storage.setItem(kStoreKey, [...data]);
},
list: async () => {
const storage = getStorage();
!Array.isArray(storage.getItem(kStoreKey)) &&
storage.setItem(kStoreKey, []);
return (
await Promise.all(
(storage.getItem(kStoreKey) as z.infer<typeof schema>).map(id =>
CRUD.get(id)
)
)
).filter(item => item !== null) as LocalWorkspace[];
},
};
@@ -1,4 +1,4 @@
import type { BroadCastChannelProvider } from '@affine/workspace/type';
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store';
import type { Awareness } from 'y-protocols/awareness';
import {
@@ -6,8 +6,8 @@ import {
encodeAwarenessUpdate,
} from 'y-protocols/awareness';
import { BlockSuiteWorkspace } from '../../../shared';
import { providerLogger } from '../../logger';
import type { BroadCastChannelProvider } from '../../type';
import { localProviderLogger } from '../logger';
import type {
AwarenessChanges,
BroadcastChannelMessageEvent,
@@ -86,7 +86,10 @@ export const createBroadCastChannelProvider = (
onmessage: handleBroadcastChannelMessage,
}
);
providerLogger.info('connect broadcast channel', blockSuiteWorkspace.id);
localProviderLogger.info(
'connect broadcast channel',
blockSuiteWorkspace.id
);
const docDiff = Y.encodeStateVector(doc);
broadcastChannel.postMessage(['doc:diff', docDiff, awareness.clientID]);
const docUpdateV2 = Y.encodeStateAsUpdate(doc);
@@ -101,7 +104,7 @@ export const createBroadCastChannelProvider = (
},
disconnect: () => {
assertExists(broadcastChannel);
providerLogger.info(
localProviderLogger.info(
'disconnect broadcast channel',
blockSuiteWorkspace.id
);
+115
View File
@@ -0,0 +1,115 @@
import { config } from '@affine/env';
import { KeckProvider } from '@affine/workspace/affine/keck';
import { getLoginStorage } from '@affine/workspace/affine/login';
import type { Provider } from '@affine/workspace/type';
import type {
AffineWebSocketProvider,
LocalIndexedDBProvider,
} from '@affine/workspace/type';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store';
import { IndexeddbPersistence } from 'y-indexeddb';
import { createBroadCastChannelProvider } from './broad-cast-channel';
import { localProviderLogger } from './logger';
const createAffineWebSocketProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): AffineWebSocketProvider => {
let webSocketProvider: KeckProvider | null = null;
return {
flavour: 'affine-websocket',
background: false,
cleanup: () => {
assertExists(webSocketProvider);
webSocketProvider.destroy();
webSocketProvider = null;
},
connect: () => {
const wsUrl = `${
window.location.protocol === 'https:' ? 'wss' : 'ws'
}://${window.location.host}/api/sync/`;
webSocketProvider = new KeckProvider(
wsUrl,
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc,
{
params: { token: getLoginStorage()?.token ?? '' },
// @ts-expect-error ignore the type
awareness: blockSuiteWorkspace.awarenessStore.awareness,
// we maintain broadcast channel by ourselves
disableBc: true,
connect: false,
}
);
localProviderLogger.info('connect', webSocketProvider.url);
webSocketProvider.connect();
},
disconnect: () => {
assertExists(webSocketProvider);
localProviderLogger.info('disconnect', webSocketProvider.url);
webSocketProvider.destroy();
webSocketProvider = null;
},
};
};
const createIndexedDBProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): LocalIndexedDBProvider => {
let indexeddbProvider: IndexeddbPersistence | null = null;
const callbacks = new Set<() => void>();
return {
flavour: 'local-indexeddb',
callbacks,
// fixme: remove background long polling
background: true,
cleanup: () => {
assertExists(indexeddbProvider);
indexeddbProvider.clearData();
callbacks.clear();
indexeddbProvider = null;
},
connect: () => {
localProviderLogger.info(
'connect indexeddb provider',
blockSuiteWorkspace.id
);
indexeddbProvider = new IndexeddbPersistence(
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc
);
indexeddbProvider.whenSynced.then(() => {
callbacks.forEach(cb => cb());
});
},
disconnect: () => {
assertExists(indexeddbProvider);
localProviderLogger.info(
'disconnect indexeddb provider',
blockSuiteWorkspace.id
);
indexeddbProvider.destroy();
indexeddbProvider = null;
},
};
};
export {
createAffineWebSocketProvider,
createBroadCastChannelProvider,
createIndexedDBProvider,
};
export const createLocalProviders = (
blockSuiteWorkspace: BlockSuiteWorkspace
): Provider[] => {
return (
[
config.enableBroadCastChannelProvider &&
createBroadCastChannelProvider(blockSuiteWorkspace),
config.enableIndexedDBProvider &&
createIndexedDBProvider(blockSuiteWorkspace),
] as any[]
).filter(v => Boolean(v));
};
@@ -0,0 +1,3 @@
import { DebugLogger } from '@affine/debug';
export const localProviderLogger = new DebugLogger('local-provider');
+1
View File
@@ -254,6 +254,7 @@ __metadata:
react: ^18.2.0
react-dom: ^18.2.0
ws: ^8.13.0
y-indexeddb: ^9.0.10
y-protocols: ^1.0.5
yjs: ^13.5.51
zod: ^3.21.4