@peerbit/shared-log 12.1.3 → 12.2.0-3333888

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 (40) hide show
  1. package/dist/benchmark/pid-convergence.d.ts +2 -0
  2. package/dist/benchmark/pid-convergence.d.ts.map +1 -0
  3. package/dist/benchmark/pid-convergence.js +138 -0
  4. package/dist/benchmark/pid-convergence.js.map +1 -0
  5. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts +2 -0
  6. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +1 -0
  7. package/dist/benchmark/rateless-iblt-sender-startsync.js +104 -0
  8. package/dist/benchmark/rateless-iblt-sender-startsync.js.map +1 -0
  9. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +2 -0
  10. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +1 -0
  11. package/dist/benchmark/rateless-iblt-startsync-cache.js +112 -0
  12. package/dist/benchmark/rateless-iblt-startsync-cache.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +3 -0
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -0
  15. package/dist/benchmark/sync-catchup.js +109 -0
  16. package/dist/benchmark/sync-catchup.js.map +1 -0
  17. package/dist/src/index.d.ts +10 -3
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +66 -32
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/like.d.ts +71 -0
  22. package/dist/src/like.d.ts.map +1 -0
  23. package/dist/src/like.js +2 -0
  24. package/dist/src/like.js.map +1 -0
  25. package/dist/src/sync/index.d.ts +14 -0
  26. package/dist/src/sync/index.d.ts.map +1 -1
  27. package/dist/src/sync/rateless-iblt.d.ts +14 -22
  28. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  29. package/dist/src/sync/rateless-iblt.js +139 -22
  30. package/dist/src/sync/rateless-iblt.js.map +1 -1
  31. package/dist/src/sync/simple.d.ts +3 -1
  32. package/dist/src/sync/simple.d.ts.map +1 -1
  33. package/dist/src/sync/simple.js +24 -2
  34. package/dist/src/sync/simple.js.map +1 -1
  35. package/package.json +20 -20
  36. package/src/index.ts +95 -37
  37. package/src/like.ts +84 -0
  38. package/src/sync/index.ts +19 -0
  39. package/src/sync/rateless-iblt.ts +193 -40
  40. package/src/sync/simple.ts +26 -3
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-3333888",
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-3333888",
58
+ "@peerbit/logger": "2.0.0-3333888",
59
+ "@peerbit/program": "5.6.0-3333888",
60
+ "@peerbit/riblt": "1.2.0-3333888",
61
+ "@peerbit/rpc": "5.4.15-3333888",
62
+ "@peerbit/any-store": "2.2.4-3333888",
63
+ "@peerbit/blocks": "3.1.6-3333888",
64
+ "@peerbit/blocks-interface": "1.5.1-3333888",
65
+ "@peerbit/cache": "2.2.0-3333888",
66
+ "@peerbit/crypto": "2.4.1-3333888",
67
+ "@peerbit/indexer-interface": "2.1.1-3333888",
68
+ "@peerbit/indexer-sqlite3": "2.1.0-3333888",
69
+ "@peerbit/pubsub": "4.1.3-3333888",
70
+ "@peerbit/pubsub-interface": "4.1.1-3333888",
71
+ "@peerbit/stream-interface": "5.3.1-3333888",
72
+ "@peerbit/time": "2.3.0-3333888",
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-3333888",
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;
@@ -365,11 +376,18 @@ export type SharedLogOptions<
365
376
  export const DEFAULT_MIN_REPLICAS = 2;
366
377
  export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
367
378
  export const WAIT_FOR_ROLE_MATURITY = 5000;
368
- export const WAIT_FOR_PRUNE_DELAY = 5000;
379
+ // TODO(prune): Investigate if/when a non-zero prune delay is required for correctness
380
+ // (e.g. responsibility/replication-info message reordering in multi-peer scenarios).
381
+ // Prefer making pruning robust without timing-based heuristics.
382
+ export const WAIT_FOR_PRUNE_DELAY = 0;
369
383
  const PRUNE_DEBOUNCE_INTERVAL = 500;
370
384
 
371
385
  // DONT SET THIS ANY LOWER, because it will make the pid controller unstable as the system responses are not fast enough to updates from the pid controller
372
386
  const RECALCULATE_PARTICIPATION_DEBOUNCE_INTERVAL = 1000;
387
+ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE = 0.01;
388
+ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_CPU_LIMIT = 0.005;
389
+ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_MEMORY_LIMIT = 0.001;
390
+ const RECALCULATE_PARTICIPATION_RELATIVE_DENOMINATOR_FLOOR = 1e-3;
373
391
 
374
392
  const DEFAULT_DISTRIBUTION_DEBOUNCE_TIME = 500;
375
393
 
