cojson 0.20.9 → 0.20.11

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 (179) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/OngoingStorageReconciliationTracker.d.ts +16 -0
  4. package/dist/OngoingStorageReconciliationTracker.d.ts.map +1 -0
  5. package/dist/OngoingStorageReconciliationTracker.js +75 -0
  6. package/dist/OngoingStorageReconciliationTracker.js.map +1 -0
  7. package/dist/PeerState.d.ts +2 -2
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js +3 -3
  10. package/dist/PeerState.js.map +1 -1
  11. package/dist/StorageReconciliationAckTracker.d.ts +14 -0
  12. package/dist/StorageReconciliationAckTracker.d.ts.map +1 -0
  13. package/dist/StorageReconciliationAckTracker.js +72 -0
  14. package/dist/StorageReconciliationAckTracker.js.map +1 -0
  15. package/dist/SyncStateManager.js +2 -2
  16. package/dist/SyncStateManager.js.map +1 -1
  17. package/dist/coValueCore/coValueCore.d.ts +2 -1
  18. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  19. package/dist/coValueCore/coValueCore.js +43 -10
  20. package/dist/coValueCore/coValueCore.js.map +1 -1
  21. package/dist/coValues/coList.d.ts +2 -0
  22. package/dist/coValues/coList.d.ts.map +1 -1
  23. package/dist/coValues/coList.js +28 -0
  24. package/dist/coValues/coList.js.map +1 -1
  25. package/dist/coValues/group.d.ts +4 -1
  26. package/dist/coValues/group.d.ts.map +1 -1
  27. package/dist/coValues/group.js +15 -1
  28. package/dist/coValues/group.js.map +1 -1
  29. package/dist/config.d.ts +8 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +14 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/exports.d.ts +9 -1
  34. package/dist/exports.d.ts.map +1 -1
  35. package/dist/exports.js +5 -1
  36. package/dist/exports.js.map +1 -1
  37. package/dist/localNode.d.ts +7 -3
  38. package/dist/localNode.d.ts.map +1 -1
  39. package/dist/localNode.js +13 -5
  40. package/dist/localNode.js.map +1 -1
  41. package/dist/permissions.d.ts +1 -0
  42. package/dist/permissions.d.ts.map +1 -1
  43. package/dist/queue/LinkedList.d.ts +2 -0
  44. package/dist/queue/LinkedList.d.ts.map +1 -1
  45. package/dist/queue/LinkedList.js +7 -0
  46. package/dist/queue/LinkedList.js.map +1 -1
  47. package/dist/queue/OutgoingLoadQueue.d.ts +4 -1
  48. package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -1
  49. package/dist/queue/OutgoingLoadQueue.js +41 -13
  50. package/dist/queue/OutgoingLoadQueue.js.map +1 -1
  51. package/dist/queue/PriorityBasedMessageQueue.d.ts +1 -0
  52. package/dist/queue/PriorityBasedMessageQueue.d.ts.map +1 -1
  53. package/dist/queue/PriorityBasedMessageQueue.js +11 -1
  54. package/dist/queue/PriorityBasedMessageQueue.js.map +1 -1
  55. package/dist/storage/knownState.d.ts +2 -0
  56. package/dist/storage/knownState.d.ts.map +1 -1
  57. package/dist/storage/knownState.js +11 -0
  58. package/dist/storage/knownState.js.map +1 -1
  59. package/dist/storage/sqlite/client.d.ts +10 -1
  60. package/dist/storage/sqlite/client.d.ts.map +1 -1
  61. package/dist/storage/sqlite/client.js +84 -0
  62. package/dist/storage/sqlite/client.js.map +1 -1
  63. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
  64. package/dist/storage/sqlite/sqliteMigrations.js +11 -0
  65. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
  66. package/dist/storage/sqliteAsync/client.d.ts +10 -1
  67. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  68. package/dist/storage/sqliteAsync/client.js +86 -0
  69. package/dist/storage/sqliteAsync/client.js.map +1 -1
  70. package/dist/storage/storageAsync.d.ts +9 -2
  71. package/dist/storage/storageAsync.d.ts.map +1 -1
  72. package/dist/storage/storageAsync.js +19 -0
  73. package/dist/storage/storageAsync.js.map +1 -1
  74. package/dist/storage/storageSync.d.ts +9 -2
  75. package/dist/storage/storageSync.d.ts.map +1 -1
  76. package/dist/storage/storageSync.js +20 -13
  77. package/dist/storage/storageSync.js.map +1 -1
  78. package/dist/storage/types.d.ts +64 -0
  79. package/dist/storage/types.d.ts.map +1 -1
  80. package/dist/storage/types.js.map +1 -1
  81. package/dist/sync.d.ts +53 -2
  82. package/dist/sync.d.ts.map +1 -1
  83. package/dist/sync.js +300 -44
  84. package/dist/sync.js.map +1 -1
  85. package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts +2 -0
  86. package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts.map +1 -0
  87. package/dist/tests/OngoingStorageReconciliationTracker.test.js +60 -0
  88. package/dist/tests/OngoingStorageReconciliationTracker.test.js.map +1 -0
  89. package/dist/tests/OutgoingLoadQueue.test.js +137 -39
  90. package/dist/tests/OutgoingLoadQueue.test.js.map +1 -1
  91. package/dist/tests/SQLiteClientAsync.test.js +1 -1
  92. package/dist/tests/SQLiteClientAsync.test.js.map +1 -1
  93. package/dist/tests/StorageApiAsync.test.js +138 -0
  94. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  95. package/dist/tests/StorageApiSync.test.js +154 -0
  96. package/dist/tests/StorageApiSync.test.js.map +1 -1
  97. package/dist/tests/StorageReconciliationAckTracker.test.d.ts +2 -0
  98. package/dist/tests/StorageReconciliationAckTracker.test.d.ts.map +1 -0
  99. package/dist/tests/StorageReconciliationAckTracker.test.js +74 -0
  100. package/dist/tests/StorageReconciliationAckTracker.test.js.map +1 -0
  101. package/dist/tests/SyncStateManager.test.js +18 -0
  102. package/dist/tests/SyncStateManager.test.js.map +1 -1
  103. package/dist/tests/coList.test.js +112 -1
  104. package/dist/tests/coList.test.js.map +1 -1
  105. package/dist/tests/coValueCore.loadFromStorage.test.js +36 -0
  106. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  107. package/dist/tests/group.test.js +44 -0
  108. package/dist/tests/group.test.js.map +1 -1
  109. package/dist/tests/knownState.lazyLoading.test.js +6 -0
  110. package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
  111. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  112. package/dist/tests/messagesTestUtils.js +4 -0
  113. package/dist/tests/messagesTestUtils.js.map +1 -1
  114. package/dist/tests/sync.concurrentLoad.test.js +333 -1
  115. package/dist/tests/sync.concurrentLoad.test.js.map +1 -1
  116. package/dist/tests/sync.garbageCollection.test.js +4 -0
  117. package/dist/tests/sync.garbageCollection.test.js.map +1 -1
  118. package/dist/tests/sync.load.test.js +19 -0
  119. package/dist/tests/sync.load.test.js.map +1 -1
  120. package/dist/tests/sync.mesh.test.js +1 -0
  121. package/dist/tests/sync.mesh.test.js.map +1 -1
  122. package/dist/tests/sync.multipleServers.test.js +41 -3
  123. package/dist/tests/sync.multipleServers.test.js.map +1 -1
  124. package/dist/tests/sync.storage.test.js +2 -0
  125. package/dist/tests/sync.storage.test.js.map +1 -1
  126. package/dist/tests/sync.storageAsync.test.js +1 -0
  127. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  128. package/dist/tests/sync.storageReconciliation.test.d.ts +2 -0
  129. package/dist/tests/sync.storageReconciliation.test.d.ts.map +1 -0
  130. package/dist/tests/sync.storageReconciliation.test.js +502 -0
  131. package/dist/tests/sync.storageReconciliation.test.js.map +1 -0
  132. package/dist/tests/testUtils.d.ts +1 -0
  133. package/dist/tests/testUtils.d.ts.map +1 -1
  134. package/dist/tests/testUtils.js +3 -2
  135. package/dist/tests/testUtils.js.map +1 -1
  136. package/package.json +4 -4
  137. package/src/OngoingStorageReconciliationTracker.ts +97 -0
  138. package/src/PeerState.ts +10 -3
  139. package/src/StorageReconciliationAckTracker.ts +83 -0
  140. package/src/SyncStateManager.ts +3 -3
  141. package/src/coValueCore/coValueCore.ts +47 -16
  142. package/src/coValues/coList.ts +23 -0
  143. package/src/coValues/group.ts +18 -0
  144. package/src/config.ts +18 -0
  145. package/src/exports.ts +8 -0
  146. package/src/localNode.ts +18 -0
  147. package/src/permissions.ts +1 -1
  148. package/src/queue/LinkedList.ts +10 -0
  149. package/src/queue/OutgoingLoadQueue.ts +57 -15
  150. package/src/queue/PriorityBasedMessageQueue.ts +15 -1
  151. package/src/storage/knownState.ts +14 -0
  152. package/src/storage/sqlite/client.ts +128 -0
  153. package/src/storage/sqlite/sqliteMigrations.ts +11 -0
  154. package/src/storage/sqliteAsync/client.ts +139 -0
  155. package/src/storage/storageAsync.ts +37 -0
  156. package/src/storage/storageSync.ts +41 -16
  157. package/src/storage/types.ts +110 -0
  158. package/src/sync.ts +359 -14
  159. package/src/tests/OngoingStorageReconciliationTracker.test.ts +85 -0
  160. package/src/tests/OutgoingLoadQueue.test.ts +226 -59
  161. package/src/tests/SQLiteClientAsync.test.ts +1 -1
  162. package/src/tests/StorageApiAsync.test.ts +161 -1
  163. package/src/tests/StorageApiSync.test.ts +176 -0
  164. package/src/tests/StorageReconciliationAckTracker.test.ts +99 -0
  165. package/src/tests/SyncStateManager.test.ts +25 -0
  166. package/src/tests/coList.test.ts +138 -0
  167. package/src/tests/coValueCore.loadFromStorage.test.ts +72 -1
  168. package/src/tests/group.test.ts +87 -0
  169. package/src/tests/knownState.lazyLoading.test.ts +36 -1
  170. package/src/tests/messagesTestUtils.ts +4 -0
  171. package/src/tests/sync.concurrentLoad.test.ts +491 -0
  172. package/src/tests/sync.garbageCollection.test.ts +4 -0
  173. package/src/tests/sync.load.test.ts +26 -0
  174. package/src/tests/sync.mesh.test.ts +1 -0
  175. package/src/tests/sync.multipleServers.test.ts +60 -2
  176. package/src/tests/sync.storage.test.ts +2 -0
  177. package/src/tests/sync.storageAsync.test.ts +1 -0
  178. package/src/tests/sync.storageReconciliation.test.ts +696 -0
  179. package/src/tests/testUtils.ts +10 -1
