cojson 0.18.19 → 0.18.21

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 (56) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/GarbageCollector.d.ts +2 -1
  4. package/dist/GarbageCollector.d.ts.map +1 -1
  5. package/dist/GarbageCollector.js +3 -2
  6. package/dist/GarbageCollector.js.map +1 -1
  7. package/dist/coValueCore/coValueCore.d.ts +28 -25
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +128 -90
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/utils.d.ts +6 -0
  12. package/dist/coValueCore/utils.d.ts.map +1 -1
  13. package/dist/coValueCore/utils.js +53 -25
  14. package/dist/coValueCore/utils.js.map +1 -1
  15. package/dist/localNode.d.ts +5 -4
  16. package/dist/localNode.d.ts.map +1 -1
  17. package/dist/localNode.js +31 -37
  18. package/dist/localNode.js.map +1 -1
  19. package/dist/sync.d.ts.map +1 -1
  20. package/dist/sync.js +56 -69
  21. package/dist/sync.js.map +1 -1
  22. package/dist/tests/GarbageCollector.test.js +14 -0
  23. package/dist/tests/GarbageCollector.test.js.map +1 -1
  24. package/dist/tests/SyncStateManager.test.js +1 -1
  25. package/dist/tests/coValueCore.dependencies.test.d.ts +2 -0
  26. package/dist/tests/coValueCore.dependencies.test.d.ts.map +1 -0
  27. package/dist/tests/coValueCore.dependencies.test.js +55 -0
  28. package/dist/tests/coValueCore.dependencies.test.js.map +1 -0
  29. package/dist/tests/coValueCore.test.js +2 -2
  30. package/dist/tests/coValueCore.test.js.map +1 -1
  31. package/dist/tests/coValueCoreLoadingState.test.js +43 -62
  32. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
  33. package/dist/tests/permissions.test.js +117 -117
  34. package/dist/tests/permissions.test.js.map +1 -1
  35. package/dist/tests/sync.load.test.js +238 -9
  36. package/dist/tests/sync.load.test.js.map +1 -1
  37. package/dist/tests/sync.peerReconciliation.test.js +7 -6
  38. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  39. package/dist/tests/testUtils.d.ts.map +1 -1
  40. package/dist/tests/testUtils.js +2 -1
  41. package/dist/tests/testUtils.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/GarbageCollector.ts +5 -2
  44. package/src/coValueCore/coValueCore.ts +172 -118
  45. package/src/coValueCore/utils.ts +85 -31
  46. package/src/localNode.ts +43 -48
  47. package/src/sync.ts +63 -89
  48. package/src/tests/GarbageCollector.test.ts +20 -0
  49. package/src/tests/SyncStateManager.test.ts +1 -1
  50. package/src/tests/coValueCore.dependencies.test.ts +90 -0
  51. package/src/tests/coValueCore.test.ts +2 -2
  52. package/src/tests/coValueCoreLoadingState.test.ts +50 -66
  53. package/src/tests/permissions.test.ts +120 -123
  54. package/src/tests/sync.load.test.ts +308 -9
  55. package/src/tests/sync.peerReconciliation.test.ts +7 -6
  56. package/src/tests/testUtils.ts +5 -3
package/package.json CHANGED
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "type": "module",
31
31
  "license": "MIT",
32
- "version": "0.18.19",
32
+ "version": "0.18.21",
33
33
  "devDependencies": {
34
34
  "@opentelemetry/sdk-metrics": "^2.0.0",
35
35
  "libsql": "^0.5.13",
@@ -43,7 +43,7 @@
43
43
  "@scure/base": "1.2.1",
44
44
  "neverthrow": "^7.0.1",
45
45
  "unicode-segmenter": "^0.12.0",
46
- "cojson-core-wasm": "0.18.19"
46
+ "cojson-core-wasm": "0.18.21"
47
47
  },
48
48
  "gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13",
