vault-link/frontend/test-support/sync-fixture.ts
2026-09-27 11:09:36 +01:00

140 lines
5.6 KiB
TypeScript

import type { UploadQueue } from "../sync-client/src/sync-operations/upload-queue";
import type { ContentSync } from "../sync-client/src/sync-operations/content-sync";
import { fetchRemoteChanges, type RemoteApplicationOptions } from "../sync-client/src/sync-operations/remote-changes";
import type { EventBatch } from "../sync-client/src/services/types/EventBatch";
import type { FileManifest } from "../sync-client/src/services/types/FileManifest";
import type { VaultSnapshot } from "../sync-client/src/services/types/VaultSnapshot";
import { SyncPhase } from "../sync-client/src/sync-operations/sync-scheduler";
import {
Database,
createEmptyDatabase,
type StoredDatabase
} from "../sync-client/src/persistence/database";
import { Settings } from "../sync-client/src/persistence/settings";
import { FileOperations } from "../sync-client/src/file-operations/file-operations";
import { Syncer } from "../sync-client/src/sync-operations/syncer";
import { hashSnapshot } from "../sync-client/src/snapshot";
import { Logger } from "../sync-client/src/tracing/logger";
import { SyncHistory } from "../sync-client/src/tracing/sync-history";
import { FixedSizeDocumentCache } from "../sync-client/src/utils/data-structures/fix-sized-cache";
import { EventListeners } from "../sync-client/src/utils/data-structures/event-listeners";
import type { ServerConfig } from "../sync-client/src/services/server-config";
import type { SyncService } from "../sync-client/src/services/sync-service";
import type { WebSocketManager } from "../sync-client/src/services/websocket-manager";
import { MemoryDisk, MemoryPersistence } from "./storage";
import type { LocalChange } from "../sync-client/src/sync-operations/local-changes";
export const bytes = (text: string) => new TextEncoder().encode(text);
export const head = (id: string, version: number, content: string) => ({
documentId: id,
vaultUpdateId: version,
contentSize: bytes(content).length,
updatedDate: "2026-09-19T00:00:00Z",
userId: "user",
deviceId: "device"
});
export async function fixture(
contents: Record<string, string> = { "a.md": "local" },
service: Partial<SyncService> = {},
recovered?: { disk: MemoryDisk; persistence: MemoryPersistence }
) {
const disk = recovered?.disk ?? new MemoryDisk();
const initial = recovered
? (recovered.persistence.snapshot().database as StoredDatabase)
: createEmptyDatabase("test");
if (!recovered) {
initial.lastSeenUpdateId = 0;
for (const [path, content] of Object.entries(contents)) {
await disk.userWrite(path, bytes(content));
const id = path.split(".")[0];
initial.local[id] = path;
initial.documents[id] = {
observedHash: (
await hashSnapshot({ content: bytes(content) })
).hash
};
}
initial.fileManifest = {
fileManifestId: 1,
entries: { ...initial.local }
};
}
const persistence =
recovered?.persistence ?? new MemoryPersistence({ database: initial });
const logger = new Logger();
const database = new Database(
initial,
async (next) => persistence.save({ database: next }),
"test",
async () => (await persistence.load()).database as StoredDatabase
);
const config = {
getConfig: async () => ({ mergeableFileExtensions: ["md"] }),
reset: () => {}
} as unknown as ServerConfig;
const settings = new Settings(
logger,
{ syncIntervalMs: 0 },
async () => {}
);
const changes: LocalChange[] = [];
const files = new FileOperations(
disk.session(),
database,
config,
undefined,
{ entries: () => changes, flush: async () => {} },
(path, size) => settings.isIgnored(path) || settings.isOversized(size)
);
const websocket = {
onWebSocketStatusChanged: new EventListeners(),
onRemoteVaultUpdateReceived: new EventListeners()
} as unknown as WebSocketManager;
const contentCache = new FixedSizeDocumentCache(1024);
const syncer = new Syncer(
logger,
database,
settings,
service as SyncService,
websocket,
files,
config,
new SyncHistory(logger),
contentCache,
{ entries: changes, save: async () => {} }
);
const internals = syncer as unknown as {
content: ContentSync;
uploads: UploadQueue;
prepareNextUpload: () => Promise<boolean>;
refreshLocalFiles: (
initial?: import("../sync-client/src/services/types/VaultSnapshot").VaultSnapshot
) => Promise<void>;
scheduler: { phase: SyncPhase };
applyRemoteChanges: (remote: FileManifest, options?: RemoteApplicationOptions) => Promise<void>;
};
return {
disk,
database,
files,
settings,
syncer,
internals: {
content: internals.content,
uploads: internals.uploads,
scheduler: internals.scheduler,
prepareNextUpload: () => internals.prepareNextUpload(),
refreshLocalFiles: (initial?: VaultSnapshot) => internals.refreshLocalFiles(initial),
applyRemoteChanges: (manifest: FileManifest) => internals.applyRemoteChanges(manifest),
applyEventBatch: async (batch: EventBatch) => {
const remote = await fetchRemoteChanges(service as SyncService, database.state, batch);
await internals.applyRemoteChanges(remote.manifest, remote);
}
},
persistence,
changes,
contentCache
};
}