@vrtmrz/livesync-commonlib 0.1.0-rc.0 → 0.1.0-rc.10

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 (148) hide show
  1. package/README.md +101 -17
  2. package/dist/API/DirectFileManipulatorV2.d.ts +3 -0
  3. package/dist/common/configForDoc.d.ts +1 -0
  4. package/dist/common/configForDoc.js +24 -7
  5. package/dist/common/models/setting.const.defaults.d.ts +19 -0
  6. package/dist/common/models/setting.const.defaults.js +21 -2
  7. package/dist/common/models/setting.const.preferred.js +1 -2
  8. package/dist/common/models/setting.lifecycle.d.ts +33 -0
  9. package/dist/common/models/setting.lifecycle.js +121 -0
  10. package/dist/common/models/setting.type.d.ts +10 -4
  11. package/dist/common/models/shared.definition.configNames.js +3 -3
  12. package/dist/common/models/tweak.definition.d.ts +1 -1
  13. package/dist/common/models/tweak.definition.js +1 -6
  14. package/dist/common/settingConstants.d.ts +3 -2
  15. package/dist/common/settingConstants.js +6 -6
  16. package/dist/common/types.d.ts +4 -2
  17. package/dist/common/types.js +16 -1
  18. package/dist/context.d.ts +3 -1
  19. package/dist/context.js +4 -0
  20. package/dist/interfaces/Confirm.d.ts +10 -2
  21. package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
  22. package/dist/interfaces/DatabaseRebuilder.d.ts +12 -2
  23. package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
  24. package/dist/interfaces/FileReflectionProvenance.js +25 -0
  25. package/dist/managers/ConflictManager.js +16 -7
  26. package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
  27. package/dist/managers/EntryManager/EntryManager.js +5 -1
  28. package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
  29. package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
  30. package/dist/platform/node/index.d.ts +1 -0
  31. package/dist/platform/node/index.js +4 -0
  32. package/dist/platform/node/standardIo.d.ts +24 -0
  33. package/dist/platform/node/standardIo.js +39 -0
  34. package/dist/platform/node/storage.d.ts +10 -1
  35. package/dist/platform/node/storage.js +86 -25
  36. package/dist/platform/standardIo.d.ts +19 -0
  37. package/dist/platform/standardIo.js +0 -0
  38. package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
  39. package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
  40. package/dist/remoteConfigurations.d.ts +9 -0
  41. package/dist/remoteConfigurations.js +14 -0
  42. package/dist/replication/LiveSyncAbstractReplicator.d.ts +3 -3
  43. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +0 -1
  44. package/dist/replication/couchdb/LiveSyncReplicator.js +0 -3
  45. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +0 -1
  46. package/dist/replication/journal/LiveSyncJournalReplicator.js +0 -3
  47. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +5 -3
  48. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +42 -30
  49. package/dist/replication/trystero/P2PReplicatorCore.d.ts +2 -6
  50. package/dist/replication/trystero/P2PReplicatorCore.js +16 -86
  51. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -1
  52. package/dist/replication/trystero/TrysteroReplicator.js +10 -4
  53. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +4 -5
  54. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +22 -30
  55. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -2
  56. package/dist/replication/trystero/addP2PEventHandlers.d.ts +4 -2
  57. package/dist/replication/trystero/addP2PEventHandlers.js +9 -10
  58. package/dist/replication/trystero/types.d.ts +2 -0
  59. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +1 -1
  60. package/dist/replication/trystero/useP2PReplicatorCommands.js +3 -1
  61. package/dist/replication/trystero/useP2PReplicatorFeature.js +25 -15
  62. package/dist/rpc/RpcRoom.d.ts +1 -1
  63. package/dist/rpc/RpcRoom.js +22 -16
  64. package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
  65. package/dist/rpc/transports/TrysteroTransport.js +24 -17
  66. package/dist/rpc/transports/trysteroRoomEvents.d.ts +14 -0
  67. package/dist/rpc/transports/trysteroRoomEvents.js +34 -0
  68. package/dist/serviceFeatures/checkRemoteSize.d.ts +5 -3
  69. package/dist/serviceFeatures/checkRemoteSize.js +51 -11
  70. package/dist/serviceFeatures/remoteConfig.d.ts +37 -1
  71. package/dist/serviceFeatures/remoteConfig.js +90 -0
  72. package/dist/serviceModules/Rebuilder.d.ts +3 -6
  73. package/dist/serviceModules/Rebuilder.js +31 -22
  74. package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
  75. package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
  76. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +20 -0
  77. package/dist/serviceModules/ServiceFileHandlerBase.js +224 -40
  78. package/dist/services/BrowserServices.d.ts +2 -0
  79. package/dist/services/BrowserServices.js +4 -1
  80. package/dist/services/HeadlessServices.d.ts +2 -0
  81. package/dist/services/HeadlessServices.js +2 -1
  82. package/dist/services/base/CommonlibMessages.d.ts +318 -0
  83. package/dist/services/base/CommonlibMessages.js +313 -0
  84. package/dist/services/base/IService.d.ts +13 -6
  85. package/dist/services/base/KeyValueDBService.d.ts +1 -1
  86. package/dist/services/base/KeyValueDBService.js +3 -1
  87. package/dist/services/base/MessageTranslator.d.ts +5 -2
  88. package/dist/services/base/MessageTranslator.js +21 -0
  89. package/dist/services/base/ReplicationService.d.ts +4 -2
  90. package/dist/services/base/ReplicationService.js +11 -16
  91. package/dist/services/base/ServiceBase.d.ts +1 -1
  92. package/dist/services/base/ServiceBase.js +2 -4
  93. package/dist/services/base/SettingService.d.ts +17 -9
  94. package/dist/services/base/SettingService.js +38 -19
  95. package/dist/services/implements/headless/HeadlessAPIService.d.ts +3 -3
  96. package/dist/services/implements/headless/HeadlessAPIService.js +2 -2
  97. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +3 -3
  98. package/dist/settings.d.ts +10 -0
  99. package/dist/settings.js +32 -0
  100. package/dist/worker/bgWorker.js +1 -1
  101. package/docs/conflict-resolution.md +153 -0
  102. package/docs/development.md +108 -8
  103. package/docs/p2p-transport-lifecycle.md +62 -0
  104. package/docs/platform-standard-io.md +44 -0
  105. package/docs/platform-storage.md +4 -0
  106. package/docs/proven-in-use.md +65 -0
  107. package/docs/releasing.md +26 -6
  108. package/docs/remote-configurations.md +63 -0
  109. package/docs/settings-lifecycle.md +85 -0
  110. package/package.json +17 -27
  111. package/dist/common/i18n.d.ts +0 -18
  112. package/dist/common/i18n.js +0 -7193
  113. package/dist/common/messages/combinedMessages.dev.d.ts +0 -7
  114. package/dist/common/messages/combinedMessages.dev.js +0 -44
  115. package/dist/common/messages/combinedMessages.prod.d.ts +0 -9264
  116. package/dist/common/messages/combinedMessages.prod.js +0 -9271
  117. package/dist/common/messages/de.d.ts +0 -297
  118. package/dist/common/messages/de.js +0 -7
  119. package/dist/common/messages/def.d.ts +0 -1119
  120. package/dist/common/messages/def.js +0 -7
  121. package/dist/common/messages/es.d.ts +0 -689
  122. package/dist/common/messages/es.js +0 -7
  123. package/dist/common/messages/fr.d.ts +0 -582
  124. package/dist/common/messages/fr.js +0 -7
  125. package/dist/common/messages/he.d.ts +0 -583
  126. package/dist/common/messages/he.js +0 -7
  127. package/dist/common/messages/ja.d.ts +0 -838
  128. package/dist/common/messages/ja.js +0 -7
  129. package/dist/common/messages/ko.d.ts +0 -800
  130. package/dist/common/messages/ko.js +0 -7
  131. package/dist/common/messages/ru.d.ts +0 -858
  132. package/dist/common/messages/ru.js +0 -7
  133. package/dist/common/messages/zh-tw.d.ts +0 -384
  134. package/dist/common/messages/zh-tw.js +0 -7
  135. package/dist/common/messages/zh.d.ts +0 -1093
  136. package/dist/common/messages/zh.js +0 -7
  137. package/dist/common/messagesJson/de.json +0 -295
  138. package/dist/common/messagesJson/en.json +0 -1117
  139. package/dist/common/messagesJson/es.json +0 -687
  140. package/dist/common/messagesJson/fr.json +0 -580
  141. package/dist/common/messagesJson/he.json +0 -581
  142. package/dist/common/messagesJson/ja.json +0 -836
  143. package/dist/common/messagesJson/ko.json +0 -798
  144. package/dist/common/messagesJson/ru.json +0 -856
  145. package/dist/common/messagesJson/zh-tw.json +0 -382
  146. package/dist/common/messagesJson/zh.json +0 -1091
  147. package/dist/common/rosetta.d.ts +0 -42
  148. package/dist/common/rosetta.js +0 -57
