mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 05:48:59 +08:00
feat: init disk remote source
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"typescript": "^5.9.3"
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,20 @@ export function setupStoreManager(framework: Framework) {
|
||||
|
||||
framework.impl(NbstoreProvider, {
|
||||
openStore(key, options) {
|
||||
try {
|
||||
// E2E/debug only: capture init options passed to the nbstore worker.
|
||||
(globalThis as any).__e2eNbstoreOpenStoreLogs =
|
||||
(globalThis as any).__e2eNbstoreOpenStoreLogs ?? [];
|
||||
(globalThis as any).__e2eNbstoreOpenStoreLogs.push({
|
||||
key,
|
||||
remotes: Object.keys(options?.remotes ?? {}),
|
||||
diskSyncFolder:
|
||||
(options as any)?.remotes?.['disk']?.doc?.opts?.syncFolder ?? null,
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const { store, dispose } = storeManagerClient.open(key, options);
|
||||
|
||||
return {
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import type { DiskSyncEvent } from '@affine/nbstore/disk';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { createDiskSyncApis } from './disk-sync-bridge';
|
||||
|
||||
describe('createDiskSyncApis', () => {
|
||||
it('forwards handler calls and filters events by session id', async () => {
|
||||
const startSession = vi.fn(async () => {});
|
||||
const stopSession = vi.fn(async () => {});
|
||||
const applyLocalUpdate = vi.fn(async () => ({
|
||||
docId: 'doc-1',
|
||||
timestamp: new Date('2026-01-04T00:00:00.000Z'),
|
||||
}));
|
||||
|
||||
const listeners = new Set<
|
||||
(payload: { sessionId: string; event: DiskSyncEvent }) => void
|
||||
>();
|
||||
const onEvent = vi.fn(
|
||||
(
|
||||
callback: (payload: { sessionId: string; event: DiskSyncEvent }) => void
|
||||
) => {
|
||||
listeners.add(callback);
|
||||
return () => {
|
||||
listeners.delete(callback);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const apis = createDiskSyncApis(
|
||||
{ startSession, stopSession, applyLocalUpdate },
|
||||
{ onEvent }
|
||||
);
|
||||
|
||||
await apis.startSession('session-a', {
|
||||
workspaceId: 'workspace-a',
|
||||
syncFolder: '/tmp/sync-a',
|
||||
});
|
||||
await apis.stopSession('session-a');
|
||||
await apis.applyLocalUpdate('session-a', {
|
||||
docId: 'doc-1',
|
||||
bin: new Uint8Array([1, 2, 3]),
|
||||
});
|
||||
|
||||
expect(startSession).toHaveBeenCalledWith('session-a', {
|
||||
workspaceId: 'workspace-a',
|
||||
syncFolder: '/tmp/sync-a',
|
||||
});
|
||||
expect(stopSession).toHaveBeenCalledWith('session-a');
|
||||
expect(applyLocalUpdate).toHaveBeenCalledWith(
|
||||
'session-a',
|
||||
expect.objectContaining({ docId: 'doc-1' }),
|
||||
undefined
|
||||
);
|
||||
|
||||
const callback = vi.fn();
|
||||
const unsubscribe = apis.subscribeEvents('session-a', callback);
|
||||
|
||||
expect(onEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
const docUpdate: DiskSyncEvent = {
|
||||
type: 'doc-update',
|
||||
update: {
|
||||
docId: 'doc-2',
|
||||
bin: new Uint8Array([4, 5, 6]),
|
||||
timestamp: new Date('2026-01-04T00:00:01.000Z'),
|
||||
},
|
||||
};
|
||||
|
||||
for (const listener of listeners) {
|
||||
listener({ sessionId: 'session-b', event: { type: 'ready' } });
|
||||
listener({ sessionId: 'session-a', event: docUpdate });
|
||||
}
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(callback).toHaveBeenCalledWith(docUpdate);
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { DocClock, DocUpdate } from '@affine/nbstore';
|
||||
import type {
|
||||
DiskSessionOptions,
|
||||
DiskSyncApis,
|
||||
DiskSyncEvent,
|
||||
} from '@affine/nbstore/disk';
|
||||
|
||||
type DiskSyncEventPayload = {
|
||||
sessionId: string;
|
||||
event: DiskSyncEvent;
|
||||
};
|
||||
|
||||
interface DiskSyncHandlers {
|
||||
startSession: (
|
||||
sessionId: string,
|
||||
options: DiskSessionOptions
|
||||
) => Promise<void>;
|
||||
stopSession: (sessionId: string) => Promise<void>;
|
||||
applyLocalUpdate: (
|
||||
sessionId: string,
|
||||
update: DocUpdate,
|
||||
origin?: string
|
||||
) => Promise<DocClock>;
|
||||
}
|
||||
|
||||
interface DiskSyncEvents {
|
||||
onEvent: (callback: (payload: DiskSyncEventPayload) => void) => () => void;
|
||||
}
|
||||
|
||||
export function createDiskSyncApis(
|
||||
handlers: DiskSyncHandlers,
|
||||
events: DiskSyncEvents
|
||||
): DiskSyncApis {
|
||||
return {
|
||||
startSession: (sessionId, options) => {
|
||||
return handlers.startSession(sessionId, options);
|
||||
},
|
||||
stopSession: sessionId => {
|
||||
return handlers.stopSession(sessionId);
|
||||
},
|
||||
applyLocalUpdate: (sessionId, update, origin) => {
|
||||
return handlers.applyLocalUpdate(sessionId, update, origin);
|
||||
},
|
||||
subscribeEvents: (sessionId, callback) => {
|
||||
return events.onEvent(payload => {
|
||||
if (payload.sessionId === sessionId) {
|
||||
callback(payload.event);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import '@affine/core/bootstrap/electron';
|
||||
|
||||
import { apis } from '@affine/electron-api';
|
||||
import { apis, events } from '@affine/electron-api';
|
||||
import { broadcastChannelStorages } from '@affine/nbstore/broadcast-channel';
|
||||
import { cloudStorages } from '@affine/nbstore/cloud';
|
||||
import { bindDiskSyncApis, diskStorages } from '@affine/nbstore/disk';
|
||||
import { bindNativeDBApis, sqliteStorages } from '@affine/nbstore/sqlite';
|
||||
import {
|
||||
bindNativeDBV1Apis,
|
||||
@@ -14,14 +15,19 @@ import {
|
||||
} from '@affine/nbstore/worker/consumer';
|
||||
import { OpConsumer } from '@toeverything/infra/op';
|
||||
|
||||
import { createDiskSyncApis } from './disk-sync-bridge';
|
||||
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
bindNativeDBApis(apis!.nbstore);
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
bindNativeDBV1Apis(apis!.db);
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
bindDiskSyncApis(createDiskSyncApis(apis!.diskSync, events!.diskSync));
|
||||
|
||||
const storeManager = new StoreManagerConsumer([
|
||||
...sqliteStorages,
|
||||
...sqliteV1Storages,
|
||||
...diskStorages,
|
||||
...broadcastChannelStorages,
|
||||
...cloudStorages,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user