@vrtmrz/livesync-commonlib 0.2.0 → 0.2.1

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.
Files changed (41) hide show
  1. package/README.md +43 -0
  2. package/dist/API/processSetting.d.ts +1 -0
  3. package/dist/common/models/setting.const.defaults.js +1 -0
  4. package/dist/common/models/setting.const.qr.js +1 -0
  5. package/dist/common/models/setting.policy.d.ts +4 -0
  6. package/dist/common/models/setting.policy.js +1 -0
  7. package/dist/common/models/setting.type.d.ts +2 -0
  8. package/dist/common/models/shared.definition.d.ts +20 -0
  9. package/dist/common/models/tweak.definition.d.ts +1 -0
  10. package/dist/common/settingConstants.d.ts +1 -0
  11. package/dist/common/settingConstants.js +6 -2
  12. package/dist/interfaces/FileHandler.d.ts +5 -1
  13. package/dist/interfaces/StorageEventManager.d.ts +5 -0
  14. package/dist/interfaces/StorageEventManager.js +7 -0
  15. package/dist/managers/StorageEventManager.d.ts +19 -10
  16. package/dist/managers/StorageEventManager.js +259 -189
  17. package/dist/replication/LiveSyncAbstractReplicator.d.ts +2 -0
  18. package/dist/replication/LiveSyncAbstractReplicator.js +5 -2
  19. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +6 -0
  20. package/dist/replication/couchdb/LiveSyncReplicator.js +39 -2
  21. package/dist/replication/couchdb/replicationProgress.d.ts +6 -0
  22. package/dist/replication/couchdb/replicationProgress.js +105 -0
  23. package/dist/replication/index.d.ts +1 -0
  24. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -0
  25. package/dist/serviceFeatures/offlineScanner.js +19 -7
  26. package/dist/serviceModules/ServiceFileAccessBase.js +5 -0
  27. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +8 -5
  28. package/dist/serviceModules/ServiceFileHandlerBase.js +75 -38
  29. package/dist/services/base/AppLifecycleService.js +1 -1
  30. package/dist/services/base/ControlService.js +6 -2
  31. package/dist/services/base/FileProcessingService.d.ts +7 -1
  32. package/dist/services/base/FileProcessingService.js +9 -1
  33. package/dist/services/base/IService.d.ts +9 -1
  34. package/dist/services/base/ReplicatorService.d.ts +1 -0
  35. package/dist/services/lib/HandlerUtils.d.ts +60 -0
  36. package/dist/services/lib/HandlerUtils.js +46 -0
  37. package/dist/worker/bgWorker.js +1 -1
  38. package/docs/offline-scanner.md +15 -13
  39. package/docs/settings-lifecycle.md +2 -0
  40. package/docs/storage-events-and-reflection.md +109 -10
  41. package/package.json +2 -2
