fix: first binary on y-indexeddb (#1972)

This commit is contained in:
Himself65
2023-04-16 21:33:54 -05:00
committed by GitHub
parent 4cb6b8fdc8
commit 9c517907eb
4 changed files with 139 additions and 60 deletions

View File

@@ -3,6 +3,7 @@
*/
import 'fake-indexeddb/auto';
import { initPage } from '@affine/env/blocksuite';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
import { openDB } from 'idb';
@@ -14,13 +15,15 @@ import type { WorkspacePersist } from '../index';
import {
createIndexedDBProvider,
dbVersion,
DEFAULT_DB_NAME,
downloadBinary,
getMilestones,
markMilestone,
revertUpdate,
setMergeCount,
} from '../index';
async function getUpdates(id: string): Promise<ArrayBuffer[]> {
async function getUpdates(id: string): Promise<Uint8Array[]> {
const db = await openDB(rootDBName, dbVersion);
const store = await db
.transaction('workspace', 'readonly')
@@ -33,7 +36,7 @@ async function getUpdates(id: string): Promise<ArrayBuffer[]> {
let id: string;
let workspace: Workspace;
const rootDBName = 'affine-local';
const rootDBName = DEFAULT_DB_NAME;
beforeEach(() => {
id = uuidv4();
@@ -62,7 +65,12 @@ describe('indexeddb provider', () => {
const data = await store.get(id);
expect(data).toEqual({
id,
updates: [],
updates: [
{
timestamp: expect.any(Number),
update: encodeStateAsUpdate(workspace.doc),
},
],
});
const page = workspace.createPage('page0');
const pageBlockId = page.addBlock('affine:page', { title: '' });
@@ -129,6 +137,7 @@ describe('indexeddb provider', () => {
provider.connect();
const p1 = provider.whenSynced;
await p1;
const snapshot = encodeStateAsUpdate(workspace.doc);
provider.disconnect();
{
const page = workspace.createPage('page0');
@@ -138,7 +147,8 @@ describe('indexeddb provider', () => {
}
{
const updates = await getUpdates(workspace.id);
expect(updates).toEqual([]);
expect(updates.length).toBe(1);
expect(updates[0]).toEqual(snapshot);
}
provider.connect();
const p2 = provider.whenSynced;
@@ -292,3 +302,32 @@ describe('milestone', () => {
}
});
});
describe('utils', () => {
test('download binary', async () => {
const page = workspace.createPage('page0');
initPage(page);
const provider = createIndexedDBProvider(
workspace.id,
workspace.doc,
rootDBName
);
provider.connect();
await provider.whenSynced;
provider.disconnect();
const update = await downloadBinary(workspace.id, rootDBName);
expect(update).toBeInstanceOf(Uint8Array);
const newWorkspace = new Workspace({
id,
isSSR: true,
});
newWorkspace.register(AffineSchemas).register(__unstableSchemas);
applyUpdate(newWorkspace.doc, update);
await new Promise<void>(resolve =>
setTimeout(() => {
expect(workspace.doc.toJSON()).toEqual(newWorkspace.doc.toJSON());
resolve();
}, 0)
);
});
});

View File

@@ -1,5 +1,5 @@
import { openDB } from 'idb';
import type { DBSchema, IDBPDatabase } from 'idb/build/entry';
import type { IDBPDatabase } from 'idb/build/entry';
import {
applyUpdate,
diffUpdate,
@@ -10,11 +10,23 @@ import {
UndoManager,
} from 'yjs';
import type {
BlockSuiteBinaryDB,
IndexedDBProvider,
OldYjsDB,
WorkspaceMilestone,
} from './shared';
import { dbVersion, DEFAULT_DB_NAME, upgradeDB } from './shared';
const indexeddbOrigin = Symbol('indexeddb-provider-origin');
const snapshotOrigin = Symbol('snapshot-origin');
let mergeCount = 500;
export function setMergeCount(count: number) {
mergeCount = count;
}
async function databaseExists(name: string): Promise<boolean> {
return new Promise(resolve => {
const req = indexedDB.open(name);
@@ -78,62 +90,11 @@ export class EarlyDisconnectError extends Error {
}
}
export function setMergeCount(count: number) {
mergeCount = count;
}
export const dbVersion = 1;
export function upgradeDB(db: IDBPDatabase<BlockSuiteBinaryDB>) {
db.createObjectStore('workspace', { keyPath: 'id' });
db.createObjectStore('milestone', { keyPath: 'id' });
}
export interface IndexedDBProvider {
connect: () => void;
disconnect: () => void;
cleanup: () => void;
whenSynced: Promise<void>;
}
export type UpdateMessage = {
timestamp: number;
update: Uint8Array;
};
export type WorkspacePersist = {
id: string;
updates: UpdateMessage[];
};
export type WorkspaceMilestone = {
id: string;
milestone: Record<string, Uint8Array>;
};
export interface BlockSuiteBinaryDB extends DBSchema {
workspace: {
key: string;
value: WorkspacePersist;
};
milestone: {
key: string;
value: WorkspaceMilestone;
};
}
export interface OldYjsDB extends DBSchema {
updates: {
key: number;
value: Uint8Array;
};
}
export const markMilestone = async (
id: string,
doc: Doc,
name: string,
dbName = 'affine-local'
dbName = DEFAULT_DB_NAME
): Promise<void> => {
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
upgrade: upgradeDB,
@@ -159,7 +120,7 @@ export const markMilestone = async (
export const getMilestones = async (
id: string,
dbName = 'affine-local'
dbName = DEFAULT_DB_NAME
): Promise<null | WorkspaceMilestone['milestone']> => {
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
upgrade: upgradeDB,
@@ -180,7 +141,7 @@ let allDb: IDBDatabaseInfo[];
export const createIndexedDBProvider = (
id: string,
doc: Doc,
dbName = 'affine-local'
dbName = DEFAULT_DB_NAME
): IndexedDBProvider => {
let resolve: () => void;
let reject: (reason?: unknown) => void;
@@ -356,7 +317,12 @@ export const createIndexedDBProvider = (
if (!data) {
await db.put('workspace', {
id,
updates: [],
updates: [
{
timestamp: Date.now(),
update: encodeStateAsUpdate(doc),
},
],
});
} else {
const updates = data.updates.map(({ update }) => update);
@@ -406,3 +372,6 @@ export const createIndexedDBProvider = (
return apis;
};
export * from './shared';
export * from './utils';

View File

@@ -0,0 +1,49 @@
import type { DBSchema, IDBPDatabase } from 'idb/build/entry';
export const dbVersion = 1;
export const DEFAULT_DB_NAME = 'affine-local' as const;
export function upgradeDB(db: IDBPDatabase<BlockSuiteBinaryDB>) {
db.createObjectStore('workspace', { keyPath: 'id' });
db.createObjectStore('milestone', { keyPath: 'id' });
}
export interface IndexedDBProvider {
connect: () => void;
disconnect: () => void;
cleanup: () => void;
whenSynced: Promise<void>;
}
export type UpdateMessage = {
timestamp: number;
update: Uint8Array;
};
export type WorkspacePersist = {
id: string;
updates: UpdateMessage[];
};
export type WorkspaceMilestone = {
id: string;
milestone: Record<string, Uint8Array>;
};
export interface BlockSuiteBinaryDB extends DBSchema {
workspace: {
key: string;
value: WorkspacePersist;
};
milestone: {
key: string;
value: WorkspaceMilestone;
};
}
export interface OldYjsDB extends DBSchema {
updates: {
key: number;
value: Uint8Array;
};
}

View File

@@ -0,0 +1,22 @@
import { openDB } from 'idb';
import { mergeUpdates } from 'yjs';
import type { BlockSuiteBinaryDB, UpdateMessage } from './shared';
import { dbVersion, DEFAULT_DB_NAME, upgradeDB } from './shared';
export async function downloadBinary(
id: string,
dbName = DEFAULT_DB_NAME
): Promise<UpdateMessage['update']> {
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
upgrade: upgradeDB,
});
const db = await dbPromise;
const t = db.transaction('workspace', 'readonly').objectStore('workspace');
const doc = await t.get(id);
if (!doc) {
return new Uint8Array(0);
} else {
return mergeUpdates(doc.updates.map(({ update }) => update));
}
}