@peerbit/shared-log 9.2.13 → 10.0.0

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 (104) hide show
  1. package/dist/benchmark/get-samples.js +190 -64
  2. package/dist/benchmark/get-samples.js.map +1 -1
  3. package/dist/benchmark/index.js +16 -38
  4. package/dist/benchmark/index.js.map +1 -1
  5. package/dist/benchmark/memory/child.js.map +1 -1
  6. package/dist/benchmark/partial-sync.d.ts +3 -0
  7. package/dist/benchmark/partial-sync.d.ts.map +1 -0
  8. package/dist/benchmark/partial-sync.js +121 -0
  9. package/dist/benchmark/partial-sync.js.map +1 -0
  10. package/dist/benchmark/replication-prune.js.map +1 -1
  11. package/dist/benchmark/replication.js.map +1 -1
  12. package/dist/benchmark/to-rebalance.d.ts +2 -0
  13. package/dist/benchmark/to-rebalance.d.ts.map +1 -0
  14. package/dist/benchmark/to-rebalance.js +117 -0
  15. package/dist/benchmark/to-rebalance.js.map +1 -0
  16. package/dist/benchmark/utils.d.ts +24 -0
  17. package/dist/benchmark/utils.d.ts.map +1 -0
  18. package/dist/benchmark/utils.js +47 -0
  19. package/dist/benchmark/utils.js.map +1 -0
  20. package/dist/src/debounce.d.ts +2 -2
  21. package/dist/src/debounce.d.ts.map +1 -1
  22. package/dist/src/debounce.js +17 -47
  23. package/dist/src/debounce.js.map +1 -1
  24. package/dist/src/exchange-heads.d.ts +1 -13
  25. package/dist/src/exchange-heads.d.ts.map +1 -1
  26. package/dist/src/exchange-heads.js +0 -32
  27. package/dist/src/exchange-heads.js.map +1 -1
  28. package/dist/src/index.d.ts +119 -60
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +1116 -762
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/integers.d.ts +22 -0
  33. package/dist/src/integers.d.ts.map +1 -0
  34. package/dist/src/integers.js +76 -0
  35. package/dist/src/integers.js.map +1 -0
  36. package/dist/src/pid.d.ts.map +1 -1
  37. package/dist/src/pid.js +22 -22
  38. package/dist/src/pid.js.map +1 -1
  39. package/dist/src/ranges.d.ts +168 -38
  40. package/dist/src/ranges.d.ts.map +1 -1
  41. package/dist/src/ranges.js +869 -272
  42. package/dist/src/ranges.js.map +1 -1
  43. package/dist/src/replication-domain-hash.d.ts +2 -3
  44. package/dist/src/replication-domain-hash.d.ts.map +1 -1
  45. package/dist/src/replication-domain-hash.js +40 -15
  46. package/dist/src/replication-domain-hash.js.map +1 -1
  47. package/dist/src/replication-domain-time.d.ts +5 -5
  48. package/dist/src/replication-domain-time.d.ts.map +1 -1
  49. package/dist/src/replication-domain-time.js +2 -0
  50. package/dist/src/replication-domain-time.js.map +1 -1
  51. package/dist/src/replication-domain.d.ts +17 -19
  52. package/dist/src/replication-domain.d.ts.map +1 -1
  53. package/dist/src/replication-domain.js +2 -6
  54. package/dist/src/replication-domain.js.map +1 -1
  55. package/dist/src/replication.d.ts +6 -6
  56. package/dist/src/replication.d.ts.map +1 -1
  57. package/dist/src/replication.js +4 -4
  58. package/dist/src/replication.js.map +1 -1
  59. package/dist/src/role.d.ts +3 -6
  60. package/dist/src/role.d.ts.map +1 -1
  61. package/dist/src/role.js +4 -5
  62. package/dist/src/role.js.map +1 -1
  63. package/dist/src/sync/index.d.ts +40 -0
  64. package/dist/src/sync/index.d.ts.map +1 -0
  65. package/dist/src/sync/index.js +2 -0
  66. package/dist/src/sync/index.js.map +1 -0
  67. package/dist/src/sync/rateless-iblt.d.ts +124 -0
  68. package/dist/src/sync/rateless-iblt.d.ts.map +1 -0
  69. package/dist/src/sync/rateless-iblt.js +495 -0
  70. package/dist/src/sync/rateless-iblt.js.map +1 -0
  71. package/dist/src/sync/simple.d.ts +69 -0
  72. package/dist/src/sync/simple.d.ts.map +1 -0
  73. package/dist/src/sync/simple.js +338 -0
  74. package/dist/src/sync/simple.js.map +1 -0
  75. package/dist/src/sync/wasm-init.browser.d.ts +1 -0
  76. package/dist/src/sync/wasm-init.browser.d.ts.map +1 -0
  77. package/dist/src/sync/wasm-init.browser.js +3 -0
  78. package/dist/src/sync/wasm-init.browser.js.map +1 -0
  79. package/dist/src/sync/wasm-init.d.ts +2 -0
  80. package/dist/src/sync/wasm-init.d.ts.map +1 -0
  81. package/dist/src/sync/wasm-init.js +13 -0
  82. package/dist/src/sync/wasm-init.js.map +1 -0
  83. package/dist/src/utils.d.ts +3 -3
  84. package/dist/src/utils.d.ts.map +1 -1
  85. package/dist/src/utils.js +2 -2
  86. package/dist/src/utils.js.map +1 -1
  87. package/package.json +10 -6
  88. package/src/debounce.ts +16 -51
  89. package/src/exchange-heads.ts +1 -23
  90. package/src/index.ts +1532 -1038
  91. package/src/integers.ts +102 -0
  92. package/src/pid.ts +23 -22
  93. package/src/ranges.ts +1204 -413
  94. package/src/replication-domain-hash.ts +43 -18
  95. package/src/replication-domain-time.ts +9 -9
  96. package/src/replication-domain.ts +21 -31
  97. package/src/replication.ts +10 -9
  98. package/src/role.ts +4 -6
  99. package/src/sync/index.ts +51 -0
  100. package/src/sync/rateless-iblt.ts +617 -0
  101. package/src/sync/simple.ts +403 -0
  102. package/src/sync/wasm-init.browser.ts +1 -0
  103. package/src/sync/wasm-init.ts +14 -0
  104. package/src/utils.ts +10 -4
