@vrtmrz/livesync-commonlib 0.1.22 → 0.1.24
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.
- package/dist/common/models/fileaccess.type.d.ts +2 -0
- package/dist/common/models/tweak.compatibility.d.ts +34 -0
- package/dist/common/models/tweak.compatibility.js +110 -0
- package/dist/common/models/tweak.definition.d.ts +105 -19
- package/dist/common/models/tweak.definition.js +62 -29
- package/dist/common/types.d.ts +3 -0
- package/dist/managers/StorageEventManager.js +1 -0
- package/dist/pouchdb/LiveSyncDBFunctions.d.ts +3 -3
- package/dist/pouchdb/LiveSyncDBFunctions.js +9 -15
- package/dist/replication/CentralCompatibility.d.ts +4 -2
- package/dist/replication/CentralCompatibility.js +5 -3
- package/dist/replication/couchdb/LiveSyncReplicator.js +10 -2
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +2 -2
- package/dist/replication/journal/LiveSyncJournalReplicator.js +18 -4
- package/dist/replication/trystero/TrysteroReplicator.js +3 -4
- package/dist/serviceFeatures/targetFilter.d.ts +2 -1
- package/dist/serviceFeatures/targetFilter.js +2 -1
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +47 -2
- package/dist/services/base/IService.d.ts +8 -4
- package/dist/services/base/ReplicationService.d.ts +2 -2
- package/dist/services/base/ReplicationService.js +14 -11
- package/dist/services/base/TweakValueService.d.ts +5 -3
- package/dist/services/base/VaultService.d.ts +4 -2
- package/dist/services/base/VaultService.js +1 -1
- package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +5 -5
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +2 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/development.md +2 -0
- package/docs/offline-scanner.md +159 -0
- package/docs/proven-in-use.md +6 -0
- package/docs/settings-lifecycle.md +20 -1
- package/docs/storage-events-and-reflection.md +107 -0
- package/package.json +1 -1
|
@@ -69,7 +69,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
69
69
|
}
|
|
70
70
|
return this._client;
|
|
71
71
|
}
|
|
72
|
-
async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2, setting = this.currentSettings, client = this.setupJournalSyncClient(setting)) {
|
|
72
|
+
async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2, setting = this.currentSettings, client = this.setupJournalSyncClient(setting), recordTweakAssessment) {
|
|
73
73
|
const milestoneResult = await client.downloadJsonWithResult(MILSTONE_DOCID);
|
|
74
74
|
if (milestoneResult.status === JournalStorageReadStatuses.UNAVAILABLE) {
|
|
75
75
|
throw milestoneResult.error;
|
|
@@ -93,7 +93,8 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
93
93
|
if (!await client.uploadJson(MILSTONE_DOCID, info)) {
|
|
94
94
|
throw new Error("Could not upload remote milestone");
|
|
95
95
|
}
|
|
96
|
-
}
|
|
96
|
+
},
|
|
97
|
+
recordTweakAssessment
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
constructor(env) {
|
|
@@ -259,7 +260,16 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
259
260
|
this.remoteLockedAndDeviceNotAccepted = false;
|
|
260
261
|
this.tweakSettingsMismatched = false;
|
|
261
262
|
this.preferredTweakValue = void 0;
|
|
262
|
-
|
|
263
|
+
let tweakAssessment;
|
|
264
|
+
const ensure = await this.ensureBucketIsCompatible(
|
|
265
|
+
this.nodeid,
|
|
266
|
+
currentVersionRange,
|
|
267
|
+
setting,
|
|
268
|
+
client,
|
|
269
|
+
(assessment) => {
|
|
270
|
+
tweakAssessment = assessment;
|
|
271
|
+
}
|
|
272
|
+
);
|
|
263
273
|
if (ensure == "INCOMPATIBLE") {
|
|
264
274
|
recordCompatibilityDecision?.(
|
|
265
275
|
centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.INCOMPATIBLE_VERSION)
|
|
@@ -301,7 +311,11 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
301
311
|
} else if (ensure == "OK") {
|
|
302
312
|
} else if (ensure[0] == "MISMATCHED") {
|
|
303
313
|
recordCompatibilityDecision?.(
|
|
304
|
-
centralCompatibilityRejected(
|
|
314
|
+
centralCompatibilityRejected(
|
|
315
|
+
CENTRAL_COMPATIBILITY_REJECTION_REASONS.TWEAK_MISMATCH,
|
|
316
|
+
ensure[1],
|
|
317
|
+
tweakAssessment
|
|
318
|
+
)
|
|
305
319
|
);
|
|
306
320
|
Logger(this.translate("liveSyncReplicator.mismatchedTweakDetected"), LOG_LEVEL_NOTICE);
|
|
307
321
|
this.tweakSettingsMismatched = true;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TweakValuesShouldMatchedTemplate } from "../../common/types.js";
|
|
2
|
+
import { assessTweakCompatibility } from "../../common/models/tweak.compatibility.js";
|
|
2
3
|
import {
|
|
3
4
|
LOG_LEVEL_INFO,
|
|
4
5
|
LOG_LEVEL_NOTICE,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
} from "./TrysteroReplicatorP2PServer.js";
|
|
18
19
|
import { encryptWithEphemeralSalt, decryptWithEphemeralSalt } from "octagonal-wheels/encryption/hkdf";
|
|
19
20
|
import { sha1 } from "octagonal-wheels/hash/purejs";
|
|
20
|
-
import { isObjectDifferent } from "octagonal-wheels/object";
|
|
21
21
|
import { getRelaySockets, pauseRelayReconnection, resumeRelayReconnection } from "@trystero-p2p/nostr";
|
|
22
22
|
import { P2PAutomationCoordinator } from "./P2PAutomationCoordinator.js";
|
|
23
23
|
import { compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
@@ -728,9 +728,6 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
728
728
|
const tweakValues = await connection.invokeRemoteObjectFunction("getTweakSettings", [this.server.serverPeerId], 5e3, signal);
|
|
729
729
|
if (signal?.aborted) return false;
|
|
730
730
|
const thisTweakValues = await this.getTweakSettings("");
|
|
731
|
-
if (!isObjectDifferent(thisTweakValues, tweakValues)) {
|
|
732
|
-
return true;
|
|
733
|
-
}
|
|
734
731
|
if (thisTweakValues.passphrase !== tweakValues.passphrase) {
|
|
735
732
|
Logger(
|
|
736
733
|
"Replication cancelled: Passphrase is not matched\nCannot replicate to a remote database until the problem is resolved.",
|
|
@@ -738,6 +735,8 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
738
735
|
);
|
|
739
736
|
return false;
|
|
740
737
|
}
|
|
738
|
+
const tweakAssessment = assessTweakCompatibility(thisTweakValues, tweakValues);
|
|
739
|
+
if (!tweakAssessment.representationDiffers) return true;
|
|
741
740
|
Logger(
|
|
742
741
|
"Some mismatched configuration have been detected... Please check settings for efficient replication.",
|
|
743
742
|
logLevel
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type UXFileInfoStub } from "../common/types.js";
|
|
2
2
|
import { type LogFunction } from "../services/lib/logUtils.js";
|
|
3
3
|
import type { NecessaryServices } from "../interfaces/ServiceModule.js";
|
|
4
|
+
import type { IVaultService } from "../services/base/IService.js";
|
|
4
5
|
/**
|
|
5
6
|
* This is a simple handler that accepts all files.
|
|
6
7
|
*/
|
|
@@ -8,7 +9,7 @@ export declare function isAcceptedAlwaysFactory(host: NecessaryServices<never, n
|
|
|
8
9
|
/**
|
|
9
10
|
* Check if a file is accepted based on filename duplication in the vault.
|
|
10
11
|
*/
|
|
11
|
-
export declare function isAcceptedInFilenameDuplicationFactory(host: NecessaryServices<"vault" | "fileProcessing", "storageAccess">, log: LogFunction): (file: string | UXFileInfoStub) => Promise<boolean>;
|
|
12
|
+
export declare function isAcceptedInFilenameDuplicationFactory(host: NecessaryServices<"vault" | "fileProcessing", "storageAccess">, log: LogFunction): (file: string | UXFileInfoStub, options?: Parameters<IVaultService["isTargetFile"]>[1]) => Promise<boolean>;
|
|
12
13
|
/**
|
|
13
14
|
* Check if a file is accepted by the local database (e.g., not rejected by the local DB's target file check).
|
|
14
15
|
* Local database responsible for non-internal files, syncOnlyRegEx, syncIgnoreRegEx
|
|
@@ -35,7 +35,8 @@ function isAcceptedInFilenameDuplicationFactory(host, log) {
|
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
|
-
return async function isAcceptedInFilenameDuplication(file) {
|
|
38
|
+
return async function isAcceptedInFilenameDuplication(file, options) {
|
|
39
|
+
if (options?.skipCaseCollisionCheck) return true;
|
|
39
40
|
const fileCountMap = (await fileCountMapComputed.update(host.services.fileProcessing.totalStorageFileEventCount)).value;
|
|
40
41
|
const filepath = getStoragePathFromUXFileInfo(file);
|
|
41
42
|
const lc = filepath.toLowerCase();
|
|
@@ -68,6 +68,7 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
68
68
|
private applyDatabaseEntryToStorage;
|
|
69
69
|
private preserveUnsyncedStorageAsConflict;
|
|
70
70
|
private _anyHandlerProcessesFileEvent;
|
|
71
|
+
private canRecordStorageDeletion;
|
|
71
72
|
private serializedByFileEventPaths;
|
|
72
73
|
/**
|
|
73
74
|
* Revalidate a persisted storage operation against the current storage state.
|
|
@@ -657,13 +657,15 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
657
657
|
this._log(`Rename event for ${path} has no source path`, LOG_LEVEL_VERBOSE);
|
|
658
658
|
return false;
|
|
659
659
|
}
|
|
660
|
-
const
|
|
660
|
+
const relatedPath = type === "RENAME" ? eventItem.oldPath : type === "DELETE" ? eventItem.renameTarget : void 0;
|
|
661
|
+
const eventPaths = relatedPath === void 0 ? [path] : [path, relatedPath];
|
|
661
662
|
return await this.serializedByFileEventPaths(eventPaths, async () => {
|
|
662
663
|
switch (type) {
|
|
663
664
|
case "CREATE":
|
|
664
665
|
case "CHANGED":
|
|
665
666
|
return await this.storeFileToDB(item.args.file);
|
|
666
667
|
case "DELETE":
|
|
668
|
+
if (!await this.canRecordStorageDeletion(item)) return true;
|
|
667
669
|
return await this.deleteFileFromDB(item.args.file);
|
|
668
670
|
case "RENAME":
|
|
669
671
|
return await this.renameFileInDB(
|
|
@@ -678,6 +680,45 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
678
680
|
}
|
|
679
681
|
});
|
|
680
682
|
}
|
|
683
|
+
async canRecordStorageDeletion(item) {
|
|
684
|
+
const path = item.args.file.path;
|
|
685
|
+
try {
|
|
686
|
+
const current = await this.storage.getStub(path);
|
|
687
|
+
let excludedRenameTarget = null;
|
|
688
|
+
if (item.args.renameTarget !== void 0) {
|
|
689
|
+
const targetPath = item.args.renameTarget;
|
|
690
|
+
const targetItem = await this.storage.getStub(targetPath);
|
|
691
|
+
const target = this.getExactCurrentFile(targetItem, targetPath);
|
|
692
|
+
if (targetItem !== null && !target) {
|
|
693
|
+
this._log(`Preserved ${path}: the rename target no longer resolves to ${targetPath}`, LOG_LEVEL_NOTICE);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
const targetIsSelected = !shouldBeIgnored(targetPath) && await this.vault.isTargetFile(targetPath, { skipCaseCollisionCheck: true }) && !(target && this.vault.isFileSizeTooLarge(target.stat.size));
|
|
697
|
+
if (targetIsSelected) {
|
|
698
|
+
this._log(`Preserved ${path}: rename target ${targetPath} is still selected`, LOG_LEVEL_NOTICE);
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
excludedRenameTarget = target;
|
|
702
|
+
}
|
|
703
|
+
if (current === null) return true;
|
|
704
|
+
if (!isFolderInfo(current)) {
|
|
705
|
+
const [currentId, deletedId] = await Promise.all([
|
|
706
|
+
this.path.path2id(current.path),
|
|
707
|
+
this.path.path2id(path)
|
|
708
|
+
]);
|
|
709
|
+
if (currentId !== deletedId) return true;
|
|
710
|
+
if (excludedRenameTarget && this.storage.normalisePath(current.path) !== this.storage.normalisePath(path) && this.storage.normalisePath(current.path) === this.storage.normalisePath(excludedRenameTarget.path)) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
this._log(`Preserved ${path}: a current storage item contradicts its deletion event`, LOG_LEVEL_NOTICE);
|
|
715
|
+
return false;
|
|
716
|
+
} catch (ex) {
|
|
717
|
+
this._log(`Could not validate deletion of ${path}; preserved the database entry`, LOG_LEVEL_NOTICE);
|
|
718
|
+
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
681
722
|
async serializedByFileEventPaths(eventPaths, callback) {
|
|
682
723
|
const documentIds = await Promise.all(eventPaths.map((eventPath) => this.path.path2id(eventPath)));
|
|
683
724
|
const lockKeys = [...new Set(documentIds)].sort().map((documentId) => `processFileEvent-${documentId}`);
|
|
@@ -702,7 +743,8 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
702
743
|
this._log(`Rename event for ${path} has no source path`, LOG_LEVEL_VERBOSE);
|
|
703
744
|
return true;
|
|
704
745
|
}
|
|
705
|
-
const
|
|
746
|
+
const relatedPath = type === "RENAME" ? eventItem.oldPath : type === "DELETE" ? eventItem.renameTarget : void 0;
|
|
747
|
+
const eventPaths = relatedPath === void 0 ? [path] : [path, relatedPath];
|
|
706
748
|
return await this.serializedByFileEventPaths(eventPaths, async (isSameDocument) => {
|
|
707
749
|
let action;
|
|
708
750
|
try {
|
|
@@ -732,6 +774,9 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
732
774
|
return current && await this.isCurrentFileSelected(current) ? { kind: "store", file: current } : { kind: "none" };
|
|
733
775
|
}
|
|
734
776
|
case "DELETE": {
|
|
777
|
+
if (item.args.renameTarget !== void 0) {
|
|
778
|
+
return await this.canRecordStorageDeletion(item) && await this.canApplyRestoredDeletion(path) ? { kind: "delete", path } : { kind: "none" };
|
|
779
|
+
}
|
|
735
780
|
const current = await this.storage.getStub(path);
|
|
736
781
|
return current === null && await this.canApplyRestoredDeletion(path) ? { kind: "delete", path } : { kind: "none" };
|
|
737
782
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FetchHttpHandler } from "@smithy/fetch-http-handler";
|
|
2
2
|
import { type LOG_LEVEL } from "octagonal-wheels/common/logger";
|
|
3
|
-
import type { AnyEntry, AUTO_MERGED, CouchDBCredentials, diff_result, DocumentID, EntryDoc, EntryHasPath, FileEventItem, FilePath, FilePathWithPrefix, LoadedEntry, MetaEntry, MISSING_OR_ERROR, ObsidianLiveSyncSettings, RemoteDBSettings, RemotePreferredTweakResult, SettingsMigrationState, TweakValues, UXFileInfo, UXFileInfoStub } from "../../common/types.js";
|
|
3
|
+
import type { AnyEntry, AUTO_MERGED, CouchDBCredentials, diff_result, DocumentID, EntryDoc, EntryHasPath, FileEventItem, FilePath, FilePathWithPrefix, LoadedEntry, MetaEntry, MISSING_OR_ERROR, ObsidianLiveSyncSettings, RemoteDBSettings, RemotePreferredTweakResult, SettingsMigrationState, TweakAssessment, TweakValues, UXFileInfo, UXFileInfoStub } from "../../common/types.js";
|
|
4
4
|
import type { LiveSyncLocalDB } from "../../pouchdb/LiveSyncLocalDB.js";
|
|
5
5
|
import type { ReplicatorInstance } from "../../replication/ReplicatorInstance.js";
|
|
6
6
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
|
@@ -335,8 +335,8 @@ export interface ISettingService {
|
|
|
335
335
|
}
|
|
336
336
|
export interface ITweakValueService {
|
|
337
337
|
fetchRemotePreferred(trialSetting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
|
|
338
|
-
checkAndAskResolvingMismatched(preferred: Partial<TweakValues
|
|
339
|
-
askResolvingMismatched(preferredSource: TweakValues, updatePreferredRemote?: (setting: ObsidianLiveSyncSettings) => Promise<boolean
|
|
338
|
+
checkAndAskResolvingMismatched(preferred: Partial<TweakValues>, assessment?: TweakAssessment): Promise<[TweakValues | boolean, boolean]>;
|
|
339
|
+
askResolvingMismatched(preferredSource: TweakValues, updatePreferredRemote?: (setting: ObsidianLiveSyncSettings) => Promise<boolean>, assessment?: TweakAssessment): Promise<"OK" | "CHECKAGAIN" | "IGNORE">;
|
|
340
340
|
checkAndAskUseRemoteConfiguration(settings: RemoteDBSettings): Promise<{
|
|
341
341
|
result: false | TweakValues;
|
|
342
342
|
requireFetch: boolean;
|
|
@@ -351,7 +351,11 @@ export interface IVaultService {
|
|
|
351
351
|
getVaultName(): string;
|
|
352
352
|
scanVault(showingNotice?: boolean, ignoreSuspending?: boolean, continueOnFileFailure?: boolean): Promise<VaultScanResult>;
|
|
353
353
|
isIgnoredByIgnoreFile(file: string | UXFileInfoStub): Promise<boolean>;
|
|
354
|
-
isTargetFile(file: string | UXFileInfoStub
|
|
354
|
+
isTargetFile(file: string | UXFileInfoStub,
|
|
355
|
+
/** Inspect selection policy without treating a filename collision as an exclusion. */
|
|
356
|
+
options?: {
|
|
357
|
+
skipCaseCollisionCheck?: boolean;
|
|
358
|
+
}): Promise<boolean>;
|
|
355
359
|
isTargetFileInExtra(file: string | UXFileInfoStub): Promise<boolean>;
|
|
356
360
|
isFileSizeTooLarge(size: number): boolean;
|
|
357
361
|
getActiveFilePath(): FilePath | undefined;
|
|
@@ -117,8 +117,8 @@ export declare abstract class ReplicationService<T extends ServiceContext = Serv
|
|
|
117
117
|
*
|
|
118
118
|
* Each attempt owns a separate reservation. Compatibility recovery runs
|
|
119
119
|
* between those reservations, and a retry is admitted only when the exact
|
|
120
|
-
* first-attempt context remains active.
|
|
121
|
-
*
|
|
120
|
+
* first-attempt context remains active. Each attempt uses a detached settings
|
|
121
|
+
* snapshot, and a compatibility retry captures its snapshot after recovery.
|
|
122
122
|
*/
|
|
123
123
|
private performDirectionalReplication;
|
|
124
124
|
replicateAllToRemote(showingNotice?: boolean): Promise<boolean>;
|
|
@@ -329,40 +329,43 @@ class ReplicationService extends ServiceBase {
|
|
|
329
329
|
*
|
|
330
330
|
* Each attempt owns a separate reservation. Compatibility recovery runs
|
|
331
331
|
* between those reservations, and a retry is admitted only when the exact
|
|
332
|
-
* first-attempt context remains active.
|
|
333
|
-
*
|
|
332
|
+
* first-attempt context remains active. Each attempt uses a detached settings
|
|
333
|
+
* snapshot, and a compatibility retry captures its snapshot after recovery.
|
|
334
334
|
*/
|
|
335
335
|
async performDirectionalReplication(direction, showingNotice, admission = DIRECTIONAL_REPLICATION_ADMISSION.ORDINARY) {
|
|
336
336
|
if (direction === DIRECTIONAL_REPLICATION.UPLOAD && !await this.onBeforeReplicate(showingNotice)) {
|
|
337
337
|
this._log(this.context.translate("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
|
338
338
|
return false;
|
|
339
339
|
}
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
340
|
+
const captureSetting = () => {
|
|
341
|
+
const detachedSetting = asCopy(this.settingService.currentSettings());
|
|
342
|
+
if (admission === DIRECTIONAL_REPLICATION_ADMISSION.EXPLICIT_REBUILD) {
|
|
343
|
+
detachedSetting.versionUpFlash = "";
|
|
344
|
+
}
|
|
345
|
+
return Object.freeze(detachedSetting);
|
|
346
|
+
};
|
|
347
|
+
const setting = captureSetting();
|
|
345
348
|
let expectedContext;
|
|
346
|
-
const run = async () => {
|
|
349
|
+
const run = async (attemptSetting) => {
|
|
347
350
|
const admitted = await this.replicatorService.runWithActiveReplicatorContext((context) => {
|
|
348
351
|
if (expectedContext && context !== expectedContext) {
|
|
349
352
|
return replicationBlocked("not-ready");
|
|
350
353
|
}
|
|
351
354
|
expectedContext ??= context;
|
|
352
|
-
return runDirectionalReplication(context,
|
|
355
|
+
return runDirectionalReplication(context, attemptSetting, direction, showingNotice);
|
|
353
356
|
});
|
|
354
357
|
if (admitted) return admitted;
|
|
355
358
|
this._log(`Active replicator not found during directional ${direction}`, LOG_LEVEL_NOTICE);
|
|
356
359
|
return replicationBlocked("no-active-replicator");
|
|
357
360
|
};
|
|
358
|
-
const outcome = await run();
|
|
361
|
+
const outcome = await run(setting);
|
|
359
362
|
if (isReplicationCompleted(outcome)) return true;
|
|
360
363
|
if (outcome.status !== "failed") return false;
|
|
361
364
|
const failedContext = expectedContext;
|
|
362
365
|
if (!failedContext) return false;
|
|
363
366
|
const failure = Object.freeze({ context: failedContext, setting, outcome });
|
|
364
367
|
const checkResult = await this.checkConnectionFailure(failure);
|
|
365
|
-
return checkResult === "CHECKAGAIN" && isReplicationCompleted(await run());
|
|
368
|
+
return checkResult === "CHECKAGAIN" && isReplicationCompleted(await run(captureSetting()));
|
|
366
369
|
}
|
|
367
370
|
async replicateAllToRemote(showingNotice = false) {
|
|
368
371
|
if (!this.appLifecycleService.isReady()) return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ObsidianLiveSyncSettings, RemoteDBSettings, RemotePreferredTweakResult, TweakValues } from "../../common/types.js";
|
|
1
|
+
import type { ObsidianLiveSyncSettings, RemoteDBSettings, RemotePreferredTweakResult, TweakAssessment, TweakValues } from "../../common/types.js";
|
|
2
2
|
import type { ITweakValueService } from "./IService.js";
|
|
3
3
|
import { ServiceBase, type ServiceContext } from "./ServiceBase.js";
|
|
4
4
|
/**
|
|
@@ -13,14 +13,16 @@ export declare abstract class TweakValueService<T extends ServiceContext = Servi
|
|
|
13
13
|
/**
|
|
14
14
|
* Check and ask the user to resolve any mismatched tweak values.
|
|
15
15
|
* @param preferred The preferred tweak values to check against.
|
|
16
|
+
* @param assessment The exact compatibility assessment from the calling operation, when available.
|
|
16
17
|
*/
|
|
17
|
-
abstract checkAndAskResolvingMismatched(preferred: Partial<TweakValues
|
|
18
|
+
abstract checkAndAskResolvingMismatched(preferred: Partial<TweakValues>, assessment?: TweakAssessment): Promise<[TweakValues | boolean, boolean]>;
|
|
18
19
|
/**
|
|
19
20
|
* Ask the user to resolve any mismatched tweak values.
|
|
20
21
|
* @param preferredSource The preferred tweak values to resolve against.
|
|
21
22
|
* @param updatePreferredRemote Optional exact-context writer supplied by the failed operation.
|
|
23
|
+
* @param assessment The exact compatibility assessment from the failed operation, when available.
|
|
22
24
|
*/
|
|
23
|
-
abstract askResolvingMismatched(preferredSource: TweakValues, updatePreferredRemote?: (setting: ObsidianLiveSyncSettings) => Promise<boolean
|
|
25
|
+
abstract askResolvingMismatched(preferredSource: TweakValues, updatePreferredRemote?: (setting: ObsidianLiveSyncSettings) => Promise<boolean>, assessment?: TweakAssessment): Promise<"OK" | "CHECKAGAIN" | "IGNORE">;
|
|
24
26
|
/**
|
|
25
27
|
* Check and ask the user to use the remote configuration.
|
|
26
28
|
* @param settings The remote database settings to connect.
|
|
@@ -35,9 +35,11 @@ export declare abstract class VaultService<T extends ServiceContext = ServiceCon
|
|
|
35
35
|
/**
|
|
36
36
|
* Check if a file is a target file for synchronisation.
|
|
37
37
|
* @param file The file path or file info stub to check.
|
|
38
|
-
* @param
|
|
38
|
+
* @param options Optional selection inspection without filename collision rejection.
|
|
39
39
|
*/
|
|
40
|
-
readonly isTargetFile: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<(file: string | import("../../common/types.js").UXFileInfoStub
|
|
40
|
+
readonly isTargetFile: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<(file: string | import("../../common/types.js").UXFileInfoStub, options?: {
|
|
41
|
+
skipCaseCollisionCheck?: boolean;
|
|
42
|
+
}) => Promise<boolean>>;
|
|
41
43
|
/**
|
|
42
44
|
* Check if a file is a target file for some extra feature
|
|
43
45
|
*/
|
|
@@ -37,7 +37,7 @@ class VaultService extends ServiceBase {
|
|
|
37
37
|
/**
|
|
38
38
|
* Check if a file is a target file for synchronisation.
|
|
39
39
|
* @param file The file path or file info stub to check.
|
|
40
|
-
* @param
|
|
40
|
+
* @param options Optional selection inspection without filename collision rejection.
|
|
41
41
|
*/
|
|
42
42
|
isTargetFile = handlers().bailFirstFailure("isTargetFile");
|
|
43
43
|
/**
|
|
@@ -2,14 +2,14 @@ import type { ServiceContext } from "../../base/ServiceBase.js";
|
|
|
2
2
|
import { TweakValueService } from "../../base/TweakValueService.js";
|
|
3
3
|
export declare class InjectableTweakValueService<T extends ServiceContext> extends TweakValueService<T> {
|
|
4
4
|
fetchRemotePreferred: import("../../lib/HandlerUtils.js").HandlerFunction<(trialSetting: import("../../../settings.js").RemoteDBSettings) => Promise<import("../../../common/types.js").RemotePreferredTweakResult>, unknown>;
|
|
5
|
-
checkAndAskResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferred: Partial<import("../../../
|
|
6
|
-
askResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferredSource: import("../../../
|
|
5
|
+
checkAndAskResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferred: Partial<import("../../../settings.js").TweakValues>, assessment?: import("../../../settings.js").TweakAssessment) => Promise<[import("../../../settings.js").TweakValues | boolean, boolean]>, unknown>;
|
|
6
|
+
askResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferredSource: import("../../../settings.js").TweakValues, updatePreferredRemote?: (setting: import("../../../settings.js").ObsidianLiveSyncSettings) => Promise<boolean>, assessment?: import("../../../settings.js").TweakAssessment) => Promise<"OK" | "CHECKAGAIN" | "IGNORE">, unknown>;
|
|
7
7
|
checkAndAskUseRemoteConfiguration: import("../../lib/HandlerUtils.js").HandlerFunction<(settings: import("../../../settings.js").RemoteDBSettings) => Promise<{
|
|
8
|
-
result: false | import("../../../
|
|
8
|
+
result: false | import("../../../settings.js").TweakValues;
|
|
9
9
|
requireFetch: boolean;
|
|
10
10
|
}>, unknown>;
|
|
11
|
-
askUseRemoteConfiguration: import("../../lib/HandlerUtils.js").HandlerFunction<(trialSetting: import("../../../settings.js").RemoteDBSettings, preferred: import("../../../
|
|
12
|
-
result: false | import("../../../
|
|
11
|
+
askUseRemoteConfiguration: import("../../lib/HandlerUtils.js").HandlerFunction<(trialSetting: import("../../../settings.js").RemoteDBSettings, preferred: import("../../../settings.js").TweakValues) => Promise<{
|
|
12
|
+
result: false | import("../../../settings.js").TweakValues;
|
|
13
13
|
requireFetch: boolean;
|
|
14
14
|
}>, unknown>;
|
|
15
15
|
}
|
package/dist/settings.d.ts
CHANGED
|
@@ -7,4 +7,7 @@ export { DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, P2P_DEFAULT_SETTINGS, SETTINGS_SC
|
|
|
7
7
|
export { PREFERRED_BASE, PREFERRED_JOURNAL_SYNC, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED, } from "./common/models/setting.const.preferred.js";
|
|
8
8
|
export { CURRENT_SETTING_VERSION } from "./common/models/setting.const.js";
|
|
9
9
|
export { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState, } from "./common/models/setting.lifecycle.js";
|
|
10
|
+
export { assessTweakCompatibility } from "./common/models/tweak.compatibility.js";
|
|
11
|
+
export type { TweakAssessment, TweakAssessmentEntry, TweakAssessmentRelation, TweakAssessmentValue, TweakTransition, TweakTransitionReason, } from "./common/models/tweak.compatibility.js";
|
|
12
|
+
export type { TweakReconstruction, TweakValues } from "./common/models/tweak.definition.js";
|
|
10
13
|
export type { ObsidianLiveSyncSettings, RemoteDBSettings, RemoteTypeSettings, } from "./common/models/setting.type.js";
|
package/dist/settings.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
prepareSettingsForLoad,
|
|
17
17
|
SettingsMigrationReviewCodes
|
|
18
18
|
} from "./common/models/setting.lifecycle.js";
|
|
19
|
+
import { assessTweakCompatibility } from "./common/models/tweak.compatibility.js";
|
|
19
20
|
export {
|
|
20
21
|
CURRENT_SETTING_VERSION,
|
|
21
22
|
DEFAULT_SETTINGS,
|
|
@@ -27,6 +28,7 @@ export {
|
|
|
27
28
|
PREFERRED_SETTING_SELF_HOSTED,
|
|
28
29
|
SETTINGS_SCHEMA_DEFAULTS,
|
|
29
30
|
SettingsMigrationReviewCodes,
|
|
31
|
+
assessTweakCompatibility,
|
|
30
32
|
createNewVaultSettings,
|
|
31
33
|
prepareSettingsForLoad
|
|
32
34
|
};
|