mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-09-20 07:57:29 +03:00
feat(y-indexeddb): remove id (#2810)
This commit is contained in:
parent
deeafb3a12
commit
c68220166a
@ -66,7 +66,7 @@ export const CRUD: WorkspaceCRUD<WorkspaceFlavour.LOCAL> = {
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
BlockSuiteWorkspace.Y.applyUpdateV2(blockSuiteWorkspace.doc, binary);
|
||||
const persistence = createIndexedDBProvider(id, blockSuiteWorkspace.doc);
|
||||
const persistence = createIndexedDBProvider(blockSuiteWorkspace.doc);
|
||||
persistence.connect();
|
||||
await persistence.whenSynced.then(() => {
|
||||
persistence.disconnect();
|
||||
|
@ -82,10 +82,7 @@ const createAffineWebSocketProvider = (
|
||||
const createIndexedDBBackgroundProvider = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
): LocalIndexedDBBackgroundProvider => {
|
||||
const indexeddbProvider = create(
|
||||
blockSuiteWorkspace.id,
|
||||
blockSuiteWorkspace.doc
|
||||
);
|
||||
const indexeddbProvider = create(blockSuiteWorkspace.doc);
|
||||
const callbacks = new CallbackSet();
|
||||
return {
|
||||
flavour: 'local-indexeddb-background',
|
||||
|
@ -1,14 +1,24 @@
|
||||
# @toeverything/y-indexeddb
|
||||
|
||||
## Features
|
||||
|
||||
- persistence data in indexeddb
|
||||
- sub-documents support
|
||||
- fully TypeScript
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { createIndexedDBProvider, downloadBinary } from '@toeverything/y-indexeddb';
|
||||
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
|
||||
const provider = createIndexedDBProvider('docName', yDoc);
|
||||
const provider = createIndexedDBProvider(yDoc);
|
||||
provider.connect();
|
||||
await provider.whenSynced.then(() => {
|
||||
console.log('synced');
|
||||
@ -16,7 +26,7 @@ await provider.whenSynced.then(() => {
|
||||
});
|
||||
|
||||
// dowload binary data from indexedDB for once
|
||||
downloadBinary('docName').then(blob => {
|
||||
downloadBinary(yDoc.guid).then(blob => {
|
||||
if (blob !== false) {
|
||||
Y.applyUpdate(yDoc, blob);
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ afterEach(() => {
|
||||
|
||||
describe('indexeddb provider', () => {
|
||||
test('connect', async () => {
|
||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
||||
const provider = createIndexedDBProvider(workspace.doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const db = await openDB(rootDBName, dbVersion);
|
||||
@ -119,11 +119,7 @@ describe('indexeddb provider', () => {
|
||||
})
|
||||
.register(AffineSchemas)
|
||||
.register(__unstableSchemas);
|
||||
const provider2 = createIndexedDBProvider(
|
||||
secondWorkspace.id,
|
||||
secondWorkspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider2 = createIndexedDBProvider(secondWorkspace.doc, rootDBName);
|
||||
provider2.connect();
|
||||
await provider2.whenSynced;
|
||||
expect(Workspace.Y.encodeStateAsUpdate(secondWorkspace.doc)).toEqual(
|
||||
@ -132,11 +128,7 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
|
||||
test('disconnect suddenly', async () => {
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(workspace.doc, rootDBName);
|
||||
const fn = vi.fn();
|
||||
provider.connect();
|
||||
provider.disconnect();
|
||||
@ -146,11 +138,7 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
|
||||
test('connect and disconnect', async () => {
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(workspace.doc, rootDBName);
|
||||
provider.connect();
|
||||
expect(provider.connected).toBe(true);
|
||||
const p1 = provider.whenSynced;
|
||||
@ -186,7 +174,7 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
|
||||
test('cleanup', async () => {
|
||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
||||
const provider = createIndexedDBProvider(workspace.doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const db = await openDB(rootDBName, dbVersion);
|
||||
@ -212,7 +200,7 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
|
||||
test('cleanup when connecting', async () => {
|
||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
||||
const provider = createIndexedDBProvider(workspace.doc);
|
||||
provider.connect();
|
||||
await expect(() => provider.cleanup()).rejects.toThrowError(
|
||||
CleanupWhenConnectingError
|
||||
@ -224,11 +212,7 @@ describe('indexeddb provider', () => {
|
||||
|
||||
test('merge', async () => {
|
||||
setMergeCount(5);
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(workspace.doc, rootDBName);
|
||||
provider.connect();
|
||||
{
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
@ -247,21 +231,20 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
|
||||
test("data won't be lost", async () => {
|
||||
const id = uuidv4();
|
||||
const doc = new Workspace.Y.Doc();
|
||||
const map = doc.getMap('map');
|
||||
for (let i = 0; i < 100; i++) {
|
||||
map.set(`${i}`, i);
|
||||
}
|
||||
{
|
||||
const provider = createIndexedDBProvider(id, doc, rootDBName);
|
||||
const provider = createIndexedDBProvider(doc, rootDBName);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
provider.disconnect();
|
||||
}
|
||||
{
|
||||
const newDoc = new Workspace.Y.Doc();
|
||||
const provider = createIndexedDBProvider(id, newDoc, rootDBName);
|
||||
const provider = createIndexedDBProvider(newDoc, rootDBName);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
provider.disconnect();
|
||||
@ -280,8 +263,10 @@ describe('indexeddb provider', () => {
|
||||
await persistence.destroy();
|
||||
}
|
||||
{
|
||||
const yDoc = new Doc();
|
||||
const provider = createIndexedDBProvider('test', yDoc);
|
||||
const yDoc = new Doc({
|
||||
guid: 'test',
|
||||
});
|
||||
const provider = createIndexedDBProvider(yDoc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
@ -293,9 +278,11 @@ describe('indexeddb provider', () => {
|
||||
throw new Error('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);
|
||||
const provider = createIndexedDBProvider('test', yDoc);
|
||||
const provider = createIndexedDBProvider(yDoc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
expect(indexedDB.databases).toBeCalledTimes(2);
|
||||
@ -314,8 +301,10 @@ describe('indexeddb provider', () => {
|
||||
expect(event).toBe('beforeunload');
|
||||
return oldRemoveEventListener(event, fn, options);
|
||||
});
|
||||
const doc = new Doc();
|
||||
const provider = createIndexedDBProvider('1', doc);
|
||||
const doc = new Doc({
|
||||
guid: '1',
|
||||
});
|
||||
const provider = createIndexedDBProvider(doc);
|
||||
const map = doc.getMap('map');
|
||||
map.set('1', 1);
|
||||
provider.connect();
|
||||
@ -394,21 +383,25 @@ describe('subDoc', () => {
|
||||
test('basic', async () => {
|
||||
let json1: any, json2: any;
|
||||
{
|
||||
const doc = new Doc();
|
||||
const doc = new Doc({
|
||||
guid: 'test',
|
||||
});
|
||||
const map = doc.getMap();
|
||||
const subDoc = new Doc();
|
||||
subDoc.load();
|
||||
map.set('1', subDoc);
|
||||
map.set('2', 'test');
|
||||
const provider = createIndexedDBProvider('test', doc);
|
||||
const provider = createIndexedDBProvider(doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
provider.disconnect();
|
||||
json1 = doc.toJSON();
|
||||
}
|
||||
{
|
||||
const doc = new Doc();
|
||||
const provider = createIndexedDBProvider('test', doc);
|
||||
const doc = new Doc({
|
||||
guid: 'test',
|
||||
});
|
||||
const provider = createIndexedDBProvider(doc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const map = doc.getMap();
|
||||
@ -427,11 +420,7 @@ describe('subDoc', () => {
|
||||
});
|
||||
await page0.waitForLoaded();
|
||||
const { paragraphBlockId: paragraphBlockIdPage1 } = initEmptyPage(page0);
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(workspace.doc, rootDBName);
|
||||
provider.connect();
|
||||
const page1 = workspace.createPage({
|
||||
id: 'page1',
|
||||
@ -446,11 +435,7 @@ describe('subDoc', () => {
|
||||
isSSR: true,
|
||||
});
|
||||
newWorkspace.register(AffineSchemas).register(__unstableSchemas);
|
||||
const provider = createIndexedDBProvider(
|
||||
newWorkspace.id,
|
||||
newWorkspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(newWorkspace.doc, rootDBName);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
const page0 = newWorkspace.getPage('page0') as Page;
|
||||
@ -474,11 +459,7 @@ describe('utils', () => {
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
initEmptyPage(page);
|
||||
const provider = createIndexedDBProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
rootDBName
|
||||
);
|
||||
const provider = createIndexedDBProvider(workspace.doc, rootDBName);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
provider.disconnect();
|
||||
|
@ -148,10 +148,16 @@ type SubDocsEvent = {
|
||||
loaded: Set<Doc>;
|
||||
};
|
||||
|
||||
/**
|
||||
* We use `doc.guid` as the unique key, please make sure it not changes.
|
||||
*/
|
||||
export const createIndexedDBProvider = (
|
||||
id: string,
|
||||
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 => {
|
||||
let resolve: () => void;
|
||||
let reject: (reason?: unknown) => void;
|
||||
@ -336,15 +342,21 @@ export const createIndexedDBProvider = (
|
||||
reject = _reject;
|
||||
});
|
||||
connected = true;
|
||||
trackDoc(id, doc);
|
||||
trackDoc(doc.guid, doc);
|
||||
// only the runs `await` below, otherwise the logic is incorrect
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
// recursively save all docs into indexeddb
|
||||
const docs: [string, Doc][] = [];
|
||||
docs.push([id, doc]);
|
||||
docs.push([doc.guid, doc]);
|
||||
while (docs.length > 0) {
|
||||
const [id, doc] = docs.pop() as [string, Doc];
|
||||
await saveDocOperation(id, doc);
|
||||
@ -361,13 +373,13 @@ export const createIndexedDBProvider = (
|
||||
if (early) {
|
||||
reject(new EarlyDisconnectError());
|
||||
}
|
||||
unTrackDoc(id, doc);
|
||||
unTrackDoc(doc.guid, doc);
|
||||
},
|
||||
async cleanup() {
|
||||
if (connected) {
|
||||
throw new CleanupWhenConnectingError();
|
||||
}
|
||||
await (await dbPromise).delete('workspace', id);
|
||||
await (await dbPromise).delete('workspace', doc.guid);
|
||||
},
|
||||
whenSynced: Promise.resolve(),
|
||||
get connected() {
|
||||
|
@ -128,7 +128,7 @@ export async function tryMigrate(
|
||||
}
|
||||
|
||||
export async function downloadBinary(
|
||||
id: string,
|
||||
guid: string,
|
||||
dbName = DEFAULT_DB_NAME
|
||||
): Promise<UpdateMessage['update'] | false> {
|
||||
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
||||
@ -136,7 +136,7 @@ export async function downloadBinary(
|
||||
});
|
||||
const db = await dbPromise;
|
||||
const t = db.transaction('workspace', 'readonly').objectStore('workspace');
|
||||
const doc = await t.get(id);
|
||||
const doc = await t.get(guid);
|
||||
if (!doc) {
|
||||
return false;
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user