From 43e52fffe74f8a9e3d2b7e769ab181407bf220fc Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 3 Jan 2023 22:22:37 +0800 Subject: [PATCH] feat: workspace list by provider --- packages/data-center/src/datacenter.ts | 100 ++++++++++++------ packages/data-center/src/provider/base.ts | 14 +++ packages/data-center/src/provider/index.ts | 3 +- .../data-center/src/provider/local/index.ts | 12 ++- packages/data-center/src/store.ts | 34 ++++-- packages/data-center/tests/datacenter.spec.ts | 22 ++-- 6 files changed, 138 insertions(+), 47 deletions(-) diff --git a/packages/data-center/src/datacenter.ts b/packages/data-center/src/datacenter.ts index fc836c19ac..47bf844f99 100644 --- a/packages/data-center/src/datacenter.ts +++ b/packages/data-center/src/datacenter.ts @@ -46,8 +46,11 @@ export class DataCenter { this._providers.set(provider.id, provider); } - private async _getProvider(id: string, providerId: string): Promise { - const providerKey = `workspace:${id}:provider`; + private async _getProvider( + id: string, + providerId = 'local' + ): Promise { + const providerKey = `${id}:provider`; if (this._providers.has(providerId)) { await this._config.set(providerKey, providerId); return providerId; @@ -58,21 +61,32 @@ export class DataCenter { throw Error(`Provider ${providerId} not found`); } - private async _getWorkspace(id: string, pid: string): Promise { - this._logger(`Init workspace ${id} with ${pid}`); + private async _getWorkspace( + id: string, + params: LoadConfig + ): Promise { + this._logger(`Init workspace ${id} with ${params.providerId}`); - const providerId = await this._getProvider(id, pid); + const providerId = await this._getProvider(id, params.providerId); // init workspace & register block schema const workspace = new Workspace({ room: id }).register(BlockSchema); const Provider = this._providers.get(providerId); assert(Provider); - const provider = new Provider(); + // initial configurator + const config = getKVConfigure(`workspace:${id}`); + // set workspace configs + const values = Object.entries(params.config || {}); + if (values.length) await config.setMany(values); + + // init data by provider + const provider = new Provider(); await provider.init({ apis: this._apis, - config: getKVConfigure(id), + config, + globalConfig: getKVConfigure(`provider:${providerId}`), debug: this._logger.enabled, logger: this._logger.extend(`${Provider.id}:${id}`), workspace, @@ -83,27 +97,22 @@ export class DataCenter { return provider; } - private async _setConfig(workspace: string, config: Record) { - const values = Object.entries(config); - if (values.length) { - const configure = getKVConfigure(workspace); - await configure.setMany(values); - } - } - - // load workspace data to memory + /** + * load workspace data to memory + * @param workspaceId workspace id + * @param config.providerId provider id + * @param config.config provider config + * @returns Workspace instance + */ async load( workspaceId: string, params: LoadConfig = {} ): Promise { - const { providerId = 'local', config = {} } = params; if (workspaceId) { if (!this._workspaces.has(workspaceId)) { this._workspaces.set( workspaceId, - this._setConfig(workspaceId, config).then(() => - this._getWorkspace(workspaceId, providerId) - ) + this._getWorkspace(workspaceId, params) ); } const workspace = this._workspaces.get(workspaceId); @@ -113,7 +122,10 @@ export class DataCenter { return null; } - // destroy workspace's instance in memory + /** + * destroy workspace's instance in memory + * @param workspaceId workspace id + */ async destroy(workspaceId: string) { const provider = await this._workspaces.get(workspaceId); if (provider) { @@ -122,7 +134,13 @@ export class DataCenter { } } - // reload new workspace instance to memory to refresh config + /** + * reload new workspace instance to memory to refresh config + * @param workspaceId workspace id + * @param config.providerId provider id + * @param config.config provider config + * @returns Workspace instance + */ async reload( workspaceId: string, config: LoadConfig = {} @@ -131,16 +149,34 @@ export class DataCenter { return this.load(workspaceId, config); } - async list() { - const keys = await this._config.keys(); - return keys - .filter(k => k.startsWith('workspace:')) - .map(k => k.split(':')[1]); + /** + * get workspace list + */ + async list(): Promise>> { + const lists = await Promise.all( + Array.from(this._providers.entries()).map(([providerId, provider]) => + provider + .list(getKVConfigure(`provider:${providerId}`)) + .then(list => [providerId, list || []] as const) + ) + ); + + return lists.reduce((ret, [providerId, list]) => { + for (const [item, isLocal] of list) { + const workspace = ret[item] || {}; + workspace[providerId] = isLocal; + ret[item] = workspace; + } + return ret; + }, {} as Record>); } - // delete local workspace's data + /** + * delete local workspace's data + * @param workspaceId workspace id + */ async delete(workspaceId: string) { - await this._config.delete(`workspace:${workspaceId}:provider`); + await this._config.delete(`${workspaceId}:provider`); const provider = await this._workspaces.get(workspaceId); if (provider) { this._workspaces.delete(workspaceId); @@ -149,9 +185,11 @@ export class DataCenter { } } - // clear all local workspace's data + /** + * clear all local workspace's data + */ async clear() { const workspaces = await this.list(); - await Promise.all(workspaces.map(id => this.delete(id))); + await Promise.all(Object.keys(workspaces).map(id => this.delete(id))); } } diff --git a/packages/data-center/src/provider/base.ts b/packages/data-center/src/provider/base.ts index ccd67bb2ad..95bd309a1c 100644 --- a/packages/data-center/src/provider/base.ts +++ b/packages/data-center/src/provider/base.ts @@ -7,6 +7,7 @@ export class BaseProvider { static id = 'base'; protected _apis!: Readonly; protected _config!: Readonly; + protected _globalConfig!: Readonly; protected _logger!: Logger; protected _workspace!: Workspace; @@ -14,9 +15,14 @@ export class BaseProvider { // Nothing to do here } + get id(): string { + return (this.constructor as any).id; + } + async init(params: InitialParams) { this._apis = params.apis; this._config = params.config; + this._globalConfig = params.globalConfig; this._logger = params.logger; this._workspace = params.workspace; this._logger.enabled = params.debug; @@ -48,4 +54,12 @@ export class BaseProvider { get workspace() { return this._workspace; } + + // get workspace list,return a map of workspace id and boolean + // if value is true, it exists locally, otherwise it does not exist locally + static async list( + _config: Readonly + ): Promise | undefined> { + throw Error('Not implemented: list'); + } } diff --git a/packages/data-center/src/provider/index.ts b/packages/data-center/src/provider/index.ts index 06b69263de..f3fc53f9ad 100644 --- a/packages/data-center/src/provider/index.ts +++ b/packages/data-center/src/provider/index.ts @@ -8,7 +8,8 @@ export type Logger = ReturnType; export type InitialParams = { apis: Apis; - config: ConfigStore; + config: Readonly; + globalConfig: Readonly; debug: boolean; logger: Logger; workspace: Workspace; diff --git a/packages/data-center/src/provider/local/index.ts b/packages/data-center/src/provider/local/index.ts index 363b50b89e..d2ced1b9ee 100644 --- a/packages/data-center/src/provider/local/index.ts +++ b/packages/data-center/src/provider/local/index.ts @@ -1,7 +1,7 @@ import type { BlobStorage } from '@blocksuite/store'; import assert from 'assert'; -import type { InitialParams } from '../index.js'; +import type { ConfigStore, InitialParams } from '../index.js'; import { BaseProvider } from '../base.js'; import { IndexedDBProvider } from './indexeddb.js'; @@ -31,12 +31,15 @@ export class LocalProvider extends BaseProvider { await this._idb.whenSynced; this._logger('Local data loaded'); + + await this._globalConfig.set(this._workspace.room, true); } async clear() { await super.clear(); await this._blobs.clear(); await this._idb?.clearData(); + await this._globalConfig.delete(this._workspace.room!); } async destroy(): Promise { @@ -51,4 +54,11 @@ export class LocalProvider extends BaseProvider { async setBlob(blob: Blob): Promise { return this._blobs.set(blob); } + + static async list( + config: Readonly> + ): Promise | undefined> { + const entries = await config.entries(); + return new Map(entries); + } } diff --git a/packages/data-center/src/store.ts b/packages/data-center/src/store.ts index b8dd831f95..4ba2adada1 100644 --- a/packages/data-center/src/store.ts +++ b/packages/data-center/src/store.ts @@ -1,10 +1,20 @@ -import { createStore, del, get, keys, set, setMany, clear } from 'idb-keyval'; +import { + createStore, + del, + get, + keys, + set, + setMany, + clear, + entries, +} from 'idb-keyval'; export type ConfigStore = { get: (key: string) => Promise; set: (key: string, value: T) => Promise; setMany: (values: [string, T][]) => Promise; keys: () => Promise; + entries: () => Promise<[string, T][]>; delete: (key: string) => Promise; clear: () => Promise; }; @@ -16,6 +26,7 @@ const initialIndexedDB = (database: string): ConfigStore => { set: (key: string, value: T) => set(key, value, store), setMany: (values: [string, T][]) => setMany(values, store), keys: () => keys(store), + entries: () => entries(store), delete: (key: string) => del(key, store), clear: () => clear(store), }; @@ -27,9 +38,10 @@ const scopedIndexedDB = () => { return (scope: string): Readonly> => { if (!storeCache.has(scope)) { + const prefix = `${scope}:`; const store = { - get: async (key: string) => idb.get(`${scope}:${key}`), - set: (key: string, value: T) => idb.set(`${scope}:${key}`, value), + get: async (key: string) => idb.get(prefix + key), + set: (key: string, value: T) => idb.set(prefix + key, value), setMany: (values: [string, T][]) => idb.setMany(values.map(([k, v]) => [`${scope}:${k}`, v])), keys: () => @@ -37,16 +49,24 @@ const scopedIndexedDB = () => { .keys() .then(keys => keys - .filter(k => k.startsWith(`${scope}:`)) - .map(k => k.replace(`${scope}:`, '')) + .filter(k => k.startsWith(prefix)) + .map(k => k.slice(prefix.length)) ), - delete: (key: string) => idb.delete(`${scope}:${key}`), + entries: () => + idb + .entries() + .then(entries => + entries + .filter(([k]) => k.startsWith(prefix)) + .map(([k, v]) => [k.slice(prefix.length), v] as [string, T]) + ), + delete: (key: string) => idb.delete(prefix + key), clear: async () => { await idb .keys() .then(keys => Promise.all( - keys.filter(k => k.startsWith(`${scope}:`)).map(k => del(k)) + keys.filter(k => k.startsWith(prefix)).map(k => del(k)) ) ); }, diff --git a/packages/data-center/tests/datacenter.spec.ts b/packages/data-center/tests/datacenter.spec.ts index c924bfd2cb..661910583c 100644 --- a/packages/data-center/tests/datacenter.spec.ts +++ b/packages/data-center/tests/datacenter.spec.ts @@ -56,12 +56,20 @@ test('list workspaces', async () => { dataCenter.load('test6'), ]); - expect(await dataCenter.list()).toStrictEqual([ - 'test3', - 'test4', - 'test5', - 'test6', - ]); + expect(await dataCenter.list()).toStrictEqual({ + test3: { local: true }, + test4: { local: true }, + test5: { local: true }, + test6: { local: true }, + }); + + await dataCenter.reload('test3', { providerId: 'affine' }); + expect(await dataCenter.list()).toStrictEqual({ + test3: { affine: true, local: true }, + test4: { local: true }, + test5: { local: true }, + test6: { local: true }, + }); }); test('destroy workspaces', async () => { @@ -87,5 +95,5 @@ test('remove workspaces', async () => { // remove workspace will remove workspace data await Promise.all([dataCenter.load('test9'), dataCenter.load('test10')]); await dataCenter.delete('test9'); - expect(await dataCenter.list()).toStrictEqual(['test10']); + expect(await dataCenter.list()).toStrictEqual({ test10: { local: true } }); });