@peerbit/shared-log 11.0.4 → 11.0.5-d8e5bfb

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
@@ -48,6 +48,7 @@ import {
48
48
  } from "@peerbit/stream-interface";
49
49
  import {
50
50
  AbortError,
51
+ TimeoutError,
51
52
  debounceAccumulator,
52
53
  debounceFixedInterval,
53
54
  waitFor,
@@ -1793,6 +1794,9 @@ export class SharedLog<
1793
1794
 
1794
1795
  await this.rpc.subscribe();
1795
1796
 
1797
+ // mark all our replicaiton ranges as "new", this would allow other peers to understand that we recently reopend our database and might need some sync and warmup
1798
+ await this.updateTimestampOfOwnedReplicationRanges(); // TODO do we need to do this before subscribing?
1799
+
1796
1800
  // if we had a previous session with replication info, and new replication info dictates that we unreplicate
1797
1801
  // we should do that. Otherwise if options is a unreplication we dont need to do anything because
1798
1802
  // we are already unreplicated (as we are just opening)
@@ -1820,6 +1824,38 @@ export class SharedLog<
1820
1824
  }, RECALCULATE_PARTICIPATION_DEBOUNCE_INTERVAL);
1821
1825
  }
1822
1826
 
1827
+ private async updateTimestampOfOwnedReplicationRanges(
1828
+ timestamp: number = +new Date(),
1829
+ ) {
1830
+ const all = await this.replicationIndex
1831
+ .iterate({
1832
+ query: { hash: this.node.identity.publicKey.hashcode() },
1833
+ })
1834
+ .all();
1835
+ let bnTimestamp = BigInt(timestamp);
1836
+ for (const x of all) {
1837
+ x.value.timestamp = bnTimestamp;
1838
+ await this.replicationIndex.put(x.value);
1839
+ }
1840
+
1841
+ if (all.length > 0) {
1842
+ // emit mature event
1843
+ const maturityTimeout = setTimeout(
1844
+ () => {
1845
+ this.events.dispatchEvent(
1846
+ new CustomEvent<ReplicationChangeEvent>("replicator:mature", {
1847
+ detail: { publicKey: this.node.identity.publicKey },
1848
+ }),
1849
+ );
1850
+ },
1851
+ await this.getDefaultMinRoleAge(),
1852
+ );
1853
+ this._closeController.signal.addEventListener("abort", () => {
1854
+ clearTimeout(maturityTimeout);
1855
+ });
1856
+ }
1857
+ }
1858
+
1823
1859
  async afterOpen(): Promise<void> {
1824
1860
  await super.afterOpen();
1825
1861
 
@@ -2615,12 +2651,16 @@ export class SharedLog<
2615
2651
  start?: NumberFromType<R>;
2616
2652
  /** Optional: end of the content range (exclusive) */
2617
2653
  end?: NumberFromType<R>;
2654
+
2655
+ /** Optional: roleAge (in ms) */
2656
+ roleAge?: number;
2618
2657
  }) {
2619
2658
  return calculateCoverage({
2620
2659
  numbers: this.indexableDomain.numbers,
2621
2660
  peers: this.replicationIndex,
2622
2661
  end: properties?.end,
2623
2662
  start: properties?.start,
2663
+ roleAge: properties?.roleAge,
2624
2664
  });
2625
2665
  }
2626
2666
 
@@ -2720,38 +2760,6 @@ export class SharedLog<
2720
2760
  return set;
2721
2761
  }
2722
2762
 
2723
- async waitForReplicator(...keys: PublicSignKey[]) {
2724
- const check = async () => {
2725
- for (const k of keys) {
2726
- const iterator = this.replicationIndex?.iterate(
2727
- { query: new StringMatch({ key: "hash", value: k.hashcode() }) },
2728
- { reference: true },
2729
- );
2730
- const rects = await iterator?.next(1);
2731
- await iterator.close();
2732
- const rect = rects[0]?.value;
2733
- if (
2734
- !rect ||
2735
- !isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())
2736
- ) {
2737
- return false;
2738
- }
2739
- }
2740
- return true;
2741
- };
2742
-
2743
- // TODO do event based
2744
- return waitFor(() => check(), {
2745
- signal: this._closeController.signal,
2746
- }).catch((e) => {
2747
- if (e instanceof AbortError) {
2748
- // ignore error
2749
- return;
2750
- }
2751
- throw e;
2752
- });
2753
- }
2754
-
2755
2763
  async join(
2756
2764
  entries: (string | Entry<T> | ShallowEntry)[],
2757
2765
  options?: {
@@ -2886,6 +2894,100 @@ export class SharedLog<
2886
2894
  }
2887
2895
  }
2888
2896
 
