@peerbit/shared-log 13.1.18 → 13.2.1

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 (59) hide show
  1. package/dist/benchmark/index.js +91 -29
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/native-graph.js +120 -10
  4. package/dist/benchmark/native-graph.js.map +1 -1
  5. package/dist/benchmark/network-preset-e2e.d.ts +2 -0
  6. package/dist/benchmark/network-preset-e2e.d.ts.map +1 -0
  7. package/dist/benchmark/network-preset-e2e.js +497 -0
  8. package/dist/benchmark/network-preset-e2e.js.map +1 -0
  9. package/dist/benchmark/receive-prune.d.ts +2 -0
  10. package/dist/benchmark/receive-prune.d.ts.map +1 -0
  11. package/dist/benchmark/receive-prune.js +816 -0
  12. package/dist/benchmark/receive-prune.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +1 -2
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -1
  15. package/dist/benchmark/sync-catchup.js +175 -7
  16. package/dist/benchmark/sync-catchup.js.map +1 -1
  17. package/dist/src/checked-prune.d.ts +3 -0
  18. package/dist/src/checked-prune.d.ts.map +1 -1
  19. package/dist/src/checked-prune.js +24 -0
  20. package/dist/src/checked-prune.js.map +1 -1
  21. package/dist/src/exchange-heads.d.ts +238 -1
  22. package/dist/src/exchange-heads.d.ts.map +1 -1
  23. package/dist/src/exchange-heads.js +1512 -36
  24. package/dist/src/exchange-heads.js.map +1 -1
  25. package/dist/src/index.d.ts +276 -5
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +7358 -666
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/pid.d.ts.map +1 -1
  30. package/dist/src/pid.js +25 -9
  31. package/dist/src/pid.js.map +1 -1
  32. package/dist/src/ranges.d.ts +11 -2
  33. package/dist/src/ranges.d.ts.map +1 -1
  34. package/dist/src/ranges.js +74 -30
  35. package/dist/src/ranges.js.map +1 -1
  36. package/dist/src/sync/index.d.ts +77 -2
  37. package/dist/src/sync/index.d.ts.map +1 -1
  38. package/dist/src/sync/rateless-iblt.d.ts +11 -5
  39. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  40. package/dist/src/sync/rateless-iblt.js +123 -60
  41. package/dist/src/sync/rateless-iblt.js.map +1 -1
  42. package/dist/src/sync/simple.d.ts +56 -4
  43. package/dist/src/sync/simple.d.ts.map +1 -1
  44. package/dist/src/sync/simple.js +495 -63
  45. package/dist/src/sync/simple.js.map +1 -1
  46. package/dist/src/utils.d.ts +2 -1
  47. package/dist/src/utils.d.ts.map +1 -1
  48. package/dist/src/utils.js +49 -2
  49. package/dist/src/utils.js.map +1 -1
  50. package/package.json +27 -17
  51. package/src/checked-prune.ts +27 -0
  52. package/src/exchange-heads.ts +2018 -41
  53. package/src/index.ts +13114 -3167
  54. package/src/pid.ts +24 -13
  55. package/src/ranges.ts +90 -40
  56. package/src/sync/index.ts +115 -2
  57. package/src/sync/rateless-iblt.ts +154 -75
  58. package/src/sync/simple.ts +590 -95
  59. package/src/utils.ts +73 -3
package/src/pid.ts CHANGED
@@ -56,22 +56,32 @@ export class PIDReplicationController {
56
56
  // prunes from repeatedly settling just above the hard budget.
57
57
  const effectiveMemoryLimit =
58
58
  this.maxMemoryLimit > 0 ? this.maxMemoryLimit * 0.95 : 0;
59
- errorMemory =
60
- currentFactor > 0 && memoryUsage > 0
61
- ? Math.max(
62
- Math.min(1, effectiveMemoryLimit / estimatedTotalSize),
63
- 0,
64
- ) - currentFactor
65
- : 0;
59
+ if (effectiveMemoryLimit <= 0) {
60
+ // A zero storage budget can never hold data; drive the factor to zero
61
+ // instead of leaving the memory term neutral.
62
+ errorMemory = -currentFactor;
63
+ } else if (currentFactor > 0 && memoryUsage > 0) {
64
+ errorMemory =
65
+ Math.max(Math.min(1, effectiveMemoryLimit / estimatedTotalSize), 0) -
66
+ currentFactor;
67
+ } else {
68
+ errorMemory = 0;
69
+ }
66
70
  // Math.max(Math.min((this.maxMemoryLimit - memoryUsage) / 100e5, 1), -1)// Math.min(Math.max((this.maxMemoryLimit - memoryUsage, 0) / 10e5, 0), 1);
67
71
  }
