@peerbit/shared-log 12.1.3 → 12.2.0-10dfe9b

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": "12.1.3",
3
+ "version": "12.2.0-10dfe9b",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -54,35 +54,35 @@
54
54
  "dependencies": {
55
55
  "@dao-xyz/borsh": "^6.0.0",
56
56
  "@libp2p/crypto": "^5.1.10",
57
+ "@peerbit/log": "5.0.6-10dfe9b",
58
+ "@peerbit/logger": "2.0.0-10dfe9b",
59
+ "@peerbit/program": "5.6.0-10dfe9b",
60
+ "@peerbit/riblt": "1.2.0-10dfe9b",
61
+ "@peerbit/rpc": "5.4.15-10dfe9b",
62
+ "@peerbit/any-store": "2.2.4-10dfe9b",
63
+ "@peerbit/blocks": "3.1.6-10dfe9b",
64
+ "@peerbit/blocks-interface": "1.5.1-10dfe9b",
65
+ "@peerbit/cache": "2.2.0-10dfe9b",
66
+ "@peerbit/crypto": "2.4.1-10dfe9b",
67
+ "@peerbit/indexer-interface": "2.1.1-10dfe9b",
68
+ "@peerbit/indexer-sqlite3": "2.1.0-10dfe9b",
69
+ "@peerbit/pubsub": "4.1.3-10dfe9b",
70
+ "@peerbit/pubsub-interface": "4.1.1-10dfe9b",
71
+ "@peerbit/stream-interface": "5.3.1-10dfe9b",
72
+ "@peerbit/time": "2.3.0-10dfe9b",
57
73
  "json-stringify-deterministic": "^1.0.7",
58
74
  "p-each-series": "^3.0.0",
59
75
  "p-defer": "^4.0.0",
60
76
  "p-queue": "^8.0.1",
61
77
  "pidusage": "^4.0.1",
62
78
  "pino": "^9.4.0",
63
- "uint8arrays": "^5.1.0",
64
- "@peerbit/log": "5.0.5",
65
- "@peerbit/logger": "2.0.0",
66
- "@peerbit/program": "5.5.2",
67
- "@peerbit/rpc": "5.4.14",
68
- "@peerbit/any-store": "2.2.4",
69
- "@peerbit/riblt": "1.1.0",
70
- "@peerbit/blocks": "3.1.6",
71
- "@peerbit/cache": "2.2.0",
72
- "@peerbit/blocks-interface": "1.5.1",
73
- "@peerbit/crypto": "2.4.1",
74
- "@peerbit/indexer-interface": "2.1.1",
75
- "@peerbit/indexer-sqlite3": "2.0.2",
76
- "@peerbit/pubsub": "4.1.3",
77
- "@peerbit/pubsub-interface": "4.1.1",
78
- "@peerbit/stream-interface": "5.3.1",
79
- "@peerbit/time": "2.3.0"
79
+ "uint8arrays": "^5.1.0"
80
80
  },
81
81
  "devDependencies": {
82
+ "@peerbit/test-utils": "2.3.15-10dfe9b",
82
83
  "@types/libsodium-wrappers": "^0.7.14",
83
84
  "@types/pidusage": "^2.0.5",
84
- "uuid": "^10.0.0",
85
- "@peerbit/test-utils": "2.3.14"
85
+ "uuid": "^10.0.0"
86
86
  },
