cojson 0.15.7 → 0.15.9

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 (218) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/IncomingMessagesQueue.d.ts +27 -0
  4. package/dist/IncomingMessagesQueue.d.ts.map +1 -0
  5. package/dist/IncomingMessagesQueue.js +114 -0
  6. package/dist/IncomingMessagesQueue.js.map +1 -0
  7. package/dist/PeerState.d.ts +2 -10
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js +9 -90
  10. package/dist/PeerState.js.map +1 -1
  11. package/dist/PriorityBasedMessageQueue.d.ts +2 -1
  12. package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
  13. package/dist/PriorityBasedMessageQueue.js +9 -6
  14. package/dist/PriorityBasedMessageQueue.js.map +1 -1
  15. package/dist/SyncStateManager.d.ts +1 -0
  16. package/dist/SyncStateManager.d.ts.map +1 -1
  17. package/dist/SyncStateManager.js +1 -1
  18. package/dist/SyncStateManager.js.map +1 -1
  19. package/dist/coValue.d.ts +1 -1
  20. package/dist/coValueCore/coValueCore.d.ts +9 -17
  21. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  22. package/dist/coValueCore/coValueCore.js +75 -50
  23. package/dist/coValueCore/coValueCore.js.map +1 -1
  24. package/dist/coValueCore/verifiedState.d.ts +10 -3
  25. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  26. package/dist/coValueCore/verifiedState.js +73 -14
  27. package/dist/coValueCore/verifiedState.js.map +1 -1
  28. package/dist/coValues/coMap.d.ts +3 -3
  29. package/dist/coValues/coStream.d.ts +2 -2
  30. package/dist/coValues/group.d.ts +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +2 -4
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +19 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +23 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  39. package/dist/crypto/WasmCrypto.js +2 -1
  40. package/dist/crypto/WasmCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +18 -7
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +11 -8
  44. package/dist/exports.js.map +1 -1
  45. package/dist/localNode.d.ts +8 -2
  46. package/dist/localNode.d.ts.map +1 -1
  47. package/dist/localNode.js +19 -12
  48. package/dist/localNode.js.map +1 -1
  49. package/dist/storage/StoreQueue.d.ts +15 -0
  50. package/dist/storage/StoreQueue.d.ts.map +1 -0
  51. package/dist/storage/StoreQueue.js +35 -0
  52. package/dist/storage/StoreQueue.js.map +1 -0
  53. package/dist/storage/index.d.ts +6 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +6 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/knownState.d.ts +18 -0
  58. package/dist/storage/knownState.d.ts.map +1 -0
  59. package/dist/storage/knownState.js +63 -0
  60. package/dist/storage/knownState.js.map +1 -0
  61. package/dist/storage/sqlite/client.d.ts +37 -0
  62. package/dist/storage/sqlite/client.d.ts.map +1 -0
  63. package/dist/storage/sqlite/client.js +89 -0
  64. package/dist/storage/sqlite/client.js.map +1 -0
  65. package/dist/storage/sqlite/index.d.ts +5 -0
  66. package/dist/storage/sqlite/index.d.ts.map +1 -0
  67. package/dist/storage/sqlite/index.js +13 -0
  68. package/dist/storage/sqlite/index.js.map +1 -0
  69. package/dist/storage/sqlite/sqliteMigrations.d.ts +3 -0
  70. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -0
  71. package/dist/storage/sqlite/sqliteMigrations.js +44 -0
  72. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -0
  73. package/dist/storage/sqlite/types.d.ts +8 -0
  74. package/dist/storage/sqlite/types.d.ts.map +1 -0
  75. package/dist/storage/sqlite/types.js +2 -0
  76. package/dist/storage/sqlite/types.js.map +1 -0
  77. package/dist/storage/sqliteAsync/client.d.ts +37 -0
  78. package/dist/storage/sqliteAsync/client.d.ts.map +1 -0
  79. package/dist/storage/sqliteAsync/client.js +88 -0
  80. package/dist/storage/sqliteAsync/client.js.map +1 -0
  81. package/dist/storage/sqliteAsync/index.d.ts +6 -0
  82. package/dist/storage/sqliteAsync/index.d.ts.map +1 -0
  83. package/dist/storage/sqliteAsync/index.js +15 -0
  84. package/dist/storage/sqliteAsync/index.js.map +1 -0
  85. package/dist/storage/sqliteAsync/types.d.ts +9 -0
  86. package/dist/storage/sqliteAsync/types.d.ts.map +1 -0
  87. package/dist/storage/sqliteAsync/types.js +2 -0
  88. package/dist/storage/sqliteAsync/types.js.map +1 -0
  89. package/dist/storage/storageAsync.d.ts +22 -0
  90. package/dist/storage/storageAsync.d.ts.map +1 -0
  91. package/dist/storage/storageAsync.js +214 -0
  92. package/dist/storage/storageAsync.js.map +1 -0
  93. package/dist/storage/storageSync.d.ts +21 -0
  94. package/dist/storage/storageSync.d.ts.map +1 -0
  95. package/dist/storage/storageSync.js +206 -0
  96. package/dist/storage/storageSync.js.map +1 -0
  97. package/dist/storage/syncUtils.d.ts +13 -0
  98. package/dist/storage/syncUtils.d.ts.map +1 -0
  99. package/dist/storage/syncUtils.js +25 -0
  100. package/dist/storage/syncUtils.js.map +1 -0
  101. package/dist/storage/types.d.ts +82 -0
  102. package/dist/storage/types.d.ts.map +1 -0
  103. package/dist/storage/types.js +2 -0
  104. package/dist/storage/types.js.map +1 -0
  105. package/dist/streamUtils.d.ts +13 -9
  106. package/dist/streamUtils.d.ts.map +1 -1
  107. package/dist/streamUtils.js +46 -13
  108. package/dist/streamUtils.js.map +1 -1
  109. package/dist/sync.d.ts +22 -14
  110. package/dist/sync.d.ts.map +1 -1
  111. package/dist/sync.js +143 -125
  112. package/dist/sync.js.map +1 -1
  113. package/dist/tests/IncomingMessagesQueue.test.d.ts +2 -0
  114. package/dist/tests/IncomingMessagesQueue.test.d.ts.map +1 -0
  115. package/dist/tests/IncomingMessagesQueue.test.js +437 -0
  116. package/dist/tests/IncomingMessagesQueue.test.js.map +1 -0
  117. package/dist/tests/PeerState.test.js +6 -94
  118. package/dist/tests/PeerState.test.js.map +1 -1
  119. package/dist/tests/PriorityBasedMessageQueue.test.js +14 -14
  120. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
  121. package/dist/tests/StoreQueue.test.d.ts +2 -0
  122. package/dist/tests/StoreQueue.test.d.ts.map +1 -0
  123. package/dist/tests/StoreQueue.test.js +208 -0
  124. package/dist/tests/StoreQueue.test.js.map +1 -0
  125. package/dist/tests/SyncStateManager.test.js +3 -1
  126. package/dist/tests/SyncStateManager.test.js.map +1 -1
  127. package/dist/tests/account.test.js +9 -9
  128. package/dist/tests/account.test.js.map +1 -1
  129. package/dist/tests/coStream.test.js +1 -1
  130. package/dist/tests/coStream.test.js.map +1 -1
  131. package/dist/tests/coValueCore.test.js +208 -1
  132. package/dist/tests/coValueCore.test.js.map +1 -1
  133. package/dist/tests/coValueCoreLoadingState.test.js +2 -2
  134. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
  135. package/dist/tests/group.addMember.test.js.map +1 -1
  136. package/dist/tests/group.removeMember.test.js +1 -1
  137. package/dist/tests/group.removeMember.test.js.map +1 -1
  138. package/dist/tests/messagesTestUtils.js +1 -1
  139. package/dist/tests/messagesTestUtils.js.map +1 -1
  140. package/dist/tests/sync.auth.test.js +23 -15
  141. package/dist/tests/sync.auth.test.js.map +1 -1
  142. package/dist/tests/sync.invite.test.js +10 -16
  143. package/dist/tests/sync.invite.test.js.map +1 -1
  144. package/dist/tests/sync.load.test.js +52 -50
  145. package/dist/tests/sync.load.test.js.map +1 -1
  146. package/dist/tests/sync.mesh.test.js +173 -56
  147. package/dist/tests/sync.mesh.test.js.map +1 -1
  148. package/dist/tests/sync.peerReconciliation.test.js +42 -32
  149. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  150. package/dist/tests/sync.storage.test.js +162 -62
  151. package/dist/tests/sync.storage.test.js.map +1 -1
  152. package/dist/tests/sync.storageAsync.test.d.ts +2 -0
  153. package/dist/tests/sync.storageAsync.test.d.ts.map +1 -0
  154. package/dist/tests/sync.storageAsync.test.js +361 -0
  155. package/dist/tests/sync.storageAsync.test.js.map +1 -0
  156. package/dist/tests/sync.test.js +16 -21
  157. package/dist/tests/sync.test.js.map +1 -1
  158. package/dist/tests/sync.upload.test.js +28 -25
  159. package/dist/tests/sync.upload.test.js.map +1 -1
  160. package/dist/tests/testStorage.d.ts +12 -0
  161. package/dist/tests/testStorage.d.ts.map +1 -0
  162. package/dist/tests/testStorage.js +151 -0
  163. package/dist/tests/testStorage.js.map +1 -0
  164. package/dist/tests/testUtils.d.ts +20 -15
  165. package/dist/tests/testUtils.d.ts.map +1 -1
  166. package/dist/tests/testUtils.js +79 -45
  167. package/dist/tests/testUtils.js.map +1 -1
  168. package/package.json +2 -2
  169. package/src/IncomingMessagesQueue.ts +142 -0
  170. package/src/PeerState.ts +11 -110
  171. package/src/PriorityBasedMessageQueue.ts +13 -5
  172. package/src/SyncStateManager.ts +1 -1
  173. package/src/coValueCore/coValueCore.ts +100 -66
  174. package/src/coValueCore/verifiedState.ts +91 -21
  175. package/src/coValues/group.ts +2 -4
  176. package/src/config.ts +26 -0
  177. package/src/crypto/WasmCrypto.ts +3 -1
  178. package/src/exports.ts +20 -27
  179. package/src/localNode.ts +27 -12
  180. package/src/storage/StoreQueue.ts +56 -0
  181. package/src/storage/index.ts +5 -0
  182. package/src/storage/knownState.ts +88 -0
  183. package/src/storage/sqlite/client.ts +180 -0
  184. package/src/storage/sqlite/index.ts +19 -0
  185. package/src/storage/sqlite/sqliteMigrations.ts +44 -0
  186. package/src/storage/sqlite/types.ts +7 -0
  187. package/src/storage/sqliteAsync/client.ts +179 -0
  188. package/src/storage/sqliteAsync/index.ts +25 -0
  189. package/src/storage/sqliteAsync/types.ts +8 -0
  190. package/src/storage/storageAsync.ts +367 -0
  191. package/src/storage/storageSync.ts +343 -0
  192. package/src/storage/syncUtils.ts +50 -0
  193. package/src/storage/types.ts +162 -0
  194. package/src/streamUtils.ts +61 -19
  195. package/src/sync.ts +191 -160
  196. package/src/tests/IncomingMessagesQueue.test.ts +626 -0
  197. package/src/tests/PeerState.test.ts +6 -118
  198. package/src/tests/PriorityBasedMessageQueue.test.ts +18 -14
  199. package/src/tests/StoreQueue.test.ts +283 -0
  200. package/src/tests/SyncStateManager.test.ts +4 -1
  201. package/src/tests/account.test.ts +11 -12
  202. package/src/tests/coStream.test.ts +1 -3
  203. package/src/tests/coValueCore.test.ts +270 -1
  204. package/src/tests/coValueCoreLoadingState.test.ts +2 -2
  205. package/src/tests/group.addMember.test.ts +1 -0
  206. package/src/tests/group.removeMember.test.ts +2 -8
  207. package/src/tests/messagesTestUtils.ts +2 -2
  208. package/src/tests/sync.auth.test.ts +24 -14
  209. package/src/tests/sync.invite.test.ts +11 -17
  210. package/src/tests/sync.load.test.ts +53 -49
  211. package/src/tests/sync.mesh.test.ts +198 -56
  212. package/src/tests/sync.peerReconciliation.test.ts +44 -34
  213. package/src/tests/sync.storage.test.ts +231 -64
  214. package/src/tests/sync.storageAsync.test.ts +486 -0
  215. package/src/tests/sync.test.ts +17 -23
  216. package/src/tests/sync.upload.test.ts +29 -24
  217. package/src/tests/testStorage.ts +216 -0
  218. package/src/tests/testUtils.ts +89 -54