68
72
 
69
73
  const errorCoverageUnmodified = Math.min(1 - totalFactor, 1);
70
74
  let errorCoverage =
71
- (this.maxMemoryLimit ? 1 - Math.sqrt(Math.abs(errorMemory)) : 1) *
75
+ (this.maxMemoryLimit != null
76
+ ? this.maxMemoryLimit > 0
77
+ ? 1 - Math.sqrt(Math.abs(errorMemory))
78
+ : 0
79
+ : 1) *
72
80
  errorCoverageUnmodified;
73
81
 
74
82
  const errorFromEven = 1 / peerCount - currentFactor;
83
+ const hasMemoryHeadroom =
84
+ this.maxMemoryLimit != null && this.maxMemoryLimit > 0 && errorMemory > 0;
75
85
  // When the network is under-covered (`totalFactor < 1`) balancing "down" (negative
76
86
  // error) can further reduce coverage and force constrained peers (memory/CPU limited)
77
87
  // to take boundary assignments that exceed their budgets.
@@ -81,11 +91,12 @@ export class PIDReplicationController {
81
91
  const coverageDeficit = Math.max(0, errorCoverageUnmodified); // ~= max(0, 1 - totalFactor)
82
92
  const negativeBalanceScale =
83
93
  coverageDeficit <= 0 ? 1 : 1 - Math.min(1, coverageDeficit / 0.1); // full clamp at 10% deficit
84
- const errorFromEvenForBalance =
94
+ let errorFromEvenForBalance =
85
95
  errorFromEven >= 0 ? errorFromEven : errorFromEven * negativeBalanceScale;
96
+ if (hasMemoryHeadroom && coverageDeficit > 0 && errorFromEvenForBalance < 0) {
97
+ errorFromEvenForBalance = 0;
98
+ }
86
99
 
87
- const hasMemoryHeadroom =
88
- this.maxMemoryLimit != null && this.maxMemoryLimit > 0 && errorMemory > 0;
89
100
  if (hasMemoryHeadroom && errorFromEvenForBalance > 0) {
90
101
  // Coverage surplus often means another peer has not pruned yet. Do not let
91
102
  // that transient surplus cancel a constrained peer that is still below an
@@ -93,7 +104,7 @@ export class PIDReplicationController {
93
104
  errorCoverage = Math.max(errorCoverage, 0);
94
105
  }
95
106
 
96
- const balanceErrorScaler = this.maxMemoryLimit
107
+ const balanceErrorScaler = this.maxMemoryLimit != null
97
108
  ? hasMemoryHeadroom
98
109
  ? Math.max(
99
110
  Math.abs(errorMemory),
@@ -105,7 +116,7 @@ export class PIDReplicationController {
105
116
  // Balance should be symmetric (allow negative error) so a peer can *reduce*
106
117
  // participation when peerCount increases. Otherwise early joiners can get
107
118
  // "stuck" over-replicating even after new peers join (no memory/CPU limits).
108
- const errorBalance = this.maxMemoryLimit
119
+ const errorBalance = this.maxMemoryLimit != null
109
120
  ? // Only balance when we have spare memory headroom. When memory is
110
121
  // constrained (`errorMemory < 0`) the memory term will dominate anyway.
111
122
  errorMemory > 0
package/src/ranges.ts CHANGED
@@ -96,6 +96,7 @@ export interface EntryReplicated<R extends "u32" | "u64"> {
96
96
  wallTime: bigint;
97
97
  assignedToRangeBoundary: boolean;
98
98
  get meta(): ShallowMeta;
99
+ getMetaBytes(): Uint8Array;
99
100
  }
100
101
 
101
102
  export const isEntryReplicated = (x: any): x is EntryReplicated<any> => {
@@ -130,22 +131,33 @@ export class EntryReplicatedU32 implements EntryReplicated<"u32"> {
130
131
  constructor(properties: {
131
132
  coordinates: number[];
132
133
  hash: string;
133
- meta: Meta;
134
+ meta?: Meta | ShallowMeta;
135
+ metaBytes?: Uint8Array;
136
+ gid?: string;
137
+ wallTime?: bigint;
134
138
  assignedToRangeBoundary: boolean;
135
139
  hashNumber: number;
136
140
  }) {
141
+ if (!properties.meta && !properties.metaBytes) {
142
+ throw new Error("Expected meta or metaBytes");
143
+ }
144
+ if (!properties.meta && (properties.gid == null || properties.wallTime == null)) {
145
+ throw new Error("Expected gid and wallTime with metaBytes");
146
+ }
137
147
  this.coordinates = properties.coordinates;
138
148
  this.hash = properties.hash;
139
- this.gid = properties.meta.gid;
140
- this.wallTime = properties.meta.clock.timestamp.wallTime;
149
+ this.gid = properties.meta?.gid ?? properties.gid!;
150
+ this.wallTime =
151
+ properties.meta?.clock.timestamp.wallTime ?? properties.wallTime!;
141
152
  this.hashNumber = properties.hashNumber;
142
- const shallow =
143
- properties.meta instanceof Meta
144
- ? new ShallowMeta(properties.meta)
145
- : properties.meta;
146
- this._meta = serialize(shallow);
147
- this._metaResolved = deserialize(this._meta, ShallowMeta);
148
- this._metaResolved = properties.meta;
153
+ this._meta =
154
+ properties.metaBytes ??
155
+ serialize(
156
+ properties.meta instanceof Meta
157
+ ? new ShallowMeta(properties.meta)
158
+ : properties.meta!,
159
+ );
160
+ this._metaResolved = properties.meta as ShallowMeta;
149
161
  this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
150
162
  }
151
163
 
@@ -155,6 +167,10 @@ export class EntryReplicatedU32 implements EntryReplicated<"u32"> {
155
167
  }
156
168
  return this._metaResolved;
157
169
  }
170
+
171
+ getMetaBytes(): Uint8Array {
172
+ return this._meta;
173
+ }
158
174
  }
159
175
 
160
176
  @variant("entry-u64")
@@ -185,22 +201,33 @@ export class EntryReplicatedU64 implements EntryReplicated<"u64"> {
185
201
  constructor(properties: {
186
202
  coordinates: bigint[];
187
203
  hash: string;
188
- meta: Meta;
204
+ meta?: Meta | ShallowMeta;
205
+ metaBytes?: Uint8Array;
206
+ gid?: string;
207
+ wallTime?: bigint;
189
208
  assignedToRangeBoundary: boolean;
190
209
  hashNumber: bigint;
191
210
  }) {
211
+ if (!properties.meta && !properties.metaBytes) {
212
+ throw new Error("Expected meta or metaBytes");
213
+ }
214
+ if (!properties.meta && (properties.gid == null || properties.wallTime == null)) {
215
+ throw new Error("Expected gid and wallTime with metaBytes");
216
+ }
192
217
  this.coordinates = properties.coordinates;
193
218
  this.hash = properties.hash;
194
219
  this.hashNumber = properties.hashNumber;
195
- this.gid = properties.meta.gid;
196
- this.wallTime = properties.meta.clock.timestamp.wallTime;
197
- const shallow =
198
- properties.meta instanceof Meta
199
- ? new ShallowMeta(properties.meta)
200
- : properties.meta;
201
- this._meta = serialize(shallow);
202
- this._metaResolved = deserialize(this._meta, ShallowMeta);
203
- this._metaResolved = properties.meta;
220
+ this.gid = properties.meta?.gid ?? properties.gid!;
221
+ this.wallTime =
222
+ properties.meta?.clock.timestamp.wallTime ?? properties.wallTime!;
223
+ this._meta =
224
+ properties.metaBytes ??
225
+ serialize(
226
+ properties.meta instanceof Meta
227
+ ? new ShallowMeta(properties.meta)
228
+ : properties.meta!,
229
+ );
230
+ this._metaResolved = properties.meta as ShallowMeta;
204
231
  this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
205
232
  }
206
233
 
@@ -210,6 +237,10 @@ export class EntryReplicatedU64 implements EntryReplicated<"u64"> {
210
237
  }
211
238
  return this._metaResolved;
212
239
  }
240
+
241
+ getMetaBytes(): Uint8Array {
242
+ return this._meta;
243
+ }
213
244
  }
214
245
 
215
246
  export const isReplicationRangeMessage = (
@@ -1310,6 +1341,11 @@ const allRangesContainingPoint = async <
1310
1341
  },
1311
1342
  options,
1312
1343
  );
1344
+ try {
1345
+ (await firstIterator.all()).forEach((x) => allResults.push(x));
1346
+ } finally {
1347
+ await firstIterator.close();
1348
+ }
1313
1349
 
1314
1350
  const secondIterator = rects.iterate(
1315
1351
  {
@@ -1318,10 +1354,11 @@ const allRangesContainingPoint = async <
1318
1354
  },
1319
1355
  options,
1320
1356
  );
1321
-
1322
- [...(await firstIterator.all()), ...(await secondIterator.all())].forEach(
1323
- (x) => allResults.push(x),
1324
- );
1357
+ try {
1358
+ (await secondIterator.all()).forEach((x) => allResults.push(x));
1359
+ } finally {
1360
+ await secondIterator.close();
1361
+ }
1325
1362
  }
1326
1363
  return allResults;
1327
1364
  /* return [...await iterateRangesContainingPoint(rects, points, options).all()]; */
@@ -2040,15 +2077,19 @@ const collectClosestAround = async <R extends "u32" | "u64">(
2040
2077
  );
2041
2078
 
2042
2079
  let visited = new Set<string>();
2043
- while (aroundIterator.done() !== true && done() !== true) {
2044
- const res = await aroundIterator.next(100);
2045
- for (const rect of res) {
2046
- visited.add(rect.value.idString);
2047
- collector(rect.value, isMatured(rect.value, now, roleAge));
2048
- if (done()) {
2049
- return;
2080
+ try {
2081
+ while (aroundIterator.done() !== true && done() !== true) {
2082
+ const res = await aroundIterator.next(100);
2083
+ for (const rect of res) {
2084
+ visited.add(rect.value.idString);
2085
+ collector(rect.value, isMatured(rect.value, now, roleAge));
2086
+ if (done()) {
2087
+ return;
2088
+ }
2050
2089
  }
2051
2090
  }
2091
+ } finally {
2092
+ await aroundIterator.close();
2052
2093
  }
2053
2094
  };
2054
2095
 
@@ -2709,6 +2750,8 @@ export const mergeReplicationChanges = <R extends NumericType>(
2709
2750
  return all;
2710
2751
  };
2711
2752
 
2753
+ const REBALANCE_ITERATOR_BATCH_SIZE = 1048;
2754
+
2712
2755
  export const toRebalance = <R extends "u32" | "u64">(
2713
2756
  changeOrChanges:
2714
2757
  | ReplicationChanges<ReplicationRangeIndexable<R>>
@@ -2718,22 +2761,29 @@ export const toRebalance = <R extends "u32" | "u64">(
2718
2761
  options?: { forceFresh?: boolean },
2719
2762
  ): AsyncIterable<EntryReplicated<R>> => {
2720
2763
  const change = options?.forceFresh
2721
- ? (Array.isArray(changeOrChanges[0])
2722
- ? (changeOrChanges as ReplicationChanges<ReplicationRangeIndexable<R>>[])
2723
- .flat()
2724
- : (changeOrChanges as ReplicationChanges<ReplicationRangeIndexable<R>>))
2764
+ ? Array.isArray(changeOrChanges[0])
2765
+ ? (
2766
+ changeOrChanges as ReplicationChanges<ReplicationRangeIndexable<R>>[]
2767
+ ).flat()
2768
+ : (changeOrChanges as ReplicationChanges<ReplicationRangeIndexable<R>>)
2725
2769
  : mergeReplicationChanges(changeOrChanges, rebalanceHistory);
2726
2770
  return {
2727
2771
  [Symbol.asyncIterator]: async function* () {
2728
2772
  const iterator = index.iterate({
2729
2773
  query: createAssignedRangesQuery(change),
2730
2774
  });
2731
-
2732
- while (iterator.done() !== true) {
2733
- const entries = await iterator.all(); // TODO choose right batch sizes here for optimal memory usage / speed
2734
- for (const entry of entries) {
2735
- yield entry.value;
2775
+ try {
2776
+ while (iterator.done() !== true) {
2777
+ const entries = await iterator.next(REBALANCE_ITERATOR_BATCH_SIZE);
2778
+ if (entries.length === 0) {
2779
+ break;
2780
+ }
2781
+ for (const entry of entries) {
2782
+ yield entry.value;
2783
+ }
2736
2784
  }
2785
+ } finally {
2786
+ await iterator.close();
2737
2787
  }
2738
2788
  },
2739
2789
  };
package/src/sync/index.ts CHANGED
@@ -15,6 +15,11 @@ export type SyncPriorityFn<R extends "u32" | "u64"> = (
15
15
  entry: EntryReplicated<R>,
16
16
  ) => number;
17
17
 
18
+ export type SyncEntryCoordinates<R extends "u32" | "u64"> = Pick<
19
+ EntryReplicated<R>,
20
+ "assignedToRangeBoundary" | "hash" | "hashNumber"
21
+ >;
22
+
18
23
  export type SyncOptions<R extends "u32" | "u64"> = {
19
24
  /**
20
25
  * Orders entries inside a sync batch; higher numbers are selected first.
@@ -39,6 +44,22 @@ export type SyncOptions<R extends "u32" | "u64"> = {
39
44
  */
40
45
  maxSimpleCoordinatesPerMessage?: number;
41
46
 
47
+ /**
48
+ * Experimental sync path for peers known to support raw exchange-head
49
+ * responses. When enabled, simple sync requests advertise raw-head support
50
+ * and capable responders can avoid full Entry materialization before sending.
51
+ */
52
+ rawExchangeHeads?: boolean;
53
+
54
+ /**
55
+ * Experimental receive-side raw-head parsing mode for native backbone. When
56
+ * enabled, signature verification happens while raw entries are decoded
57
+ * instead of during the later native commit step. When unset, shared-log may
58
+ * enable this automatically for retained native receive paths where planning
59
+ * already indicates that the local peer is replicating.
60
+ */
61
+ rawExchangeHeadsVerifySignaturesDuringPrepare?: boolean;
62
+
42
63
  /**
43
64
  * Maximum number of hashes tracked per convergent repair session target.
44
65
  * Large sessions still dispatch all entries, but only this many are tracked
@@ -53,6 +74,15 @@ export type SyncOptions<R extends "u32" | "u64"> = {
53
74
  */
54
75
  repairSweepTargetBufferSize?: number;
55
76
 
77
+ /**
78
+ * Native wire receive fusion (requires nativeBackbone). Shared-log
79
+ * registers its RPC topic with this session so raw exchange-head payloads
80
+ * recognized by the native wire decoder stay in wasm memory: the TS borsh
81
+ * decode of the entries and the JS-to-wasm copy of their block bytes are
82
+ * skipped. Messages without a stash entry fall back to the regular path.
83
+ */
84
+ nativeWireSync?: SharedLogNativeWireSync;
85
+
56
86
  /**
57
87
  * Optional profiling callback. It is only invoked when provided, and should
58
88
  * avoid blocking because it runs inside sync hot paths.
@@ -60,6 +90,72 @@ export type SyncOptions<R extends "u32" | "u64"> = {
60
90
  profile?: SyncProfileFn;
61
91
  };
62
92
 
93
+ /**
94
+ * The per-node native wire-sync session surface shared-log consumes
95
+ * (implemented by `NativeBackboneWireSyncSession` in `@peerbit/native-backbone`;
96
+ * the same object also implements the `nativeWire` option of
97
+ * `@peerbit/stream`'s DirectStream).
98
+ */
99
+ export type SharedLogNativeWireSync = {
100
+ /** Raw wasm session handle consumed by `prepareStashedRawReceive*`. */
101
+ handle: unknown;
102
+ registerTopic(topic: string): void;
103
+ unregisterTopic(topic: string): boolean;
104
+ stashedMeta(id: Uint8Array):
105
+ | {
106
+ hashes: string[];
107
+ gidRefrences: string[][];
108
+ byteLengths: Uint32Array;
109
+ reserved: Uint8Array;
110
+ payloadLength: number;
111
+ }
112
+ | undefined;
113
+ stashedBlocks(
114
+ id: Uint8Array,
115
+ indexes?: Uint32Array,
116
+ ): Uint8Array[] | undefined;
117
+ release(id: Uint8Array): boolean;
118
+ };
119
+
120
+ /**
121
+ * Fused raw exchange-heads sender provided by the shared log when the native
122
+ * backbone can serialize the sync payload in wasm (block bytes never
123
+ * materialize in JS). Resolves to the number of messages sent, or `undefined`
124
+ * when the fused path is unavailable for these hashes — the caller then falls
125
+ * back to the TS message path.
126
+ */
127
+ export type RawExchangeHeadsSender = (
128
+ hashes: string[],
129
+ to: string[],
130
+ options?: { priority?: number },
131
+ ) => Promise<number | undefined>;
132
+
133
+ export type HashSymbolInput = readonly bigint[] | BigUint64Array;
134
+
135
+ export type HashSymbolResolver = (
136
+ symbols: HashSymbolInput,
137
+ ) =>
138
+ | ReadonlyMap<bigint, Iterable<string>>
139
+ | undefined
140
+ | Promise<ReadonlyMap<bigint, Iterable<string>> | undefined>;
141
+
142
+ export type HashSymbolHashListResolver = (
143
+ symbols: HashSymbolInput,
144
+ ) =>
145
+ | Iterable<string>
146
+ | undefined
147
+ | Promise<Iterable<string> | undefined>;
148
+
149
+ export type HashSymbolRangeResolver = (range: {
150
+ start1: bigint | number;
151
+ end1: bigint | number;
152
+ start2: bigint | number;
153
+ end2: bigint | number;
154
+ }) =>
155
+ | Iterable<bigint | number>
156
+ | undefined
157
+ | Promise<Iterable<bigint | number> | undefined>;
158
+
63
159
  export type SynchronizerComponents<R extends "u32" | "u64"> = {
64
160
  rpc: RPC<TransportMessage, TransportMessage>;
65
161
  rangeIndex: Index<ReplicationRangeIndexable<R>, any>;
@@ -67,7 +163,16 @@ export type SynchronizerComponents<R extends "u32" | "u64"> = {
67
163
  log: Log<any>;
68
164
  coordinateToHash: Cache<string>;
69
165
  numbers: Numbers<R>;
166
+ resolveHashesForSymbols?: HashSymbolResolver;
167
+ resolveHashListForSymbols?: HashSymbolHashListResolver;
168
+ resolveHashNumbersInRange?: HashSymbolRangeResolver;
70
169
  sync?: SyncOptions<R>;
170
+ isEntryRecentlyKnownByPeer?: (
171
+ hash: string,
172
+ peer: string,
173
+ maxAgeMs: number,
174
+ ) => boolean;
175
+ sendRawExchangeHeads?: RawExchangeHeadsSender;
71
176
  };
72
177
  export type SynchronizerConstructor<R extends "u32" | "u64"> = new (
73
178
  properties: SynchronizerComponents<R>,
@@ -97,7 +202,7 @@ export type RepairSession = {
97
202
 
98
203
  export interface Syncronizer<R extends "u32" | "u64"> {
99
204
  startRepairSession(properties: {
100
- entries: Map<string, EntryReplicated<R>>;
205
+ entries: Map<string, SyncEntryCoordinates<R>>;
101
206
  targets: string[];
102
207
  mode?: RepairSessionMode;
103
208
  timeoutMs?: number;
@@ -105,7 +210,7 @@ export interface Syncronizer<R extends "u32" | "u64"> {
105
210
  }): RepairSession;
106
211
 
107
212
  onMaybeMissingEntries(properties: {
108
- entries: Map<string, EntryReplicated<R>>;
213
+ entries: Map<string, SyncEntryCoordinates<R>>;
109
214
  targets: string[];
110
215
  }): Promise<void> | void;
111
216
 
@@ -119,7 +224,15 @@ export interface Syncronizer<R extends "u32" | "u64"> {
119
224
  from: PublicSignKey;
120
225
  }): Promise<void> | void;
121
226
 
227
+ onReceivedEntryHashes?(properties: {
228
+ hashes: string[];
229
+ from: PublicSignKey;
230
+ }): Promise<void> | void;
231
+
232
+ onEntryAddedHashes?(hashes: string[]): void;
233
+ onEntryAddedHash?(hash: string): void;
122
234
  onEntryAdded(entry: Entry<any>): void;
235
+ onEntryRemovedHashes?(hashes: string[]): void;
123
236
  onEntryRemoved(hash: string): void;
124
237
  onPeerDisconnected(key: PublicSignKey | string): void;
125
238