87
87
  "scripts": {
88
88
  "clean": "aegir clean",
package/src/index.ts CHANGED
@@ -135,7 +135,11 @@ import {
135
135
  maxReplicas,
136
136
  } from "./replication.js";
137
137
  import { Observer, Replicator } from "./role.js";
138
- import type { SynchronizerConstructor, Syncronizer } from "./sync/index.js";
138
+ import type {
139
+ SyncOptions,
140
+ SynchronizerConstructor,
141
+ Syncronizer,
142
+ } from "./sync/index.js";
139
143
  import { RatelessIBLTSynchronizer } from "./sync/rateless-iblt.js";
140
144
  import { SimpleSyncronizer } from "./sync/simple.js";
141
145
  import { groupByGid } from "./utils.js";
@@ -149,6 +153,12 @@ export {
149
153
  };
150
154
  export { type CPUUsage, CPUUsageIntervalLag };
151
155
  export * from "./replication.js";
156
+ export type {
157
+ LogLike,
158
+ LogResultsIterator,
159
+ SharedLogLike,
160
+ SharedLogReplicationIndexLike,
161
+ } from "./like.js";
152
162
  export {
153
163
  type ReplicationRangeIndexable,
154
164
  ReplicationRangeIndexableU32,
@@ -352,6 +362,7 @@ export type SharedLogOptions<
352
362
  keep?: (
353
363
  entry: ShallowOrFullEntry<T> | EntryReplicated<R>,
354
364
  ) => Promise<boolean> | boolean;
365
+ sync?: SyncOptions<R>;
355
366
  syncronizer?: SynchronizerConstructor<R>;
356
367
  timeUntilRoleMaturity?: number;
357
368
  waitForReplicatorTimeout?: number;
@@ -2037,6 +2048,7 @@ export class SharedLog<
2037
2048
  rangeIndex: this._replicationRangeIndex,
2038
2049
  rpc: this.rpc,
2039
2050
  coordinateToHash: this.coordinateToHash,
2051
+ sync: options?.sync,
2040
2052
  });
2041
2053
  } else {
2042
2054
  if (
@@ -2048,6 +2060,7 @@ export class SharedLog<
2048
2060
  rpc: this.rpc,
2049
2061
  entryIndex: this.entryCoordinatesIndex,
2050
2062
  coordinateToHash: this.coordinateToHash,
2063
+ sync: options?.sync,
2051
2064
  });
2052
2065
  } else {
2053
2066
  if (this.domain.resolution === "u32") {
@@ -2063,6 +2076,7 @@ export class SharedLog<
2063
2076
  rangeIndex: this._replicationRangeIndex,
2064
2077
  rpc: this.rpc,
2065
2078
  coordinateToHash: this.coordinateToHash,
2079
+ sync: options?.sync,
2066
2080
  }) as Syncronizer<R>;
2067
2081
  }
2068
2082
  }
package/src/like.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { PublicSignKey } from "@peerbit/crypto";
2
+ import type {
3
+ CountOptions,
4
+ IndexIterator,
5
+ IterateOptions,
6
+ } from "@peerbit/indexer-interface";
7
+ import type { Entry } from "@peerbit/log";
8
+ import type { ShallowEntry } from "@peerbit/log";
9
+ import type { ReplicationOptions, ReplicationRangeIndexable } from "./index.js";
10
+
11
+ export type LogBlocksLike = {
12
+ has: (hash: string) => Promise<boolean> | boolean;
13
+ };
14
+
15
+ export type LogResultsIterator<T> = {
16
+ close: () => void | Promise<void>;
17
+ next: (amount: number) => T[] | Promise<T[]>;
18
+ done: () => boolean | undefined;
19
+ all: () => T[] | Promise<T[]>;
20
+ };
21
+
22
+ export type LogLike<T = any> = {
23
+ idString?: string;
24
+ length: number;
25
+ get: (
26
+ hash: string,
27
+ options?: any,
28
+ ) => Promise<Entry<T> | undefined> | Entry<T> | undefined;
29
+ has: (hash: string) => Promise<boolean> | boolean;
30
+ getHeads: (resolve?: boolean) => LogResultsIterator<Entry<T> | ShallowEntry>;
31
+ toArray: () => Promise<Entry<T>[]>;
32
+ blocks?: LogBlocksLike;
33
+ };
34
+
35
+ export type SharedLogReplicationIndexLike<R extends "u32" | "u64" = any> = {
36
+ iterate: (
37
+ request?: IterateOptions,
38
+ ) => IndexIterator<ReplicationRangeIndexable<R>, undefined>;
39
+ count: (options?: CountOptions) => Promise<number> | number;
40
+ getSize?: () => Promise<number> | number;
41
+ };
42
+
43
+ export type SharedLogLike<T = any, R extends "u32" | "u64" = any> = {
44
+ closed?: boolean;
45
+ events: EventTarget;
46
+ log: LogLike<T>;
47
+ replicationIndex: SharedLogReplicationIndexLike<R>;
48
+ node?: { identity: { publicKey: PublicSignKey } };
49
+ getReplicators: () => Promise<Set<string>>;
50
+ waitForReplicator: (
51
+ publicKey: PublicSignKey,
52
+ options?: {
53
+ eager?: boolean;
54
+ roleAge?: number;
55
+ timeout?: number;
56
+ signal?: AbortSignal;
57
+ },
58
+ ) => Promise<void>;
59
+ waitForReplicators: (options?: {
60
+ timeout?: number;
61
+ roleAge?: number;
62
+ coverageThreshold?: number;
63
+ waitForNewPeers?: boolean;
64
+ signal?: AbortSignal;
65
+ }) => Promise<void>;
66
+ replicate: (
67
+ rangeOrEntry?: ReplicationOptions<R> | any,
68
+ options?: {
69
+ reset?: boolean;
70
+ checkDuplicates?: boolean;
71
+ rebalance?: boolean;
72
+ mergeSegments?: boolean;
73
+ },
74
+ ) => Promise<void | ReplicationRangeIndexable<R>[]>;
75
+ unreplicate: (rangeOrEntry?: { id: Uint8Array }[]) => Promise<void>;
76
+ calculateCoverage: (options?: {
77
+ start?: number | bigint;
78
+ end?: number | bigint;
79
+ roleAge?: number;
80
+ }) => Promise<number>;
81
+ getMyReplicationSegments: () => Promise<ReplicationRangeIndexable<R>[]>;
82
+ getAllReplicationSegments: () => Promise<ReplicationRangeIndexable<R>[]>;
83
+ close: () => Promise<void | boolean>;
84
+ };
package/src/sync/index.ts CHANGED
@@ -8,6 +8,24 @@ import type { Numbers } from "../integers.js";
8
8
  import type { TransportMessage } from "../message.js";
