feat: init disk remote source

This commit is contained in:
DarkSky
2026-02-27 02:39:53 +08:00
parent 895e774569
commit 6557e5d01d
51 changed files with 6793 additions and 158 deletions
@@ -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 {
@@ -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,
]);