From 69cd6250d2b553e0a81d3822cbffbf827a8b3b63 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 30 Aug 2026 12:42:34 +0000 Subject: [PATCH] Tighten provider adapter boundaries --- src/LiveSyncBaseCore.ts | 8 +-- src/common/centralRemoteAdministration.ts | 59 ++++++++++++++++--- src/common/replicatorProviders.ts | 19 +++--- src/common/replicatorResources/connection.ts | 18 ++++-- .../replicatorResources/preferredTweak.ts | 8 +-- .../replicatorResources/securitySeed.ts | 8 +-- src/common/replicatorResources/shared.ts | 7 +++ .../synchronisationInformation.ts | 7 ++- .../CmdLocalDatabaseMainte.ts | 39 +++++++++--- .../centralCompatibilityRecovery.ts | 34 +++++++---- src/serviceFeatures/replication/index.ts | 13 +++- 11 files changed, 160 insertions(+), 60 deletions(-) diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index 6d2c69a9..4e4f7e07 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -15,11 +15,7 @@ import type { StorageAccess } from "@vrtmrz/livesync-commonlib/compat/interfaces import type { LiveSyncLocalDBEnv } from "@vrtmrz/livesync-commonlib/compat/pouchdb/LiveSyncLocalDB"; import type { LiveSyncCouchDBReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import type { CheckPointInfo } from "@vrtmrz/livesync-commonlib/compat/replication/journal/JournalSyncTypes"; -import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv"; -import type { - LiveSyncAbstractReplicator, - LiveSyncReplicatorEnv, -} from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator"; +import type { LiveSyncAbstractReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator"; import type { ReplicatorInstance } from "@vrtmrz/livesync-commonlib/replication"; import { useTargetFilters } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/targetFilter"; import { useRemoteConfigurationMigration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig"; @@ -51,8 +47,6 @@ export class LiveSyncBaseCore< > implements LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, LiveSyncCouchDBReplicatorEnv, HasSettings { diff --git a/src/common/centralRemoteAdministration.ts b/src/common/centralRemoteAdministration.ts index 24d937b7..790c96a3 100644 --- a/src/common/centralRemoteAdministration.ts +++ b/src/common/centralRemoteAdministration.ts @@ -19,6 +19,7 @@ import { type CentralRemoteAdministrationReplicator, type CentralRemoteAdministrationResult, type CentralRemoteAdministrationRunner, + type ReplicatorInstance, type SupportedCapability, } from "@vrtmrz/livesync-commonlib/replication"; @@ -40,6 +41,19 @@ type CouchDBAdministrationReplicator = CentralRemoteAdministrationReplicator & type JournalAdministrationClient = Pick; +function isCentralRemoteAdministrationReplicator( + replicator: ReplicatorInstance +): replicator is CentralRemoteAdministrationReplicator { + return ( + "nodeid" in replicator && + typeof replicator.nodeid === "string" && + "markRemoteResolved" in replicator && + typeof replicator.markRemoteResolved === "function" && + "markRemoteLocked" in replicator && + typeof replicator.markRemoteLocked === "function" + ); +} + async function ensureLocalNodeIdentity( replicator: CentralRemoteAdministrationReplicator ): Promise { @@ -116,8 +130,12 @@ async function runCentralRemoteAdministration( function requireCouchDBAdministrationOperations( replicator: CentralRemoteAdministrationReplicator ): asserts replicator is CouchDBAdministrationReplicator { - const candidate = replicator as Partial; - if (typeof candidate.connectRemoteCouchDBWithSetting !== "function" || typeof candidate.isMobile !== "function") { + if ( + !("connectRemoteCouchDBWithSetting" in replicator) || + typeof replicator.connectRemoteCouchDBWithSetting !== "function" || + !("isMobile" in replicator) || + typeof replicator.isMobile !== "function" + ) { throw new Error("The configured CouchDB administration adapter does not provide milestone access."); } } @@ -164,14 +182,22 @@ function prepareCouchDBMilestoneReader( }; } +function isJournalAdministrationClient(client: unknown): client is JournalAdministrationClient { + return ( + typeof client === "object" && + client !== null && + "downloadJson" in client && + typeof client.downloadJson === "function" + ); +} + function requireJournalAdministrationClient( replicator: CentralRemoteAdministrationReplicator ): JournalAdministrationClient { - const client = (replicator as { readonly client?: JournalAdministrationClient }).client; - if (typeof client?.downloadJson !== "function") { + if (!("client" in replicator) || !isJournalAdministrationClient(replicator.client)) { throw new Error("The configured Object Storage administration adapter does not provide milestone access."); } - return client; + return replicator.client; } function prepareObjectStorageMilestoneReader( @@ -191,14 +217,31 @@ function prepareObjectStorageMilestoneReader( }; } -const runCouchDBCentralRemoteAdministration: CentralRemoteAdministrationRunner = async (replicator, setting, request) => - await runCentralRemoteAdministration(replicator, setting, request, prepareCouchDBMilestoneReader); +const runCouchDBCentralRemoteAdministration: CentralRemoteAdministrationRunner = async ( + replicator, + setting, + request +) => { + if (!isCentralRemoteAdministrationReplicator(replicator)) { + return centralRemoteAdministrationVerificationFailed( + CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS.CAPABILITY_NOT_APPLICABLE + ); + } + return await runCentralRemoteAdministration(replicator, setting, request, prepareCouchDBMilestoneReader); +}; const runObjectStorageCentralRemoteAdministration: CentralRemoteAdministrationRunner = async ( replicator, setting, request -) => await runCentralRemoteAdministration(replicator, setting, request, prepareObjectStorageMilestoneReader); +) => { + if (!isCentralRemoteAdministrationReplicator(replicator)) { + return centralRemoteAdministrationVerificationFailed( + CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS.CAPABILITY_NOT_APPLICABLE + ); + } + return await runCentralRemoteAdministration(replicator, setting, request, prepareObjectStorageMilestoneReader); +}; /** CouchDB mutation and milestone postcondition verification capability. */ export const COUCHDB_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY: SupportedCapability = diff --git a/src/common/replicatorProviders.ts b/src/common/replicatorProviders.ts index c94003f9..effba2a7 100644 --- a/src/common/replicatorProviders.ts +++ b/src/common/replicatorProviders.ts @@ -21,7 +21,6 @@ import { type LiveSyncCouchDBReplicatorEnv, } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator"; -import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv"; import { getCouchDBReplicatorConfigurationIdentity, getObjectStorageReplicatorConfigurationIdentity, @@ -40,18 +39,19 @@ import { OBJECT_STORAGE_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY, } from "./centralRemoteAdministration"; -export type CentralReplicatorProviderHost = LiveSyncCouchDBReplicatorEnv & LiveSyncJournalReplicatorEnv; +/** Host environment sufficient to construct every current central provider. */ +export type CentralReplicatorProviderHost = LiveSyncCouchDBReplicatorEnv; /** Minimal operation required by both central one-shot adapters. */ interface OneShotOutcomeReplicator extends ReplicatorInstance { openOneShotReplicationWithOutcome(setting: RemoteDBSettings, showResult: boolean): Promise; } -function asOneShotOutcomeReplicator(instance: ReplicatorInstance): OneShotOutcomeReplicator | undefined { - const candidate = instance as Partial; - return typeof candidate.openOneShotReplicationWithOutcome === "function" - ? (instance as OneShotOutcomeReplicator) - : undefined; +function isOneShotOutcomeReplicator(instance: ReplicatorInstance): instance is OneShotOutcomeReplicator { + return ( + "openOneShotReplicationWithOutcome" in instance && + typeof instance.openOneShotReplicationWithOutcome === "function" + ); } async function runOneShotWithOutcome( @@ -59,11 +59,10 @@ async function runOneShotWithOutcome( setting: RemoteDBSettings, showResult: boolean ): Promise { - const replicator = asOneShotOutcomeReplicator(instance); - if (!replicator) { + if (!isOneShotOutcomeReplicator(instance)) { return replicationFailed(new Error("The configured provider does not implement one-shot replication.")); } - return await replicator.openOneShotReplicationWithOutcome(setting, showResult); + return await instance.openOneShotReplicationWithOutcome(setting, showResult); } const couchDBUserInitiatedOneShot: UserInitiatedOneShotRunner = async (instance, setting, request) => { diff --git a/src/common/replicatorResources/connection.ts b/src/common/replicatorResources/connection.ts index 2f20a7ec..3b52bede 100644 --- a/src/common/replicatorResources/connection.ts +++ b/src/common/replicatorResources/connection.ts @@ -9,10 +9,10 @@ import { type LiveSyncCouchDBReplicatorEnv, } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator"; -import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv"; import { createReplicatorDisposer, snapshotRemoteSettings } from "./shared"; -export type ConnectionResourceHost = LiveSyncCouchDBReplicatorEnv & LiveSyncJournalReplicatorEnv; +/** Host environment sufficient to construct either central connection probe. */ +export type ConnectionResourceHost = LiveSyncCouchDBReplicatorEnv; function createCouchDBConnectionProbe( replicator: LiveSyncCouchDBReplicator, @@ -60,7 +60,12 @@ function createObjectStorageConnectionProbe( }; } -/** Build an unpublished CouchDB connection resource for one host. */ +/** + * Build an unpublished CouchDB connection probe for one host. + * + * The probe owns both its concrete Replicator and each connection it opens. It + * never publishes that Replicator as the active provider instance. + */ export function createCouchDBConnectionProbeFactory(host: ConnectionResourceHost): ConnectionProbeFactory { return (setting) => { const snapshot = snapshotRemoteSettings(setting); @@ -68,7 +73,12 @@ export function createCouchDBConnectionProbeFactory(host: ConnectionResourceHost }; } -/** Build an unpublished Object Storage connection resource for one host. */ +/** + * Build an unpublished Object Storage connection probe for one host. + * + * The probe owns its concrete Replicator and never publishes or replaces the + * active provider instance. + */ export function createObjectStorageConnectionProbeFactory(host: ConnectionResourceHost): ConnectionProbeFactory { return (setting) => { const snapshot = snapshotRemoteSettings(setting); diff --git a/src/common/replicatorResources/preferredTweak.ts b/src/common/replicatorResources/preferredTweak.ts index b2c66415..80148c27 100644 --- a/src/common/replicatorResources/preferredTweak.ts +++ b/src/common/replicatorResources/preferredTweak.ts @@ -5,10 +5,10 @@ import { type LiveSyncCouchDBReplicatorEnv, } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator"; -import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv"; import { createReplicatorDisposer, snapshotRemoteSettings, type ResourceReplicator } from "./shared"; -export type PreferredTweakResourceHost = LiveSyncCouchDBReplicatorEnv & LiveSyncJournalReplicatorEnv; +/** Host environment sufficient to construct either preferred-tweak probe. */ +export type PreferredTweakResourceHost = LiveSyncCouchDBReplicatorEnv; interface PreferredTweakReplicator extends ResourceReplicator { getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise; @@ -24,7 +24,7 @@ function createPreferredTweakProbe( }; } -/** Build an unpublished CouchDB preferred-tweak resource for one host. */ +/** Build an unpublished, independently disposed CouchDB preferred-tweak probe. */ export function createCouchDBPreferredTweakProbeFactory(host: PreferredTweakResourceHost): PreferredTweakProbeFactory { return (setting) => { const snapshot = snapshotRemoteSettings(setting); @@ -32,7 +32,7 @@ export function createCouchDBPreferredTweakProbeFactory(host: PreferredTweakReso }; } -/** Build an unpublished Object Storage preferred-tweak resource for one host. */ +/** Build an unpublished, independently disposed Object Storage preferred-tweak probe. */ export function createObjectStoragePreferredTweakProbeFactory( host: PreferredTweakResourceHost ): PreferredTweakProbeFactory { diff --git a/src/common/replicatorResources/securitySeed.ts b/src/common/replicatorResources/securitySeed.ts index b0e873bb..07bc6a6a 100644 --- a/src/common/replicatorResources/securitySeed.ts +++ b/src/common/replicatorResources/securitySeed.ts @@ -5,10 +5,10 @@ import { type LiveSyncCouchDBReplicatorEnv, } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator"; -import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv"; import { createReplicatorDisposer, snapshotRemoteSettings, type ResourceReplicator } from "./shared"; -export type SecuritySeedResourceHost = LiveSyncCouchDBReplicatorEnv & LiveSyncJournalReplicatorEnv; +/** Host environment sufficient to construct either Security Seed resource. */ +export type SecuritySeedResourceHost = LiveSyncCouchDBReplicatorEnv; /** Minimal private Replicator surface required by a Security Seed resource. */ interface SecuritySeedReplicator extends ResourceReplicator { @@ -28,12 +28,12 @@ function createSecuritySeedResourceFactory( }; } -/** Build an unpublished CouchDB Security Seed resource for one host. */ +/** Build an unpublished, independently disposed CouchDB Security Seed resource. */ export function createCouchDBSecuritySeedResourceFactory(host: SecuritySeedResourceHost): SecuritySeedResourceFactory { return createSecuritySeedResourceFactory(() => new LiveSyncCouchDBReplicator(host)); } -/** Build an unpublished Object Storage Security Seed resource for one host. */ +/** Build an unpublished, independently disposed Object Storage Security Seed resource. */ export function createObjectStorageSecuritySeedResourceFactory( host: SecuritySeedResourceHost ): SecuritySeedResourceFactory { diff --git a/src/common/replicatorResources/shared.ts b/src/common/replicatorResources/shared.ts index 77d3fb98..a88f070a 100644 --- a/src/common/replicatorResources/shared.ts +++ b/src/common/replicatorResources/shared.ts @@ -1,5 +1,12 @@ import type { RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; +/** + * Closeable surface of a concrete Replicator owned by one private resource. + * + * It deliberately exposes no active-provider controls: the resource may use + * the helper for one bounded operation, then must dispose it without + * publishing or replacing the active Replicator. + */ export interface ResourceReplicator { closeReplication(): void | Promise; } diff --git a/src/common/replicatorResources/synchronisationInformation.ts b/src/common/replicatorResources/synchronisationInformation.ts index edd83174..80b38e57 100644 --- a/src/common/replicatorResources/synchronisationInformation.ts +++ b/src/common/replicatorResources/synchronisationInformation.ts @@ -6,7 +6,12 @@ import { import { checkSyncInfo } from "@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation"; import { createReplicatorDisposer, snapshotRemoteSettings } from "./shared"; -/** Build an owned CouchDB synchronisation-information verifier for one host. */ +/** + * Build an unpublished CouchDB synchronisation-information verifier. + * + * The resource owns its concrete Replicator and connection, and cannot replace + * the active provider instance. + */ export function createCouchDBSynchronisationInformationResourceFactory( host: LiveSyncCouchDBReplicatorEnv ): SynchronisationInformationResourceFactory { diff --git a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts index 9ce9bd92..85b15d90 100644 --- a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts +++ b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts @@ -17,6 +17,7 @@ import { serialized } from "octagonal-wheels/concurrency/lock_v2"; import { arrayToChunkedArray } from "octagonal-wheels/collection"; import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events"; import type { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; +import type { ReplicatorInstance } from "@vrtmrz/livesync-commonlib/replication"; import { delay } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { isNotFoundError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc"; import { ensureLocalDatabaseMaintenancePrerequisites } from "./maintenancePrerequisites"; @@ -29,6 +30,31 @@ type NoteDocumentID = DocumentID; type Rev = string; type ChunkUsageMap = Map>>; + +type CouchDBCompactionReplicator = ReplicatorInstance & + Pick; + +type CouchDBGarbageCollectionReplicator = ReplicatorInstance & + Pick; + +function canCompactCouchDBRemote(replicator: ReplicatorInstance): replicator is CouchDBCompactionReplicator { + return ( + "connectRemoteCouchDBWithSetting" in replicator && + typeof replicator.connectRemoteCouchDBWithSetting === "function" + ); +} + +function canRunCouchDBGarbageCollection( + replicator: ReplicatorInstance +): replicator is CouchDBGarbageCollectionReplicator { + return ( + "getConnectedDeviceList" in replicator && + typeof replicator.getConnectedDeviceList === "function" && + "openOneShotReplication" in replicator && + typeof replicator.openOneShotReplication === "function" + ); +} + export class LocalDatabaseMaintenance extends LiveSyncCommands { onunload(): void { // NO OP. @@ -737,8 +763,8 @@ Success: ${successCount}, Errored: ${errored}`; } async compactDatabase() { - const replicator = this.core.replicator as Partial; - if (typeof replicator?.connectRemoteCouchDBWithSetting !== "function") return; + const replicator = this.core.replicator; + if (!canCompactCouchDBRemote(replicator)) return; const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); if (!remote) { this._notice("Failed to connect to remote for compaction.", "gc-compact"); @@ -841,13 +867,8 @@ Success: ${successCount}, Errored: ${errored}`; // } // } async gcv3() { - const replicator = this.core.replicator as Partial; - if ( - this.settings.remoteType !== REMOTE_COUCHDB || - typeof replicator?.openOneShotReplication !== "function" || - typeof replicator.getConnectedDeviceList !== "function" - ) - return; + const replicator = this.core.replicator; + if (this.settings.remoteType !== REMOTE_COUCHDB || !canRunCouchDBGarbageCollection(replicator)) return; if (!(await this.ensureAvailable("Garbage Collection"))) return; // Start one-shot replication to ensure all changes are synced before GC. const r0 = await replicator.openOneShotReplication(this.settings, false, false, "sync"); diff --git a/src/serviceFeatures/replication/centralCompatibilityRecovery.ts b/src/serviceFeatures/replication/centralCompatibilityRecovery.ts index 28ebd029..648a5f24 100644 --- a/src/serviceFeatures/replication/centralCompatibilityRecovery.ts +++ b/src/serviceFeatures/replication/centralCompatibilityRecovery.ts @@ -5,6 +5,7 @@ import { balanceChunkPurgedDBs, purgeUnreferencedChunks } from "@vrtmrz/livesync import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { CENTRAL_COMPATIBILITY_REJECTION_REASONS, + type ReplicatorInstance, type ReplicationFailureRequest, } from "@vrtmrz/livesync-commonlib/replication"; import { $msg } from "@/common/translation"; @@ -23,6 +24,25 @@ interface CentralCompatibilityRecoveryContext { readonly services: CentralCompatibilityRecoveryServices; } +interface PreferredRemoteTweakWriter extends ReplicatorInstance { + setPreferredRemoteTweakSettings(setting: ObsidianLiveSyncSettings): Promise; +} + +interface ResolvedRemoteWriter extends ReplicatorInstance { + markRemoteResolved(setting: ObsidianLiveSyncSettings): Promise; +} + +function canSetPreferredRemoteTweakSettings(replicator: ReplicatorInstance): replicator is PreferredRemoteTweakWriter { + return ( + "setPreferredRemoteTweakSettings" in replicator && + typeof replicator.setPreferredRemoteTweakSettings === "function" + ); +} + +function canMarkRemoteResolved(replicator: ReplicatorInstance): replicator is ResolvedRemoteWriter { + return "markRemoteResolved" in replicator && typeof replicator.markRemoteResolved === "function"; +} + /** * Compose central compatibility recovery around the exact failed publication. * Remote mutations re-admit that publication and become no-ops after a @@ -126,11 +146,8 @@ Even if you choose to clean up, you will see this option again if you exit Obsid let updated = false; await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => { if (activeContext !== failedContext) return; - const candidate = activeContext.replicator as typeof activeContext.replicator & { - setPreferredRemoteTweakSettings?: (setting: ObsidianLiveSyncSettings) => Promise; - }; - if (typeof candidate.setPreferredRemoteTweakSettings !== "function") return; - await candidate.setPreferredRemoteTweakSettings({ ...effectiveSetting }); + if (!canSetPreferredRemoteTweakSettings(activeContext.replicator)) return; + await activeContext.replicator.setPreferredRemoteTweakSettings({ ...effectiveSetting }); updated = true; }); return updated; @@ -177,11 +194,8 @@ Even if you choose to clean up, you will see this option again if you exit Obsid let unlocked = false; await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => { if (activeContext !== failedContext) return; - const replicator = activeContext.replicator as typeof activeContext.replicator & { - markRemoteResolved(setting: ObsidianLiveSyncSettings): Promise; - }; - if (typeof replicator.markRemoteResolved !== "function") return; - await replicator.markRemoteResolved(setting); + if (!canMarkRemoteResolved(activeContext.replicator)) return; + await activeContext.replicator.markRemoteResolved(setting); unlocked = true; }); if (unlocked) { diff --git a/src/serviceFeatures/replication/index.ts b/src/serviceFeatures/replication/index.ts index 78c28441..813f106a 100644 --- a/src/serviceFeatures/replication/index.ts +++ b/src/serviceFeatures/replication/index.ts @@ -13,6 +13,12 @@ type LocalApplicationActivityOwner = { runBoundedLocalApplicationActivity(task: () => T | PromiseLike, options?: { label?: string }): Promise; }; +function ownsLocalApplicationActivity(value: object): value is LocalApplicationActivityOwner { + return ( + "runBoundedLocalApplicationActivity" in value && typeof value.runBoundedLocalApplicationActivity === "function" + ); +} + /** * Compose result application, automatic triggers, preflight, and central * compatibility recovery around the existing typed Services. @@ -28,8 +34,9 @@ export function useReplicationFeature; + const localApplicationActivityOwner = ownsLocalApplicationActivity(services.replicator) + ? services.replicator + : undefined; const resultProcessor = new ReplicateResultProcessor({ currentSettings: () => services.setting.currentSettings(), keyValueDB: services.keyValueDB.kvDB, @@ -40,7 +47,7 @@ export function useReplicationFeature services.replicator.onCloseActiveReplication()); }, runLocalApplicationActivity: async (task, options) => - localApplicationActivityOwner.runBoundedLocalApplicationActivity + localApplicationActivityOwner ? await localApplicationActivityOwner.runBoundedLocalApplicationActivity(task, options) : await task(), services: {