@@ -40,7 +40,9 @@ class ControlService extends ServiceBase {
40
40
  await this.services.appLifecycleService.onSuspending();
41
41
  await this.services.settingService.onBeforeRealiseSetting();
42
42
  this.services.databaseService.localDatabase.refreshSettings();
43
- await this.services.fileProcessingService.commitPendingFileEvents();
43
+ if (!await this.services.fileProcessingService.commitPendingFileEvents()) {
44
+ throw new Error("Pending local file operations could not be completed before applying settings");
45
+ }
44
46
  await this.services.settingService.onRealiseSetting();
45
47
  if (this.services.appLifecycleService.isSuspended()) return;
46
48
  await this.services.appLifecycleService.onResuming();
@@ -49,7 +51,8 @@ class ControlService extends ServiceBase {
49
51
  }
50
52
  async _onLiveSyncUnload() {
51
53
  this.context.events.emitEvent(EVENT_PLUGIN_UNLOADED);
52
- await this.services.appLifecycleService.onBeforeUnload();
54
+ const prepared = await this.services.appLifecycleService.onBeforeUnload();
55
+ if (!prepared) this._log("Unload preparation failed; pending storage work may require recovery", LOG_LEVEL_URGENT);
53
56
  await Promise.resolve(this.services.appLifecycleService.onAppUnload());
54
57
  cancelAllPeriodicTask();
55
58
  cancelAllTasks();
@@ -64,6 +67,7 @@ class ControlService extends ServiceBase {
64
67
  this.context.events.emitEvent(EVENT_PLATFORM_UNLOADED);
65
68
  this.context.events.offAll();
66
69
  this._log(this.context.translate("moduleLiveSyncMain.logUnloadingPlugin"));
70
+ if (!prepared) throw new Error("Unload preparation failed");
67
71
  return;
68
72
  }
69
73
  /**
@@ -9,10 +9,16 @@ export declare class FileProcessingService<T extends ServiceContext = ServiceCon
9
9
  */
10
10
  readonly processFileEvent: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<(item: import("../../common/types.js").FileEventItem) => Promise<boolean>>;
11
11
  /**
12
- * Process a file event item optionally, if any handler is registered.
12
+ * Dispatch a legacy optional notification, without confirming persistence.
13
13
  * i.e., hidden files synchronisation or customisation sync.
14
14
  */
15
15
  readonly processOptionalFileEvent: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<(path: import("../../common/types.js").FilePath) => Promise<boolean>>;
16
+ /**
17
+ * Complete optional file operations through the first applicable handler.
18
+ * Hosts migrate all optional providers together. Once any typed provider is
19
+ * registered, dispatch uses this contract exclusively.
20
+ */
21
+ readonly processOptionalFileEventWithResult?: import("../lib/HandlerUtils.js").FirstHandledFunction<(path: import("../../common/types.js").FilePath, options?: import("./IService.js").OptionalFileEventOptions) => Promise<import("../lib/HandlerUtils.js").HandlerOutcome<void, string>>>;
16
22
  /**
17
23
  * Commit any pending file events that have been queued for processing.
18
24
  */
@@ -7,10 +7,18 @@ class FileProcessingService extends ServiceBase {
7
7
  */
8
8
  processFileEvent = handlers().anySuccess("processFileEvent");
9
9
  /**
10
- * Process a file event item optionally, if any handler is registered.
10
+ * Dispatch a legacy optional notification, without confirming persistence.
11
11
  * i.e., hidden files synchronisation or customisation sync.
12
12
  */
13
13
  processOptionalFileEvent = handlers().anySuccess("processOptionalFileEvent");
14
+ /**
15
+ * Complete optional file operations through the first applicable handler.
16
+ * Hosts migrate all optional providers together. Once any typed provider is
17
+ * registered, dispatch uses this contract exclusively.
18
+ */
19
+ processOptionalFileEventWithResult = handlers().firstHandled(
20
+ "processOptionalFileEventWithResult"
21
+ );
14
22
  /**
15
23
  * Commit any pending file events that have been queued for processing.
16
24
  */
@@ -16,7 +16,7 @@ import type { OwnedCouchDBConnection, RemoteConnectionOpenOptions } from "./Remo
16
16
  import type { ActiveReplicatorContext, ContinuousReplicationRequest, ReplicationAttemptFailure, ReplicationFailureRequest, ReplicatorProviderDefinitionMap, ReplicationOutcome, ReplicationReadinessRequirements, UnattendedOneShotRequest, UserInitiatedOneShotRequest } from "../../replication/ReplicatorProvider.js";
17
17
  import type { RemoteResourceKind, RemoteResourceMap } from "../../replication/RemoteResource.js";
18
18
  import type { CentralRemoteAdministrationRequest, CentralRemoteAdministrationResult } from "../../replication/CentralRemoteAdministration.js";
19
- import type { MultipleHandlerFunction } from "../lib/HandlerUtils.js";
19
+ import type { HandlerOutcome, MultipleHandlerFunction } from "../lib/HandlerUtils.js";
20
20
  import type { VaultScanResult } from "./VaultScanResult.js";
21
21
  import type { UICommandRegistry } from "../../uiCommands.js";
22
22
  declare global {
@@ -97,9 +97,17 @@ export interface IKeyValueDBService {
97
97
  openSimpleStore<T>(kind: string): SimpleStore<T>;
98
98
  simpleStore: SimpleStore<unknown>;
99
99
  }
100
+ /** Options for completing an admitted optional file event. */
101
+ export interface OptionalFileEventOptions {
102
+ /** Resolves when a finite flush needs this event to finish without its debounce delay. */
103
+ whenFlushRequested?: Promise<void>;
104
+ }
100
105
  export interface IFileProcessingService {
101
106
  processFileEvent(item: FileEventItem): Promise<boolean>;
107
+ /** Legacy notification contract; its boolean does not confirm persistence. */
102
108
  processOptionalFileEvent(path: FilePath): Promise<boolean>;
109
+ /** Complete the actual optional file operation, or explain why it is not completed. */
110
+ processOptionalFileEventWithResult?(path: FilePath, options?: OptionalFileEventOptions): Promise<HandlerOutcome<void, string>>;
103
111
  commitPendingFileEvents(): Promise<boolean>;
104
112
  batched: ReactiveSource<number>;
105
113
  processing: ReactiveSource<number>;
@@ -125,5 +125,6 @@ export declare abstract class ReplicatorService<T extends ServiceContext = Servi
125
125
  lastSyncPullSeq: number;
126
126
  lastSyncPushSeq: number;
127
127
  syncStatus: import("../../common/types.js").DatabaseConnectingStatus;
128
+ couchDB?: import("../../common/models/shared.definition.js").CouchDBReplicationProgress;
128
129
  }>;
129
130
  }
@@ -2,6 +2,27 @@
2
2
  * A function type that can be used as a handler.
3
3
  */
4
4
  type HandlerFunc<TArg extends unknown[], TResult> = (...args: TArg) => TResult | Promise<TResult>;
5
+ /**
6
+ * The outcome of a handler.
7
+ *
8
+ * A completed outcome carries a value when `TValue` is not void, and a veto
9
+ * carries a reason when `TVetoReason` is not void. Not-applicable outcomes
10
+ * always continue to the next registered handler.
11
+ */
12
+ export type HandlerOutcome<TValue = void, TVetoReason = void> = {
13
+ kind: "not-applicable";
14
+ } | ([TValue] extends [void] ? {
15
+ kind: "completed";
16
+ } : {
17
+ kind: "completed";
18
+ value: TValue;
19
+ }) | ([TVetoReason] extends [void] ? {
20
+ kind: "veto";
21
+ } : {
22
+ kind: "veto";
23
+ value: TVetoReason;
24
+ });
25
+ type AnyHandlerOutcome = HandlerOutcome | HandlerOutcome<unknown, unknown>;
5
26
  /**
6
27
  * A function type that returns a boolean or a Promise of boolean.
7
28
  */
@@ -244,6 +265,29 @@ export declare class FirstResultHandler<T extends unknown[], U> extends MultiBin
244
265
  */
245
266
  invoke(...args: T): Promise<U | false>;
246
267
  }
268
+ /**
269
+ * Invokes registered handlers in priority and registration order until one
270
+ * completes or vetoes. Each invocation uses a snapshot of registrations.
271
+ * Callback errors and rejections propagate unchanged, and registrations are
272
+ * owned by this handler.
273
+ */
274
+ export declare class FirstHandledHandler<T extends unknown[], U extends AnyHandlerOutcome = HandlerOutcome> extends MultiBinder<HandlerFunc<T, U>> implements InvokableHandler<T, U | {
275
+ kind: "not-applicable";
276
+ }> {
277
+ /**
278
+ * Whether at least one callback is currently registered.
279
+ */
280
+ get hasHandlers(): boolean;
281
+ /**
282
+ * Invokes handlers until one returns a completed or veto outcome.
283
+ * @param args The arguments to pass to the handlers.
284
+ * @returns The first handled outcome, or a not-applicable outcome when no handler applies.
285
+ * @throws Any synchronous callback error or rejected callback value, unchanged.
286
+ */
287
+ invoke(...args: T): Promise<U | {
288
+ kind: "not-applicable";
289
+ }>;
290
+ }
247
291
  /**
248
292
  * A function type that can be used as a handler with assignable functionality.
249
293
  */
@@ -297,6 +341,11 @@ export interface MultipleHandlerFunction<TFunc extends (...args: any[]) => U | P
297
341
  */
298
342
  removeHandler: (callback: TFunc) => void;
299
343
  }
344
+ export type FirstHandledFunction<TFunc extends (...args: any[]) => AnyHandlerOutcome | Promise<AnyHandlerOutcome>> = ((...args: Parameters<TFunc>) => Promise<Awaited<ReturnType<TFunc>> | {
345
+ kind: "not-applicable";
346
+ }>) & Pick<MultipleHandlerFunction<TFunc>, "addHandler" | "removeHandler"> & {
347
+ readonly hasHandlers: boolean;
348
+ };
300
349
  /**
301
350
  * A function type that can be used as a value-collecting handler with add/remove functionality.
302
351
  */
@@ -351,12 +400,16 @@ export declare function bailFirstFailureWithResultFunction<TFunc extends (...arg
351
400
  export declare function allParallelFunction<TFunc extends (...args: any[]) => Promise<boolean>>(name?: string): BooleanMultipleHandlerFunction<TFunc>;
352
401
  export declare function anySuccessFunction<TFunc extends (...args: any[]) => Promise<boolean>>(name?: string): BooleanMultipleHandlerFunction<TFunc>;
353
402
  export declare function firstResultFunction<TFunc extends (...args: any[]) => Promise<unknown>>(name?: string): MultipleHandlerFunction<TFunc>;
403
+ export declare function firstHandledFunction<TFunc extends (...args: any[]) => AnyHandlerOutcome | Promise<AnyHandlerOutcome>>(name?: string): FirstHandledFunction<TFunc>;
354
404
  export declare function dispatchParallelFunction<TFunc extends (...args: any[]) => Promise<unknown[]>>(name?: string): CollectiveHandlerFunction<TFunc>;
355
405
  export declare function bindableFunction<TFunc extends (...args: any[]) => any>(name?: string): HandlerFunction<TFunc>;
356
406
  export declare function lazyBindableFunction<TFunc extends (...args: any[]) => any>(name?: string): LazyHandlerFunction<TFunc>;
357
407
  type FunctionKeys<T> = Extract<{
358
408
  [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
359
409
  }[keyof T], string>;
410
+ type FirstHandledFunctionKeys<T> = Extract<{
411
+ [K in keyof T]: [NonNullable<T[K]>] extends [never] ? never : NonNullable<T[K]> extends (...args: any[]) => AnyHandlerOutcome | Promise<AnyHandlerOutcome> ? K : never;
412
+ }[keyof T], string>;
360
413
  export declare function handlers<T extends object>(): {
361
414
  /**
362
415
  * Create a handler that invokes all added handler functions sequentially until one returns false.
@@ -403,6 +456,13 @@ export declare function handlers<T extends object>(): {
403
456
  * @returns
404
457
  */
405
458
  firstResult<K extends FunctionKeys<T>>(name: K): MultipleHandlerFunction<Extract<T[K], (...args: any[]) => Promise<unknown>>>;
459
+ /**
460
+ * Create a handler that invokes registered handlers until one completes or vetoes.
461
+ * Callback errors and rejections propagate unchanged.
462
+ * @param name The name of the callback on `T`.
463
+ * @returns A bound handler with a live `hasHandlers` property.
464
+ */
465
+ firstHandled<K extends FirstHandledFunctionKeys<T>>(name: K): FirstHandledFunction<Extract<NonNullable<T[K]>, (...args: any[]) => AnyHandlerOutcome | Promise<AnyHandlerOutcome>>>;
406
466
  /**
407
467
  * Create a handler that invokes all added handler functions in parallel.
408
468
  * @param name
@@ -326,6 +326,30 @@ class FirstResultHandler extends MultiBinder {
326
326
  return false;
327
327
  }
328
328
  }
329
+ class FirstHandledHandler extends MultiBinder {
330
+ /**
331
+ * Whether at least one callback is currently registered.
332
+ */
333
+ get hasHandlers() {
334
+ return this._callbackMap.size > 0;
335
+ }
336
+ /**
337
+ * Invokes handlers until one returns a completed or veto outcome.
338
+ * @param args The arguments to pass to the handlers.
339
+ * @returns The first handled outcome, or a not-applicable outcome when no handler applies.
340
+ * @throws Any synchronous callback error or rejected callback value, unchanged.
341
+ */
342
+ async invoke(...args) {
343
+ const callbacks = [...this._callbacks];
344
+ for (const callback of callbacks) {
345
+ const outcome = await Promise.resolve(callback(...args));
346
+ if (outcome.kind !== "not-applicable") {
347
+ return outcome;
348
+ }
349
+ }
350
+ return { kind: "not-applicable" };
351
+ }
352
+ }
329
353
  function getMultipleBound(handler) {
330
354
  const _handler = "invoke" in handler ? handler.invoke : handler.dispatch;
331
355
  const __handler = _handler.bind(handler);
@@ -367,6 +391,17 @@ function firstResultFunction(name) {
367
391
  const handler = new FirstResultHandler(name ?? "firstResultFunction");
368
392
  return getMultipleBound(handler);
369
393
  }
394
+ function firstHandledFunction(name) {
395
+ const handler = new FirstHandledHandler(
396
+ name ?? "firstHandledFunction"
397
+ );
398
+ const toHandlerCallback = (callback) => callback;
399
+ const bound = (...args) => handler.invoke(...args);
400
+ bound.addHandler = (callback, priority) => handler.addHandler(toHandlerCallback(callback), priority);
401
+ bound.removeHandler = (callback) => handler.removeHandler(toHandlerCallback(callback));
402
+ Object.defineProperty(bound, "hasHandlers", { get: () => handler.hasHandlers });
403
+ return bound;
404
+ }
370
405
  function dispatchParallelFunction(name) {
371
406
  const handler = new DispatchParallel(
372
407
  name ?? "dispatchParallelFunction"
@@ -452,6 +487,15 @@ function handlers() {
452
487
  firstResult(name) {
453
488
  return firstResultFunction(String(name));
454
489
  },
490
+ /**
491
+ * Create a handler that invokes registered handlers until one completes or vetoes.
492
+ * Callback errors and rejections propagate unchanged.
493
+ * @param name The name of the callback on `T`.
494
+ * @returns A bound handler with a live `hasHandlers` property.
495
+ */
496
+ firstHandled(name) {
497
+ return firstHandledFunction(String(name));
498
+ },
455
499
  /**
456
500
  * Create a handler that invokes all added handler functions in parallel.
457
501
  * @param name
@@ -481,6 +525,7 @@ export {
481
525
  BooleanHandlerBase,
482
526
  Dispatch,
483
527
  DispatchParallel,
528
+ FirstHandledHandler,
484
529
  FirstResultHandler,
485
530
  LazyBinder,
486
531
  MultiBinder,
@@ -494,6 +539,7 @@ export {
494
539
  bailFirstFailureWithResultFunction,
495
540
  bindableFunction,
496
541
  dispatchParallelFunction,
542
+ firstHandledFunction,
497
543
  firstResultFunction,
498
544
  handlers,
499
545
  lazyBindableFunction