@@ -606,15 +624,6 @@ export class SharedLog<
606
624
  ) {
607
625
  this.rebalanceParticipationDebounced = undefined;
608
626
 
609
- // make the rebalancing to respect warmup time
610
- let intervalTime = interval * 2;
611
- let timeout = setTimeout(() => {
612
- intervalTime = interval;
613
- }, this.timeUntilRoleMaturity);
614
- this._closeController.signal.addEventListener("abort", () => {
615
- clearTimeout(timeout);
616
- });
617
-
618
627
  this.rebalanceParticipationDebounced = debounceFixedInterval(
619
628
  () => this.rebalanceParticipation(),
620
629
  /* Math.max(
@@ -624,7 +633,7 @@ export class SharedLog<
624
633
  REBALANCE_DEBOUNCE_INTERVAL
625
634
  )
626
635
  ) */
627
- () => intervalTime, // TODO make this dynamic on the number of replicators
636
+ interval, // TODO make this dynamic on the number of replicators
628
637
  );
629
638
  }
630
639
 
@@ -1187,16 +1196,20 @@ export class SharedLog<
1187
1196
  }
1188
1197
  }
1189
1198
 
1199
+ let prevCountForOwner: number | undefined = undefined;
1190
1200
  if (existing.length === 0) {
1191
- let prevCount = await this.replicationIndex.count({
1201
+ prevCountForOwner = await this.replicationIndex.count({
1192
1202
  query: new StringMatch({ key: "hash", value: from.hashcode() }),
1193
1203
  });
1194
- isNewReplicator = prevCount === 0;
1204
+ isNewReplicator = prevCountForOwner === 0;
1195
1205
  } else {
1196
1206
  isNewReplicator = false;
1197
1207
  }
1198
1208
 
1199
- if (checkDuplicates) {
1209
+ if (
1210
+ checkDuplicates &&
1211
+ (existing.length > 0 || (prevCountForOwner ?? 0) > 0)
1212
+ ) {
1200
1213
  let deduplicated: ReplicationRangeIndexable<any>[] = [];
1201
1214
 
1202
1215
  // TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
@@ -1872,8 +1885,8 @@ export class SharedLog<
1872
1885
  this.timeUntilRoleMaturity =
1873
1886
  options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
1874
1887
  this.waitForReplicatorTimeout =
1875
- options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
1876
- this.waitForPruneDelay = options?.waitForPruneDelay || WAIT_FOR_PRUNE_DELAY;
1888
+ options?.waitForReplicatorTimeout ?? WAIT_FOR_REPLICATOR_TIMEOUT;
1889
+ this.waitForPruneDelay = options?.waitForPruneDelay ?? WAIT_FOR_PRUNE_DELAY;
1877
1890
 
1878
1891
  if (this.waitForReplicatorTimeout < this.timeUntilRoleMaturity) {
1879
1892
  this.waitForReplicatorTimeout = this.timeUntilRoleMaturity; // does not makes sense to expect a replicator to mature faster than it is reachable
@@ -2037,6 +2050,7 @@ export class SharedLog<
2037
2050
  rangeIndex: this._replicationRangeIndex,
2038
2051
  rpc: this.rpc,
2039
2052
  coordinateToHash: this.coordinateToHash,
2053
+ sync: options?.sync,
2040
2054
  });
2041
2055
  } else {
2042
2056
  if (
@@ -2048,6 +2062,7 @@ export class SharedLog<
2048
2062
  rpc: this.rpc,
2049
2063
  entryIndex: this.entryCoordinatesIndex,
2050
2064
  coordinateToHash: this.coordinateToHash,
2065
+ sync: options?.sync,
2051
2066
  });
2052
2067
  } else {
2053
2068
  if (this.domain.resolution === "u32") {
@@ -2063,6 +2078,7 @@ export class SharedLog<
2063
2078
  rangeIndex: this._replicationRangeIndex,
2064
2079
  rpc: this.rpc,
2065
2080
  coordinateToHash: this.coordinateToHash,
2081
+ sync: options?.sync,
2066
2082
  }) as Syncronizer<R>;
2067
2083
  }
2068
2084
  }
@@ -2378,15 +2394,35 @@ export class SharedLog<
2378
2394
  set.add(key);
2379
2395
  }
2380
2396
 
