@vrtmrz/livesync-commonlib 0.2.1 → 0.2.3

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 (70) hide show
  1. package/README.md +60 -0
  2. package/dist/API/processSetting.d.ts +1 -0
  3. package/dist/ContentSplitter/ContentSplitterRabinKarp.js +2 -17
  4. package/dist/ContentSplitter/ContentSplitterV1.js +9 -19
  5. package/dist/ContentSplitter/ContentSplitterV2.js +9 -19
  6. package/dist/common/models/setting.const.defaults.js +1 -0
  7. package/dist/common/models/setting.const.qr.js +2 -0
  8. package/dist/common/models/setting.policy.d.ts +4 -0
  9. package/dist/common/models/setting.policy.js +1 -0
  10. package/dist/common/models/setting.type.d.ts +4 -0
  11. package/dist/common/models/tweak.definition.d.ts +1 -0
  12. package/dist/common/settingConstants.d.ts +1 -0
  13. package/dist/common/settingConstants.js +4 -0
  14. package/dist/common/utils.d.ts +33 -3
  15. package/dist/common/utils.js +12 -44
  16. package/dist/common/utils.object.js +2 -1
  17. package/dist/concurrency/keyedPendingWork.d.ts +13 -0
  18. package/dist/concurrency/keyedPendingWork.js +27 -0
  19. package/dist/conflicts/conflictCheckQueue.d.ts +45 -0
  20. package/dist/conflicts/conflictCheckQueue.js +108 -0
  21. package/dist/conflicts.d.ts +3 -0
  22. package/dist/conflicts.js +4 -0
  23. package/dist/diagnostics/integrity.js +11 -12
  24. package/dist/interfaces/FileHandler.d.ts +2 -0
  25. package/dist/interfaces/ServiceModule.d.ts +42 -24
  26. package/dist/interfaces/ServiceModule.js +4 -3
  27. package/dist/interfaces/StorageAccess.d.ts +2 -0
  28. package/dist/interfaces/StorageEventManager.d.ts +2 -0
  29. package/dist/interfaces/StorageEventManager.js +4 -0
  30. package/dist/managers/StorageEventManager.d.ts +4 -3
  31. package/dist/managers/StorageEventManager.js +24 -6
  32. package/dist/pouchdb/LiveSyncLocalDB.js +1 -0
  33. package/dist/pouchdb/chunks.d.ts +16 -0
  34. package/dist/pouchdb/pouchdb-browser.js +2 -84
  35. package/dist/pouchdb/pouchdb-http.js +2 -84
  36. package/dist/pouchdb/purgeMulti.d.ts +4 -0
  37. package/dist/pouchdb/purgeMulti.js +89 -0
  38. package/dist/replication/journal/JournalSyncCore.js +6 -1
  39. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -0
  40. package/dist/replication/trystero/TrysteroReplicator.js +3 -4
  41. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +3 -2
  42. package/dist/replication/trystero/peerPatterns.d.ts +1 -0
  43. package/dist/replication/trystero/peerPatterns.js +6 -0
  44. package/dist/rpc/RpcRoom.d.ts +5 -1
  45. package/dist/rpc/RpcRoom.js +80 -105
  46. package/dist/serviceFeatures/offlineScanner.d.ts +8 -8
  47. package/dist/serviceFeatures/offlineScanner.js +25 -21
  48. package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +4 -3
  49. package/dist/serviceFeatures/prepareDatabaseForUse.js +5 -4
  50. package/dist/serviceFeatures/remoteConfig.d.ts +10 -5
  51. package/dist/serviceModules/ServiceFileAccessBase.d.ts +1 -0
  52. package/dist/serviceModules/ServiceFileAccessBase.js +3 -0
  53. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
  54. package/dist/serviceModules/ServiceFileHandlerBase.js +23 -7
  55. package/dist/services/base/DatabaseEventService.d.ts +1 -1
  56. package/dist/services/base/IService.d.ts +3 -3
  57. package/dist/services/base/VaultScanResult.d.ts +5 -0
  58. package/dist/services/base/VaultService.d.ts +1 -1
  59. package/dist/services/implements/injectable/InjectableConflictService.d.ts +5 -5
  60. package/dist/services/lib/HandlerUtils.d.ts +8 -6
  61. package/dist/services/lib/HandlerUtils.js +14 -0
  62. package/dist/worker/bgWorker.js +1 -1
  63. package/docs/conflict-resolution.md +30 -0
  64. package/docs/development.md +46 -0
  65. package/docs/offline-scanner.md +12 -4
  66. package/docs/proven-in-use.md +63 -0
  67. package/docs/releasing.md +2 -0
  68. package/docs/service-feature-composition.md +56 -9
  69. package/docs/storage-events-and-reflection.md +6 -2
  70. package/package.json +10 -2
