@peerbit/shared-log 13.1.12 → 13.1.14

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "13.1.12",
3
+ "version": "13.1.14",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -62,27 +62,27 @@
62
62
  "pino": "^9.4.0",
63
63
  "uint8arrays": "^5.1.0",
64
64
  "@peerbit/any-store": "2.2.9",
65
- "@peerbit/blocks": "4.1.5",
65
+ "@peerbit/blocks": "4.1.6",
66
66
  "@peerbit/blocks-interface": "2.0.11",
67
- "@peerbit/cache": "3.0.0",
68
67
  "@peerbit/crypto": "3.1.1",
69
- "@peerbit/indexer-sqlite3": "3.0.6",
68
+ "@peerbit/cache": "3.0.0",
70
69
  "@peerbit/indexer-interface": "3.0.3",
70
+ "@peerbit/indexer-sqlite3": "3.0.6",
71
71
  "@peerbit/logger": "2.0.1",
72
- "@peerbit/program": "6.0.26",
73
- "@peerbit/log": "6.0.32",
74
- "@peerbit/pubsub": "5.2.7",
75
- "@peerbit/riblt": "1.2.0",
72
+ "@peerbit/program": "6.0.27",
73
+ "@peerbit/pubsub": "5.2.8",
74
+ "@peerbit/log": "6.0.33",
76
75
  "@peerbit/pubsub-interface": "5.1.3",
77
- "@peerbit/rpc": "6.0.30",
78
- "@peerbit/time": "3.0.0",
79
- "@peerbit/stream-interface": "6.0.9"
76
+ "@peerbit/riblt": "1.2.0",
77
+ "@peerbit/stream-interface": "6.0.9",
78
+ "@peerbit/rpc": "6.0.31",
79
+ "@peerbit/time": "3.0.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@types/libsodium-wrappers": "^0.7.14",
83
83
  "@types/pidusage": "^2.0.5",
84
84
  "uuid": "^10.0.0",
85
- "@peerbit/test-utils": "3.0.30"
85
+ "@peerbit/test-utils": "3.0.31"
86
86
  },