@@ -11,13 +11,16 @@ import type {
11
11
  DBTransactionInterfaceAsync,
12
12
  SessionRow,
13
13
  SignatureAfterRow,
14
+ StorageReconciliationLockRow,
14
15
  StoredCoValueRow,
15
16
  StoredSessionRow,
16
17
  TransactionRow,
18
+ StorageReconciliationAcquireResult,
17
19
  } from "../types.js";
18
20
  import { DeletedCoValueDeletionStatus } from "../types.js";
19
21
  import type { SQLiteDatabaseDriverAsync } from "./types.js";
20
22
  import type { PeerID } from "../../sync.js";
23
+ import { STORAGE_RECONCILIATION_CONFIG } from "../../config.js";
21
24
 
22
25
  export type RawCoValueRow = {
23
26
  id: RawCoID;
@@ -153,6 +156,37 @@ export class SQLiteTransactionAsync implements DBTransactionInterfaceAsync {
153
156
  ],
154
157
  );
155
158
  }
159
+
160
+ async getStorageReconciliationLock(
161
+ key: string,
162
+ ): Promise<StorageReconciliationLockRow | undefined> {
163
+ return this.tx.get<StorageReconciliationLockRow>(
164
+ "SELECT * FROM storageReconciliationLocks WHERE key = ?",
165
+ [key],
166
+ );
167
+ }
168
+
169
+ async putStorageReconciliationLock(
170
+ entry: StorageReconciliationLockRow,
171
+ ): Promise<void> {
172
+ const {
173
+ key,
174
+ holderSessionId,
175
+ acquiredAt,
176
+ releasedAt,
177
+ lastProcessedOffset,
178
+ } = entry;
179
+ await this.tx.run(
180
+ `INSERT OR REPLACE INTO storageReconciliationLocks (key, holderSessionId, acquiredAt, releasedAt, lastProcessedOffset) VALUES (?, ?, ?, ?, ?)`,
181
+ [
182
+ key,
183
+ holderSessionId,
184
+ acquiredAt,
185
+ releasedAt ?? null,
186
+ lastProcessedOffset,
187
+ ],
188
+ );
189
+ }
156
190
  }
