@peerbit/shared-log 10.3.2 → 10.3.3-2ec6eb5

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/src/index.ts CHANGED
@@ -694,27 +694,38 @@ export class SharedLog<
694
694
 
695
695
  // also merge segments that are already in the index
696
696
  if (this.domain.canMerge) {
697
- const mergable = await getAllMergeCandiates(
697
+ const mergeRangesThatAlreadyExist = await getAllMergeCandiates(
698
698
  this.replicationIndex,
699
699
  range,
700
700
  this.indexableDomain.numbers,
701
701
  );
702
- const mergeableFiltered: ReplicationRangeIndexable<R>[] = [range];
702
+ const mergeableFiltered: ReplicationRangeIndexable<R>[] = [];
703
+ const toKeep: Set<string> = new Set();
703
704
 
704
- for (const mergeCandidate of mergable) {
705
+ for (const [_key, mergeCandidate] of mergeRangesThatAlreadyExist) {
705
706
  if (this.domain.canMerge(mergeCandidate, range)) {
706
707
  mergeableFiltered.push(mergeCandidate);
707
- if (mergeCandidate.idString !== range.idString) {
708
- rangesToUnreplicate.push(mergeCandidate);
709
- }
708
+ } else {
709
+ toKeep.add(mergeCandidate.idString);
710
710
  }
711
711
  }
712
+
713
+ mergeableFiltered.push(range); // * we push this last, because mergeRanges will reuse ids of the first elements
712
714
  if (mergeableFiltered.length > 1) {
715
+ // ** this is important here as we want to reuse ids of what we already persist, not the new ranges, so we dont get a delet add op, but just a update op
713
716
  range = mergeRanges(
714
717
  mergeableFiltered,
715
718
  this.indexableDomain.numbers,
716
719
  );
717
720
  }
721
+ for (const [_key, mergeCandidate] of mergeRangesThatAlreadyExist) {
722
+ if (
723
+ mergeCandidate.idString !== range.idString &&
724
+ !toKeep.has(mergeCandidate.idString)
725
+ ) {
726
+ rangesToUnreplicate.push(mergeCandidate);
727
+ }
728
+ }
718
729
  }
719
730
  rangesToReplicate = [range];
720
731
  }
@@ -1005,6 +1016,9 @@ export class SharedLog<
1005
1016
  this.pendingMaturity.delete(from.hashcode());
1006
1017
  }
1007
1018
  }
1019
+ if (ranges.length === 0) {
1020
+ throw new Error("???");
1021
+ }
1008
1022
 
