From 9d60ec14dda5ec145b72158a9c2b4e5c5ffb2bbd Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 27 Nov 2025 21:30:17 +0000 Subject: [PATCH] Improve API --- frontend/local-client-cli/src/cli.ts | 1 + .../obsidian-plugin/src/vault-link-plugin.ts | 6 +- frontend/sync-client/src/consts.ts | 1 + frontend/sync-client/src/sync-client.ts | 104 ++++++++---------- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/frontend/local-client-cli/src/cli.ts b/frontend/local-client-cli/src/cli.ts index bc84b565..36449d8d 100644 --- a/frontend/local-client-cli/src/cli.ts +++ b/frontend/local-client-cli/src/cli.ts @@ -188,6 +188,7 @@ async function main(): Promise { ); fileWatcher.stop(); + await client.waitUntilFinished(); await client.destroy(); console.log(colorize("Shutdown complete", "green")); process.exit(0); diff --git a/frontend/obsidian-plugin/src/vault-link-plugin.ts b/frontend/obsidian-plugin/src/vault-link-plugin.ts index 336f9750..74cbf381 100644 --- a/frontend/obsidian-plugin/src/vault-link-plugin.ts +++ b/frontend/obsidian-plugin/src/vault-link-plugin.ts @@ -49,7 +49,11 @@ export default class VaultLinkPlugin extends Plugin { this.registerEditorEvents(client); - this.register(async () => client.destroy()); + this.register(async () => { + await client.waitUntilFinished(); + await client.destroy(); + }); + await client.start(); }); } diff --git a/frontend/sync-client/src/consts.ts b/frontend/sync-client/src/consts.ts index 8fa50f47..b90c48c3 100644 --- a/frontend/sync-client/src/consts.ts +++ b/frontend/sync-client/src/consts.ts @@ -3,3 +3,4 @@ export const DIFF_CACHE_SIZE_MB = 2; export const MAX_LOG_MESSAGE_COUNT = 100000; export const MAX_HISTORY_ENTRY_COUNT = 5000; export const SUPPORTED_API_VERSION = 1; +export const WEBSOCKET_DISCONNECT_TIMEOUT_IN_S = 10; diff --git a/frontend/sync-client/src/sync-client.ts b/frontend/sync-client/src/sync-client.ts index 06c839c9..0ca98137 100644 --- a/frontend/sync-client/src/sync-client.ts +++ b/frontend/sync-client/src/sync-client.ts @@ -39,7 +39,6 @@ export class SyncClient { private readonly settings: Settings, private readonly database: Database, private readonly syncer: Syncer, - private readonly syncService: SyncService, private readonly webSocketManager: WebSocketManager, public readonly logger: Logger, private readonly fetchController: FetchController, @@ -57,14 +56,10 @@ export class SyncClient { ) {} public get documentCount(): number { - this.checkIfDestroyed(); - return this.database.length; } public get isWebSocketConnected(): boolean { - this.checkIfDestroyed(); - return this.webSocketManager.isWebSocketConnected; } public static async create({ @@ -195,7 +190,6 @@ export class SyncClient { settings, database, syncer, - syncService, webSocketManager, logger, fetchController, @@ -213,7 +207,7 @@ export class SyncClient { } public async start(): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("start"); if (this.hasStarted) { throw new Error("SyncClient has already been started"); @@ -250,7 +244,7 @@ export class SyncClient { * retaining current in-memory settings. */ public async reloadSettings(): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("reloadSettings"); const state = (await this.persistence.load()) ?? { settings: undefined @@ -265,7 +259,7 @@ export class SyncClient { } public async checkConnection(): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("checkConnection"); const server = await this.serverConfig.checkConnection(true); return { @@ -276,15 +270,13 @@ export class SyncClient { } public getHistoryEntries(): readonly HistoryEntry[] { - this.checkIfDestroyed(); - return this.history.entries; } public addSyncHistoryUpdateListener( listener: (stats: HistoryStats) => unknown ): void { - this.checkIfDestroyed(); + this.checkIfDestroyed("addSyncHistoryUpdateListener"); this.history.addSyncHistoryUpdateListener(listener); } @@ -295,7 +287,7 @@ export class SyncClient { * The SyncClient can be used again after calling this method. */ public async applyChangedConnectionSettings(): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("applyChangedConnectionSettings"); this.logger.info( "Stopping SyncClient to apply changed connection settings" @@ -311,35 +303,10 @@ export class SyncClient { this.hasFinishedOfflineSync = false; this.serverConfig.reset(); - // restart syncing - this.fetchController.finishReset(); await this.startSyncing(); } - /** - * Completely destroy the SyncClient, cancelling all in-progress operations. - * After calling this method, the SyncClient cannot be used again. - */ - public async destroy(): Promise { - this.checkIfDestroyed(); - - // cancel everything that's in progress - this.fetchController.startReset(); - await this.pause(); - - this.hasBeenDestroyed = true; - - // clean-up memory early - this.resetInMemoryState(); - - this.logger.info("SyncClient has been successfully disposed"); - - this.unloadTelemetry?.(); - } - public getSettings(): SyncSettings { - this.checkIfDestroyed(); - return this.settings.getSettings(); } @@ -347,13 +314,13 @@ export class SyncClient { key: T, value: SyncSettings[T] ): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("setSetting"); await this.settings.setSetting(key, value); } public async setSettings(value: Partial): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("setSettings"); await this.settings.setSettings(value); } @@ -361,7 +328,7 @@ export class SyncClient { public addOnSettingsChangeListener( listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown ): void { - this.checkIfDestroyed(); + this.checkIfDestroyed("addOnSettingsChangeListener"); this.settings.addOnSettingsChangeListener(listener); } @@ -369,13 +336,13 @@ export class SyncClient { public addRemainingSyncOperationsListener( listener: (remainingOperations: number) => unknown ): void { - this.checkIfDestroyed(); + this.checkIfDestroyed("addRemainingSyncOperationsListener"); this.syncer.addRemainingOperationsListener(listener); } public addWebSocketStatusChangeListener(listener: () => unknown): void { - this.checkIfDestroyed(); + this.checkIfDestroyed("addWebSocketStatusChangeListener"); this.webSocketManager.addWebSocketStatusChangeListener(listener); } @@ -383,7 +350,7 @@ export class SyncClient { public async syncLocallyCreatedFile( relativePath: RelativePath ): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("syncLocallyCreatedFile"); this.fileChangeNotifier.notifyOfFileChange(relativePath); return this.syncer.syncLocallyCreatedFile(relativePath); @@ -392,7 +359,7 @@ export class SyncClient { public async syncLocallyDeletedFile( relativePath: RelativePath ): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("syncLocallyDeletedFile"); this.fileChangeNotifier.notifyOfFileChange(relativePath); return this.syncer.syncLocallyDeletedFile(relativePath); @@ -405,7 +372,7 @@ export class SyncClient { oldPath?: RelativePath; relativePath: RelativePath; }): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("syncLocallyUpdatedFile"); this.fileChangeNotifier.notifyOfFileChange(relativePath); return this.syncer.syncLocallyUpdatedFile({ @@ -417,7 +384,7 @@ export class SyncClient { public getDocumentSyncingStatus( relativePath: RelativePath ): DocumentSyncStatus { - this.checkIfDestroyed(); + this.checkIfDestroyed("getDocumentSyncingStatus"); if (!this.settings.getSettings().isSyncEnabled) { return DocumentSyncStatus.SYNCING_IS_DISABLED; @@ -440,7 +407,7 @@ export class SyncClient { public async updateLocalCursors( documentToCursors: Record ): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("updateLocalCursors"); await this.cursorTracker.sendLocalCursorsToServer(documentToCursors); } @@ -448,13 +415,40 @@ export class SyncClient { public addRemoteCursorsUpdateListener( listener: (cursors: MaybeOutdatedClientCursors[]) => unknown ): void { - this.checkIfDestroyed(); + this.checkIfDestroyed("addRemoteCursorsUpdateListener"); this.cursorTracker.addRemoteCursorsUpdateListener(listener); } + public async waitUntilFinished(): Promise { + this.checkIfDestroyed("waitUntilIdle"); + await this.syncer.waitUntilFinished(); + await this.webSocketManager.waitUntilFinished(); + await this.database.save(); // flush all changes to disk + } + + /** + * Completely destroy the SyncClient, cancelling all in-progress operations. + * After calling this method, the SyncClient cannot be used again. + */ + public async destroy(): Promise { + this.checkIfDestroyed("destroy"); + + // cancel everything that's in progress + await this.pause(); + + this.hasBeenDestroyed = true; + + this.resetInMemoryState(); + + this.logger.info("SyncClient has been successfully disposed"); + + this.unloadTelemetry?.(); + } + private async startSyncing(): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("startSyncing"); + this.fetchController.finishReset(); await this.serverConfig.initialize(); @@ -464,15 +458,13 @@ export class SyncClient { } this.hasFinishedOfflineSync = true; - this.fetchController.finishReset(); this.webSocketManager.start(); } private async pause(): Promise { this.fetchController.startReset(); await this.webSocketManager.stop(); - await this.syncer.waitUntilFinished(); - await this.database.save(); // flush all changes to disk + await this.waitUntilFinished(); } private resetInMemoryState(): void { @@ -488,7 +480,7 @@ export class SyncClient { newSettings: SyncSettings, oldSettings: SyncSettings ): Promise { - this.checkIfDestroyed(); + this.checkIfDestroyed("onSettingsChange"); if ( newSettings.vaultName !== oldSettings.vaultName || @@ -518,10 +510,10 @@ export class SyncClient { } } - private checkIfDestroyed(): void { + private checkIfDestroyed(origin: string): void { if (this.hasBeenDestroyed) { throw new Error( - "SyncClient has been destroyed and can no longer be used." + `SyncClient has been destroyed and can no longer be used; called from ${origin}` ); } }