157
191
 
158
192
  export class SQLiteClientAsync implements DBClientInterfaceAsync {
@@ -334,6 +368,111 @@ export class SQLiteClientAsync implements DBClientInterfaceAsync {
334
368
  ]);
335
369
  }
336
370
 
371
+ async getCoValueIDs(
372
+ limit: number,
373
+ offset: number,
374
+ ): Promise<{ id: RawCoID }[]> {
375
+ return this.db.query<{ id: RawCoID }>(
376
+ "SELECT id FROM coValues WHERE rowID > ? ORDER BY rowID LIMIT ?",
377
+ [offset, limit],
378
+ );
379
+ }
380
+
381
+ async getCoValueCount(): Promise<number> {
382
+ const row = await this.db.get<{ count: number }>(
383
+ "SELECT COUNT(*) as count FROM coValues",
384
+ [],
385
+ );
386
+ return row?.count ?? 0;
387
+ }
388
+
389
+ async tryAcquireStorageReconciliationLock(
390
+ sessionId: SessionID,
391
+ peerId: PeerID,
392
+ ): Promise<StorageReconciliationAcquireResult> {
393
+ let result: StorageReconciliationAcquireResult = {
394
+ acquired: false,
395
+ reason: "not_due",
396
+ };
397
+ await this.transaction(async (tx) => {
398
+ const now = Date.now();
399
+ const lockKey = `lock#${peerId}`;
400
+
401
+ const lockRow = await tx.getStorageReconciliationLock(lockKey);
402
+ if (
403
+ lockRow?.releasedAt &&
404
+ now - lockRow.releasedAt <
405
+ STORAGE_RECONCILIATION_CONFIG.RECONCILIATION_INTERVAL_MS
406
+ ) {
407
+ result = { acquired: false, reason: "not_due" };
408
+ return;
409
+ }
410
+ const expiresAt = lockRow
411
+ ? lockRow.acquiredAt + STORAGE_RECONCILIATION_CONFIG.LOCK_TTL_MS
412
+ : 0;
413
+ const isLockHeldByOtherSession = lockRow?.holderSessionId !== sessionId;
414
+ if (
415
+ lockRow &&
416
+ !lockRow.releasedAt &&
417
+ expiresAt >= now &&
418
+ isLockHeldByOtherSession
419
+ ) {
420
+ result = { acquired: false, reason: "lock_held" };
421
+ return;
422
+ }
423
+
424
+ const lastProcessedOffset =
425
+ lockRow && !lockRow.releasedAt ? (lockRow.lastProcessedOffset ?? 0) : 0;
426
+ await tx.putStorageReconciliationLock({
427
+ key: lockKey,
428
+ holderSessionId: sessionId,
429
+ acquiredAt: now,
430
+ lastProcessedOffset,
431
+ });
432
+ result = { acquired: true, lastProcessedOffset };
433
+ });
434
+ return result;
435
+ }
436
+
437
+ async renewStorageReconciliationLock(
438
+ sessionId: SessionID,
439
+ peerId: PeerID,
440
+ offset: number,
441
+ ): Promise<void> {
442
+ await this.transaction(async (tx) => {
443
+ const lockKey = `lock#${peerId}`;
444
+ const lockRow = await tx.getStorageReconciliationLock(lockKey);
445
+ if (
446
+ lockRow &&
447
+ lockRow.holderSessionId === sessionId &&
448
+ !lockRow.releasedAt
449
+ ) {
450
+ await tx.putStorageReconciliationLock({
451
+ ...lockRow,
452
+ lastProcessedOffset: offset,
453
+ });
454
+ }
455
+ });
456
+ }
457
+
458
+ async releaseStorageReconciliationLock(
459
+ sessionId: SessionID,
460
+ peerId: PeerID,
461
+ ): Promise<void> {
462
+ await this.transaction(async (tx) => {
463
+ const lockKey = `lock#${peerId}`;
464
+ const releasedAt = Date.now();
465
+ const lockRow = await tx.getStorageReconciliationLock(lockKey);
466
+ if (lockRow && lockRow.holderSessionId === sessionId) {
467
+ await tx.putStorageReconciliationLock({
468
+ ...lockRow,
469
+ releasedAt,
470
+ lastProcessedOffset: 0,
471
+ });
472
+ }
473
+ });
474
+ }
475
+
337
476
  async getCoValueKnownState(
338
477
  coValueId: string,
339
478
  ): Promise<CoValueKnownState | undefined> {
@@ -30,6 +30,7 @@ import type {
30
30
  SignatureAfterRow,
31
31
  StoredCoValueRow,
32
32
  StoredSessionRow,
33
+ StorageReconciliationAcquireResult,
33
34
  } from "./types.js";
34
35
  import { isDeleteSessionID } from "../ids.js";
35
36
 
@@ -517,6 +518,40 @@ export class StorageApiAsync implements StorageAPI {
517
518
  this.dbClient.trackCoValuesSyncState(updates).then(() => done?.());
518
519
  }
519
520
 
521
+ getCoValueIDs(
522
+ limit: number,
523
+ offset: number,
524
+ callback: (batch: { id: RawCoID }[]) => void,
525
+ ): void {
526
+ this.dbClient.getCoValueIDs(limit, offset).then(callback);
527
+ }
528
+
529
+ getCoValueCount(callback: (count: number) => void): void {
530
+ this.dbClient.getCoValueCount().then(callback);
531
+ }
532
+
533
+ tryAcquireStorageReconciliationLock(
534
+ sessionId: SessionID,
535
+ peerId: PeerID,
536
+ callback: (result: StorageReconciliationAcquireResult) => void,
537
+ ): void {
538
+ this.dbClient
539
+ .tryAcquireStorageReconciliationLock(sessionId, peerId)
540
+ .then(callback);
541
+ }
542
+
543
+ renewStorageReconciliationLock(
544
+ sessionId: SessionID,
545
+ peerId: PeerID,
546
+ offset: number,
547
+ ): void {
548
+ this.dbClient.renewStorageReconciliationLock(sessionId, peerId, offset);
549
+ }
550
+
551
+ releaseStorageReconciliationLock(sessionId: SessionID, peerId: PeerID): void {
552
+ this.dbClient.releaseStorageReconciliationLock(sessionId, peerId);
553
+ }
554
+
520
555
  getUnsyncedCoValueIDs(
521
556
  callback: (unsyncedCoValueIDs: RawCoID[]) => void,
522
557
  ): void {
@@ -529,11 +564,13 @@ export class StorageApiAsync implements StorageAPI {
529
564
 
530
565
  onCoValueUnmounted(id: RawCoID): void {
531
566
  this.inMemoryCoValues.delete(id);
567
+ this.knownStates.deleteKnownState(id);
532
568
  }
533
569
 
534
570
  close() {
535
571
  this.deletedCoValuesEraserScheduler?.dispose();
536
572
  this.inMemoryCoValues.clear();
573
+ this.knownStates.clear();
537
574
  return this.storeQueue.close();
538
575
  }
539
576
  }
@@ -29,6 +29,7 @@ import type {
29
29
  SignatureAfterRow,
30
30
  StoredCoValueRow,
31
31
  StoredSessionRow,
32
+ StorageReconciliationAcquireResult,
32
33
  } from "./types.js";
33
34
  import { DeletedCoValuesEraserScheduler } from "./DeletedCoValuesEraserScheduler.js";
34
35
  import {
@@ -68,26 +69,48 @@ export class StorageApiSync implements StorageAPI {
68
69
  return this.knownStates.getKnownState(id);
69
70
  }
70
71
 
71
- loadKnownState(
72
- id: string,
73
- callback: (knownState: CoValueKnownState | undefined) => void,
72
+ getCoValueIDs(
73
+ limit: number,
74
+ offset: number,
75
+ callback: (batch: { id: RawCoID }[]) => void,
74
76
  ): void {
75
- // Check in-memory cache first
76
- const cached = this.knownStates.getCachedKnownState(id);
77
- if (cached) {
78
- callback(cached);
79
- return;
80
- }
77
+ const batch = this.dbClient.getCoValueIDs(limit, offset);
78
+ callback(batch);
79
+ }
81
80
 
82
- // Load from database
83
- const knownState = this.dbClient.getCoValueKnownState(id);
81
+ getCoValueCount(callback: (count: number) => void): void {
82
+ callback(this.dbClient.getCoValueCount());
83
+ }
84
84
 
85
- if (knownState) {
86
- // Cache for future use
87
- this.knownStates.setKnownState(id, knownState);
88
- }
85
+ tryAcquireStorageReconciliationLock(
86
+ sessionId: SessionID,
87
+ peerId: PeerID,
88
+ callback: (result: StorageReconciliationAcquireResult) => void,
89
+ ): void {
90
+ const result = this.dbClient.tryAcquireStorageReconciliationLock(
91
+ sessionId,
92
+ peerId,
93
+ );
94
+ callback(result);
95
+ }
96
+
97
+ renewStorageReconciliationLock(
98
+ sessionId: SessionID,
99
+ peerId: PeerID,
100
+ offset: number,
101
+ ): void {
102
+ this.dbClient.renewStorageReconciliationLock(sessionId, peerId, offset);
103
+ }
104
+
105
+ releaseStorageReconciliationLock(sessionId: SessionID, peerId: PeerID): void {
106
+ this.dbClient.releaseStorageReconciliationLock(sessionId, peerId);
107
+ }
89
108
 
90
- callback(knownState);
109
+ loadKnownState(
110
+ id: string,
111
+ callback: (knownState: CoValueKnownState | undefined) => void,
112
+ ): void {
113
+ callback(this.dbClient.getCoValueKnownState(id));
91
114
  }
92
115
 
93
116
  async load(
@@ -515,11 +538,13 @@ export class StorageApiSync implements StorageAPI {
515
538
 
516
539
  onCoValueUnmounted(id: RawCoID): void {
517
540
  this.inMemoryCoValues.delete(id);
541
+ this.knownStates.deleteKnownState(id);
518
542
  }
519
543
 
520
544
  close() {
521
545
  this.deletedCoValuesEraserScheduler?.dispose();
522
546
  this.inMemoryCoValues.clear();
547
+ this.knownStates.clear();
523
548
  return undefined;
524
549
  }
525
550
  }
@@ -13,6 +13,10 @@ export type CorrectionCallback = (
13
13
  correction: CoValueKnownState,
14
14
  ) => NewContentMessage[] | undefined;
15
15
 
16
+ export type StorageReconciliationAcquireResult =
17
+ | { acquired: true; lastProcessedOffset: number }
18
+ | { acquired: false; reason: "not_due" | "lock_held" };
19
+
16
20
  /**
17
21
  * Deletion work queue status for `deletedCoValues` (SQLite).
18
22
  *
@@ -86,6 +90,52 @@ export interface StorageAPI {
86
90
  */
87
91
  stopTrackingSyncState(id: RawCoID): void;
88
92
 
93
+ /**
94
+ * Get a batch of CoValue IDs from storage.
95
+ * Used for full storage reconciliation. Call repeatedly with increasing offset
96
+ * until the returned batch has length < limit (or 0) to enumerate all IDs.
97
+ * @param limit - Max number of IDs to return (e.g. 100).
98
+ * @param offset - Number of IDs to skip (0 for first batch).
99
+ * @param callback - Called with the batch. Ordering must be stable (e.g. by id).
100
+ */
101
+ getCoValueIDs(
102
+ limit: number,
103
+ offset: number,
104
+ callback: (batch: { id: RawCoID }[]) => void,
105
+ ): void;
106
+
107
+ /**
108
+ * Get the total number of CoValues in storage.
109
+ */
110
+ getCoValueCount(callback: (count: number) => void): void;
111
+
112
+ /**
113
+ * Try to acquire the storage reconciliation lock for a given peer.
114
+ * Atomically checks if reconciliation is due for this peer (lastRun older than 30 days or missing)
115
+ * and if no other process/tab holds the lock for this peer, then acquires it.
116
+ */
117
+ tryAcquireStorageReconciliationLock(
118
+ sessionId: SessionID,
119
+ peerId: PeerID,
120
+ callback: (result: StorageReconciliationAcquireResult) => void,
121
+ ): void;
122
+
123
+ /**
124
+ * Update the last processed offset for the storage reconciliation lock held for this peer.
125
+ * Only call after a batch has been acked; used to resume from this offset on interrupt.
126
+ */
127
+ renewStorageReconciliationLock(
128
+ sessionId: SessionID,
129
+ peerId: PeerID,
130
+ offset: number,
131
+ ): void;
132
+
133
+ /**
134
+ * Release the storage reconciliation lock for a peer and record completion. Only call on successful completion.
135
+ * On failure/interrupt, do not call; the lock expires after LOCK_TTL_MS and another process can retry for this peer.
136
+ */
137
+ releaseStorageReconciliationLock(sessionId: SessionID, peerId: PeerID): void;
138
+
89
139
  /**
90
140
  * Load only the knownState (header presence + session counters) for a CoValue.
91
141
  * This is more efficient than load() when we only need to check if a peer needs new content.
@@ -136,6 +186,15 @@ export type SignatureAfterRow = {
136
186
  signature: Signature;
137
187
  };
138
188
 
189
+ export type StorageReconciliationLockRow = {
190
+ key: string;
191
+ holderSessionId: SessionID;
192
+ acquiredAt: number;
193
+ releasedAt?: number;
194
+ /** Offset up to which all batches have been acked; used to resume after interrupt. */
195
+ lastProcessedOffset: number;
196
+ };
197
+
139
198
  export interface DBTransactionInterfaceAsync {
140
199
  getSingleCoValueSession(
141
200
  coValueRowId: number,
@@ -176,6 +235,14 @@ export interface DBTransactionInterfaceAsync {
176
235
  deleteCoValueContent(
177
236
  coValueRow: Pick<StoredCoValueRow, "rowID" | "id">,
178
237
  ): Promise<unknown>;
238
+
239
+ getStorageReconciliationLock(
240
+ key: string,
241
+ ): Promise<StorageReconciliationLockRow | undefined>;
242
+
243
+ putStorageReconciliationLock(
244
+ entry: StorageReconciliationLockRow,
245
+ ): Promise<void>;
179
246
  }
180
247
 
181
248
  export interface DBClientInterfaceAsync {
@@ -232,6 +299,26 @@ export interface DBClientInterfaceAsync {
232
299
  getCoValueKnownState(
233
300
  coValueId: string,
234
301
  ): Promise<CoValueKnownState | undefined>;
302
+
303
+ getCoValueIDs(limit: number, offset: number): Promise<{ id: RawCoID }[]>;
304
+
305
+ getCoValueCount(): Promise<number>;
306
+
307
+ tryAcquireStorageReconciliationLock(
308
+ sessionId: SessionID,
309
+ peerId: PeerID,
310
+ ): Promise<StorageReconciliationAcquireResult>;
311
+
312
+ renewStorageReconciliationLock(
313
+ sessionId: SessionID,
314
+ peerId: PeerID,
315
+ offset: number,
316
+ ): Promise<void>;
317
+
318
+ releaseStorageReconciliationLock(
319
+ sessionId: SessionID,
320
+ peerId: PeerID,
321
+ ): Promise<void>;
235
322
  }
236
323
 
237
324
  export interface DBTransactionInterfaceSync {
@@ -270,6 +357,12 @@ export interface DBTransactionInterfaceSync {
270
357
  idx: number;
271
358
  signature: Signature;
272
359
  }): number | undefined | unknown;
360
+
361
+ getStorageReconciliationLock(
362
+ key: string,
363
+ ): StorageReconciliationLockRow | undefined;
364
+
365
+ putStorageReconciliationLock(entry: StorageReconciliationLockRow): void;
273
366
  }
274
367
 
275
368
  export interface DBClientInterfaceSync {
@@ -317,4 +410,21 @@ export interface DBClientInterfaceSync {
317
410
  * Returns undefined if the CoValue doesn't exist.
318
411
  */
319
412
  getCoValueKnownState(coValueId: string): CoValueKnownState | undefined;
413
+
414
+ getCoValueIDs(limit: number, offset: number): { id: RawCoID }[];
415
+
416
+ getCoValueCount(): number;
417
+
418
+ tryAcquireStorageReconciliationLock(
419
+ sessionId: SessionID,
420
+ peerId: PeerID,
421
+ ): StorageReconciliationAcquireResult;
422
+
423
+ renewStorageReconciliationLock(
424
+ sessionId: SessionID,
425
+ peerId: PeerID,
426
+ offset: number,
427
+ ): void;
428
+
429
+ releaseStorageReconciliationLock(sessionId: SessionID, peerId: PeerID): void;
320
430
  }