@@ -45,6 +45,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
45
45
  path;
46
46
  setting;
47
47
  vault;
48
+ fileReflectionProvenance;
48
49
  constructor(services) {
49
50
  super(services);
50
51
  this.events = services.events;
@@ -54,6 +55,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
54
55
  this.path = services.path;
55
56
  this.setting = services.setting;
56
57
  this.vault = services.vault;
58
+ this.fileReflectionProvenance = services.fileReflectionProvenance;
57
59
  services.fileProcessing.processFileEvent.addHandler(this._anyHandlerProcessesFileEvent.bind(this), 100);
58
60
  services.replication.processSynchroniseResult.addHandler(this._anyProcessReplicatedDoc.bind(this), 100);
59
61
  }
@@ -63,6 +65,66 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
63
65
  get storage() {
64
66
  return this.storageAccess;
65
67
  }
68
+ async getProvenance(path) {
69
+ if (!this.fileReflectionProvenance) return void 0;
70
+ try {
71
+ const record = await this.fileReflectionProvenance.get(path);
72
+ if (!record) return void 0;
73
+ const entry = await this.db.fetchEntryMeta(path, record.revision, true);
74
+ if (entry && !entry._deleted && !entry.deleted) {
75
+ return record;
76
+ }
77
+ await this.fileReflectionProvenance.delete(path);
78
+ } catch (ex) {
79
+ this._log(`Could not read file reflection provenance for ${path}`, LOG_LEVEL_VERBOSE);
80
+ this._log(ex, LOG_LEVEL_VERBOSE);
81
+ }
82
+ return void 0;
83
+ }
84
+ async setProvenance(path, revision, observedStorageMtime) {
85
+ if (!this.fileReflectionProvenance || !revision) return;
86
+ try {
87
+ await this.fileReflectionProvenance.set(path, {
88
+ revision,
89
+ observedStorageMtime
90
+ });
91
+ } catch (ex) {
92
+ this._log(`Could not record file reflection provenance for ${path}`, LOG_LEVEL_VERBOSE);
93
+ this._log(ex, LOG_LEVEL_VERBOSE);
94
+ }
95
+ }
96
+ async deleteProvenance(path) {
97
+ if (!this.fileReflectionProvenance) return;
98
+ try {
99
+ await this.fileReflectionProvenance.delete(path);
100
+ } catch (ex) {
101
+ this._log(`Could not delete file reflection provenance for ${path}`, LOG_LEVEL_VERBOSE);
102
+ this._log(ex, LOG_LEVEL_VERBOSE);
103
+ }
104
+ }
105
+ async findUniqueContentRevision(file) {
106
+ try {
107
+ const revisions = await this.db.findContentRevisions(file, file.body);
108
+ return revisions.length === 1 ? revisions[0] : void 0;
109
+ } catch (ex) {
110
+ this._log(`Could not reconstruct file reflection provenance for ${file.path}`, LOG_LEVEL_VERBOSE);
111
+ this._log(ex, LOG_LEVEL_VERBOSE);
112
+ return void 0;
113
+ }
114
+ }
115
+ async getProvenBaseRevision(file, preferredPath) {
116
+ const path = preferredPath ?? file.path;
117
+ const recorded = await this.getProvenance(path) ?? (preferredPath && preferredPath !== file.path ? await this.getProvenance(file.path) : void 0);
118
+ if (recorded) {
119
+ return recorded.revision;
120
+ }
121
+ const matched = await this.findUniqueContentRevision(file);
122
+ if (matched) {
123
+ await this.setProvenance(path, matched, file.stat.mtime);
124
+ return matched;
125
+ }
126
+ return void 0;
127
+ }
66
128
  getPath(entry) {
67
129
  return this.path.getPath(entry);
68
130
  }
