@vrtmrz/livesync-commonlib 0.1.0-rc.1 → 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 (141) hide show
  1. package/README.md +90 -20
  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 +2 -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/storage.d.ts +10 -1
  31. package/dist/platform/node/storage.js +86 -25
  32. package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
  33. package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
  34. package/dist/remoteConfigurations.d.ts +9 -0
  35. package/dist/remoteConfigurations.js +14 -0
  36. package/dist/replication/LiveSyncAbstractReplicator.d.ts +3 -3
  37. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +0 -1
  38. package/dist/replication/couchdb/LiveSyncReplicator.js +0 -3
  39. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +0 -1
  40. package/dist/replication/journal/LiveSyncJournalReplicator.js +0 -3
  41. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +5 -3
  42. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +42 -30
  43. package/dist/replication/trystero/P2PReplicatorCore.d.ts +2 -6
  44. package/dist/replication/trystero/P2PReplicatorCore.js +16 -86
  45. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -1
  46. package/dist/replication/trystero/TrysteroReplicator.js +10 -4
  47. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +4 -5
  48. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +22 -30
  49. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -2
  50. package/dist/replication/trystero/addP2PEventHandlers.d.ts +4 -2
  51. package/dist/replication/trystero/addP2PEventHandlers.js +9 -10
  52. package/dist/replication/trystero/types.d.ts +2 -0
  53. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +1 -1
  54. package/dist/replication/trystero/useP2PReplicatorCommands.js +3 -1
  55. package/dist/replication/trystero/useP2PReplicatorFeature.js +25 -15
  56. package/dist/rpc/RpcRoom.d.ts +1 -1
  57. package/dist/rpc/RpcRoom.js +22 -16
  58. package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
  59. package/dist/rpc/transports/TrysteroTransport.js +24 -17
  60. package/dist/rpc/transports/trysteroRoomEvents.d.ts +14 -0
  61. package/dist/rpc/transports/trysteroRoomEvents.js +34 -0
  62. package/dist/serviceFeatures/checkRemoteSize.d.ts +5 -3
  63. package/dist/serviceFeatures/checkRemoteSize.js +51 -11
  64. package/dist/serviceFeatures/remoteConfig.d.ts +37 -1
  65. package/dist/serviceFeatures/remoteConfig.js +90 -0
  66. package/dist/serviceModules/Rebuilder.d.ts +3 -6
  67. package/dist/serviceModules/Rebuilder.js +31 -22
  68. package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
  69. package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
  70. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +20 -0
  71. package/dist/serviceModules/ServiceFileHandlerBase.js +224 -40
  72. package/dist/services/BrowserServices.d.ts +2 -0
  73. package/dist/services/BrowserServices.js +4 -1
  74. package/dist/services/HeadlessServices.d.ts +2 -0
  75. package/dist/services/HeadlessServices.js +2 -1
  76. package/dist/services/base/CommonlibMessages.d.ts +318 -0
  77. package/dist/services/base/CommonlibMessages.js +313 -0
  78. package/dist/services/base/IService.d.ts +13 -6
  79. package/dist/services/base/KeyValueDBService.d.ts +1 -1
  80. package/dist/services/base/KeyValueDBService.js +3 -1
  81. package/dist/services/base/MessageTranslator.d.ts +5 -2
  82. package/dist/services/base/MessageTranslator.js +21 -0
  83. package/dist/services/base/ReplicationService.d.ts +4 -2
  84. package/dist/services/base/ReplicationService.js +11 -16
  85. package/dist/services/base/ServiceBase.d.ts +1 -1
  86. package/dist/services/base/ServiceBase.js +2 -4
  87. package/dist/services/base/SettingService.d.ts +17 -9
  88. package/dist/services/base/SettingService.js +38 -19
  89. package/dist/services/implements/headless/HeadlessAPIService.d.ts +3 -3
  90. package/dist/services/implements/headless/HeadlessAPIService.js +2 -2
  91. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +3 -3
  92. package/dist/settings.d.ts +10 -0
  93. package/dist/settings.js +32 -0
  94. package/dist/worker/bgWorker.js +1 -1
  95. package/docs/conflict-resolution.md +153 -0
  96. package/docs/development.md +106 -7
  97. package/docs/p2p-transport-lifecycle.md +62 -0
  98. package/docs/platform-storage.md +4 -0
  99. package/docs/proven-in-use.md +65 -0
  100. package/docs/releasing.md +26 -6
  101. package/docs/remote-configurations.md +63 -0
  102. package/docs/settings-lifecycle.md +85 -0
  103. package/package.json +17 -27
  104. package/dist/common/i18n.d.ts +0 -18
  105. package/dist/common/i18n.js +0 -7193
  106. package/dist/common/messages/combinedMessages.dev.d.ts +0 -7
  107. package/dist/common/messages/combinedMessages.dev.js +0 -44
  108. package/dist/common/messages/combinedMessages.prod.d.ts +0 -9264
  109. package/dist/common/messages/combinedMessages.prod.js +0 -9271
  110. package/dist/common/messages/de.d.ts +0 -297
  111. package/dist/common/messages/de.js +0 -7
  112. package/dist/common/messages/def.d.ts +0 -1119
  113. package/dist/common/messages/def.js +0 -7
  114. package/dist/common/messages/es.d.ts +0 -689
  115. package/dist/common/messages/es.js +0 -7
  116. package/dist/common/messages/fr.d.ts +0 -582
  117. package/dist/common/messages/fr.js +0 -7
  118. package/dist/common/messages/he.d.ts +0 -583
  119. package/dist/common/messages/he.js +0 -7
  120. package/dist/common/messages/ja.d.ts +0 -838
  121. package/dist/common/messages/ja.js +0 -7
  122. package/dist/common/messages/ko.d.ts +0 -800
  123. package/dist/common/messages/ko.js +0 -7
  124. package/dist/common/messages/ru.d.ts +0 -858
  125. package/dist/common/messages/ru.js +0 -7
  126. package/dist/common/messages/zh-tw.d.ts +0 -384
  127. package/dist/common/messages/zh-tw.js +0 -7
  128. package/dist/common/messages/zh.d.ts +0 -1093
  129. package/dist/common/messages/zh.js +0 -7
  130. package/dist/common/messagesJson/de.json +0 -295
  131. package/dist/common/messagesJson/en.json +0 -1117
  132. package/dist/common/messagesJson/es.json +0 -687
  133. package/dist/common/messagesJson/fr.json +0 -580
  134. package/dist/common/messagesJson/he.json +0 -581
  135. package/dist/common/messagesJson/ja.json +0 -836
  136. package/dist/common/messagesJson/ko.json +0 -798
  137. package/dist/common/messagesJson/ru.json +0 -856
  138. package/dist/common/messagesJson/zh-tw.json +0 -382
  139. package/dist/common/messagesJson/zh.json +0 -1091
  140. package/dist/common/rosetta.d.ts +0 -42
  141. package/dist/common/rosetta.js +0 -57
