feat: remove workspace & cloud sync

This commit is contained in:
DarkSky 2023-01-02 19:55:38 +08:00 committed by DarkSky
parent 7fea77b64f
commit c0626cf8ac
13 changed files with 101 additions and 30 deletions

View File

@ -20,7 +20,7 @@ const DynamicBlocksuite = ({
new Promise(async resolve => {
if (workspaceId) {
const workspace = await getDataCenter().then(dc =>
dc.initWorkspace(workspaceId, 'affine')
dc.getWorkspace(workspaceId, 'affine')
);
resolve(workspace);

View File

@ -13,18 +13,18 @@ export class DataCenter {
private readonly _config;
private readonly _logger;
static async init(): Promise<DataCenter> {
const dc = new DataCenter();
static async init(debug: boolean): Promise<DataCenter> {
const dc = new DataCenter(debug);
dc.addProvider(AffineProvider);
dc.addProvider(LocalProvider);
return dc;
}
private constructor() {
private constructor(debug: boolean) {
this._config = getKVConfigure('sys');
this._logger = getLogger('dc');
this._logger.enabled = true;
this._logger.enabled = debug;
}
private addProvider(provider: typeof BaseProvider) {
@ -43,7 +43,7 @@ export class DataCenter {
throw Error(`Provider ${providerId} not found`);
}
private async _initWorkspace(id: string, pid: string): Promise<BaseProvider> {
private async _getWorkspace(id: string, pid: string): Promise<BaseProvider> {
this._logger(`Init workspace ${id} with ${pid}`);
const providerId = await this._getProvider(id, pid);
@ -57,6 +57,7 @@ export class DataCenter {
await provider.init({
config: getKVConfigure(id),
debug: this._logger.enabled,
logger: this._logger.extend(`${Provider.id}:${id}`),
workspace,
});
@ -66,13 +67,13 @@ export class DataCenter {
return provider;
}
async initWorkspace(
async getWorkspace(
id: string,
provider = 'local'
): Promise<Workspace | null> {
if (id) {
if (!this._workspaces.has(id)) {
this._workspaces.set(id, this._initWorkspace(id, provider));
this._workspaces.set(id, this._getWorkspace(id, provider));
}
const workspace = this._workspaces.get(id);
assert(workspace);
@ -86,10 +87,24 @@ export class DataCenter {
return config.set(key, value);
}
async getWorkspaceList() {
async listWorkspace() {
const keys = await this._config.keys();
return keys
.filter(k => k.startsWith('workspace:'))
.map(k => k.split(':')[1]);
}
async removeWorkspace(id: string) {
await this._config.delete(`workspace:${id}:provider`);
const provider = await this._workspaces.get(id);
if (provider) {
this._workspaces.delete(id);
await provider.clear();
}
}
async clearWorkspaces() {
const workspaces = await this.listWorkspace();
await Promise.all(workspaces.map(id => this.removeWorkspace(id)));
}
}

View File

@ -4,9 +4,9 @@ import { DataCenter } from './datacenter.js';
const _initializeDataCenter = () => {
let _dataCenterInstance: Promise<DataCenter>;
return () => {
return (debug = true) => {
if (!_dataCenterInstance) {
_dataCenterInstance = DataCenter.init();
_dataCenterInstance = DataCenter.init(debug);
}
return _dataCenterInstance;

View File

@ -5,11 +5,13 @@ import type { InitialParams } from '../index.js';
import { LocalProvider } from '../local/index.js';
import { downloadWorkspace } from './apis.js';
import { WebsocketProvider } from './sync.js';
import { token, Callback } from './token.js';
export class AffineProvider extends LocalProvider {
static id = 'affine';
private _onTokenRefresh?: Callback = undefined;
private _ws?: WebsocketProvider;
constructor() {
super();
@ -64,7 +66,21 @@ export class AffineProvider extends LocalProvider {
try {
const updates = await downloadWorkspace(workspace.room);
if (updates) {
applyUpdate(doc, new Uint8Array(updates));
await new Promise(resolve => {
doc.once('update', resolve);
applyUpdate(doc, new Uint8Array(updates));
});
// TODO: wait util data loaded
this._ws = new WebsocketProvider('/', workspace.room, doc);
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
await new Promise<void>((resolve, reject) => {
// TODO: synced will also be triggered on reconnection after losing sync
// There needs to be an event mechanism to emit the synchronization state to the upper layer
assert(this._ws);
this._ws.once('synced', () => resolve());
this._ws.once('lost-connection', () => resolve());
this._ws.once('connection-error', () => reject());
});
}
} catch (e) {
this._logger('Failed to init cloud workspace', e);

View File

@ -17,7 +17,12 @@ export class BaseProvider {
this._config = params.config;
this._logger = params.logger;
this._workspace = params.workspace;
this._logger.enabled = true;
this._logger.enabled = params.debug;
}
async clear() {
await this.destroy();
await this._config.clear();
}
async destroy() {

View File

@ -7,6 +7,7 @@ export type Logger = ReturnType<typeof getLogger>;
export type InitialParams = {
config: ConfigStore;
debug: boolean;
logger: Logger;
workspace: Workspace;
};

View File

@ -33,6 +33,12 @@ export class LocalProvider extends BaseProvider {
this._logger('Local data loaded');
}
async clear() {
await super.clear();
await this._blobs.clear();
this._idb?.clearData();
}
async destroy(): Promise<void> {
super.destroy();
if (this._idb) {

View File

@ -1,10 +1,11 @@
import { createStore, del, get, keys, set } from 'idb-keyval';
import { createStore, del, get, keys, set, clear } from 'idb-keyval';
export type ConfigStore<T = any> = {
get: (key: string) => Promise<T | undefined>;
set: (key: string, value: T) => Promise<void>;
keys: () => Promise<string[]>;
delete: (key: string) => Promise<void>;
clear: () => Promise<void>;
};
const initialIndexedDB = <T = any>(database: string): ConfigStore<T> => {
@ -14,6 +15,7 @@ const initialIndexedDB = <T = any>(database: string): ConfigStore<T> => {
set: (key: string, value: T) => set(key, value, store),
keys: () => keys(store),
delete: (key: string) => del(key, store),
clear: () => clear(store),
};
};
@ -34,7 +36,16 @@ const scopedIndexedDB = () => {
.filter(k => k.startsWith(`${scope}:`))
.map(k => k.replace(`${scope}:`, ''))
),
delete: (key: string) => del(`${scope}:${key}`),
delete: (key: string) => idb.delete(`${scope}:${key}`),
clear: async () => {
await idb
.keys()
.then(keys =>
Promise.all(
keys.filter(k => k.startsWith(`${scope}:`)).map(k => del(k))
)
);
},
};
storeCache.set(scope, store);

View File

@ -1,6 +1,5 @@
export { signInWithGoogle, onAuthStateChanged } from './auth';
export * from './sdks';
export * from './websocket';
export { getDataCenter } from './datacenter';

View File

@ -1 +0,0 @@
export { WebsocketProvider } from './y-websocket';

View File

@ -7,43 +7,61 @@ import 'fake-indexeddb/auto';
test('init data center', async () => {
const dataCenter = await getDataCenter();
expect(dataCenter).toBeTruthy();
await dataCenter.clearWorkspaces();
const workspace = await dataCenter.initWorkspace('test');
const workspace = await dataCenter.getWorkspace('test1');
expect(workspace).toBeTruthy();
});
test('should init error with unknown provider', async () => {
const dataCenter = await getDataCenter();
await dataCenter.clearWorkspaces();
test.fail();
await dataCenter.initWorkspace('test', 'not exist provider');
await dataCenter.getWorkspace('test2', 'not exist provider');
});
test.skip('init affine provider', async () => {
const dataCenter = await getDataCenter();
await dataCenter.clearWorkspaces();
// TODO: set constant token for testing
await dataCenter.setWorkspaceConfig('6', 'token', 'YOUR_TOKEN');
const workspace = await dataCenter.initWorkspace('6', 'affine');
const workspace = await dataCenter.getWorkspace('6', 'affine');
expect(workspace).toBeTruthy();
});
test('list workspaces', async () => {
const dataCenter = await getDataCenter();
await dataCenter.clearWorkspaces();
await Promise.all([
dataCenter.initWorkspace('test1'),
dataCenter.initWorkspace('test2'),
dataCenter.initWorkspace('test3'),
dataCenter.initWorkspace('test4'),
dataCenter.getWorkspace('test3'),
dataCenter.getWorkspace('test4'),
dataCenter.getWorkspace('test5'),
dataCenter.getWorkspace('test6'),
]);
expect(await dataCenter.getWorkspaceList()).toStrictEqual([
'test1',
'test2',
expect(await dataCenter.listWorkspace()).toStrictEqual([
'test3',
'test4',
'test5',
'test6',
]);
});
test('remove workspaces', async () => {
const dataCenter = await getDataCenter();
await dataCenter.clearWorkspaces();
await Promise.all([
dataCenter.getWorkspace('test7'),
dataCenter.getWorkspace('test8'),
]);
await dataCenter.removeWorkspace('test7');
expect(await dataCenter.listWorkspace()).toStrictEqual(['test8']);
});

View File

@ -1,4 +1,5 @@
export const getDataCenter = () =>
import('../src/datacenter/index.js').then(async dataCenter =>
dataCenter.getDataCenter()
export const getDataCenter = () => {
return import('../src/datacenter/index.js').then(async dataCenter =>
dataCenter.getDataCenter(false)
);
};