@@ -85,6 +147,9 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
85
147
  return file;
86
148
  }
87
149
  async storeFileToDB(info, force = false, onlyChunks = false) {
150
+ return await this.storeFileToDBFromRevision(info, force, onlyChunks);
151
+ }
152
+ async storeFileToDBFromRevision(info, force = false, onlyChunks = false, preferredBasePath) {
88
153
  const file = await this.infoToStub(info);
89
154
  if (file == null) {
90
155
  this._log(`File ${tryGetFilePath(info)} is not exist on the storage`, LOG_LEVEL_VERBOSE);
@@ -97,27 +162,69 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
97
162
  );
98
163
  return false;
99
164
  }
165
+ if (onlyChunks) {
166
+ const readFile2 = await this.readFileFromStub(file);
167
+ return await this.db.createChunks(readFile2, force, true);
168
+ }
169
+ const readFile = await this.readFileFromStub(file);
100
170
  const entry = await this.db.fetchEntry(file, void 0, true, true);
171
+ const conflictedRevs = await this.db.getConflictedRevs(file);
172
+ const isConflicted = conflictedRevs.length > 0;
173
+ if (isConflicted) {
174
+ const baseRevision = await this.getProvenBaseRevision(readFile, preferredBasePath);
175
+ if (baseRevision) {
176
+ const baseEntry = await this.db.fetchEntry(file, baseRevision, true, true);
177
+ if (baseEntry && await isDocContentSame(getDocDataAsArray(baseEntry.data), readFile.body) && !force) {
178
+ await this.setProvenance(file.path, baseRevision, readFile.stat.mtime);
179
+ this._log(`File ${file.path} is not changed on its displayed conflict branch`, LOG_LEVEL_VERBOSE);
180
+ return true;
181
+ }
182
+ const storedRevision3 = await this.db.storeWithBaseRevision(readFile, baseRevision, true);
183
+ if (storedRevision3 === false) return false;
184
+ if (preferredBasePath && preferredBasePath !== file.path) {
185
+ await this.deleteProvenance(preferredBasePath);
186
+ }
187
+ await this.setProvenance(file.path, storedRevision3, readFile.stat.mtime);
188
+ await this.conflict.queueCheckFor(file.path);
189
+ return true;
190
+ }
191
+ const currentRevision = entry && entry._rev;
192
+ if (!currentRevision) {
193
+ this._log(
194
+ `Could not preserve the unknown conflict branch for ${file.path}; no current revision is available`,
195
+ LOG_LEVEL_NOTICE
196
+ );
197
+ await this.conflict.queueCheckFor(file.path);
198
+ return false;
199
+ }
200
+ const storedRevision2 = await this.db.storeAsConflictedRevisionWithResult(readFile, currentRevision, true);
201
+ if (storedRevision2 === false) {
202
+ this._log(`Could not preserve the unknown conflict branch for ${file.path}`, LOG_LEVEL_NOTICE);
203
+ await this.conflict.queueCheckFor(file.path);
204
+ return false;
205
+ }
206
+ if (preferredBasePath && preferredBasePath !== file.path) {
207
+ await this.deleteProvenance(preferredBasePath);
208
+ }
209
+ await this.setProvenance(file.path, storedRevision2, readFile.stat.mtime);
210
+ await this.conflict.queueCheckFor(file.path);
211
+ return true;
212
+ }
101
213
  if (!entry || entry.deleted || entry._deleted) {
102
- const readFile = await this.readFileFromStub(file);
103
- if (!onlyChunks) {
104
- return await this.db.store(readFile);
105
- } else {
106
- return await this.db.createChunks(readFile, false, true);
214
+ const storedRevision2 = await this.db.storeWithBaseRevision(readFile, entry && entry._rev, true);
215
+ if (storedRevision2 === false) return false;
216
+ if (preferredBasePath && preferredBasePath !== file.path) {
217
+ await this.deleteProvenance(preferredBasePath);
107
218
  }
219
+ await this.setProvenance(file.path, storedRevision2, readFile.stat.mtime);
220
+ return true;
108
221
  }
109
222
  let shouldApplied = false;
110
223
  if (!force && !onlyChunks) {
111
224
  if (this.path.compareFileFreshness(file, entry) !== EVEN) {
112
225
  shouldApplied = true;
113
226
  }
114
- let readFile = void 0;
115
227
  if (!shouldApplied) {
116
- readFile = await this.readFileFromStub(file);
117
- if (!readFile) {
118
- this._log(`File ${file.path} is not exist on the storage`, LOG_LEVEL_NOTICE);
119
- return false;
120
- }
121
228
  if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
122
229
  this.path.markChangesAreSame(readFile, readFile.stat.mtime, entry.mtime);
123
230
  } else {
@@ -125,28 +232,23 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
125
232
  }
126
233
  }
127
234
  if (!shouldApplied) {
235
+ await this.setProvenance(file.path, entry._rev, readFile.stat.mtime);
128
236
  this._log(`File ${file.path} is not changed`, LOG_LEVEL_VERBOSE);
129
237
  return true;
130
238
  }
131
- if (!readFile) readFile = await this.readFileFromStub(file);
132
- if (onlyChunks) {
133
- return await this.db.createChunks(readFile, false, true);
134
- } else {
135
- return await this.db.store(readFile, false, true);
136
- }
137
- } else {
138
- const readFile = await this.readFileFromStub(file);
139
- if (onlyChunks) {
140
- return await this.db.createChunks(readFile, true, true);
141
- } else {
142
- return await this.db.store(readFile, true, true);
143
- }
144
239
  }
