@vrtmrz/livesync-commonlib 0.1.43 → 0.2.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 +57 -6
- package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
- package/dist/API/DirectFileManipulatorV2.js +3 -0
- package/dist/common/configForDoc.d.ts +1 -0
- package/dist/common/configForDoc.js +11 -1
- package/dist/common/models/db.definition.d.ts +1 -0
- package/dist/common/models/setting.const.preferred.js +1 -3
- package/dist/diagnostics/couchdb.js +86 -13
- package/dist/diagnostics/index.d.ts +2 -2
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/integrity.js +385 -158
- package/dist/diagnostics/names.d.ts +16 -0
- package/dist/diagnostics/names.js +50 -0
- package/dist/diagnostics/reports.d.ts +13 -10
- package/dist/diagnostics/reports.js +191 -50
- package/dist/diagnostics/types.d.ts +64 -4
- package/dist/diagnostics/usage.d.ts +4 -0
- package/dist/diagnostics/usage.js +158 -54
- package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
- package/dist/pouchdb/LiveSyncDBFunctions.js +4 -0
- package/dist/pouchdb/StreamingFetch.d.ts +5 -1
- package/dist/pouchdb/StreamingFetch.js +529 -65
- package/dist/replication/RemoteResource.d.ts +23 -2
- package/dist/replication/RemoteResource.js +6 -2
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +8 -0
- package/dist/replication/couchdb/LiveSyncReplicator.js +132 -1
- package/dist/replication/couchdb/fastForward.d.ts +19 -0
- package/dist/replication/couchdb/fastForward.js +119 -0
- package/dist/replication/index.d.ts +3 -1
- package/dist/replication/joinedDevices.d.ts +25 -0
- package/dist/replication/joinedDevices.js +123 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -1
- package/dist/replication/journal/JournalSyncCore.js +11 -1
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +4 -0
- package/dist/replication/journal/LiveSyncJournalReplicator.js +56 -1
- package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +3 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -1
- package/dist/replication/journal/objectstore/MinioStorageAdapter.js +27 -1
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +1 -1
- package/dist/replication/trystero/P2PRoomSessionOwner.js +27 -14
- package/dist/serviceModules/Rebuilder.js +41 -15
- package/dist/services/BrowserServices.d.ts +4 -2
- package/dist/services/BrowserServices.js +2 -5
- package/dist/services/HeadlessServices.d.ts +3 -0
- package/dist/services/HeadlessServices.js +2 -5
- package/dist/services/ServiceHub.d.ts +4 -4
- package/dist/services/base/IService.d.ts +10 -18
- package/dist/services/base/ReplicatorService.remoteResourceResolver.js +1 -1
- package/dist/services/base/SettingService.d.ts +8 -18
- package/dist/services/base/SettingService.js +28 -46
- package/dist/services/implements/base/SvelteDialog.d.ts +2 -2
- package/dist/services/implements/base/UIService.d.ts +2 -0
- package/dist/services/implements/base/UIService.js +5 -0
- package/dist/services/implements/injectable/InjectableServiceHub.d.ts +4 -4
- package/dist/services/implements/injectable/InjectableServices.d.ts +2 -2
- package/dist/services/implements/injectable/InjectableSettingService.d.ts +0 -3
- package/dist/services/implements/injectable/InjectableSettingService.js +0 -11
- package/dist/string_and_binary/chunks.js +7 -5
- package/dist/uiCommands.d.ts +26 -0
- package/dist/uiCommands.js +57 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/database-diagnostics.md +45 -0
- package/docs/development.md +15 -3
- package/docs/proven-in-use.md +45 -5
- package/docs/releasing.md +20 -13
- package/docs/settings-lifecycle.md +37 -5
- package/package.json +13 -17
- package/dist/services/base/ConfigService.d.ts +0 -7
- package/dist/services/base/ConfigService.js +0 -6
- package/dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts +0 -16
- package/dist/services/implements/browser/ConfigServiceBrowserCompat.js +0 -23
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const USAGE_ACCOUNTING_YIELD_EVERY = 64;
|
|
1
2
|
function asRecord(value) {
|
|
2
3
|
return value;
|
|
3
4
|
}
|
|
@@ -30,13 +31,29 @@ async function analyseDatabaseUsage(options) {
|
|
|
30
31
|
const errors = [];
|
|
31
32
|
const documentMap = /* @__PURE__ */ new Map();
|
|
32
33
|
const chunkMap = /* @__PURE__ */ new Map();
|
|
33
|
-
const progress = {
|
|
34
|
+
const progress = {
|
|
35
|
+
phase: "reading",
|
|
36
|
+
idsProcessed: 0,
|
|
37
|
+
revisionsProcessed: 0,
|
|
38
|
+
processed: 0,
|
|
39
|
+
total: 0,
|
|
40
|
+
totalIsFinal: false
|
|
41
|
+
};
|
|
34
42
|
let startUpdateSeq;
|
|
35
43
|
let endUpdateSeq;
|
|
36
44
|
let enumerationFailed = false;
|
|
37
|
-
const reportProgress = () =>
|
|
45
|
+
const reportProgress = () => {
|
|
46
|
+
try {
|
|
47
|
+
onProgress?.({ ...progress });
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
};
|
|
38
51
|
const isCancelled = () => signal?.aborted === true;
|
|
39
52
|
let cancelled = isCancelled();
|
|
53
|
+
let documentRevisionCount = 0;
|
|
54
|
+
let referenceCount = 0;
|
|
55
|
+
reportProgress();
|
|
56
|
+
if (isCancelled()) cancelled = true;
|
|
40
57
|
if (!cancelled) {
|
|
41
58
|
try {
|
|
42
59
|
const info = await source.info();
|
|
@@ -47,6 +64,7 @@ async function analyseDatabaseUsage(options) {
|
|
|
47
64
|
}
|
|
48
65
|
if (isCancelled()) cancelled = true;
|
|
49
66
|
}
|
|
67
|
+
let enumerationFinished = false;
|
|
50
68
|
try {
|
|
51
69
|
if (!cancelled && !isCancelled()) {
|
|
52
70
|
for await (const id of source.enumerateIds()) {
|
|
@@ -74,7 +92,16 @@ async function analyseDatabaseUsage(options) {
|
|
|
74
92
|
errors.push({ code: "revision_list_read_failed", id });
|
|
75
93
|
}
|
|
76
94
|
progress.idsProcessed++;
|
|
95
|
+
for (const revisionInfo of revisionInfos) {
|
|
96
|
+
if (revisionInfo.status === "available" && typeof revisionInfo.rev === "string") {
|
|
97
|
+
progress.total++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
77
100
|
reportProgress();
|
|
101
|
+
if (isCancelled()) {
|
|
102
|
+
cancelled = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
78
105
|
for (const revisionInfo of revisionInfos) {
|
|
79
106
|
if (isCancelled()) {
|
|
80
107
|
cancelled = true;
|
|
@@ -86,27 +113,28 @@ async function analyseDatabaseUsage(options) {
|
|
|
86
113
|
errors.push({ code: "revision_document_invalid", id });
|
|
87
114
|
continue;
|
|
88
115
|
}
|
|
89
|
-
progress.revisionsProcessed++;
|
|
90
116
|
let rawDocument;
|
|
91
117
|
try {
|
|
92
118
|
rawDocument = await source.get(id, { rev: revision });
|
|
93
119
|
} catch {
|
|
94
120
|
if (isCancelled()) {
|
|
95
121
|
cancelled = true;
|
|
96
|
-
reportProgress();
|
|
97
122
|
break;
|
|
98
123
|
}
|
|
99
124
|
errors.push({ code: "revision_read_failed", id, revision });
|
|
125
|
+
progress.revisionsProcessed++;
|
|
126
|
+
progress.processed++;
|
|
100
127
|
reportProgress();
|
|
101
128
|
continue;
|
|
102
129
|
}
|
|
103
130
|
if (isCancelled()) {
|
|
104
131
|
cancelled = true;
|
|
105
|
-
reportProgress();
|
|
106
132
|
break;
|
|
107
133
|
}
|
|
108
134
|
if (!rawDocument) {
|
|
109
135
|
errors.push({ code: "revision_not_found", id, revision });
|
|
136
|
+
progress.revisionsProcessed++;
|
|
137
|
+
progress.processed++;
|
|
110
138
|
reportProgress();
|
|
111
139
|
continue;
|
|
112
140
|
}
|
|
@@ -124,9 +152,13 @@ async function analyseDatabaseUsage(options) {
|
|
|
124
152
|
path,
|
|
125
153
|
chunks,
|
|
126
154
|
uniqueChunks: /* @__PURE__ */ new Set(),
|
|
127
|
-
sharedChunks: /* @__PURE__ */ new Set()
|
|
155
|
+
sharedChunks: /* @__PURE__ */ new Set(),
|
|
156
|
+
uniqueChunkSize: 0,
|
|
157
|
+
sharedChunkSize: 0
|
|
128
158
|
});
|
|
129
159
|
documentMap.set(documentId, revisions);
|
|
160
|
+
documentRevisionCount++;
|
|
161
|
+
referenceCount += chunks.size;
|
|
130
162
|
} else if (document.type === "leaf") {
|
|
131
163
|
const chunkId = getStringField(document._id);
|
|
132
164
|
if (!chunkMap.has(chunkId) && !document._deleted) {
|
|
@@ -137,11 +169,14 @@ async function analyseDatabaseUsage(options) {
|
|
|
137
169
|
} catch {
|
|
138
170
|
errors.push({ code: "revision_document_invalid", id, revision });
|
|
139
171
|
}
|
|
172
|
+
progress.revisionsProcessed++;
|
|
173
|
+
progress.processed++;
|
|
140
174
|
reportProgress();
|
|
141
175
|
}
|
|
142
176
|
if (isCancelled()) cancelled = true;
|
|
143
177
|
if (cancelled) break;
|
|
144
178
|
}
|
|
179
|
+
if (!cancelled && !isCancelled()) enumerationFinished = true;
|
|
145
180
|
} else if (isCancelled()) {
|
|
146
181
|
cancelled = true;
|
|
147
182
|
}
|
|
@@ -152,60 +187,127 @@ async function analyseDatabaseUsage(options) {
|
|
|
152
187
|
errors.push({ code: "id_enumeration_failed" });
|
|
153
188
|
}
|
|
154
189
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
if (isCancelled()) cancelled = true;
|
|
191
|
+
if (enumerationFinished) {
|
|
192
|
+
progress.totalIsFinal = true;
|
|
193
|
+
reportProgress();
|
|
194
|
+
if (isCancelled()) cancelled = true;
|
|
195
|
+
}
|
|
196
|
+
const readingWasCancelled = cancelled;
|
|
197
|
+
progress.phase = "accounting";
|
|
198
|
+
progress.processed = 0;
|
|
199
|
+
progress.total = referenceCount + documentRevisionCount + chunkMap.size;
|
|
200
|
+
progress.totalIsFinal = true;
|
|
201
|
+
reportProgress();
|
|
202
|
+
let accountingCancelled = !readingWasCancelled && isCancelled();
|
|
203
|
+
const rows = [];
|
|
204
|
+
const accountingErrors = [];
|
|
205
|
+
const recordAccountingWork = () => {
|
|
206
|
+
progress.processed++;
|
|
207
|
+
return progress.processed % USAGE_ACCOUNTING_YIELD_EVERY === 0;
|
|
208
|
+
};
|
|
209
|
+
const yieldAfterAccountingBatch = async () => {
|
|
210
|
+
reportProgress();
|
|
211
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
212
|
+
return readingWasCancelled || !isCancelled();
|
|
213
|
+
};
|
|
214
|
+
if (!readingWasCancelled && isCancelled()) accountingCancelled = true;
|
|
215
|
+
if (!accountingCancelled) {
|
|
216
|
+
for (const revisions of documentMap.values()) {
|
|
217
|
+
for (const document of revisions) {
|
|
218
|
+
for (const chunkId of document.chunks) {
|
|
219
|
+
const chunk = chunkMap.get(chunkId);
|
|
220
|
+
if (chunk) {
|
|
221
|
+
if (chunk.refCount === 0) {
|
|
222
|
+
document.uniqueChunks.add(chunkId);
|
|
223
|
+
document.uniqueChunkSize += chunk.length;
|
|
224
|
+
} else {
|
|
225
|
+
document.sharedChunks.add(chunkId);
|
|
226
|
+
document.sharedChunkSize += chunk.length;
|
|
227
|
+
}
|
|
228
|
+
chunk.refCount++;
|
|
229
|
+
}
|
|
230
|
+
if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
|
|
231
|
+
accountingCancelled = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (accountingCancelled) break;
|
|
236
|
+
}
|
|
237
|
+
if (accountingCancelled) break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!accountingCancelled) {
|
|
241
|
+
for (const revisions of documentMap.values()) {
|
|
242
|
+
for (const document of revisions) {
|
|
243
|
+
const revisionNumber = getRevisionNumber(document.revision);
|
|
244
|
+
let revisionHash;
|
|
245
|
+
try {
|
|
246
|
+
revisionHash = getRevisionHash(document.revision);
|
|
247
|
+
} catch {
|
|
248
|
+
accountingErrors.push({
|
|
249
|
+
code: "revision_document_invalid",
|
|
250
|
+
id: document.id,
|
|
251
|
+
revision: document.revision
|
|
252
|
+
});
|
|
253
|
+
if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
|
|
254
|
+
accountingCancelled = true;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
rows.push({
|
|
260
|
+
title: `${document.path} (${document.revision})`,
|
|
261
|
+
id: document.id,
|
|
262
|
+
path: document.path,
|
|
263
|
+
revision: document.revision,
|
|
264
|
+
rev: revisionNumber,
|
|
265
|
+
revHash: revisionHash,
|
|
266
|
+
uniqueChunkCount: document.uniqueChunks.size,
|
|
267
|
+
sharedChunkCount: document.sharedChunks.size,
|
|
268
|
+
uniqueChunkSize: document.uniqueChunkSize,
|
|
269
|
+
sharedChunkSize: document.sharedChunkSize
|
|
270
|
+
});
|
|
271
|
+
if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
|
|
272
|
+
accountingCancelled = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
163
275
|
}
|
|
276
|
+
if (accountingCancelled) break;
|
|
164
277
|
}
|
|
165
278
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
revisionHash = getRevisionHash(document.revision);
|
|
178
|
-
} catch {
|
|
179
|
-
errors.push({ code: "revision_document_invalid", id: document.id, revision: document.revision });
|
|
180
|
-
continue;
|
|
279
|
+
let orphanChunkCount = 0;
|
|
280
|
+
let orphanChunkSize = 0;
|
|
281
|
+
if (!accountingCancelled) {
|
|
282
|
+
for (const chunk of chunkMap.values()) {
|
|
283
|
+
if (chunk.refCount === 0) {
|
|
284
|
+
orphanChunkCount++;
|
|
285
|
+
orphanChunkSize += chunk.length;
|
|
286
|
+
}
|
|
287
|
+
if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
|
|
288
|
+
accountingCancelled = true;
|
|
289
|
+
break;
|
|
181
290
|
}
|
|
182
|
-
rows.push({
|
|
183
|
-
title: `${document.path} (${document.revision})`,
|
|
184
|
-
id: document.id,
|
|
185
|
-
path: document.path,
|
|
186
|
-
revision: document.revision,
|
|
187
|
-
rev: revisionNumber,
|
|
188
|
-
revHash: revisionHash,
|
|
189
|
-
uniqueChunkCount: document.uniqueChunks.size,
|
|
190
|
-
sharedChunkCount: document.sharedChunks.size,
|
|
191
|
-
uniqueChunkSize: totalSize(document.uniqueChunks),
|
|
192
|
-
sharedChunkSize: totalSize(document.sharedChunks)
|
|
193
|
-
});
|
|
194
291
|
}
|
|
195
292
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
293
|
+
if (accountingCancelled) {
|
|
294
|
+
cancelled = true;
|
|
295
|
+
rows.length = 0;
|
|
296
|
+
} else {
|
|
297
|
+
errors.push(...accountingErrors);
|
|
298
|
+
rows.push({
|
|
299
|
+
title: "__orphan",
|
|
300
|
+
id: "__orphan",
|
|
301
|
+
path: "__orphan",
|
|
302
|
+
revision: "1-xxxxx",
|
|
303
|
+
rev: "1",
|
|
304
|
+
revHash: "xxxxx",
|
|
305
|
+
uniqueChunkCount: orphanChunkCount,
|
|
306
|
+
sharedChunkCount: 0,
|
|
307
|
+
uniqueChunkSize: orphanChunkSize,
|
|
308
|
+
sharedChunkSize: 0
|
|
309
|
+
});
|
|
310
|
+
}
|
|
209
311
|
if (!cancelled && !isCancelled()) {
|
|
210
312
|
try {
|
|
211
313
|
const info = await source.info();
|
|
@@ -216,6 +318,8 @@ async function analyseDatabaseUsage(options) {
|
|
|
216
318
|
}
|
|
217
319
|
}
|
|
218
320
|
if (isCancelled()) cancelled = true;
|
|
321
|
+
reportProgress();
|
|
322
|
+
if (isCancelled()) cancelled = true;
|
|
219
323
|
const changedDuringRun = startUpdateSeq === void 0 || endUpdateSeq === void 0 ? void 0 : startUpdateSeq !== endUpdateSeq;
|
|
220
324
|
const snapshotUncertain = startUpdateSeq === void 0 || endUpdateSeq === void 0;
|
|
221
325
|
const hasDocumentRows = rows.length > 1;
|
|
@@ -206,7 +206,8 @@ async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarg
|
|
|
206
206
|
}
|
|
207
207
|
function isTargetFile(host, filenameSrc) {
|
|
208
208
|
const settings = host.services.setting.currentSettings();
|
|
209
|
-
const
|
|
209
|
+
const isInternalFile = filenameSrc.startsWith(ICHeader);
|
|
210
|
+
const file = isInternalFile ? filenameSrc.substring(ICHeader.length) : filenameSrc;
|
|
210
211
|
if (file.startsWith(ICXHeader)) return true;
|
|
211
212
|
if (file.startsWith(PSCHeader)) return true;
|
|
212
213
|
if (file.startsWith(IDPrefixes.Chunk) || file.startsWith(IDPrefixes.Obfuscated)) {
|
|
@@ -217,7 +218,7 @@ function isTargetFile(host, filenameSrc) {
|
|
|
217
218
|
return false;
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
|
-
if (settings.syncOnlyRegEx) {
|
|
221
|
+
if (!isInternalFile && settings.syncOnlyRegEx) {
|
|
221
222
|
const syncOnly = getFileRegExp(settings, "syncOnlyRegEx");
|
|
222
223
|
if (syncOnly.length > 0 && !syncOnly.some((e) => e.test(file))) return false;
|
|
223
224
|
}
|
|
@@ -86,6 +86,10 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
|
|
|
86
86
|
writeMilestone = true;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
if (nodeDeviceInfo.platform !== void 0 && info.platform !== nodeDeviceInfo.platform) {
|
|
90
|
+
remoteMilestone.node_info[deviceNodeID].platform = nodeDeviceInfo.platform;
|
|
91
|
+
writeMilestone = true;
|
|
92
|
+
}
|
|
89
93
|
const diffLastConnected = now - (remoteMilestone.node_info[deviceNodeID].last_connected || 0);
|
|
90
94
|
if (diffLastConnected > 6e4) {
|
|
91
95
|
remoteMilestone.node_info[deviceNodeID].last_connected = now;
|
|
@@ -54,6 +54,10 @@ export type FetchChangesForInitialSyncProgress = {
|
|
|
54
54
|
* @param decryptFunction Function to decrypt each document.
|
|
55
55
|
* @param since Sequence ID to start fetching changes from (default is '0').
|
|
56
56
|
* @param customHeaders Additional request headers required by the CouchDB endpoint or its reverse proxy.
|
|
57
|
+
* @param onStoredRevisions Observes revision identities after their complete local batch is persisted.
|
|
57
58
|
*/
|
|
58
|
-
export declare function fetchChangesForInitialSync(downloadToDB: PouchDB.Database, remoteDbUrl: string, authHeader: string, decryptFunction: (doc: EntryDoc) => Promise<AnyEntry | EntryLeaf>, since?: number | string, onProgress?: (progress: FetchChangesForInitialSyncProgress) => void, onCheckpoint?: (sequence: DBSequence) => void | Promise<void>, customHeaders?: Record<string, string
|
|
59
|
+
export declare function fetchChangesForInitialSync(downloadToDB: PouchDB.Database, remoteDbUrl: string, authHeader: string, decryptFunction: (doc: EntryDoc) => Promise<AnyEntry | EntryLeaf>, since?: number | string, onProgress?: (progress: FetchChangesForInitialSyncProgress) => void, onCheckpoint?: (sequence: DBSequence) => void | Promise<void>, customHeaders?: Record<string, string>, onStoredRevisions?: (revisions: readonly {
|
|
60
|
+
id: string;
|
|
61
|
+
rev: string;
|
|
62
|
+
}[]) => void | Promise<void>): Promise<void>;
|
|
59
63
|
export {};
|