@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
|
@@ -53,6 +53,8 @@ export type FileEventArgs = {
|
|
|
53
53
|
file: UXFileInfoStub | UXInternalFileInfoStub;
|
|
54
54
|
cache?: CacheData;
|
|
55
55
|
oldPath?: string;
|
|
56
|
+
/** The destination of a rename converted to DELETE after target filtering. */
|
|
57
|
+
renameTarget?: string;
|
|
56
58
|
ctx?: unknown;
|
|
57
59
|
};
|
|
58
60
|
export type FileEventItem = {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type TweakReconstruction, type TweakValues } from "./tweak.definition.js";
|
|
2
|
+
export type TweakAssessmentRelation = "equal" | "different" | "unadvertised";
|
|
3
|
+
export interface TweakAssessmentValue {
|
|
4
|
+
readonly present: boolean;
|
|
5
|
+
readonly rawValue: TweakValues[keyof TweakValues];
|
|
6
|
+
readonly effectiveValue: TweakValues[keyof TweakValues];
|
|
7
|
+
}
|
|
8
|
+
export interface TweakAssessmentEntry {
|
|
9
|
+
readonly key: keyof TweakValues;
|
|
10
|
+
readonly current: TweakAssessmentValue;
|
|
11
|
+
readonly preferred: TweakAssessmentValue;
|
|
12
|
+
readonly relation: TweakAssessmentRelation;
|
|
13
|
+
}
|
|
14
|
+
export interface TweakTransitionReason {
|
|
15
|
+
readonly key: keyof TweakValues;
|
|
16
|
+
readonly reconstruction: Exclude<TweakReconstruction, "none">;
|
|
17
|
+
}
|
|
18
|
+
export interface TweakTransition {
|
|
19
|
+
readonly changes: Readonly<TweakValues>;
|
|
20
|
+
readonly reconstruction: TweakReconstruction;
|
|
21
|
+
readonly reasons: readonly TweakTransitionReason[];
|
|
22
|
+
}
|
|
23
|
+
export interface TweakAssessment {
|
|
24
|
+
readonly alignment: "matched" | "mismatched";
|
|
25
|
+
readonly representationDiffers: boolean;
|
|
26
|
+
readonly entries: readonly TweakAssessmentEntry[];
|
|
27
|
+
readonly adoptPreferred: TweakTransition;
|
|
28
|
+
readonly adoptCurrent: TweakTransition;
|
|
29
|
+
readonly onlyCompatibleLossyDifferences: boolean;
|
|
30
|
+
readonly currentValues: Readonly<TweakValues>;
|
|
31
|
+
readonly preferredValues: Readonly<TweakValues>;
|
|
32
|
+
}
|
|
33
|
+
/** Assess the semantic and stored-representation relationship between two tweak snapshots. */
|
|
34
|
+
export declare function assessTweakCompatibility(current: Readonly<TweakValues>, preferred: Readonly<TweakValues>): TweakAssessment;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TweakCompatibilityRules,
|
|
3
|
+
TweakValuesTemplate
|
|
4
|
+
} from "./tweak.definition.js";
|
|
5
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
6
|
+
function snapshotTweakValues(values) {
|
|
7
|
+
const snapshot = {};
|
|
8
|
+
for (const key of Object.keys(TweakValuesTemplate)) {
|
|
9
|
+
if (hasOwn(values, key)) snapshot[key] = values[key];
|
|
10
|
+
}
|
|
11
|
+
return Object.freeze(snapshot);
|
|
12
|
+
}
|
|
13
|
+
function assessedValue(values, key, knownDefault) {
|
|
14
|
+
const present = hasOwn(values, key);
|
|
15
|
+
const rawValue = values[key];
|
|
16
|
+
const effectiveValue = rawValue === void 0 ? knownDefault : rawValue;
|
|
17
|
+
return Object.freeze({ present, rawValue, effectiveValue });
|
|
18
|
+
}
|
|
19
|
+
function highestReconstruction(current, next) {
|
|
20
|
+
if (current === "required" || next === "required") return "required";
|
|
21
|
+
return "recommended";
|
|
22
|
+
}
|
|
23
|
+
function reconstructionForTransition(key, from, to) {
|
|
24
|
+
const rule = TweakCompatibilityRules.find((candidate) => candidate.key === key);
|
|
25
|
+
if (!rule) return "none";
|
|
26
|
+
let reconstruction = rule.reconstruction;
|
|
27
|
+
const extensibleRule = rule;
|
|
28
|
+
for (const pattern of extensibleRule.specificPatterns ?? []) {
|
|
29
|
+
const matchesFrom = hasOwn(pattern, "from") && pattern.from === from;
|
|
30
|
+
const matchesTo = hasOwn(pattern, "to") && pattern.to === to;
|
|
31
|
+
if (matchesFrom || matchesTo) {
|
|
32
|
+
reconstruction = highestReconstruction(reconstruction, pattern.reconstruction);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return reconstruction;
|
|
36
|
+
}
|
|
37
|
+
function transition(entries, sourceSide, targetSide) {
|
|
38
|
+
const changes = {};
|
|
39
|
+
const reasons = [];
|
|
40
|
+
let reconstruction = "none";
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const source = entry[sourceSide];
|
|
43
|
+
const target = entry[targetSide];
|
|
44
|
+
const sourceAdvertised = source.present && source.rawValue !== void 0;
|
|
45
|
+
const targetAdvertised = target.present && target.rawValue !== void 0;
|
|
46
|
+
const effectiveValueDiffers = source.effectiveValue !== target.effectiveValue;
|
|
47
|
+
const shouldFillMissingTarget = !targetAdvertised && sourceAdvertised;
|
|
48
|
+
if (source.effectiveValue === void 0 || !effectiveValueDiffers && !shouldFillMissingTarget) continue;
|
|
49
|
+
changes[entry.key] = source.effectiveValue;
|
|
50
|
+
if (entry.relation === "equal") continue;
|
|
51
|
+
const effect = reconstructionForTransition(entry.key, target.effectiveValue, source.effectiveValue);
|
|
52
|
+
if (effect === "none") continue;
|
|
53
|
+
const reason = Object.freeze({
|
|
54
|
+
key: entry.key,
|
|
55
|
+
reconstruction: effect
|
|
56
|
+
});
|
|
57
|
+
reasons.push(reason);
|
|
58
|
+
reconstruction = highestReconstruction(reconstruction, reason.reconstruction);
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
changes: Object.freeze(changes),
|
|
62
|
+
reconstruction,
|
|
63
|
+
reasons: Object.freeze(reasons)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function assessTweakCompatibility(current, preferred) {
|
|
67
|
+
const currentValues = snapshotTweakValues(current);
|
|
68
|
+
const preferredValues = snapshotTweakValues(preferred);
|
|
69
|
+
let representationDiffers = false;
|
|
70
|
+
const entries = TweakCompatibilityRules.map((rule) => {
|
|
71
|
+
const knownDefault = "knownDefault" in rule ? rule.knownDefault : void 0;
|
|
72
|
+
const currentValue = assessedValue(currentValues, rule.key, knownDefault);
|
|
73
|
+
const preferredValue = assessedValue(preferredValues, rule.key, knownDefault);
|
|
74
|
+
const currentAdvertised = currentValue.present && currentValue.rawValue !== void 0;
|
|
75
|
+
const preferredAdvertised = preferredValue.present && preferredValue.rawValue !== void 0;
|
|
76
|
+
let relation;
|
|
77
|
+
if ((!currentAdvertised || !preferredAdvertised) && knownDefault === void 0) {
|
|
78
|
+
relation = "unadvertised";
|
|
79
|
+
} else {
|
|
80
|
+
relation = currentValue.effectiveValue === preferredValue.effectiveValue ? "equal" : "different";
|
|
81
|
+
}
|
|
82
|
+
if (currentValue.present !== preferredValue.present || currentValue.rawValue !== preferredValue.rawValue) {
|
|
83
|
+
representationDiffers = true;
|
|
84
|
+
}
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
key: rule.key,
|
|
87
|
+
current: currentValue,
|
|
88
|
+
preferred: preferredValue,
|
|
89
|
+
relation
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
const frozenEntries = Object.freeze(entries);
|
|
93
|
+
const differences = frozenEntries.filter(({ relation }) => relation === "different");
|
|
94
|
+
const compatibleLossyKeys = new Set(
|
|
95
|
+
TweakCompatibilityRules.filter((rule) => "compatibleLossyOrder" in rule).map(({ key }) => key)
|
|
96
|
+
);
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
alignment: differences.length === 0 ? "matched" : "mismatched",
|
|
99
|
+
representationDiffers,
|
|
100
|
+
entries: frozenEntries,
|
|
101
|
+
adoptPreferred: transition(frozenEntries, "preferred", "current"),
|
|
102
|
+
adoptCurrent: transition(frozenEntries, "current", "preferred"),
|
|
103
|
+
onlyCompatibleLossyDifferences: differences.length > 0 && differences.every(({ key }) => compatibleLossyKeys.has(key)) && !frozenEntries.some(({ relation }) => relation === "unadvertised"),
|
|
104
|
+
currentValues,
|
|
105
|
+
preferredValues
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
assessTweakCompatibility
|
|
110
|
+
};
|
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
import type { ObsidianLiveSyncSettings } from "./setting.type.js";
|
|
2
|
-
export
|
|
2
|
+
export type TweakReconstruction = "none" | "recommended" | "required";
|
|
3
|
+
/**
|
|
4
|
+
* Single source for tweak comparison semantics and the maintained legacy
|
|
5
|
+
* compatibility projections below.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TweakCompatibilityRules: readonly [{
|
|
8
|
+
readonly key: "minimumChunkSize";
|
|
9
|
+
readonly templateValue: 20;
|
|
10
|
+
readonly reconstruction: "none";
|
|
11
|
+
}, {
|
|
12
|
+
readonly key: "longLineThreshold";
|
|
13
|
+
readonly templateValue: 250;
|
|
14
|
+
readonly reconstruction: "none";
|
|
15
|
+
}, {
|
|
16
|
+
readonly key: "encrypt";
|
|
17
|
+
readonly templateValue: false;
|
|
18
|
+
readonly reconstruction: "required";
|
|
19
|
+
}, {
|
|
20
|
+
readonly key: "usePathObfuscation";
|
|
21
|
+
readonly templateValue: false;
|
|
22
|
+
readonly reconstruction: "required";
|
|
23
|
+
}, {
|
|
24
|
+
readonly key: "enableCompression";
|
|
25
|
+
readonly templateValue: false;
|
|
26
|
+
readonly reconstruction: "none";
|
|
27
|
+
}, {
|
|
28
|
+
readonly key: "useEden";
|
|
29
|
+
readonly templateValue: false;
|
|
30
|
+
readonly reconstruction: "none";
|
|
31
|
+
}, {
|
|
32
|
+
readonly key: "customChunkSize";
|
|
33
|
+
readonly templateValue: 0;
|
|
34
|
+
readonly reconstruction: "recommended";
|
|
35
|
+
readonly compatibleLossyOrder: 1;
|
|
36
|
+
}, {
|
|
37
|
+
readonly key: "useDynamicIterationCount";
|
|
38
|
+
readonly templateValue: false;
|
|
39
|
+
readonly reconstruction: "required";
|
|
40
|
+
}, {
|
|
41
|
+
readonly key: "hashAlg";
|
|
42
|
+
readonly templateValue: "xxhash64";
|
|
43
|
+
readonly reconstruction: "recommended";
|
|
44
|
+
readonly compatibleLossyOrder: 0;
|
|
45
|
+
}, {
|
|
46
|
+
readonly key: "enableChunkSplitterV2";
|
|
47
|
+
readonly templateValue: false;
|
|
48
|
+
readonly reconstruction: "none";
|
|
49
|
+
}, {
|
|
50
|
+
readonly key: "maxChunksInEden";
|
|
51
|
+
readonly templateValue: 10;
|
|
52
|
+
readonly reconstruction: "none";
|
|
53
|
+
}, {
|
|
54
|
+
readonly key: "maxTotalLengthInEden";
|
|
55
|
+
readonly templateValue: 1024;
|
|
56
|
+
readonly reconstruction: "none";
|
|
57
|
+
}, {
|
|
58
|
+
readonly key: "maxAgeInEden";
|
|
59
|
+
readonly templateValue: 10;
|
|
60
|
+
readonly reconstruction: "none";
|
|
61
|
+
}, {
|
|
62
|
+
readonly key: "usePluginSyncV2";
|
|
63
|
+
readonly templateValue: false;
|
|
64
|
+
readonly reconstruction: "none";
|
|
65
|
+
readonly knownDefault: false;
|
|
66
|
+
}, {
|
|
67
|
+
readonly key: "handleFilenameCaseSensitive";
|
|
68
|
+
readonly templateValue: false;
|
|
69
|
+
readonly reconstruction: "required";
|
|
70
|
+
readonly knownDefault: false;
|
|
71
|
+
}, {
|
|
72
|
+
readonly key: "useSegmenter";
|
|
73
|
+
readonly templateValue: false;
|
|
74
|
+
readonly reconstruction: "none";
|
|
75
|
+
}, {
|
|
76
|
+
readonly key: "E2EEAlgorithm";
|
|
77
|
+
readonly templateValue: "v2";
|
|
78
|
+
readonly reconstruction: "none";
|
|
79
|
+
readonly knownDefault: import("./setting.type.js").E2EEAlgorithm;
|
|
80
|
+
}, {
|
|
81
|
+
readonly key: "chunkSplitterVersion";
|
|
82
|
+
readonly templateValue: "v3-rabin-karp";
|
|
83
|
+
readonly reconstruction: "recommended";
|
|
84
|
+
readonly knownDefault: import("./setting.type.js").ChunkSplitterVersion;
|
|
85
|
+
readonly compatibleLossyOrder: 2;
|
|
86
|
+
}];
|
|
87
|
+
export type TweakComparedKey = (typeof TweakCompatibilityRules)[number]["key"];
|
|
88
|
+
export declare const TweakValuesShouldMatchedTemplate: Pick<ObsidianLiveSyncSettings, TweakComparedKey>;
|
|
3
89
|
type TweakKeys = keyof TweakValues;
|
|
4
90
|
export declare const IncompatibleChanges: TweakKeys[];
|
|
5
91
|
export declare const CompatibleButLossyChanges: TweakKeys[];
|
|
@@ -19,6 +105,24 @@ export declare const TweakValuesRecommendedTemplate: Partial<ObsidianLiveSyncSet
|
|
|
19
105
|
export declare const TweakValuesDefault: Partial<ObsidianLiveSyncSettings>;
|
|
20
106
|
export declare const TweakValuesTemplate: {
|
|
21
107
|
tweakModified: number;
|
|
108
|
+
usePluginSyncV2: boolean;
|
|
109
|
+
encrypt: boolean;
|
|
110
|
+
usePathObfuscation: boolean;
|
|
111
|
+
E2EEAlgorithm: import("./setting.type.js").E2EEAlgorithm;
|
|
112
|
+
hashAlg: import("./setting.type.js").HashAlgorithm;
|
|
113
|
+
minimumChunkSize: number;
|
|
114
|
+
customChunkSize: number;
|
|
115
|
+
longLineThreshold: number;
|
|
116
|
+
useSegmenter: boolean;
|
|
117
|
+
enableChunkSplitterV2: boolean;
|
|
118
|
+
chunkSplitterVersion: import("./setting.type.js").ChunkSplitterVersion;
|
|
119
|
+
useEden: boolean;
|
|
120
|
+
maxChunksInEden: number;
|
|
121
|
+
maxTotalLengthInEden: number;
|
|
122
|
+
maxAgeInEden: number;
|
|
123
|
+
useDynamicIterationCount: boolean;
|
|
124
|
+
enableCompression: boolean;
|
|
125
|
+
handleFilenameCaseSensitive: boolean;
|
|
22
126
|
liveSync?: boolean;
|
|
23
127
|
syncOnSave?: boolean;
|
|
24
128
|
syncOnStart?: boolean;
|
|
@@ -72,7 +176,6 @@ export declare const TweakValuesTemplate: {
|
|
|
72
176
|
autoSweepPluginsPeriodic?: boolean;
|
|
73
177
|
notifyPluginOrSettingUpdated?: boolean;
|
|
74
178
|
deviceAndVaultName?: string;
|
|
75
|
-
usePluginSyncV2?: boolean;
|
|
76
179
|
usePluginEtc?: boolean;
|
|
77
180
|
pluginSyncExtendedSetting?: Record<import("./setting.type.js").PluginSyncSettingEntry["key"], import("./setting.type.js").PluginSyncSettingEntry>;
|
|
78
181
|
useAdvancedMode?: boolean;
|
|
@@ -124,34 +227,18 @@ export declare const TweakValuesTemplate: {
|
|
|
124
227
|
bucketPrefix?: string;
|
|
125
228
|
forcePathStyle?: boolean;
|
|
126
229
|
remoteType?: import("./setting.type.js").RemoteType;
|
|
127
|
-
encrypt?: boolean;
|
|
128
230
|
passphrase?: string;
|
|
129
|
-
usePathObfuscation?: boolean;
|
|
130
|
-
E2EEAlgorithm?: import("./setting.type.js").E2EEAlgorithm;
|
|
131
|
-
hashAlg?: import("./setting.type.js").HashAlgorithm;
|
|
132
|
-
minimumChunkSize?: number;
|
|
133
|
-
customChunkSize?: number;
|
|
134
|
-
longLineThreshold?: number;
|
|
135
|
-
useSegmenter?: boolean;
|
|
136
|
-
enableChunkSplitterV2?: boolean;
|
|
137
231
|
doNotUseFixedRevisionForChunks?: boolean;
|
|
138
|
-
chunkSplitterVersion?: import("./setting.type.js").ChunkSplitterVersion;
|
|
139
|
-
useEden?: boolean;
|
|
140
|
-
maxChunksInEden?: number;
|
|
141
|
-
maxTotalLengthInEden?: number;
|
|
142
|
-
maxAgeInEden?: number;
|
|
143
232
|
checkIntegrityOnSave?: boolean;
|
|
144
233
|
useHistory?: boolean;
|
|
145
234
|
disableRequestURI?: boolean;
|
|
146
235
|
sendChunksBulk?: boolean;
|
|
147
236
|
sendChunksBulkMaxSize?: number;
|
|
148
|
-
useDynamicIterationCount?: boolean;
|
|
149
237
|
doNotPaceReplication?: boolean;
|
|
150
238
|
readChunksOnline?: boolean;
|
|
151
239
|
useOnlyLocalChunk?: boolean;
|
|
152
240
|
concurrencyOfReadChunksOnline?: number;
|
|
153
241
|
minimumIntervalOfReadChunksOnline?: number;
|
|
154
|
-
enableCompression?: boolean;
|
|
155
242
|
batch_size?: number;
|
|
156
243
|
batches_limit?: number;
|
|
157
244
|
ignoreVersionCheck?: boolean;
|
|
@@ -160,7 +247,6 @@ export declare const TweakValuesTemplate: {
|
|
|
160
247
|
hashCacheMaxCount?: number;
|
|
161
248
|
hashCacheMaxAmount?: number;
|
|
162
249
|
permitEmptyPassphrase?: boolean;
|
|
163
|
-
handleFilenameCaseSensitive?: boolean;
|
|
164
250
|
checkConflictOnlyOnOpen?: boolean;
|
|
165
251
|
showMergeDialogOnlyOnActive?: boolean;
|
|
166
252
|
additionalSuffixOfDatabaseName?: string | undefined;
|
|
@@ -1,33 +1,63 @@
|
|
|
1
1
|
import { ChunkAlgorithms, E2EEAlgorithms } from "./setting.const.js";
|
|
2
2
|
import { DEFAULT_SETTINGS } from "./setting.const.defaults.js";
|
|
3
|
-
const
|
|
4
|
-
minimumChunkSize: 20,
|
|
5
|
-
longLineThreshold: 250,
|
|
6
|
-
encrypt: false,
|
|
7
|
-
usePathObfuscation: false,
|
|
8
|
-
enableCompression: false,
|
|
9
|
-
useEden: false,
|
|
10
|
-
customChunkSize: 0,
|
|
11
|
-
useDynamicIterationCount: false,
|
|
12
|
-
hashAlg: "xxhash64",
|
|
13
|
-
enableChunkSplitterV2: false,
|
|
14
|
-
maxChunksInEden: 10,
|
|
15
|
-
maxTotalLengthInEden: 1024,
|
|
16
|
-
maxAgeInEden: 10,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
3
|
+
const TweakCompatibilityRules = Object.freeze([
|
|
4
|
+
{ key: "minimumChunkSize", templateValue: 20, reconstruction: "none" },
|
|
5
|
+
{ key: "longLineThreshold", templateValue: 250, reconstruction: "none" },
|
|
6
|
+
{ key: "encrypt", templateValue: false, reconstruction: "required" },
|
|
7
|
+
{ key: "usePathObfuscation", templateValue: false, reconstruction: "required" },
|
|
8
|
+
{ key: "enableCompression", templateValue: false, reconstruction: "none" },
|
|
9
|
+
{ key: "useEden", templateValue: false, reconstruction: "none" },
|
|
10
|
+
{ key: "customChunkSize", templateValue: 0, reconstruction: "recommended", compatibleLossyOrder: 1 },
|
|
11
|
+
{ key: "useDynamicIterationCount", templateValue: false, reconstruction: "required" },
|
|
12
|
+
{ key: "hashAlg", templateValue: "xxhash64", reconstruction: "recommended", compatibleLossyOrder: 0 },
|
|
13
|
+
{ key: "enableChunkSplitterV2", templateValue: false, reconstruction: "none" },
|
|
14
|
+
{ key: "maxChunksInEden", templateValue: 10, reconstruction: "none" },
|
|
15
|
+
{ key: "maxTotalLengthInEden", templateValue: 1024, reconstruction: "none" },
|
|
16
|
+
{ key: "maxAgeInEden", templateValue: 10, reconstruction: "none" },
|
|
17
|
+
{
|
|
18
|
+
key: "usePluginSyncV2",
|
|
19
|
+
templateValue: false,
|
|
20
|
+
reconstruction: "none",
|
|
21
|
+
knownDefault: false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: "handleFilenameCaseSensitive",
|
|
25
|
+
templateValue: false,
|
|
26
|
+
reconstruction: "required",
|
|
27
|
+
knownDefault: false
|
|
28
|
+
},
|
|
29
|
+
{ key: "useSegmenter", templateValue: false, reconstruction: "none" },
|
|
30
|
+
{
|
|
31
|
+
key: "E2EEAlgorithm",
|
|
32
|
+
templateValue: E2EEAlgorithms.V2,
|
|
33
|
+
reconstruction: "none",
|
|
34
|
+
knownDefault: DEFAULT_SETTINGS.E2EEAlgorithm
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "chunkSplitterVersion",
|
|
38
|
+
templateValue: ChunkAlgorithms.RabinKarp,
|
|
39
|
+
reconstruction: "recommended",
|
|
40
|
+
knownDefault: DEFAULT_SETTINGS.chunkSplitterVersion,
|
|
41
|
+
compatibleLossyOrder: 2
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
const TweakValuesShouldMatchedTemplate = Object.fromEntries(
|
|
45
|
+
TweakCompatibilityRules.map(({ key, templateValue }) => [key, templateValue])
|
|
46
|
+
);
|
|
23
47
|
const IncompatibleChanges = [
|
|
24
|
-
"
|
|
25
|
-
"usePathObfuscation",
|
|
26
|
-
"useDynamicIterationCount",
|
|
27
|
-
"handleFilenameCaseSensitive"
|
|
48
|
+
...TweakCompatibilityRules.filter(({ reconstruction }) => reconstruction === "required").map(({ key }) => key)
|
|
28
49
|
];
|
|
29
|
-
const CompatibleButLossyChanges =
|
|
30
|
-
|
|
50
|
+
const CompatibleButLossyChanges = TweakCompatibilityRules.filter(
|
|
51
|
+
(rule) => "compatibleLossyOrder" in rule
|
|
52
|
+
).slice().sort((left, right) => left.compatibleLossyOrder - right.compatibleLossyOrder).map(({ key }) => key);
|
|
53
|
+
const IncompatibleChangesInSpecificPattern = TweakCompatibilityRules.flatMap(
|
|
54
|
+
(rule) => (rule.specificPatterns ?? []).map((pattern) => ({
|
|
55
|
+
key: rule.key,
|
|
56
|
+
...Object.prototype.hasOwnProperty.call(pattern, "from") ? { from: pattern.from } : {},
|
|
57
|
+
...Object.prototype.hasOwnProperty.call(pattern, "to") ? { to: pattern.to } : {},
|
|
58
|
+
...pattern.reconstruction === "recommended" ? { isRecommendation: true } : {}
|
|
59
|
+
}))
|
|
60
|
+
);
|
|
31
61
|
const TweakValuesRecommendedTemplate = {
|
|
32
62
|
useIgnoreFiles: false,
|
|
33
63
|
useCustomRequestHandler: false,
|
|
@@ -49,9 +79,11 @@ const TweakValuesRecommendedTemplate = {
|
|
|
49
79
|
chunkSplitterVersion: ChunkAlgorithms.RabinKarp
|
|
50
80
|
};
|
|
51
81
|
const TweakValuesDefault = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
...Object.fromEntries(
|
|
83
|
+
TweakCompatibilityRules.filter((rule) => "knownDefault" in rule && rule.key !== "handleFilenameCaseSensitive").map(
|
|
84
|
+
({ key, knownDefault }) => [key, knownDefault]
|
|
85
|
+
)
|
|
86
|
+
),
|
|
55
87
|
tweakModified: DEFAULT_SETTINGS.tweakModified
|
|
56
88
|
};
|
|
57
89
|
const TweakValuesTemplate = {
|
|
@@ -77,6 +109,7 @@ export {
|
|
|
77
109
|
IncompatibleChangesInSpecificPattern,
|
|
78
110
|
RemotePreferredTweakNotConfiguredReasons,
|
|
79
111
|
RemotePreferredTweakStatuses,
|
|
112
|
+
TweakCompatibilityRules,
|
|
80
113
|
TweakValuesDefault,
|
|
81
114
|
TweakValuesRecommendedTemplate,
|
|
82
115
|
TweakValuesShouldMatchedTemplate,
|
package/dist/common/types.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ import { configurationNames, LEVEL_ADVANCED, LEVEL_POWER_USER, LEVEL_EDGE_CASE,
|
|
|
23
23
|
import type { CustomRegExpSource, CustomRegExpSourceList, ParsedCustomRegExp, Prettify } from "./models/shared.type.util.js";
|
|
24
24
|
import { ProtocolVersions, type ProtocolVersion, DOCID_SYNC_PARAMETERS, DOCID_JOURNAL_SYNC_PARAMETERS, type SyncParameters, DEFAULT_SYNC_PARAMETERS } from "./models/sync.definition.js";
|
|
25
25
|
import { TweakValuesShouldMatchedTemplate, IncompatibleChanges, CompatibleButLossyChanges, IncompatibleChangesInSpecificPattern, TweakValuesRecommendedTemplate, TweakValuesDefault, TweakValuesTemplate, type TweakValues, DEVICE_ID_PREFERRED, RemotePreferredTweakStatuses, type RemotePreferredTweakStatus, RemotePreferredTweakNotConfiguredReasons, type RemotePreferredTweakNotConfiguredReason, type RemotePreferredTweakResult } from "./models/tweak.definition.js";
|
|
26
|
+
import type { TweakAssessment, TweakAssessmentEntry, TweakAssessmentRelation, TweakAssessmentValue, TweakTransition, TweakTransitionReason } from "./models/tweak.compatibility.js";
|
|
27
|
+
import type { TweakReconstruction } from "./models/tweak.definition.js";
|
|
26
28
|
import type { diff_result_leaf, dmp_result, diff_result, DIFF_CHECK_RESULT_AUTO, diff_check_result } from "./models/diff.definition.js";
|
|
27
29
|
import { PREFIXMD_LOGFILE, PREFIXMD_LOGFILE_UC, FlagFilesOriginal, FlagFilesHumanReadable, FLAGMD_REDFLAG, FLAGMD_REDFLAG2, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3, FLAGMD_REDFLAG3_HR } from "./models/redflag.const.js";
|
|
28
30
|
import { DatabaseConnectingStatuses, type DatabaseConnectingStatus } from "./models/shared.definition.js";
|
|
@@ -69,6 +71,7 @@ export { confName };
|
|
|
69
71
|
export { confDesc };
|
|
70
72
|
export { TweakValuesTemplate };
|
|
71
73
|
export type { TweakValues };
|
|
74
|
+
export type { TweakAssessment, TweakAssessmentEntry, TweakAssessmentRelation, TweakAssessmentValue, TweakReconstruction, TweakTransition, TweakTransitionReason, };
|
|
72
75
|
export { DEVICE_ID_PREFERRED };
|
|
73
76
|
export { RemotePreferredTweakStatuses };
|
|
74
77
|
export type { RemotePreferredTweakStatus };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntryDoc, type EntryMilestoneInfo, type RemoteDBSettings, type ChunkVersionRange, type TweakValues, type DeviceInfo } from "../common/types.js";
|
|
1
|
+
import { type EntryDoc, type EntryMilestoneInfo, type RemoteDBSettings, type ChunkVersionRange, type TweakValues, type DeviceInfo, type TweakAssessment } from "../common/types.js";
|
|
2
2
|
export type ENSURE_DB_RESULT = "OK" | "INCOMPATIBLE" | "LOCKED" | "NODE_LOCKED" | "NODE_CLEANED" | ["MISMATCHED", TweakValues];
|
|
3
3
|
/**
|
|
4
4
|
* Ensures that the remote database is compatible with the current device.
|
|
@@ -10,5 +10,5 @@ export type ENSURE_DB_RESULT = "OK" | "INCOMPATIBLE" | "LOCKED" | "NODE_LOCKED"
|
|
|
10
10
|
* @param updateCallback - The callback function to update the remote milestone.
|
|
11
11
|
* @returns A promise that resolves to the result of ensuring compatibility.
|
|
12
12
|
*/
|
|
13
|
-
export declare function ensureRemoteIsCompatible(infoSrc: EntryMilestoneInfo | false, setting: RemoteDBSettings, deviceNodeID: string, currentVersionRange: ChunkVersionRange, nodeDeviceInfo: DeviceInfo, updateCallback: (info: EntryMilestoneInfo) => Promise<void
|
|
14
|
-
export declare function ensureDatabaseIsCompatible(db: PouchDB.Database<EntryDoc>, setting: RemoteDBSettings, deviceNodeID: string, currentVersionRange: ChunkVersionRange, nodeDeviceInfo: DeviceInfo): Promise<ENSURE_DB_RESULT>;
|
|
13
|
+
export declare function ensureRemoteIsCompatible(infoSrc: EntryMilestoneInfo | false, setting: RemoteDBSettings, deviceNodeID: string, currentVersionRange: ChunkVersionRange, nodeDeviceInfo: DeviceInfo, updateCallback: (info: EntryMilestoneInfo) => Promise<void>, recordTweakAssessment?: (assessment: TweakAssessment) => void): Promise<ENSURE_DB_RESULT>;
|
|
14
|
+
export declare function ensureDatabaseIsCompatible(db: PouchDB.Database<EntryDoc>, setting: RemoteDBSettings, deviceNodeID: string, currentVersionRange: ChunkVersionRange, nodeDeviceInfo: DeviceInfo, recordTweakAssessment?: (assessment: TweakAssessment) => void): Promise<ENSURE_DB_RESULT>;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MILESTONE_DOCID as MILESTONE_DOC_ID,
|
|
3
|
-
TweakValuesShouldMatchedTemplate,
|
|
4
3
|
TweakValuesTemplate,
|
|
5
|
-
DEVICE_ID_PREFERRED
|
|
6
|
-
TweakValuesDefault
|
|
4
|
+
DEVICE_ID_PREFERRED
|
|
7
5
|
} from "../common/types.js";
|
|
8
6
|
import { extractObject, isObjectDifferent, resolveWithIgnoreKnownError } from "../common/utils.js";
|
|
9
|
-
|
|
7
|
+
import { assessTweakCompatibility } from "../common/models/tweak.compatibility.js";
|
|
8
|
+
async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentVersionRange, nodeDeviceInfo, updateCallback, recordTweakAssessment) {
|
|
10
9
|
const now = Date.now();
|
|
11
10
|
const baseMilestone = {
|
|
12
11
|
_id: MILESTONE_DOC_ID,
|
|
@@ -83,15 +82,9 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
|
|
|
83
82
|
if (!setting.disableCheckingConfigMismatch) {
|
|
84
83
|
const preferred_tweak = remoteMilestone.tweak_values?.[DEVICE_ID_PREFERRED] ?? currentTweakValues;
|
|
85
84
|
const current_tweak = currentTweakValues;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
const current_should_matched = extractObject(TweakValuesShouldMatchedTemplate, {
|
|
91
|
-
...TweakValuesDefault,
|
|
92
|
-
...current_tweak
|
|
93
|
-
});
|
|
94
|
-
if (isObjectDifferent(preferred_should_matched, current_should_matched, true)) {
|
|
85
|
+
const tweakAssessment = assessTweakCompatibility(current_tweak, preferred_tweak);
|
|
86
|
+
recordTweakAssessment?.(tweakAssessment);
|
|
87
|
+
if (tweakAssessment.alignment === "mismatched") {
|
|
95
88
|
return ["MISMATCHED", preferred_tweak];
|
|
96
89
|
}
|
|
97
90
|
}
|
|
@@ -106,7 +99,7 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
|
|
|
106
99
|
}
|
|
107
100
|
return "OK";
|
|
108
101
|
}
|
|
109
|
-
async function ensureDatabaseIsCompatible(db, setting, deviceNodeID, currentVersionRange, nodeDeviceInfo) {
|
|
102
|
+
async function ensureDatabaseIsCompatible(db, setting, deviceNodeID, currentVersionRange, nodeDeviceInfo, recordTweakAssessment) {
|
|
110
103
|
const remoteMilestone = await resolveWithIgnoreKnownError(
|
|
111
104
|
db.get(MILESTONE_DOC_ID),
|
|
112
105
|
false
|
|
@@ -119,7 +112,8 @@ async function ensureDatabaseIsCompatible(db, setting, deviceNodeID, currentVers
|
|
|
119
112
|
nodeDeviceInfo,
|
|
120
113
|
async (info) => {
|
|
121
114
|
await db.put(info);
|
|
122
|
-
}
|
|
115
|
+
},
|
|
116
|
+
recordTweakAssessment
|
|
123
117
|
);
|
|
124
118
|
return ret;
|
|
125
119
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TweakValues } from "../common/types.js";
|
|
1
|
+
import type { TweakAssessment, TweakValues } from "../common/types.js";
|
|
2
2
|
/** Stable machine states for one central-remote compatibility assessment. */
|
|
3
3
|
export declare const CENTRAL_COMPATIBILITY_STATUSES: Readonly<{
|
|
4
4
|
readonly NOT_ASSESSED: "not-assessed";
|
|
@@ -23,6 +23,7 @@ export interface CentralCompatibilityRejected {
|
|
|
23
23
|
readonly status: typeof CENTRAL_COMPATIBILITY_STATUSES.REJECTED;
|
|
24
24
|
readonly reason: CentralCompatibilityRejectionReason;
|
|
25
25
|
readonly preferredTweakValue?: Readonly<TweakValues>;
|
|
26
|
+
readonly tweakAssessment?: TweakAssessment;
|
|
26
27
|
}
|
|
27
28
|
/** Immutable result produced by one provider-local compatibility assessment. */
|
|
28
29
|
export type CentralCompatibilityDecision = typeof CENTRAL_COMPATIBILITY_NOT_ASSESSED | typeof CENTRAL_COMPATIBILITY_ACCEPTED | CentralCompatibilityRejected;
|
|
@@ -32,7 +33,8 @@ export type CentralCompatibilityDecisionRecorder = (decision: CentralCompatibili
|
|
|
32
33
|
export interface CentralCompatibilityRecoveryHint {
|
|
33
34
|
readonly reason: CentralCompatibilityRejectionReason;
|
|
34
35
|
readonly preferredTweakValue?: Readonly<TweakValues>;
|
|
36
|
+
readonly tweakAssessment?: TweakAssessment;
|
|
35
37
|
}
|
|
36
|
-
export declare function centralCompatibilityRejected(reason: CentralCompatibilityRejectionReason, preferredTweakValue?: TweakValues): CentralCompatibilityRejected;
|
|
38
|
+
export declare function centralCompatibilityRejected(reason: CentralCompatibilityRejectionReason, preferredTweakValue?: Readonly<TweakValues>, tweakAssessment?: TweakAssessment): CentralCompatibilityRejected;
|
|
37
39
|
/** Project only a rejected decision into the generic replication failure path. */
|
|
38
40
|
export declare function centralCompatibilityRecoveryHint(decision: CentralCompatibilityDecision): CentralCompatibilityRecoveryHint | undefined;
|
|
@@ -15,18 +15,20 @@ const CENTRAL_COMPATIBILITY_NOT_ASSESSED = Object.freeze({
|
|
|
15
15
|
const CENTRAL_COMPATIBILITY_ACCEPTED = Object.freeze({
|
|
16
16
|
status: CENTRAL_COMPATIBILITY_STATUSES.ACCEPTED
|
|
17
17
|
});
|
|
18
|
-
function centralCompatibilityRejected(reason, preferredTweakValue) {
|
|
18
|
+
function centralCompatibilityRejected(reason, preferredTweakValue, tweakAssessment) {
|
|
19
19
|
return Object.freeze({
|
|
20
20
|
status: CENTRAL_COMPATIBILITY_STATUSES.REJECTED,
|
|
21
21
|
reason,
|
|
22
|
-
...preferredTweakValue === void 0 ? {} : { preferredTweakValue: Object.freeze({ ...preferredTweakValue }) }
|
|
22
|
+
...preferredTweakValue === void 0 ? {} : { preferredTweakValue: Object.freeze({ ...preferredTweakValue }) },
|
|
23
|
+
...tweakAssessment === void 0 ? {} : { tweakAssessment }
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
function centralCompatibilityRecoveryHint(decision) {
|
|
26
27
|
if (decision.status !== CENTRAL_COMPATIBILITY_STATUSES.REJECTED) return void 0;
|
|
27
28
|
return Object.freeze({
|
|
28
29
|
reason: decision.reason,
|
|
29
|
-
...decision.preferredTweakValue === void 0 ? {} : { preferredTweakValue: decision.preferredTweakValue }
|
|
30
|
+
...decision.preferredTweakValue === void 0 ? {} : { preferredTweakValue: decision.preferredTweakValue },
|
|
31
|
+
...decision.tweakAssessment === void 0 ? {} : { tweakAssessment: decision.tweakAssessment }
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
export {
|
|
@@ -903,12 +903,16 @@ ${sendingDocs}/(${sentDocsCount} done)`,
|
|
|
903
903
|
device_name: this.env.services.vault.getVaultName(),
|
|
904
904
|
progress
|
|
905
905
|
};
|
|
906
|
+
let tweakAssessment;
|
|
906
907
|
const ensure = await ensureDatabaseIsCompatible(
|
|
907
908
|
dbRet.db,
|
|
908
909
|
setting,
|
|
909
910
|
this.nodeid,
|
|
910
911
|
currentVersionRange,
|
|
911
|
-
info
|
|
912
|
+
info,
|
|
913
|
+
(assessment) => {
|
|
914
|
+
tweakAssessment = assessment;
|
|
915
|
+
}
|
|
912
916
|
);
|
|
913
917
|
if (ensure == "INCOMPATIBLE") {
|
|
914
918
|
recordCompatibilityDecision?.(
|
|
@@ -951,7 +955,11 @@ ${sendingDocs}/(${sentDocsCount} done)`,
|
|
|
951
955
|
} else if (ensure == "OK") {
|
|
952
956
|
} else if (ensure[0] == "MISMATCHED") {
|
|
953
957
|
recordCompatibilityDecision?.(
|
|
954
|
-
centralCompatibilityRejected(
|
|
958
|
+
centralCompatibilityRejected(
|
|
959
|
+
CENTRAL_COMPATIBILITY_REJECTION_REASONS.TWEAK_MISMATCH,
|
|
960
|
+
ensure[1],
|
|
961
|
+
tweakAssessment
|
|
962
|
+
)
|
|
955
963
|
);
|
|
956
964
|
Logger(this.translate("liveSyncReplicator.mismatchedTweakDetected"), LOG_LEVEL_NOTICE);
|
|
957
965
|
this.tweakSettingsMismatched = true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RemoteDBSettings, type ChunkVersionRange, type NodeData, type RemotePreferredTweakResult } from "../../common/types.js";
|
|
1
|
+
import { type RemoteDBSettings, type ChunkVersionRange, type NodeData, type RemotePreferredTweakResult, type TweakAssessment } from "../../common/types.js";
|
|
2
2
|
import { JournalSyncCore } from "./JournalSyncCore.js";
|
|
3
3
|
import { LiveSyncAbstractReplicator, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
|
|
4
4
|
import { type ENSURE_DB_RESULT } from "../../pouchdb/LiveSyncDBFunctions.js";
|
|
@@ -22,7 +22,7 @@ export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicato
|
|
|
22
22
|
private journalTransferStopGeneration;
|
|
23
23
|
getReplicationPBKDF2Salt(setting: RemoteDBSettings, refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
|
|
24
24
|
setupJournalSyncClient(settings?: RemoteDBSettings): JournalSyncCore;
|
|
25
|
-
ensureBucketIsCompatible(deviceNodeID: string, currentVersionRange: ChunkVersionRange, setting?: RemoteDBSettings, client?: Pick<JournalSyncCore, "downloadJsonWithResult" | "getCheckpointInfo" | "uploadJson"
|
|
25
|
+
ensureBucketIsCompatible(deviceNodeID: string, currentVersionRange: ChunkVersionRange, setting?: RemoteDBSettings, client?: Pick<JournalSyncCore, "downloadJsonWithResult" | "getCheckpointInfo" | "uploadJson">, recordTweakAssessment?: (assessment: TweakAssessment) => void): Promise<ENSURE_DB_RESULT>;
|
|
26
26
|
constructor(env: LiveSyncJournalReplicatorEnv);
|
|
27
27
|
migrate(from: number, to: number): Promise<boolean>;
|
|
28
28
|
/**
|