cojson 0.16.3 → 0.16.5

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 (138) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/dist/coValue.d.ts +1 -1
  4. package/dist/coValue.d.ts.map +1 -1
  5. package/dist/coValue.js.map +1 -1
  6. package/dist/coValueContentMessage.d.ts +10 -0
  7. package/dist/coValueContentMessage.d.ts.map +1 -0
  8. package/dist/coValueContentMessage.js +46 -0
  9. package/dist/coValueContentMessage.js.map +1 -0
  10. package/dist/coValueCore/coValueCore.d.ts +6 -10
  11. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  12. package/dist/coValueCore/coValueCore.js +20 -125
  13. package/dist/coValueCore/coValueCore.js.map +1 -1
  14. package/dist/coValueCore/verifiedState.d.ts +1 -0
  15. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  16. package/dist/coValueCore/verifiedState.js +14 -27
  17. package/dist/coValueCore/verifiedState.js.map +1 -1
  18. package/dist/coValues/group.d.ts +18 -10
  19. package/dist/coValues/group.d.ts.map +1 -1
  20. package/dist/coValues/group.js +237 -67
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/ids.d.ts +3 -3
  23. package/dist/ids.d.ts.map +1 -1
  24. package/dist/ids.js.map +1 -1
  25. package/dist/localNode.d.ts +11 -6
  26. package/dist/localNode.d.ts.map +1 -1
  27. package/dist/localNode.js +7 -2
  28. package/dist/localNode.js.map +1 -1
  29. package/dist/queue/LocalTransactionsSyncQueue.d.ts +24 -0
  30. package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -0
  31. package/dist/queue/LocalTransactionsSyncQueue.js +55 -0
  32. package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -0
  33. package/dist/queue/StoreQueue.d.ts +9 -6
  34. package/dist/queue/StoreQueue.d.ts.map +1 -1
  35. package/dist/queue/StoreQueue.js +10 -2
  36. package/dist/queue/StoreQueue.js.map +1 -1
  37. package/dist/storage/storageAsync.d.ts +11 -3
  38. package/dist/storage/storageAsync.d.ts.map +1 -1
  39. package/dist/storage/storageAsync.js +59 -46
  40. package/dist/storage/storageAsync.js.map +1 -1
  41. package/dist/storage/storageSync.d.ts +9 -3
  42. package/dist/storage/storageSync.d.ts.map +1 -1
  43. package/dist/storage/storageSync.js +48 -35
  44. package/dist/storage/storageSync.js.map +1 -1
  45. package/dist/storage/syncUtils.d.ts +2 -1
  46. package/dist/storage/syncUtils.d.ts.map +1 -1
  47. package/dist/storage/syncUtils.js +4 -0
  48. package/dist/storage/syncUtils.js.map +1 -1
  49. package/dist/storage/types.d.ts +3 -2
  50. package/dist/storage/types.d.ts.map +1 -1
  51. package/dist/sync.d.ts +6 -6
  52. package/dist/sync.d.ts.map +1 -1
  53. package/dist/sync.js +33 -56
  54. package/dist/sync.js.map +1 -1
  55. package/dist/tests/StorageApiAsync.test.d.ts +2 -0
  56. package/dist/tests/StorageApiAsync.test.d.ts.map +1 -0
  57. package/dist/tests/StorageApiAsync.test.js +574 -0
  58. package/dist/tests/StorageApiAsync.test.js.map +1 -0
  59. package/dist/tests/StorageApiSync.test.d.ts +2 -0
  60. package/dist/tests/StorageApiSync.test.d.ts.map +1 -0
  61. package/dist/tests/StorageApiSync.test.js +426 -0
  62. package/dist/tests/StorageApiSync.test.js.map +1 -0
  63. package/dist/tests/StoreQueue.test.js +9 -21
  64. package/dist/tests/StoreQueue.test.js.map +1 -1
  65. package/dist/tests/SyncStateManager.test.js +18 -8
  66. package/dist/tests/SyncStateManager.test.js.map +1 -1
  67. package/dist/tests/group.inheritance.test.js +274 -2
  68. package/dist/tests/group.inheritance.test.js.map +1 -1
  69. package/dist/tests/group.removeMember.test.js +152 -1
  70. package/dist/tests/group.removeMember.test.js.map +1 -1
  71. package/dist/tests/group.roleOf.test.js +2 -2
  72. package/dist/tests/group.roleOf.test.js.map +1 -1
  73. package/dist/tests/group.test.js +81 -3
  74. package/dist/tests/group.test.js.map +1 -1
  75. package/dist/tests/sync.auth.test.js +22 -10
  76. package/dist/tests/sync.auth.test.js.map +1 -1
  77. package/dist/tests/sync.load.test.js +30 -25
  78. package/dist/tests/sync.load.test.js.map +1 -1
  79. package/dist/tests/sync.mesh.test.js +12 -6
  80. package/dist/tests/sync.mesh.test.js.map +1 -1
  81. package/dist/tests/sync.peerReconciliation.test.js +6 -4
  82. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  83. package/dist/tests/sync.storage.test.js +8 -14
  84. package/dist/tests/sync.storage.test.js.map +1 -1
  85. package/dist/tests/sync.storageAsync.test.js +31 -14
  86. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  87. package/dist/tests/sync.test.js +5 -9
  88. package/dist/tests/sync.test.js.map +1 -1
  89. package/dist/tests/sync.upload.test.js +31 -1
  90. package/dist/tests/sync.upload.test.js.map +1 -1
  91. package/dist/tests/testStorage.d.ts +2 -3
  92. package/dist/tests/testStorage.d.ts.map +1 -1
  93. package/dist/tests/testStorage.js +16 -8
  94. package/dist/tests/testStorage.js.map +1 -1
  95. package/dist/tests/testUtils.d.ts +4 -0
  96. package/dist/tests/testUtils.d.ts.map +1 -1
  97. package/dist/tests/testUtils.js +22 -4
  98. package/dist/tests/testUtils.js.map +1 -1
  99. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts +2 -2
  100. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts.map +1 -1
  101. package/dist/typeUtils/expectGroup.d.ts.map +1 -1
  102. package/dist/typeUtils/expectGroup.js +6 -5
  103. package/dist/typeUtils/expectGroup.js.map +1 -1
  104. package/package.json +1 -1
  105. package/src/coValue.ts +1 -4
  106. package/src/coValueContentMessage.ts +73 -0
  107. package/src/coValueCore/coValueCore.ts +36 -192
  108. package/src/coValueCore/verifiedState.ts +28 -35
  109. package/src/coValues/group.ts +329 -99
  110. package/src/ids.ts +3 -3
  111. package/src/localNode.ts +15 -10
  112. package/src/queue/LocalTransactionsSyncQueue.ts +96 -0
  113. package/src/queue/StoreQueue.ts +22 -12
  114. package/src/storage/storageAsync.ts +78 -56
  115. package/src/storage/storageSync.ts +66 -45
  116. package/src/storage/syncUtils.ts +9 -1
  117. package/src/storage/types.ts +6 -5
  118. package/src/sync.ts +47 -67
  119. package/src/tests/StorageApiAsync.test.ts +829 -0
  120. package/src/tests/StorageApiSync.test.ts +628 -0
  121. package/src/tests/StoreQueue.test.ts +10 -24
  122. package/src/tests/SyncStateManager.test.ts +22 -21
  123. package/src/tests/group.inheritance.test.ts +415 -1
  124. package/src/tests/group.removeMember.test.ts +244 -1
  125. package/src/tests/group.roleOf.test.ts +2 -2
  126. package/src/tests/group.test.ts +105 -5
  127. package/src/tests/sync.auth.test.ts +22 -10
  128. package/src/tests/sync.load.test.ts +32 -26
  129. package/src/tests/sync.mesh.test.ts +12 -6
  130. package/src/tests/sync.peerReconciliation.test.ts +6 -4
  131. package/src/tests/sync.storage.test.ts +8 -14
  132. package/src/tests/sync.storageAsync.test.ts +39 -14
  133. package/src/tests/sync.test.ts +6 -14
  134. package/src/tests/sync.upload.test.ts +38 -1
  135. package/src/tests/testStorage.ts +19 -13
  136. package/src/tests/testUtils.ts +29 -5
  137. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -2
  138. package/src/typeUtils/expectGroup.ts +8 -5