2381
- if (options?.reachableOnly) {
2382
- let reachableSet: string[] = [];
2383
- for (const peer of set) {
2384
- if (this.uniqueReplicators.has(peer)) {
2385
- reachableSet.push(peer);
2397
+ if (options?.reachableOnly) {
2398
+ // Prefer the live pubsub subscriber set when filtering reachability.
2399
+ // `uniqueReplicators` is primarily driven by replication messages and can lag during
2400
+ // joins/restarts; using subscribers prevents excluding peers that are reachable but
2401
+ // whose replication ranges were loaded from disk or haven't been processed yet.
2402
+ const subscribers =
2403
+ (await this.node.services.pubsub.getSubscribers(this.topic)) ??
2404
+ undefined;
2405
+ const subscriberHashcodes = subscribers
2406
+ ? new Set(subscribers.map((key) => key.hashcode()))
2407
+ : undefined;
2408
+
2409
+ const reachable: string[] = [];
2410
+ const selfHash = this.node.identity.publicKey.hashcode();
2411
+ for (const peer of set) {
2412
+ if (peer === selfHash) {
2413
+ reachable.push(peer);
2414
+ continue;
2415
+ }
2416
+ if (
2417
+ subscriberHashcodes
2418
+ ? subscriberHashcodes.has(peer)
2419
+ : this.uniqueReplicators.has(peer)
2420
+ ) {
2421
+ reachable.push(peer);
2422
+ }
2386
2423
  }
2424
+ return reachable;
2387
2425
  }
2388
- return reachableSet;
2389
- }
2390
2426
 
2391
2427
  return [...set];
2392
2428
  } catch (error) {
@@ -3126,25 +3162,29 @@ export class SharedLog<
3126
3162
  },
3127
3163
  ): Promise<void> {
3128
3164
  let entriesToReplicate: Entry<T>[] = [];
3129
- if (options?.replicate) {
3165
+ if (options?.replicate && this.log.length > 0) {
3130
3166
  // TODO this block should perhaps be called from a callback on the this.log.join method on all the ignored element because already joined, like "onAlreadyJoined"
3131
3167
 
3132
3168
  // check which entrise we already have but not are replicating, and replicate them
3133
3169
  // we can not just do the 'join' call because it will ignore the already joined entries
3134
3170
  for (const element of entries) {
3135
3171
  if (typeof element === "string") {
3136
- const entry = await this.log.get(element);
3137
- if (entry) {
3138
- entriesToReplicate.push(entry);
3172
+ if (await this.log.has(element)) {
3173
+ const entry = await this.log.get(element);
3174
+ if (entry) {
3175
+ entriesToReplicate.push(entry);
3176
+ }
3139
3177
  }
3140
3178
  } else if (element instanceof Entry) {
3141
3179
  if (await this.log.has(element.hash)) {
3142
3180
  entriesToReplicate.push(element);
3143
3181
  }
3144
3182
  } else {
3145
- const entry = await this.log.get(element.hash);
3146
- if (entry) {
3147
- entriesToReplicate.push(entry);
3183
+ if (await this.log.has(element.hash)) {
3184
+ const entry = await this.log.get(element.hash);
3185
+ if (entry) {
3186
+ entriesToReplicate.push(entry);
3187
+ }
3148
3188
  }
3149
3189
  }
3150
3190
  }
@@ -3736,7 +3776,7 @@ export class SharedLog<
3736
3776
  for (const [k, v] of this._requestIPruneResponseReplicatorSet) {
3737
3777
  v.delete(publicKey.hashcode());
3738
3778
  if (v.size === 0) {
3739
- this._requestIPruneSent.delete(k);
3779
+ this._requestIPruneResponseReplicatorSet.delete(k);
3740
3780
  }
3741
3781
  }
3742
3782
 
@@ -4086,8 +4126,13 @@ export class SharedLog<
4086
4126
  );
4087
4127
  }
4088
4128
 
4089
- async waitForPruned() {
4090
- await waitFor(() => this._pendingDeletes.size === 0);
4129
+ async waitForPruned(options?: {
4130
+ timeout?: number;
4131
+ signal?: AbortSignal;
4132
+ delayInterval?: number;
4133
+ timeoutMessage?: string;
4134
+ }) {
4135
+ await waitFor(() => this._pendingDeletes.size === 0, options);
4091
4136
  }
4092
4137
 
4093
4138
  async onReplicationChange(
@@ -4266,11 +4311,24 @@ export class SharedLog<
4266
4311
  cpuUsage: this.cpuUsage?.value(),
4267
4312
  });
4268
4313
 
4314
+ const absoluteDifference = Math.abs(dynamicRange.widthNormalized - newFactor);
4269
4315
  const relativeDifference =
4270
- Math.abs(dynamicRange.widthNormalized - newFactor) /
4271
- dynamicRange.widthNormalized;
4316
+ absoluteDifference /
4317
+ Math.max(
4318
+ dynamicRange.widthNormalized,
4319
+ RECALCULATE_PARTICIPATION_RELATIVE_DENOMINATOR_FLOOR,
4320
+ );
4321
+
4322
+ let minRelativeChange = RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE;
4323
+ if (this.replicationController.maxMemoryLimit != null) {
4324
+ minRelativeChange =
4325
+ RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_MEMORY_LIMIT;
4326
+ } else if (this.replicationController.maxCPUUsage != null) {
4327
+ minRelativeChange =
4328
+ RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_CPU_LIMIT;
4329
+ }
4272
4330
 
4273
- if (relativeDifference > 0.0001) {
4331
+ if (relativeDifference > minRelativeChange) {
4274
4332
  // TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
4275
4333
  dynamicRange = new this.indexableDomain.constructorRange({
4276
4334
  offset: dynamicRange.start1,
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>,