@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.
Files changed (71) hide show
  1. package/README.md +57 -6
  2. package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
  3. package/dist/API/DirectFileManipulatorV2.js +3 -0
  4. package/dist/common/configForDoc.d.ts +1 -0
  5. package/dist/common/configForDoc.js +11 -1
  6. package/dist/common/models/db.definition.d.ts +1 -0
  7. package/dist/common/models/setting.const.preferred.js +1 -3
  8. package/dist/diagnostics/couchdb.js +86 -13
  9. package/dist/diagnostics/index.d.ts +2 -2
  10. package/dist/diagnostics/index.js +2 -0
  11. package/dist/diagnostics/integrity.js +385 -158
  12. package/dist/diagnostics/names.d.ts +16 -0
  13. package/dist/diagnostics/names.js +50 -0
  14. package/dist/diagnostics/reports.d.ts +13 -10
  15. package/dist/diagnostics/reports.js +191 -50
  16. package/dist/diagnostics/types.d.ts +64 -4
  17. package/dist/diagnostics/usage.d.ts +4 -0
  18. package/dist/diagnostics/usage.js +158 -54
  19. package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
  20. package/dist/pouchdb/LiveSyncDBFunctions.js +4 -0
  21. package/dist/pouchdb/StreamingFetch.d.ts +5 -1
  22. package/dist/pouchdb/StreamingFetch.js +529 -65
  23. package/dist/replication/RemoteResource.d.ts +23 -2
  24. package/dist/replication/RemoteResource.js +6 -2
  25. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +8 -0
  26. package/dist/replication/couchdb/LiveSyncReplicator.js +132 -1
  27. package/dist/replication/couchdb/fastForward.d.ts +19 -0
  28. package/dist/replication/couchdb/fastForward.js +119 -0
  29. package/dist/replication/index.d.ts +3 -1
  30. package/dist/replication/joinedDevices.d.ts +25 -0
  31. package/dist/replication/joinedDevices.js +123 -0
  32. package/dist/replication/journal/JournalSyncCore.d.ts +2 -1
  33. package/dist/replication/journal/JournalSyncCore.js +11 -1
  34. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +4 -0
  35. package/dist/replication/journal/LiveSyncJournalReplicator.js +56 -1
  36. package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +3 -0
  37. package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -1
  38. package/dist/replication/journal/objectstore/MinioStorageAdapter.js +27 -1
  39. package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +1 -1
  40. package/dist/replication/trystero/P2PRoomSessionOwner.js +27 -14
  41. package/dist/serviceModules/Rebuilder.js +41 -15
  42. package/dist/services/BrowserServices.d.ts +4 -2
  43. package/dist/services/BrowserServices.js +2 -5
  44. package/dist/services/HeadlessServices.d.ts +3 -0
  45. package/dist/services/HeadlessServices.js +2 -5
  46. package/dist/services/ServiceHub.d.ts +4 -4
  47. package/dist/services/base/IService.d.ts +10 -18
  48. package/dist/services/base/ReplicatorService.remoteResourceResolver.js +1 -1
  49. package/dist/services/base/SettingService.d.ts +8 -18
  50. package/dist/services/base/SettingService.js +28 -46
  51. package/dist/services/implements/base/SvelteDialog.d.ts +2 -2
  52. package/dist/services/implements/base/UIService.d.ts +2 -0
  53. package/dist/services/implements/base/UIService.js +5 -0
  54. package/dist/services/implements/injectable/InjectableServiceHub.d.ts +4 -4
  55. package/dist/services/implements/injectable/InjectableServices.d.ts +2 -2
  56. package/dist/services/implements/injectable/InjectableSettingService.d.ts +0 -3
  57. package/dist/services/implements/injectable/InjectableSettingService.js +0 -11
  58. package/dist/string_and_binary/chunks.js +7 -5
  59. package/dist/uiCommands.d.ts +26 -0
  60. package/dist/uiCommands.js +57 -0
  61. package/dist/worker/bgWorker.js +1 -1
  62. package/docs/database-diagnostics.md +45 -0
  63. package/docs/development.md +15 -3
  64. package/docs/proven-in-use.md +45 -5
  65. package/docs/releasing.md +20 -13
  66. package/docs/settings-lifecycle.md +37 -5
  67. package/package.json +13 -17
  68. package/dist/services/base/ConfigService.d.ts +0 -7
  69. package/dist/services/base/ConfigService.js +0 -6
  70. package/dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts +0 -16
  71. 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 = { idsProcessed: 0, revisionsProcessed: 0 };
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 = () => onProgress?.({ ...progress });
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
- for (const revisions of documentMap.values()) {
156
- for (const document of revisions) {
157
- for (const chunkId of document.chunks) {
158
- const chunk = chunkMap.get(chunkId);
159
- if (!chunk) continue;
160
- if (chunk.refCount === 0) document.uniqueChunks.add(chunkId);
161
- else document.sharedChunks.add(chunkId);
162
- chunk.refCount++;
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
- const totalSize = (ids) => {
167
- let size = 0;
168
- for (const id of ids) size += chunkMap.get(id)?.length ?? 0;
169
- return size;
170
- };
171
- const rows = [];
172
- for (const revisions of documentMap.values()) {
173
- for (const document of revisions) {
174
- const revisionNumber = getRevisionNumber(document.revision);
175
- let revisionHash;
176
- try {
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
- const orphanChunks = [...chunkMap.entries()].filter(([, chunk]) => chunk.refCount === 0);
197
- rows.push({
198
- title: "__orphan",
199
- id: "__orphan",
200
- path: "__orphan",
201
- revision: "1-xxxxx",
202
- rev: "1",
203
- revHash: "xxxxx",
204
- uniqueChunkCount: orphanChunks.length,
205
- sharedChunkCount: 0,
206
- uniqueChunkSize: orphanChunks.reduce((size, [, chunk]) => size + chunk.length, 0),
207
- sharedChunkSize: 0
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 file = filenameSrc.startsWith(ICHeader) ? filenameSrc.substring(ICHeader.length) : filenameSrc;
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>): Promise<void>;
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 {};