mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
fix(y-indexeddb): migration in firefox (#1904)
This commit is contained in:
@@ -7,6 +7,7 @@ import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { openDB } from 'idb';
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { IndexeddbPersistence } from 'y-indexeddb';
|
||||
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import type { WorkspacePersist } from '../index';
|
||||
@@ -45,6 +46,7 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
indexedDB.deleteDatabase('affine-local');
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('indexeddb provider', () => {
|
||||
@@ -196,6 +198,38 @@ describe('indexeddb provider', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('migration', async () => {
|
||||
{
|
||||
const yDoc = new Doc();
|
||||
yDoc.getMap().set('foo', 'bar');
|
||||
const persistence = new IndexeddbPersistence('test', yDoc);
|
||||
await persistence.whenSynced;
|
||||
persistence.destroy();
|
||||
}
|
||||
{
|
||||
const yDoc = new Doc();
|
||||
const provider = createIndexedDBProvider('test', yDoc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
expect(yDoc.getMap().get('foo')).toBe('bar');
|
||||
}
|
||||
localStorage.clear();
|
||||
{
|
||||
indexedDB.databases = vi.fn(async () => {
|
||||
throw new Error('not supported');
|
||||
});
|
||||
expect(indexedDB.databases).rejects.toThrow('not supported');
|
||||
const yDoc = new Doc();
|
||||
expect(indexedDB.databases).toBeCalledTimes(1);
|
||||
const provider = createIndexedDBProvider('test', yDoc);
|
||||
provider.connect();
|
||||
await provider.whenSynced;
|
||||
expect(indexedDB.databases).toBeCalledTimes(2);
|
||||
expect(yDoc.getMap().get('foo')).toBe('bar');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('milestone', () => {
|
||||
|
||||
@@ -15,6 +15,23 @@ const snapshotOrigin = Symbol('snapshot-origin');
|
||||
|
||||
let mergeCount = 500;
|
||||
|
||||
async function databaseExists(name: string): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const req = indexedDB.open(name);
|
||||
let existed = true;
|
||||
req.onsuccess = function () {
|
||||
req.result.close();
|
||||
if (!existed) {
|
||||
indexedDB.deleteDatabase(name);
|
||||
}
|
||||
resolve(existed);
|
||||
};
|
||||
req.onupgradeneeded = function () {
|
||||
existed = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function revertUpdate(
|
||||
doc: Doc,
|
||||
snapshotUpdate: Uint8Array,
|
||||
@@ -158,12 +175,13 @@ export const getMilestones = async (
|
||||
return milestone.milestone;
|
||||
};
|
||||
|
||||
let allDb: IDBDatabaseInfo[];
|
||||
|
||||
export const createIndexedDBProvider = (
|
||||
id: string,
|
||||
doc: Doc,
|
||||
dbName = 'affine-local'
|
||||
): IndexedDBProvider => {
|
||||
let allDb: IDBDatabaseInfo[];
|
||||
let resolve: () => void;
|
||||
let reject: (reason?: unknown) => void;
|
||||
let early = true;
|
||||
@@ -238,53 +256,96 @@ export const createIndexedDBProvider = (
|
||||
doc.on('destroy', handleDestroy);
|
||||
// only run promise below, otherwise the logic is incorrect
|
||||
const db = await dbPromise;
|
||||
if (!allDb || localStorage.getItem(`${dbName}-migration`) !== 'true') {
|
||||
allDb = await indexedDB.databases();
|
||||
// run the migration
|
||||
await Promise.all(
|
||||
allDb.map(meta => {
|
||||
if (meta.name && meta.version === 1) {
|
||||
const name = meta.name;
|
||||
const version = meta.version;
|
||||
return openDB<IDBPDatabase<OldYjsDB>>(name, version).then(
|
||||
async oldDB => {
|
||||
if (!oldDB.objectStoreNames.contains('updates')) {
|
||||
return;
|
||||
}
|
||||
const t = oldDB
|
||||
.transaction('updates', 'readonly')
|
||||
.objectStore('updates');
|
||||
const updates = await t.getAll();
|
||||
if (
|
||||
!Array.isArray(updates) ||
|
||||
!updates.every(update => update instanceof Uint8Array)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const update = mergeUpdates(updates);
|
||||
const workspaceTransaction = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
const data = await workspaceTransaction.get(name);
|
||||
if (!data) {
|
||||
console.log('upgrading the database');
|
||||
await workspaceTransaction.put({
|
||||
id: name,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
do {
|
||||
if (!allDb || localStorage.getItem(`${dbName}-migration`) !== 'true') {
|
||||
try {
|
||||
allDb = await indexedDB.databases();
|
||||
} catch {
|
||||
// in firefox, `indexedDB.databases` is not exist
|
||||
if (await databaseExists(id)) {
|
||||
await openDB<IDBPDatabase<OldYjsDB>>(id, 1).then(async oldDB => {
|
||||
if (!oldDB.objectStoreNames.contains('updates')) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
const t = oldDB
|
||||
.transaction('updates', 'readonly')
|
||||
.objectStore('updates');
|
||||
const updates = await t.getAll();
|
||||
if (
|
||||
!Array.isArray(updates) ||
|
||||
!updates.every(update => update instanceof Uint8Array)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const update = mergeUpdates(updates);
|
||||
const workspaceTransaction = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
const data = await workspaceTransaction.get(id);
|
||||
if (!data) {
|
||||
console.log('upgrading the database');
|
||||
await workspaceTransaction.put({
|
||||
id,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
localStorage.setItem(`${dbName}-migration`, 'true');
|
||||
}
|
||||
}
|
||||
// run the migration
|
||||
await Promise.all(
|
||||
allDb.map(meta => {
|
||||
if (meta.name && meta.version === 1) {
|
||||
const name = meta.name;
|
||||
const version = meta.version;
|
||||
return openDB<IDBPDatabase<OldYjsDB>>(name, version).then(
|
||||
async oldDB => {
|
||||
if (!oldDB.objectStoreNames.contains('updates')) {
|
||||
return;
|
||||
}
|
||||
const t = oldDB
|
||||
.transaction('updates', 'readonly')
|
||||
.objectStore('updates');
|
||||
const updates = await t.getAll();
|
||||
if (
|
||||
!Array.isArray(updates) ||
|
||||
!updates.every(update => update instanceof Uint8Array)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const update = mergeUpdates(updates);
|
||||
const workspaceTransaction = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
const data = await workspaceTransaction.get(name);
|
||||
if (!data) {
|
||||
console.log('upgrading the database');
|
||||
await workspaceTransaction.put({
|
||||
id: name,
|
||||
updates: [
|
||||
{
|
||||
timestamp: Date.now(),
|
||||
update,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
localStorage.setItem(`${dbName}-migration`, 'true');
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
} while (false);
|
||||
const store = db
|
||||
.transaction('workspace', 'readwrite')
|
||||
.objectStore('workspace');
|
||||
|
||||
Reference in New Issue
Block a user