@@ -0,0 +1,96 @@
1
+ import {
2
+ addTransactionToContentMessage,
3
+ createContentMessage,
4
+ } from "../coValueContentMessage.js";
5
+ import { Transaction, VerifiedState } from "../coValueCore/verifiedState.js";
6
+ import { Signature } from "../crypto/crypto.js";
7
+ import { SessionID } from "../ids.js";
8
+ import { NewContentMessage } from "../sync.js";
9
+ import { LinkedList } from "./LinkedList.js";
10
+
11
+ /**
12
+ * This queue is used to batch the sync of local transactions while preserving the order of updates between CoValues.
13
+ *
14
+ * We need to preserve the order of updates between CoValues to keep the state always consistent in case of shutdown in the middle of a sync.
15
+ *
16
+ * Examples:
17
+ * 1. When we extend a Group we need to always ensure that the parent group is persisted before persisting the extension transaction.
18
+ * 2. If we do multiple updates on the same CoMap, the updates will be batched because it's safe to do so.
19
+ */
20
+ export class LocalTransactionsSyncQueue {
21
+ private readonly queue = new LinkedList<NewContentMessage>();
22
+
23
+ constructor(private readonly sync: (content: NewContentMessage) => void) {}
24
+
25
+ syncHeader = (coValue: VerifiedState) => {
26
+ const lastPendingSync = this.queue.tail?.value;
27
+
28
+ if (lastPendingSync?.id === coValue.id) {
29
+ return;
30
+ }
31
+
32
+ this.enqueue(createContentMessage(coValue.id, coValue.header));
33
+ };
34
+
35
+ syncTransaction = (
36
+ coValue: VerifiedState,
37
+ transaction: Transaction,
38
+ sessionID: SessionID,
39
+ signature: Signature,
40
+ txIdx: number,
41
+ ) => {
42
+ const lastPendingSync = this.queue.tail?.value;
43
+ const lastSignatureIdx = coValue.getLastSignatureCheckpoint(sessionID);
44
+ const isSignatureCheckpoint =
45
+ lastSignatureIdx > -1 && lastSignatureIdx === txIdx - 1;
46
+
47
+ if (lastPendingSync?.id === coValue.id && !isSignatureCheckpoint) {
48
+ addTransactionToContentMessage(
49
+ lastPendingSync,
50
+ transaction,
51
+ sessionID,
52
+ signature,
53
+ txIdx,
54
+ );
55
+
56
+ return;
57
+ }
58
+
59
+ const content = createContentMessage(coValue.id, coValue.header, false);
60
+
61
+ addTransactionToContentMessage(
62
+ content,
63
+ transaction,
64
+ sessionID,
65
+ signature,
66
+ txIdx,
67
+ );
68
+
69
+ this.enqueue(content);
70
+ };
71
+
72
+ enqueue(content: NewContentMessage) {
73
+ this.queue.push(content);
74
+
75
+ this.processPendingSyncs();
76
+ }
77
+
78
+ private processingSyncs = false;
79
+ processPendingSyncs() {
80
+ if (this.processingSyncs) return;
81
+
82
+ this.processingSyncs = true;
83
+
84
+ queueMicrotask(() => {
85
+ while (this.queue.head) {
86
+ const content = this.queue.head.value;
87
+
88
+ this.sync(content);
89
+
90
+ this.queue.shift();
91
+ }
92
+
93
+ this.processingSyncs = false;
94
+ });
95
+ }
96
+ }
@@ -1,19 +1,22 @@
1
+ import { CorrectionCallback } from "../exports.js";
1
2
  import { logger } from "../logger.js";
