Improve API

This commit is contained in:
Andras Schmelczer 2025-11-27 21:30:17 +00:00
parent d45d2c0be3
commit 9d60ec14dd
4 changed files with 55 additions and 57 deletions

View file

@ -188,6 +188,7 @@ async function main(): Promise<void> {
); );
fileWatcher.stop(); fileWatcher.stop();
await client.waitUntilFinished();
await client.destroy(); await client.destroy();
console.log(colorize("Shutdown complete", "green")); console.log(colorize("Shutdown complete", "green"));
process.exit(0); process.exit(0);

View file

@ -49,7 +49,11 @@ export default class VaultLinkPlugin extends Plugin {
this.registerEditorEvents(client); this.registerEditorEvents(client);
this.register(async () => client.destroy()); this.register(async () => {
await client.waitUntilFinished();
await client.destroy();
});
await client.start(); await client.start();
}); });
} }

View file

@ -3,3 +3,4 @@ export const DIFF_CACHE_SIZE_MB = 2;
export const MAX_LOG_MESSAGE_COUNT = 100000; export const MAX_LOG_MESSAGE_COUNT = 100000;
export const MAX_HISTORY_ENTRY_COUNT = 5000; export const MAX_HISTORY_ENTRY_COUNT = 5000;
export const SUPPORTED_API_VERSION = 1; export const SUPPORTED_API_VERSION = 1;
export const WEBSOCKET_DISCONNECT_TIMEOUT_IN_S = 10;

View file