240
+ const storedRevision = await this.db.storeWithBaseRevision(readFile, entry._rev, true);
241
+ if (storedRevision === false) return false;
242
+ if (preferredBasePath && preferredBasePath !== file.path) {
243
+ await this.deleteProvenance(preferredBasePath);
244
+ }
245
+ await this.setProvenance(file.path, storedRevision, readFile.stat.mtime);
246
+ return true;
145
247
  }
146
248
  async deleteFileFromDB(info) {
147
249
  const file = await this.infoToStub(info);
250
+ const path = typeof info === "string" ? info : tryGetFilePath(info);
148
251
  if (file == null) {
149
- const path = typeof info === "string" ? info : tryGetFilePath(info);
150
252
  if (path === void 0) {
151
253
  this._log(`File ${tryGetFilePath(info)} is not exist on the storage`, LOG_LEVEL_VERBOSE);
152
254
  return false;
@@ -159,8 +261,27 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
159
261
  );
160
262
  return false;
161
263
  }
264
+ const conflictedRevs2 = await this.db.getConflictedRevs(path);
265
+ if (conflictedRevs2.length > 0) {
266
+ const provenance = await this.getProvenance(path);
267
+ if (!provenance) {
268
+ this._log(
269
+ `The deleted storage file ${path} has conflicts, but its displayed revision is unknown; preserving every database branch`,
270
+ LOG_LEVEL_NOTICE
271
+ );
272
+ await this.conflict.queueCheckFor(path);
273
+ return true;
274
+ }
275
+ const storedRevision = await this.db.storeDeletionWithBaseRevision(path, provenance.revision);
276
+ if (storedRevision === false) return false;
277
+ await this.deleteProvenance(path);
278
+ await this.conflict.queueCheckFor(path);
279
+ return true;
280
+ }
162
281
  this._log(`File ${path} is missing on storage; deleting from the database by path`, LOG_LEVEL_INFO);
163
- return await this.db.delete(path);
282
+ const deleted2 = await this.db.delete(path);
283
+ if (deleted2) await this.deleteProvenance(path);
284
+ return deleted2;
164
285
  }
