@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.
Files changed (35) hide show
  1. package/dist/common/models/fileaccess.type.d.ts +2 -0
  2. package/dist/common/models/tweak.compatibility.d.ts +34 -0
  3. package/dist/common/models/tweak.compatibility.js +110 -0
  4. package/dist/common/models/tweak.definition.d.ts +105 -19
  5. package/dist/common/models/tweak.definition.js +62 -29
  6. package/dist/common/types.d.ts +3 -0
  7. package/dist/managers/StorageEventManager.js +1 -0
  8. package/dist/pouchdb/LiveSyncDBFunctions.d.ts +3 -3
  9. package/dist/pouchdb/LiveSyncDBFunctions.js +9 -15
  10. package/dist/replication/CentralCompatibility.d.ts +4 -2
  11. package/dist/replication/CentralCompatibility.js +5 -3
  12. package/dist/replication/couchdb/LiveSyncReplicator.js +10 -2
  13. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +2 -2
  14. package/dist/replication/journal/LiveSyncJournalReplicator.js +18 -4
  15. package/dist/replication/trystero/TrysteroReplicator.js +3 -4
  16. package/dist/serviceFeatures/targetFilter.d.ts +2 -1
  17. package/dist/serviceFeatures/targetFilter.js +2 -1
  18. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
  19. package/dist/serviceModules/ServiceFileHandlerBase.js +47 -2
  20. package/dist/services/base/IService.d.ts +8 -4
  21. package/dist/services/base/ReplicationService.d.ts +2 -2
  22. package/dist/services/base/ReplicationService.js +14 -11
  23. package/dist/services/base/TweakValueService.d.ts +5 -3
  24. package/dist/services/base/VaultService.d.ts +4 -2
  25. package/dist/services/base/VaultService.js +1 -1
  26. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +5 -5
  27. package/dist/settings.d.ts +3 -0
  28. package/dist/settings.js +2 -0
  29. package/dist/worker/bgWorker.js +1 -1
  30. package/docs/development.md +2 -0
  31. package/docs/offline-scanner.md +159 -0
  32. package/docs/proven-in-use.md +6 -0
  33. package/docs/settings-lifecycle.md +20 -1
  34. package/docs/storage-events-and-reflection.md +107 -0
  35. 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 declare const TweakValuesShouldMatchedTemplate: Partial<ObsidianLiveSyncSettings>;
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 TweakValuesShouldMatchedTemplate = {
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
- usePluginSyncV2: false,
18
- handleFilenameCaseSensitive: false,
19
- useSegmenter: false,
20
- E2EEAlgorithm: E2EEAlgorithms.V2,
21
- chunkSplitterVersion: ChunkAlgorithms.RabinKarp
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
- "encrypt",
25
- "usePathObfuscation",
26
- "useDynamicIterationCount",
27
- "handleFilenameCaseSensitive"
48
+ ...TweakCompatibilityRules.filter(({ reconstruction }) => reconstruction === "required").map(({ key }) => key)
28
49
  ];
29
- const CompatibleButLossyChanges = ["hashAlg", "customChunkSize", "chunkSplitterVersion"];
30
- const IncompatibleChangesInSpecificPattern = [];
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
- usePluginSyncV2: false,
53
- E2EEAlgorithm: DEFAULT_SETTINGS.E2EEAlgorithm,
54
- chunkSplitterVersion: DEFAULT_SETTINGS.chunkSplitterVersion,
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,
@@ -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 };
@@ -166,6 +166,7 @@ class StorageEventManagerBase extends StorageEventManager {
166
166
  args: {
167
167
  file,
168
168
  oldPath: type === "RENAME" ? oldPath : void 0,
169
+ ...param.type === "RENAME" && type === "DELETE" ? { renameTarget: param.file.path } : {},
169
170
  cache,
170
171
  ctx
171
172
  },
@@ -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>): Promise<ENSURE_DB_RESULT>;
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
- async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentVersionRange, nodeDeviceInfo, updateCallback) {
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 preferred_should_matched = extractObject(TweakValuesShouldMatchedTemplate, {
87
- ...TweakValuesDefault,
88
- ...preferred_tweak
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(CENTRAL_COMPATIBILITY_REJECTION_REASONS.TWEAK_MISMATCH, ensure[1])
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">): Promise<ENSURE_DB_RESULT>;
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
  /**