2
- import { CoValueKnownState, NewContentMessage } from "../sync.js";
3
+ import { NewContentMessage } from "../sync.js";
3
4
  import { LinkedList } from "./LinkedList.js";
4
5
 
5
6
  type StoreQueueEntry = {
6
- data: NewContentMessage[];
7
- correctionCallback: (data: CoValueKnownState) => void;
7
+ data: NewContentMessage;
8
+ correctionCallback: CorrectionCallback;
8
9
  };
9
10
 
10
11
  export class StoreQueue {
11
12
  private queue = new LinkedList<StoreQueueEntry>();
13
+ closed = false;
14
+
15
+ public push(data: NewContentMessage, correctionCallback: CorrectionCallback) {
16
+ if (this.closed) {
17
+ return;
18
+ }
12
19
 
13
- public push(
14
- data: NewContentMessage[],
15
- correctionCallback: (data: CoValueKnownState) => void,
16
- ) {
17
20
  this.queue.push({ data, correctionCallback });
18
21
  }
19
22
 
@@ -22,12 +25,13 @@ export class StoreQueue {
22
25
  }
23
26
 
24
27
  processing = false;
28
+ lastCallback: Promise<unknown> | undefined;
25
29
 
26
30
  async processQueue(
27
31
  callback: (
28
- data: NewContentMessage[],
29
- correctionCallback: (data: CoValueKnownState) => void,
30
- ) => Promise<void>,
32
+ data: NewContentMessage,
33
+ correctionCallback: CorrectionCallback,
34
+ ) => Promise<unknown>,
31
35
  ) {
32
36
  if (this.processing) {
33
37
  return;
@@ -41,16 +45,22 @@ export class StoreQueue {
41
45
  const { data, correctionCallback } = entry;
42
46
 
43
47
  try {
44
- await callback(data, correctionCallback);
48
+ this.lastCallback = callback(data, correctionCallback);
49
+ await this.lastCallback;
45
50
  } catch (err) {
46
51
  logger.error("Error processing message in store queue", { err });
47
52
  }
48
53
  }
49
54
 
55
+ this.lastCallback = undefined;
50
56
  this.processing = false;
51
57
  }
52
58
 
53
- drain() {
59
+ close() {
60
+ this.closed = true;
61
+
54
62
  while (this.pull()) {}
63
+
64
+ return this.lastCallback;
55
65
  }
56
66
  }
@@ -1,11 +1,15 @@
1
+ import {
2
+ createContentMessage,
3
+ exceedsRecommendedSize,
4
+ getTransactionSize,
5
+ } from "../coValueContentMessage.js";
1
6
  import {
2
7
  type CoValueCore,
3
- MAX_RECOMMENDED_TX_SIZE,
4
8
  type RawCoID,
5
9
  type SessionID,
6
10
  type StorageAPI,
11
+ logger,
7
12
  } from "../exports.js";
8
- import { getPriorityFromHeader } from "../priority.js";
9
13
  import { StoreQueue } from "../queue/StoreQueue.js";
10
14
  import {
11
15
  CoValueKnownState,
@@ -13,8 +17,13 @@ import {
13
17
  emptyKnownState,
14
18
  } from "../sync.js";
15
19
  import { StorageKnownState } from "./knownState.js";
16
- import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
20
+ import {
21
+ collectNewTxs,
22
+ getDependedOnCoValues,
23
+ getNewTransactionsSize,
24
+ } from "./syncUtils.js";
17
25
  import type {
26
+ CorrectionCallback,
18
27
  DBClientInterfaceAsync,
19
28
  SignatureAfterRow,
20
29
  StoredCoValueRow,
@@ -82,6 +91,7 @@ export class StorageApiAsync implements StorageAPI {
82
91
  );
83
92
 
84
93
  const knownState = this.knwonStates.getKnownState(coValueRow.id);
94
+ knownState.header = true;
85
95
 
86
96
  for (const sessionRow of allCoValueSessions) {
87
97
  knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
@@ -89,13 +99,7 @@ export class StorageApiAsync implements StorageAPI {
89
99
 
90
100
  this.loadedCoValues.add(coValueRow.id);
91
101
 
92
- let contentMessage = {
93
- action: "content",
94
- id: coValueRow.id,
95
- header: coValueRow.header,
96
- new: {},
97
- priority: getPriorityFromHeader(coValueRow.header),
98
- } as NewContentMessage;
102
+ let contentMessage = createContentMessage(coValueRow.id, coValueRow.header);
99
103
 
100
104
  if (contentStreaming) {
101
105
  contentMessage.expectContentUntil = knownState["sessions"];
@@ -136,13 +140,10 @@ export class StorageApiAsync implements StorageAPI {
136
140
  contentMessage,
137
141
  callback,
138
142
  );
139
- contentMessage = {
140
- action: "content",
141
- id: coValueRow.id,
142
- header: coValueRow.header,
143
- new: {},
144
- priority: getPriorityFromHeader(coValueRow.header),
145
- } satisfies NewContentMessage;
143
+ contentMessage = createContentMessage(
144
+ coValueRow.id,
145
+ coValueRow.header,
146
+ );
146
147
  }
147
148
  }
148
149
  }
@@ -194,33 +195,64 @@ export class StorageApiAsync implements StorageAPI {
194
195
 
195
196
  storeQueue = new StoreQueue();
196
197
 
197
- async store(
198
- msgs: NewContentMessage[],
199
- correctionCallback: (data: CoValueKnownState) => void,
200
- ) {
198
+ async store(msg: NewContentMessage, correctionCallback: CorrectionCallback) {
201
199
  /**
202
200
  * The store operations must be done one by one, because we can't start a new transaction when there
203
201
  * is already a transaction open.
204
202
  */
205
- this.storeQueue.push(msgs, correctionCallback);
203
+ this.storeQueue.push(msg, correctionCallback);
206
204
 
207
205
  this.storeQueue.processQueue(async (data, correctionCallback) => {
208
- for (const msg of data) {
209
- const success = await this.storeSingle(msg, correctionCallback);
206
+ return this.storeSingle(data, correctionCallback);
207
+ });
208
+ }
210
209
 
211
- if (!success) {
212
- // Stop processing the messages for this entry, because the data is out of sync with storage
213
- // and the other transactions will be rejected anyway.
214
- break;
215
- }
210
+ /**
211
+ * This function is called when the storage lacks the information required to store the incoming content.
212
+ *
213
+ * It triggers a `correctionCallback` to ask the syncManager to provide the missing information.
214
+ *
215
+ * The correction is applied immediately, to ensure that, when applicable, the dependent content in the queue won't require additional corrections.
216
+ */
217
+ private async handleCorrection(
218
+ knownState: CoValueKnownState,
219
+ correctionCallback: CorrectionCallback,
220
+ ) {
221
+ const correction = correctionCallback(knownState);
222
+
223
+ if (!correction) {
224
+ logger.error("Correction callback returned undefined", {
225
+ knownState,
226
+ correction: correction ?? null,
227
+ });
228
+ return false;
229
+ }
230
+
231
+ for (const msg of correction) {
232
+ const success = await this.storeSingle(msg, (knownState) => {
233
+ logger.error("Double correction requested", {
234
+ msg,
235
+ knownState,
236
+ });
237
+ return undefined;
238
+ });
239
+
240
+ if (!success) {
241
+ return false;
216
242
  }
217
- });
243
+ }
244
+
245
+ return true;
218
246
  }
219
247
 
220
248
  private async storeSingle(
221
249
  msg: NewContentMessage,
222
- correctionCallback: (data: CoValueKnownState) => void,
250
+ correctionCallback: CorrectionCallback,
223
251
  ): Promise<boolean> {
252
+ if (this.storeQueue.closed) {
253
+ return false;
254
+ }
255
+
224
256
  const id = msg.id;
225
257
  const coValueRow = await this.dbClient.getCoValue(id);
226
258
 
@@ -231,8 +263,7 @@ export class StorageApiAsync implements StorageAPI {
231
263
  const knownState = emptyKnownState(id as RawCoID);
232
264
  this.knwonStates.setKnownState(id, knownState);
233
265
 
234
- correctionCallback(knownState);
235
- return false;
266
+ return this.handleCorrection(knownState, correctionCallback);
236
267
  }
237
268
 
238
269
  const storedCoValueRowID: number = coValueRow
@@ -276,8 +307,7 @@ export class StorageApiAsync implements StorageAPI {
276
307
  this.knwonStates.handleUpdate(id, knownState);
277
308
 
278
309
  if (invalidAssumptions) {
279
- correctionCallback(knownState);
280
- return false;
310
+ return this.handleCorrection(knownState, correctionCallback);
281
311
  }
282
312
 
283
313
  return true;
@@ -290,38 +320,31 @@ export class StorageApiAsync implements StorageAPI {
290
320
  storedCoValueRowID: number,
291
321
  ) {
292
322
  const newTransactions = msg.new[sessionID]?.newTransactions || [];
323
+ const lastIdx = sessionRow?.lastIdx || 0;
293
324
 
294
- const actuallyNewOffset =
295
- (sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
325
+ const actuallyNewOffset = lastIdx - (msg.new[sessionID]?.after || 0);
296
326
 
297
327
  const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
298
328
 
299
329
  if (actuallyNewTransactions.length === 0) {
300
- return sessionRow?.lastIdx || 0;
330
+ return lastIdx;
301
331
  }
302
332
 
303
- let newBytesSinceLastSignature =
304
- (sessionRow?.bytesSinceLastSignature || 0) +
305
- actuallyNewTransactions.reduce(
306
- (sum, tx) =>
307
- sum +
308
- (tx.privacy === "private"
309
- ? tx.encryptedChanges.length
310
- : tx.changes.length),
311
- 0,
312
- );
333
+ let bytesSinceLastSignature = sessionRow?.bytesSinceLastSignature || 0;
334
+ const newTransactionsSize = getNewTransactionsSize(actuallyNewTransactions);
313
335
 
314
- const newLastIdx =
315
- (sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
336
+ const newLastIdx = lastIdx + actuallyNewTransactions.length;
316
337
 
317
338
  let shouldWriteSignature = false;
318
339
 
319
- if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
340
+ if (exceedsRecommendedSize(bytesSinceLastSignature, newTransactionsSize)) {
320
341
  shouldWriteSignature = true;
321
- newBytesSinceLastSignature = 0;
342
+ bytesSinceLastSignature = 0;
343
+ } else {
344
+ bytesSinceLastSignature += newTransactionsSize;
322
345
  }
323
346
 
324
- const nextIdx = sessionRow?.lastIdx || 0;
347
+ const nextIdx = lastIdx;
325
348
 
326
349
  if (!msg.new[sessionID]) throw new Error("Session ID not found");
327
350
 
@@ -330,7 +353,7 @@ export class StorageApiAsync implements StorageAPI {
330
353
  sessionID,
331
354
  lastIdx: newLastIdx,
332
355
  lastSignature: msg.new[sessionID].lastSignature,
333
- bytesSinceLastSignature: newBytesSinceLastSignature,
356
+ bytesSinceLastSignature,
334
357
  };
335
358
 
336
359
  const sessionRowID: number = await this.dbClient.addSessionUpdate({
@@ -360,7 +383,6 @@ export class StorageApiAsync implements StorageAPI {
360
383
  }
361
384
 
362
385
  close() {
363
- // Drain the store queue
364
- this.storeQueue.drain();
386
+ return this.storeQueue.close();
365
387
  }
366
388
  }
@@ -1,20 +1,29 @@
1
1
  import { UpDownCounter, metrics } from "@opentelemetry/api";
2
+ import {
3
+ createContentMessage,
4
+ exceedsRecommendedSize,
5
+ getTransactionSize,
6
+ } from "../coValueContentMessage.js";
2
7
  import {
3
8
  CoValueCore,
4
- MAX_RECOMMENDED_TX_SIZE,
5
9
  RawCoID,
6
10
  type SessionID,
7
11
  type StorageAPI,
12
+ logger,
8
13
  } from "../exports.js";
9
- import { getPriorityFromHeader } from "../priority.js";
10
14
  import {
11
15
  CoValueKnownState,
12
16
  NewContentMessage,
13
17
  emptyKnownState,
14
18
  } from "../sync.js";
15
19
  import { StorageKnownState } from "./knownState.js";
16
- import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
20
+ import {
21
+ collectNewTxs,
22
+ getDependedOnCoValues,
23
+ getNewTransactionsSize,
24
+ } from "./syncUtils.js";
17
25
  import type {
26
+ CorrectionCallback,
18
27
  DBClientInterfaceSync,
19
28
  SignatureAfterRow,
20
29
  StoredCoValueRow,
@@ -84,6 +93,7 @@ export class StorageApiSync implements StorageAPI {
84
93
  }
85
94
 
86
95
  const knownState = this.knwonStates.getKnownState(coValueRow.id);
96
+ knownState.header = true;
87
97
 
88
98
  for (const sessionRow of allCoValueSessions) {
89
99
  knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
@@ -91,13 +101,7 @@ export class StorageApiSync implements StorageAPI {
91
101
 
92
102
  this.loadedCoValues.add(coValueRow.id);
93
103
 
94
- let contentMessage = {
95
- action: "content",
96
- id: coValueRow.id,
97
- header: coValueRow.header,
98
- new: {},
99
- priority: getPriorityFromHeader(coValueRow.header),
100
- } as NewContentMessage;
104
+ let contentMessage = createContentMessage(coValueRow.id, coValueRow.header);
101
105
 
102
106
  if (contentStreaming) {
103
107
  this.streamingCounter.add(1);
@@ -137,13 +141,10 @@ export class StorageApiSync implements StorageAPI {
137
141
  contentMessage,
138
142
  callback,
139
143
  );
140
- contentMessage = {
141
- action: "content",
142
- id: coValueRow.id,
143
- header: coValueRow.header,
144
- new: {},
145
- priority: getPriorityFromHeader(coValueRow.header),
146
- } satisfies NewContentMessage;
144
+ contentMessage = createContentMessage(
145
+ coValueRow.id,
146
+ coValueRow.header,
147
+ );
147
148
 
148
149
  // Introduce a delay to not block the main thread
149
150
  // for the entire content processing
@@ -189,22 +190,49 @@ export class StorageApiSync implements StorageAPI {
189
190
  pushCallback(contentMessage);
190
191
  }
191
192
 
192
- store(
193
- msgs: NewContentMessage[],
194
- correctionCallback: (data: CoValueKnownState) => void,
193
+ store(msg: NewContentMessage, correctionCallback: CorrectionCallback) {
194
+ return this.storeSingle(msg, correctionCallback);
195
+ }
196
+
197
+ /**
198
+ * This function is called when the storage lacks the information required to store the incoming content.
199
+ *
200
+ * It triggers a `correctionCallback` to ask the syncManager to provide the missing information.
201
+ */
202
+ private handleCorrection(
203
+ knownState: CoValueKnownState,
204
+ correctionCallback: CorrectionCallback,
195
205
  ) {
196
- for (const msg of msgs) {
197
- const success = this.storeSingle(msg, correctionCallback);
206
+ const correction = correctionCallback(knownState);
207
+
208
+ if (!correction) {
209
+ logger.error("Correction callback returned undefined", {
210
+ knownState,
211
+ correction: correction ?? null,
212
+ });
213
+ return false;
214
+ }
215
+
216
+ for (const msg of correction) {
217
+ const success = this.storeSingle(msg, (knownState) => {
218
+ logger.error("Double correction requested", {
219
+ msg,
220
+ knownState,
221
+ });
222
+ return undefined;
223
+ });
198
224
 
199
225
  if (!success) {
200
226
  return false;
201
227
  }
202
228
  }
229
+
230
+ return true;
203
231
  }
204
232
 
205
233
  private storeSingle(
206
234
  msg: NewContentMessage,
207
- correctionCallback: (data: CoValueKnownState) => void,
235
+ correctionCallback: CorrectionCallback,
208
236
  ): boolean {
209
237
  const id = msg.id;
210
238
  const coValueRow = this.dbClient.getCoValue(id);
@@ -214,11 +242,9 @@ export class StorageApiSync implements StorageAPI {
214
242
 
215
243
  if (invalidAssumptionOnHeaderPresence) {
216
244
  const knownState = emptyKnownState(id as RawCoID);
217
- correctionCallback(knownState);
218
-
219
245
  this.knwonStates.setKnownState(id, knownState);
220
246
 
221
- return false;
247
+ return this.handleCorrection(knownState, correctionCallback);
222
248
  }
223
249
 
224
250
  const storedCoValueRowID: number = coValueRow
@@ -258,8 +284,7 @@ export class StorageApiSync implements StorageAPI {
258
284
  this.knwonStates.handleUpdate(id, knownState);
259
285
 
260
286
  if (invalidAssumptions) {
261
- correctionCallback(knownState);
262
- return false;
287
+ return this.handleCorrection(knownState, correctionCallback);
263
288
  }
264
289
 
265
290
  return true;
@@ -272,35 +297,29 @@ export class StorageApiSync implements StorageAPI {
272
297
  storedCoValueRowID: number,
273
298
  ) {
274
299
  const newTransactions = msg.new[sessionID]?.newTransactions || [];
300
+ const lastIdx = sessionRow?.lastIdx || 0;
275
301
 
276
- const actuallyNewOffset =
277
- (sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
302
+ const actuallyNewOffset = lastIdx - (msg.new[sessionID]?.after || 0);
278
303
 
279
304
  const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
280
305
 
281
306
  if (actuallyNewTransactions.length === 0) {
282
- return sessionRow?.lastIdx || 0;
307
+ return lastIdx;
283
308
  }
284
309
 
285
- let newBytesSinceLastSignature =
286
- (sessionRow?.bytesSinceLastSignature || 0) +
287
- actuallyNewTransactions.reduce(
288
- (sum, tx) =>
289
- sum +
290
- (tx.privacy === "private"
291
- ? tx.encryptedChanges.length
292
- : tx.changes.length),
293
- 0,
294
- );
310
+ let bytesSinceLastSignature = sessionRow?.bytesSinceLastSignature || 0;
311
+ const newTransactionsSize = getNewTransactionsSize(actuallyNewTransactions);
295
312
 
296
313
  const newLastIdx =
297
314
  (sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
298
315
 
299
316
  let shouldWriteSignature = false;
300
317
 
301
- if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
318
+ if (exceedsRecommendedSize(bytesSinceLastSignature, newTransactionsSize)) {
302
319
  shouldWriteSignature = true;
303
- newBytesSinceLastSignature = 0;
320
+ bytesSinceLastSignature = 0;
321
+ } else {
322
+ bytesSinceLastSignature += newTransactionsSize;
304
323
  }
305
324
 
306
325
  const nextIdx = sessionRow?.lastIdx || 0;
@@ -312,7 +331,7 @@ export class StorageApiSync implements StorageAPI {
312
331
  sessionID,
313
332
  lastIdx: newLastIdx,
314
333
  lastSignature: msg.new[sessionID].lastSignature,
315
- bytesSinceLastSignature: newBytesSinceLastSignature,
334
+ bytesSinceLastSignature,
316
335
  };
317
336
 
318
337
  const sessionRowID: number = this.dbClient.addSessionUpdate({
@@ -339,5 +358,7 @@ export class StorageApiSync implements StorageAPI {
339
358
  return this.knwonStates.waitForSync(id, coValue);
340
359
  }
341
360
 
342
- close() {}
361
+ close() {
362
+ return undefined;
363
+ }
343
364
  }
@@ -1,5 +1,9 @@
1
+ import { getTransactionSize } from "../coValueContentMessage.js";
1
2
  import { getDependedOnCoValuesFromRawData } from "../coValueCore/utils.js";
2
- import type { CoValueHeader } from "../coValueCore/verifiedState.js";
3
+ import type {
4
+ CoValueHeader,
5
+ Transaction,
6
+ } from "../coValueCore/verifiedState.js";
3
7
  import type { Signature } from "../crypto/crypto.js";
4
8
  import type { SessionID } from "../exports.js";
5
9
  import type { NewContentMessage } from "../sync.js";
@@ -48,3 +52,7 @@ export function getDependedOnCoValues(
48
52
 
49
53
  return getDependedOnCoValuesFromRawData(id, header, sessionIDs, transactions);
50
54
  }
55
+
56
+ export function getNewTransactionsSize(newTxs: Transaction[]) {
57
+ return newTxs.reduce((sum, tx) => sum + getTransactionSize(tx), 0);
58
+ }
@@ -6,6 +6,10 @@ import { Signature } from "../crypto/crypto.js";
6
6
  import type { CoValueCore, RawCoID, SessionID } from "../exports.js";
7
7
  import { CoValueKnownState, NewContentMessage } from "../sync.js";
8
8
 
9
+ export type CorrectionCallback = (
10
+ correction: CoValueKnownState,
11
+ ) => NewContentMessage[] | undefined;
12
+
9
13
  /**
10
14
  * The StorageAPI is the interface that the StorageSync and StorageAsync classes implement.
11
15
  *
@@ -18,16 +22,13 @@ export interface StorageAPI {
18
22
  callback: (data: NewContentMessage) => void,
19
23
  done?: (found: boolean) => void,
20
24
  ): void;
21
- store(
22
- data: NewContentMessage[] | undefined,
23
- handleCorrection: (correction: CoValueKnownState) => void,
24
- ): void;
25
+ store(data: NewContentMessage, handleCorrection: CorrectionCallback): void;
25
26
 
26
27
  getKnownState(id: string): CoValueKnownState;
27
28
 
28
29
  waitForSync(id: string, coValue: CoValueCore): Promise<void>;
29
30
 
30
- close(): void;
31
+ close(): Promise<unknown> | undefined;
31
32
  }
32
33
 
33
34
  export type CoValueRow = {