165
286
  if (file.isInternal) {
166
287
  this._log(
@@ -176,9 +297,31 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
176
297
  }
177
298
  const conflictedRevs = await this.db.getConflictedRevs(file);
178
299
  if (conflictedRevs.length > 0) {
179
- return await this.db.delete(file, entry._rev);
300
+ let baseRevision = (await this.getProvenance(file.path))?.revision;
301
+ if (!baseRevision) {
302
+ try {
303
+ const readFile = await this.readFileFromStub(file);
304
+ baseRevision = await this.findUniqueContentRevision(readFile);
305
+ } catch {
306
+ }
307
+ }
308
+ if (!baseRevision) {
309
+ this._log(
310
+ `The deleted storage file ${file.path} has conflicts, but its displayed revision is unknown; preserving every database branch`,
311
+ LOG_LEVEL_NOTICE
312
+ );
313
+ await this.conflict.queueCheckFor(file.path);
314
+ return true;
315
+ }
316
+ const storedRevision = await this.db.storeDeletionWithBaseRevision(file.path, baseRevision);
317
+ if (storedRevision === false) return false;
318
+ await this.deleteProvenance(file.path);
319
+ await this.conflict.queueCheckFor(file.path);
320
+ return true;
180
321
  }
181
- return await this.db.delete(file);
322
+ const deleted = await this.db.delete(file);
323
+ if (deleted) await this.deleteProvenance(file.path);
324
+ return deleted;
182
325
  }
