feat(y-indexeddb): remove id (#2810)

This commit is contained in:
Alex Yang
2023-06-17 13:58:48 +08:00
committed by GitHub
parent deeafb3a12
commit c68220166a
6 changed files with 68 additions and 68 deletions

View File

@@ -66,7 +66,7 @@ export const CRUD: WorkspaceCRUD<WorkspaceFlavour.LOCAL> = {
WorkspaceFlavour.LOCAL WorkspaceFlavour.LOCAL
); );
BlockSuiteWorkspace.Y.applyUpdateV2(blockSuiteWorkspace.doc, binary); BlockSuiteWorkspace.Y.applyUpdateV2(blockSuiteWorkspace.doc, binary);
const persistence = createIndexedDBProvider(id, blockSuiteWorkspace.doc); const persistence = createIndexedDBProvider(blockSuiteWorkspace.doc);
persistence.connect(); persistence.connect();
await persistence.whenSynced.then(() => { await persistence.whenSynced.then(() => {
persistence.disconnect(); persistence.disconnect();

View File

@@ -82,10 +82,7 @@ const createAffineWebSocketProvider = (
const createIndexedDBBackgroundProvider = ( const createIndexedDBBackgroundProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
): LocalIndexedDBBackgroundProvider => { ): LocalIndexedDBBackgroundProvider => {
const indexeddbProvider = create( const indexeddbProvider = create(blockSuiteWorkspace.doc);
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc
);
const callbacks = new CallbackSet(); const callbacks = new CallbackSet();
return { return {
flavour: 'local-indexeddb-background', flavour: 'local-indexeddb-background',

View File

@@ -1,14 +1,24 @@
# @toeverything/y-indexeddb # @toeverything/y-indexeddb
## Features
- persistence data in indexeddb
- sub-documents support
- fully TypeScript
## Usage ## Usage
```ts ```ts
import { createIndexedDBProvider, downloadBinary } from '@toeverything/y-indexeddb'; import { createIndexedDBProvider, downloadBinary } from '@toeverything/y-indexeddb';
import * as Y from 'yjs'; import * as Y from 'yjs';
const yDoc = new Y.Doc();
const yDoc = new Y.Doc({
// we use `guid` as unique key
guid: 'my-doc',
});
// sync yDoc with indexedDB // sync yDoc with indexedDB
const provider = createIndexedDBProvider('docName', yDoc); const provider = createIndexedDBProvider(yDoc);
provider.connect(); provider.connect();
await provider.whenSynced.then(() => { await provider.whenSynced.then(() => {
console.log('synced'); console.log('synced');
@@ -16,7 +26,7 @@ await provider.whenSynced.then(() => {
}); });
// dowload binary data from indexedDB for once // dowload binary data from indexedDB for once
downloadBinary('docName').then(blob => { downloadBinary(yDoc.guid).then(blob => {
if (blob !== false) { if (blob !== false) {
Y.applyUpdate(yDoc, blob); Y.applyUpdate(yDoc, blob);
} }

View File

@@ -70,7 +70,7 @@ afterEach(() => {
describe('indexeddb provider', () => { describe('indexeddb provider', () => {
test('connect', async () => { test('connect', async () => {
const provider = createIndexedDBProvider(workspace.id, workspace.doc); const provider = createIndexedDBProvider(workspace.doc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
const db = await openDB(rootDBName, dbVersion); const db = await openDB(rootDBName, dbVersion);
@@ -119,11 +119,7 @@ describe('indexeddb provider', () => {
}) })
.register(AffineSchemas) .register(AffineSchemas)
.register(__unstableSchemas); .register(__unstableSchemas);
const provider2 = createIndexedDBProvider( const provider2 = createIndexedDBProvider(secondWorkspace.doc, rootDBName);
secondWorkspace.id,
secondWorkspace.doc,
rootDBName
);
provider2.connect(); provider2.connect();
await provider2.whenSynced; await provider2.whenSynced;
expect(Workspace.Y.encodeStateAsUpdate(secondWorkspace.doc)).toEqual( expect(Workspace.Y.encodeStateAsUpdate(secondWorkspace.doc)).toEqual(
@@ -132,11 +128,7 @@ describe('indexeddb provider', () => {
}); });
test('disconnect suddenly', async () => { test('disconnect suddenly', async () => {
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(workspace.doc, rootDBName);
workspace.id,
workspace.doc,
rootDBName
);
const fn = vi.fn(); const fn = vi.fn();
provider.connect(); provider.connect();
provider.disconnect(); provider.disconnect();
@@ -146,11 +138,7 @@ describe('indexeddb provider', () => {
}); });
test('connect and disconnect', async () => { test('connect and disconnect', async () => {
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(workspace.doc, rootDBName);
workspace.id,
workspace.doc,
rootDBName
);
provider.connect(); provider.connect();
expect(provider.connected).toBe(true); expect(provider.connected).toBe(true);
const p1 = provider.whenSynced; const p1 = provider.whenSynced;
@@ -186,7 +174,7 @@ describe('indexeddb provider', () => {
}); });
test('cleanup', async () => { test('cleanup', async () => {
const provider = createIndexedDBProvider(workspace.id, workspace.doc); const provider = createIndexedDBProvider(workspace.doc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
const db = await openDB(rootDBName, dbVersion); const db = await openDB(rootDBName, dbVersion);
@@ -212,7 +200,7 @@ describe('indexeddb provider', () => {
}); });
test('cleanup when connecting', async () => { test('cleanup when connecting', async () => {
const provider = createIndexedDBProvider(workspace.id, workspace.doc); const provider = createIndexedDBProvider(workspace.doc);
provider.connect(); provider.connect();
await expect(() => provider.cleanup()).rejects.toThrowError( await expect(() => provider.cleanup()).rejects.toThrowError(
CleanupWhenConnectingError CleanupWhenConnectingError
@@ -224,11 +212,7 @@ describe('indexeddb provider', () => {
test('merge', async () => { test('merge', async () => {
setMergeCount(5); setMergeCount(5);
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(workspace.doc, rootDBName);
workspace.id,
workspace.doc,
rootDBName
);
provider.connect(); provider.connect();
{ {
const page = workspace.createPage({ id: 'page0' }); const page = workspace.createPage({ id: 'page0' });
@@ -247,21 +231,20 @@ describe('indexeddb provider', () => {
}); });
test("data won't be lost", async () => { test("data won't be lost", async () => {
const id = uuidv4();
const doc = new Workspace.Y.Doc(); const doc = new Workspace.Y.Doc();
const map = doc.getMap('map'); const map = doc.getMap('map');
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
map.set(`${i}`, i); map.set(`${i}`, i);
} }
{ {
const provider = createIndexedDBProvider(id, doc, rootDBName); const provider = createIndexedDBProvider(doc, rootDBName);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
provider.disconnect(); provider.disconnect();
} }
{ {
const newDoc = new Workspace.Y.Doc(); const newDoc = new Workspace.Y.Doc();
const provider = createIndexedDBProvider(id, newDoc, rootDBName); const provider = createIndexedDBProvider(newDoc, rootDBName);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
provider.disconnect(); provider.disconnect();
@@ -280,8 +263,10 @@ describe('indexeddb provider', () => {
await persistence.destroy(); await persistence.destroy();
} }
{ {
const yDoc = new Doc(); const yDoc = new Doc({
const provider = createIndexedDBProvider('test', yDoc); guid: 'test',
});
const provider = createIndexedDBProvider(yDoc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
await new Promise(resolve => setTimeout(resolve, 0)); await new Promise(resolve => setTimeout(resolve, 0));
@@ -293,9 +278,11 @@ describe('indexeddb provider', () => {
throw new Error('not supported'); throw new Error('not supported');
}); });
await expect(indexedDB.databases).rejects.toThrow('not supported'); await expect(indexedDB.databases).rejects.toThrow('not supported');
const yDoc = new Doc(); const yDoc = new Doc({
guid: 'test',
});
expect(indexedDB.databases).toBeCalledTimes(1); expect(indexedDB.databases).toBeCalledTimes(1);
const provider = createIndexedDBProvider('test', yDoc); const provider = createIndexedDBProvider(yDoc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
expect(indexedDB.databases).toBeCalledTimes(2); expect(indexedDB.databases).toBeCalledTimes(2);
@@ -314,8 +301,10 @@ describe('indexeddb provider', () => {
expect(event).toBe('beforeunload'); expect(event).toBe('beforeunload');
return oldRemoveEventListener(event, fn, options); return oldRemoveEventListener(event, fn, options);
}); });
const doc = new Doc(); const doc = new Doc({
const provider = createIndexedDBProvider('1', doc); guid: '1',
});
const provider = createIndexedDBProvider(doc);
const map = doc.getMap('map'); const map = doc.getMap('map');
map.set('1', 1); map.set('1', 1);
provider.connect(); provider.connect();
@@ -394,21 +383,25 @@ describe('subDoc', () => {
test('basic', async () => { test('basic', async () => {
let json1: any, json2: any; let json1: any, json2: any;
{ {
const doc = new Doc(); const doc = new Doc({
guid: 'test',
});
const map = doc.getMap(); const map = doc.getMap();
const subDoc = new Doc(); const subDoc = new Doc();
subDoc.load(); subDoc.load();
map.set('1', subDoc); map.set('1', subDoc);
map.set('2', 'test'); map.set('2', 'test');
const provider = createIndexedDBProvider('test', doc); const provider = createIndexedDBProvider(doc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
provider.disconnect(); provider.disconnect();
json1 = doc.toJSON(); json1 = doc.toJSON();
} }
{ {
const doc = new Doc(); const doc = new Doc({
const provider = createIndexedDBProvider('test', doc); guid: 'test',
});
const provider = createIndexedDBProvider(doc);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
const map = doc.getMap(); const map = doc.getMap();
@@ -427,11 +420,7 @@ describe('subDoc', () => {
}); });
await page0.waitForLoaded(); await page0.waitForLoaded();
const { paragraphBlockId: paragraphBlockIdPage1 } = initEmptyPage(page0); const { paragraphBlockId: paragraphBlockIdPage1 } = initEmptyPage(page0);
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(workspace.doc, rootDBName);
workspace.id,
workspace.doc,
rootDBName
);
provider.connect(); provider.connect();
const page1 = workspace.createPage({ const page1 = workspace.createPage({
id: 'page1', id: 'page1',
@@ -446,11 +435,7 @@ describe('subDoc', () => {
isSSR: true, isSSR: true,
}); });
newWorkspace.register(AffineSchemas).register(__unstableSchemas); newWorkspace.register(AffineSchemas).register(__unstableSchemas);
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(newWorkspace.doc, rootDBName);
newWorkspace.id,
newWorkspace.doc,
rootDBName
);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
const page0 = newWorkspace.getPage('page0') as Page; const page0 = newWorkspace.getPage('page0') as Page;
@@ -474,11 +459,7 @@ describe('utils', () => {
const page = workspace.createPage({ id: 'page0' }); const page = workspace.createPage({ id: 'page0' });
await page.waitForLoaded(); await page.waitForLoaded();
initEmptyPage(page); initEmptyPage(page);
const provider = createIndexedDBProvider( const provider = createIndexedDBProvider(workspace.doc, rootDBName);
workspace.id,
workspace.doc,
rootDBName
);
provider.connect(); provider.connect();
await provider.whenSynced; await provider.whenSynced;
provider.disconnect(); provider.disconnect();

View File

@@ -148,10 +148,16 @@ type SubDocsEvent = {
loaded: Set<Doc>; loaded: Set<Doc>;
}; };
/**
* We use `doc.guid` as the unique key, please make sure it not changes.
*/
export const createIndexedDBProvider = ( export const createIndexedDBProvider = (
id: string,
doc: Doc, doc: Doc,
dbName: string = DEFAULT_DB_NAME dbName: string = DEFAULT_DB_NAME,
/**
* In the future, migrate will be removed and there will be a separate function
*/
migrate = true
): IndexedDBProvider => { ): IndexedDBProvider => {
let resolve: () => void; let resolve: () => void;
let reject: (reason?: unknown) => void; let reject: (reason?: unknown) => void;
@@ -336,15 +342,21 @@ export const createIndexedDBProvider = (
reject = _reject; reject = _reject;
}); });
connected = true; connected = true;
trackDoc(id, doc); trackDoc(doc.guid, doc);
// only the runs `await` below, otherwise the logic is incorrect // only the runs `await` below, otherwise the logic is incorrect
const db = await dbPromise; const db = await dbPromise;
await tryMigrate(db, id, dbName); if (migrate) {
// Tips:
// this is only backward compatible with the yjs official version of y-indexeddb
await tryMigrate(db, doc.guid, dbName);
}
if (!connected) { if (!connected) {
return; return;
} }
// recursively save all docs into indexeddb
const docs: [string, Doc][] = []; const docs: [string, Doc][] = [];
docs.push([id, doc]); docs.push([doc.guid, doc]);
while (docs.length > 0) { while (docs.length > 0) {
const [id, doc] = docs.pop() as [string, Doc]; const [id, doc] = docs.pop() as [string, Doc];
await saveDocOperation(id, doc); await saveDocOperation(id, doc);
@@ -361,13 +373,13 @@ export const createIndexedDBProvider = (
if (early) { if (early) {
reject(new EarlyDisconnectError()); reject(new EarlyDisconnectError());
} }
unTrackDoc(id, doc); unTrackDoc(doc.guid, doc);
}, },
async cleanup() { async cleanup() {
if (connected) { if (connected) {
throw new CleanupWhenConnectingError(); throw new CleanupWhenConnectingError();
} }
await (await dbPromise).delete('workspace', id); await (await dbPromise).delete('workspace', doc.guid);
}, },
whenSynced: Promise.resolve(), whenSynced: Promise.resolve(),
get connected() { get connected() {

View File

@@ -128,7 +128,7 @@ export async function tryMigrate(
} }
export async function downloadBinary( export async function downloadBinary(
id: string, guid: string,
dbName = DEFAULT_DB_NAME dbName = DEFAULT_DB_NAME
): Promise<UpdateMessage['update'] | false> { ): Promise<UpdateMessage['update'] | false> {
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, { const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
@@ -136,7 +136,7 @@ export async function downloadBinary(
}); });
const db = await dbPromise; const db = await dbPromise;
const t = db.transaction('workspace', 'readonly').objectStore('workspace'); const t = db.transaction('workspace', 'readonly').objectStore('workspace');
const doc = await t.get(id); const doc = await t.get(guid);
if (!doc) { if (!doc) {
return false; return false;
} else { } else {