@@ -3,6 +3,10 @@ import { type RemoteConfigurationResult } from "../common/ConnectionString.js";
3
3
  import type { ObsidianLiveSyncSettings, RemoteConfiguration } from "../common/models/setting.type.js";
4
4
  import type { NecessaryServices } from "../interfaces/ServiceModule.js";
5
5
  export type RemoteConfigHost = NecessaryServices<"setting" | "UI" | "replication" | "control" | "appLifecycle" | "API", never>;
6
+ type RemoteConfigMigrationHost = NecessaryServices<"setting" | "API", never>;
7
+ type RemoteConfigMigrationFeatureHost = NecessaryServices<"setting" | "API" | "appLifecycle", never>;
8
+ type RemoteConfigSwitchHost = NecessaryServices<"setting" | "UI" | "control" | "API", never>;
9
+ type RemoteConfigReplicationHost = NecessaryServices<"setting" | "UI" | "replication" | "control" | "API", never>;
6
10
  export declare function migrateLegacyRemoteConfigurationsInPlace(settings: ObsidianLiveSyncSettings, log?: (message: string, level?: LOG_LEVEL) => void): boolean;
7
11
  /**
8
12
  * Generate a unique ID for a new remote configuration.
@@ -54,7 +58,7 @@ export declare function migrateP2PActiveRemoteConfigurationIdInPlace(settings: O
54
58
  /**
55
59
  * Migrates existing flat settings to the new multiple remote configurations list.
56
60
  */
57
- export declare function migrateToMultipleRemoteConfigurations(host: RemoteConfigHost): Promise<boolean>;
61
+ export declare function migrateToMultipleRemoteConfigurations(host: RemoteConfigMigrationHost): Promise<boolean>;
58
62
  /**
59
63
  * Logic to switch the active configuration.
60
64
  */
@@ -67,16 +71,17 @@ export declare function activateP2PRemoteConfiguration(settings: ObsidianLiveSyn
67
71
  /**
68
72
  * Command: Switch active connection
69
73
  */
70
- export declare function commandSwitchActiveRemote(host: RemoteConfigHost): Promise<void>;
74
+ export declare function commandSwitchActiveRemote(host: RemoteConfigSwitchHost): Promise<void>;
71
75
  /**
72
76
  * Command: Sync with a saved connection
73
77
  */
74
- export declare function commandReplicateWithSpecificRemote(host: RemoteConfigHost): Promise<void>;
78
+ export declare function commandReplicateWithSpecificRemote(host: RemoteConfigReplicationHost): Promise<void>;
75
79
  /**
76
80
  * Migration feature to be used during initialisation.
77
81
  */
78
- export declare function useRemoteConfigurationMigration(host: RemoteConfigHost): void;
82
+ export declare function useRemoteConfigurationMigration(host: RemoteConfigMigrationFeatureHost): void;
79
83
  /**
80
84
  * Hook to set up remote configuration features (Commands).
81
85
  */
82
- export declare function useRemoteConfiguration(host: RemoteConfigHost): boolean;
86
+ export declare function useRemoteConfiguration(host: RemoteConfigReplicationHost): boolean;
87
+ export {};
@@ -26,6 +26,7 @@ export declare class ServiceFileAccessBase<TAdapter extends IFileSystemAdapter<a
26
26
  private vault;
27
27
  private setting;
28
28
  constructor(services: StorageAccessBaseDependencies<TAdapter>);
29
+ hasPendingFileEvent(path: FilePath): boolean;
29
30
  restoreState(): Promise<void>;
30
31
  _everyOnFirstInitialize(): Promise<boolean>;
31
32
  _everyCommitPendingFileEvent(): Promise<boolean>;
@@ -21,6 +21,9 @@ class ServiceFileAccessBase extends ServiceModuleBase {
21
21
  return true;
22
22
  });