@@ -48,10 +48,6 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
48
48
  constructor(services: ServiceRebuilderDependencies);
49
49
  $performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void>;
50
50
  informOptionalFeatures(): Promise<void>;
51
- askUsingOptionalFeature(opt: {
52
- enableFetch?: boolean;
53
- enableOverwrite?: boolean;
54
- }): Promise<void>;
55
51
  rebuildRemote(): Promise<void>;
56
52
  private performRemoteRebuild;
57
53
  $rebuildRemote(): Promise<void>;
@@ -60,8 +56,9 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
60
56
  $rebuildEverything(): Promise<void>;
61
57
  $fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean): Promise<void>;
62
58
  $fetchLocalDBFast(autoResume: boolean): Promise<void>;
63
- scheduleRebuild(): Promise<void>;
64
- scheduleFetch(): Promise<void>;
59
+ private scheduleInitialisation;
60
+ scheduleRebuild(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
61
+ scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
65
62
  private _tryResetRemoteDatabase;
66
63
  private _onResetLocalDatabase;
67
64
  suspendAllSync(): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { FlagFilesHumanReadable } from "../common/models/redflag.const.js";
2
- import { REMOTE_COUCHDB, REMOTE_MINIO } from "../common/models/setting.const.js";
2
+ import { REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P } from "../common/models/setting.const.js";
3
3
  import { DEFAULT_SETTINGS } from "../common/models/setting.const.defaults.js";
4
4
  import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
5
5
  import { delay } from "octagonal-wheels/promises";
@@ -67,14 +67,6 @@ Please enable them from the settings screen after setup is complete.`,
67
67
  ["OK"]
68
68
  );
69
69
  }
70
- async askUsingOptionalFeature(opt) {
71
- if (await this.UI.confirm.askYesNoDialog(
72
- "Do you want to enable extra features? If you are new to Self-hosted LiveSync, try the core feature first!",
73
- { title: "Enable extra features", defaultOption: "No", timeout: 15 }
74
- ) == "yes") {
75
- await this.setting.suggestOptionalFeatures(opt);
76
- }
77
- }
78
70
  async rebuildRemote() {
79
71
  await this.replicator.runBoundedRemoteActivity(() => this.performRemoteRebuild(), {
80
72
  label: "rebuild-remote"
@@ -95,7 +87,7 @@ Please enable them from the settings screen after setup is complete.`,
95
87
  await delay(1e3);
96
88
  await this.replication.replicateAllToRemote(true);
97
89
  await delay(1e3);
98
- await this.replication.replicateAllToRemote(true, true);
90
+ await this.replication.replicateAllToRemote(true);
99
91
  }
