@vrtmrz/livesync-commonlib 0.1.18 → 0.1.19-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,7 +48,9 @@ class DirectFileManipulator {
48
48
  async init() {
49
49
  try {
50
50
  await this.services.appLifecycle.onReady();
51
- await this.liveSyncLocalDB.initializeDatabase();
51
+ if (!await this.liveSyncLocalDB.initializeDatabase()) {
52
+ throw new Error("Direct database initialisation was rejected.");
53
+ }
52
54
  this.liveSyncLocalDB.refreshSettings();
53
55
  this.ready.resolve();
54
56
  } catch (error) {
@@ -59,6 +59,13 @@ export type FileEventItem = {
59
59
  type: FileEventType;
60
60
  args: FileEventArgs;
61
61
  key: string;
62
+ /**
63
+ * The event was loaded from the persisted storage-operation snapshot.
64
+ *
65
+ * A restored event records pending operation intent, not current storage
66
+ * state. File handlers must revalidate it before applying side effects.
67
+ */
68
+ restoredFromPreviousRuntime?: true;
62
69
  skipBatchWait?: boolean;
63
70
  cancelled?: boolean;
64
71
  batched?: boolean;
@@ -17,7 +17,9 @@ export interface Rebuilder {
17
17
  /** See {@link scheduleRebuild}; this variant writes the Fetch flag. */
18
18
  scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
19
19
  /**
20
- * Declares the finish of the rebuild process and unlock remote, resume reflecting the changes.
20
+ * Complete the final reflection scan and checks owned by a fetch, then establish application readiness.
21
+ *
22
+ * @returns `true` when every required completion phase succeeded; otherwise, `false`.
21
23
  */
22
- finishRebuild(): Promise<void>;
24
+ finishRebuild(): Promise<boolean>;
23
25
  }
@@ -21,6 +21,9 @@ type FileEventItemSentinelFlush = {
21
21
  type: typeof TYPE_SENTINEL_FLUSH;
22
22
  };
23
23
  export type FileEventItemSentinel = FileEventItemSentinelFlush;
24
+ type RunQueuedEventsOptions = {
25
+ waitForProcessing?: boolean;
26
+ };
24
27
  export interface StorageEventManagerBaseDependencies {
25
28
  setting: SettingService;
26
29
  vaultService: IVaultService;
@@ -61,13 +64,11 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
61
64
  protected updateStatus(): void;
62
65
  /**
63
66
  * Snapshot restoration promise.
64
- * Snapshot will be restored before starting to watch vault changes.
65
- * In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
67
+ * It resolves after the restored file operations have finished, before Vault watching begins.
66
68
  */
67
69
  snapShotRestored: Promise<void> | null;
68
70
  /**
69
- * Restore the previous snapshot if exists.
70
- * @returns
71
+ * Restore and complete the previous storage-event snapshot when it exists.
71
72
  */
72
73
  restoreState(): Promise<void>;
73
74
  appendQueue(params: FileEvent[], ctx?: unknown): Promise<void>;
@@ -111,7 +112,17 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
111
112
  processFileEvent(fei: FileEventItem): Promise<void>;
112
113
  _takeSnapshot(): Promise<void>;
113
114
  _restoreFromSnapshot(): Promise<void>;
114
- protected runQueuedEvents(): Promise<void>;
115
+ /**
116
+ * Dispatch buffered storage events.
117
+ *
118
+ * Ordinary watcher events retain the existing fire-and-forget behaviour.
119
+ * Snapshot restoration requests completion so its caller can use the replay as
120
+ * a lifecycle boundary. In that mode, a sentinel also waits for every operation
121
+ * dispatched before it, preserving the ordering represented by the snapshot.
122
+ * An individual restored operation failure is logged but does not prevent the
123
+ * following Offline Scanner from reconciling the resulting current state.
124
+ */
125
+ protected runQueuedEvents(options?: RunQueuedEventsOptions): Promise<void>;
115
126
  protected processingCount: number;
116
127
  protected requestProcessQueue(fei: FileEventItem): Promise<void>;
117
128
  isWaiting(filename: FilePath): boolean;
@@ -72,13 +72,11 @@ class StorageEventManagerBase extends StorageEventManager {
72
72
  }
73
73
  /**
74
74
  * Snapshot restoration promise.
75
- * Snapshot will be restored before starting to watch vault changes.
76
- * In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
75
+ * It resolves after the restored file operations have finished, before Vault watching begins.
77
76
  */
78
77
  snapShotRestored = null;
79
78
  /**
80
- * Restore the previous snapshot if exists.
81
- * @returns
79
+ * Restore and complete the previous storage-event snapshot when it exists.
82
80
  */
83
81
  restoreState() {
84
82
  this.snapShotRestored = this._restoreFromSnapshot();
@@ -371,15 +369,44 @@ class StorageEventManagerBase extends StorageEventManager {
371
369
  const snapShot = await this._loadSnapshot();
372
370
  if (snapShot && Array.isArray(snapShot) && snapShot.length > 0) {
373
371
  this._log(`Restoring storage operation snapshot: ${snapShot.length} items`, LOG_LEVEL_VERBOSE);
374
- this.bufferedQueuedItems = snapShot.map((e) => ({ ...e, skipBatchWait: true }));
372
+ this.bufferedQueuedItems = snapShot.map(
373
+ (event) => event.type === TYPE_SENTINEL_FLUSH ? event : { ...event, restoredFromPreviousRuntime: true, skipBatchWait: true }
374
+ );
375
375
  this.updateStatus();
376
- await this.runQueuedEvents();
376
+ await this.runQueuedEvents({ waitForProcessing: true });
377
377
  } else {
378
378
  this._log(`No snapshot to restore`, LOG_LEVEL_VERBOSE);
379
379
  }
380
380
  }
381
- runQueuedEvents() {
381
+ /**
382
+ * Dispatch buffered storage events.
383
+ *
384
+ * Ordinary watcher events retain the existing fire-and-forget behaviour.
385
+ * Snapshot restoration requests completion so its caller can use the replay as
386
+ * a lifecycle boundary. In that mode, a sentinel also waits for every operation
387
+ * dispatched before it, preserving the ordering represented by the snapshot.
388
+ * An individual restored operation failure is logged but does not prevent the
389
+ * following Offline Scanner from reconciling the resulting current state.
390
+ */
391
+ runQueuedEvents(options = {}) {
392
+ const waitForProcessing = options.waitForProcessing ?? false;
382
393
  return skipIfDuplicated("storage-event-manager-run-queued-events", async () => {
394
+ let dispatched = [];
395
+ const waitForDispatched = async () => {
396
+ if (dispatched.length === 0) return;
397
+ const pending = dispatched;
398
+ dispatched = [];
399
+ const results = await Promise.allSettled(pending.map(({ processing }) => processing));
400
+ for (const [index, result] of results.entries()) {
401
+ if (result.status === "rejected") {
402
+ this._log(
403
+ `Restored storage operation failed for ${pending[index].path}; the Offline Scanner will reconcile the current state`,
404
+ LOG_LEVEL_NOTICE
405
+ );
406
+ this._log(result.reason, LOG_LEVEL_VERBOSE);
407
+ }
408
+ }
409
+ };
383
410
  do {
384
411
  if (this.bufferedQueuedItems.length === 0) {
385
412
  break;
@@ -392,12 +419,23 @@ class StorageEventManagerBase extends StorageEventManager {
392
419
  this.updateStatus();
393
420
  if (fei.type === TYPE_SENTINEL_FLUSH) {
394
421
  this._log(`Waiting for idle`, LOG_LEVEL_VERBOSE);
422
+ if (waitForProcessing) {
423
+ await waitForDispatched();
424
+ }
395
425
  await this.waitForIdle();
396
426
  this.updateStatus();
397
427
  continue;
398
428
  }
399
- fireAndForget(() => this.processFileEvent(fei));
429
+ const processing = this.processFileEvent(fei);
430
+ if (waitForProcessing) {
431
+ dispatched.push({ path: fei.args.file.path, processing });
432
+ } else {
433
+ fireAndForget(processing);
434
+ }
400
435
  } while (this.bufferedQueuedItems.length > 0);
436
+ if (waitForProcessing) {
437
+ await waitForDispatched();
438
+ }
401
439
  });
402
440
  }
403
441
  processingCount = 0;
@@ -46,11 +46,17 @@ export declare class LiveSyncLocalDB {
46
46
  env: LiveSyncLocalDBEnv;
47
47
  clearCaches(): void;
48
48
  _prepareHashFunctions(): Promise<void>;
49
- onunload(): void;
50
49
  refreshSettings(): void;
51
50
  offRemoteChunkFetchedHandler?: ReturnType<LiveSyncEventHub["onEvent"]>;
51
+ private databaseCloseCleanup?;
52
+ private databaseUnloadNotification?;
52
53
  constructor(dbname: string, env: LiveSyncLocalDBEnv);
54
+ /** Notify dependent services at most once for each physical database lifecycle. */
55
+ private notifyDatabaseUnload;
56
+ onunload(): void;
53
57
  close(): Promise<void>;
58
+ private teardownDatabaseDependencies;
59
+ private rollbackFailedInitialisation;
54
60
  onNewLeaf(chunk: EntryLeaf): void;
55
61
  initializeDatabase(): Promise<boolean>;
56
62
  /**
@@ -37,16 +37,14 @@ class LiveSyncLocalDB {
37
37
  async _prepareHashFunctions() {
38
38
  await this._managers?.prepareHashFunction();
39
39
  }
40
- onunload() {
41
- void this.env.services.databaseEvents.onUnloadDatabase(this);
42
- this.localDatabase.removeAllListeners();
43
- }
44
40
  refreshSettings() {
45
41
  const settings = this.env.services.setting.currentSettings();
46
42
  this.settings = settings;
47
43
  void this._prepareHashFunctions();
48
44
  }
49
45
  offRemoteChunkFetchedHandler;
46
+ databaseCloseCleanup;
47
+ databaseUnloadNotification;
50
48
  constructor(dbname, env) {
51
49
  this.auth = {
52
50
  username: "",
@@ -57,14 +55,79 @@ class LiveSyncLocalDB {
57
55
  this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
58
56
  this.refreshSettings();
59
57
  }
58
+ /** Notify dependent services at most once for each physical database lifecycle. */
59
+ notifyDatabaseUnload() {
60
+ if (!this.databaseUnloadNotification) {
61
+ this.databaseUnloadNotification = Promise.resolve().then(async () => {
62
+ await this.env.services.databaseEvents.onUnloadDatabase(this);
63
+ });
64
+ }
65
+ return this.databaseUnloadNotification;
66
+ }
67
+ onunload() {
68
+ void this.notifyDatabaseUnload();
69
+ }
60
70
  async close() {
61
71
  this._log("Database closed (by close)");
62
72
  this.isReady = false;
63
73
  this.offRemoteChunkFetchedHandler?.();
74
+ this.offRemoteChunkFetchedHandler = void 0;
64
75
  if (this.localDatabase != null) {
65
- await this.localDatabase.close();
76
+ const database = this.localDatabase;
77
+ await database.close();
78
+ if (this.databaseCloseCleanup?.database === database) {
79
+ await this.databaseCloseCleanup.promise;
80
+ }
81
+ if (this.localDatabase === database) {
82
+ this.localDatabase = null;
83
+ }
84
+ }
85
+ this._managers = void 0;
86
+ await this.notifyDatabaseUnload();
87
+ }
88
+ async teardownDatabaseDependencies(managers) {
89
+ try {
90
+ await Promise.resolve(this.env.services.replicator.getActiveReplicator()?.closeReplication());
91
+ } catch (error) {
92
+ this._log("Failed to close replication while tearing down the local database.", LOG_LEVEL_VERBOSE);
93
+ this._log(error, LOG_LEVEL_VERBOSE);
94
+ }
95
+ try {
96
+ await managers.teardownManagers();
97
+ } catch (error) {
98
+ this._log("Failed to tear down managers while closing the local database.", LOG_LEVEL_VERBOSE);
99
+ this._log(error, LOG_LEVEL_VERBOSE);
100
+ }
101
+ }
102
+ async rollbackFailedInitialisation(database, managers) {
103
+ this.isReady = false;
104
+ this.offRemoteChunkFetchedHandler?.();
105
+ this.offRemoteChunkFetchedHandler = void 0;
106
+ const closeCleanup = this.databaseCloseCleanup?.database === database ? this.databaseCloseCleanup : void 0;
107
+ if (closeCleanup) {
108
+ await closeCleanup.promise;
109
+ } else {
110
+ database.removeAllListeners();
111
+ await this.teardownDatabaseDependencies(managers);
112
+ try {
113
+ await database.close();
114
+ } catch (error) {
115
+ this._log("Failed to close the database after its initialisation was rejected.", LOG_LEVEL_VERBOSE);
116
+ this._log(error, LOG_LEVEL_VERBOSE);
117
+ }
118
+ }
119
+ if (this.localDatabase === database) {
120
+ this.localDatabase = null;
121
+ }
122
+ if (this._managers === managers) {
123
+ this._managers = void 0;
124
+ }
125
+ try {
126
+ await this.notifyDatabaseUnload();
127
+ } catch (error) {
128
+ this._log("A database unload handler failed after initialisation was rejected.", LOG_LEVEL_VERBOSE);
129
+ this._log(error, LOG_LEVEL_VERBOSE);
66
130
  }
67
- await this.env.services.databaseEvents.onUnloadDatabase(this);
68
131
  }
69
132
  onNewLeaf(chunk) {
70
133
  this.managers.chunkManager?.emitEvent(EVENT_CHUNK_FETCHED, chunk);
@@ -84,6 +147,7 @@ class LiveSyncLocalDB {
84
147
  deterministic_revs: true
85
148
  }
86
149
  );
150
+ this.databaseUnloadNotification = void 0;
87
151
  const manager = new LiveSyncManagers({
88
152
  database: this.localDatabase,
89
153
  APIService: this.env.services.API,
@@ -93,38 +157,53 @@ class LiveSyncLocalDB {
93
157
  databaseService: this.env.services.database
94
158
  });
95
159
  this._managers = manager;
96
- if (!await this.env.services.databaseEvents.onDatabaseInitialisation(this)) {
97
- this._log("Initializing Database has been failed on some module", LOG_LEVEL_NOTICE);
98
- }
99
- this._log("Opening Database...");
100
- this._log("Database info", LOG_LEVEL_VERBOSE);
101
- this._log(JSON.stringify(await this.localDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
102
- await this.managers.initialise();
103
- this.localDatabase.on("close", () => {
104
- this._log("Database closed.");
105
- this.isReady = false;
106
- this.localDatabase.removeAllListeners();
107
- this.env.services.replicator.getActiveReplicator()?.closeReplication();
108
- void this.managers.teardownManagers();
109
- });
110
- const _instance = new FallbackWeakRef(this);
111
- const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
112
- if (_instance.deref() == null) {
113
- unload();
160
+ const openedDatabase = this.localDatabase;
161
+ this.databaseCloseCleanup = void 0;
162
+ try {
163
+ if (!await this.env.services.databaseEvents.onDatabaseInitialisation(this)) {
164
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
165
+ this._log("Database initialisation was rejected by a required module.", LOG_LEVEL_NOTICE);
166
+ return false;
114
167
  }
115
- _instance.deref()?.onNewLeaf(chunk);
116
- });
117
- this.offRemoteChunkFetchedHandler = unload;
118
- this.isReady = true;
119
- if (!await this.env.services.databaseEvents.onDatabaseHasReady()) {
120
- this._log(
121
- "Some module has prevented the database from being ready. The database is initialised but not ready for use.",
122
- LOG_LEVEL_NOTICE
123
- );
124
- return false;
168
+ this._log("Opening Database...");
169
+ this._log("Database info", LOG_LEVEL_VERBOSE);
170
+ this._log(JSON.stringify(await openedDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
171
+ await manager.initialise();
172
+ openedDatabase.on("close", () => {
173
+ this._log("Database closed.");
174
+ this.isReady = false;
175
+ openedDatabase.removeAllListeners();
176
+ if (this.databaseCloseCleanup?.database !== openedDatabase) {
177
+ this.databaseCloseCleanup = {
178
+ database: openedDatabase,
179
+ promise: this.teardownDatabaseDependencies(manager)
180
+ };
181
+ }
182
+ });
183
+ const _instance = new FallbackWeakRef(this);
184
+ const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
185
+ if (_instance.deref() == null) {
186
+ unload();
187
+ }
188
+ _instance.deref()?.onNewLeaf(chunk);
189
+ });
190
+ this.offRemoteChunkFetchedHandler = unload;
191
+ this.isReady = true;
192
+ const readyAccepted = await this.env.services.databaseEvents.onDatabaseHasReady();
193
+ if (!readyAccepted || this.localDatabase !== openedDatabase || !this.isReady) {
194
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
195
+ this._log(
196
+ "Some module has prevented the database from being ready. The database is initialised but not ready for use.",
197
+ LOG_LEVEL_NOTICE
198
+ );
199
+ return false;
200
+ }
201
+ this._log("Database is now ready.");
202
+ return true;
203
+ } catch (error) {
204
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
205
+ throw error;
125
206
  }
126
- this._log("Database is now ready.");
127
- return true;
128
207
  }
129
208
  /**
130
209
  * Retrieve all used and existing chunks in the database.
@@ -212,6 +291,7 @@ class LiveSyncLocalDB {
212
291
  return { used, existing };
213
292
  }
214
293
  async resetDatabase() {
294
+ this.isReady = false;
215
295
  await this.managers.teardownManagers();
216
296
  this.env.services.replicator.getActiveReplicator()?.closeReplication();
217
297
  if (!await this.env.services.databaseEvents.onResetDatabase(this)) {
@@ -219,10 +299,12 @@ class LiveSyncLocalDB {
219
299
  return false;
220
300
  }
221
301
  Logger("Database closed for reset Database.");
222
- this.isReady = false;
223
302
  await this.localDatabase.destroy();
224
303
  this.localDatabase = null;
225
- await this.initializeDatabase();
304
+ if (!await this.initializeDatabase()) {
305
+ this._log("Local Database could not be reinitialised after reset.", LOG_LEVEL_NOTICE);
306
+ return false;
307
+ }
226
308
  this._log("Local Database Reset", LOG_LEVEL_NOTICE);
227
309
  return true;
228
310
  }
@@ -4,26 +4,29 @@ import { createInstanceLogFunction } from "../services/lib/logUtils.js";
4
4
  async function prepareDatabaseForUse(host, log, errorManager, showingNotice = false, reopenDatabase = true, ignoreSuspending = false) {
5
5
  const appLifecycle = host.services.appLifecycle;
6
6
  appLifecycle.resetIsReady();
7
- if (!reopenDatabase || await host.services.database.openDatabase({
7
+ if (reopenDatabase && !await host.services.database.openDatabase({
8
8
  databaseEvents: host.services.databaseEvents,
9
9
  replicator: host.services.replicator
10
10
  })) {
11
- if (host.services.database.localDatabase.isReady) {
12
- await host.services.vault.scanVault(showingNotice, ignoreSuspending);
13
- }
14
- const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
15
- if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
16
- errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
17
- return false;
18
- }
19
- errorManager.clearError(ERR_INITIALISATION_FAILED);
20
- appLifecycle.markIsReady();
21
- await host.services.fileProcessing.commitPendingFileEvents();
22
- return true;
23
- } else {
24
- appLifecycle.resetIsReady();
25
11
  return false;
26
12
  }
13
+ if (!host.services.database.isDatabaseReady()) {
14
+ return false;
15
+ }
16
+ if (!await host.services.vault.scanVault(showingNotice, ignoreSuspending)) {
17
+ return false;
18
+ }
19
+ const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
20
+ if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
21
+ errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
22
+ return false;
23
+ }
24
+ errorManager.clearError(ERR_INITIALISATION_FAILED);
25
+ if (!await host.services.fileProcessing.commitPendingFileEvents()) {
26
+ return false;
27
+ }
28
+ appLifecycle.markIsReady();
29
+ return true;
27
30
  }
28
31
  function usePrepareDatabaseForUse(host) {
29
32
  const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
@@ -14,6 +14,7 @@ import type { StorageAccess } from "../interfaces/StorageAccess.js";
14
14
  import type { LiveSyncEventHub } from "../hub/hub.js";
15
15
  import { ServiceModuleBase } from "./ServiceModuleBase.js";
16
16
  import type { ControlService } from "../services/base/ControlService.js";
17
+ import type { IFileProcessingService } from "../services/base/IService.js";
17
18
  export interface ServiceRebuilderDependencies {
18
19
  events: LiveSyncEventHub;
19
20
  appLifecycle: AppLifecycleService;
@@ -28,6 +29,7 @@ export interface ServiceRebuilderDependencies {
28
29
  replication: ReplicationService;
29
30
  database: DatabaseService;
30
31
  fileHandler: IFileHandler;
32
+ fileProcessing: IFileProcessingService;
31
33
  control: ControlService;
32
34
  }
33
35
  export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilderDependencies> implements Rebuilder {
@@ -44,6 +46,7 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
44
46
  private replication;
45
47
  private database;
46
48
  private fileHandler;
49
+ private fileProcessing;
47
50
  private control;
48
51
  constructor(services: ServiceRebuilderDependencies);
49
52
  $performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void>;
@@ -63,17 +66,37 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
63
66
  private _onResetLocalDatabase;
64
67
  suspendAllSync(): Promise<void>;
65
68
  suspendReflectingDatabase(ignoreMinIO?: boolean): Promise<void>;
66
- resumeReflectingDatabase(ignoreMinIO?: boolean): Promise<void>;
69
+ /**
70
+ * Stage reflection resumption and perform the final reconciliation checks.
71
+ *
72
+ * File watching is resumed in memory before the final scan so that changes
73
+ * made while the scan is running can enter the storage-event queue. This
74
+ * phase deliberately does not persist the resumed settings. The owning
75
+ * completion boundary persists them only after the scan, replication
76
+ * pre-check, and current batch-wait release have succeeded.
77
+ *
78
+ * @param ignoreMinIO Whether to resume reflection for a MinIO remote.
79
+ * @returns `true` when the applicable reconciliation checks succeeded.
80
+ */
81
+ resumeReflectingDatabase(ignoreMinIO?: boolean): Promise<boolean>;
82
+ private prepareLocalDatabaseForRebuild;
83
+ private completePreparedRebuild;
67
84
  fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean, autoResume?: boolean): Promise<void>;
68
85
  private performFetchLocal;
69
86
  fetchLocalDBFast(autoResume: boolean): Promise<void>;
70
87
  private performFetchLocalDBFast;
71
88
  /**
72
- * Finish rebuild process with resuming the reflection.
89
+ * Complete the final rebuild phases and establish application readiness.
90
+ *
91
+ * A failed completion restores the previous reflection settings in memory.
92
+ * Persistence atomicity remains a responsibility of the host setting store;
93
+ * Commonlib cannot roll back a store which writes successfully and then
94
+ * reports an error.
73
95
  *
74
- * @param ignoreMinIO Whether to ignore minio for resuming the reflection.
96
+ * @param ignoreMinIO Whether to resume reflection for a MinIO remote.
97
+ * @returns `true` when finalisation completed; otherwise, `false`.
75
98
  */
76
- finishRebuild(ignoreMinIO?: boolean): Promise<void>;
99
+ finishRebuild(ignoreMinIO?: boolean): Promise<boolean>;
77
100
  /**
78
101
  * Fetch local database with making all chunks.
79
102
  * This is a wrapper for {@link fetchLocal} with makeLocalChunkBeforeSync = true.