23
23
  }
24
+ hasPendingFileEvent(path) {
25
+ return this.vaultManager.hasPendingFileEvent?.(path) ?? true;
26
+ }
24
27
  restoreState() {
25
28
  return this.vaultManager.restoreState();
26
29
  }
@@ -98,6 +98,7 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
98
98
  getPath(entry: AnyEntry): FilePathWithPrefix;
99
99
  getPathWithoutPrefix(entry: AnyEntry): FilePathWithPrefix;
100
100
  readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
101
+ canSkipUnchangedFile(info: UXFileInfoStub, expected: MetaEntry): Promise<boolean>;
101
102
  tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
102
103
  private infoToStub;
103
104
  storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean, options?: StoreFileOptions): Promise<boolean>;
@@ -1,5 +1,5 @@
1
1
  import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
2
- import { serialized } from "octagonal-wheels/concurrency/lock";
2
+ import { withLocks } from "octagonal-wheels/concurrency/lock";
3
3
  import {
4
4
  createBlob,
5
5
  getDocDataAsArray,
@@ -15,11 +15,6 @@ import { getStoragePathFromUXFileInfo } from "../common/typeUtils.js";
15
15
  import { EVEN } from "../common/models/shared.const.symbols.js";
16
16
  import { tryGetFilePath } from "../common/utils.doc.js";
17
17
  import { getPathNormalizationVersion, normalisePathForIdentity } from "../common/pathNormalization.js";
18
- async function serializedByKeys(keys, callback) {
19
- const [key, ...remainingKeys] = keys;
20
- if (key === void 0) return await callback();
21
- return await serialized(key, () => serializedByKeys(remainingKeys, callback));
22
- }
23
18
  function getParentPath(path) {
24
19
  const lastSeparator = path.lastIndexOf("/");
25
20
  return lastSeparator < 0 ? "" : path.slice(0, lastSeparator);
@@ -258,6 +253,27 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
258
253
  }
259
254
  return readFile;
260
255
  }