49
49
  "scripts": {
@@ -5,7 +5,10 @@ import { RawCoID } from "./ids.js";
5
5
  export class GarbageCollector {
6
6
  private readonly interval: ReturnType<typeof setInterval>;
7
7
 
8
- constructor(private readonly coValues: Map<RawCoID, CoValueCore>) {
8
+ constructor(
9
+ private readonly coValues: Map<RawCoID, CoValueCore>,
10
+ private readonly garbageCollectGroups: boolean,
11
+ ) {
9
12
  this.interval = setInterval(() => {
10
13
  this.collect();
11
14
  }, GARBAGE_COLLECTOR_CONFIG.INTERVAL);
@@ -33,7 +36,7 @@ export class GarbageCollector {
33
36
  const timeSinceLastAccessed = currentTime - verified.lastAccessed;
34
37
 
35
38
  if (timeSinceLastAccessed > GARBAGE_COLLECTOR_CONFIG.MAX_AGE) {
36
- coValue.unmount();
39
+ coValue.unmount(this.garbageCollectGroups);
37
40
  }
38
41
  }
39
42
  }
@@ -20,10 +20,19 @@ import { JsonObject, JsonValue } from "../jsonValue.js";
20
20
  import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
21
21
  import { logger } from "../logger.js";
22
22
  import { determineValidTransactions } from "../permissions.js";
23
- import { CoValueKnownState, PeerID, emptyKnownState } from "../sync.js";
23
+ import {
24
+ CoValueKnownState,
25
+ NewContentMessage,
26
+ PeerID,
27
+ emptyKnownState,
28
+ } from "../sync.js";
24
29
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
25
30
  import { expectGroup } from "../typeUtils/expectGroup.js";
26
- import { getDependedOnCoValuesFromRawData } from "./utils.js";
31
+ import {
32
+ getDependenciesFromContentMessage,
33
+ getDependenciesFromGroupRawTransactions,
34
+ getDependenciesFromHeader,
35
+ } from "./utils.js";
27
36
  import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
28
37
  import { SessionMap } from "./SessionMap.js";
29
38
  import {
@@ -125,7 +134,6 @@ export class CoValueCore {
125
134
  private _cachedContent?: RawCoValue;
126
135
  readonly listeners: Set<(core: CoValueCore, unsub: () => void) => void> =
127
136
  new Set();
128
- private _cachedDependentOn?: Set<RawCoID>;
129
137
  private counter: UpDownCounter;
130
138
 
131
139
  private constructor(
@@ -182,8 +190,12 @@ export class CoValueCore {
182
190
  return "unavailable";
183
191
  }
184
192
 
193
+ hasMissingDependencies() {
194
+ return this.missingDependencies.size > 0;
195
+ }
196
+
185
197
  isAvailable(): this is AvailableCoValueCore {
186
- return this.hasVerifiedContent() && this.missingDependencies.size === 0;
198
+ return this.hasVerifiedContent();
187
199
  }
188
200
 
189
201
  hasVerifiedContent(): this is AvailableCoValueCore {
@@ -251,9 +263,11 @@ export class CoValueCore {
251
263
  }
252
264
  }
253
265
 
254
- unmount() {
255
- // For simplicity, we don't unmount groups and accounts
256
- if (this.verified?.header.ruleset.type === "group") {
266
+ unmount(garbageCollectGroups = false) {
267
+ if (
268
+ !garbageCollectGroups &&
269
+ this.verified?.header.ruleset.type === "group"
270
+ ) {
257
271
  return false;
258
272
  }
259
273
 
@@ -280,10 +294,15 @@ export class CoValueCore {
280
294
  this.scheduleNotifyUpdate();
281
295
  }
282
296
 
297
+ markFoundInPeer(peerId: PeerID, previousState: string) {
298
+ this.peers.set(peerId, { type: "available" });
299
+ this.updateCounter(previousState);
300
+ this.scheduleNotifyUpdate();
301
+ }
302
+
283
303
  missingDependencies = new Set<RawCoID>();
284
304
 
285
- // Checks if the current CoValueCore is already a missing dependency of the given CoValueCore
286
- checkCircularDependencies(dependency: CoValueCore) {
305
+ isCircularMissingDependency(dependency: CoValueCore) {
287
306
  const visited = new Set<RawCoID>();
288
307
  const stack = [dependency];
289
308
 
@@ -291,14 +310,14 @@ export class CoValueCore {
291
310
  const current = stack.pop();
292
311
 
293
312
  if (!current) {
294
- return true;
313
+ return false;
295
314
  }
296
315
 
297
316
  visited.add(current.id);
298
317
 
299
318
  for (const dependency of current.missingDependencies) {
300
319
  if (dependency === this.id) {
301
- return false;
320
+ return true;
302
321
  }
303
322
 
304
323
  if (!visited.has(dependency)) {
@@ -307,43 +326,71 @@ export class CoValueCore {
307
326
  }
308
327
  }
309
328
 
310
- return true;
329
+ return false;
311
330
  }
312
331
 
313
- markMissingDependency(dependency: RawCoID) {
314
- const value = this.node.getCoValue(dependency);
332
+ markDependencyAvailable(dependency: RawCoID) {
333
+ this.missingDependencies.delete(dependency);
315
334
 
316
- if (value.isAvailable()) {
317
- this.missingDependencies.delete(dependency);
318
- } else if (this.checkCircularDependencies(value)) {
319
- const unsubscribe = value.subscribe(() => {
320
- if (value.isAvailable()) {
321
- this.missingDependencies.delete(dependency);
322
- unsubscribe();
323
- }
335
+ if (this.missingDependencies.size === 0) {
336
+ this.scheduleNotifyUpdate();
337
+ }
338
+ }
324
339
 
325
- if (this.isAvailable()) {
326
- this.scheduleNotifyUpdate();
340
+ newContentQueue: {
341
+ msg: NewContentMessage;
342
+ from: PeerState | "storage" | "import";
343
+ }[] = [];
344
+ /**
345
+ * Add a new content to the queue and handle it when the dependencies are available
346
+ */
347
+ addNewContentToQueue(
348
+ msg: NewContentMessage,
349
+ from: PeerState | "storage" | "import",
350
+ ) {
351
+ const alreadyEnqueued = this.newContentQueue.length > 0;
352
+
353
+ this.newContentQueue.push({ msg, from });
354
+
355
+ if (alreadyEnqueued) {
356
+ return;
357
+ }
358
+
359
+ this.subscribe((core, unsubscribe) => {
360
+ if (!core.hasMissingDependencies()) {
361
+ unsubscribe();
362
+
363
+ const enqueuedNewContent = this.newContentQueue;
364
+ this.newContentQueue = [];
365
+
366
+ for (const { msg, from } of enqueuedNewContent) {
367
+ this.node.syncManager.handleNewContent(msg, from);
327
368
  }
328
- });
369
+ }
370
+ });
371
+ }
329
372
 
330
- this.missingDependencies.add(dependency);
373
+ addDependencyFromHeader(header: CoValueHeader) {
374
+ for (const dep of getDependenciesFromHeader(header)) {
375
+ this.addDependency(dep);
331
376
  }
332
377
  }
333
378
 
334
379
  provideHeader(
335
380
  header: CoValueHeader,
336
- fromPeerId: PeerID,
337
381
  streamingKnownState?: CoValueKnownState["sessions"],
382
+ skipVerify?: boolean,
338
383
  ) {
339
- const previousState = this.loadingState;
340
-
341
- const expectedId = idforHeader(header, this.node.crypto);
384
+ if (!skipVerify) {
385
+ const expectedId = idforHeader(header, this.node.crypto);
342
386
 
343
- if (this.id !== expectedId) {
344
- return false;
387
+ if (this.id !== expectedId) {
388
+ return false;
389
+ }
345
390
  }
346
391
 
392
+ this.addDependencyFromHeader(header);
393
+
347
394
  if (this._verified?.sessions.size) {
348
395
  throw new Error(
349
396
  "CoValueCore: provideHeader called on coValue with verified sessions present!",
@@ -357,26 +404,9 @@ export class CoValueCore {
357
404
  streamingKnownState,
358
405
  );
359
406
 
360
- this.peers.set(fromPeerId, { type: "available" });
361
-
362
- this.updateCounter(previousState);
363
- this.scheduleNotifyUpdate();
364
-
365
407
  return true;
366
408
  }
367
409
 
368
- internalMarkMagicallyAvailable(
369
- verified: VerifiedState,
370
- { forceOverwrite = false }: { forceOverwrite?: boolean } = {},
371
- ) {
372
- const previousState = this.loadingState;
373
- this.internalShamefullyCloneVerifiedStateFrom(verified, {
374
- forceOverwrite,
375
- });
376
- this.updateCounter(previousState);
377
- this.scheduleNotifyUpdate();
378
- }
379
-
380
410
  markErrored(peerId: PeerID, error: TryAddTransactionsError) {
381
411
  const previousState = this.loadingState;
382
412
  this.peers.set(peerId, { type: "errored", error });
@@ -391,25 +421,6 @@ export class CoValueCore {
391
421
  this.scheduleNotifyUpdate();
392
422
  }
393
423
 
394
- internalShamefullyCloneVerifiedStateFrom(
395
- state: VerifiedState,
396
- { forceOverwrite = false }: { forceOverwrite?: boolean } = {},
397
- ) {
398
- if (!forceOverwrite && this._verified?.sessions.size) {
399
- throw new Error(
400
- "CoValueCore: internalShamefullyCloneVerifiedStateFrom called on coValue with verified sessions present!",
401
- );
402
- }
403
- this._verified = state.clone();
404
- this.internalShamefullyResetCachedContent();
405
- }
406
-
407
- internalShamefullyResetCachedContent() {
408
- this._cachedContent = undefined;
409
- this._cachedDependentOn = undefined;
410
- this.resetParsedTransactions();
411
- }
412
-
413
424
  groupInvalidationSubscription?: () => void;
414
425
 
415
426
  subscribeToGroupInvalidation() {
@@ -430,7 +441,7 @@ export class CoValueCore {
430
441
  if (entry.isAvailable()) {
431
442
  this.groupInvalidationSubscription = entry.subscribe((_groupUpdate) => {
432
443
  // When the group is updated, we need to reset the cached content because the transactions validity might have changed
433
- this.internalShamefullyResetCachedContent();
444
+ this.resetParsedTransactions();
434
445
  this.scheduleNotifyUpdate();
435
446
  }, false);
436
447
  } else {
@@ -445,7 +456,7 @@ export class CoValueCore {
445
456
  contentInClonedNodeWithDifferentAccount(account: ControlledAccountOrAgent) {
446
457
  return this.node
447
458
  .loadCoValueAsDifferentAgent(this.id, account.agentSecret, account.id)
448
- .getCurrentContent();
459
+ .then((core) => core.getCurrentContent());
449
460
  }
450
461
 
451
462
  knownStateWithStreaming(): CoValueKnownState {
@@ -490,6 +501,14 @@ export class CoValueCore {
490
501
  };
491
502
  }
492
503
 
504
+ addDependenciesFromContentMessage(newContent: NewContentMessage) {
505
+ const dependencies = getDependenciesFromContentMessage(this, newContent);
506
+
507
+ for (const dependency of dependencies) {
508
+ this.addDependency(dependency);
509
+ }
510
+ }
511
+
493
512
  tryAddTransactions(
494
513
  sessionID: SessionID,
495
514
  newTransactions: Transaction[],
@@ -528,7 +547,7 @@ export class CoValueCore {
528
547
  );
529
548
 
530
549
  if (result.isOk()) {
531
- this.updateCurrentContent();
550
+ this.processNewTransactions();
532
551
  this.scheduleNotifyUpdate();
533
552
  }
534
553
 
@@ -536,18 +555,18 @@ export class CoValueCore {
536
555
  });
537
556
  }
538
557
 
539
- private updateCurrentContent() {
540
- if (
541
- this._cachedContent &&
542
- "processNewTransactions" in this._cachedContent &&
543
- typeof this._cachedContent.processNewTransactions === "function"
544
- ) {
545
- this._cachedContent.processNewTransactions();
546
- } else {
547
- this._cachedContent = undefined;
558
+ private processNewTransactions() {
559
+ if (this._cachedContent) {
560
+ // Does the cached content support incremental processing?
561
+ if (
562
+ "processNewTransactions" in this._cachedContent &&
563
+ typeof this._cachedContent.processNewTransactions === "function"
564
+ ) {
565
+ this._cachedContent.processNewTransactions();
566
+ } else {
567
+ this._cachedContent = undefined;
568
+ }
548
569
  }
549
-
550
- this._cachedDependentOn = undefined;
551
570
  }
552
571
 
553
572
  #isNotificationScheduled = false;
@@ -574,8 +593,18 @@ export class CoValueCore {
574
593
  }
575
594
  }
576
595
 
596
+ #isNotifyUpdatePaused = false;
597
+ pauseNotifyUpdate() {
598
+ this.#isNotifyUpdatePaused = true;
599
+ }
600
+
601
+ resumeNotifyUpdate() {
602
+ this.#isNotifyUpdatePaused = false;
603
+ this.notifyUpdate();
604
+ }
605
+
577
606
  private notifyUpdate() {
578
- if (this.listeners.size === 0) {
607
+ if (this.listeners.size === 0 || this.#isNotifyUpdatePaused) {
579
608
  return;
580
609
  }
581
610
 
@@ -664,15 +693,17 @@ export class CoValueCore {
664
693
 
665
694
  const { transaction, signature } = result;
666
695
 
667
- this.node.syncManager.recordTransactionsSize([transaction], "local");
696
+ // Assign pre-parsed meta and changes to skip the parse/decrypt operation when loading
697
+ // this transaction in the current content
698
+ this.parsingCache.set(transaction, { changes, meta });
668
699
 
669
- // We pre-populate the parsed transactions and meta for the new transaction, to skip the parsing step later
670
- this.loadVerifiedTransactionsFromLogs({ transaction, changes, meta });
700
+ this.node.syncManager.recordTransactionsSize([transaction], "local");
671
701
 
672
702
  const session = this.verified.sessions.get(sessionID);
673
703
  const txIdx = session ? session.transactions.length - 1 : 0;
674
704
 
675
- this.updateCurrentContent();
705
+ this.processNewTransactions();
706
+ this.addDependenciesFromNewTransaction(transaction);
676
707
 
677
708
  // force immediate notification because local updates may come from the UI
678
709
  // where we need synchronous updates
@@ -688,6 +719,16 @@ export class CoValueCore {
688
719
  return true;
689
720
  }
690
721
 
722
+ addDependenciesFromNewTransaction(transaction: Transaction) {
723
+ if (this.verified?.header.ruleset.type === "group") {
724
+ for (const dependency of getDependenciesFromGroupRawTransactions([
725
+ transaction,
726
+ ])) {
727
+ this.addDependency(dependency);
728
+ }
729
+ }
730
+ }
731
+
691
732
  getCurrentContent(options?: { ignorePrivateTransactions: true }): RawCoValue {
692
733
  if (!this.verified) {
693
734
  throw new Error(
@@ -721,6 +762,8 @@ export class CoValueCore {
721
762
 
722
763
  // Reset the parsed transactions and branches, to validate them again from scratch when the group is updated
723
764
  resetParsedTransactions() {
765
+ this._cachedContent = undefined;
766
+
724
767
  this.branchStart = undefined;
725
768
  this.mergeCommits = [];
726
769
 
@@ -738,23 +781,21 @@ export class CoValueCore {
738
781
  VerifiedTransaction
739
782
  > = {};
740
783
 
784
+ private parsingCache = new Map<
785
+ Transaction,
786
+ { changes: JsonValue[]; meta: JsonObject | undefined }
787
+ >();
788
+
741
789
  /**
742
790
  * Loads the new transaction from the SessionMap into verifiedTransactions as a VerifiedTransaction.
743
791
  *
744
792
  * If the transaction is already loaded from the SessionMap in the past, it will not be loaded again.
745
793
  *
746
794
  * Used to have a fast way to iterate over the CoValue transactions, and track their validation/decoding state.
747
- *
748
- * @param preload - Optional preload object containing the transaction, changes, and meta.
749
- * If provided, the transaction will be preloaded with the given changes and meta.
750
- *
751
- * @internal
795
+
796
+ * @internal
752
797
  * */
753
- loadVerifiedTransactionsFromLogs(preload?: {
754
- transaction: Transaction;
755
- changes: JsonValue[];
756
- meta: JsonObject | undefined;
757
- }) {
798
+ loadVerifiedTransactionsFromLogs() {
758
799
  if (!this.verified) {
759
800
  return;
760
801
  }
@@ -780,14 +821,19 @@ export class CoValueCore {
780
821
  txIndex,
781
822
  };
782
823
 
824
+ const cache = this.parsingCache.get(tx);
825
+ if (cache) {
826
+ this.parsingCache.delete(tx);
827
+ }
828
+
783
829
  const verifiedTransaction = {
784
830
  author: accountOrAgentIDfromSessionID(sessionID),
785
831
  txID,
786
832
  madeAt: tx.madeAt,
787
833
  isValidated: false,
788
834
  isValid: false,
789
- changes: tx === preload?.transaction ? preload.changes : undefined,
790
- meta: tx === preload?.transaction ? preload.meta : undefined,
835
+ changes: cache?.changes,
836
+ meta: cache?.meta,
791
837
  hasInvalidChanges: false,
792
838
  hasInvalidMeta: false,
793
839
  hasMetaBeenParsed: false,
@@ -978,6 +1024,32 @@ export class CoValueCore {
978
1024
  return matchingTransactions;
979
1025
  }
980
1026
 
1027
+ dependencies: Set<RawCoID> = new Set();
1028
+ private addDependency(dependency: RawCoID) {
1029
+ if (this.dependencies.has(dependency)) {
1030
+ return true;
1031
+ }
1032
+
1033
+ this.dependencies.add(dependency);
1034
+
1035
+ const dependencyCoValue = this.node.getCoValue(dependency);
1036
+
1037
+ if (
1038
+ !dependencyCoValue.isAvailable() &&
1039
+ !this.isCircularMissingDependency(dependencyCoValue)
1040
+ ) {
1041
+ this.missingDependencies.add(dependency);
1042
+
1043
+ dependencyCoValue.subscribe((dependencyCoValue, unsubscribe) => {
1044
+ if (dependencyCoValue.isAvailable()) {
1045
+ unsubscribe();
1046
+ this.markDependencyAvailable(dependency);
1047
+ }
1048
+ });
1049
+ return false;
1050
+ }
1051
+ }
1052
+
981
1053
  createBranch(name: string, ownerId?: RawCoID) {
982
1054
  return createBranch(this, name, ownerId);
983
1055
  }
@@ -1144,25 +1216,7 @@ export class CoValueCore {
1144
1216
  }
1145
1217
 
1146
1218
  getDependedOnCoValues(): Set<RawCoID> {
1147
- if (this._cachedDependentOn) {
1148
- return this._cachedDependentOn;
1149
- } else {
1150
- if (!this.verified) {
1151
- return new Set();
1152
- }
1153
-
1154
- const dependentOn = getDependedOnCoValuesFromRawData(
1155
- this.id,
1156
- this.verified.header,
1157
- this.verified.sessions.keys(),
1158
- Array.from(
1159
- this.verified.sessions.values(),
1160
- (session) => session.transactions,
1161
- ),
1162
- );
1163
- this._cachedDependentOn = dependentOn;
1164
- return dependentOn;
1165
- }
1219
+ return this.dependencies;
1166
1220
  }
1167
1221
 
1168
1222
  waitForSync(options?: { timeout?: number }) {
@@ -1,61 +1,115 @@
1
+ import { CoValueCore } from "../exports.js";
1
2
  import { getGroupDependentKey } from "../ids.js";
2
3
  import { RawCoID, SessionID } from "../ids.js";
3
4
  import { Stringified, parseJSON } from "../jsonStringify.js";
4
5
  import { JsonValue } from "../jsonValue.js";
6
+ import { NewContentMessage } from "../sync.js";
5
7
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
6
8
  import { isAccountID } from "../typeUtils/isAccountID.js";
7
9
  import { CoValueHeader, Transaction } from "./verifiedState.js";
8
10
 
9
- export function getDependedOnCoValuesFromRawData(
10
- id: RawCoID,
11
+ export function getDependenciesFromHeader(
11
12
  header: CoValueHeader,
12
- sessions: Iterable<SessionID>,
13
- transactions: Iterable<Iterable<Transaction>>,
13
+ deps = new Set<RawCoID>(),
14
14
  ): Set<RawCoID> {
15
- const deps = new Set<RawCoID>();
15
+ if (header.ruleset.type === "ownedByGroup") {
16
+ deps.add(header.ruleset.group);
17
+ }
16
18
 
19
+ if (header.meta?.source) {
20
+ deps.add(header.meta.source as RawCoID);
21
+ }
22
+
23
+ return deps;
24
+ }
25
+
26
+ export function getDependenciesFromSessions(
27
+ sessions: Iterable<SessionID>,
28
+ deps: Set<RawCoID>,
29
+ ): void {
17
30
  for (const session of sessions) {
18
31
  const accountId = accountOrAgentIDfromSessionID(session);
19
32
 
20
- if (isAccountID(accountId) && accountId !== id) {
33
+ if (isAccountID(accountId)) {
21
34
  deps.add(accountId);
22
35
  }
23
36
  }
37
+ }
24
38
 
25
- if (header.ruleset.type === "group") {
26
- for (const txs of transactions) {
27
- for (const tx of txs) {
28
- if (tx.privacy !== "trusting") continue;
29
-
30
- const changes = safeParseChanges(tx.changes);
31
- for (const change of changes) {
32
- if (
33
- change &&
34
- typeof change === "object" &&
35
- "op" in change &&
36
- change.op === "set" &&
37
- "key" in change &&
38
- change.key
39
- ) {
40
- const key = getGroupDependentKey(change.key);
41
-
42
- if (key && key !== id) {
43
- deps.add(key);
44
- }
45
- }
39
+ export function getDependenciesFromGroupRawTransactions(
40
+ transactions: Iterable<Transaction>,
41
+ deps = new Set<RawCoID>(),
42
+ ): Set<RawCoID> {
43
+ for (const tx of transactions) {
44
+ if (tx.privacy !== "trusting") continue;
45
+
46
+ const changes = safeParseChanges(tx.changes);
47
+ for (const change of changes) {
48
+ if (
49
+ change &&
50
+ typeof change === "object" &&
51
+ "op" in change &&
52
+ change.op === "set" &&
53
+ "key" in change &&
54
+ change.key
55
+ ) {
56
+ const key = getGroupDependentKey(change.key);
57
+
58
+ if (key) {
59
+ deps.add(key);
46
60
  }
47
61
  }
48
62
  }
49
63
  }
50
64
 
51
- if (header.ruleset.type === "ownedByGroup") {
52
- deps.add(header.ruleset.group);
65
+ return deps;
66
+ }
67
+
68
+ export function getDependedOnCoValuesFromRawData(
69
+ id: RawCoID,
70
+ header: CoValueHeader,
71
+ sessions: Iterable<SessionID>,
72
+ transactions: Iterable<Iterable<Transaction>>,
73
+ ): Set<RawCoID> {
74
+ const deps = new Set<RawCoID>();
75
+
76
+ getDependenciesFromHeader(header, deps);
77
+ getDependenciesFromSessions(sessions, deps);
78
+
79
+ if (header.ruleset.type === "group") {
80
+ for (const txs of transactions) {
81
+ getDependenciesFromGroupRawTransactions(txs, deps);
82
+ }
53
83
  }
54
84
 
55
- if (header.meta?.source) {
56
- deps.add(header.meta.source as RawCoID);
85
+ deps.delete(id);
86
+
87
+ return deps;
88
+ }
89
+
90
+ export function getDependenciesFromContentMessage(
91
+ coValue: CoValueCore,
92
+ contentMessage: NewContentMessage,
93
+ ) {
94
+ const deps = new Set<RawCoID>();
95
+
96
+ if (contentMessage.header) {
97
+ getDependenciesFromHeader(contentMessage.header, deps);
57
98
  }
58
99
 
100
+ const sessions = Object.keys(contentMessage.new) as SessionID[];
101
+ getDependenciesFromSessions(sessions, deps);
102
+
103
+ const header = coValue.verified?.header ?? contentMessage.header;
104
+
105
+ if (header?.ruleset.type === "group") {
106
+ for (const { newTransactions } of Object.values(contentMessage.new)) {
107
+ getDependenciesFromGroupRawTransactions(newTransactions, deps);
108
+ }
109
+ }
110
+
111
+ deps.delete(coValue.id);
112
+
59
113
  return deps;
60
114
  }
61
115