183
326
  async renameFileInDB(info, oldPath) {
184
327
  const newPath = getStoragePathFromUXFileInfo(info);
@@ -188,9 +331,17 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
188
331
  ]);
189
332
  if (oldDocumentId === newDocumentId) {
190
333
  this._log(`Updating the stored path for case-only rename: ${oldPath} -> ${newPath}`, LOG_LEVEL_VERBOSE);
191
- return await this.storeFileToDB(info, true);
334
+ return await this.storeFileToDBFromRevision(info, true, false, oldPath);
192
335
  }
193
336
  const oldEntry = await this.db.fetchEntryMeta(oldPath, void 0, true);
337
+ const newEntry = await this.db.fetchEntryMeta(newPath, void 0, true);
338
+ if (newEntry && !newEntry.deleted && !newEntry._deleted) {
339
+ this._log(
340
+ `Refusing to overwrite the existing database entry while renaming ${oldPath} to ${newPath}`,
341
+ LOG_LEVEL_NOTICE
342
+ );
343
+ return false;
344
+ }
194
345
  if (!await this.storeFileToDB(info, true)) {
195
346
  this._log(`Failed to store rename target; preserving source in the database: ${oldPath}`, LOG_LEVEL_NOTICE);
196
347
  return false;
@@ -199,13 +350,34 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
199
350
  this._log(`Rename source is not present in the database: ${oldPath}`, LOG_LEVEL_VERBOSE);
200
351
  return true;
201
352
  }
202
- const oldFile = {
203
- path: oldPath,
204
- name: oldPath.split("/").pop() ?? info.name,
205
- stat: info.stat,
206
- deleted: true
207
- };
208
- return await this.deleteFileFromDB(oldFile);
353
+ const oldConflicts = await this.db.getConflictedRevs(oldPath);
354
+ if (oldConflicts.length > 0) {
355
+ let baseRevision = (await this.getProvenance(oldPath))?.revision;
356
+ if (!baseRevision) {
357
+ const readFile = await this.readFileFromStub(info);
358
+ const revisions = await this.db.findContentRevisions(oldPath, readFile.body);
359
+ baseRevision = revisions.length === 1 ? revisions[0] : void 0;
360
+ }
361
+ if (!baseRevision) {
362
+ this._log(
363
+ `Renamed ${oldPath} to ${newPath}, but preserved every conflicted source branch because the displayed source revision is unknown`,
364
+ LOG_LEVEL_NOTICE
365
+ );
366
+ await this.conflict.queueCheckFor(oldPath);
367
+ return true;
368
+ }
369
+ const storedRevision = await this.db.storeDeletionWithBaseRevision(
370
+ oldPath,
371
+ baseRevision
372
+ );
373
+ if (storedRevision === false) return false;
374
+ await this.deleteProvenance(oldPath);
375
+ await this.conflict.queueCheckFor(oldPath);
376
+ return true;
377
+ }
378
+ const deleted = await this.db.delete(oldPath);
379
+ if (deleted) await this.deleteProvenance(oldPath);
380
+ return deleted;
209
381
  }
