@vrtmrz/livesync-commonlib 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/interfaces/DatabaseFileAccess.d.ts +10 -0
- package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManager.js +4 -0
- package/dist/managers/EntryManager/EntryManagerImpls.d.ts +7 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +21 -3
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +1 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +22 -4
- package/docs/conflict-resolution.md +2 -0
- package/docs/development.md +1 -1
- package/docs/proven-in-use.md +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,16 @@ export interface DatabaseFileAccess {
|
|
|
4
4
|
store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
|
5
5
|
/** Store a file as a child of an exact revision and return the created revision. */
|
|
6
6
|
storeWithBaseRevision: (file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean) => Promise<string | false>;
|
|
7
|
+
/**
|
|
8
|
+
* Store a file as a child of an exact revision only while that revision remains a live leaf.
|
|
9
|
+
*
|
|
10
|
+
* Unlike {@link storeWithBaseRevision}, this method does not force a branch below an obsolete
|
|
11
|
+
* revision. 'Live leaf' means any current revision-tree leaf, including a non-winning conflict
|
|
12
|
+
* leaf or a logical-deletion leaf. In particular, it returns `false` when another writer has
|
|
13
|
+
* already advanced the supplied base; ordinary target and Chunk validation can also refuse the
|
|
14
|
+
* write without creating a Metadata successor.
|
|
15
|
+
*/
|
|
16
|
+
storeWithLiveBaseRevision: (file: UXFileInfo, baseRevision: string, skipCheck?: boolean) => Promise<string | false>;
|
|
7
17
|
storeAsConflictedRevision: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<boolean>;
|
|
8
18
|
/** Preserve unknown storage content as a conflict and return its exact revision. */
|
|
9
19
|
storeAsConflictedRevisionWithResult: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<string | false>;
|
|
@@ -35,4 +35,5 @@ export declare class EntryManager {
|
|
|
35
35
|
deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
36
36
|
storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
|
|
37
37
|
putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
38
|
+
putDBEntryWithLiveBaseRevision(note: SavingEntry, baseRevision: string, onlyChunks?: boolean): Promise<false | PouchDB.Core.Response>;
|
|
38
39
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isTargetFile,
|
|
10
10
|
prepareChunk,
|
|
11
11
|
putDBEntry,
|
|
12
|
+
putDBEntryWithLiveBaseRevision,
|
|
12
13
|
storeDeletionByPathAtRevision
|
|
13
14
|
} from "./EntryManagerImpls.js";
|
|
14
15
|
class EntryManager {
|
|
@@ -71,6 +72,9 @@ class EntryManager {
|
|
|
71
72
|
async putDBEntry(note, onlyChunks, conflictBaseRev) {
|
|
72
73
|
return await putDBEntry(this.serviceHost, this, note, onlyChunks, conflictBaseRev);
|
|
73
74
|
}
|
|
75
|
+
async putDBEntryWithLiveBaseRevision(note, baseRevision, onlyChunks) {
|
|
76
|
+
return await putDBEntryWithLiveBaseRevision(this.serviceHost, this, note, baseRevision, onlyChunks);
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
export {
|
|
76
80
|
EntryManager
|
|
@@ -13,6 +13,13 @@ type Managers = {
|
|
|
13
13
|
type NecessaryManagers<T extends keyof Managers> = Pick<Managers, T>;
|
|
14
14
|
export declare function createChunks(managers: NecessaryManagers<"chunkManager" | "hashManager" | "splitter">, dispFilename: string, note: SavingEntry): Promise<false | DocumentID[]>;
|
|
15
15
|
export declare function putDBEntry(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager" | "hashManager" | "splitter">, note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
16
|
+
/**
|
|
17
|
+
* Store an entry below an exact revision only while that revision remains a live leaf.
|
|
18
|
+
*
|
|
19
|
+
* PouchDB's ordinary MVCC write is the authority for this check. A 409 response means that the
|
|
20
|
+
* base has already been advanced, so the caller receives `false` and can retry from fresh state.
|
|
21
|
+
*/
|
|
22
|
+
export declare function putDBEntryWithLiveBaseRevision(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager" | "hashManager" | "splitter">, note: SavingEntry, baseRevision: string, onlyChunks?: boolean): Promise<false | PouchDB.Core.Response>;
|
|
16
23
|
export declare function isTargetFile(host: NecessaryServicesInterfaces<"setting", never>, filenameSrc: string): boolean;
|
|
17
24
|
export declare function prepareChunk({ chunkManager, hashManager }: NecessaryManagers<"chunkManager" | "hashManager">, piece: string): Promise<GeneratedChunk>;
|
|
18
25
|
export declare function getDBEntryMetaByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
@@ -110,6 +110,23 @@ async function createChunks(managers, dispFilename, note) {
|
|
|
110
110
|
return chunks;
|
|
111
111
|
}
|
|
112
112
|
async function putDBEntry(host, managers, note, onlyChunks, conflictBaseRev) {
|
|
113
|
+
const revisionTarget = conflictBaseRev ? { mode: "force-base", baseRevision: conflictBaseRev } : { mode: "latest" };
|
|
114
|
+
return await putDBEntryInternal(host, managers, note, onlyChunks, revisionTarget);
|
|
115
|
+
}
|
|
116
|
+
async function putDBEntryWithLiveBaseRevision(host, managers, note, baseRevision, onlyChunks) {
|
|
117
|
+
try {
|
|
118
|
+
return await putDBEntryInternal(host, managers, note, onlyChunks, {
|
|
119
|
+
mode: "live-base",
|
|
120
|
+
baseRevision
|
|
121
|
+
});
|
|
122
|
+
} catch (ex) {
|
|
123
|
+
if (ex && typeof ex === "object" && "status" in ex && ex.status === 409) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
throw ex;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarget) {
|
|
113
130
|
const { localDatabase, chunkManager, splitter } = managers;
|
|
114
131
|
const filename = host.services.path.id2path(note._id, note);
|
|
115
132
|
const dispFilename = stripAllPrefixes(filename);
|
|
@@ -146,8 +163,8 @@ async function putDBEntry(host, managers, note, onlyChunks, conflictBaseRev) {
|
|
|
146
163
|
eden: {}
|
|
147
164
|
};
|
|
148
165
|
return await serialized("file:" + filename, async () => {
|
|
149
|
-
if (
|
|
150
|
-
newDoc._rev =
|
|
166
|
+
if (revisionTarget.mode !== "latest") {
|
|
167
|
+
newDoc._rev = revisionTarget.baseRevision;
|
|
151
168
|
} else {
|
|
152
169
|
try {
|
|
153
170
|
const old = await localDatabase.get(newDoc._id);
|
|
@@ -159,7 +176,7 @@ async function putDBEntry(host, managers, note, onlyChunks, conflictBaseRev) {
|
|
|
159
176
|
}
|
|
160
177
|
}
|
|
161
178
|
}
|
|
162
|
-
const r = await localDatabase.put(newDoc, { force: true });
|
|
179
|
+
const r = revisionTarget.mode === "live-base" ? await localDatabase.put(newDoc) : await localDatabase.put(newDoc, { force: true });
|
|
163
180
|
if (r.ok) {
|
|
164
181
|
return r;
|
|
165
182
|
} else {
|
|
@@ -523,5 +540,6 @@ export {
|
|
|
523
540
|
isTargetFile,
|
|
524
541
|
prepareChunk,
|
|
525
542
|
putDBEntry,
|
|
543
|
+
putDBEntryWithLiveBaseRevision,
|
|
526
544
|
storeDeletionByPathAtRevision
|
|
527
545
|
};
|
|
@@ -192,6 +192,7 @@ export declare class LiveSyncLocalDB {
|
|
|
192
192
|
deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
193
193
|
storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
|
|
194
194
|
putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
195
|
+
putDBEntryWithLiveBaseRevision(note: SavingEntry, baseRevision: string, onlyChunks?: boolean): Promise<false | PouchDB.Core.Response>;
|
|
195
196
|
getConflictedDoc(path: FilePathWithPrefix, rev: string): Promise<false | diff_result_leaf>;
|
|
196
197
|
tryAutoMerge(path: FilePathWithPrefix, enableMarkdownAutoMerge: boolean): AutoMergeResult;
|
|
197
198
|
}
|
|
@@ -370,6 +370,9 @@ class LiveSyncLocalDB {
|
|
|
370
370
|
async putDBEntry(note, onlyChunks, conflictBaseRev) {
|
|
371
371
|
return await this.managers.entryManager.putDBEntry(note, onlyChunks, conflictBaseRev);
|
|
372
372
|
}
|
|
373
|
+
async putDBEntryWithLiveBaseRevision(note, baseRevision, onlyChunks) {
|
|
374
|
+
return await this.managers.entryManager.putDBEntryWithLiveBaseRevision(note, baseRevision, onlyChunks);
|
|
375
|
+
}
|
|
373
376
|
async getConflictedDoc(path, rev) {
|
|
374
377
|
return await this.managers.conflictManager.getConflictedDoc(path, rev);
|
|
375
378
|
}
|
|
@@ -27,6 +27,7 @@ export declare class ServiceDatabaseFileAccessBase extends ServiceModuleBase<Ser
|
|
|
27
27
|
createChunks(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
|
|
28
28
|
store(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
|
|
29
29
|
storeWithBaseRevision(file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean): Promise<string | false>;
|
|
30
|
+
storeWithLiveBaseRevision(file: UXFileInfo, baseRevision: string, skipCheck?: boolean): Promise<string | false>;
|
|
30
31
|
storeAsConflictedRevision(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<boolean>;
|
|
31
32
|
storeAsConflictedRevisionWithResult(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<string | false>;
|
|
32
33
|
storeDeletionWithBaseRevision(file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string): Promise<string | false>;
|
|
@@ -53,7 +53,17 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
|
|
|
53
53
|
return await this.__store(file, force, skipCheck, false) !== false;
|
|
54
54
|
}
|
|
55
55
|
async storeWithBaseRevision(file, baseRevision, skipCheck) {
|
|
56
|
-
const result = await this.__store(file, true, skipCheck, false,
|
|
56
|
+
const result = await this.__store(file, true, skipCheck, false, {
|
|
57
|
+
mode: "force-base",
|
|
58
|
+
baseRevision
|
|
59
|
+
});
|
|
60
|
+
return result !== null && typeof result === "object" && "rev" in result ? result.rev : false;
|
|
61
|
+
}
|
|
62
|
+
async storeWithLiveBaseRevision(file, baseRevision, skipCheck) {
|
|
63
|
+
const result = await this.__store(file, true, skipCheck, false, {
|
|
64
|
+
mode: "live-base",
|
|
65
|
+
baseRevision
|
|
66
|
+
});
|
|
57
67
|
return result !== null && typeof result === "object" && "rev" in result ? result.rev : false;
|
|
58
68
|
}
|
|
59
69
|
async storeAsConflictedRevision(file, currentRev, skipCheck) {
|
|
@@ -97,7 +107,7 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
|
|
|
97
107
|
};
|
|
98
108
|
return await this.__store(dummyUXFileInfo, true, false, false) !== false;
|
|
99
109
|
}
|
|
100
|
-
async __store(file, force = false, skipCheck, onlyChunks,
|
|
110
|
+
async __store(file, force = false, skipCheck, onlyChunks, revisionTarget = { mode: "default" }) {
|
|
101
111
|
if (!skipCheck) {
|
|
102
112
|
if (!await this.checkIsTargetFile(file)) {
|
|
103
113
|
return true;
|
|
@@ -148,7 +158,7 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
|
|
|
148
158
|
};
|
|
149
159
|
const msg = `STORAGE -> DB (${datatype}) `;
|
|
150
160
|
const isNotChanged = await serialized("file-" + fullPath, async () => {
|
|
151
|
-
if (
|
|
161
|
+
if (revisionTarget.mode !== "default") {
|
|
152
162
|
return false;
|
|
153
163
|
}
|
|
154
164
|
if (force) {
|
|
@@ -183,7 +193,15 @@ class ServiceDatabaseFileAccessBase extends ServiceModuleBase {
|
|
|
183
193
|
this._log(msg + " Skip " + fullPath, LOG_LEVEL_VERBOSE);
|
|
184
194
|
return true;
|
|
185
195
|
}
|
|
186
|
-
const ret = await this.database.localDatabase.
|
|
196
|
+
const ret = revisionTarget.mode === "live-base" ? await this.database.localDatabase.putDBEntryWithLiveBaseRevision(
|
|
197
|
+
d,
|
|
198
|
+
revisionTarget.baseRevision,
|
|
199
|
+
onlyChunks
|
|
200
|
+
) : await this.database.localDatabase.putDBEntry(
|
|
201
|
+
d,
|
|
202
|
+
onlyChunks,
|
|
203
|
+
revisionTarget.mode === "force-base" ? revisionTarget.baseRevision : void 0
|
|
204
|
+
);
|
|
187
205
|
if (ret !== false) {
|
|
188
206
|
this._log(msg + fullPath);
|
|
189
207
|
this.events.emitEvent(EVENT_FILE_SAVED);
|
|
@@ -80,6 +80,8 @@ The record is updated only after a successful database-to-storage reflection or
|
|
|
80
80
|
|
|
81
81
|
User-directed reconciliation may select any exact current live revision, including a conflict leaf rather than the deterministic winner. `dbToStorageWithSpecificRev()` rechecks that the selected revision is still live before reflecting its content. `storeFileToDBWithBaseRevision()` performs the same live check before storing current storage content as a child of that revision. If the content already matches the selected non-deleted revision, it records that revision directly without creating a child. A host can set `createIfDifferent` to `false` when it intends only to record an exact content match; differing content then fails instead of changing the revision tree. A host may also create a logical deletion on an explicitly selected branch with `deleteRevisionFromDB()` after performing its own current-live-revision check. This leaves storage unchanged and clears device-local provenance only when that record names the deleted revision.
|
|
82
82
|
|
|
83
|
+
`storeWithBaseRevision()` is the force-write primitive for deliberately preserving a branch below an exact revision, including a revision which is no longer a leaf. It must not be used when an operation means 'advance this revision only if it is still current'. For that compare-and-store boundary, `storeWithLiveBaseRevision()` uses PouchDB's ordinary revision check and returns `false` if the supplied base has already been advanced. Any current revision-tree leaf is eligible, including a non-winning conflict leaf or a logical-deletion leaf. The caller must refresh its state after refusal rather than silently falling back to the deterministic winner.
|
|
84
|
+
|
|
83
85
|
A successful non-deleted reconciliation records the exact selected or newly created revision as provenance. Applying a logical deletion removes the storage item and its provenance record. Deletion provenance is not retained indefinitely: an absent storage item and a current logical-deletion winner already agree and need no further reconciliation.
|
|
84
86
|
|
|
85
87
|
Hosts may construct the namespaced store handle during service composition, before its backing database is open. Store operations begin only after the host's storage lifecycle is ready. They fail rather than wait when that lifecycle contract is violated, because an implicit readiness wait could hang after failed initialisation or wait on its own initialisation handler. Database reset is a transient unavailable boundary; the host must avoid file processing during it and reconstruct derived provenance after reopening.
|
package/docs/development.md
CHANGED
|
@@ -59,7 +59,7 @@ The multiple-remote profile contract has focused unit coverage in `src/serviceFe
|
|
|
59
59
|
|
|
60
60
|
The full offline scanner applies both the host's target-file policy and Commonlib's built-in `shouldBeIgnored` policy before constructing storage/database pairs. This keeps legacy LiveSync log files and flag files outside ordinary create, restore, and delete decisions even when a host accepts their file extension. Lower-level write guards remain a defence for direct calls; they are not the normal full-scan filter. The complete pair-state decision matrix and database-to-storage reflection result contract are documented in [Full offline scan](offline-scanner.md). Extend that document, the Cartesian action test, and both collection-side tests whenever the policy changes.
|
|
61
61
|
|
|
62
|
-
The revision-tree safety rules are documented in [Conflict resolution and file provenance](conflict-resolution.md). `ConflictManager.unit.spec.ts` owns common-ancestor and conservative merge behaviour, deterministic pair selection for multiple live leaves, and reconstruction from the committed tree between stages. `ServiceDatabaseFileAccessBase.unit.spec.ts` owns all-branch content discovery, short-circuiting history existence checks after an exact match,
|
|
62
|
+
The revision-tree safety rules are documented in [Conflict resolution and file provenance](conflict-resolution.md). `ConflictManager.unit.spec.ts` owns common-ancestor and conservative merge behaviour, deterministic pair selection for multiple live leaves, and reconstruction from the committed tree between stages. `EntryManagerImpls.unit.spec.ts` owns the distinction between force-writing below an exact revision and conditionally advancing a current live leaf through PouchDB's ordinary revision check. `ServiceDatabaseFileAccessBase.unit.spec.ts` owns all-branch content discovery, short-circuiting history existence checks after an exact match, exact-revision logical deletion, and the public live-base result and event boundary. `ServiceFileHandlerBase.unit.spec.ts` owns the injected provenance policy for edit, deletion, and rename operations, together with user-selected reflection, child creation, strict matching, matching-provenance cleanup after exact branch deletion, and stale-revision refusal. `LiveSyncLocalDB.unit.spec.ts` owns chunk reachability across current winners, shared chunks, live conflicts, available merge ancestry, conflict resolution, and PouchDB replication of collection and recreation. Extend the relevant sides when changing conflict resolution, compaction handling, provenance, chunk collection, or database-to-storage overwrite protection.
|
|
63
63
|
|
|
64
64
|
`LiveSyncReplicator.unit.spec.ts` owns the CouchDB remote-chunk batch contract: a mixed `_all_docs` result must return each available document even when another keyed row reports an error. `ChunkFetcher.unit.spec.ts` owns the consumer behaviour: it writes valid returned chunks and emits `missingChunkRemote` only for requested IDs which are absent from the returned set. Extend both sides when changing remote chunk acquisition or missing-chunk delivery.
|
|
65
65
|
|
package/docs/proven-in-use.md
CHANGED
|
@@ -14,7 +14,7 @@ The consumer contract in `test/contracts/serviceContext.ts` checks shared result
|
|
|
14
14
|
|
|
15
15
|
Commonlib supplies the two sleep preferences and preserves them through Setup URI encoding. Self-hosted LiveSync applies those values once at its injected bounded-activity runner, so remote operations and downloaded-document application share the same host-owned Wake Lock policy. Focused downstream tests cover the platform preference table and both activity categories; operating-system suspend behaviour remains real-platform integration evidence rather than a Commonlib guarantee.
|
|
16
16
|
|
|
17
|
-
Commonlib's real-PouchDB and injected-boundary unit tests prove that conservative merge uses the nearest revision actually shared by both conflict leaves, multiple remaining leaves are selected in a deterministic order, each completed pair is reconstructed from the committed tree, storage content from a deleted losing branch remains recognisable after resolution, and conflict-time operations retain the exact displayed branch or preserve every branch when that identity cannot be proved. They also verify that a boolean history check stops after its first exact content match without reading an older unavailable revision, while revision enumeration still returns every match. A local edit remains a readable sibling branch when the winning body is unavailable but its exact revision and parent metadata remain, a generation-one revision without a parent is left unresolved, and a separately confirmed exact deletion of that unreadable live leaf permits a replacement to be stored without reading the missing chunk. Exact reconciliation tests cover reflecting a selected live leaf, storing Vault content as its child, recording a matching leaf without creating a revision, clearing matching provenance after exact branch deletion, and refusing to record a stale revision or a revision whose content differs. Self-hosted LiveSync supplies the host composition, persistent device-local provenance, and the per-revision repair interface. Its optional two-Vault scopes cover post-resolution propagation and edit, logical deletion, case-only rename, and cross-path rename while conflicts remain active. The conflict-operation scope passes in real Obsidian against the exact locally packed Commonlib artefact, including replicated parent revisions and preservation of the other live branches.
|
|
17
|
+
Commonlib's real-PouchDB and injected-boundary unit tests prove that conservative merge uses the nearest revision actually shared by both conflict leaves, multiple remaining leaves are selected in a deterministic order, each completed pair is reconstructed from the committed tree, storage content from a deleted losing branch remains recognisable after resolution, and conflict-time operations retain the exact displayed branch or preserve every branch when that identity cannot be proved. They also verify that a boolean history check stops after its first exact content match without reading an older unavailable revision, while revision enumeration still returns every match. A local edit remains a readable sibling branch when the winning body is unavailable but its exact revision and parent metadata remain, a generation-one revision without a parent is left unresolved, and a separately confirmed exact deletion of that unreadable live leaf permits a replacement to be stored without reading the missing chunk. Exact reconciliation tests cover reflecting a selected live leaf, storing Vault content as its child, recording a matching leaf without creating a revision, clearing matching provenance after exact branch deletion, and refusing to record a stale revision or a revision whose content differs. Separate live-base tests advance a selected conflict leaf or logical-deletion leaf through PouchDB's ordinary revision check, retain the other live leaf, and reject reuse of a stale base. Self-hosted LiveSync supplies the host composition, persistent device-local provenance, and the per-revision repair interface. Its optional two-Vault scopes cover post-resolution propagation and edit, logical deletion, case-only rename, and cross-path rename while conflicts remain active. The conflict-operation scope passes in real Obsidian against the exact locally packed Commonlib artefact, including replicated parent revisions and preservation of the other live branches. Its Document History scenario restores readable historical bytes as a new live successor and reflects that exact revision to the Vault.
|
|
18
18
|
|
|
19
19
|
Commonlib's chunk-reachability tests use real PouchDB revision trees. They verify that an ordinary superseded revision releases its unique chunks, a chunk shared by current files remains reachable, live conflict branches and their nearest available shared ancestor remain protected, and losing-branch ancestry becomes eligible after resolution. They also replicate a collected chunk deletion to another PouchDB database and verify that writing the same content can recreate and replicate the content-addressed chunk. Self-hosted LiveSync owns the CouchDB-specific Garbage Collection V3 workflow, including device checks, confirmation, deletion, push-only replication, and remote compaction.
|
|
20
20
|
|
package/package.json
CHANGED