9
9
  import type { EntryReplicated, ReplicationRangeIndexable } from "../ranges.js";
10
10
 
11
+ export type SyncPriorityFn<R extends "u32" | "u64"> = (
12
+ entry: EntryReplicated<R>,
13
+ ) => number;
14
+
15
+ export type SyncOptions<R extends "u32" | "u64"> = {
16
+ /**
17
+ * Higher numbers are synced first.
18
+ * The callback should be fast and side-effect free.
19
+ */
20
+ priority?: SyncPriorityFn<R>;
21
+
22
+ /**
23
+ * When using rateless IBLT sync, optionally pre-sync up to this many
24
+ * high-priority entries using the simple synchronizer.
25
+ */
26
+ maxSimpleEntries?: number;
27
+ };
28
+
11
29
  export type SynchronizerComponents<R extends "u32" | "u64"> = {
12
30
  rpc: RPC<TransportMessage, TransportMessage>;
13
31
  rangeIndex: Index<ReplicationRangeIndexable<R>, any>;
@@ -15,6 +33,7 @@ export type SynchronizerComponents<R extends "u32" | "u64"> = {
15
33
  log: Log<any>;
16
34
  coordinateToHash: Cache<string>;
17
35
  numbers: Numbers<R>;
36
+ sync?: SyncOptions<R>;
18
37
  };
19
38
  export type SynchronizerConstructor<R extends "u32" | "u64"> = new (
20
39
  properties: SynchronizerComponents<R>,
@@ -4,18 +4,24 @@ import { type PublicSignKey, randomBytes, toBase64 } from "@peerbit/crypto";
4
4
  import { type Index } from "@peerbit/indexer-interface";
5
5
  import type { Entry, Log } from "@peerbit/log";
6
6
  import { logger as loggerFn } from "@peerbit/logger";
7
- import { DecoderWrapper, EncoderWrapper } from "@peerbit/riblt";
7
+ import {
8
+ DecoderWrapper,
9
+ EncoderWrapper,
10
+ ready as ribltReady,
11
+ } from "@peerbit/riblt";
8
12
  import type { RPC, RequestContext } from "@peerbit/rpc";
9
13
  import { SilentDelivery } from "@peerbit/stream-interface";
10
14
  import { type EntryWithRefs } from "../exchange-heads.js";
11
- import { type Numbers } from "../integers.js";
12
15
  import { TransportMessage } from "../message.js";
13
16
  import {
14
17
  type EntryReplicated,
15
- type ReplicationRangeIndexable,
16
18
  matchEntriesInRangeQuery,
17
19
  } from "../ranges.js";
18
- import type { SyncableKey, Syncronizer } from "./index.js";
20
+ import type {
21
+ SyncableKey,
22
+ SynchronizerComponents,
23
+ Syncronizer,
24
+ } from "./index.js";
19
25
  import { SimpleSyncronizer } from "./simple.js";
20
26
 
21
27
  export const logger = loggerFn("peerbit:shared-log:rateless");
@@ -142,6 +148,7 @@ const buildEncoderOrDecoderFromRange = async <
142
148
  entryIndex: Index<EntryReplicated<D>>,
143
149
  type: T,
144
150
  ): Promise<E | false> => {
151
+ await ribltReady;
145
152
  const encoder =
146
153
  type === "encoder" ? new EncoderWrapper() : new DecoderWrapper();
147
154
 
@@ -180,6 +187,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
180
187
  simple: SimpleSyncronizer<D>;
181
188
 
182
189
  startedOrCompletedSynchronizations: Cache<string>;
190
+ private localRangeEncoderCacheVersion = 0;
191
+ private localRangeEncoderCache: Map<
192
+ string,
193
+ { encoder: EncoderWrapper; version: number; lastUsed: number }
194
+ > = new Map();
195
+ private localRangeEncoderCacheMax = 2;
196
+
183
197
  ingoingSyncProcesses: Map<
184
198
  string,
185
199
  {
@@ -207,14 +221,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
207
221
  >;
208
222
 
209
223
  constructor(
210
- readonly properties: {
211
- rpc: RPC<TransportMessage, TransportMessage>;
212
- rangeIndex: Index<ReplicationRangeIndexable<D>, any>;
213
- entryIndex: Index<EntryReplicated<D>, any>;
214
- log: Log<any>;
215
- coordinateToHash: Cache<string>;
216
- numbers: Numbers<D>;
217
- },
224
+ readonly properties: SynchronizerComponents<D>,
218
225
  ) {
219
226
  this.simple = new SimpleSyncronizer(properties);
220
227
  this.outgoingSyncProcesses = new Map();
@@ -222,6 +229,91 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
222
229
  this.startedOrCompletedSynchronizations = new Cache({ max: 1e4 });
223
230
  }
224
231
 
232
+ private clearLocalRangeEncoderCache() {
233
+ for (const [, cached] of this.localRangeEncoderCache) {
234
+ cached.encoder.free();
235
+ }
236
+ this.localRangeEncoderCache.clear();
237
+ }
238
+
239
+ private invalidateLocalRangeEncoderCache() {
240
+ this.localRangeEncoderCacheVersion += 1;
241
+ this.clearLocalRangeEncoderCache();
242
+ }
243
+
244
+ private localRangeEncoderCacheKey(ranges: {
245
+ start1: NumberOrBigint;
246
+ end1: NumberOrBigint;
247
+ start2: NumberOrBigint;
248
+ end2: NumberOrBigint;
249
+ }) {
250
+ return `${String(ranges.start1)}:${String(ranges.end1)}:${String(
251
+ ranges.start2,
252
+ )}:${String(ranges.end2)}`;
253
+ }
254
+
255
+ private decoderFromCachedEncoder(encoder: EncoderWrapper): DecoderWrapper {
256
+ const clone = encoder.clone();
257
+ const decoder = clone.to_decoder();
258
+ clone.free();
259
+ return decoder;
260
+ }
261
+
262
+ private async getLocalDecoderForRange(ranges: {
263
+ start1: NumberOrBigint;
264
+ end1: NumberOrBigint;
265
+ start2: NumberOrBigint;
266
+ end2: NumberOrBigint;
267
+ }): Promise<DecoderWrapper | false> {
268
+ const key = this.localRangeEncoderCacheKey(ranges);
269
+ const cached = this.localRangeEncoderCache.get(key);
270
+ if (cached && cached.version === this.localRangeEncoderCacheVersion) {
271
+ cached.lastUsed = Date.now();
272
+ return this.decoderFromCachedEncoder(cached.encoder);
273
+ }
274
+
275
+ const encoder = (await buildEncoderOrDecoderFromRange(
276
+ ranges,
277
+ this.properties.entryIndex,
278
+ "encoder",
279
+ )) as EncoderWrapper | false;
280
+ if (!encoder) {
281
+ return false;
282
+ }
283
+
284
+ const now = Date.now();
285
+ const existing = this.localRangeEncoderCache.get(key);
286
+ if (existing) {
287
+ existing.encoder.free();
288
+ }
289
+ this.localRangeEncoderCache.set(key, {
290
+ encoder,
291
+ version: this.localRangeEncoderCacheVersion,
292
+ lastUsed: now,
293
+ });
294
+
295
+ while (this.localRangeEncoderCache.size > this.localRangeEncoderCacheMax) {
296
+ let oldestKey: string | undefined;
297
+ let oldestUsed = Number.POSITIVE_INFINITY;
298
+ for (const [candidateKey, value] of this.localRangeEncoderCache) {
299
+ if (value.lastUsed < oldestUsed) {
300
+ oldestUsed = value.lastUsed;
301
+ oldestKey = candidateKey;
302
+ }
303
+ }
304
+ if (!oldestKey) {
305
+ break;
306
+ }
307
+ const victim = this.localRangeEncoderCache.get(oldestKey);
308
+ if (victim) {
309
+ victim.encoder.free();
310
+ }
311
+ this.localRangeEncoderCache.delete(oldestKey);
312
+ }
313
+
314
+ return this.decoderFromCachedEncoder(encoder);
315
+ }
316
+
225
317
  async onMaybeMissingEntries(properties: {
226
318
  entries: Map<string, EntryReplicated<D>>;
227
319
  targets: string[];
@@ -233,7 +325,6 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
233
325
  // such as those assigned to range boundaries.
234
326
 
235
327
  let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
236
- let allCoordinatesToSyncWithIblt: bigint[] = [];
237
328
  let minSyncIbltSize = 333; // TODO: make configurable
238
329
  let maxSyncWithSimpleMethod = 1e3;
239
330
 
@@ -246,13 +337,59 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
246
337
  return;
247
338
  }
248
339
 
249
- // Mixed strategy for larger batches
340
+ const nonBoundaryEntries: EntryReplicated<D>[] = [];
250
341
  for (const entry of properties.entries.values()) {
251
342
  if (entry.assignedToRangeBoundary) {
252
343
  entriesToSyncNaively.set(entry.hash, entry);
253
344
  } else {
254
- allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
345
+ nonBoundaryEntries.push(entry);
346
+ }
347
+ }
348
+
349
+ const priorityFn = this.properties.sync?.priority;
350
+ const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
351
+ const maxAdditionalNaive =
352
+ priorityFn &&
353
+ typeof maxSimpleEntries === "number" &&
354
+ Number.isFinite(maxSimpleEntries) &&
355
+ maxSimpleEntries > 0
356
+ ? Math.max(
357
+ 0,
358
+ Math.min(
359
+ Math.floor(maxSimpleEntries),
360
+ maxSyncWithSimpleMethod - entriesToSyncNaively.size,
361
+ ),
362
+ )
363
+ : 0;
364
+
365
+ if (priorityFn && maxAdditionalNaive > 0 && nonBoundaryEntries.length > 0) {
366
+ let index = 0;
367
+ const scored: {
368
+ entry: EntryReplicated<D>;
369
+ index: number;
370
+ priority: number;
371
+ }[] = [];
372
+ for (const entry of nonBoundaryEntries) {
373
+ const priorityValue = priorityFn(entry);
374
+ scored.push({
375
+ entry,
376
+ index,
377
+ priority: Number.isFinite(priorityValue) ? priorityValue : 0,
378
+ });
379
+ index += 1;
380
+ }
381
+ scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
382
+ for (const { entry } of scored.slice(0, maxAdditionalNaive)) {
383
+ entriesToSyncNaively.set(entry.hash, entry);
384
+ }
385
+ }
386
+
387
+ let allCoordinatesToSyncWithIblt: bigint[] = [];
388
+ for (const entry of nonBoundaryEntries) {
389
+ if (entriesToSyncNaively.has(entry.hash)) {
390
+ continue;
255
391
  }
392
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
256
393
  }
257
394
 
258
395
  if (entriesToSyncNaively.size > 0) {
@@ -268,31 +405,44 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
268
405
  entriesToSyncNaively.size > maxSyncWithSimpleMethod
269
406
  ) {
270
407
  // Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
271
- allCoordinatesToSyncWithIblt = Array.from(
272
- properties.entries.values(),
273
- ).map((x) => coerceBigInt(x.hashNumber));
408
+ allCoordinatesToSyncWithIblt = [];
409
+ for (const entry of properties.entries.values()) {
410
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
411
+ }
274
412
  }
275
413
 
276
414
  if (allCoordinatesToSyncWithIblt.length === 0) {
277
415
  return;
278
416
  }
279
417
 
280
- const sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
281
- if (a > b) {
282
- return 1;
283
- } else if (a < b) {
284
- return -1;
285
- } else {
286
- return 0;
418
+ await ribltReady;
419
+
420
+ let sortedEntries: bigint[] | BigUint64Array;
421
+ if (typeof BigUint64Array !== "undefined") {
422
+ const typed = new BigUint64Array(allCoordinatesToSyncWithIblt.length);
423
+ for (let i = 0; i < allCoordinatesToSyncWithIblt.length; i++) {
424
+ typed[i] = allCoordinatesToSyncWithIblt[i];
287
425
  }
288
- });
426
+ typed.sort();
427
+ sortedEntries = typed;
428
+ } else {
429
+ sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
430
+ if (a > b) {
431
+ return 1;
432
+ } else if (a < b) {
433
+ return -1;
434
+ } else {
435
+ return 0;
436
+ }
437
+ });
438
+ }
289
439
 
290
440
  // assume sorted, and find the largest gap
291
441
  let largestGap = 0n;
292
442
  let largestGapIndex = 0;
293
- for (let i = 0; i < sortedEntries.length - 1; i++) {
443
+ for (let i = 0; i < sortedEntries.length; i++) {
294
444
  const current = sortedEntries[i];
295
- const next = sortedEntries[i + 1];
445
+ const next = sortedEntries[(i + 1) % sortedEntries.length];
296
446
  const gap =
297
447
  next >= current
298
448
  ? next - current
@@ -322,8 +472,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
322
472
 
323
473
  const startSync = new StartSync({ from: start, to: end, symbols: [] });
324
474
  const encoder = new EncoderWrapper();
325
- for (const entry of sortedEntries) {
326
- encoder.add_symbol(coerceBigInt(entry));
475
+ if (typeof BigUint64Array !== "undefined" && sortedEntries instanceof BigUint64Array) {
476
+ encoder.add_symbols(sortedEntries);
477
+ } else {
478
+ for (const entry of sortedEntries) {
479
+ encoder.add_symbol(coerceBigInt(entry));
480
+ }
327
481
  }
328
482
 
329
483
  let initialSymbols = Math.round(
@@ -399,16 +553,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
399
553
  this.startedOrCompletedSynchronizations.add(syncId);
400
554
 
401
555
  const wrapped = message.end < message.start;
402
- const decoder = await buildEncoderOrDecoderFromRange(
403
- {
404
- start1: message.start,
405
- end1: wrapped ? this.properties.numbers.maxValue : message.end,
406
- start2: 0n,
407
- end2: wrapped ? message.end : 0n,
408
- },
409
- this.properties.entryIndex,
410
- "decoder",
411
- );
556
+ const decoder = await this.getLocalDecoderForRange({
557
+ start1: message.start,
558
+ end1: wrapped ? this.properties.numbers.maxValue : message.end,
559
+ start2: 0n,
560
+ end2: wrapped ? message.end : 0n,
561
+ });
412
562
 
413
563
  if (!decoder) {
414
564
  await this.simple.rpc.send(
@@ -613,10 +763,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
613
763
  }
614
764
 
615
765
  onEntryAdded(entry: Entry<any>): void {
766
+ this.invalidateLocalRangeEncoderCache();
616
767
  return this.simple.onEntryAdded(entry);
617
768
  }
618
769
 
619
770
  onEntryRemoved(hash: string) {
771
+ this.invalidateLocalRangeEncoderCache();
620
772
  return this.simple.onEntryRemoved(hash);
621
773
  }
622
774
 
@@ -635,6 +787,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
635
787
  for (const [, obj] of this.outgoingSyncProcesses) {
636
788
  obj.free();
637
789
  }
790
+ this.clearLocalRangeEncoderCache();
638
791
  return this.simple.close();
639
792
  }
640
793
 
@@ -16,7 +16,7 @@ import {
16
16
  } from "../exchange-heads.js";
17
17
  import { TransportMessage } from "../message.js";
18
18
  import type { EntryReplicated } from "../ranges.js";
19
- import type { SyncableKey, Syncronizer } from "./index.js";
19
+ import type { SyncableKey, SyncOptions, Syncronizer } from "./index.js";
20
20
 
21
21
  @variant([0, 1])
22
22
  export class RequestMaybeSync extends TransportMessage {
@@ -57,7 +57,7 @@ const getHashesFromSymbols = async (
57
57
  coordinateToHash: Cache<string>,
58
58
  ) => {
59
59
  let queries: IntegerCompare[] = [];
60
- let batchSize = 1; // TODO arg
60
+ let batchSize = 128; // TODO arg
61
61
  let results = new Set<string>();
62
62
  const handleBatch = async (end = false) => {
63
63
  if (queries.length >= batchSize || (end && queries.length > 0)) {
@@ -109,6 +109,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
109
109
  log: Log<any>;
110
110
  entryIndex: Index<EntryReplicated<R>, any>;
111
111
  coordinateToHash: Cache<string>;
112
+ private syncOptions?: SyncOptions<R>;
112
113
 
113
114
  // Syncing and dedeplucation work
114
115
  syncMoreInterval?: ReturnType<typeof setTimeout>;
@@ -120,6 +121,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
120
121
  entryIndex: Index<EntryReplicated<R>, any>;
121
122
  log: Log<any>;
122
123
  coordinateToHash: Cache<string>;
124
+ sync?: SyncOptions<R>;
123
125
  }) {
124
126
  this.syncInFlightQueue = new Map();
125
127
  this.syncInFlightQueueInverted = new Map();
@@ -128,14 +130,35 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
128
130
  this.log = properties.log;
129
131
  this.entryIndex = properties.entryIndex;
130
132
  this.coordinateToHash = properties.coordinateToHash;
133
+ this.syncOptions = properties.sync;
131
134
  }
132
135
 
133
136
  onMaybeMissingEntries(properties: {
134
137
  entries: Map<string, EntryReplicated<R>>;
135
138
  targets: string[];
136
139
  }): Promise<void> {
140
+ let hashes: string[];
141
+ const priorityFn = this.syncOptions?.priority;
142
+ if (priorityFn) {
143
+ let index = 0;
144
+ const scored: { hash: string; index: number; priority: number }[] = [];
145
+ for (const [hash, entry] of properties.entries) {
146
+ const priorityValue = priorityFn(entry);
147
+ scored.push({
148
+ hash,
149
+ index,
150
+ priority: Number.isFinite(priorityValue) ? priorityValue : 0,
151
+ });
152
+ index += 1;
153
+ }
154
+ scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
155
+ hashes = scored.map((x) => x.hash);
156
+ } else {
157
+ hashes = [...properties.entries.keys()];
158
+ }
159
+
137
160
  return this.rpc.send(
138
- new RequestMaybeSync({ hashes: [...properties.entries.keys()] }),
161
+ new RequestMaybeSync({ hashes }),
139
162
  {
140
163
  priority: 1,
141
164
  mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),