1009
1023
  await this.replicationIndex.del({
1010
1024
  query: new Or(
@@ -1303,24 +1317,29 @@ export class SharedLog<
1303
1317
  logger.warn("Not allowed to replicate by canReplicate");
1304
1318
  }
1305
1319
 
1306
- let message: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage;
1307
-
1308
1320
  if (change) {
1309
- if (options.reset) {
1310
- message = new AllReplicatingSegmentsMessage({
1311
- segments: range.map((x) => x.toReplicationRange()),
1312
- });
1313
- } else {
1314
- message = new AddedReplicationSegmentMessage({
1315
- segments: range.map((x) => x.toReplicationRange()),
1316
- });
1317
- }
1318
- if (options.announce) {
1319
- return options.announce(message);
1320
- } else {
1321
- await this.rpc.send(message, {
1322
- priority: 1,
1323
- });
1321
+ let addedOrReplaced = change.filter((x) => x.type !== "removed");
1322
+ if (addedOrReplaced.length > 0) {
1323
+ let message:
1324
+ | AllReplicatingSegmentsMessage
1325
+ | AddedReplicationSegmentMessage
1326
+ | undefined = undefined;
1327
+ if (options.reset) {
1328
+ message = new AllReplicatingSegmentsMessage({
1329
+ segments: addedOrReplaced.map((x) => x.range.toReplicationRange()),
1330
+ });
1331
+ } else {
1332
+ message = new AddedReplicationSegmentMessage({
1333
+ segments: addedOrReplaced.map((x) => x.range.toReplicationRange()),
1334
+ });
1335
+ }
1336
+ if (options.announce) {
1337
+ return options.announce(message);
1338
+ } else {
1339
+ await this.rpc.send(message, {
1340
+ priority: 1,
1341
+ });
1342
+ }
1324
1343
  }
1325
1344
  }
1326
1345
  }
@@ -2284,10 +2303,7 @@ export class SharedLog<
2284
2303
  }
2285
2304
 
2286
2305
  if (isLeader) {
2287
- //console.log("IS LEADER", this.node.identity.publicKey.hashcode(), hash);
2288
-
2289
2306
  hasAndIsLeader.push(hash);
2290
-
2291
2307
  hasAndIsLeader.length > 0 &&
2292
2308
  this.responseToPruneDebouncedFn.add({
2293
2309
  hashes: hasAndIsLeader,
@@ -2484,6 +2500,7 @@ export class SharedLog<
2484
2500
  msg.segmentIds,
2485
2501
  context.from,
2486
2502
  );
2503
+
2487
2504
  await this.removeReplicationRanges(rangesToRemove, context.from);
2488
2505
  const timestamp = BigInt(+new Date());
2489
2506
  for (const range of rangesToRemove) {
@@ -3635,7 +3652,6 @@ export class SharedLog<
3635
3652
  });
3636
3653
  }
3637
3654
 
3638
- // console.log("DELETE RESPONSE AS LEADER", this.node.identity.publicKey.hashcode(), entryReplicated.hash)
3639
3655
  this.responseToPruneDebouncedFn.delete(entryReplicated.hash); // don't allow others to prune because of expecting me to replicating this entry
3640
3656
  } else {
3641
3657
  this.pruneDebouncedFn.delete(entryReplicated.hash);
package/src/ranges.ts CHANGED
@@ -1019,68 +1019,112 @@ export const mergeRanges = <R extends "u32" | "u64">(
1019
1019
  throw new Error("Segments have different publicKeyHash");
1020
1020
  }
1021
1021
 
1022
- const sorted = segments.sort((a, b) => Number(a.start1 - b.start1));
1023
-
1024
- let calculateLargeGap = (): [
1025
- NumberFromType<R>,
1026
- number,
1027
- ReplicationIntent,
1028
- ] => {
1029
- let last = sorted[sorted.length - 1];
1030
- let largestArc = numbers.zero;
1031
- let largestArcIndex = -1;
1032
- let mode = ReplicationIntent.NonStrict;
1033
- for (let i = 0; i < sorted.length; i++) {
1034
- const current = sorted[i];
1035
-
1036
- if (current.mode === ReplicationIntent.Strict) {
1037
- mode = ReplicationIntent.Strict;
1038
- }
1039
-
1040
- if (current.start1 !== last.start1) {
1041
- let arc = numbers.zero;
1042
- if (current.start1 < last.end2) {
1043
- arc += ((numbers.maxValue as any) - last.end2) as any;
1044
-
1045
- arc += (current.start1 - numbers.zero) as any;
1046
- } else {
1047
- arc += (current.start1 - last.end2) as any;
1048
- }
1022
+ // 1) Sort by start offset (avoid subtracting bigints).
1023
+ // We do slice() to avoid mutating the original 'segments'.
1024
+ const sorted = segments.slice().sort((a, b) => {
1025
+ if (a.start1 < b.start1) return -1;
1026
+ if (a.start1 > b.start1) return 1;
1027
+ return 0;
1028
+ });
1049
1029
 
1050
- if (arc > largestArc) {
1051
- largestArc = arc;
1052
- largestArcIndex = i;
1053
- }
1030
+ // 2) Merge overlapping arcs in a purely functional way
1031
+ // so we don’t mutate any intermediate objects.
1032
+ const merged = sorted.reduce<ReplicationRangeIndexable<R>[]>(
1033
+ (acc, current) => {
1034
+ if (acc.length === 0) {
1035
+ return [current];
1054
1036
  }
1055
- last = current;
1056
- }
1057
1037
 
1058
- return [largestArc, largestArcIndex, mode];
1059
- };
1060
- const [largestArc, largestArcIndex, mode] = calculateLargeGap();
1038
+ const last = acc[acc.length - 1];
1039
+
1040
+ // Check overlap: next arc starts before (or exactly at) last arc's end => overlap
1041
+ if (current.start1 <= last.end2) {
1042
+ // Merge them:
1043
+ // - end2 is the max of last.end2 and current.end2
1044
+ // - width is adjusted so total covers both
1045
+ // - mode is strict if either arc is strict
1046
+ const newEnd2 = last.end2 > current.end2 ? last.end2 : current.end2;
1047
+ const extendedWidth = Number(newEnd2 - last.start1); // safe if smaller arcs
1048
+
1049
+ // If you need to handle big widths carefully, you might do BigInt logic here.
1050
+ const newMode =
1051
+ last.mode === ReplicationIntent.Strict ||
1052
+ current.mode === ReplicationIntent.Strict
1053
+ ? ReplicationIntent.Strict
1054
+ : ReplicationIntent.NonStrict;
1055
+
1056
+ // Create a new merged arc object (no mutation of last)
1057
+ const proto = segments[0].constructor as any;
1058
+ const mergedArc = new proto({
1059
+ width: extendedWidth,
1060
+ offset: last.start1,
1061
+ publicKeyHash: last.hash,
1062
+ mode: newMode,
1063
+ id: last.id, // re-use id
1064
+ });
1065
+
1066
+ // Return a new array with the last item replaced by mergedArc
1067
+ return [...acc.slice(0, -1), mergedArc];
1068
+ } else {
1069
+ // No overlap => just append current
1070
+ return [...acc, current];
1071
+ }
1072
+ },
1073
+ [],
1074
+ );
1061
1075
 
1062
- let totalLengthFinal: number = numbers.maxValue - largestArc;
1076
+ // After the merge pass:
1077
+ if (merged.length === 1) {
1078
+ // Everything merged into one arc already
1079
+ return merged[0];
1080
+ }
1081
+
1082
+ // 3) OPTIONAL: If your existing logic always wants to produce a single ring arc
1083
+ // that covers "everything except the largest gap," do it here:
1084
+
1085
+ // Determine if any arc ended up Strict
1086
+ const finalMode = merged.some((m) => m.mode === ReplicationIntent.Strict)
1087
+ ? ReplicationIntent.Strict
1088
+ : ReplicationIntent.NonStrict;
1089
+
1090
+ // Find the largest gap on a ring among these disjoint arcs
1091
+ const { largestGap, largestGapIndex } = merged.reduce<{
1092
+ largestGap: NumberFromType<R>;
1093
+ largestGapIndex: number;
1094
+ }>(
1095
+ (acc, arc, i, arr) => {
1096
+ // next arc in a ring
1097
+ const nextArc = arr[(i + 1) % arr.length];
1098
+
1099
+ // measure gap from arc.end2 -> nextArc.start1
1100
+ let gap: NumberFromType<R>;
1101
+ if (nextArc.start1 < arc.end2) {
1102
+ // wrap-around scenario
1103
+ gap = (numbers.maxValue -
1104
+ arc.end2 +
1105
+ (nextArc.start1 - numbers.zero)) as NumberFromType<R>;
1106
+ } else {
1107
+ gap = (nextArc.start1 - arc.end2) as NumberFromType<R>;
1108
+ }
1063
1109
 
1064
- const proto = segments[0].constructor;
1110
+ if (gap > acc.largestGap) {
1111
+ return { largestGap: gap, largestGapIndex: (i + 1) % arr.length };
1112
+ }
1113
+ return acc;
1114
+ },
1115
+ { largestGap: numbers.zero, largestGapIndex: -1 },
1116
+ );
1065
1117
 
1066
- if (largestArcIndex === -1) {
1067
- if (mode !== segments[0].mode) {
1068
- return new (proto as any)({
1069
- width: segments[0].width,
1070
- offset: segments[0].start1,
1071
- publicKeyHash: segments[0].hash,
1072
- mode,
1073
- });
1074
- }
1075
- return segments[0]; // all ranges are the same
1076
- }
1077
- // use segments[0] constructor to create a new object
1118
+ // Single arc coverage = "the ring minus largestGap"
1119
+ const totalCoverage = (numbers.maxValue - largestGap) as number;
1120
+ const offset = merged[largestGapIndex].start1;
1078
1121
 
1079
- return new (proto as any)({
1080
- width: totalLengthFinal,
1081
- offset: segments[largestArcIndex].start1,
1122
+ const proto = segments[0].constructor as any;
1123
+ return new proto({
1124
+ width: totalCoverage,
1125
+ offset,
1082
1126
  publicKeyHash: segments[0].hash,
1083
- mode,
1127
+ mode: finalMode,
1084
1128
  });
1085
1129
  };
1086
1130
 
@@ -1802,7 +1846,7 @@ export const getAllMergeCandiates = async <R extends "u32" | "u64">(
1802
1846
  id: Uint8Array;
1803
1847
  },
1804
1848
  numbers: Numbers<R>,
1805
- ): Promise<MapIterator<ReplicationRangeIndexable<R>>> => {
1849
+ ): Promise<Map<string, ReplicationRangeIndexable<R>>> => {
1806
1850
  const adjacent = await getAdjecentSameOwner(peers, range, numbers);
1807
1851
  const covering = await getCoveringRangesSameOwner(peers, range).all();
1808
1852
 
@@ -1816,7 +1860,7 @@ export const getAllMergeCandiates = async <R extends "u32" | "u64">(
1816
1860
  for (const range of covering) {
1817
1861
  ret.set(range.value.idString, range.value);
1818
1862
  }
1819
- return ret.values();
1863
+ return ret;
1820
1864
  };
1821
1865
 
1822
1866
  export const isMatured = (