@ -39,7 +39,6 @@ export class SyncClient {
private readonly settings: Settings, private readonly settings: Settings,
private readonly database: Database, private readonly database: Database,
private readonly syncer: Syncer, private readonly syncer: Syncer,
private readonly syncService: SyncService,
private readonly webSocketManager: WebSocketManager, private readonly webSocketManager: WebSocketManager,
public readonly logger: Logger, public readonly logger: Logger,
private readonly fetchController: FetchController, private readonly fetchController: FetchController,
@ -57,14 +56,10 @@ export class SyncClient {
) {} ) {}
public get documentCount(): number { public get documentCount(): number {
this.checkIfDestroyed();
return this.database.length; return this.database.length;
} }
public get isWebSocketConnected(): boolean { public get isWebSocketConnected(): boolean {
this.checkIfDestroyed();
return this.webSocketManager.isWebSocketConnected; return this.webSocketManager.isWebSocketConnected;
} }
public static async create({ public static async create({
@ -195,7 +190,6 @@ export class SyncClient {
settings, settings,
database, database,
syncer, syncer,
syncService,
webSocketManager, webSocketManager,
logger, logger,
fetchController, fetchController,
@ -213,7 +207,7 @@ export class SyncClient {
} }
public async start(): Promise<void> { public async start(): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("start");
if (this.hasStarted) { if (this.hasStarted) {
throw new Error("SyncClient has already been started"); throw new Error("SyncClient has already been started");
@ -250,7 +244,7 @@ export class SyncClient {
* retaining current in-memory settings. * retaining current in-memory settings.
*/ */
public async reloadSettings(): Promise<void> { public async reloadSettings(): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("reloadSettings");
const state = (await this.persistence.load()) ?? { const state = (await this.persistence.load()) ?? {
settings: undefined settings: undefined
@ -265,7 +259,7 @@ export class SyncClient {
} }
public async checkConnection(): Promise<NetworkConnectionStatus> { public async checkConnection(): Promise<NetworkConnectionStatus> {
this.checkIfDestroyed(); this.checkIfDestroyed("checkConnection");
const server = await this.serverConfig.checkConnection(true); const server = await this.serverConfig.checkConnection(true);
return { return {
@ -276,15 +270,13 @@ export class SyncClient {
} }
public getHistoryEntries(): readonly HistoryEntry[] { public getHistoryEntries(): readonly HistoryEntry[] {
this.checkIfDestroyed();
return this.history.entries; return this.history.entries;
} }
public addSyncHistoryUpdateListener( public addSyncHistoryUpdateListener(
listener: (stats: HistoryStats) => unknown listener: (stats: HistoryStats) => unknown
): void { ): void {
this.checkIfDestroyed(); this.checkIfDestroyed("addSyncHistoryUpdateListener");
this.history.addSyncHistoryUpdateListener(listener); this.history.addSyncHistoryUpdateListener(listener);
} }
@ -295,7 +287,7 @@ export class SyncClient {
* The SyncClient can be used again after calling this method. * The SyncClient can be used again after calling this method.
*/ */
public async applyChangedConnectionSettings(): Promise<void> { public async applyChangedConnectionSettings(): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("applyChangedConnectionSettings");
this.logger.info( this.logger.info(
"Stopping SyncClient to apply changed connection settings" "Stopping SyncClient to apply changed connection settings"
@ -311,35 +303,10 @@ export class SyncClient {
this.hasFinishedOfflineSync = false; this.hasFinishedOfflineSync = false;
this.serverConfig.reset(); this.serverConfig.reset();
// restart syncing
this.fetchController.finishReset();
await this.startSyncing(); 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<void> {
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 { public getSettings(): SyncSettings {
this.checkIfDestroyed();
return this.settings.getSettings(); return this.settings.getSettings();
} }
@ -347,13 +314,13 @@ export class SyncClient {
key: T, key: T,
value: SyncSettings[T] value: SyncSettings[T]
): Promise<void> { ): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("setSetting");
await this.settings.setSetting(key, value); await this.settings.setSetting(key, value);
} }
public async setSettings(value: Partial<SyncSettings>): Promise<void> { public async setSettings(value: Partial<SyncSettings>): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("setSettings");
await this.settings.setSettings(value); await this.settings.setSettings(value);
} }
@ -361,7 +328,7 @@ export class SyncClient {
public addOnSettingsChangeListener( public addOnSettingsChangeListener(
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
): void { ): void {
this.checkIfDestroyed(); this.checkIfDestroyed("addOnSettingsChangeListener");
this.settings.addOnSettingsChangeListener(listener); this.settings.addOnSettingsChangeListener(listener);
} }
@ -369,13 +336,13 @@ export class SyncClient {
public addRemainingSyncOperationsListener( public addRemainingSyncOperationsListener(
listener: (remainingOperations: number) => unknown listener: (remainingOperations: number) => unknown
): void { ): void {
this.checkIfDestroyed(); this.checkIfDestroyed("addRemainingSyncOperationsListener");
this.syncer.addRemainingOperationsListener(listener); this.syncer.addRemainingOperationsListener(listener);
} }
public addWebSocketStatusChangeListener(listener: () => unknown): void { public addWebSocketStatusChangeListener(listener: () => unknown): void {
this.checkIfDestroyed(); this.checkIfDestroyed("addWebSocketStatusChangeListener");
this.webSocketManager.addWebSocketStatusChangeListener(listener); this.webSocketManager.addWebSocketStatusChangeListener(listener);
} }
@ -383,7 +350,7 @@ export class SyncClient {
public async syncLocallyCreatedFile( public async syncLocallyCreatedFile(
relativePath: RelativePath relativePath: RelativePath
): Promise<void> { ): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("syncLocallyCreatedFile");
this.fileChangeNotifier.notifyOfFileChange(relativePath); this.fileChangeNotifier.notifyOfFileChange(relativePath);
return this.syncer.syncLocallyCreatedFile(relativePath); return this.syncer.syncLocallyCreatedFile(relativePath);
@ -392,7 +359,7 @@ export class SyncClient {
public async syncLocallyDeletedFile( public async syncLocallyDeletedFile(
relativePath: RelativePath relativePath: RelativePath
): Promise<void> { ): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("syncLocallyDeletedFile");
this.fileChangeNotifier.notifyOfFileChange(relativePath); this.fileChangeNotifier.notifyOfFileChange(relativePath);
return this.syncer.syncLocallyDeletedFile(relativePath); return this.syncer.syncLocallyDeletedFile(relativePath);
@ -405,7 +372,7 @@ export class SyncClient {
oldPath?: RelativePath; oldPath?: RelativePath;
relativePath: RelativePath; relativePath: RelativePath;
}): Promise<void> { }): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("syncLocallyUpdatedFile");
this.fileChangeNotifier.notifyOfFileChange(relativePath); this.fileChangeNotifier.notifyOfFileChange(relativePath);
return this.syncer.syncLocallyUpdatedFile({ return this.syncer.syncLocallyUpdatedFile({
@ -417,7 +384,7 @@ export class SyncClient {
public getDocumentSyncingStatus( public getDocumentSyncingStatus(
relativePath: RelativePath relativePath: RelativePath
): DocumentSyncStatus { ): DocumentSyncStatus {
this.checkIfDestroyed(); this.checkIfDestroyed("getDocumentSyncingStatus");
if (!this.settings.getSettings().isSyncEnabled) { if (!this.settings.getSettings().isSyncEnabled) {
return DocumentSyncStatus.SYNCING_IS_DISABLED; return DocumentSyncStatus.SYNCING_IS_DISABLED;
@ -440,7 +407,7 @@ export class SyncClient {
public async updateLocalCursors( public async updateLocalCursors(
documentToCursors: Record<RelativePath, CursorSpan[]> documentToCursors: Record<RelativePath, CursorSpan[]>
): Promise<void> { ): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("updateLocalCursors");
await this.cursorTracker.sendLocalCursorsToServer(documentToCursors); await this.cursorTracker.sendLocalCursorsToServer(documentToCursors);
} }
@ -448,13 +415,40 @@ export class SyncClient {
public addRemoteCursorsUpdateListener( public addRemoteCursorsUpdateListener(
listener: (cursors: MaybeOutdatedClientCursors[]) => unknown listener: (cursors: MaybeOutdatedClientCursors[]) => unknown
): void { ): void {
this.checkIfDestroyed(); this.checkIfDestroyed("addRemoteCursorsUpdateListener");
this.cursorTracker.addRemoteCursorsUpdateListener(listener); this.cursorTracker.addRemoteCursorsUpdateListener(listener);
} }
public async waitUntilFinished(): Promise<void> {
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<void> {
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<void> { private async startSyncing(): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("startSyncing");
this.fetchController.finishReset();
await this.serverConfig.initialize(); await this.serverConfig.initialize();
@ -464,15 +458,13 @@ export class SyncClient {
} }
this.hasFinishedOfflineSync = true; this.hasFinishedOfflineSync = true;
this.fetchController.finishReset();
this.webSocketManager.start(); this.webSocketManager.start();
} }
private async pause(): Promise<void> { private async pause(): Promise<void> {
this.fetchController.startReset(); this.fetchController.startReset();
await this.webSocketManager.stop(); await this.webSocketManager.stop();
await this.syncer.waitUntilFinished(); await this.waitUntilFinished();
await this.database.save(); // flush all changes to disk
} }
private resetInMemoryState(): void { private resetInMemoryState(): void {
@ -488,7 +480,7 @@ export class SyncClient {
newSettings: SyncSettings, newSettings: SyncSettings,
oldSettings: SyncSettings oldSettings: SyncSettings
): Promise<void> { ): Promise<void> {
this.checkIfDestroyed(); this.checkIfDestroyed("onSettingsChange");
if ( if (
newSettings.vaultName !== oldSettings.vaultName || newSettings.vaultName !== oldSettings.vaultName ||
@ -518,10 +510,10 @@ export class SyncClient {
} }
} }
private checkIfDestroyed(): void { private checkIfDestroyed(origin: string): void {
if (this.hasBeenDestroyed) { if (this.hasBeenDestroyed) {
throw new Error( 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}`
); );
} }
} }