@vrtmrz/livesync-commonlib 0.1.0-rc.9 → 0.1.1-rc.0
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/README.md +7 -4
- package/dist/API/DirectFileManipulator.d.ts +1 -1
- package/dist/API/DirectFileManipulatorV2.d.ts +16 -31
- package/dist/API/DirectFileManipulatorV2.js +35 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
- package/dist/interfaces/FileHandler.d.ts +17 -1
- package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
- package/dist/interfaces/FileReflectionProvenance.js +25 -0
- package/dist/managers/ConflictManager.js +42 -9
- package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManager.js +5 -1
- package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
- package/dist/managers/LiveSyncManagers.js +1 -1
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
- package/dist/serviceFeatures/offlineScanner.js +3 -2
- package/dist/serviceFeatures/remoteConfig.d.ts +2 -2
- package/dist/serviceFeatures/remoteConfig.js +2 -2
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +22 -1
- package/dist/serviceModules/ServiceFileHandlerBase.js +323 -48
- package/dist/services/HeadlessServices.d.ts +4 -0
- package/dist/services/HeadlessServices.js +7 -3
- package/dist/services/base/KeyValueDBService.d.ts +2 -1
- package/dist/services/base/KeyValueDBService.js +10 -6
- package/dist/services/base/PathService.d.ts +6 -0
- package/dist/services/base/PathService.js +4 -1
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- package/dist/services/base/ReplicatorService.js +8 -6
- package/dist/services/implements/base/SvelteDialog.d.ts +5 -0
- package/dist/services/implements/headless/HeadlessAPIService.js +2 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/conflict-resolution.md +173 -0
- package/docs/development.md +6 -0
- package/docs/proven-in-use.md +18 -6
- package/docs/releasing.md +10 -7
- package/package.json +6 -26
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,29 @@ 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
|
+
storeFileToDBWithBaseRevision(info: UXFileInfoStub | UXFileInfo | FilePathWithPrefix, baseRevision: string, createIfDifferent?: boolean): Promise<boolean>;
|
|
61
|
+
private storeFileToDBFromRevision;
|
|
42
62
|
deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean>;
|
|
43
63
|
renameFileInDB(info: UXFileInfoStub | UXFileInfo, oldPath: FilePath | FilePathWithPrefix): Promise<boolean>;
|
|
44
64
|
deleteRevisionFromDB(info: UXFileInfoStub | FilePath | FilePathWithPrefix, rev: string): Promise<boolean | undefined>;
|
|
45
65
|
resolveConflictedByDeletingRevision(info: UXFileInfoStub | FilePath, rev: string): Promise<boolean | undefined>;
|
|
46
|
-
dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | null, rev: string, force?: boolean): Promise<boolean>;
|
|
66
|
+
dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | FilePathWithPrefix | null, rev: string, force?: boolean): Promise<boolean>;
|
|
47
67
|
dbToStorage(entryInfo: MetaEntry | FilePathWithPrefix, info: UXFileInfoStub | UXFileInfo | FilePath | null, force?: boolean): Promise<boolean>;
|
|
68
|
+
private applyDatabaseEntryToStorage;
|
|
48
69
|
private preserveUnsyncedStorageAsConflict;
|
|
49
70
|
private _anyHandlerProcessesFileEvent;
|
|
50
71
|
_anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean>;
|