package/src/ranges.ts CHANGED
@@ -1,5 +1,11 @@
1
- import { deserialize, field, serialize, variant } from "@dao-xyz/borsh";
2
- import { PublicSignKey, equals, randomBytes, toBase64 } from "@peerbit/crypto";
1
+ import { deserialize, field, serialize, variant, vec } from "@dao-xyz/borsh";
2
+ import {
3
+ PublicSignKey,
4
+ equals,
5
+ randomBytes,
6
+ sha256Base64Sync,
7
+ toBase64,
8
+ } from "@peerbit/crypto";
3
9
  import {
4
10
  And,
5
11
  BoolQuery,
@@ -18,30 +24,48 @@ import {
18
24
  Sort,
19
25
  SortDirection,
20
26
  StringMatch,
21
- iteratorInSeries,
27
+ /* iteratorInSeries, */
22
28
  } from "@peerbit/indexer-interface";
23
29
  import { id } from "@peerbit/indexer-interface";
24
30
  import { Meta, ShallowMeta } from "@peerbit/log";
25
- import { type ReplicationChanges, type u32 } from "./replication-domain.js";
26
- import { MAX_U32, scaleToU32 } from "./role.js";
27
- import { groupByGidSync } from "./utils.js";
31
+ import {
32
+ MAX_U32,
33
+ MAX_U64,
34
+ type NumberFromType,
35
+ type Numbers,
36
+ } from "./integers.js";
37
+ import { type ReplicationChanges } from "./replication-domain.js";
28
38
 
29
39
  export enum ReplicationIntent {
30
40
  NonStrict = 0, // indicates that the segment will be replicated and nearby data might be replicated as well
31
41
  Strict = 1, // only replicate data in the segment to the specified replicator, not any other data
32
42
  }
33
43
 
34
- export const getSegmentsFromOffsetAndRange = (
35
- offset: number,
36
- factor: number,
37
- ): [[number, number], [number, number]] => {
44
+ export enum SyncStatus {
45
+ Unsynced = 0,
46
+ Synced = 1,
47
+ }
48
+
49
+ const min = (a: number | bigint, b: number | bigint) => (a < b ? a : b);
50
+
51
+ const getSegmentsFromOffsetAndRange = <T extends number | bigint>(
52
+ offset: T,
53
+ factor: T,
54
+ zero: T,
55
+ max: T,
56
+ ): [[T, T], [T, T]] => {
38
57
  let start1 = offset;
58
+ // @ts-ignore
39
59
  let end1Unscaled = offset + factor; // only add factor if it is not 1 to prevent numerical issues (like (0.9 + 1) % 1 => 0.8999999)
40
- let end1 = Math.min(end1Unscaled, MAX_U32);
60
+ let end1: T = min(end1Unscaled, max) as T;
41
61
  return [
42
62
  [start1, end1],
43
- end1Unscaled > MAX_U32
44
- ? [0, (factor !== MAX_U32 ? offset + factor : offset) % MAX_U32]
63
+ /* eslint-disable no-irregular-whitespace */
64
+ // @ts-ignore
65
+ end1Unscaled > max
66
+ ? /* eslint-disable no-irregular-whitespace */
67
+ // @ts-ignore
68
+ [zero, (factor !== max ? offset + factor : offset) % max]
45
69
  : [start1, end1],
46
70
  ];
47
71
  };
@@ -59,8 +83,8 @@ export const shouldAssigneToRangeBoundary = (
59
83
  ) => {
60
84
  let assignedToRangeBoundary = leaders === false || leaders.size < minReplicas;
61
85
  if (!assignedToRangeBoundary && leaders) {
62
- for (const [_, { intersecting }] of leaders) {
63
- if (!intersecting) {
86
+ for (const [_, value] of leaders) {
87
+ if (!value.intersecting) {
64
88
  assignedToRangeBoundary = true;
65
89
  break;
66
90
  }
@@ -68,18 +92,77 @@ export const shouldAssigneToRangeBoundary = (
68
92
  }
69
93
  return assignedToRangeBoundary;
70
94
  };
71
- export class EntryReplicated {
95
+ export interface EntryReplicated<R extends "u32" | "u64"> {
96
+ hash: string; // id of the entry
97
+ gid: string;
98
+ coordinates: NumberFromType<R>[];
99
+ wallTime: bigint;
100
+ assignedToRangeBoundary: boolean;
101
+ get meta(): ShallowMeta;
102
+ }
103
+
104
+ export const isEntryReplicated = (x: any): x is EntryReplicated<any> => {
105
+ return x instanceof EntryReplicatedU32 || x instanceof EntryReplicatedU64;
106
+ };
107
+
108
+ export class EntryReplicatedU32 implements EntryReplicated<"u32"> {
72
109
  @id({ type: "string" })
73
- id: string; // hash + coordinate
110
+ hash: string;
74
111
 
75
112
  @field({ type: "string" })
113
+ gid: string;
114
+
115
+ @field({ type: vec("u32") })
116
+ coordinates: number[];
117
+
118
+ @field({ type: "u64" })
119
+ wallTime: bigint;
120
+
121
+ @field({ type: "bool" })
122
+ assignedToRangeBoundary: boolean;
123
+
124
+ @field({ type: Uint8Array })
125
+ private _meta: Uint8Array;
126
+
127
+ private _metaResolved: ShallowMeta;
128
+
129
+ constructor(properties: {
130
+ coordinates: number[];
131
+ hash: string;
132
+ meta: Meta;
133
+ assignedToRangeBoundary: boolean;
134
+ }) {
135
+ this.coordinates = properties.coordinates;
136
+ this.hash = properties.hash;
137
+ this.gid = properties.meta.gid;
138
+ this.wallTime = properties.meta.clock.timestamp.wallTime;
139
+ const shallow =
140
+ properties.meta instanceof Meta
141
+ ? new ShallowMeta(properties.meta)
142
+ : properties.meta;
143
+ this._meta = serialize(shallow);
144
+ this._metaResolved = deserialize(this._meta, ShallowMeta);
145
+ this._metaResolved = properties.meta;
146
+ this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
147
+ }
148
+
149
+ get meta(): ShallowMeta {
150
+ if (!this._metaResolved) {
151
+ this._metaResolved = deserialize(this._meta, ShallowMeta);
152
+ }
153
+ return this._metaResolved;
154
+ }
155
+ }
156
+
157
+ export class EntryReplicatedU64 implements EntryReplicated<"u64"> {
158
+ @id({ type: "string" })
76
159
  hash: string;
77
160
 
78
161
  @field({ type: "string" })
79
162
  gid: string;
80
163
 
81
- @field({ type: "u32" })
82
- coordinate: number;
164
+ @field({ type: vec("u64") })
165
+ coordinates: bigint[];
83
166
 
84
167
  @field({ type: "u64" })
85
168
  wallTime: bigint;
@@ -93,15 +176,14 @@ export class EntryReplicated {
93
176
  private _metaResolved: ShallowMeta;
94
177
 
95
178
  constructor(properties: {
96
- coordinate: number;
179
+ coordinates: bigint[];
97
180
  hash: string;
98
181
  meta: Meta;
99
182
  assignedToRangeBoundary: boolean;
100
183
  }) {
101
- this.coordinate = properties.coordinate;
184
+ this.coordinates = properties.coordinates;
102
185
  this.hash = properties.hash;
103
186
  this.gid = properties.meta.gid;
104
- this.id = this.hash + "-" + this.coordinate;
105
187
  this.wallTime = properties.meta.clock.timestamp.wallTime;
106
188
  const shallow =
107
189
  properties.meta instanceof Meta
@@ -121,8 +203,25 @@ export class EntryReplicated {
121
203
  }
122
204
  }
123
205
 
206
+ export interface ReplicationRangeMessage<R extends "u32" | "u64"> {
207
+ id: Uint8Array;
208
+ timestamp: bigint;
209
+ get offset(): NumberFromType<R>;
210
+ get factor(): NumberFromType<R>;
211
+ mode: ReplicationIntent;
212
+ toReplicationRangeIndexable(key: PublicSignKey): ReplicationRangeIndexable<R>;
213
+ }
214
+
215
+ export const isReplicationRangeMessage = (
216
+ x: any,
217
+ ): x is ReplicationRangeMessage<any> => {
218
+ return x instanceof ReplicationRangeMessage;
219
+ };
220
+
221
+ export abstract class ReplicationRangeMessage<R extends "u32" | "u64"> {}
222
+
124
223
  @variant(0)
125
- export class ReplicationRange {
224
+ export class ReplicationRangeMessageU32 extends ReplicationRangeMessage<"u32"> {
126
225
  @field({ type: Uint8Array })
127
226
  id: Uint8Array;
128
227
 
@@ -145,6 +244,7 @@ export class ReplicationRange {
145
244
  timestamp: bigint;
146
245
  mode: ReplicationIntent;
147
246
  }) {
247
+ super();
148
248
  const { id, offset, factor, timestamp, mode } = properties;
149
249
  this.id = id;
150
250
  this._offset = offset;
@@ -161,8 +261,65 @@ export class ReplicationRange {
161
261
  return this._offset;
162
262
  }
163
263
 
164
- toReplicationRangeIndexable(key: PublicSignKey): ReplicationRangeIndexable {
165
- return new ReplicationRangeIndexable({
264
+ toReplicationRangeIndexable(
265
+ key: PublicSignKey,
266
+ ): ReplicationRangeIndexableU32 {
267
+ return new ReplicationRangeIndexableU32({
268
+ id: this.id,
269
+ publicKeyHash: key.hashcode(),
270
+ offset: this.offset,
271
+ length: this.factor,
272
+ timestamp: this.timestamp,
273
+ mode: this.mode,
274
+ });
275
+ }
276
+ }
277
+
278
+ @variant(1)
279
+ export class ReplicationRangeMessageU64 extends ReplicationRangeMessage<"u64"> {
280
+ @field({ type: Uint8Array })
281
+ id: Uint8Array;
282
+
283
+ @field({ type: "u64" })
284
+ timestamp: bigint;
285
+
286
+ @field({ type: "u64" })
287
+ private _offset: bigint;
288
+
289
+ @field({ type: "u64" })
290
+ private _factor: bigint;
291
+
292
+ @field({ type: "u8" })
293
+ mode: ReplicationIntent;
294
+
295
+ constructor(properties: {
296
+ id: Uint8Array;
297
+ offset: bigint;
298
+ factor: bigint;
299
+ timestamp: bigint;
300
+ mode: ReplicationIntent;
301
+ }) {
302
+ super();
303
+ const { id, offset, factor, timestamp, mode } = properties;
304
+ this.id = id;
305
+ this._offset = offset;
306
+ this._factor = factor;
307
+ this.timestamp = timestamp;
308
+ this.mode = mode;
309
+ }
310
+
311
+ get factor(): bigint {
312
+ return this._factor;
313
+ }
314
+
315
+ get offset(): bigint {
316
+ return this._offset;
317
+ }
318
+
319
+ toReplicationRangeIndexable(
320
+ key: PublicSignKey,
321
+ ): ReplicationRangeIndexableU64 {
322
+ return new ReplicationRangeIndexableU64({
166
323
  id: this.id,
167
324
  publicKeyHash: key.hashcode(),
168
325
  offset: this.offset,
@@ -173,7 +330,91 @@ export class ReplicationRange {
173
330
  }
174
331
  }
175
332
 
176
- export class ReplicationRangeIndexable {
333
+ class HashableSegmentU32 {
334
+ @field({ type: "u32" })
335
+ start1!: number;
336
+
337
+ @field({ type: "u32" })
338
+ end1!: number;
339
+
340
+ @field({ type: "u32" })
341
+ start2!: number;
342
+
343
+ @field({ type: "u32" })
344
+ end2!: number;
345
+
346
+ @field({ type: "u8" })
347
+ mode: ReplicationIntent;
348
+
349
+ constructor(properties: {
350
+ start1: number;
351
+ start2: number;
352
+ end1: number;
353
+ end2: number;
354
+ mode: ReplicationIntent;
355
+ }) {
356
+ this.start1 = properties.start1;
357
+ this.end1 = properties.end1;
358
+ this.start2 = properties.start2;
359
+ this.end2 = properties.end2;
360
+ this.mode = properties.mode;
361
+ }
362
+ }
363
+
364
+ class HashableSegmentU64 {
365
+ @field({ type: "u64" })
366
+ start1!: bigint;
367
+
368
+ @field({ type: "u64" })
369
+ end1!: bigint;
370
+
371
+ @field({ type: "u64" })
372
+ start2!: bigint;
373
+
374
+ @field({ type: "u64" })
375
+ end2!: bigint;
376
+
377
+ @field({ type: "u8" })
378
+ mode: ReplicationIntent;
379
+
380
+ constructor(properties: {
381
+ start1: bigint;
382
+ start2: bigint;
383
+ end1: bigint;
384
+ end2: bigint;
385
+ mode: ReplicationIntent;
386
+ }) {
387
+ this.start1 = properties.start1;
388
+ this.end1 = properties.end1;
389
+ this.start2 = properties.start2;
390
+ this.end2 = properties.end2;
391
+ this.mode = properties.mode;
392
+ }
393
+ }
394
+
395
+ export interface ReplicationRangeIndexable<R extends "u32" | "u64"> {
396
+ id: Uint8Array;
397
+ idString: string;
398
+ hash: string;
399
+ timestamp: bigint;
400
+ start1: NumberFromType<R>;
401
+ end1: NumberFromType<R>;
402
+ start2: NumberFromType<R>;
403
+ end2: NumberFromType<R>;
404
+ width: NumberFromType<R>;
405
+ widthNormalized: number;
406
+ mode: ReplicationIntent;
407
+ wrapped: boolean;
408
+ toUniqueSegmentId(): string;
409
+ toReplicationRange(): ReplicationRangeMessage<R>;
410
+ contains(point: NumberFromType<R>): boolean;
411
+ equalRange(other: ReplicationRangeIndexable<R>): boolean;
412
+ overlaps(other: ReplicationRangeIndexable<R>): boolean;
413
+ }
414
+
415
+ export class ReplicationRangeIndexableU32
416
+ implements ReplicationRangeIndexable<"u32">
417
+ {
177
418
  @id({ type: Uint8Array })
178
419
  id: Uint8Array;
179
420
 
@@ -204,7 +445,6 @@ export class ReplicationRangeIndexable {
204
445
  constructor(
205
446
  properties: {
206
447
  id?: Uint8Array;
207
- normalized?: boolean;
208
448
  offset: number;
209
449
  length: number;
210
450
  mode?: ReplicationIntent;
@@ -215,14 +455,7 @@ export class ReplicationRangeIndexable {
215
455
  this.hash =
216
456
  (properties as { publicKeyHash: string }).publicKeyHash ||
217
457
  (properties as { publicKey: PublicSignKey }).publicKey.hashcode();
218
- if (!properties.normalized) {
219
- this.transform({ length: properties.length, offset: properties.offset });
220
- } else {
221
- this.transform({
222
- length: scaleToU32(properties.length),
223
- offset: scaleToU32(properties.offset),
224
- });
225
- }
458
+ this.transform({ length: properties.length, offset: properties.offset });
226
459
 
227
460
  this.mode = properties.mode ?? ReplicationIntent.NonStrict;
228
461
  this.timestamp = properties.timestamp || BigInt(0);
@@ -232,6 +465,8 @@ export class ReplicationRangeIndexable {
232
465
  const ranges = getSegmentsFromOffsetAndRange(
233
466
  properties.offset,
234
467
  properties.length,
468
+ 0,
469
+ MAX_U32,
235
470
  );
236
471
  this.start1 = Math.round(ranges[0][0]);
237
472
  this.end1 = Math.round(ranges[0][1]);
@@ -244,11 +479,11 @@ export class ReplicationRangeIndexable {
244
479
  (this.end2 < this.end1 ? this.end2 - this.start2 : 0);
245
480
 
246
481
  if (
247
- this.start1 > 0xffffffff ||
248
- this.end1 > 0xffffffff ||
249
- this.start2 > 0xffffffff ||
250
- this.end2 > 0xffffffff ||
251
- this.width > 0xffffffff ||
482
+ this.start1 > MAX_U32 ||
483
+ this.end1 > MAX_U32 ||
484
+ this.start2 > MAX_U32 ||
485
+ this.end2 > MAX_U32 ||
486
+ this.width > MAX_U32 ||
252
487
  this.width < 0
253
488
  ) {
254
489
  throw new Error("Segment coordinate out of bounds");
@@ -266,7 +501,7 @@ export class ReplicationRangeIndexable {
266
501
  );
267
502
  }
268
503
 
269
- overlaps(other: ReplicationRangeIndexable, checkOther = true): boolean {
504
+ overlaps(other: ReplicationRangeIndexableU32, checkOther = true): boolean {
270
505
  if (
271
506
  this.contains(other.start1) ||
272
507
  this.contains(other.start2) ||
@@ -282,7 +517,7 @@ export class ReplicationRangeIndexable {
282
517
  return false;
283
518
  }
284
519
  toReplicationRange() {
285
- return new ReplicationRange({
520
+ return new ReplicationRangeMessageU32({
286
521
  id: this.id,
287
522
  offset: this.start1,
288
523
  factor: this.width,
@@ -291,15 +526,6 @@ export class ReplicationRangeIndexable {
291
526
  });
292
527
  }
293
528
 
294
- distanceTo(point: number) {
295
- let wrappedPoint = MAX_U32 - point;
296
- return Math.min(
297
- Math.abs(this.start1 - point),
298
- Math.abs(this.end2 - point),
299
- Math.abs(this.start1 - wrappedPoint),
300
- Math.abs(this.end2 - wrappedPoint),
301
- );
302
- }
303
529
  get wrapped() {
304
530
  return this.end2 < this.end1;
305
531
  }
@@ -308,7 +534,7 @@ export class ReplicationRangeIndexable {
308
534
  return this.width / MAX_U32;
309
535
  }
310
536
 
311
- equals(other: ReplicationRangeIndexable) {
537
+ equals(other: ReplicationRangeIndexableU32) {
312
538
  if (
313
539
  equals(this.id, other.id) &&
314
540
  this.hash === other.hash &&
@@ -326,7 +552,7 @@ export class ReplicationRangeIndexable {
326
552
  return false;
327
553
  }
328
554
 
329
- equalRange(other: ReplicationRangeIndexable) {
555
+ equalRange(other: ReplicationRangeIndexableU32) {
330
556
  return (
331
557
  this.start1 === other.start1 &&
332
558
  this.end1 === other.end1 &&
@@ -348,137 +574,494 @@ export class ReplicationRangeIndexable {
348
574
  return `(hash ${this.hash} range: ${this.toString()})`;
349
575
  }
350
576
 
351
- /* removeRange(other: ReplicationRangeIndexable): ReplicationRangeIndexable | ReplicationRangeIndexable[] {
352
- if (!this.overlaps(other)) {
353
- return this
577
+ toUniqueSegmentId() {
578
+ // return a unique id as a function of the segments location and the replication intent
579
+ const hashable = new HashableSegmentU32(this);
580
+ return sha256Base64Sync(serialize(hashable));
581
+ }
582
+ }
583
+
584
+ export class ReplicationRangeIndexableU64
585
+ implements ReplicationRangeIndexable<"u64">
586
+ {
587
+ @id({ type: Uint8Array })
588
+ id: Uint8Array;
589
+
590
+ @field({ type: "string" })
591
+ hash: string;
592
+
593
+ @field({ type: "u64" })
594
+ timestamp: bigint;
595
+
596
+ @field({ type: "u64" })
597
+ start1!: bigint;
598
+
599
+ @field({ type: "u64" })
600
+ end1!: bigint;
601
+
602
+ @field({ type: "u64" })
603
+ start2!: bigint;
604
+
605
+ @field({ type: "u64" })
606
+ end2!: bigint;
607
+
608
+ @field({ type: "u64" })
609
+ width!: bigint;
610
+
611
+ @field({ type: "u8" })
612
+ mode: ReplicationIntent;
613
+
614
+ constructor(
615
+ properties: {
616
+ id?: Uint8Array;
617
+ offset: bigint | number;
618
+ length: bigint | number;
619
+ mode?: ReplicationIntent;
620
+ timestamp?: bigint;
621
+ } & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
622
+ ) {
623
+ this.id = properties.id ?? randomBytes(32);
624
+ this.hash =
625
+ (properties as { publicKeyHash: string }).publicKeyHash ||
626
+ (properties as { publicKey: PublicSignKey }).publicKey.hashcode();
627
+ this.transform({ length: properties.length, offset: properties.offset });
628
+
629
+ this.mode = properties.mode ?? ReplicationIntent.NonStrict;
630
+ this.timestamp = properties.timestamp || BigInt(0);
631
+ }
632
+
633
+ private transform(properties: {
634
+ offset: bigint | number;
635
+ length: bigint | number;
636
+ }) {
637
+ const ranges = getSegmentsFromOffsetAndRange(
638
+ BigInt(properties.offset),
639
+ BigInt(properties.length),
640
+ 0n,
641
+ MAX_U64,
642
+ );
643
+ this.start1 = ranges[0][0];
644
+ this.end1 = ranges[0][1];
645
+ this.start2 = ranges[1][0];
646
+ this.end2 = ranges[1][1];
647
+
648
+ this.width =
649
+ this.end1 -
650
+ this.start1 +
651
+ (this.end2 < this.end1 ? this.end2 - this.start2 : 0n);
652
+
653
+ if (
654
+ this.start1 > MAX_U64 ||
655
+ this.end1 > MAX_U64 ||
656
+ this.start2 > MAX_U64 ||
657
+ this.end2 > MAX_U64 ||
658
+ this.width > MAX_U64 ||
659
+ this.width < 0n
660
+ ) {
661
+ throw new Error("Segment coordinate out of bounds");
354
662
  }
663
+ }
664
+
665
+ get idString() {
666
+ return toBase64(this.id);
667
+ }
355
668
 
356
- if (this.equalRange(other)) {
357
- return []
669
+ contains(point: bigint) {
670
+ return (
671
+ (point >= this.start1 && point < this.end1) ||
672
+ (point >= this.start2 && point < this.end2)
673
+ );
674
+ }
675
+
676
+ overlaps(other: ReplicationRangeIndexableU64, checkOther = true): boolean {
677
+ if (
678
+ this.contains(other.start1) ||
679
+ this.contains(other.start2) ||
680
+ this.contains(other.end1 - 1n) ||
681
+ this.contains(other.end2 - 1n)
682
+ ) {
683
+ return true;
358
684
  }
359
685
 
360
- let diff: ReplicationRangeIndexable[] = [];
361
- let start1 = this.start1;
362
- if (other.start1 > start1) {
363
- diff.push(new ReplicationRangeIndexable({
364
- id: this.id,
365
- offset: this.start1,
366
- length: other.start1 - this.start1,
367
- mode: this.mode,
368
- publicKeyHash: this.hash,
369
- timestamp: this.timestamp,
370
- normalized: false
371
- }));
372
-
373
- start1 = other.end2
686
+ if (checkOther) {
687
+ return other.overlaps(this, false);
374
688
  }
689
+ return false;
690
+ }
691
+ toReplicationRange() {
692
+ return new ReplicationRangeMessageU64({
693
+ id: this.id,
694
+ offset: this.start1,
695
+ factor: this.width,
696
+ timestamp: this.timestamp,
697
+ mode: this.mode,
698
+ });
699
+ }
375
700
 
376
- if (other.end1 < this.end1) {
377
- diff.push(new ReplicationRangeIndexable({
378
- id: this.id,
379
- offset: other.end1,
380
- length: this.end1 - other.end1,
381
- mode: this.mode,
382
- publicKeyHash: this.hash,
383
- timestamp: this.timestamp,
384
- normalized: false
385
- }));
701
+ get wrapped() {
702
+ return this.end2 < this.end1;
703
+ }
704
+
705
+ get widthNormalized() {
706
+ return Number(this.width) / Number(MAX_U64);
707
+ }
708
+
709
+ equals(other: ReplicationRangeIndexableU64) {
710
+ if (
711
+ equals(this.id, other.id) &&
712
+ this.hash === other.hash &&
713
+ this.timestamp === other.timestamp &&
714
+ this.mode === other.mode &&
715
+ this.start1 === other.start1 &&
716
+ this.end1 === other.end1 &&
717
+ this.start2 === other.start2 &&
718
+ this.end2 === other.end2 &&
719
+ this.width === other.width
720
+ ) {
721
+ return true;
386
722
  }
387
723
 
388
- if (other.start2 > this.start2) {
389
- diff.push(new ReplicationRangeIndexable({
390
- id: this.id,
391
- offset: this.start2,
392
- length: other.start2 - this.start2,
393
- mode: this.mode,
394
- publicKeyHash: this.hash,
395
- timestamp: this.timestamp,
396
- normalized: false
397
- }));
724
+ return false;
725
+ }
726
+
727
+ equalRange(other: ReplicationRangeIndexableU64) {
728
+ return (
729
+ this.start1 === other.start1 &&
730
+ this.end1 === other.end1 &&
731
+ this.start2 === other.start2 &&
732
+ this.end2 === other.end2
733
+ );
734
+ }
735
+
736
+ toString() {
737
+ let roundToTwoDecimals = (num: number) => Math.round(num * 100) / 100;
738
+
739
+ if (Math.abs(Number(this.start1 - this.start2)) < 0.0001) {
740
+ return `([${roundToTwoDecimals(Number(this.start1) / Number(MAX_U64))}, ${roundToTwoDecimals(Number(this.start1) / Number(MAX_U64))}])`;
398
741
  }
742
+ return `([${roundToTwoDecimals(Number(this.start1) / Number(MAX_U64))}, ${roundToTwoDecimals(Number(this.start1) / Number(MAX_U64))}] [${roundToTwoDecimals(Number(this.start2) / Number(MAX_U64))}, ${roundToTwoDecimals(Number(this.end2) / Number(MAX_U64))}])`;
743
+ }
744
+
745
+ toStringDetailed() {
746
+ return `(hash ${this.hash} range: ${this.toString()})`;
747
+ }
748
+
749
+ toUniqueSegmentId() {
750
+ // return a unique id as a function of the segments location and the replication intent
751
+ const hashable = new HashableSegmentU64(this);
752
+ return sha256Base64Sync(serialize(hashable));
753
+ }
754
+ }
755
+
756
+ export const mergeRanges = <R extends "u32" | "u64">(
757
+ segments: ReplicationRangeIndexable<R>[],
758
+ numbers: { zero: NumberFromType<R>; maxValue: NumberFromType<R> },
759
+ ) => {
760
+ if (segments.length === 0) {
761
+ throw new Error("No segments to merge");
762
+ }
763
+ if (segments.length === 1) {
764
+ return segments[0];
765
+ }
766
+
767
+ // only allow merging from same publicKeyHash
768
+ const sameHash = segments.every((x) => x.hash === segments[0].hash);
769
+ if (!sameHash) {
770
+ throw new Error("Segments have different publicKeyHash");
771
+ }
772
+
773
+ // only allow merging segments with length 1 (trivial)
774
+ const sameLength = segments.every((x) => x.width === 1 || x.width === 1n);
775
+ if (!sameLength) {
776
+ throw new Error(
777
+ "Segments have different length, only merging of segments length 1 is supported",
778
+ );
779
+ }
780
+
781
+ const sorted = segments.sort((a, b) => Number(a.start1 - b.start1));
782
+
783
+ let calculateLargeGap = (): [NumberFromType<R>, number] => {
784
+ let last = sorted[sorted.length - 1];
785
+ let largestArc = numbers.zero;
786
+ let largestArcIndex = -1;
787
+ for (let i = 0; i < sorted.length; i++) {
788
+ const current = sorted[i];
789
+ if (current.start1 !== last.start1) {
790
+ let arc = numbers.zero;
791
+ if (current.start1 < last.end2) {
792
+ arc += ((numbers.maxValue as any) - last.end2) as any;
793
+
794
+ arc += (current.start1 - numbers.zero) as any;
795
+ } else {
796
+ arc += (current.start1 - last.end2) as any;
797
+ }
399
798
 
400
- if (other.end2 < this.end2) {
401
- diff.push(new ReplicationRangeIndexable({
402
- id: this.id,
403
- offset: other.end2,
404
- length: this.end2 - other.end2,
405
- mode: this.mode,
406
- publicKeyHash: this.hash,
407
- timestamp: this.timestamp,
408
- normalized: false
409
- }));
799
+ if (arc > largestArc) {
800
+ largestArc = arc;
801
+ largestArcIndex = i;
802
+ }
803
+ }
804
+ last = current;
410
805
  }
411
806
 
412
- return diff;
413
- } */
414
- }
807
+ return [largestArc, largestArcIndex];
808
+ };
809
+ const [largestArc, largestArcIndex] = calculateLargeGap();
810
+
811
+ let totalLengthFinal: number = numbers.maxValue - largestArc;
812
+
813
+ if (largestArcIndex === -1) {
814
+ return segments[0]; // all ranges are the same
815
+ }
816
+ // use segments[0] constructor to create a new object
817
+
818
+ const proto = segments[0].constructor;
819
+ return new (proto as any)({
820
+ length: totalLengthFinal,
821
+ offset: segments[largestArcIndex].start1,
822
+ publicKeyHash: segments[0].hash,
823
+ });
824
+ };
825
+
826
+ const createContainingPointQuery = <R extends "u32" | "u64">(
827
+ points: NumberFromType<R>[] | NumberFromType<R>,
828
+ options?: {
829
+ time?: {
830
+ roleAgeLimit: number;
831
+ matured: boolean;
832
+ now: number;
833
+ };
834
+ },
835
+ ) => {
836
+ const or: Query[] = [];
837
+ for (const point of Array.isArray(points) ? points : [points]) {
838
+ or.push(
839
+ new And([
840
+ new IntegerCompare({
841
+ key: "start1",
842
+ compare: Compare.LessOrEqual,
843
+ value: point,
844
+ }),
845
+ new IntegerCompare({
846
+ key: "end1",
847
+ compare: Compare.Greater,
848
+ value: point,
849
+ }),
850
+ ]),
851
+ );
852
+ or.push(
853
+ new And([
854
+ new IntegerCompare({
855
+ key: "start2",
856
+ compare: Compare.LessOrEqual,
857
+ value: point,
858
+ }),
859
+ new IntegerCompare({
860
+ key: "end2",
861
+ compare: Compare.Greater,
862
+ value: point,
863
+ }),
864
+ ]),
865
+ );
866
+ }
867
+ if (options?.time) {
868
+ let queries = [
869
+ new Or(or),
870
+ new IntegerCompare({
871
+ key: "timestamp",
872
+ compare: options.time.matured ? Compare.LessOrEqual : Compare.Greater,
873
+ value: BigInt(options.time.now - options.time.roleAgeLimit),
874
+ }),
875
+ ];
876
+ return queries;
877
+ } else {
878
+ return new Or(or);
879
+ }
880
+ };
881
+
882
+ const createContainingPartialPointQuery = <R extends "u32" | "u64">(
883
+ point: NumberFromType<R>,
884
+ first: boolean,
885
+ options?: {
886
+ time?: {
887
+ roleAgeLimit: number;
888
+ matured: boolean;
889
+ now: number;
890
+ };
891
+ },
892
+ ) => {
893
+ let query: Query[];
894
+ if (first) {
895
+ query = [
896
+ new IntegerCompare({
897
+ key: "start1",
898
+ compare: Compare.LessOrEqual,
899
+ value: point,
900
+ }),
901
+ new IntegerCompare({
902
+ key: "end1",
903
+ compare: Compare.Greater,
904
+ value: point,
905
+ }),
906
+ ];
907
+ } else {
908
+ query = [
909
+ new IntegerCompare({
910
+ key: "start2",
911
+ compare: Compare.LessOrEqual,
912
+ value: point,
913
+ }),
914
+ new IntegerCompare({
915
+ key: "end2",
916
+ compare: Compare.Greater,
917
+ value: point,
918
+ }),
919
+ ];
920
+ }
921
+
922
+ if (options?.time) {
923
+ query.push(
924
+ new IntegerCompare({
925
+ key: "timestamp",
926
+ compare: options.time.matured ? Compare.LessOrEqual : Compare.Greater,
927
+ value: BigInt(options.time.now - options.time.roleAgeLimit),
928
+ }),
929
+ );
930
+ }
931
+
932
+ return query;
933
+ };
934
+
935
+ const iterateRangesContainingPoint = <
936
+ S extends Shape | undefined,
937
+ R extends "u32" | "u64",
938
+ >(
939
+ rects: Index<ReplicationRangeIndexable<R>>,
940
+ points: NumberFromType<R>[] | NumberFromType<R>,
415
941
 
416
- const containingPoint = <S extends Shape | undefined = undefined>(
417
- rects: Index<ReplicationRangeIndexable>,
418
- point: number,
419
- roleAgeLimit: number,
420
- matured: boolean,
421
- now: number,
422
942
  options?: {
423
943
  shape?: S;
424
944
  sort?: Sort[];
945
+ time?: {
946
+ roleAgeLimit: number;
947
+ matured: boolean;
948
+ now: number;
949
+ };
425
950
  },
426
- ): IndexIterator<ReplicationRangeIndexable, S> => {
951
+ ): IndexIterator<ReplicationRangeIndexable<R>, S> => {
427
952
  // point is between 0 and 1, and the range can start at any offset between 0 and 1 and have length between 0 and 1
428
953
 
429
- let queries = [
430
- new Or([
431
- new And([
432
- new IntegerCompare({
433
- key: "start1",
434
- compare: Compare.LessOrEqual,
435
- value: point,
436
- }),
437
- new IntegerCompare({
438
- key: "end1",
439
- compare: Compare.Greater,
440
- value: point,
441
- }),
442
- ]),
443
- new And([
444
- new IntegerCompare({
445
- key: "start2",
446
- compare: Compare.LessOrEqual,
447
- value: point,
448
- }),
449
- new IntegerCompare({
450
- key: "end2",
451
- compare: Compare.Greater,
452
- value: point,
453
- }),
454
- ]),
455
- ]),
456
- new IntegerCompare({
457
- key: "timestamp",
458
- compare: matured ? Compare.LessOrEqual : Compare.Greater,
459
- value: BigInt(now - roleAgeLimit),
460
- }),
461
- ];
462
954
  return rects.iterate(
463
955
  {
464
- query: queries,
956
+ query: createContainingPointQuery(points, {
957
+ time: options?.time,
958
+ }), // new Or(points.map(point => new And(createContainingPointQuery(point, roleAgeLimit, matured, now)))
465
959
  sort: options?.sort,
466
960
  },
467
961
  options,
468
962
  );
469
963
  };
470
964
 
471
- const getClosest = <S extends Shape | undefined = undefined>(
965
+ const allRangesContainingPoint = async <
966
+ S extends Shape | undefined,
967
+ R extends "u32" | "u64",
968
+ >(
969
+ rects: Index<ReplicationRangeIndexable<R>>,
970
+ points: NumberFromType<R>[] | NumberFromType<R>,
971
+ options?: {
972
+ shape?: S;
973
+ sort?: Sort[];
974
+ time?: {
975
+ roleAgeLimit: number;
976
+ matured: boolean;
977
+ now: number;
978
+ };
979
+ },
980
+ ) => {
981
+ // point is between 0 and 1, and the range can start at any offset between 0 and 1 and have length between 0 and 1
982
+
983
+ let allResults: IndexedResult<
984
+ ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>
985
+ >[] = [];
986
+ for (const point of Array.isArray(points) ? points : [points]) {
987
+ const firstIterator = rects.iterate(
988
+ {
989
+ query: createContainingPartialPointQuery(point, false, options),
990
+ sort: options?.sort,
991
+ },
992
+ options,
993
+ );
994
+
995
+ const secondIterator = rects.iterate(
996
+ {
997
+ query: createContainingPartialPointQuery(point, true, options),
998
+ sort: options?.sort,
999
+ },
1000
+ options,
1001
+ );
1002
+
1003
+ [...(await firstIterator.all()), ...(await secondIterator.all())].forEach(
1004
+ (x) => allResults.push(x),
1005
+ );
1006
+ }
1007
+ return allResults;
1008
+ /* return [...await iterateRangesContainingPoint(rects, points, options).all()]; */
1009
+ };
1010
+
1011
+ const countRangesContainingPoint = async <R extends "u32" | "u64">(
1012
+ rects: Index<ReplicationRangeIndexable<R>>,
1013
+ point: NumberFromType<R>,
1014
+ options?: {
1015
+ time?: {
1016
+ roleAgeLimit: number;
1017
+ matured: boolean;
1018
+ now: number;
1019
+ };
1020
+ },
1021
+ ) => {
1022
+ return rects.count({
1023
+ query: createContainingPointQuery(point, options),
1024
+ });
1025
+ };
1026
+
1027
+ export const appromixateCoverage = async <R extends "u32" | "u64">(properties: {
1028
+ peers: Index<ReplicationRangeIndexable<R>>;
1029
+ samples: number;
1030
+ numbers: Numbers<R>;
1031
+ roleAge?: number;
1032
+ normalized?: boolean; // if true, we dont care about the actual number of ranges, only if there is a range, hence the output will be between 0 and 1
1033
+ }) => {
1034
+ const grid = properties.numbers.getGrid(
1035
+ properties.numbers.zero,
1036
+ properties.samples,
1037
+ );
1038
+ /* const now = +new Date(); */
1039
+ let hits = 0;
1040
+ for (const point of grid) {
1041
+ const count = await countRangesContainingPoint(
1042
+ properties.peers,
1043
+ point,
1044
+ /* properties?.roleAge ?? 0,
1045
+ true,
1046
+ now, */
1047
+ );
1048
+ hits += properties.normalized ? (count > 0 ? 1 : 0) : count;
1049
+ }
1050
+ return hits / properties.samples;
1051
+ };
1052
+
1053
+ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
472
1054
  direction: "above" | "below",
473
- rects: Index<ReplicationRangeIndexable>,
474
- point: number,
1055
+ rects: Index<ReplicationRangeIndexable<R>>,
1056
+ point: NumberFromType<R>,
475
1057
  roleAgeLimit: number,
476
1058
  matured: boolean,
477
1059
  now: number,
478
1060
  includeStrict: boolean,
1061
+ numbers: Numbers<R>,
479
1062
  options?: { shape?: S },
480
- ): IndexIterator<ReplicationRangeIndexable, S> => {
481
- const createQueries = (p: number, equality: boolean) => {
1063
+ ): IndexIterator<ReplicationRangeIndexable<R>, S> => {
1064
+ const createQueries = (p: NumberFromType<R>, equality: boolean) => {
482
1065
  let queries: Query[];
483
1066
  if (direction === "below") {
484
1067
  queries = [
@@ -507,6 +1090,7 @@ const getClosest = <S extends Shape | undefined = undefined>(
507
1090
  }),
508
1091
  ];
509
1092
  }
1093
+
510
1094
  queries.push(
511
1095
  new IntegerCompare({ key: "width", compare: Compare.Greater, value: 0 }),
512
1096
  );
@@ -542,7 +1126,10 @@ const getClosest = <S extends Shape | undefined = undefined>(
542
1126
 
543
1127
  const iteratorWrapped = rects.iterate(
544
1128
  {
545
- query: createQueries(direction === "below" ? MAX_U32 : 0, true),
1129
+ query: createQueries(
1130
+ direction === "below" ? numbers.maxValue : numbers.zero,
1131
+ true,
1132
+ ),
546
1133
  sort: [
547
1134
  direction === "below"
548
1135
  ? new Sort({ key: ["end2"], direction: "desc" })
@@ -554,68 +1141,83 @@ const getClosest = <S extends Shape | undefined = undefined>(
554
1141
  options,
555
1142
  );
556
1143
 
557
- return joinIterator<S>([iterator, iteratorWrapped], point, direction);
1144
+ return joinIterator<S, R>(
1145
+ [iterator, iteratorWrapped],
1146
+ point,
1147
+ direction,
1148
+ numbers,
1149
+ );
558
1150
  };
559
1151
 
560
- export const hasCoveringRange = async (
561
- rects: Index<ReplicationRangeIndexable>,
562
- range: ReplicationRangeIndexable,
1152
+ export const getCoveringRangeQuery = (range: {
1153
+ start1: number | bigint;
1154
+ end1: number | bigint;
1155
+ start2: number | bigint;
1156
+ end2: number | bigint;
1157
+ }) => {
1158
+ return [
1159
+ new Or([
1160
+ new And([
1161
+ new IntegerCompare({
1162
+ key: "start1",
1163
+ compare: Compare.LessOrEqual,
1164
+ value: range.start1,
1165
+ }),
1166
+ new IntegerCompare({
1167
+ key: "end1",
1168
+ compare: Compare.GreaterOrEqual,
1169
+ value: range.end1,
1170
+ }),
1171
+ ]),
1172
+ new And([
1173
+ new IntegerCompare({
1174
+ key: "start2",
1175
+ compare: Compare.LessOrEqual,
1176
+ value: range.start1,
1177
+ }),
1178
+ new IntegerCompare({
1179
+ key: "end2",
1180
+ compare: Compare.GreaterOrEqual,
1181
+ value: range.end1,
1182
+ }),
1183
+ ]),
1184
+ ]),
1185
+ new Or([
1186
+ new And([
1187
+ new IntegerCompare({
1188
+ key: "start1",
1189
+ compare: Compare.LessOrEqual,
1190
+ value: range.start2,
1191
+ }),
1192
+ new IntegerCompare({
1193
+ key: "end1",
1194
+ compare: Compare.GreaterOrEqual,
1195
+ value: range.end2,
1196
+ }),
1197
+ ]),
1198
+ new And([
1199
+ new IntegerCompare({
1200
+ key: "start2",
1201
+ compare: Compare.LessOrEqual,
1202
+ value: range.start2,
1203
+ }),
1204
+ new IntegerCompare({
1205
+ key: "end2",
1206
+ compare: Compare.GreaterOrEqual,
1207
+ value: range.end2,
1208
+ }),
1209
+ ]),
1210
+ ]),
1211
+ ];
1212
+ };
1213
+ export const iHaveCoveringRange = async <R extends "u32" | "u64">(
1214
+ rects: Index<ReplicationRangeIndexable<R>>,
1215
+ range: ReplicationRangeIndexable<R>,
563
1216
  ) => {
564
1217
  return (
565
1218
  (await rects.count({
566
1219
  query: [
567
- new Or([
568
- new And([
569
- new IntegerCompare({
570
- key: "start1",
571
- compare: Compare.LessOrEqual,
572
- value: range.start1,
573
- }),
574
- new IntegerCompare({
575
- key: "end1",
576
- compare: Compare.GreaterOrEqual,
577
- value: range.end1,
578
- }),
579
- ]),
580
- new And([
581
- new IntegerCompare({
582
- key: "start2",
583
- compare: Compare.LessOrEqual,
584
- value: range.start1,
585
- }),
586
- new IntegerCompare({
587
- key: "end2",
588
- compare: Compare.GreaterOrEqual,
589
- value: range.end1,
590
- }),
591
- ]),
592
- ]),
593
- new Or([
594
- new And([
595
- new IntegerCompare({
596
- key: "start1",
597
- compare: Compare.LessOrEqual,
598
- value: range.start2,
599
- }),
600
- new IntegerCompare({
601
- key: "end1",
602
- compare: Compare.GreaterOrEqual,
603
- value: range.end2,
604
- }),
605
- ]),
606
- new And([
607
- new IntegerCompare({
608
- key: "start2",
609
- compare: Compare.LessOrEqual,
610
- value: range.start2,
611
- }),
612
- new IntegerCompare({
613
- key: "end2",
614
- compare: Compare.GreaterOrEqual,
615
- value: range.end2,
616
- }),
617
- ]),
618
- ]),
1220
+ ...getCoveringRangeQuery(range),
619
1221
  new StringMatch({
620
1222
  key: "hash",
621
1223
  value: range.hash,
@@ -632,54 +1234,55 @@ export const hasCoveringRange = async (
632
1234
  );
633
1235
  };
634
1236
 
635
- export const getDistance = (
636
- from: number,
637
- to: number,
1237
+ // TODO
1238
+ export function getDistance(
1239
+ from: any,
1240
+ to: any,
638
1241
  direction: "above" | "below" | "closest",
639
- end = MAX_U32,
640
- ) => {
641
- // if direction is 'above' only measure distance from 'from to 'to' from above.
642
- // i.e if from < to, then from needs to wrap around 0 to 1 and then to to
643
- // if direction is 'below' and from > to, then from needs to wrap around 1 to 0 and then to to
644
- // if direction is 'closest' then the shortest distance is the distance
645
-
646
- // also from is 0.1 and to is 0.9, then distance should be 0.2 not 0.8
647
- // same as for if from is 0.9 and to is 0.1, then distance should be 0.2 not 0.8
1242
+ end: any,
1243
+ ): any {
1244
+ const abs = (value: number | bigint): number | bigint =>
1245
+ value < 0 ? -value : value;
1246
+ const diff = <T extends number | bigint>(a: T, b: T): T => abs(a - b) as T;
648
1247
 
649
1248
  if (direction === "closest") {
650
1249
  if (from === to) {
651
- return 0;
1250
+ return typeof from === "number" ? 0 : 0n; // returns 0 of the correct type
652
1251
  }
653
-
654
- return Math.min(Math.abs(from - to), Math.abs(end - Math.abs(from - to)));
1252
+ return diff(from, to) < diff(end, diff(from, to))
1253
+ ? diff(from, to)
1254
+ : diff(end, diff(from, to));
655
1255
  }
656
1256
 
657
1257
  if (direction === "above") {
658
1258
  if (from <= to) {
659
- return Math.abs(end - to) + from;
1259
+ return end - to + from;
660
1260
  }
661
1261
  return from - to;
662
1262
  }
663
1263
 
664
1264
  if (direction === "below") {
665
1265
  if (from >= to) {
666
- return Math.abs(end - from) + to;
1266
+ return end - from + to;
667
1267
  }
668
1268
  return to - from;
669
1269
  }
670
1270
 
671
1271
  throw new Error("Invalid direction");
672
- };
1272
+ }
673
1273
 
674
- const joinIterator = <S extends Shape | undefined = undefined>(
675
- iterators: IndexIterator<ReplicationRangeIndexable, S>[],
676
- point: number,
1274
+ const joinIterator = <S extends Shape | undefined, R extends "u32" | "u64">(
1275
+ iterators: IndexIterator<ReplicationRangeIndexable<R>, S>[],
1276
+ point: NumberFromType<R>,
677
1277
  direction: "above" | "below" | "closest",
678
- ): IndexIterator<ReplicationRangeIndexable, S> => {
1278
+ numbers: Numbers<R>,
1279
+ ): IndexIterator<ReplicationRangeIndexable<R>, S> => {
679
1280
  let queues: {
680
1281
  elements: {
681
- result: IndexedResult<ReturnTypeFromShape<ReplicationRangeIndexable, S>>;
682
- dist: number;
1282
+ result: IndexedResult<
1283
+ ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>
1284
+ >;
1285
+ dist: NumberFromType<R>;
683
1286
  }[];
684
1287
  }[] = [];
685
1288
 
@@ -687,10 +1290,10 @@ const joinIterator = <S extends Shape | undefined = undefined>(
687
1290
  next: async (
688
1291
  count: number,
689
1292
  ): Promise<
690
- IndexedResults<ReturnTypeFromShape<ReplicationRangeIndexable, S>>
1293
+ IndexedResults<ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>>
691
1294
  > => {
692
1295
  let results: IndexedResults<
693
- ReturnTypeFromShape<ReplicationRangeIndexable, S>
1296
+ ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>
694
1297
  > = [];
695
1298
  for (let i = 0; i < iterators.length; i++) {
696
1299
  let queue = queues[i];
@@ -705,16 +1308,36 @@ const joinIterator = <S extends Shape | undefined = undefined>(
705
1308
  for (const el of res) {
706
1309
  const closest = el.value;
707
1310
 
708
- let dist: number;
1311
+ let dist: NumberFromType<R>;
709
1312
  if (direction === "closest") {
710
- dist = Math.min(
711
- getDistance(closest.start1, point, direction),
712
- getDistance(closest.end2, point, direction),
1313
+ dist = numbers.min(
1314
+ getDistance(
1315
+ closest.start1,
1316
+ point as any,
1317
+ direction,
1318
+ numbers.maxValue as any,
1319
+ ) as NumberFromType<R>,
1320
+ getDistance(
1321
+ closest.end2,
1322
+ point as any,
1323
+ direction,
1324
+ numbers.maxValue as any,
1325
+ ) as NumberFromType<R>,
713
1326
  );
714
1327
  } else if (direction === "above") {
715
- dist = getDistance(closest.start1, point, direction);
1328
+ dist = getDistance(
1329
+ closest.start1,
1330
+ point as any,
1331
+ direction,
1332
+ numbers.maxValue as any,
1333
+ ) as NumberFromType<R>;
716
1334
  } else if (direction === "below") {
717
- dist = getDistance(closest.end2, point, direction);
1335
+ dist = getDistance(
1336
+ closest.end2,
1337
+ point as any,
1338
+ direction,
1339
+ numbers.maxValue as any,
1340
+ ) as NumberFromType<R>;
718
1341
  } else {
719
1342
  throw new Error("Invalid direction");
720
1343
  }
@@ -728,7 +1351,7 @@ const joinIterator = <S extends Shape | undefined = undefined>(
728
1351
 
729
1352
  for (let i = 0; i < count; i++) {
730
1353
  let closestQueue = -1;
731
- let closestDist = Number.MAX_SAFE_INTEGER;
1354
+ let closestDist: bigint | number = Number.MAX_VALUE;
732
1355
  for (let j = 0; j < queues.length; j++) {
733
1356
  let queue = queues[j];
734
1357
  if (queue && queue.elements.length > 0) {
@@ -763,7 +1386,7 @@ const joinIterator = <S extends Shape | undefined = undefined>(
763
1386
  },
764
1387
  all: async () => {
765
1388
  let results: IndexedResult<
766
- ReturnTypeFromShape<ReplicationRangeIndexable, S>
1389
+ ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>
767
1390
  >[] = [];
768
1391
  for (const iterator of iterators) {
769
1392
  let res = await iterator.all();
@@ -775,17 +1398,19 @@ const joinIterator = <S extends Shape | undefined = undefined>(
775
1398
  };
776
1399
 
777
1400
  const getClosestAround = <
778
- S extends (Shape & { timestamp: true }) | undefined = undefined,
1401
+ S extends (Shape & { timestamp: true }) | undefined,
1402
+ R extends "u32" | "u64",
779
1403
  >(
780
- peers: Index<ReplicationRangeIndexable>,
781
- point: number,
1404
+ peers: Index<ReplicationRangeIndexable<R>>,
1405
+ point: NumberFromType<R>,
782
1406
  roleAge: number,
783
1407
  now: number,
784
1408
  includeStrictBelow: boolean,
785
1409
  includeStrictAbove: boolean,
1410
+ numbers: Numbers<R>,
786
1411
  options?: { shape?: S },
787
1412
  ) => {
788
- const closestBelow = getClosest<S>(
1413
+ const closestBelow = getClosest<S, R>(
789
1414
  "below",
790
1415
  peers,
791
1416
  point,
@@ -793,9 +1418,10 @@ const getClosestAround = <
793
1418
  true,
794
1419
  now,
795
1420
  includeStrictBelow,
1421
+ numbers,
796
1422
  options,
797
1423
  );
798
- const closestAbove = getClosest<S>(
1424
+ const closestAbove = getClosest<S, R>(
799
1425
  "above",
800
1426
  peers,
801
1427
  point,
@@ -803,43 +1429,59 @@ const getClosestAround = <
803
1429
  true,
804
1430
  now,
805
1431
  includeStrictAbove,
1432
+ numbers,
806
1433
  options,
807
1434
  );
808
- const containing = containingPoint<S>(
1435
+ /* const containing = iterateRangesContainingPoint<S, R>(
809
1436
  peers,
810
1437
  point,
811
- roleAge,
812
- true,
813
- now,
814
- options,
1438
+ {
1439
+ time: {
1440
+ roleAgeLimit: roleAge,
1441
+ matured: true,
1442
+ now,
1443
+ }
1444
+ }
815
1445
  );
816
1446
 
817
1447
  return iteratorInSeries(
818
1448
  containing,
819
- joinIterator<S>([closestBelow, closestAbove], point, "closest"),
1449
+ joinIterator<S, R>([closestBelow, closestAbove], point, "closest", numbers),
1450
+ ); */
1451
+ return joinIterator<S, R>(
1452
+ [closestBelow, closestAbove],
1453
+ point,
1454
+ "closest",
1455
+ numbers,
820
1456
  );
821
1457
  };
822
1458
 
823
- const collectNodesAroundPoint = async (
1459
+ export const isMatured = (
1460
+ segment: { timestamp: bigint },
1461
+ now: number,
1462
+ minAge: number,
1463
+ ) => {
1464
+ return now - Number(segment.timestamp) >= minAge;
1465
+ };
1466
+ /*
1467
+
1468
+ const collectNodesAroundPoint = async <R extends "u32" | "u64">(
824
1469
  roleAge: number,
825
- peers: Index<ReplicationRangeIndexable>,
1470
+ peers: Index<ReplicationRangeIndexable<R>>,
826
1471
  collector: (
827
1472
  rect: { hash: string },
828
1473
  matured: boolean,
829
- interescting: boolean,
1474
+ intersecting: boolean,
830
1475
  ) => void,
831
- point: u32,
1476
+ point: NumberFromType<R>,
832
1477
  now: number,
1478
+ numbers: Numbers<R>,
833
1479
  done: () => boolean = () => true,
834
1480
  ) => {
835
- /* let shape = { timestamp: true, hash: true } as const */
836
- const containing = containingPoint(
837
- peers,
838
- point,
839
- 0,
840
- true,
841
- now /* , { shape } */,
842
- );
1481
+ const containing = iterateRangesContainingPoint<
1482
+ { timestamp: true, hash: true },
1483
+ R
1484
+ >(peers, point, 0, true, now, { shape: { timestamp: true, hash: true } as const });
843
1485
  const allContaining = await containing.all();
844
1486
  for (const rect of allContaining) {
845
1487
  collector(rect.value, isMatured(rect.value, now, roleAge), true);
@@ -849,28 +1491,31 @@ const collectNodesAroundPoint = async (
849
1491
  return;
850
1492
  }
851
1493
 
852
- const closestBelow = getClosest(
1494
+ const closestBelow = getClosest<undefined, R>(
853
1495
  "below",
854
1496
  peers,
855
1497
  point,
856
1498
  0,
857
1499
  true,
858
1500
  now,
859
- false /* , { shape } */,
1501
+ false,
1502
+ numbers
860
1503
  );
861
- const closestAbove = getClosest(
1504
+ const closestAbove = getClosest<undefined, R>(
862
1505
  "above",
863
1506
  peers,
864
1507
  point,
865
1508
  0,
866
1509
  true,
867
1510
  now,
868
- false /* , { shape } */,
1511
+ false,
1512
+ numbers
869
1513
  );
870
- const aroundIterator = joinIterator(
1514
+ const aroundIterator = joinIterator<undefined, R>(
871
1515
  [closestBelow, closestAbove],
872
1516
  point,
873
1517
  "closest",
1518
+ numbers,
874
1519
  );
875
1520
  while (aroundIterator.done() !== true && done() !== true) {
876
1521
  const res = await aroundIterator.next(1);
@@ -882,30 +1527,75 @@ const collectNodesAroundPoint = async (
882
1527
  }
883
1528
  }
884
1529
  };
1530
+ */
885
1531
 
886
- export const getEvenlySpacedU32 = (from: number, count: number) => {
887
- let ret: number[] = new Array(count);
888
- for (let i = 0; i < count; i++) {
889
- ret[i] = Math.round(from + (i * MAX_U32) / count) % MAX_U32;
890
- }
891
- return ret;
892
- };
893
-
894
- export const isMatured = (
895
- segment: { timestamp: bigint },
1532
+ const collectClosestAround = async <R extends "u32" | "u64">(
1533
+ roleAge: number,
1534
+ peers: Index<ReplicationRangeIndexable<R>>,
1535
+ collector: (rect: { hash: string }, matured: boolean) => void,
1536
+ point: NumberFromType<R>,
896
1537
  now: number,
897
- minAge: number,
1538
+ numbers: Numbers<R>,
1539
+ done: () => boolean = () => true,
898
1540
  ) => {
899
- return now - Number(segment.timestamp) >= minAge;
1541
+ const closestBelow = getClosest<undefined, R>(
1542
+ "below",
1543
+ peers,
1544
+ point,
1545
+ 0,
1546
+ true,
1547
+ now,
1548
+ false,
1549
+ numbers,
1550
+ );
1551
+ const closestAbove = getClosest<undefined, R>(
1552
+ "above",
1553
+ peers,
1554
+ point,
1555
+ 0,
1556
+ true,
1557
+ now,
1558
+ false,
1559
+ numbers,
1560
+ );
1561
+ /* const containingIterator = iterateRangesContainingPoint<undefined, R>(
1562
+ peers,
1563
+ point,
1564
+ );
1565
+ */
1566
+ const aroundIterator = joinIterator<undefined, R>(
1567
+ [/* containingIterator, */ closestBelow, closestAbove],
1568
+ point,
1569
+ "closest",
1570
+ numbers,
1571
+ );
1572
+
1573
+ let visited = new Set<string>();
1574
+ while (aroundIterator.done() !== true && done() !== true) {
1575
+ const res = await aroundIterator.next(100);
1576
+ for (const rect of res) {
1577
+ visited.add(rect.value.idString);
1578
+ collector(rect.value, isMatured(rect.value, now, roleAge));
1579
+ if (done()) {
1580
+ return;
1581
+ }
1582
+ }
1583
+ }
900
1584
  };
1585
+
901
1586
  // get peer sample that are responsible for the cursor point
902
1587
  // will return a list of peers that want to replicate the data,
903
1588
  // but also if necessary a list of peers that are responsible for the data
904
1589
  // but have not explicitly replicating a range that cover the cursor point
905
- export const getSamples = async (
906
- cursor: u32[],
907
- peers: Index<ReplicationRangeIndexable>,
1590
+ export const getSamples = async <R extends "u32" | "u64">(
1591
+ cursor: NumberFromType<R>[],
1592
+ peers: Index<ReplicationRangeIndexable<R>>,
908
1593
  roleAge: number,
1594
+ numbers: Numbers<R>,
1595
+ options?: {
1596
+ onlyIntersecting?: boolean;
1597
+ uniqueReplicators?: Set<string>;
1598
+ },
909
1599
  ): Promise<Map<string, { intersecting: boolean }>> => {
910
1600
  const leaders: Map<string, { intersecting: boolean }> = new Map();
911
1601
  if (!peers) {
@@ -913,50 +1603,87 @@ export const getSamples = async (
913
1603
  }
914
1604
 
915
1605
  const now = +new Date();
1606
+ let matured = 0;
916
1607
 
917
- const maturedLeaders = new Set();
1608
+ /* let missingForCursors: NumberFromType<R>[] = [] */
1609
+ let uniqueVisited = new Set<string>();
918
1610
  for (let i = 0; i < cursor.length; i++) {
919
- // evenly distributed
1611
+ let point = cursor[i];
920
1612
 
921
- // aquire at least one unique node for each point
922
- await collectNodesAroundPoint(
923
- roleAge,
1613
+ const allContaining = await allRangesContainingPoint<undefined, R>(
924
1614
  peers,
925
- (rect, m, intersecting) => {
926
- if (m) {
927
- maturedLeaders.add(rect.hash);
1615
+ point,
1616
+ );
1617
+
1618
+ for (const rect of allContaining) {
1619
+ uniqueVisited.add(rect.value.hash);
1620
+ let prev = leaders.get(rect.value.hash);
1621
+ if (!prev) {
1622
+ if (isMatured(rect.value, now, roleAge)) {
1623
+ matured++;
928
1624
  }
1625
+ leaders.set(rect.value.hash, { intersecting: true });
1626
+ } else {
1627
+ prev.intersecting = true;
1628
+ }
1629
+ }
929
1630
 
930
- const prev = leaders.get(rect.hash);
1631
+ if (options?.uniqueReplicators) {
1632
+ if (
1633
+ options.uniqueReplicators.size === leaders.size ||
1634
+ options.uniqueReplicators.size === uniqueVisited.size
1635
+ ) {
1636
+ break; // nothing more to find
1637
+ }
1638
+ }
931
1639
 
932
- if (!prev || (intersecting && !prev.intersecting)) {
933
- leaders.set(rect.hash, { intersecting });
1640
+ if (options?.onlyIntersecting || matured > i) {
1641
+ continue;
1642
+ }
1643
+
1644
+ let foundOneUniqueMatured = false;
1645
+ await collectClosestAround(
1646
+ roleAge,
1647
+ peers,
1648
+ (rect, m) => {
1649
+ uniqueVisited.add(rect.hash);
1650
+ const prev = leaders.get(rect.hash);
1651
+ if (m) {
1652
+ if (!prev) {
1653
+ matured++;
1654
+ leaders.set(rect.hash, { intersecting: false });
1655
+ }
1656
+ if (matured > i) {
1657
+ foundOneUniqueMatured = true;
1658
+ }
934
1659
  }
935
1660
  },
936
- cursor[i],
1661
+ point,
937
1662
  now,
938
- () => {
939
- if (maturedLeaders.size > i) {
940
- return true;
941
- }
942
- return false;
943
- },
1663
+ numbers,
1664
+ () => foundOneUniqueMatured,
944
1665
  );
1666
+ /* if (!foundOneUniqueMatured) {
1667
+ missingForCursors.push(point);
1668
+ } */
945
1669
  }
946
-
1670
+ /* if (leaders.size < cursor.length) {
1671
+ throw new Error("Missing leaders got: " + leaders.size + " -- expected -- " + cursor.length + " role age " + roleAge + " missing " + missingForCursors.length + " replication index size: " + (await peers.count()));
1672
+ } */
947
1673
  return leaders;
948
1674
  };
949
1675
 
950
- const fetchOne = async <S extends Shape | undefined>(
951
- iterator: IndexIterator<ReplicationRangeIndexable, S>,
1676
+ const fetchOne = async <S extends Shape | undefined, R extends "u32" | "u64">(
1677
+ iterator: IndexIterator<ReplicationRangeIndexable<R>, S>,
952
1678
  ) => {
953
1679
  const value = await iterator.next(1);
954
1680
  await iterator.close();
955
1681
  return value[0]?.value;
956
1682
  };
957
1683
 
958
- export const minimumWidthToCover = async (
1684
+ export const minimumWidthToCover = async <R extends "u32" | "u64">(
959
1685
  minReplicas: number /* , replicatorCount: number */,
1686
+ numbers: Numbers<R>,
960
1687
  ) => {
961
1688
  /* minReplicas = Math.min(minReplicas, replicatorCount); */ // TODO do we need this?
962
1689
 
@@ -965,34 +1692,29 @@ export const minimumWidthToCover = async (
965
1692
  // to make sure we reach sufficient amount of nodes such that at least one one has
966
1693
  // the entry we are looking for
967
1694
 
968
- let widthToCoverScaled = Math.round(MAX_U32 / minReplicas);
1695
+ let widthToCoverScaled = numbers.divRound(numbers.maxValue, minReplicas);
969
1696
  return widthToCoverScaled;
970
1697
  };
971
1698
 
972
- export const getCoverSet = async (properties: {
973
- peers: Index<ReplicationRangeIndexable>;
974
- start: number | PublicSignKey | undefined;
975
- widthToCoverScaled: number;
1699
+ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
1700
+ peers: Index<ReplicationRangeIndexable<R>>;
1701
+ start: NumberFromType<R> | PublicSignKey | undefined;
1702
+ widthToCoverScaled: NumberFromType<R>;
976
1703
  roleAge: number;
977
- intervalWidth?: number;
1704
+ numbers: Numbers<R>;
978
1705
  eager?:
979
1706
  | {
980
1707
  unmaturedFetchCoverSize?: number;
981
1708
  }
982
1709
  | boolean;
983
1710
  }): Promise<Set<string>> => {
984
- let intervalWidth: number = properties.intervalWidth ?? MAX_U32;
985
1711
  const { peers, start, widthToCoverScaled, roleAge } = properties;
986
1712
 
987
1713
  const now = Date.now();
988
- const { startNode, startLocation, endLocation } = await getStartAndEnd(
989
- peers,
990
- start,
991
- widthToCoverScaled,
992
- roleAge,
993
- now,
994
- intervalWidth,
995
- );
1714
+ const { startNode, startLocation, endLocation } = await getStartAndEnd<
1715
+ undefined,
1716
+ R
1717
+ >(peers, start, widthToCoverScaled, roleAge, now, properties.numbers);
996
1718
 
997
1719
  let ret = new Set<string>();
998
1720
 
@@ -1034,29 +1756,46 @@ export const getCoverSet = async (properties: {
1034
1756
  ret.add(current.hash);
1035
1757
 
1036
1758
  const resolveNextContaining = async (
1037
- nextLocation: number,
1759
+ nextLocation: NumberFromType<R>,
1038
1760
  roleAge: number,
1039
1761
  ) => {
1040
1762
  let next = await fetchOne(
1041
- containingPoint(peers, nextLocation, roleAge, true, now, {
1763
+ iterateRangesContainingPoint<undefined, R>(peers, nextLocation, {
1042
1764
  sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
1765
+ time: {
1766
+ matured: true,
1767
+ roleAgeLimit: roleAge,
1768
+ now,
1769
+ },
1043
1770
  }),
1044
1771
  ); // get entersecting sort by largest end2
1045
1772
  return next;
1046
1773
  };
1047
1774
 
1048
- const resolveNextAbove = async (nextLocation: number, roleAge: number) => {
1775
+ const resolveNextAbove = async (
1776
+ nextLocation: NumberFromType<R>,
1777
+ roleAge: number,
1778
+ ) => {
1049
1779
  // if not get closest from above
1050
- let next = await fetchOne(
1051
- getClosest("above", peers, nextLocation, roleAge, true, now, true),
1780
+ let next = await fetchOne<undefined, R>(
1781
+ getClosest(
1782
+ "above",
1783
+ peers,
1784
+ nextLocation,
1785
+ roleAge,
1786
+ true,
1787
+ now,
1788
+ true,
1789
+ properties.numbers,
1790
+ ),
1052
1791
  );
1053
1792
  return next;
1054
1793
  };
1055
1794
 
1056
1795
  const resolveNext = async (
1057
- nextLocation: number,
1796
+ nextLocation: NumberFromType<R>,
1058
1797
  roleAge: number,
1059
- ): Promise<[ReplicationRangeIndexable, boolean]> => {
1798
+ ): Promise<[ReplicationRangeIndexable<R>, boolean]> => {
1060
1799
  const containing = await resolveNextContaining(nextLocation, roleAge);
1061
1800
  if (containing) {
1062
1801
  return [containing, true];
@@ -1067,13 +1806,16 @@ export const getCoverSet = async (properties: {
1067
1806
  // fill the middle
1068
1807
  let wrappedOnce = current.end2 < current.end1;
1069
1808
 
1070
- let coveredLength = 0;
1071
- const addLength = (from: number) => {
1809
+ let coveredLength = properties.numbers.zero;
1810
+ const addLength = (from: NumberFromType<R>) => {
1072
1811
  if (current.end2 < from || current.wrapped) {
1073
1812
  wrappedOnce = true;
1074
- coveredLength += MAX_U32 - from;
1813
+ // @ts-ignore
1814
+ coveredLength += properties.numbers.maxValue - from;
1815
+ // @ts-ignore
1075
1816
  coveredLength += current.end2;
1076
1817
  } else {
1818
+ // @ts-ignore
1077
1819
  coveredLength += current.end1 - from;
1078
1820
  }
1079
1821
  };
@@ -1085,7 +1827,7 @@ export const getCoverSet = async (properties: {
1085
1827
 
1086
1828
  while (
1087
1829
  maturedCoveredLength < widthToCoverScaled && // eslint-disable-line no-unmodified-loop-condition
1088
- coveredLength <= MAX_U32 // eslint-disable-line no-unmodified-loop-condition
1830
+ coveredLength <= properties.numbers.maxValue // eslint-disable-line no-unmodified-loop-condition
1089
1831
  ) {
1090
1832
  let nextCandidate = await resolveNext(nextLocation, roleAge);
1091
1833
  /* let fromAbove = false; */
@@ -1119,13 +1861,33 @@ export const getCoverSet = async (properties: {
1119
1861
  if (
1120
1862
  !isLast ||
1121
1863
  nextCandidate[1] ||
1122
- Math.min(
1123
- getDistance(last.start1, endLocation, "closest"),
1124
- getDistance(last.end2, endLocation, "closest"),
1864
+ properties.numbers.min(
1865
+ getDistance(
1866
+ last.start1,
1867
+ endLocation,
1868
+ "closest",
1869
+ properties.numbers.maxValue,
1870
+ ),
1871
+ getDistance(
1872
+ last.end2,
1873
+ endLocation,
1874
+ "closest",
1875
+ properties.numbers.maxValue,
1876
+ ),
1125
1877
  ) >
1126
- Math.min(
1127
- getDistance(current.start1, endLocation, "closest"),
1128
- getDistance(current.end2, endLocation, "closest"),
1878
+ properties.numbers.min(
1879
+ getDistance(
1880
+ current.start1,
1881
+ endLocation,
1882
+ "closest",
1883
+ properties.numbers.maxValue,
1884
+ ),
1885
+ getDistance(
1886
+ current.end2,
1887
+ endLocation,
1888
+ "closest",
1889
+ properties.numbers.maxValue,
1890
+ ),
1129
1891
  )
1130
1892
  ) {
1131
1893
  ret.add(current.hash);
@@ -1141,9 +1903,9 @@ export const getCoverSet = async (properties: {
1141
1903
 
1142
1904
  nextLocation = endIsWrapped
1143
1905
  ? wrappedOnce
1144
- ? Math.min(current.end2, endLocation)
1906
+ ? properties.numbers.min(current.end2, endLocation)
1145
1907
  : current.end2
1146
- : Math.min(current.end2, endLocation);
1908
+ : properties.numbers.min(current.end2, endLocation);
1147
1909
  }
1148
1910
 
1149
1911
  start instanceof PublicSignKey && ret.add(start.hashcode());
@@ -1153,63 +1915,69 @@ export const getCoverSet = async (properties: {
1153
1915
  // reduce the change set to only regions that are changed for each peer
1154
1916
  // i.e. subtract removed regions from added regions, and vice versa
1155
1917
  const result = new Map<string, { range: ReplicationRangeIndexable, added: boolean }[]>();
1156
-
1918
+
1157
1919
  for (const addedChange of changes.added ?? []) {
1158
1920
  let prev = result.get(addedChange.hash) ?? [];
1159
1921
  for (const [_hash, ranges] of result.entries()) {
1160
1922
  for (const r of ranges) {
1161
-
1923
+
1162
1924
  }
1163
1925
  }
1164
1926
  }
1165
1927
  }
1166
1928
  */
1167
1929
 
1168
- const matchRangeQuery = (range: ReplicationRangeIndexable) => {
1169
- let ors = [];
1170
- ors.push(
1171
- new And([
1172
- new IntegerCompare({
1173
- key: "coordinate",
1174
- compare: "gte",
1175
- value: range.start1,
1176
- }),
1177
- new IntegerCompare({
1178
- key: "coordinate",
1179
- compare: "lt",
1180
- value: range.end1,
1181
- }),
1182
- ]),
1183
- );
1930
+ export const matchEntriesInRangeQuery = (range: {
1931
+ start1: number | bigint;
1932
+ end1: number | bigint;
1933
+ start2: number | bigint;
1934
+ end2: number | bigint;
1935
+ }) => {
1936
+ const c1 = new And([
1937
+ new IntegerCompare({
1938
+ key: "coordinates",
1939
+ compare: "gte",
1940
+ value: range.start1,
1941
+ }),
1942
+ new IntegerCompare({
1943
+ key: "coordinates",
1944
+ compare: "lt",
1945
+ value: range.end1,
1946
+ }),
1947
+ ]);
1948
+
1949
+ if (range.start2 === range.end2) {
1950
+ return c1;
1951
+ }
1184
1952
 
1185
- ors.push(
1953
+ let ors = [
1954
+ c1,
1186
1955
  new And([
1187
1956
  new IntegerCompare({
1188
- key: "coordinate",
1957
+ key: "coordinates",
1189
1958
  compare: "gte",
1190
1959
  value: range.start2,
1191
1960
  }),
1192
1961
  new IntegerCompare({
1193
- key: "coordinate",
1962
+ key: "coordinates",
1194
1963
  compare: "lt",
1195
1964
  value: range.end2,
1196
1965
  }),
1197
1966
  ]),
1198
- );
1199
-
1967
+ ];
1200
1968
  return new Or(ors);
1201
1969
  };
1202
- export const toRebalance = (
1970
+ export const toRebalance = <R extends "u32" | "u64">(
1203
1971
  changes: ReplicationChanges,
1204
- index: Index<EntryReplicated>,
1205
- ): AsyncIterable<{ gid: string; entries: EntryReplicated[] }> => {
1972
+ index: Index<EntryReplicated<R>>,
1973
+ ): AsyncIterable<EntryReplicated<R>> => {
1206
1974
  const assignedRangesQuery = (changes: ReplicationChanges) => {
1207
1975
  let ors: Query[] = [];
1208
1976
  for (const change of changes) {
1209
- const matchRange = matchRangeQuery(change.range);
1977
+ const matchRange = matchEntriesInRangeQuery(change.range);
1210
1978
  if (change.type === "updated") {
1211
1979
  // assuming a range is to be removed, is this entry still enoughly replicated
1212
- const prevMatchRange = matchRangeQuery(change.prev);
1980
+ const prevMatchRange = matchEntriesInRangeQuery(change.prev);
1213
1981
  ors.push(prevMatchRange);
1214
1982
  ors.push(matchRange);
1215
1983
  } else {
@@ -1235,13 +2003,15 @@ export const toRebalance = (
1235
2003
  });
1236
2004
 
1237
2005
  while (iterator.done() !== true) {
1238
- const entries = await iterator.next(1000); // TODO choose right batch sizes here for optimal memory usage / speed
1239
-
1240
- // TODO do we need this
1241
- const grouped = await groupByGidSync(entries.map((x) => x.value));
2006
+ const entries = await iterator.all(); // TODO choose right batch sizes here for optimal memory usage / speed
1242
2007
 
2008
+ /* const grouped = await groupByGidSync(entries.map((x) => x.value));
1243
2009
  for (const [gid, entries] of grouped.entries()) {
1244
2010
  yield { gid, entries };
2011
+ } */
2012
+
2013
+ for (const entry of entries) {
2014
+ yield entry.value;
1245
2015
  }
1246
2016
  }
1247
2017
  },
@@ -1249,12 +2019,14 @@ export const toRebalance = (
1249
2019
  };
1250
2020
 
1251
2021
  export const fetchOneFromPublicKey = async <
1252
- S extends (Shape & { timestamp: true }) | undefined = undefined,
2022
+ S extends (Shape & { timestamp: true }) | undefined,
2023
+ R extends "u32" | "u64",
1253
2024
  >(
1254
2025
  publicKey: PublicSignKey,
1255
- index: Index<ReplicationRangeIndexable>,
2026
+ index: Index<ReplicationRangeIndexable<R>>,
1256
2027
  roleAge: number,
1257
2028
  now: number,
2029
+ numbers: Numbers<R>,
1258
2030
  options?: {
1259
2031
  shape: S;
1260
2032
  },
@@ -1271,13 +2043,14 @@ export const fetchOneFromPublicKey = async <
1271
2043
  if (node) {
1272
2044
  if (!isMatured(node, now, roleAge)) {
1273
2045
  const matured = await fetchOne(
1274
- getClosestAround<S>(
2046
+ getClosestAround<S, R>(
1275
2047
  index,
1276
2048
  node.start1,
1277
2049
  roleAge,
1278
2050
  now,
1279
2051
  false,
1280
2052
  false,
2053
+ numbers,
1281
2054
  options,
1282
2055
  ),
1283
2056
  );
@@ -1291,33 +2064,36 @@ export const fetchOneFromPublicKey = async <
1291
2064
 
1292
2065
  export const getStartAndEnd = async <
1293
2066
  S extends (Shape & { timestamp: true }) | undefined,
2067
+ R extends "u32" | "u64",
1294
2068
  >(
1295
- peers: Index<ReplicationRangeIndexable>,
1296
- start: number | PublicSignKey | undefined | undefined,
1297
- widthToCoverScaled: number,
2069
+ peers: Index<ReplicationRangeIndexable<R>>,
2070
+ start: NumberFromType<R> | PublicSignKey | undefined | undefined,
2071
+ widthToCoverScaled: NumberFromType<R>,
1298
2072
  roleAge: number,
1299
2073
  now: number,
1300
- intervalWidth: number,
2074
+ numbers: Numbers<R>,
1301
2075
  options?: { shape: S },
1302
2076
  ): Promise<{
1303
- startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined;
1304
- startLocation: number;
1305
- endLocation: number;
2077
+ startNode: ReturnTypeFromShape<ReplicationRangeIndexable<R>, S> | undefined;
2078
+ startLocation: NumberFromType<R>;
2079
+ endLocation: NumberFromType<R>;
1306
2080
  }> => {
1307
2081
  // find a good starting point
1308
- let startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined =
1309
- undefined;
1310
- let startLocation: number | undefined = undefined;
2082
+ let startNode:
2083
+ | ReturnTypeFromShape<ReplicationRangeIndexable<R>, S>
2084
+ | undefined = undefined;
2085
+ let startLocation: NumberFromType<R> | undefined = undefined;
1311
2086
 
1312
- const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
2087
+ const nodeFromPoint = async (point = numbers.random()) => {
1313
2088
  startLocation = point;
1314
- startNode = await fetchOneClosest(
2089
+ startNode = await fetchOneClosest<S, R>(
1315
2090
  peers,
1316
2091
  startLocation,
1317
2092
  roleAge,
1318
2093
  now,
1319
2094
  false,
1320
2095
  true,
2096
+ numbers,
1321
2097
  options,
1322
2098
  );
1323
2099
  };
@@ -1329,6 +2105,7 @@ export const getStartAndEnd = async <
1329
2105
  peers,
1330
2106
  roleAge,
1331
2107
  now,
2108
+ numbers,
1332
2109
  options,
1333
2110
  );
1334
2111
  if (!startNode) {
@@ -1337,62 +2114,76 @@ export const getStartAndEnd = async <
1337
2114
  } else {
1338
2115
  startLocation = startNode.start1;
1339
2116
  }
1340
- } else if (typeof start === "number") {
2117
+ } else if (typeof start === "number" || typeof start === "bigint") {
1341
2118
  await nodeFromPoint(start);
1342
2119
  } else {
1343
2120
  await nodeFromPoint();
1344
2121
  }
1345
2122
 
1346
2123
  if (!startNode || startLocation == null) {
1347
- return { startNode: undefined, startLocation: 0, endLocation: 0 };
2124
+ return {
2125
+ startNode: undefined,
2126
+ startLocation: numbers.zero,
2127
+ endLocation: numbers.zero,
2128
+ };
1348
2129
  }
1349
2130
 
1350
- let endLocation = startLocation + widthToCoverScaled;
1351
- if (intervalWidth != null) {
1352
- endLocation = endLocation % intervalWidth;
1353
- }
2131
+ // @ts-ignore
2132
+ let endLocation: T = (startLocation + widthToCoverScaled) % numbers.maxValue;
1354
2133
 
1355
- // if start location is after endLocation and startNode is strict then return undefined because this is not a node we want to choose
1356
- let coveredDistanceToStart = 0;
1357
- if (startNode.start1 < startLocation) {
1358
- coveredDistanceToStart += intervalWidth - startLocation + startNode.start1;
1359
- } else {
1360
- coveredDistanceToStart += startNode.start1 - startLocation;
1361
- }
2134
+ // if the start node range is not containing the start point, then figure out if the startNode is ideal
2135
+ if (!startNode.contains(startLocation)) {
2136
+ let coveredDistanceToStart = numbers.zero;
2137
+ if (startNode.start1 < startLocation) {
2138
+ coveredDistanceToStart +=
2139
+ numbers.maxValue - startLocation + startNode.start1;
2140
+ } else {
2141
+ coveredDistanceToStart += ((startNode.start1 as any) -
2142
+ startLocation) as any;
2143
+ }
1362
2144
 
1363
- if (
1364
- startNode.mode === ReplicationIntent.Strict &&
1365
- coveredDistanceToStart > widthToCoverScaled
1366
- ) {
1367
- return { startNode: undefined, startLocation: 0, endLocation: 0 };
2145
+ // in this case, the gap to the start point is larger than the width we want to cover. Assume there are no good points
2146
+ if (
2147
+ startNode.mode === ReplicationIntent.Strict &&
2148
+ coveredDistanceToStart > widthToCoverScaled
2149
+ ) {
2150
+ return {
2151
+ startNode: undefined,
2152
+ startLocation: numbers.zero,
2153
+ endLocation: numbers.zero,
2154
+ };
2155
+ }
1368
2156
  }
1369
2157
 
1370
2158
  return {
1371
2159
  startNode,
1372
- startLocation: Math.round(startLocation),
1373
- endLocation: Math.round(endLocation),
2160
+ startLocation,
2161
+ endLocation,
1374
2162
  };
1375
2163
  };
1376
2164
 
1377
2165
  export const fetchOneClosest = <
1378
- S extends (Shape & { timestamp: true }) | undefined = undefined,
2166
+ S extends (Shape & { timestamp: true }) | undefined,
2167
+ R extends "u32" | "u64",
1379
2168
  >(
1380
- peers: Index<ReplicationRangeIndexable>,
1381
- point: number,
2169
+ peers: Index<ReplicationRangeIndexable<R>>,
2170
+ point: NumberFromType<R>,
1382
2171
  roleAge: number,
1383
2172
  now: number,
1384
2173
  includeStrictBelow: boolean,
1385
2174
  includeStrictAbove: boolean,
2175
+ numbers: Numbers<R>,
1386
2176
  options?: { shape?: S },
1387
2177
  ) => {
1388
- return fetchOne(
1389
- getClosestAround<S>(
2178
+ return fetchOne<S, R>(
2179
+ getClosestAround<S, R>(
1390
2180
  peers,
1391
2181
  point,
1392
2182
  roleAge,
1393
2183
  now,
1394
2184
  includeStrictBelow,
1395
2185
  includeStrictAbove,
2186
+ numbers,
1396
2187
  options,
1397
2188
  ),
1398
2189
  );