@@ -4,6 +4,7 @@ import { PeerState } from "../PeerState.js";
4
4
  import { RawCoValue } from "../coValue.js";
5
5
  import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
6
6
  import { RawGroup } from "../coValues/group.js";
7
+ import { CO_VALUE_LOADING_CONFIG, MAX_RECOMMENDED_TX_SIZE } from "../config.js";
7
8
  import { coreToCoValue } from "../coreToCoValue.js";
8
9
  import {
9
10
  CryptoProvider,
@@ -19,8 +20,6 @@ import {
19
20
  RawCoID,
20
21
  SessionID,
21
22
  TransactionID,
22
- getGroupDependentKey,
23
- getGroupDependentKeyList,
24
23
  getParentGroupId,
25
24
  isParentGroupReference,
26
25
  } from "../ids.js";
@@ -39,15 +38,6 @@ import { isAccountID } from "../typeUtils/isAccountID.js";
39
38
  import { getDependedOnCoValuesFromRawData } from "./utils.js";
40
39
  import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
41
40
 
42
- /**
43
- In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
44
- since they are the smallest unit of progress that can be synced within a CoValue.
45
- This is particularly important for storing binary data in CoValues, since they are likely to be at least on the order of megabytes.
46
- This also means that we want to keep signatures roughly after each MAX_RECOMMENDED_TX size chunk,
47
- to be able to verify partially loaded CoValues or CoValues that are still being created (like a video live stream).
48
- **/
49
- export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
50
-
51
41
  export function idforHeader(
52
42
  header: CoValueHeader,
53
43
  crypto: CryptoProvider,
@@ -66,12 +56,6 @@ const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
66
56
 
67
57
  export type AvailableCoValueCore = CoValueCore & { verified: VerifiedState };
68
58
 
69
- export const CO_VALUE_LOADING_CONFIG = {
70
- MAX_RETRIES: 1,
71
- TIMEOUT: 30_000,
72
- RETRY_DELAY: 3000,
73
- };
74
-
75
59
  export class CoValueCore {
76
60
  // context
77
61
  readonly node: LocalNode;
@@ -174,7 +158,11 @@ export class CoValueCore {
174
158
  }
175
159
 
176
160
  isAvailable(): this is AvailableCoValueCore {
177
- return !!this.verified && this.missingDependencies.size === 0;
161
+ return this.hasVerifiedContent() && this.missingDependencies.size === 0;
162
+ }
163
+
164
+ hasVerifiedContent(): this is AvailableCoValueCore {
165
+ return !!this.verified;
178
166
  }
179
167
 
180
168
  isErroredInPeer(peerId: PeerID) {
@@ -232,12 +220,41 @@ export class CoValueCore {
232
220
  }
233
221
 
234
222
  missingDependencies = new Set<RawCoID>();
223
+
224
+ // Checks if the current CoValueCore is already a missing dependency of the given CoValueCore
225
+ checkCircularDependencies(dependency: CoValueCore) {
226
+ const visited = new Set<RawCoID>();
227
+ const stack = [dependency];
228
+
229
+ while (stack.length > 0) {
230
+ const current = stack.pop();
231
+
232
+ if (!current) {
233
+ return true;
234
+ }
235
+
236
+ visited.add(current.id);
237
+
238
+ for (const dependency of current.missingDependencies) {
239
+ if (dependency === this.id) {
240
+ return false;
241
+ }
242
+
243
+ if (!visited.has(dependency)) {
244
+ stack.push(this.node.getCoValue(dependency));
245
+ }
246
+ }
247
+ }
248
+
249
+ return true;
250
+ }
251
+
235
252
  markMissingDependency(dependency: RawCoID) {
236
253
  const value = this.node.getCoValue(dependency);
237
254
 
238
255
  if (value.isAvailable()) {
239
256
  this.missingDependencies.delete(dependency);
240
- } else {
257
+ } else if (this.checkCircularDependencies(value)) {
241
258
  const unsubscribe = value.subscribe(() => {
242
259
  if (value.isAvailable()) {
243
260
  this.missingDependencies.delete(dependency);
@@ -253,7 +270,11 @@ export class CoValueCore {
253
270
  }
254
271
  }
255
272
 
256
- provideHeader(header: CoValueHeader, fromPeerId: PeerID) {
273
+ provideHeader(
274
+ header: CoValueHeader,
275
+ fromPeerId: PeerID,
276
+ streamingKnownState?: CoValueKnownState["sessions"],
277
+ ) {
257
278
  const previousState = this.loadingState;
258
279
 
259
280
  if (this._verified?.sessions.size) {
@@ -266,9 +287,11 @@ export class CoValueCore {
266
287
  this.node.crypto,
267
288
  header,
268
289
  new Map(),
290
+ streamingKnownState,
269
291
  );
270
292
 
271
293
  this.peers.set(fromPeerId, { type: "available" });
294
+
272
295
  this.updateCounter(previousState);
273
296
  this.notifyUpdate("immediate");
274
297
  }
@@ -292,7 +315,7 @@ export class CoValueCore {
292
315
  this.notifyUpdate("immediate");
293
316
  }
294
317
 
295
- private markPending(peerId: PeerID) {
318
+ markPending(peerId: PeerID) {
296
319
  const previousState = this.loadingState;
297
320
  this.peers.set(peerId, { type: "pending" });
298
321
  this.updateCounter(previousState);
@@ -355,6 +378,14 @@ export class CoValueCore {
355
378
  .getCurrentContent();
356
379
  }
357
380
 
381
+ knownStateWithStreaming(): CoValueKnownState {
382
+ if (this.isAvailable()) {
383
+ return this.verified.knownStateWithStreaming();
384
+ } else {
385
+ return emptyKnownState(this.id);
386
+ }
387
+ }
388
+
358
389
  knownState(): CoValueKnownState {
359
390
  if (this.isAvailable()) {
360
391
  return this.verified.knownState();
@@ -980,65 +1011,68 @@ export class CoValueCore {
980
1011
  return this.node.syncManager.waitForSync(this.id, options?.timeout);
981
1012
  }
982
1013
 
983
- async loadFromPeers(peers: PeerState[]) {
984
- if (peers.length === 0) {
1014
+ load(peers: PeerState[]) {
1015
+ this.loadFromStorage((found) => {
1016
+ // When found the load is triggered by handleNewContent
1017
+ if (!found) {
1018
+ this.loadFromPeers(peers);
1019
+ }
1020
+ });
1021
+ }
1022
+
1023
+ loadFromStorage(done?: (found: boolean) => void) {
1024
+ const node = this.node;
1025
+
1026
+ if (!node.storage) {
1027
+ done?.(false);
985
1028
  return;
986
1029
  }
987
1030
 
988
- const peersToActuallyLoadFrom = {
989
- storage: [] as PeerState[],
990
- server: [] as PeerState[],
991
- };
1031
+ const currentState = this.peers.get("storage");
992
1032
 
993
- for (const peer of peers) {
994
- const currentState = this.peers.get(peer.id);
1033
+ if (currentState && currentState.type !== "unknown") {
1034
+ done?.(currentState.type === "available");
1035
+ return;
1036
+ }
995
1037
 
996
- if (
997
- currentState?.type === "available" ||
998
- currentState?.type === "pending"
999
- ) {
1000
- continue;
1001
- }
1038
+ this.markPending("storage");
1039
+ node.storage.load(
1040
+ this.id,
1041
+ (data) => {
1042
+ node.syncManager.handleNewContent(data, "storage");
1043
+ },
1044
+ (found) => {
1045
+ if (!found) {
1046
+ this.markNotFoundInPeer("storage");
1047
+ }
1002
1048
 
1003
- if (currentState?.type === "errored") {
1004
- continue;
1005
- }
1049
+ done?.(found);
1050
+ },
1051
+ );
1052
+ }
1006
1053
 
1007
- if (currentState?.type === "unavailable") {
1008
- if (peer.role === "server") {
1009
- peersToActuallyLoadFrom.server.push(peer);
1010
- this.markPending(peer.id);
1011
- }
1054
+ loadFromPeers(peers: PeerState[]) {
1055
+ if (peers.length === 0) {
1056
+ return;
1057
+ }
1012
1058
 
1013
- continue;
1014
- }
1059
+ const peersToActuallyLoadFrom = [] as PeerState[];
1015
1060
 
1016
- if (!currentState || currentState?.type === "unknown") {
1017
- if (peer.role === "storage") {
1018
- peersToActuallyLoadFrom.storage.push(peer);
1019
- } else {
1020
- peersToActuallyLoadFrom.server.push(peer);
1021
- }
1061
+ for (const peer of peers) {
1062
+ const currentState = this.peers.get(peer.id)?.type;
1022
1063
 
1064
+ if (
1065
+ !currentState ||
1066
+ currentState === "unknown" ||
1067
+ currentState === "unavailable"
1068
+ ) {
1069
+ peersToActuallyLoadFrom.push(peer);
1023
1070
  this.markPending(peer.id);
1024
1071
  }
1025
1072
  }
1026
1073
 
1027
- // Load from storage peers first, then from server peers
1028
- if (peersToActuallyLoadFrom.storage.length > 0) {
1029
- await Promise.all(
1030
- peersToActuallyLoadFrom.storage.map((peer) =>
1031
- this.internalLoadFromPeer(peer),
1032
- ),
1033
- );
1034
- }
1035
-
1036
- if (peersToActuallyLoadFrom.server.length > 0) {
1037
- await Promise.all(
1038
- peersToActuallyLoadFrom.server.map((peer) =>
1039
- this.internalLoadFromPeer(peer),
1040
- ),
1041
- );
1074
+ for (const peer of peersToActuallyLoadFrom) {
1075
+ this.internalLoadFromPeer(peer);
1042
1076
  }
1043
1077
  }
1044
1078
 
@@ -1,5 +1,6 @@
1
1
  import { Result, err, ok } from "neverthrow";
2
2
  import { AnyRawCoValue } from "../coValue.js";
3
+ import { MAX_RECOMMENDED_TX_SIZE } from "../config.js";
3
4
  import {
4
5
  CryptoProvider,
5
6
  Encrypted,
@@ -15,11 +16,7 @@ import { JsonObject, JsonValue } from "../jsonValue.js";
15
16
  import { PermissionsDef as RulesetDef } from "../permissions.js";
16
17
  import { getPriorityFromHeader } from "../priority.js";
17
18
  import { CoValueKnownState, NewContentMessage } from "../sync.js";
18
- import {
19
- InvalidHashError,
20
- InvalidSignatureError,
21
- MAX_RECOMMENDED_TX_SIZE,
22
- } from "./coValueCore.js";
19
+ import { InvalidHashError, InvalidSignatureError } from "./coValueCore.js";
23
20
  import { TryAddTransactionsError } from "./coValueCore.js";
24
21
 
25
22
  export type CoValueHeader = {
@@ -50,8 +47,7 @@ export type Transaction = PrivateTransaction | TrustingTransaction;
50
47
 
51
48
  type SessionLog = {
52
49
  readonly transactions: Transaction[];
53
- lastHash?: Hash;
54
- streamingHash: StreamingHash;
50
+ streamingHash?: StreamingHash;
55
51
  readonly signatureAfter: { [txIdx: number]: Signature | undefined };
56
52
  lastSignature: Signature;
57
53
  };
@@ -65,17 +61,22 @@ export class VerifiedState {
65
61
  readonly sessions: ValidatedSessions;
66
62
  private _cachedKnownState?: CoValueKnownState;
67
63
  private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
64
+ private streamingKnownState?: CoValueKnownState["sessions"];
68
65
 
69
66
  constructor(
70
67
  id: RawCoID,
71
68
  crypto: CryptoProvider,
72
69
  header: CoValueHeader,
73
70
  sessions: ValidatedSessions,
71
+ streamingKnownState?: CoValueKnownState["sessions"],
74
72
  ) {
75
73
  this.id = id;
76
74
  this.crypto = crypto;
77
75
  this.header = header;
78
76
  this.sessions = sessions;
77
+ this.streamingKnownState = streamingKnownState
78
+ ? { ...streamingKnownState }
79
+ : undefined;
79
80
  }
80
81
 
81
82
  clone(): VerifiedState {
@@ -84,13 +85,18 @@ export class VerifiedState {
84
85
  for (let [sessionID, sessionLog] of this.sessions) {
85
86
  clonedSessions.set(sessionID, {
86
87
  lastSignature: sessionLog.lastSignature,
87
- lastHash: sessionLog.lastHash,
88
- streamingHash: sessionLog.streamingHash.clone(),
88
+ streamingHash: sessionLog.streamingHash?.clone(),
89
89
  signatureAfter: { ...sessionLog.signatureAfter },
90
90
  transactions: sessionLog.transactions.slice(),
91
91
  } satisfies SessionLog);
92
92
  }
93
- return new VerifiedState(this.id, this.crypto, this.header, clonedSessions);
93
+ return new VerifiedState(
94
+ this.id,
95
+ this.crypto,
96
+ this.header,
97
+ clonedSessions,
98
+ this.streamingKnownState,
99
+ );
94
100
  }
95
101
 
96
102
  tryAddTransactions(
@@ -102,12 +108,11 @@ export class VerifiedState {
102
108
  skipVerify: boolean = false,
103
109
  givenNewStreamingHash?: StreamingHash,
104
110
  ): Result<true, TryAddTransactionsError> {
105
- if (skipVerify === true && givenNewStreamingHash && givenExpectedNewHash) {
111
+ if (skipVerify === true) {
106
112
  this.doAddTransactions(
107
113
  sessionID,
108
114
  newTransactions,
109
115
  newSignature,
110
- givenExpectedNewHash,
111
116
  givenNewStreamingHash,
112
117
  );
113
118
  } else {
@@ -139,7 +144,6 @@ export class VerifiedState {
139
144
  sessionID,
140
145
  newTransactions,
141
146
  newSignature,
142
- expectedNewHash,
143
147
  newStreamingHash,
144
148
  );
145
149
  }
@@ -151,16 +155,16 @@ export class VerifiedState {
151
155
  sessionID: SessionID,
152
156
  newTransactions: Transaction[],
153
157
  newSignature: Signature,
154
- expectedNewHash: Hash,
155
- newStreamingHash: StreamingHash,
158
+ newStreamingHash?: StreamingHash,
156
159
  ) {
157
- const transactions = this.sessions.get(sessionID)?.transactions ?? [];
160
+ const sessionLog = this.sessions.get(sessionID);
161
+ const transactions = sessionLog?.transactions ?? [];
158
162
 
159
163
  for (const tx of newTransactions) {
160
164
  transactions.push(tx);
161
165
  }
162
166
 
163
- const signatureAfter = this.sessions.get(sessionID)?.signatureAfter ?? {};
167
+ const signatureAfter = sessionLog?.signatureAfter ?? {};
164
168
 
165
169
  const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
166
170
  (max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
@@ -184,7 +188,6 @@ export class VerifiedState {
184
188
 
185
189
  this.sessions.set(sessionID, {
186
190
  transactions,
187
- lastHash: expectedNewHash,
188
191
  streamingHash: newStreamingHash,
189
192
  lastSignature: newSignature,
190
193
  signatureAfter: signatureAfter,
@@ -198,9 +201,27 @@ export class VerifiedState {
198
201
  sessionID: SessionID,
199
202
  newTransactions: Transaction[],
200
203
  ): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
201
- const streamingHash =
202
- this.sessions.get(sessionID)?.streamingHash.clone() ??
203
- new StreamingHash(this.crypto);
204
+ const sessionLog = this.sessions.get(sessionID);
205
+
206
+ if (!sessionLog?.streamingHash) {
207
+ const streamingHash = new StreamingHash(this.crypto);
208
+ const oldTransactions = sessionLog?.transactions ?? [];
209
+
210
+ for (const transaction of oldTransactions) {
211
+ streamingHash.update(transaction);
212
+ }
213
+
214
+ for (const transaction of newTransactions) {
215
+ streamingHash.update(transaction);
216
+ }
217
+
218
+ return {
219
+ expectedNewHash: streamingHash.digest(),
220
+ newStreamingHash: streamingHash,
221
+ };
222
+ }
223
+
224
+ const streamingHash = sessionLog.streamingHash.clone();
204
225
 
205
226
  for (const transaction of newTransactions) {
206
227
  streamingHash.update(transaction);
@@ -285,6 +306,11 @@ export class VerifiedState {
285
306
  }
286
307
 
287
308
  if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
309
+ if (!currentPiece.expectContentUntil && pieces.length === 1) {
310
+ currentPiece.expectContentUntil =
311
+ this.knownStateWithStreaming().sessions;
312
+ }
313
+
288
314
  currentPiece = {
289
315
  action: "content",
290
316
  id: this.id,
@@ -336,6 +362,50 @@ export class VerifiedState {
336
362
  return piecesWithContent;
337
363
  }
338
364
 
365
+ /**
366
+ * Returns the known state considering the known state of the streaming source
367
+ *
368
+ * Used to correctly manage the content & subscriptions during the content streaming process
369
+ */
370
+ knownStateWithStreaming(): CoValueKnownState {
371
+ const knownState = this.knownState();
372
+
373
+ if (this.streamingKnownState) {
374
+ const newSessions: CoValueKnownState["sessions"] = {};
375
+ const entries = Object.entries(this.streamingKnownState);
376
+
377
+ for (const [sessionID, txs] of entries) {
378
+ newSessions[sessionID as SessionID] = txs;
379
+ if ((knownState.sessions[sessionID as SessionID] ?? 0) < txs) {
380
+ newSessions[sessionID as SessionID] = txs;
381
+ } else {
382
+ newSessions[sessionID as SessionID] = txs;
383
+ delete this.streamingKnownState[sessionID as SessionID];
384
+ }
385
+ }
386
+
387
+ if (Object.keys(this.streamingKnownState).length === 0) {
388
+ this.streamingKnownState = undefined;
389
+ return knownState;
390
+ } else {
391
+ return {
392
+ id: knownState.id,
393
+ header: knownState.header,
394
+ sessions: newSessions,
395
+ };
396
+ }
397
+ }
398
+
399
+ return knownState;
400
+ }
401
+
402
+ isStreaming(): boolean {
403
+ // Call knownStateWithStreaming to delete the streamingKnownState when it matches the current knownState
404
+ this.knownStateWithStreaming();
405
+
406
+ return this.streamingKnownState !== undefined;
407
+ }
408
+
339
409
  knownState(): CoValueKnownState {
340
410
  if (this._cachedKnownState) {
341
411
  return this._cachedKnownState;
@@ -193,7 +193,7 @@ export class RawGroup<
193
193
 
194
194
  loadAllChildGroups() {
195
195
  const requests: Promise<unknown>[] = [];
196
- const peers = this.core.node.syncManager.getServerAndStoragePeers();
196
+ const peers = this.core.node.syncManager.getServerPeers();
197
197
 
198
198
  for (const key of this.keys()) {
199
199
  if (!isChildGroupReference(key)) {
@@ -207,9 +207,7 @@ export class RawGroup<
207
207
  child.loadingState === "unknown" ||
208
208
  child.loadingState === "unavailable"
209
209
  ) {
210
- child.loadFromPeers(peers).catch(() => {
211
- logger.error(`Failed to load child group ${id}`);
212
- });
210
+ child.load(peers);
213
211
  }
214
212
 
215
213
  requests.push(
package/src/config.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
3
+ since they are the smallest unit of progress that can be synced within a CoValue.
4
+ This is particularly important for storing binary data in CoValues, since they are likely to be at least on the order of megabytes.
5
+ This also means that we want to keep signatures roughly after each MAX_RECOMMENDED_TX size chunk,
6
+ to be able to verify partially loaded CoValues or CoValues that are still being created (like a video live stream).
7
+ **/
8
+ export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
9
+
10
+ export const CO_VALUE_LOADING_CONFIG = {
11
+ MAX_RETRIES: 1,
12
+ TIMEOUT: 30_000,
13
+ RETRY_DELAY: 3000,
14
+ };
15
+
16
+ export function setCoValueLoadingRetryDelay(delay: number) {
17
+ CO_VALUE_LOADING_CONFIG.RETRY_DELAY = delay;
18
+ }
19
+
20
+ export const SYNC_SCHEDULER_CONFIG = {
21
+ INCOMING_MESSAGES_TIME_BUDGET: 50,
22
+ };
23
+
24
+ export function setIncomingMessagesTimeBudget(budget: number) {
25
+ SYNC_SCHEDULER_CONFIG.INCOMING_MESSAGES_TIME_BUDGET = budget;
26
+ }
@@ -108,11 +108,13 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
108
108
  }
109
109
 
110
110
  verify(signature: Signature, message: JsonValue, id: SignerID): boolean {
111
- return verify(
111
+ const result = verify(
112
112
  textEncoder.encode(signature),
113
113
  textEncoder.encode(stableStringify(message)),
114
114
  textEncoder.encode(id),
115
115
  );
116
+
117
+ return result;
116
118
  }
117
119
 
118
120
  newX25519StaticSecret(): Uint8Array {
package/src/exports.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
2
2
  import { type RawCoValue } from "./coValue.js";
3
- import {
4
- CO_VALUE_LOADING_CONFIG,
5
- CoValueCore,
6
- MAX_RECOMMENDED_TX_SIZE,
7
- idforHeader,
8
- } from "./coValueCore/coValueCore.js";
3
+ import { CoValueCore, idforHeader } from "./coValueCore/coValueCore.js";
9
4
  import { CoValueUniqueness } from "./coValueCore/verifiedState.js";
10
5
  import {
11
6
  ControlledAccount,
@@ -42,7 +37,7 @@ import {
42
37
  import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
43
38
  import { LocalNode } from "./localNode.js";
44
39
  import type { AccountRole, Role } from "./permissions.js";
45
- import { Channel, connectedPeers } from "./streamUtils.js";
40
+ import { ConnectedPeerChannel, connectedPeers } from "./streamUtils.js";
46
41
  import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
47
42
  import { expectGroup } from "./typeUtils/expectGroup.js";
48
43
  import { isAccountID } from "./typeUtils/isAccountID.js";
@@ -63,24 +58,21 @@ import type { AgentID, RawCoID, SessionID } from "./ids.js";
63
58
  import type { JsonObject, JsonValue } from "./jsonValue.js";
64
59
  import type * as Media from "./media.js";
65
60
  import { disablePermissionErrors } from "./permissions.js";
66
- import type {
67
- IncomingSyncStream,
68
- OutgoingSyncQueue,
69
- Peer,
70
- SyncMessage,
71
- } from "./sync.js";
72
- import {
73
- DisconnectedError,
74
- PingTimeoutError,
75
- SyncManager,
76
- emptyKnownState,
77
- } from "./sync.js";
61
+ import type { Peer, SyncMessage } from "./sync.js";
62
+ import { DisconnectedError, SyncManager, emptyKnownState } from "./sync.js";
78
63
 
79
64
  type Value = JsonValue | AnyRawCoValue;
80
65
 
66
+ export { PriorityBasedMessageQueue } from "./PriorityBasedMessageQueue.js";
81
67
  import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
68
+ import {
69
+ CO_VALUE_LOADING_CONFIG,
70
+ MAX_RECOMMENDED_TX_SIZE,
71
+ setCoValueLoadingRetryDelay,
72
+ setIncomingMessagesTimeBudget,
73
+ } from "./config.js";
82
74
  import { LogLevel, logger } from "./logger.js";
83
- import { getPriorityFromHeader } from "./priority.js";
75
+ import { CO_VALUE_PRIORITY, getPriorityFromHeader } from "./priority.js";
84
76
 
85
77
  /** @hidden */
86
78
  export const cojsonInternals = {
@@ -100,16 +92,16 @@ export const cojsonInternals = {
100
92
  accountHeaderForInitialAgentSecret,
101
93
  idforHeader,
102
94
  StreamingHash,
103
- Channel,
104
95
  getPriorityFromHeader,
105
96
  getGroupDependentKeyList,
106
97
  getGroupDependentKey,
107
98
  disablePermissionErrors,
108
99
  SyncManager,
109
100
  CO_VALUE_LOADING_CONFIG,
110
- setCoValueLoadingRetryDelay(delay: number) {
111
- CO_VALUE_LOADING_CONFIG.RETRY_DELAY = delay;
112
- },
101
+ CO_VALUE_PRIORITY,
102
+ setIncomingMessagesTimeBudget,
103
+ setCoValueLoadingRetryDelay,
104
+ ConnectedPeerChannel,
113
105
  textEncoder,
114
106
  textDecoder,
115
107
  };
@@ -161,10 +153,7 @@ export {
161
153
 
162
154
  export type {
163
155
  Value,
164
- IncomingSyncStream,
165
- OutgoingSyncQueue,
166
156
  DisconnectedError,
167
- PingTimeoutError,
168
157
  CoValueUniqueness,
169
158
  Stringified,
170
159
  CoStreamItem,
@@ -174,6 +163,8 @@ export type {
174
163
  AccountRole,
175
164
  };
176
165
 
166
+ export * from "./storage/index.js";
167
+
177
168
  // biome-ignore format: off
178
169
  // eslint-disable-next-line @typescript-eslint/no-namespace
179
170
  export namespace CojsonInternalTypes {
@@ -196,5 +187,7 @@ export namespace CojsonInternalTypes {
196
187
  export type SignerID = import("./crypto/crypto.js").SignerID;
197
188
  export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
198
189
  export type JsonObject = import("./jsonValue.js").JsonObject;
190
+ export type OutgoingPeerChannel = import("./sync.js").OutgoingPeerChannel;
191
+ export type IncomingPeerChannel = import("./sync.js").IncomingPeerChannel;
199
192
  }
200
193
  // biome-ignore format: on