2897
+ async waitForReplicator(...keys: PublicSignKey[]) {
2898
+ const check = async () => {
2899
+ for (const k of keys) {
2900
+ const iterator = this.replicationIndex?.iterate(
2901
+ { query: new StringMatch({ key: "hash", value: k.hashcode() }) },
2902
+ { reference: true },
2903
+ );
2904
+ const rects = await iterator?.next(1);
2905
+ await iterator.close();
2906
+ const rect = rects[0]?.value;
2907
+ if (
2908
+ !rect ||
2909
+ !isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())
2910
+ ) {
2911
+ return false;
2912
+ }
2913
+ }
2914
+ return true;
2915
+ };
2916
+
2917
+ // TODO do event based
2918
+ return waitFor(() => check(), {
2919
+ signal: this._closeController.signal,
2920
+ }).catch((e) => {
2921
+ if (e instanceof AbortError) {
2922
+ // ignore error
2923
+ return;
2924
+ }
2925
+ throw e;
2926
+ });
2927
+ }
2928
+
2929
+ async waitForReplicators(options?: {
2930
+ timeout?: number;
2931
+ roleAge?: number;
2932
+ signal?: AbortSignal;
2933
+ coverageThreshold?: number;
2934
+ }) {
2935
+ let coverageThreshold = options?.coverageThreshold ?? 0.99;
2936
+ let deferred = pDefer<void>();
2937
+ const roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge());
2938
+ const providedCustomRoleAge = options?.roleAge != null;
2939
+
2940
+ let checkCoverage = async () => {
2941
+ const coverage = await this.calculateCoverage({
2942
+ roleAge,
2943
+ });
2944
+ if (coverage > coverageThreshold) {
2945
+ deferred.resolve();
2946
+ return true;
2947
+ }
2948
+ return false;
2949
+ };
2950
+ this.events.addEventListener("replicator:mature", checkCoverage);
2951
+ this.events.addEventListener("replication:change", checkCoverage);
2952
+ await checkCoverage();
2953
+
2954
+ let interval = providedCustomRoleAge
2955
+ ? setInterval(() => {
2956
+ checkCoverage();
2957
+ }, 100)
2958
+ : undefined;
2959
+
2960
+ let timeout = options?.timeout ?? this.waitForReplicatorTimeout;
2961
+ const timer = setTimeout(() => {
2962
+ clear();
2963
+ deferred.reject(
2964
+ new TimeoutError(`Timeout waiting for mature replicators`),
2965
+ );
2966
+ }, timeout);
2967
+
2968
+ const abortListener = () => {
2969
+ clear();
2970
+ deferred.reject(new AbortError());
2971
+ };
2972
+
2973
+ if (options?.signal) {
2974
+ options.signal.addEventListener("abort", abortListener);
2975
+ }
2976
+ const clear = () => {
2977
+ interval && clearInterval(interval);
2978
+ this.events.removeEventListener("join", checkCoverage);
2979
+ this.events.removeEventListener("leave", checkCoverage);
2980
+ clearTimeout(timer);
2981
+ if (options?.signal) {
2982
+ options.signal.removeEventListener("abort", abortListener);
2983
+ }
2984
+ };
2985
+
2986
+ return deferred.promise.finally(() => {
2987
+ return clear();
2988
+ });
2989
+ }
2990
+
2889
2991
  private async _waitForReplicators(
2890
2992
  cursors: NumberFromType<R>[],
2891
2993
  entry: Entry<T> | EntryReplicated<R> | ShallowEntry,
@@ -3046,9 +3148,10 @@ export class SharedLog<
3046
3148
  }
3047
3149
 
3048
3150
  const now = +new Date();
3049
- const subscribers =
3050
- (await this.node.services.pubsub.getSubscribers(this.rpc.topic))
3051
- ?.length ?? 1;
3151
+ const subscribers = this.rpc.closed
3152
+ ? 1
3153
+ : ((await this.node.services.pubsub.getSubscribers(this.rpc.topic))
3154
+ ?.length ?? 1);
3052
3155
  const diffToOldest =
3053
3156
  subscribers > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
3054
3157
 
package/src/ranges.ts CHANGED
@@ -1372,6 +1372,9 @@ export const calculateCoverage = async <R extends "u32" | "u64">(properties: {
1372
1372
  start?: NumberFromType<R>;
1373
1373
  /** Optional: end of the content range (exclusive) */
1374
1374
  end?: NumberFromType<R>;
1375
+
1376
+ /** Optional: role age limit in milliseconds */
1377
+ roleAge?: number;
1375
1378
  }): Promise<number> => {
1376
1379
  // Use the provided content range if given; otherwise use the default full range.
1377
1380
  const contentStart = properties.start ?? properties.numbers.zero;
@@ -1385,12 +1388,14 @@ export const calculateCoverage = async <R extends "u32" | "u64">(properties: {
1385
1388
  numbers: properties.numbers,
1386
1389
  start: contentStart,
1387
1390
  end: properties.numbers.maxValue,
1391
+ roleAge: properties.roleAge,
1388
1392
  });
1389
1393
  const coverage2 = await calculateCoverage({
1390
1394
  peers: properties.peers,
1391
1395
  numbers: properties.numbers,
1392
1396
  start: properties.numbers.zero,
1393
1397
  end: contentEnd,
1398
+ roleAge: properties.roleAge,
1394
1399
  });
1395
1400
 
1396
1401
  return Math.min(coverage1, coverage2);
@@ -1399,7 +1404,21 @@ export const calculateCoverage = async <R extends "u32" | "u64">(properties: {
1399
1404
  const endpoints: { point: NumberFromType<R>; delta: -1 | 1 }[] = [];
1400
1405
 
1401
1406
  // For each range, record its start and end as events.
1402
- for (const r of await properties.peers.iterate().all()) {
1407
+ const timeThresholdQuery =
1408
+ properties?.roleAge != null
1409
+ ? [
1410
+ new IntegerCompare({
1411
+ key: "timestamp",
1412
+ compare: Compare.LessOrEqual,
1413
+ value: BigInt(Date.now() - properties.roleAge),
1414
+ }),
1415
+ ]
1416
+ : undefined;
1417
+ for (const r of await properties.peers
1418
+ .iterate({
1419
+ query: timeThresholdQuery,
1420
+ })
1421
+ .all()) {
1403
1422
  endpoints.push({ point: r.value.start1, delta: +1 });
1404
1423
  endpoints.push({ point: r.value.end1, delta: -1 });
1405
1424
 
@@ -541,7 +541,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
541
541
  if (outProcess === true) {
542
542
  return true;
543
543
  } else if (outProcess === undefined) {
544
- return false; // we don't have enough information, or received information that is redundant
544
+ return true; // we don't have enough information, or received information that is redundant
545
545
  }
546
546
 
547
547
  // we are not done