100
92
  $rebuildRemote() {
101
93
  return this.rebuildRemote();
@@ -116,6 +108,9 @@ Please enable them from the settings screen after setup is complete.`,
116
108
  await this.resetLocalDatabase();
117
109
  await delay(1e3);
118
110
  await this.databaseEvents.initialiseDatabase(true, true, true);
111
+ if (this.setting.currentSettings().remoteType === REMOTE_P2P) {
112
+ return;
113
+ }
119
114
  await this.replication.markLocked();
120
115
  await this._tryResetRemoteDatabase();
121
116
  await this.replication.markLocked();
@@ -123,7 +118,7 @@ Please enable them from the settings screen after setup is complete.`,
123
118
  await delay(1e3);
124
119
  await this.replication.replicateAllToRemote(true);
125
120
  await delay(1e3);
126
- await this.replication.replicateAllToRemote(true, true);
121
+ await this.replication.replicateAllToRemote(true);
127
122
  }
128
123
  $rebuildEverything() {
129
124
  return this.rebuildEverything();
@@ -134,23 +129,35 @@ Please enable them from the settings screen after setup is complete.`,
134
129
  $fetchLocalDBFast(autoResume) {
135
130
  return this.fetchLocalDBFast(autoResume);
136
131
  }
137
- async scheduleRebuild() {
132
+ async scheduleInitialisation(flag, prepareBeforeRestart) {
138
133
  try {
139
- await this.storageAccess.writeFileAuto(FlagFilesHumanReadable.REBUILD_ALL, "");
134
+ await this.storageAccess.writeFileAuto(flag, "");
140
135
  } catch (ex) {
141
- this._log(`Could not create ${FlagFilesHumanReadable.REBUILD_ALL}`, LOG_LEVEL_NOTICE);
136
+ this._log(`Could not create ${flag}`, LOG_LEVEL_NOTICE);
142
137
  this._log(ex, LOG_LEVEL_VERBOSE);
138
+ return false;
143
139
  }
144
- this.appLifecycle.performRestart();
145
- }
146
- async scheduleFetch() {
140
+ this.appLifecycle.setSuspended(true);
147
141
  try {
148
- await this.storageAccess.writeFileAuto(FlagFilesHumanReadable.FETCH_ALL, "");
142
+ await prepareBeforeRestart?.();
149
143
  } catch (ex) {
150
- this._log(`Could not create ${FlagFilesHumanReadable.FETCH_ALL}`, LOG_LEVEL_NOTICE);
151
- this._log(ex, LOG_LEVEL_VERBOSE);
144
+ try {
145
+ await this.storageAccess.delete(flag, true);
146
+ } catch (cleanupError) {
147
+ this._log(`Could not remove ${flag} after restart preparation failed`, LOG_LEVEL_NOTICE);
148
+ this._log(cleanupError, LOG_LEVEL_VERBOSE);
149
+ }
150
+ this.appLifecycle.setSuspended(false);
151
+ throw ex;
152
152
  }
153
153
  this.appLifecycle.performRestart();
154
+ return true;
155
+ }
156
+ scheduleRebuild(prepareBeforeRestart) {
157
+ return this.scheduleInitialisation(FlagFilesHumanReadable.REBUILD_ALL, prepareBeforeRestart);
158
+ }
159
+ scheduleFetch(prepareBeforeRestart) {
160
+ return this.scheduleInitialisation(FlagFilesHumanReadable.FETCH_ALL, prepareBeforeRestart);
154
161
  }
155
162
  async _tryResetRemoteDatabase() {
156
163
  const currentReplicator = this.replicator.getActiveReplicator();
@@ -269,8 +276,10 @@ Are you sure you wish to proceed?`;
269
276
  await this.replication.markResolved();
270
277
  await delay(500);
271
278
  await this.replication.replicateAllFromRemote(true);
272
- await delay(1e3);
273
- await this.replication.replicateAllFromRemote(true);
279
+ if (this.setting.currentSettings().remoteType !== REMOTE_P2P) {
280
+ await delay(1e3);
281
+ await this.replication.replicateAllFromRemote(true);
282
+ }
274
283
  if (autoResume) {
275
284
  await this.finishRebuild();
276
285
  }
@@ -26,10 +26,15 @@ export declare class ServiceDatabaseFileAccessBase extends ServiceModuleBase<Ser
26
26
  delete(file: UXFileInfoStub | FilePathWithPrefix, rev?: string): Promise<boolean>;
27
27
  createChunks(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
28
28
  store(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
29
+ storeWithBaseRevision(file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean): Promise<string | false>;
29
30
  storeAsConflictedRevision(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<boolean>;
31
+ storeAsConflictedRevisionWithResult(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<string | false>;
32
+ storeDeletionWithBaseRevision(file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string): Promise<string | false>;
30
33
  storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
31
34
  private __store;
32
35
  private getParentRev;
36
+ private findContentRevisionsInternal;
37
+ findContentRevisions(file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string): Promise<string[]>;
33
38
  hasContentInRevisionHistory(file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string): Promise<boolean>;
34
39
  getConflictedRevs(file: UXFileInfoStub | FilePathWithPrefix): Promise<string[]>;
35
40
  fetch(file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean): Promise<UXFileInfo | false>;
@@ -47,18 +47,37 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
47
47
  }
48
48
  }
49
49
  async createChunks(file, force = false, skipCheck) {
50
- return await this.__store(file, force, skipCheck, true);
50
+ return await this.__store(file, force, skipCheck, true) !== false;
51
51
  }
52
52
  async store(file, force = false, skipCheck) {
53
- return await this.__store(file, force, skipCheck, false);
53
+ return await this.__store(file, force, skipCheck, false) !== false;
54
+ }
55
+ async storeWithBaseRevision(file, baseRevision, skipCheck) {
56
+ const result = await this.__store(file, true, skipCheck, false, baseRevision);
57
+ return result !== null && typeof result === "object" && "rev" in result ? result.rev : false;
54
58
  }
55
59
  async storeAsConflictedRevision(file, currentRev, skipCheck) {
60
+ return await this.storeAsConflictedRevisionWithResult(file, currentRev, skipCheck) !== false;
61
+ }
62
+ async storeAsConflictedRevisionWithResult(file, currentRev, skipCheck) {
56
63
  const conflictBaseRev = await this.getParentRev(file, currentRev);
57
64
  if (!conflictBaseRev) {
58
65
  this._log(`Could not find parent revision for ${file.path} (${currentRev})`, LOG_LEVEL_VERBOSE);
59
66
  return false;
60
67
  }
61
- return await this.__store(file, true, skipCheck, false, conflictBaseRev);
68
+ return await this.storeWithBaseRevision(file, conflictBaseRev, skipCheck);
69
+ }
70
+ async storeDeletionWithBaseRevision(file, baseRevision) {
71
+ if (!await this.checkIsTargetFile(file)) {
72
+ return false;
73
+ }
74
+ const fullPath = getDatabasePathFromUXFileInfo(file);
75
+ const result = await this.database.localDatabase.storeDeletionAtRevision(fullPath, baseRevision);
76
+ if (result === false) {
77
+ return false;
78
+ }
79
+ this.events.emitEvent(EVENT_FILE_SAVED);
80
+ return result.rev;
62
81
  }
63
82
  async storeContent(path, content) {
64
83
  const blob = createTextBlob(content);
@@ -76,7 +95,7 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
76
95
  body: blob,
77
96
  isInternal
78
97
  };
79
- return await this.__store(dummyUXFileInfo, true, false, false);
98
+ return await this.__store(dummyUXFileInfo, true, false, false) !== false;
80
99
  }
81
100
  async __store(file, force = false, skipCheck, onlyChunks, conflictBaseRev) {
82
101
  if (!skipCheck) {
@@ -169,7 +188,7 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
169
188
  this._log(msg + fullPath);
170
189
  this.events.emitEvent(EVENT_FILE_SAVED);
171
190
  }
172
- return ret != false;
191
+ return ret;
173
192
  }
174
193
  async getParentRev(file, rev) {
175
194
  const filename = getDatabasePathFromUXFileInfo(file);
@@ -189,10 +208,7 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
189
208
  return false;
190
209
  }
191
210
  }
192
- async hasContentInRevisionHistory(file, content, currentRev) {
193
- if (!await this.checkIsTargetFile(file)) {
194
- return true;
195
- }
211
+ async findContentRevisionsInternal(file, content, currentRev) {
196
212
  const filename = getDatabasePathFromUXFileInfo(file);
197
213
  try {
198
214
  const doc = await this.database.localDatabase.getDBEntryMeta(
@@ -201,24 +217,64 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
201
217
  true
202
218
  );
203
219
  if (doc === false) {
204
- return false;
220
+ return [];
205
221
  }
206
222
  const revisions = doc._revs_info;
207
223
  const availableRevs = new Set((revisions || []).filter((e) => e.status === "available").map((e) => e.rev));
208
224
  if (currentRev) {
209
225
  availableRevs.add(currentRev);
210
226
  }
227
+ const leaves = await this.database.localDatabase.getRaw(doc._id, {
228
+ open_revs: "all"
229
+ });
230
+ if (!Array.isArray(leaves)) {
231
+ return [];
232
+ }
233
+ for (const leaf of leaves) {
234
+ const leafRev = leaf.ok?._rev;
235
+ if (!leafRev) {
236
+ continue;
237
+ }
238
+ const branch = await this.database.localDatabase.getDBEntryMeta(
239
+ filename,
240
+ { rev: leafRev, revs_info: true },
241
+ true
242
+ );
243
+ if (branch === false) {
244
+ continue;
245
+ }
246
+ const branchRevisions = branch._revs_info;
247
+ for (const revision of branchRevisions || []) {
248
+ if (revision.status === "available") {
249
+ availableRevs.add(revision.rev);
250
+ }
251
+ }
252
+ }
253
+ const matchingRevisions = [];
211
254
  for (const rev of availableRevs) {
212
255
  const entry = await this.database.localDatabase.getDBEntry(filename, { rev }, false, true, true);
213
- if (entry !== false && await isDocContentSame(readContent(entry), content)) {
214
- return true;
256
+ if (entry !== false && !entry._deleted && await isDocContentSame(readContent(entry), content)) {
257
+ matchingRevisions.push(rev);
215
258
  }
216
259
  }
260
+ return matchingRevisions;
217
261
  } catch (ex) {
218
262
  this._log(`Could not check revision history for ${filename}`, LOG_LEVEL_VERBOSE);
219
263
  this._log(ex, LOG_LEVEL_VERBOSE);
220
264
  }
221
- return false;
265
+ return [];
266
+ }
267
+ async findContentRevisions(file, content, currentRev) {
268
+ if (!await this.checkIsTargetFile(file)) {
269
+ return [];
270
+ }
271
+ return await this.findContentRevisionsInternal(file, content, currentRev);
272
+ }
273
+ async hasContentInRevisionHistory(file, content, currentRev) {
274
+ if (!await this.checkIsTargetFile(file)) {
275
+ return true;
276
+ }
277
+ return (await this.findContentRevisionsInternal(file, content, currentRev)).length > 0;
222
278
  }
223
279
  async getConflictedRevs(file) {
224
280
  if (!await this.checkIsTargetFile(file)) {
@@ -11,6 +11,7 @@ import type { ConflictService } from "../services/base/ConflictService.js";
11
11
  import type { PathService } from "../services/base/PathService.js";
12
12
  import type { SettingService } from "../services/base/SettingService.js";
13
13
  import type { VaultService } from "../services/base/VaultService.js";
14
+ import type { FileReflectionProvenance } from "../interfaces/FileReflectionProvenance.js";
14
15
  export interface ServiceFileHandlerDependencies {
15
16
  events: LiveSyncEventHub;
16
17
  API: APIService;
@@ -22,6 +23,17 @@ export interface ServiceFileHandlerDependencies {
22
23
  path: PathService;
23
24
  setting: SettingService;
24
25
  vault: VaultService;
26
+ /**
27
+ * Device-local record of the exact database revision reflected in storage.
28
+ *
29
+ * This is optional for compatibility hosts. Maintained hosts should provide
30
+ * it so edits made while a document is conflicted extend the displayed
31
+ * branch instead of whichever branch PouchDB currently selects as winner.
32
+ * The host must finish opening its backing store before it dispatches file
33
+ * or replication events; provenance does not hide lifecycle violations by
34
+ * waiting for readiness.
35
+ */
36
+ fileReflectionProvenance?: FileReflectionProvenance;
25
37
  }
26
38
  export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<ServiceFileHandlerDependencies> implements IFileHandler {
27
39
  private events;
@@ -31,20 +43,28 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
31
43
  private path;
32
44
  private setting;
33
45
  private vault;
46
+ private fileReflectionProvenance?;
34
47
  constructor(services: ServiceFileHandlerDependencies);
35
48
  get db(): DatabaseFileAccess;
36
49
  get storage(): StorageAccess;
50
+ private getProvenance;
51
+ private setProvenance;
52
+ private deleteProvenance;
53
+ private findUniqueContentRevision;
54
+ private getProvenBaseRevision;
37
55
  getPath(entry: AnyEntry): FilePathWithPrefix;
38
56
  getPathWithoutPrefix(entry: AnyEntry): FilePathWithPrefix;
39
57
  readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
40
58
  private infoToStub;
41
59
  storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
60
+ private storeFileToDBFromRevision;
42
61
  deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean>;
43
62
  renameFileInDB(info: UXFileInfoStub | UXFileInfo, oldPath: FilePath | FilePathWithPrefix): Promise<boolean>;
44
63
  deleteRevisionFromDB(info: UXFileInfoStub | FilePath | FilePathWithPrefix, rev: string): Promise<boolean | undefined>;
45
64
  resolveConflictedByDeletingRevision(info: UXFileInfoStub | FilePath, rev: string): Promise<boolean | undefined>;
46
65
  dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | null, rev: string, force?: boolean): Promise<boolean>;
47
66
  dbToStorage(entryInfo: MetaEntry | FilePathWithPrefix, info: UXFileInfoStub | UXFileInfo | FilePath | null, force?: boolean): Promise<boolean>;
67
+ private applyDatabaseEntryToStorage;
48
68
  private preserveUnsyncedStorageAsConflict;
49
69
  private _anyHandlerProcessesFileEvent;
50
70
  _anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean>;