256
+ async canSkipUnchangedFile(info, expected) {
257
+ const path = getStoragePathFromUXFileInfo(info);
258
+ const provenance = this.fileReflectionProvenance;
259
+ if (!provenance || !expected._rev || expected.deleted || expected._deleted || expected._conflicts?.length || this.storage.hasPendingFileEvent?.(path) !== false) return false;
260
+ try {
261
+ return await this.serializedByFileEventPaths([path], async () => {
262
+ const provenancePath = this.getProvenancePath(path);
263
+ const record = await provenance.get(provenancePath);
264
+ if (!record || !hasContentHash(record) || record.revision !== expected._rev || !Number.isFinite(record.observedStorageMtime) || record.observedStorageMtime !== info.stat.mtime || record.contentSize !== info.stat.size) return false;
265
+ const current = await this.db.fetchEntryMeta(path, void 0, true);
266
+ if (!current || current._rev !== record.revision || current.deleted || current._deleted || (await this.db.getConflictedRevs(path)).length > 0) return false;
267
+ const stat = await this.storage.stat(path);
268
+ if (!stat || stat.type !== "file" || stat.mtime !== record.observedStorageMtime || stat.size !== record.contentSize) return false;
269
+ return sameProvenance(await provenance.get(provenancePath), record) && this.storage.hasPendingFileEvent?.(path) === false;
270
+ });
271
+ } catch (error) {
272
+ this._log(`Could not inspect unchanged file metadata for ${path}`, LOG_LEVEL_VERBOSE);
273
+ this._log(error, LOG_LEVEL_VERBOSE);
274
+ return false;
275
+ }
276
+ }
261
277
  async tryRecordUntrackedFileRevision(info, expectedRevision) {
262
278
  if (!this.fileReflectionProvenance || !expectedRevision) return false;
263
279
  const path = getStoragePathFromUXFileInfo(info);
@@ -1179,7 +1195,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
1179
1195
  let failed = false;
1180
1196
  let failure;
1181
1197
  try {
1182
- result = await serializedByKeys(lockKeys, async () => {
1198
+ result = await withLocks(lockKeys, async () => {
1183
1199
  for (const id of uniqueIds) this.deferredConflictChecks.set(id, pending);
1184
1200
  try {
1185
1201
  return await callback(documentIds.length === 2 && documentIds[0] === documentIds[1]);
@@ -34,5 +34,5 @@ export declare abstract class DatabaseEventService<T extends ServiceContext = Se
34
34
  * @param reopenDatabase Whether to reopen the database if it is already open.
35
35
  * @param ignoreSuspending Whether to ignore any suspending state.
36
36
  */
37
- readonly initialiseDatabase: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
37
+ readonly initialiseDatabase: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: import("./VaultScanResult.js").VaultScanOptions) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
38
38
  }
@@ -17,7 +17,7 @@ import type { ActiveReplicatorContext, ContinuousReplicationRequest, Replication
17
17
  import type { RemoteResourceKind, RemoteResourceMap } from "../../replication/RemoteResource.js";
18
18
  import type { CentralRemoteAdministrationRequest, CentralRemoteAdministrationResult } from "../../replication/CentralRemoteAdministration.js";
19
19
  import type { HandlerOutcome, MultipleHandlerFunction } from "../lib/HandlerUtils.js";
20
- import type { VaultScanResult } from "./VaultScanResult.js";
20
+ import type { VaultScanResult, VaultScanOptions } from "./VaultScanResult.js";
21
21
  import type { UICommandRegistry } from "../../uiCommands.js";
22
22
  declare global {
23
23
  interface OPTIONAL_SYNC_FEATURES {
@@ -91,7 +91,7 @@ export interface IDatabaseEventService {
91
91
  onDatabaseInitialised(showNotice: boolean): Promise<boolean>;
92
92
  onDatabaseHasReady(): Promise<boolean>;
93
93
  onResetDatabase(db: LiveSyncLocalDB): Promise<boolean>;
94
- initialiseDatabase(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
94
+ initialiseDatabase(showingNotice?: boolean, reopenDatabase?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: VaultScanOptions): Promise<VaultScanResult>;
95
95
  }
96
96
  export interface IKeyValueDBService {
97
97
  openSimpleStore<T>(kind: string): SimpleStore<T>;
@@ -355,7 +355,7 @@ export interface ITweakValueService {
355
355
  export interface IVaultService {
356
356
  vaultName(): string;
357
357
  getVaultName(): string;
358
- scanVault(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
358
+ scanVault(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: VaultScanOptions): Promise<VaultScanResult>;
359
359
  isIgnoredByIgnoreFile(file: string | UXFileInfoStub): Promise<boolean>;
360
360
  isTargetFile(file: string | UXFileInfoStub,
361
361
  /** Inspect selection policy without treating a filename collision as an exclusion. */
@@ -10,4 +10,9 @@ export declare const VaultScanResults: {
10
10
  readonly COMPLETED: true;
11
11
  readonly COMPLETED_WITH_FILE_FAILURES: "completed-with-file-failures";
12
12
  };
13
+ /** Optional policy for an ordinary scan; omitted options retain strict content checks. */
14
+ export interface VaultScanOptions {
15
+ /** False permits unchanged tracked files to be skipped using storage and revision metadata. */
16
+ strictContentCheck?: boolean;
17
+ }
13
18
  export type VaultScanResult = (typeof VaultScanResults)[keyof typeof VaultScanResults];
@@ -26,7 +26,7 @@ export declare abstract class VaultService<T extends ServiceContext = ServiceCon
26
26
  * @param showingNotice Whether to show a notice to the user.
27
27
  * @param ignoreSuspending Whether to ignore any suspending state.
28
28
  */
29
- readonly scanVault: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
29
+ readonly scanVault: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean, scanOptions?: import("./VaultScanResult.js").VaultScanOptions) => Promise<import("./VaultScanResult.js").VaultScanResult>, unknown>;
30
30
  /**
31
31
  * Check if a file is ignored by the ignore file (e.g., .gitignore, .obsidianignore).
32
32
  * @param file The file path or file info stub to check.
@@ -1,11 +1,11 @@
1
1
  import { ConflictService } from "../../base/ConflictService.js";
2
2
  import type { ServiceContext } from "../../base/ServiceBase.js";
3
3
  export declare class InjectableConflictService<T extends ServiceContext> extends ConflictService<T> {
4
- queueCheckForIfOpen: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../common/types.js").FilePathWithPrefix) => Promise<void>, unknown>;
5
- queueCheckFor: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../common/types.js").FilePathWithPrefix) => Promise<void>, unknown>;
4
+ queueCheckForIfOpen: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../conflicts.js").FilePathWithPrefix) => Promise<void>, unknown>;
5
+ queueCheckFor: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../conflicts.js").FilePathWithPrefix) => Promise<void>, unknown>;
6
6
  ensureAllProcessed: import("../../lib/HandlerUtils.js").HandlerFunction<() => Promise<boolean>, unknown>;
7
- resolveByDeletingRevision: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../common/types.js").FilePathWithPrefix, deleteRevision: string, title: string) => Promise<typeof import("../../../common/types.js").MISSING_OR_ERROR | typeof import("../../../common/types.js").AUTO_MERGED>, unknown>;
8
- resolve: import("../../lib/HandlerUtils.js").HandlerFunction<(filename: import("../../../common/types.js").FilePathWithPrefix) => Promise<void>, unknown>;
9
- resolveByNewest: import("../../lib/HandlerUtils.js").HandlerFunction<(filename: import("../../../common/types.js").FilePathWithPrefix) => Promise<boolean>, unknown>;
7
+ resolveByDeletingRevision: import("../../lib/HandlerUtils.js").HandlerFunction<(path: import("../../../conflicts.js").FilePathWithPrefix, deleteRevision: string, title: string) => Promise<typeof import("../../../common/types.js").MISSING_OR_ERROR | typeof import("../../../common/types.js").AUTO_MERGED>, unknown>;
8
+ resolve: import("../../lib/HandlerUtils.js").HandlerFunction<(filename: import("../../../conflicts.js").FilePathWithPrefix) => Promise<void>, unknown>;
9
+ resolveByNewest: import("../../lib/HandlerUtils.js").HandlerFunction<(filename: import("../../../conflicts.js").FilePathWithPrefix) => Promise<boolean>, unknown>;
10
10
  resolveAllConflictedFilesByNewerOnes: import("../../lib/HandlerUtils.js").HandlerFunction<() => Promise<void>, unknown>;
11
11
  }
@@ -94,11 +94,12 @@ export declare class Binder<T extends (...args: any[]) => any> implements Binder
94
94
  */
95
95
  constructor(name: string, initialCallback?: T);
96
96
  private _callback;
97
+ private _registrationToken;
97
98
  /**
98
99
  * Assigns a new handler function.
99
100
  * @param callback The new handler function to assign.
100
101
  */
101
- assign(callback: T, override?: boolean): () => void;
102
+ assign(callback: T, override?: boolean): UnregisterFunction;
102
103
  /**
103
104
  * Invokes the assigned handler function with the provided arguments.
104
105
  * @param args The arguments to pass to the handler function.
@@ -114,13 +115,14 @@ export declare class LazyBinder<T extends (...args: any[]) => any> implements Bi
114
115
  private _name;
115
116
  private _callbackPromise;
116
117
  private _callback;
118
+ private _registrationToken;
117
119
  /**
118
120
  * Creates a new LazyBinder instance.
119
121
  * @param name The name of the handler.
120
122
  * @param initialCallback An optional initial callback function to assign.
121
123
  */
122
124
  constructor(name: string, initialCallback?: T);
123
- assign(callback: T, override?: boolean): () => void;
125
+ assign(callback: T, override?: boolean): UnregisterFunction;
124
126
  /**
125
127
  * Invokes the assigned handler function with the provided arguments.
126
128
  * @param args The arguments to pass to the handler function.
@@ -300,9 +302,9 @@ export interface HandlerFunction<TFunc extends (...args: any[]) => U | Promise<U
300
302
  * Assigns a new handler function.
301
303
  * @param callback The new handler function to assign.
302
304
  * @param override Whether to override the existing handler if one is already assigned.
303
- * @returns A function to unregister the assigned handler.
305
+ * @returns A function that unregisters this registration if it is still current.
304
306
  */
305
- setHandler: (callback: TFunc, override?: boolean) => void;
307
+ setHandler: (callback: TFunc, override?: boolean) => UnregisterFunction;
306
308
  }
307
309
  /**
308
310
  * A function type that can be used as a handler with assignable functionality.
@@ -316,9 +318,9 @@ export interface LazyHandlerFunction<TFunc extends (...args: any[]) => U | Promi
316
318
  * Assigns a new handler function.
317
319
  * @param callback The new handler function to assign.
318
320
  * @param override Whether to override the existing handler if one is already assigned.
319
- * @returns A function to unregister the assigned handler.
321
+ * @returns A function that unregisters this registration if it is still current.
320
322
  */
321
- setHandler: (callback: TFunc, override?: boolean) => void;
323
+ setHandler: (callback: TFunc, override?: boolean) => UnregisterFunction;
322
324
  }
323
325
  /**
324
326
  * A function type that can be used as a multiple handler with add/remove functionality.
@@ -14,6 +14,7 @@ class Binder {
14
14
  }
15
15
  }
16
16
  _callback = null;
17
+ _registrationToken = null;
17
18
  /**
18
19
  * Assigns a new handler function.
19
20
  * @param callback The new handler function to assign.
@@ -22,8 +23,14 @@ class Binder {
22
23
  if (this._callback && !override) {
23
24
  throw new Error(`Handler ${this._name} is already assigned.`);
24
25
  }
26
+ const registrationToken = /* @__PURE__ */ Symbol();
27
+ this._registrationToken = registrationToken;
25
28
  this._callback = callback;
26
29
  return () => {
30
+ if (this._registrationToken !== registrationToken) {
31
+ return;
32
+ }
33
+ this._registrationToken = null;
27
34
  this._callback = null;
28
35
  };
29
36
  }
@@ -43,6 +50,7 @@ class LazyBinder {
43
50
  _name;
44
51
  _callbackPromise = promiseWithResolvers();
45
52
  _callback = null;
53
+ _registrationToken = null;
46
54
  /**
47
55
  * Creates a new LazyBinder instance.
48
56
  * @param name The name of the handler.
@@ -59,9 +67,15 @@ class LazyBinder {
59
67
  if (this._callback && !override) {
60
68
  throw new Error(`Handler ${this._name} is already assigned.`);
61
69
  }
70
+ const registrationToken = /* @__PURE__ */ Symbol();
71
+ this._registrationToken = registrationToken;
62
72
  this._callback = callback;
63
73
  this._callbackPromise.resolve();
64
74
  return () => {
75
+ if (this._registrationToken !== registrationToken) {
76
+ return;
77
+ }
78
+ this._registrationToken = null;
65
79
  this._callback = null;
66
80
  this._callbackPromise = promiseWithResolvers();
67
81
  };