mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
fix: first binary on y-indexeddb (#1972)
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import 'fake-indexeddb/auto';
|
import 'fake-indexeddb/auto';
|
||||||
|
|
||||||
|
import { initPage } from '@affine/env/blocksuite';
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
||||||
import { openDB } from 'idb';
|
import { openDB } from 'idb';
|
||||||
@@ -14,13 +15,15 @@ import type { WorkspacePersist } from '../index';
|
|||||||
import {
|
import {
|
||||||
createIndexedDBProvider,
|
createIndexedDBProvider,
|
||||||
dbVersion,
|
dbVersion,
|
||||||
|
DEFAULT_DB_NAME,
|
||||||
|
downloadBinary,
|
||||||
getMilestones,
|
getMilestones,
|
||||||
markMilestone,
|
markMilestone,
|
||||||
revertUpdate,
|
revertUpdate,
|
||||||
setMergeCount,
|
setMergeCount,
|
||||||
} from '../index';
|
} from '../index';
|
||||||
|
|
||||||
async function getUpdates(id: string): Promise<ArrayBuffer[]> {
|
async function getUpdates(id: string): Promise<Uint8Array[]> {
|
||||||
const db = await openDB(rootDBName, dbVersion);
|
const db = await openDB(rootDBName, dbVersion);
|
||||||
const store = await db
|
const store = await db
|
||||||
.transaction('workspace', 'readonly')
|
.transaction('workspace', 'readonly')
|
||||||
@@ -33,7 +36,7 @@ async function getUpdates(id: string): Promise<ArrayBuffer[]> {
|
|||||||
|
|
||||||
let id: string;
|
let id: string;
|
||||||
let workspace: Workspace;
|
let workspace: Workspace;
|
||||||
const rootDBName = 'affine-local';
|
const rootDBName = DEFAULT_DB_NAME;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
id = uuidv4();
|
id = uuidv4();
|
||||||
@@ -62,7 +65,12 @@ describe('indexeddb provider', () => {
|
|||||||
const data = await store.get(id);
|
const data = await store.get(id);
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
id,
|
id,
|
||||||
updates: [],
|
updates: [
|
||||||
|
{
|
||||||
|
timestamp: expect.any(Number),
|
||||||
|
update: encodeStateAsUpdate(workspace.doc),
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const page = workspace.createPage('page0');
|
const page = workspace.createPage('page0');
|
||||||
const pageBlockId = page.addBlock('affine:page', { title: '' });
|
const pageBlockId = page.addBlock('affine:page', { title: '' });
|
||||||
@@ -129,6 +137,7 @@ describe('indexeddb provider', () => {
|
|||||||
provider.connect();
|
provider.connect();
|
||||||
const p1 = provider.whenSynced;
|
const p1 = provider.whenSynced;
|
||||||
await p1;
|
await p1;
|
||||||
|
const snapshot = encodeStateAsUpdate(workspace.doc);
|
||||||
provider.disconnect();
|
provider.disconnect();
|
||||||
{
|
{
|
||||||
const page = workspace.createPage('page0');
|
const page = workspace.createPage('page0');
|
||||||
@@ -138,7 +147,8 @@ describe('indexeddb provider', () => {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
const updates = await getUpdates(workspace.id);
|
const updates = await getUpdates(workspace.id);
|
||||||
expect(updates).toEqual([]);
|
expect(updates.length).toBe(1);
|
||||||
|
expect(updates[0]).toEqual(snapshot);
|
||||||
}
|
}
|
||||||
provider.connect();
|
provider.connect();
|
||||||
const p2 = provider.whenSynced;
|
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)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { openDB } from 'idb';
|
import { openDB } from 'idb';
|
||||||
import type { DBSchema, IDBPDatabase } from 'idb/build/entry';
|
import type { IDBPDatabase } from 'idb/build/entry';
|
||||||
import {
|
import {
|
||||||
applyUpdate,
|
applyUpdate,
|
||||||
diffUpdate,
|
diffUpdate,
|
||||||
@@ -10,11 +10,23 @@ import {
|
|||||||
UndoManager,
|
UndoManager,
|
||||||
} from 'yjs';
|
} 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 indexeddbOrigin = Symbol('indexeddb-provider-origin');
|
||||||
const snapshotOrigin = Symbol('snapshot-origin');
|
const snapshotOrigin = Symbol('snapshot-origin');
|
||||||
|
|
||||||
let mergeCount = 500;
|
let mergeCount = 500;
|
||||||
|
|
||||||
|
export function setMergeCount(count: number) {
|
||||||
|
mergeCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
async function databaseExists(name: string): Promise<boolean> {
|
async function databaseExists(name: string): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const req = indexedDB.open(name);
|
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 (
|
export const markMilestone = async (
|
||||||
id: string,
|
id: string,
|
||||||
doc: Doc,
|
doc: Doc,
|
||||||
name: string,
|
name: string,
|
||||||
dbName = 'affine-local'
|
dbName = DEFAULT_DB_NAME
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
||||||
upgrade: upgradeDB,
|
upgrade: upgradeDB,
|
||||||
@@ -159,7 +120,7 @@ export const markMilestone = async (
|
|||||||
|
|
||||||
export const getMilestones = async (
|
export const getMilestones = async (
|
||||||
id: string,
|
id: string,
|
||||||
dbName = 'affine-local'
|
dbName = DEFAULT_DB_NAME
|
||||||
): Promise<null | WorkspaceMilestone['milestone']> => {
|
): Promise<null | WorkspaceMilestone['milestone']> => {
|
||||||
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
|
||||||
upgrade: upgradeDB,
|
upgrade: upgradeDB,
|
||||||
@@ -180,7 +141,7 @@ let allDb: IDBDatabaseInfo[];
|
|||||||
export const createIndexedDBProvider = (
|
export const createIndexedDBProvider = (
|
||||||
id: string,
|
id: string,
|
||||||
doc: Doc,
|
doc: Doc,
|
||||||
dbName = 'affine-local'
|
dbName = DEFAULT_DB_NAME
|
||||||
): IndexedDBProvider => {
|
): IndexedDBProvider => {
|
||||||
let resolve: () => void;
|
let resolve: () => void;
|
||||||
let reject: (reason?: unknown) => void;
|
let reject: (reason?: unknown) => void;
|
||||||
@@ -356,7 +317,12 @@ export const createIndexedDBProvider = (
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
await db.put('workspace', {
|
await db.put('workspace', {
|
||||||
id,
|
id,
|
||||||
updates: [],
|
updates: [
|
||||||
|
{
|
||||||
|
timestamp: Date.now(),
|
||||||
|
update: encodeStateAsUpdate(doc),
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const updates = data.updates.map(({ update }) => update);
|
const updates = data.updates.map(({ update }) => update);
|
||||||
@@ -406,3 +372,6 @@ export const createIndexedDBProvider = (
|
|||||||
|
|
||||||
return apis;
|
return apis;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export * from './shared';
|
||||||
|
export * from './utils';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user