mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-13 21:02:30 +00:00
Tighten provider adapter boundaries
This commit is contained in:
@@ -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 { LiveSyncLocalDBEnv } from "@vrtmrz/livesync-commonlib/compat/pouchdb/LiveSyncLocalDB";
|
||||||
import type { LiveSyncCouchDBReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
import type { LiveSyncCouchDBReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import type { CheckPointInfo } from "@vrtmrz/livesync-commonlib/compat/replication/journal/JournalSyncTypes";
|
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 } from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
||||||
import type {
|
|
||||||
LiveSyncAbstractReplicator,
|
|
||||||
LiveSyncReplicatorEnv,
|
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/LiveSyncAbstractReplicator";
|
|
||||||
import type { ReplicatorInstance } from "@vrtmrz/livesync-commonlib/replication";
|
import type { ReplicatorInstance } from "@vrtmrz/livesync-commonlib/replication";
|
||||||
import { useTargetFilters } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/targetFilter";
|
import { useTargetFilters } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/targetFilter";
|
||||||
import { useRemoteConfigurationMigration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
import { useRemoteConfigurationMigration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
||||||
@@ -51,8 +47,6 @@ export class LiveSyncBaseCore<
|
|||||||
>
|
>
|
||||||
implements
|
implements
|
||||||
LiveSyncLocalDBEnv,
|
LiveSyncLocalDBEnv,
|
||||||
LiveSyncReplicatorEnv,
|
|
||||||
LiveSyncJournalReplicatorEnv,
|
|
||||||
LiveSyncCouchDBReplicatorEnv,
|
LiveSyncCouchDBReplicatorEnv,
|
||||||
HasSettings<ObsidianLiveSyncSettings>
|
HasSettings<ObsidianLiveSyncSettings>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
type CentralRemoteAdministrationReplicator,
|
type CentralRemoteAdministrationReplicator,
|
||||||
type CentralRemoteAdministrationResult,
|
type CentralRemoteAdministrationResult,
|
||||||
type CentralRemoteAdministrationRunner,
|
type CentralRemoteAdministrationRunner,
|
||||||
|
type ReplicatorInstance,
|
||||||
type SupportedCapability,
|
type SupportedCapability,
|
||||||
} from "@vrtmrz/livesync-commonlib/replication";
|
} from "@vrtmrz/livesync-commonlib/replication";
|
||||||
|
|
||||||
@@ -40,6 +41,19 @@ type CouchDBAdministrationReplicator = CentralRemoteAdministrationReplicator &
|
|||||||
|
|
||||||
type JournalAdministrationClient = Pick<LiveSyncJournalReplicator["client"], "downloadJson">;
|
type JournalAdministrationClient = Pick<LiveSyncJournalReplicator["client"], "downloadJson">;
|
||||||
|
|
||||||
|
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(
|
async function ensureLocalNodeIdentity(
|
||||||
replicator: CentralRemoteAdministrationReplicator
|
replicator: CentralRemoteAdministrationReplicator
|
||||||
): Promise<CentralRemoteAdministrationResult | undefined> {
|
): Promise<CentralRemoteAdministrationResult | undefined> {
|
||||||
@@ -116,8 +130,12 @@ async function runCentralRemoteAdministration(
|
|||||||
function requireCouchDBAdministrationOperations(
|
function requireCouchDBAdministrationOperations(
|
||||||
replicator: CentralRemoteAdministrationReplicator
|
replicator: CentralRemoteAdministrationReplicator
|
||||||
): asserts replicator is CouchDBAdministrationReplicator {
|
): asserts replicator is CouchDBAdministrationReplicator {
|
||||||
const candidate = replicator as Partial<CouchDBAdministrationReplicator>;
|
if (
|
||||||
if (typeof candidate.connectRemoteCouchDBWithSetting !== "function" || typeof candidate.isMobile !== "function") {
|
!("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.");
|
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(
|
function requireJournalAdministrationClient(
|
||||||
replicator: CentralRemoteAdministrationReplicator
|
replicator: CentralRemoteAdministrationReplicator
|
||||||
): JournalAdministrationClient {
|
): JournalAdministrationClient {
|
||||||
const client = (replicator as { readonly client?: JournalAdministrationClient }).client;
|
if (!("client" in replicator) || !isJournalAdministrationClient(replicator.client)) {
|
||||||
if (typeof client?.downloadJson !== "function") {
|
|
||||||
throw new Error("The configured Object Storage administration adapter does not provide milestone access.");
|
throw new Error("The configured Object Storage administration adapter does not provide milestone access.");
|
||||||
}
|
}
|
||||||
return client;
|
return replicator.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareObjectStorageMilestoneReader(
|
function prepareObjectStorageMilestoneReader(
|
||||||
@@ -191,14 +217,31 @@ function prepareObjectStorageMilestoneReader(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const runCouchDBCentralRemoteAdministration: CentralRemoteAdministrationRunner = async (replicator, setting, request) =>
|
const runCouchDBCentralRemoteAdministration: CentralRemoteAdministrationRunner = async (
|
||||||
await runCentralRemoteAdministration(replicator, setting, request, prepareCouchDBMilestoneReader);
|
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 (
|
const runObjectStorageCentralRemoteAdministration: CentralRemoteAdministrationRunner = async (
|
||||||
replicator,
|
replicator,
|
||||||
setting,
|
setting,
|
||||||
request
|
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. */
|
/** CouchDB mutation and milestone postcondition verification capability. */
|
||||||
export const COUCHDB_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY: SupportedCapability<CentralRemoteAdministrationRunner> =
|
export const COUCHDB_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY: SupportedCapability<CentralRemoteAdministrationRunner> =
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
type LiveSyncCouchDBReplicatorEnv,
|
type LiveSyncCouchDBReplicatorEnv,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
||||||
import type { LiveSyncJournalReplicatorEnv } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicatorEnv";
|
|
||||||
import {
|
import {
|
||||||
getCouchDBReplicatorConfigurationIdentity,
|
getCouchDBReplicatorConfigurationIdentity,
|
||||||
getObjectStorageReplicatorConfigurationIdentity,
|
getObjectStorageReplicatorConfigurationIdentity,
|
||||||
@@ -40,18 +39,19 @@ import {
|
|||||||
OBJECT_STORAGE_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY,
|
OBJECT_STORAGE_CENTRAL_REMOTE_ADMINISTRATION_CAPABILITY,
|
||||||
} from "./centralRemoteAdministration";
|
} 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. */
|
/** Minimal operation required by both central one-shot adapters. */
|
||||||
interface OneShotOutcomeReplicator extends ReplicatorInstance {
|
interface OneShotOutcomeReplicator extends ReplicatorInstance {
|
||||||
openOneShotReplicationWithOutcome(setting: RemoteDBSettings, showResult: boolean): Promise<ReplicationOutcome>;
|
openOneShotReplicationWithOutcome(setting: RemoteDBSettings, showResult: boolean): Promise<ReplicationOutcome>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function asOneShotOutcomeReplicator(instance: ReplicatorInstance): OneShotOutcomeReplicator | undefined {
|
function isOneShotOutcomeReplicator(instance: ReplicatorInstance): instance is OneShotOutcomeReplicator {
|
||||||
const candidate = instance as Partial<OneShotOutcomeReplicator>;
|
return (
|
||||||
return typeof candidate.openOneShotReplicationWithOutcome === "function"
|
"openOneShotReplicationWithOutcome" in instance &&
|
||||||
? (instance as OneShotOutcomeReplicator)
|
typeof instance.openOneShotReplicationWithOutcome === "function"
|
||||||
: undefined;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runOneShotWithOutcome(
|
async function runOneShotWithOutcome(
|
||||||
@@ -59,11 +59,10 @@ async function runOneShotWithOutcome(
|
|||||||
setting: RemoteDBSettings,
|
setting: RemoteDBSettings,
|
||||||
showResult: boolean
|
showResult: boolean
|
||||||
): Promise<ReplicationOutcome> {
|
): Promise<ReplicationOutcome> {
|
||||||
const replicator = asOneShotOutcomeReplicator(instance);
|
if (!isOneShotOutcomeReplicator(instance)) {
|
||||||
if (!replicator) {
|
|
||||||
return replicationFailed(new Error("The configured provider does not implement one-shot replication."));
|
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) => {
|
const couchDBUserInitiatedOneShot: UserInitiatedOneShotRunner = async (instance, setting, request) => {
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
type LiveSyncCouchDBReplicatorEnv,
|
type LiveSyncCouchDBReplicatorEnv,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
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";
|
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(
|
function createCouchDBConnectionProbe(
|
||||||
replicator: LiveSyncCouchDBReplicator,
|
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 {
|
export function createCouchDBConnectionProbeFactory(host: ConnectionResourceHost): ConnectionProbeFactory {
|
||||||
return (setting) => {
|
return (setting) => {
|
||||||
const snapshot = snapshotRemoteSettings(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 {
|
export function createObjectStorageConnectionProbeFactory(host: ConnectionResourceHost): ConnectionProbeFactory {
|
||||||
return (setting) => {
|
return (setting) => {
|
||||||
const snapshot = snapshotRemoteSettings(setting);
|
const snapshot = snapshotRemoteSettings(setting);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
type LiveSyncCouchDBReplicatorEnv,
|
type LiveSyncCouchDBReplicatorEnv,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
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";
|
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 {
|
interface PreferredTweakReplicator extends ResourceReplicator {
|
||||||
getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
|
getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
|
||||||
@@ -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 {
|
export function createCouchDBPreferredTweakProbeFactory(host: PreferredTweakResourceHost): PreferredTweakProbeFactory {
|
||||||
return (setting) => {
|
return (setting) => {
|
||||||
const snapshot = snapshotRemoteSettings(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(
|
export function createObjectStoragePreferredTweakProbeFactory(
|
||||||
host: PreferredTweakResourceHost
|
host: PreferredTweakResourceHost
|
||||||
): PreferredTweakProbeFactory {
|
): PreferredTweakProbeFactory {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
type LiveSyncCouchDBReplicatorEnv,
|
type LiveSyncCouchDBReplicatorEnv,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
|
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";
|
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. */
|
/** Minimal private Replicator surface required by a Security Seed resource. */
|
||||||
interface SecuritySeedReplicator extends ResourceReplicator {
|
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 {
|
export function createCouchDBSecuritySeedResourceFactory(host: SecuritySeedResourceHost): SecuritySeedResourceFactory {
|
||||||
return createSecuritySeedResourceFactory(() => new LiveSyncCouchDBReplicator(host));
|
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(
|
export function createObjectStorageSecuritySeedResourceFactory(
|
||||||
host: SecuritySeedResourceHost
|
host: SecuritySeedResourceHost
|
||||||
): SecuritySeedResourceFactory {
|
): SecuritySeedResourceFactory {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import type { RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
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 {
|
export interface ResourceReplicator {
|
||||||
closeReplication(): void | Promise<void>;
|
closeReplication(): void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import {
|
|||||||
import { checkSyncInfo } from "@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation";
|
import { checkSyncInfo } from "@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation";
|
||||||
import { createReplicatorDisposer, snapshotRemoteSettings } from "./shared";
|
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(
|
export function createCouchDBSynchronisationInformationResourceFactory(
|
||||||
host: LiveSyncCouchDBReplicatorEnv
|
host: LiveSyncCouchDBReplicatorEnv
|
||||||
): SynchronisationInformationResourceFactory {
|
): SynchronisationInformationResourceFactory {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
|||||||
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||||
import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events";
|
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 { 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 { delay } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||||
import { isNotFoundError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc";
|
import { isNotFoundError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc";
|
||||||
import { ensureLocalDatabaseMaintenancePrerequisites } from "./maintenancePrerequisites";
|
import { ensureLocalDatabaseMaintenancePrerequisites } from "./maintenancePrerequisites";
|
||||||
@@ -29,6 +30,31 @@ type NoteDocumentID = DocumentID;
|
|||||||
type Rev = string;
|
type Rev = string;
|
||||||
|
|
||||||
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
|
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
|
||||||
|
|
||||||
|
type CouchDBCompactionReplicator = ReplicatorInstance &
|
||||||
|
Pick<LiveSyncCouchDBReplicator, "connectRemoteCouchDBWithSetting">;
|
||||||
|
|
||||||
|
type CouchDBGarbageCollectionReplicator = ReplicatorInstance &
|
||||||
|
Pick<LiveSyncCouchDBReplicator, "getConnectedDeviceList" | "openOneShotReplication">;
|
||||||
|
|
||||||
|
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 {
|
export class LocalDatabaseMaintenance extends LiveSyncCommands {
|
||||||
onunload(): void {
|
onunload(): void {
|
||||||
// NO OP.
|
// NO OP.
|
||||||
@@ -737,8 +763,8 @@ Success: ${successCount}, Errored: ${errored}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async compactDatabase() {
|
async compactDatabase() {
|
||||||
const replicator = this.core.replicator as Partial<LiveSyncCouchDBReplicator>;
|
const replicator = this.core.replicator;
|
||||||
if (typeof replicator?.connectRemoteCouchDBWithSetting !== "function") return;
|
if (!canCompactCouchDBRemote(replicator)) return;
|
||||||
const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
|
const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
this._notice("Failed to connect to remote for compaction.", "gc-compact");
|
this._notice("Failed to connect to remote for compaction.", "gc-compact");
|
||||||
@@ -841,13 +867,8 @@ Success: ${successCount}, Errored: ${errored}`;
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
async gcv3() {
|
async gcv3() {
|
||||||
const replicator = this.core.replicator as Partial<LiveSyncCouchDBReplicator>;
|
const replicator = this.core.replicator;
|
||||||
if (
|
if (this.settings.remoteType !== REMOTE_COUCHDB || !canRunCouchDBGarbageCollection(replicator)) return;
|
||||||
this.settings.remoteType !== REMOTE_COUCHDB ||
|
|
||||||
typeof replicator?.openOneShotReplication !== "function" ||
|
|
||||||
typeof replicator.getConnectedDeviceList !== "function"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
if (!(await this.ensureAvailable("Garbage Collection"))) return;
|
if (!(await this.ensureAvailable("Garbage Collection"))) return;
|
||||||
// Start one-shot replication to ensure all changes are synced before GC.
|
// Start one-shot replication to ensure all changes are synced before GC.
|
||||||
const r0 = await replicator.openOneShotReplication(this.settings, false, false, "sync");
|
const r0 = await replicator.openOneShotReplication(this.settings, false, false, "sync");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { balanceChunkPurgedDBs, purgeUnreferencedChunks } from "@vrtmrz/livesync
|
|||||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||||
import {
|
import {
|
||||||
CENTRAL_COMPATIBILITY_REJECTION_REASONS,
|
CENTRAL_COMPATIBILITY_REJECTION_REASONS,
|
||||||
|
type ReplicatorInstance,
|
||||||
type ReplicationFailureRequest,
|
type ReplicationFailureRequest,
|
||||||
} from "@vrtmrz/livesync-commonlib/replication";
|
} from "@vrtmrz/livesync-commonlib/replication";
|
||||||
import { $msg } from "@/common/translation";
|
import { $msg } from "@/common/translation";
|
||||||
@@ -23,6 +24,25 @@ interface CentralCompatibilityRecoveryContext {
|
|||||||
readonly services: CentralCompatibilityRecoveryServices;
|
readonly services: CentralCompatibilityRecoveryServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PreferredRemoteTweakWriter extends ReplicatorInstance {
|
||||||
|
setPreferredRemoteTweakSettings(setting: ObsidianLiveSyncSettings): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResolvedRemoteWriter extends ReplicatorInstance {
|
||||||
|
markRemoteResolved(setting: ObsidianLiveSyncSettings): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
* Compose central compatibility recovery around the exact failed publication.
|
||||||
* Remote mutations re-admit that publication and become no-ops after a
|
* 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;
|
let updated = false;
|
||||||
await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => {
|
await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => {
|
||||||
if (activeContext !== failedContext) return;
|
if (activeContext !== failedContext) return;
|
||||||
const candidate = activeContext.replicator as typeof activeContext.replicator & {
|
if (!canSetPreferredRemoteTweakSettings(activeContext.replicator)) return;
|
||||||
setPreferredRemoteTweakSettings?: (setting: ObsidianLiveSyncSettings) => Promise<void>;
|
await activeContext.replicator.setPreferredRemoteTweakSettings({ ...effectiveSetting });
|
||||||
};
|
|
||||||
if (typeof candidate.setPreferredRemoteTweakSettings !== "function") return;
|
|
||||||
await candidate.setPreferredRemoteTweakSettings({ ...effectiveSetting });
|
|
||||||
updated = true;
|
updated = true;
|
||||||
});
|
});
|
||||||
return updated;
|
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;
|
let unlocked = false;
|
||||||
await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => {
|
await context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => {
|
||||||
if (activeContext !== failedContext) return;
|
if (activeContext !== failedContext) return;
|
||||||
const replicator = activeContext.replicator as typeof activeContext.replicator & {
|
if (!canMarkRemoteResolved(activeContext.replicator)) return;
|
||||||
markRemoteResolved(setting: ObsidianLiveSyncSettings): Promise<void>;
|
await activeContext.replicator.markRemoteResolved(setting);
|
||||||
};
|
|
||||||
if (typeof replicator.markRemoteResolved !== "function") return;
|
|
||||||
await replicator.markRemoteResolved(setting);
|
|
||||||
unlocked = true;
|
unlocked = true;
|
||||||
});
|
});
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ type LocalApplicationActivityOwner = {
|
|||||||
runBoundedLocalApplicationActivity<T>(task: () => T | PromiseLike<T>, options?: { label?: string }): Promise<T>;
|
runBoundedLocalApplicationActivity<T>(task: () => T | PromiseLike<T>, options?: { label?: string }): Promise<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ownsLocalApplicationActivity(value: object): value is LocalApplicationActivityOwner {
|
||||||
|
return (
|
||||||
|
"runBoundedLocalApplicationActivity" in value && typeof value.runBoundedLocalApplicationActivity === "function"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compose result application, automatic triggers, preflight, and central
|
* Compose result application, automatic triggers, preflight, and central
|
||||||
* compatibility recovery around the existing typed Services.
|
* compatibility recovery around the existing typed Services.
|
||||||
@@ -28,8 +34,9 @@ export function useReplicationFeature<TContext extends ServiceContext, TCommands
|
|||||||
const { services } = core;
|
const { services } = core;
|
||||||
// Obsidian adds an application-activity owner to its ReplicatorService.
|
// Obsidian adds an application-activity owner to its ReplicatorService.
|
||||||
// Generic hosts retain the former direct-execution fallback.
|
// Generic hosts retain the former direct-execution fallback.
|
||||||
const localApplicationActivityOwner = services.replicator as typeof services.replicator &
|
const localApplicationActivityOwner = ownsLocalApplicationActivity(services.replicator)
|
||||||
Partial<LocalApplicationActivityOwner>;
|
? services.replicator
|
||||||
|
: undefined;
|
||||||
const resultProcessor = new ReplicateResultProcessor({
|
const resultProcessor = new ReplicateResultProcessor({
|
||||||
currentSettings: () => services.setting.currentSettings(),
|
currentSettings: () => services.setting.currentSettings(),
|
||||||
keyValueDB: services.keyValueDB.kvDB,
|
keyValueDB: services.keyValueDB.kvDB,
|
||||||
@@ -40,7 +47,7 @@ export function useReplicationFeature<TContext extends ServiceContext, TCommands
|
|||||||
fireAndForget(() => services.replicator.onCloseActiveReplication());
|
fireAndForget(() => services.replicator.onCloseActiveReplication());
|
||||||
},
|
},
|
||||||
runLocalApplicationActivity: async (task, options) =>
|
runLocalApplicationActivity: async (task, options) =>
|
||||||
localApplicationActivityOwner.runBoundedLocalApplicationActivity
|
localApplicationActivityOwner
|
||||||
? await localApplicationActivityOwner.runBoundedLocalApplicationActivity(task, options)
|
? await localApplicationActivityOwner.runBoundedLocalApplicationActivity(task, options)
|
||||||
: await task(),
|
: await task(),
|
||||||
services: {
|
services: {
|
||||||
|
|||||||
Reference in New Issue
Block a user