210
382
  async deleteRevisionFromDB(info, rev) {
211
383
  return await this.db.delete(info, rev);
@@ -232,17 +404,20 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
232
404
  this._log(`File ${file.path} is not exist on the database`, LOG_LEVEL_VERBOSE);
233
405
  return false;
234
406
  }
235
- return await this.dbToStorage(docEntry, file, force);
407
+ return await this.applyDatabaseEntryToStorage(docEntry, file, force);
236
408
  }
237
409
  async dbToStorage(entryInfo, info, force) {
238
410
  const file = await this.infoToStub(info);
239
- const mode = file == null ? "create" : "modify";
240
411
  const pathFromEntryInfo = typeof entryInfo === "string" ? entryInfo : this.getPath(entryInfo);
241
412
  const docEntry = await this.db.fetchEntryMeta(pathFromEntryInfo, void 0, true);
242
413
  if (!docEntry) {
243
414
  this._log(`File ${pathFromEntryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
244
415
  return false;
245
416
  }
417
+ return await this.applyDatabaseEntryToStorage(docEntry, file, force);
418
+ }
419
+ async applyDatabaseEntryToStorage(docEntry, file, force) {
420
+ const mode = file == null ? "create" : "modify";
246
421
  const path = this.getPath(docEntry);
247
422
  const settings = this.setting.currentSettings();
248
423
  const revs = await this.db.getConflictedRevs(path);
@@ -261,6 +436,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
261
436
  const existOnDB = !(docEntry._deleted || docEntry.deleted || false);
262
437
  if (!existOnDB && !existDoc) {
263
438
  this._log(`File ${path} seems to be deleted, but already not on storage`, LOG_LEVEL_VERBOSE);
439
+ await this.deleteProvenance(path);
264
440
  return true;
265
441
  }
266
442
  if (!existOnDB && existDoc) {
@@ -268,6 +444,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
268
444
  return true;
269
445
  }
270
446
  await this.storage.deleteVaultItem(path);
447
+ await this.deleteProvenance(path);
271
448
  return true;
272
449
  }
273
450
  if (existDoc && existDoc.path !== path) {
@@ -294,6 +471,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
294
471
  this._log(`Could not apply the stored filename case: ${existDoc.path} -> ${path}`, LOG_LEVEL_NOTICE);
295
472
  return false;
296
473
  }
474
+ await this.deleteProvenance(existDoc.path);
297
475
  existDoc = renamedFile;
298
476
  }
299
477
  const docRead = await this.db.fetchEntryFromMeta(docEntry);
@@ -327,6 +505,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
327
505
  }
328
506
  }
329
507
  if (!shouldApplied) {
508
+ await this.setProvenance(path, docEntry._rev, existDoc.stat.mtime);
330
509
  this._log(`File ${docRead.path} is not changed`, LOG_LEVEL_VERBOSE);
331
510
  return true;
332
511
  }
@@ -343,6 +522,10 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
343
522
  const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });
344
523
  await this.storage.touched(path);
345
524
  this.storage.triggerFileEvent(mode, path);
525
+ if (ret && this.fileReflectionProvenance) {
526
+ const storedStat = await this.storage.stat(path);
527
+ await this.setProvenance(path, docEntry._rev, storedStat?.mtime);
528
+ }
346
529
  return ret;
347
530
  }
348
531
  async preserveUnsyncedStorageAsConflict(path, existDoc, incomingEntry, incomingContent) {
@@ -359,11 +542,12 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
359
542
  if (await this.db.hasContentInRevisionHistory(path, readFile.body, incomingEntry._rev)) {
360
543
  return false;
361
544
  }
362
- const stored = await this.db.storeAsConflictedRevision(readFile, incomingEntry._rev, true);
363
- if (!stored) {
545
+ const storedRevision = await this.db.storeAsConflictedRevisionWithResult(readFile, incomingEntry._rev, true);
546
+ if (storedRevision === false) {
364
547
  this._log(`Prevented overwriting unsynchronised local changes for ${path}`, LOG_LEVEL_NOTICE);
365
548
  return true;
366
549
  }
550
+ await this.setProvenance(path, storedRevision, readFile.stat.mtime);
367
551
  this._log(`Preserved unsynchronised local changes as a conflict for ${path}`, LOG_LEVEL_NOTICE);
368
552
  await this.conflict.queueCheckFor(path);
369
553
  return true;
@@ -8,6 +8,7 @@ import type { ReplicatorService } from "./base/ReplicatorService.js";
8
8
  import type { UIService } from "./implements/base/UIService.js";
9
9
  import type { InjectableAPIService } from "./implements/injectable/InjectableAPIService.js";
10
10
  import type { KeyValueDatabaseFactory } from "../interfaces/KeyValueDatabase.js";
11
+ import type { ObsidianLiveSyncSettings } from "../common/types.js";
11
12
  export type BrowserServiceHostDependencies<T extends ServiceContext> = {
12
13
  API: InjectableAPIService<T>;
13
14
  appLifecycle: AppLifecycleService<T>;
@@ -29,6 +30,7 @@ export type BrowserServiceHubOptions<T extends ServiceContext> = {
29
30
  context?: T;
30
31
  host: BrowserServiceHost<T>;
31
32
  openKeyValueDatabase?: KeyValueDatabaseFactory;
33
+ onDisplayLanguageChanged?: (language: ObsidianLiveSyncSettings["displayLanguage"]) => void;
32
34
  };
33
35
  export declare class BrowserServiceHub<T extends ServiceContext> extends InjectableServiceHub<T> {
34
36
  get vault(): InjectableVaultServiceCompat<T>;
@@ -32,7 +32,10 @@ class BrowserServiceHub extends InjectableServiceHub {
32
32
  const API = options.host.createAPI(context);
33
33
  const conflict = new InjectableConflictService(context);
34
34
  const fileProcessing = new InjectableFileProcessingService(context);
35
- const setting = new InjectableSettingService(context, { APIService: API });
35
+ const setting = new InjectableSettingService(context, {
36
+ APIService: API,
37
+ onDisplayLanguageChanged: options.onDisplayLanguageChanged
38
+ });
36
39
  const appLifecycle = new BrowserAppLifecycleService(context, { settingService: setting });
37
40
  const remote = new InjectableRemoteService(context, {
38
41
  pouchDB: PouchDB,
@@ -4,11 +4,13 @@ import type { DatabaseService } from "./base/DatabaseService.js";
4
4
  import type { Constructor } from "../common/utils.type.js";
5
5
  import type { KeyValueDatabaseFactory } from "../interfaces/KeyValueDatabase.js";
6
6
  import type { PouchDBConstructor } from "../pouchdb/PouchDBConstructor.js";
7
+ import type { ObsidianLiveSyncSettings } from "../common/types.js";
7
8
  export declare class HeadlessServiceHub<T extends ServiceContext> extends InjectableServiceHub<T> {
8
9
  constructor(_context: T | undefined, overrideServiceConstructor: {
9
10
  /** PouchDB with the adapters required by this headless runtime. */
10
11
  pouchDB: PouchDBConstructor;
11
12
  database?: Constructor<DatabaseService<T>>;
12
13
  openKeyValueDatabase?: KeyValueDatabaseFactory;
14
+ onDisplayLanguageChanged?: (language: ObsidianLiveSyncSettings["displayLanguage"]) => void;
13
15
  });
14
16
  }
@@ -54,7 +54,8 @@ class HeadlessServiceHub extends InjectableServiceHub {
54
54
  const conflict = new InjectableConflictService(context);
55
55
  const fileProcessing = new InjectableFileProcessingService(context);
56
56
  const setting = new InjectableSettingService(context, {
57
- APIService: API
57
+ APIService: API,
58
+ onDisplayLanguageChanged: overrideServiceConstructor.onDisplayLanguageChanged
58
59
  });
59
60
  const appLifecycle = new HeadlessAppLifecycleService(context, {
60
61
  settingService: setting