87
87
  "repository": {
88
88
  "type": "git",
package/src/index.ts CHANGED
@@ -241,6 +241,12 @@ export { MAX_U32, MAX_U64, type NumberFromType };
241
241
  export const logger = loggerFn("peerbit:shared-log");
242
242
  const warn = logger.newScope("warn");
243
243
 
244
+ type CheckedPruneLeaderMap = Map<string, { intersecting: boolean }>;
245
+ type CheckedPruneEntry<T, R extends "u32" | "u64"> =
246
+ | Entry<T>
247
+ | ShallowEntry
248
+ | EntryReplicated<R>;
249
+
244
250
  const getLatestEntry = (
245
251
  entries: (ShallowOrFullEntry<any> | EntryWithRefs<any>)[],
246
252
  ) => {
@@ -859,8 +865,8 @@ export class SharedLog<
859
865
 
860
866
  // A fn for debouncing the calls for pruning
861
867
  pruneDebouncedFn!: DebouncedAccumulatorMap<{
862
- entry: Entry<T> | ShallowEntry | EntryReplicated<R>;
863
- leaders: Map<string, any>;
868
+ entry: CheckedPruneEntry<T, R>;
869
+ leaders: CheckedPruneLeaderMap;
864
870
  }>;
865
871
  private responseToPruneDebouncedFn!: ReturnType<
866
872
  typeof debounceAccumulator<
@@ -3554,8 +3560,8 @@ export class SharedLog<
3554
3560
  private async pruneDebouncedFnAddIfNotKeeping(args: {
3555
3561
  key: string;
3556
3562
  value: {
3557
- entry: Entry<T> | ShallowEntry | EntryReplicated<R>;
3558
- leaders: Map<string, any>;
3563
+ entry: CheckedPruneEntry<T, R>;
3564
+ leaders: CheckedPruneLeaderMap;
3559
3565
  };
3560
3566
  }): Promise<boolean> {
3561
3567
  if (this.keep && (await this.keep(args.value.entry))) {
@@ -3565,6 +3571,75 @@ export class SharedLog<
3565
3571
  return true;
3566
3572
  }
3567
3573
 
3574
+ private async cancelCheckedPruneForLocalLeader(hash: string) {
3575
+ this.pruneDebouncedFn.delete(hash);
3576
+ this.clearCheckedPruneRetry(hash);
3577
+ this.removePruneRequestSent(hash);
3578
+ this._requestIPruneResponseReplicatorSet.delete(hash);
3579
+ await this._pendingDeletes
3580
+ .get(hash)
3581
+ ?.reject(new Error("Failed to delete, is leader again"));
3582
+ }
3583
+
3584
+ private hasActiveCheckedPruneWork(hash: string) {
3585
+ return (
3586
+ this._pendingDeletes.has(hash) ||
3587
+ this._requestIPruneSent.has(hash) ||
3588
+ this._requestIPruneResponseReplicatorSet.has(hash) ||
3589
+ this._checkedPruneRetries.has(hash)
3590
+ );
3591
+ }
3592
+
3593
+ private async resolveCheckedPruneLeaders(args: {
3594
+ hash: string;
3595
+ entry: CheckedPruneEntry<T, R>;
3596
+ leaders: CheckedPruneLeaderMap;
3597
+ selfReplicating?: boolean;
3598
+ }): Promise<{
3599
+ leaders: CheckedPruneLeaderMap;
3600
+ localLeader: boolean;
3601
+ }> {
3602
+ const selfHash = this.node.identity.publicKey.hashcode();
3603
+ if (args.leaders.has(selfHash)) {
3604
+ if (args.selfReplicating === false) {
3605
+ return { leaders: args.leaders, localLeader: false };
3606
+ }
3607
+ if (args.selfReplicating == null && !(await this.isReplicating())) {
3608
+ return { leaders: args.leaders, localLeader: false };
3609
+ }
3610
+ return { leaders: args.leaders, localLeader: true };
3611
+ }
3612
+
3613
+ if (!this.hasActiveCheckedPruneWork(args.hash)) {
3614
+ return { leaders: args.leaders, localLeader: false };
3615
+ }
3616
+
3617
+ if (args.selfReplicating === false) {
3618
+ return { leaders: args.leaders, localLeader: false };
3619
+ }
3620
+ if (args.selfReplicating == null && !(await this.isReplicating())) {
3621
+ return { leaders: args.leaders, localLeader: false };
3622
+ }
3623
+
3624
+ try {
3625
+ const currentLeaders = await this.findLeadersFromEntry(
3626
+ args.entry,
3627
+ decodeReplicas(args.entry).getValue(this),
3628
+ );
3629
+ if (currentLeaders.size > 0) {
3630
+ return {
3631
+ leaders: currentLeaders,
3632
+ localLeader: currentLeaders.has(selfHash),
3633
+ };
3634
+ }
3635
+ } catch {
3636
+ // Best-effort only. If the fresh check fails, keep the original prune
3637
+ // decision instead of hiding a legitimately prunable entry.
3638
+ }
3639
+
3640
+ return { leaders: args.leaders, localLeader: false };
3641
+ }
3642
+
3568
3643
  private async pruneJoinedEntriesNoLongerLed(entries: Entry<T>[]) {
3569
3644
  const selfHash = this.node.identity.publicKey.hashcode();
3570
3645
  for (const entry of entries) {
@@ -3579,10 +3654,7 @@ export class SharedLog<
3579
3654
  );
3580
3655
 
3581
3656
  if (leaders.has(selfHash)) {
3582
- this.pruneDebouncedFn.delete(entry.hash);
3583
- await this._pendingDeletes
3584
- .get(entry.hash)
3585
- ?.reject(new Error("Failed to delete, is leader again"));
3657
+ await this.cancelCheckedPruneForLocalLeader(entry.hash);
3586
3658
  continue;
3587
3659
  }
3588
3660
 
@@ -3622,11 +3694,7 @@ export class SharedLog<
3622
3694
  );
3623
3695
 
3624
3696
  if (leaders.has(selfHash)) {
3625
- this.pruneDebouncedFn.delete(entryReplicated.hash);
3626
- await this._pendingDeletes
3627
- .get(entryReplicated.hash)
3628
- ?.reject(new Error("Failed to delete, is leader again"));
3629
- this.removePruneRequestSent(entryReplicated.hash);
3697
+ await this.cancelCheckedPruneForLocalLeader(entryReplicated.hash);
3630
3698
  continue;
3631
3699
  }
3632
3700
 
@@ -3663,8 +3731,8 @@ export class SharedLog<
3663
3731
  }
3664
3732
 
3665
3733
  private scheduleCheckedPruneRetry(args: {
3666
- entry: EntryReplicated<R> | ShallowOrFullEntry<any>;
3667
- leaders: Map<string, unknown> | Set<string>;
3734
+ entry: CheckedPruneEntry<T, R>;
3735
+ leaders: CheckedPruneLeaderMap | Set<string>;
3668
3736
  }) {
3669
3737
  if (this.closed) return;
3670
3738
  if (this._pendingDeletes.has(args.entry.hash)) return;
@@ -3694,7 +3762,7 @@ export class SharedLog<
3694
3762
  if (this.closed) return;
3695
3763
  if (this._pendingDeletes.has(hash)) return;
3696
3764
 
3697
- let leadersMap: Map<string, any> | undefined;
3765
+ let leadersMap: CheckedPruneLeaderMap | undefined;
3698
3766
  try {
3699
3767
  const replicas = decodeReplicas(args.entry).getValue(this);
3700
3768
  leadersMap = await this.findLeadersFromEntry(args.entry, replicas, {
@@ -3704,27 +3772,27 @@ export class SharedLog<
3704
3772
  // Best-effort only.
3705
3773
  }
3706
3774
 
3707
- if (!leadersMap || leadersMap.size === 0) {
3708
- if (args.leaders instanceof Map) {
3709
- leadersMap = args.leaders as any;
3710
- } else {
3711
- leadersMap = new Map<string, any>();
3712
- for (const k of args.leaders) {
3713
- leadersMap.set(k, { intersecting: true });
3714
- }
3775
+ if (!leadersMap || leadersMap.size === 0) {
3776
+ if (args.leaders instanceof Map) {
3777
+ leadersMap = args.leaders;
3778
+ } else {
3779
+ leadersMap = new Map<string, { intersecting: boolean }>();
3780
+ for (const k of args.leaders) {
3781
+ leadersMap.set(k, { intersecting: true });
3715
3782
  }
3716
3783
  }
3784
+ }
3717
3785
 
3718
- try {
3719
- const leadersForRetry = leadersMap ?? new Map<string, any>();
3720
- await this.pruneDebouncedFnAddIfNotKeeping({
3721
- key: hash,
3722
- // TODO types
3723
- value: { entry: args.entry as any, leaders: leadersForRetry },
3724
- });
3725
- } catch {
3726
- // Best-effort only; pruning will be re-attempted on future changes.
3727
- }
3786
+ try {
3787
+ const leadersForRetry =
3788
+ leadersMap ?? new Map<string, { intersecting: boolean }>();
3789
+ await this.pruneDebouncedFnAddIfNotKeeping({
3790
+ key: hash,
3791
+ value: { entry: args.entry, leaders: leadersForRetry },
3792
+ });
3793
+ } catch {
3794
+ // Best-effort only; pruning will be re-attempted on future changes.
3795
+ }
3728
3796
  }, delayMs);
3729
3797
  state.timer.unref?.();
3730
3798
  this._checkedPruneRetries.set(hash, state);
@@ -4084,8 +4152,34 @@ export class SharedLog<
4084
4152
  );
4085
4153
 
4086
4154
  this.pruneDebouncedFn = debouncedAccumulatorMap(
4087
- (map) => {
4088
- this.prune(map);
4155
+ async (map) => {
4156
+ const current = new Map<
4157
+ string,
4158
+ {
4159
+ entry: CheckedPruneEntry<T, R>;
4160
+ leaders: CheckedPruneLeaderMap;
4161
+ }
4162
+ >();
4163
+ const selfReplicating = await this.isReplicating();
4164
+ for (const [hash, value] of map) {
4165
+ const checkedPruneLeaders = await this.resolveCheckedPruneLeaders({
4166
+ hash,
4167
+ entry: value.entry,
4168
+ leaders: value.leaders,
4169
+ selfReplicating,
4170
+ });
4171
+ if (checkedPruneLeaders.localLeader) {
4172
+ await this.cancelCheckedPruneForLocalLeader(hash);
4173
+ continue;
4174
+ }
4175
+ current.set(hash, {
4176
+ ...value,
4177
+ leaders: checkedPruneLeaders.leaders,
4178
+ });
4179
+ }
4180
+ if (current.size > 0) {
4181
+ this.prune(current);
4182
+ }
4089
4183
  },
4090
4184
  PRUNE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
4091
4185
  (into, from) => {
@@ -6943,8 +7037,8 @@ export class SharedLog<
6943
7037
  entries: Map<
6944
7038
  string,
6945
7039
  {
6946
- entry: EntryReplicated<R> | ShallowOrFullEntry<any>;
6947
- leaders: Map<string, unknown> | Set<string>;
7040
+ entry: CheckedPruneEntry<T, R>;
7041
+ leaders: CheckedPruneLeaderMap | Set<string>;
6948
7042
  }
6949
7043
  >,
6950
7044
  options?: { timeout?: number; unchecked?: boolean },
@@ -7553,11 +7647,7 @@ export class SharedLog<
7553
7647
 
7554
7648
  this.responseToPruneDebouncedFn.delete(entryReplicated.hash);
7555
7649
  } else {
7556
- this.pruneDebouncedFn.delete(entryReplicated.hash);
7557
- await this._pendingDeletes
7558
- .get(entryReplicated.hash)
7559
- ?.reject(new Error("Failed to delete, is leader again"));
7560
- this.removePruneRequestSent(entryReplicated.hash);
7650
+ await this.cancelCheckedPruneForLocalLeader(entryReplicated.hash);
7561
7651
  }
7562
7652
  continue;
7563
7653
  }
@@ -7627,11 +7717,7 @@ export class SharedLog<
7627
7717
 
7628
7718
  this.responseToPruneDebouncedFn.delete(entryReplicated.hash); // don't allow others to prune because of expecting me to replicating this entry
7629
7719
  } else {
7630
- this.pruneDebouncedFn.delete(entryReplicated.hash);
7631
- await this._pendingDeletes
7632
- .get(entryReplicated.hash)
7633
- ?.reject(new Error("Failed to delete, is leader again"));
7634
- this.removePruneRequestSent(entryReplicated.hash);
7720
+ await this.cancelCheckedPruneForLocalLeader(entryReplicated.hash);
7635
7721
  }
7636
7722
  }
7637
7723
  }