From 7fea77b64f1931c1194bde30aaed2145b72c682f Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 2 Jan 2023 18:55:53 +0800 Subject: [PATCH] feat: local provider --- .../app/src/hooks/use-ensure-workspace.ts | 21 +- packages/data-center/package.json | 2 + .../data-center/src/datacenter/datacenter.ts | 65 +++--- packages/data-center/src/datacenter/index.ts | 7 + .../src/datacenter/provider/affine/index.ts | 80 ++++++- .../datacenter/provider/affine/provider.ts | 74 ------- .../src/datacenter/provider/affine/token.ts | 9 +- .../src/datacenter/provider/base.ts | 26 ++- .../src/datacenter/provider/index.ts | 18 +- .../src/datacenter/provider/local/index.ts | 50 +++++ .../datacenter/provider/local/indexeddb.ts | 199 ++++++++++++++++++ .../src/datacenter/provider/memory.ts | 11 - packages/data-center/tests/datacenter.spec.ts | 30 ++- pnpm-lock.yaml | 14 ++ 14 files changed, 464 insertions(+), 142 deletions(-) delete mode 100644 packages/data-center/src/datacenter/provider/affine/provider.ts create mode 100644 packages/data-center/src/datacenter/provider/local/index.ts create mode 100644 packages/data-center/src/datacenter/provider/local/indexeddb.ts delete mode 100644 packages/data-center/src/datacenter/provider/memory.ts diff --git a/packages/app/src/hooks/use-ensure-workspace.ts b/packages/app/src/hooks/use-ensure-workspace.ts index 8d956eb1e8..0fc076f1da 100644 --- a/packages/app/src/hooks/use-ensure-workspace.ts +++ b/packages/app/src/hooks/use-ensure-workspace.ts @@ -31,19 +31,18 @@ export const useEnsureWorkspace = () => { return; } // If user is not login and input a custom workspaceId, jump to 404 page - if ( - !user && - router.query.workspaceId && - router.query.workspaceId !== defaultOutLineWorkspaceId - ) { - debugger; - router.push('/404'); - return; - } + // if ( + // !user && + // router.query.workspaceId && + // router.query.workspaceId !== defaultOutLineWorkspaceId + // ) { + // router.push('/404'); + // return; + // } const workspaceId = user - ? (router.query.workspaceId as string) || workspacesMeta?.[0]?.id - : defaultOutLineWorkspaceId; + ? (router.query.workspaceId as string) || workspacesMeta[0]?.id + : (router.query.workspaceId as string) || defaultOutLineWorkspaceId; loadWorkspace(workspaceId).finally(() => { setWorkspaceLoaded(true); diff --git a/packages/data-center/package.json b/packages/data-center/package.json index 7fb4d1134a..6f54011ca3 100644 --- a/packages/data-center/package.json +++ b/packages/data-center/package.json @@ -21,12 +21,14 @@ }, "devDependencies": { "@playwright/test": "^1.29.1", + "@types/debug": "^4.1.7", "fake-indexeddb": "4.0.1", "typescript": "^4.8.4" }, "dependencies": { "@blocksuite/blocks": "0.3.0-20221228162706-9576a3a", "@blocksuite/store": "0.3.0-20221228162706-9576a3a", + "debug": "^4.3.4", "encoding": "^0.1.13", "firebase": "^9.15.0", "idb-keyval": "^6.2.0", diff --git a/packages/data-center/src/datacenter/datacenter.ts b/packages/data-center/src/datacenter/datacenter.ts index be083b7ed8..41634b39fa 100644 --- a/packages/data-center/src/datacenter/datacenter.ts +++ b/packages/data-center/src/datacenter/datacenter.ts @@ -2,32 +2,52 @@ import assert from 'assert'; import { BlockSchema } from '@blocksuite/blocks/models'; import { Workspace } from '@blocksuite/store'; +import { getLogger } from './index.js'; import { AffineProvider, BaseProvider } from './provider/index.js'; -import { MemoryProvider } from './provider/index.js'; +import { LocalProvider } from './provider/index.js'; import { getKVConfigure } from './store.js'; export class DataCenter { private readonly _providers = new Map(); private readonly _workspaces = new Map>(); private readonly _config; + private readonly _logger; static async init(): Promise { const dc = new DataCenter(); dc.addProvider(AffineProvider); - dc.addProvider(MemoryProvider); + dc.addProvider(LocalProvider); return dc; } private constructor() { this._config = getKVConfigure('sys'); + this._logger = getLogger('dc'); + this._logger.enabled = true; } - addProvider(provider: typeof BaseProvider) { + private addProvider(provider: typeof BaseProvider) { this._providers.set(provider.id, provider); } - private async _initWithProvider(id: string, providerId: string) { + private async _getProvider(id: string, providerId: string): Promise { + const providerKey = `workspace:${id}:provider`; + if (this._providers.has(providerId)) { + await this._config.set(providerKey, providerId); + return providerId; + } else { + const providerValue = await this._config.get(providerKey); + if (providerValue) return providerValue; + } + throw Error(`Provider ${providerId} not found`); + } + + private async _initWorkspace(id: string, pid: string): Promise { + this._logger(`Init workspace ${id} with ${pid}`); + + const providerId = await this._getProvider(id, pid); + // init workspace & register block schema const workspace = new Workspace({ room: id }).register(BlockSchema); @@ -35,38 +55,22 @@ export class DataCenter { assert(Provider); const provider = new Provider(); - console.log(`Loading workspace ${id} with provider ${Provider.id}`); - await provider.init(getKVConfigure(id), workspace); + await provider.init({ + config: getKVConfigure(id), + logger: this._logger.extend(`${Provider.id}:${id}`), + workspace, + }); await provider.initData(); - console.log(`Workspace ${id} loaded`); + this._logger(`Workspace ${id} loaded`); return provider; } - private async _initWorkspace( - id: string, - providerId: string - ): Promise { - const providerKey = `workspace:${id}:provider`; - const providerValue = await this._config.get(providerKey); - - if (this._providers.has(providerValue || providerId)) { - if (!providerValue) { - await this._config.set(providerKey, providerId); - } - - return this._initWithProvider(id, await this._config.get(providerKey)); - } else { - throw Error(`provider ${providerId} not found`); - } - } - async initWorkspace( id: string, - provider = 'memory' + provider = 'local' ): Promise { if (id) { - console.log('initWorkspace', id); if (!this._workspaces.has(id)) { this._workspaces.set(id, this._initWorkspace(id, provider)); } @@ -81,4 +85,11 @@ export class DataCenter { const config = getKVConfigure(workspace); return config.set(key, value); } + + async getWorkspaceList() { + const keys = await this._config.keys(); + return keys + .filter(k => k.startsWith('workspace:')) + .map(k => k.split(':')[1]); + } } diff --git a/packages/data-center/src/datacenter/index.ts b/packages/data-center/src/datacenter/index.ts index b6feba0795..a95a63a365 100644 --- a/packages/data-center/src/datacenter/index.ts +++ b/packages/data-center/src/datacenter/index.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import { DataCenter } from './datacenter.js'; const _initializeDataCenter = () => { @@ -13,3 +14,9 @@ const _initializeDataCenter = () => { }; export const getDataCenter = _initializeDataCenter(); + +export function getLogger(namespace: string) { + const logger = debug(namespace); + logger.log = console.log.bind(console); + return logger; +} diff --git a/packages/data-center/src/datacenter/provider/affine/index.ts b/packages/data-center/src/datacenter/provider/affine/index.ts index cae85d878d..302183d390 100644 --- a/packages/data-center/src/datacenter/provider/affine/index.ts +++ b/packages/data-center/src/datacenter/provider/affine/index.ts @@ -1 +1,79 @@ -export { AffineProvider } from './provider.js'; +import assert from 'assert'; +import { applyUpdate } from 'yjs'; + +import type { InitialParams } from '../index.js'; +import { LocalProvider } from '../local/index.js'; + +import { downloadWorkspace } from './apis.js'; +import { token, Callback } from './token.js'; + +export class AffineProvider extends LocalProvider { + static id = 'affine'; + private _onTokenRefresh?: Callback = undefined; + + constructor() { + super(); + } + + async init(params: InitialParams) { + super.init(params); + + this._onTokenRefresh = () => { + if (token.refresh) { + this._config.set('token', token.refresh); + } + }; + assert(this._onTokenRefresh); + + token.onChange(this._onTokenRefresh); + + // initial login token + if (token.isExpired) { + try { + const refreshToken = await this._config.get('token'); + await token.refreshToken(refreshToken); + + if (token.refresh) { + this._config.set('token', token.refresh); + } + + assert(token.isLogin); + } catch (_) { + this._logger('Authorization failed, fallback to local mode'); + } + } else { + this._config.set('token', token.refresh); + } + } + + async destroy() { + if (this._onTokenRefresh) { + token.offChange(this._onTokenRefresh); + } + } + + async initData() { + await super.initData(); + + const workspace = this._workspace; + const doc = workspace.doc; + + this._logger(`Login: ${token.isLogin}`); + + if (workspace.room && token.isLogin) { + try { + const updates = await downloadWorkspace(workspace.room); + if (updates) { + applyUpdate(doc, new Uint8Array(updates)); + } + } catch (e) { + this._logger('Failed to init cloud workspace', e); + } + } + + // if after update, the space:meta is empty + // then we need to get map with doc + // just a workaround for yjs + doc.getMap('space:meta'); + } +} diff --git a/packages/data-center/src/datacenter/provider/affine/provider.ts b/packages/data-center/src/datacenter/provider/affine/provider.ts deleted file mode 100644 index bc32c025ed..0000000000 --- a/packages/data-center/src/datacenter/provider/affine/provider.ts +++ /dev/null @@ -1,74 +0,0 @@ -import assert from 'assert'; -import { Workspace } from '@blocksuite/store'; -import { BaseProvider, ConfigStore } from '../index.js'; -import { downloadWorkspace } from './apis.js'; -import { token, Callback } from './token.js'; - -export class AffineProvider extends BaseProvider { - static id = 'affine'; - private _onTokenRefresh?: Callback = undefined; - - constructor() { - super(); - } - - async init(config: ConfigStore, workspace: Workspace) { - super.init(config, workspace); - - this._onTokenRefresh = () => { - if (token.refresh) { - this._config.set('token', token.refresh); - } - }; - assert(this._onTokenRefresh); - - token.onChange(this._onTokenRefresh); - - // initial login token - if (token.isExpired) { - try { - const refreshToken = await this._config.get('token'); - await token.refreshToken(refreshToken); - - if (token.refresh) { - this._config.set('token', token.refresh); - } - - assert(token.isLogin); - } catch (_) { - console.warn('authorization failed, fallback to local mode'); - } - } else { - this._config.set('token', token.refresh); - } - } - - async destroy() { - if (this._onTokenRefresh) { - token.offChange(this._onTokenRefresh); - } - } - - async initData() { - const workspace = this._workspace; - const doc = workspace.doc; - - console.log(workspace.room, token.isLogin); - - if (workspace.room && token.isLogin) { - try { - const updates = await downloadWorkspace(workspace.room); - if (updates) { - Workspace.Y.applyUpdate(doc, new Uint8Array(updates)); - } - } catch (e) { - console.warn('Failed to init cloud workspace', e); - } - } - - // if after update, the space:meta is empty - // then we need to get map with doc - // just a workaround for yjs - doc.getMap('space:meta'); - } -} diff --git a/packages/data-center/src/datacenter/provider/affine/token.ts b/packages/data-center/src/datacenter/provider/affine/token.ts index e2f87b333b..fd60c317ea 100644 --- a/packages/data-center/src/datacenter/provider/affine/token.ts +++ b/packages/data-center/src/datacenter/provider/affine/token.ts @@ -1,3 +1,4 @@ +import { getLogger } from '../../index.js'; import { bareClient } from './request.js'; export interface AccessTokenMessage { @@ -27,6 +28,7 @@ const login = (params: LoginParams): Promise => bareClient.post('api/user/token', { json: params }).json(); class Token { + private readonly _logger; private _accessToken!: string; private _refreshToken!: string; @@ -34,17 +36,22 @@ class Token { private _padding?: Promise; constructor() { + this._logger = getLogger('token'); + this._logger.enabled = true; + this._setToken(); // fill with default value } private _setToken(login?: LoginResponse) { - console.log('set login', login); this._accessToken = login?.token || ''; this._refreshToken = login?.refresh || ''; this._user = Token.parse(this._accessToken); if (login) { + this._logger('set login', login); this.triggerChange(this._user); + } else { + this._logger('empty login'); } } diff --git a/packages/data-center/src/datacenter/provider/base.ts b/packages/data-center/src/datacenter/provider/base.ts index 3f9d8e4642..202ff859c3 100644 --- a/packages/data-center/src/datacenter/provider/base.ts +++ b/packages/data-center/src/datacenter/provider/base.ts @@ -1,19 +1,23 @@ -import { Workspace } from '@blocksuite/store'; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { Workspace } from '@blocksuite/store'; -import type { ConfigStore } from '../store.js'; +import type { Logger, InitialParams, ConfigStore } from './index'; export class BaseProvider { - static id = 'memory'; + static id = 'base'; protected _config!: ConfigStore; + protected _logger!: Logger; protected _workspace!: Workspace; constructor() { // Nothing to do here } - async init(config: ConfigStore, workspace: Workspace) { - this._config = config; - this._workspace = workspace; + async init(params: InitialParams) { + this._config = params.config; + this._logger = params.logger; + this._workspace = params.workspace; + this._logger.enabled = true; } async destroy() { @@ -24,6 +28,16 @@ export class BaseProvider { throw Error('Not implemented: initData'); } + // should return a blob url + async getBlob(_id: string): Promise { + throw Error('Not implemented: getBlob'); + } + + // should return a blob unique id + async setBlob(_blob: Blob): Promise { + throw Error('Not implemented: setBlob'); + } + get workspace() { return this._workspace; } diff --git a/packages/data-center/src/datacenter/provider/index.ts b/packages/data-center/src/datacenter/provider/index.ts index 50c70023a3..aa9a4cd871 100644 --- a/packages/data-center/src/datacenter/provider/index.ts +++ b/packages/data-center/src/datacenter/provider/index.ts @@ -1,5 +1,17 @@ -export type { ConfigStore } from '../store'; +import type { Workspace } from '@blocksuite/store'; -export { BaseProvider } from './base.js'; +import type { getLogger } from '../index'; +import type { ConfigStore } from '../store'; + +export type Logger = ReturnType; + +export type InitialParams = { + config: ConfigStore; + logger: Logger; + workspace: Workspace; +}; + +export type { ConfigStore, Workspace }; +export type { BaseProvider } from './base.js'; export { AffineProvider } from './affine/index.js'; -export { MemoryProvider } from './memory.js'; +export { LocalProvider } from './local/index.js'; diff --git a/packages/data-center/src/datacenter/provider/local/index.ts b/packages/data-center/src/datacenter/provider/local/index.ts new file mode 100644 index 0000000000..31c7677631 --- /dev/null +++ b/packages/data-center/src/datacenter/provider/local/index.ts @@ -0,0 +1,50 @@ +import type { BlobStorage } from '@blocksuite/store'; +import assert from 'assert'; + +import type { InitialParams } from '../index.js'; +import { BaseProvider } from '../base.js'; +import { IndexedDBProvider } from './indexeddb.js'; + +export class LocalProvider extends BaseProvider { + static id = 'local'; + private _blobs!: BlobStorage; + private _idb?: IndexedDBProvider = undefined; + + constructor() { + super(); + } + async init(params: InitialParams) { + super.init(params); + + const blobs = await this._workspace.blobs; + assert(blobs); + this._blobs = blobs; + } + + async initData() { + assert(this._workspace.room); + this._logger('Loading local data'); + this._idb = new IndexedDBProvider( + this._workspace.room, + this._workspace.doc + ); + + await this._idb.whenSynced; + this._logger('Local data loaded'); + } + + async destroy(): Promise { + super.destroy(); + if (this._idb) { + await this._idb.destroy(); + } + } + + async getBlob(id: string): Promise { + return this._blobs.get(id); + } + + async setBlob(blob: Blob): Promise { + return this._blobs.set(blob); + } +} diff --git a/packages/data-center/src/datacenter/provider/local/indexeddb.ts b/packages/data-center/src/datacenter/provider/local/indexeddb.ts new file mode 100644 index 0000000000..db7fab29d7 --- /dev/null +++ b/packages/data-center/src/datacenter/provider/local/indexeddb.ts @@ -0,0 +1,199 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as idb from 'lib0/indexeddb.js'; +import { Observable } from 'lib0/observable.js'; +import type { Doc } from 'yjs'; +import { applyUpdate, encodeStateAsUpdate, transact } from 'yjs'; + +const customStoreName = 'custom'; +const updatesStoreName = 'updates'; + +const PREFERRED_TRIM_SIZE = 500; + +const fetchUpdates = async (provider: IndexedDBProvider) => { + const [updatesStore] = idb.transact(provider.db as IDBDatabase, [ + updatesStoreName, + ]); // , 'readonly') + if (updatesStore) { + const updates = await idb.getAll( + updatesStore, + idb.createIDBKeyRangeLowerBound(provider._dbref, false) + ); + transact( + provider.doc, + () => { + updates.forEach(val => applyUpdate(provider.doc, val)); + }, + provider, + false + ); + const lastKey = await idb.getLastKey(updatesStore); + provider._dbref = lastKey + 1; + const cnt = await idb.count(updatesStore); + provider._dbsize = cnt; + } + return updatesStore; +}; + +const storeState = (provider: IndexedDBProvider, forceStore = true) => + fetchUpdates(provider).then(updatesStore => { + if ( + updatesStore && + (forceStore || provider._dbsize >= PREFERRED_TRIM_SIZE) + ) { + idb + .addAutoKey(updatesStore, encodeStateAsUpdate(provider.doc)) + .then(() => + idb.del( + updatesStore, + idb.createIDBKeyRangeUpperBound(provider._dbref, true) + ) + ) + .then(() => + idb.count(updatesStore).then(cnt => { + provider._dbsize = cnt; + }) + ); + } + }); + +export class IndexedDBProvider extends Observable { + doc: Doc; + name: string; + _dbref: number; + _dbsize: number; + private _destroyed: boolean; + whenSynced: Promise; + db: IDBDatabase | null; + private _db: Promise; + private _storeTimeout: number; + private _storeTimeoutId: NodeJS.Timeout | null; + private _storeUpdate: (update: Uint8Array, origin: any) => void; + + constructor(name: string, doc: Doc) { + super(); + this.doc = doc; + this.name = name; + this._dbref = 0; + this._dbsize = 0; + this._destroyed = false; + this.db = null; + this._db = idb.openDB(name, db => + idb.createStores(db, [['updates', { autoIncrement: true }], ['custom']]) + ); + + this.whenSynced = this._db.then(async db => { + this.db = db; + const currState = encodeStateAsUpdate(doc); + const updatesStore = await fetchUpdates(this); + if (updatesStore) { + await idb.addAutoKey(updatesStore, currState); + } + if (this._destroyed) { + return this; + } + this.emit('synced', [this]); + return this; + }); + + // Timeout in ms untill data is merged and persisted in idb. + this._storeTimeout = 1000; + + this._storeTimeoutId = null; + + this._storeUpdate = (update: Uint8Array, origin: any) => { + if (this.db && origin !== this) { + const [updatesStore] = idb.transact( + /** @type {IDBDatabase} */ this.db, + [updatesStoreName] + ); + if (updatesStore) { + idb.addAutoKey(updatesStore, update); + } + if (++this._dbsize >= PREFERRED_TRIM_SIZE) { + // debounce store call + if (this._storeTimeoutId !== null) { + clearTimeout(this._storeTimeoutId); + } + this._storeTimeoutId = setTimeout(() => { + storeState(this, false); + this._storeTimeoutId = null; + }, this._storeTimeout); + } + } + }; + doc.on('update', this._storeUpdate); + this.destroy = this.destroy.bind(this); + doc.on('destroy', this.destroy); + } + + override destroy() { + if (this._storeTimeoutId) { + clearTimeout(this._storeTimeoutId); + } + this.doc.off('update', this._storeUpdate); + this.doc.off('destroy', this.destroy); + this._destroyed = true; + return this._db.then(db => { + db.close(); + }); + } + + /** + * Destroys this instance and removes all data from indexeddb. + * + * @return {Promise} + */ + async clearData(): Promise { + return this.destroy().then(() => { + idb.deleteDB(this.name); + }); + } + + /** + * @param {String | number | ArrayBuffer | Date} key + * @return {Promise} + */ + async get( + key: string | number | ArrayBuffer | Date + ): Promise { + return this._db.then(db => { + const [custom] = idb.transact(db, [customStoreName], 'readonly'); + if (custom) { + return idb.get(custom, key); + } + return undefined; + }); + } + + /** + * @param {String | number | ArrayBuffer | Date} key + * @param {String | number | ArrayBuffer | Date} value + * @return {Promise} + */ + async set( + key: string | number | ArrayBuffer | Date, + value: string | number | ArrayBuffer | Date + ): Promise { + return this._db.then(db => { + const [custom] = idb.transact(db, [customStoreName]); + if (custom) { + return idb.put(custom, value, key); + } + return undefined; + }); + } + + /** + * @param {String | number | ArrayBuffer | Date} key + * @return {Promise} + */ + async del(key: string | number | ArrayBuffer | Date): Promise { + return this._db.then(db => { + const [custom] = idb.transact(db, [customStoreName]); + if (custom) { + return idb.del(custom, key); + } + return undefined; + }); + } +} diff --git a/packages/data-center/src/datacenter/provider/memory.ts b/packages/data-center/src/datacenter/provider/memory.ts deleted file mode 100644 index 9afe7adf66..0000000000 --- a/packages/data-center/src/datacenter/provider/memory.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseProvider } from './base.js'; - -export class MemoryProvider extends BaseProvider { - constructor() { - super(); - } - - async initData() { - console.log('Skip data reload in memory provider'); - } -} diff --git a/packages/data-center/tests/datacenter.spec.ts b/packages/data-center/tests/datacenter.spec.ts index d8edb3ce0d..7592c1790e 100644 --- a/packages/data-center/tests/datacenter.spec.ts +++ b/packages/data-center/tests/datacenter.spec.ts @@ -4,7 +4,7 @@ import { getDataCenter } from './utils.js'; import 'fake-indexeddb/auto'; -test('can init data center', async () => { +test('init data center', async () => { const dataCenter = await getDataCenter(); expect(dataCenter).toBeTruthy(); @@ -12,24 +12,38 @@ test('can init data center', async () => { expect(workspace).toBeTruthy(); }); -test('should init error', async () => { +test('should init error with unknown provider', async () => { const dataCenter = await getDataCenter(); test.fail(); await dataCenter.initWorkspace('test', 'not exist provider'); }); -test('can init affine provider', async () => { +test.skip('init affine provider', async () => { const dataCenter = await getDataCenter(); // TODO: set constant token for testing - await dataCenter.setWorkspaceConfig( - '6', - 'token', - 'Zzq338Py_3veZwD4XTa0nyoDGsLqhd9nFeaT1p_SK43TAOCSkcV63Tn3kDUWfBI4JHKqX7mhED4IFejm_62KUpGXRWZv11c5BGay7Nhvb_br' - ); + await dataCenter.setWorkspaceConfig('6', 'token', 'YOUR_TOKEN'); const workspace = await dataCenter.initWorkspace('6', 'affine'); expect(workspace).toBeTruthy(); }); + +test('list workspaces', async () => { + const dataCenter = await getDataCenter(); + + await Promise.all([ + dataCenter.initWorkspace('test1'), + dataCenter.initWorkspace('test2'), + dataCenter.initWorkspace('test3'), + dataCenter.initWorkspace('test4'), + ]); + + expect(await dataCenter.getWorkspaceList()).toStrictEqual([ + 'test1', + 'test2', + 'test3', + 'test4', + ]); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a717d2322..e14d930886 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,8 @@ importers: '@blocksuite/blocks': 0.3.0-20221228162706-9576a3a '@blocksuite/store': 0.3.0-20221228162706-9576a3a '@playwright/test': ^1.29.1 + '@types/debug': ^4.1.7 + debug: ^4.3.4 encoding: ^0.1.13 fake-indexeddb: 4.0.1 firebase: ^9.15.0 @@ -148,6 +150,7 @@ importers: dependencies: '@blocksuite/blocks': 0.3.0-20221228162706-9576a3a_yjs@13.5.44 '@blocksuite/store': 0.3.0-20221228162706-9576a3a_yjs@13.5.44 + debug: 4.3.4 encoding: 0.1.13 firebase: 9.15.0_encoding@0.1.13 idb-keyval: 6.2.0 @@ -159,6 +162,7 @@ importers: yjs: 13.5.44 devDependencies: '@playwright/test': 1.29.1 + '@types/debug': 4.1.7 fake-indexeddb: 4.0.1 typescript: 4.9.3 @@ -3623,6 +3627,12 @@ packages: '@babel/types': 7.20.5 dev: true + /@types/debug/4.1.7: + resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} + dependencies: + '@types/ms': 0.7.31 + dev: true + /@types/eslint/8.4.10: resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==} dependencies: @@ -3697,6 +3707,10 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true + /@types/ms/0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: true + /@types/node/12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true