@peerbit/shared-log 10.0.6 → 10.1.0-7d319f1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/get-samples.js +1 -1
- package/dist/benchmark/get-samples.js.map +1 -1
- package/dist/benchmark/utils.js +1 -1
- package/dist/benchmark/utils.js.map +1 -1
- package/dist/src/index.d.ts +16 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +146 -65
- package/dist/src/index.js.map +1 -1
- package/dist/src/ranges.d.ts +95 -11
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +440 -163
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +2 -2
- package/dist/src/replication-domain-hash.d.ts.map +1 -1
- package/dist/src/replication-domain-hash.js +2 -17
- package/dist/src/replication-domain-hash.js.map +1 -1
- package/dist/src/replication-domain-time.d.ts +7 -2
- package/dist/src/replication-domain-time.d.ts.map +1 -1
- package/dist/src/replication-domain-time.js +7 -12
- package/dist/src/replication-domain-time.js.map +1 -1
- package/dist/src/replication-domain.d.ts +3 -20
- package/dist/src/replication-domain.d.ts.map +1 -1
- package/dist/src/replication-domain.js +0 -33
- package/dist/src/replication-domain.js.map +1 -1
- package/package.json +70 -70
- package/src/index.ts +215 -108
- package/src/ranges.ts +652 -202
- package/src/replication-domain-hash.ts +16 -29
- package/src/replication-domain-time.ts +46 -40
- package/src/replication-domain.ts +7 -59
package/src/ranges.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deserialize, field, serialize, variant, vec } from "@dao-xyz/borsh";
|
|
2
|
+
import type { Cache } from "@peerbit/cache";
|
|
2
3
|
import {
|
|
3
4
|
PublicSignKey,
|
|
4
5
|
equals,
|
|
@@ -24,17 +25,18 @@ import {
|
|
|
24
25
|
Sort,
|
|
25
26
|
SortDirection,
|
|
26
27
|
StringMatch,
|
|
28
|
+
iteratorInSeries,
|
|
27
29
|
/* iteratorInSeries, */
|
|
28
30
|
} from "@peerbit/indexer-interface";
|
|
29
31
|
import { id } from "@peerbit/indexer-interface";
|
|
30
32
|
import { Meta, ShallowMeta } from "@peerbit/log";
|
|
33
|
+
import { debounceAccumulator } from "./debounce.js";
|
|
31
34
|
import {
|
|
32
35
|
MAX_U32,
|
|
33
36
|
MAX_U64,
|
|
34
37
|
type NumberFromType,
|
|
35
38
|
type Numbers,
|
|
36
39
|
} from "./integers.js";
|
|
37
|
-
import { type ReplicationChanges } from "./replication-domain.js";
|
|
38
40
|
|
|
39
41
|
export enum ReplicationIntent {
|
|
40
42
|
NonStrict = 0, // indicates that the segment will be replicated and nearby data might be replicated as well
|
|
@@ -268,7 +270,7 @@ export class ReplicationRangeMessageU32 extends ReplicationRangeMessage<"u32"> {
|
|
|
268
270
|
id: this.id,
|
|
269
271
|
publicKeyHash: key.hashcode(),
|
|
270
272
|
offset: this.offset,
|
|
271
|
-
|
|
273
|
+
width: this.factor,
|
|
272
274
|
timestamp: this.timestamp,
|
|
273
275
|
mode: this.mode,
|
|
274
276
|
});
|
|
@@ -323,7 +325,7 @@ export class ReplicationRangeMessageU64 extends ReplicationRangeMessage<"u64"> {
|
|
|
323
325
|
id: this.id,
|
|
324
326
|
publicKeyHash: key.hashcode(),
|
|
325
327
|
offset: this.offset,
|
|
326
|
-
|
|
328
|
+
width: this.factor,
|
|
327
329
|
timestamp: this.timestamp,
|
|
328
330
|
mode: this.mode,
|
|
329
331
|
});
|
|
@@ -410,6 +412,246 @@ export interface ReplicationRangeIndexable<R extends "u32" | "u64"> {
|
|
|
410
412
|
contains(point: NumberFromType<R>): boolean;
|
|
411
413
|
equalRange(other: ReplicationRangeIndexable<R>): boolean;
|
|
412
414
|
overlaps(other: ReplicationRangeIndexable<R>): boolean;
|
|
415
|
+
toString(): string;
|
|
416
|
+
get rangeHash(): string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export type NumericType = "u32" | "u64";
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Convert a GeneralRange<N> into one or two `[bigint, bigint]` segments.
|
|
423
|
+
* - If it’s not wrapped, there’s one segment: [start1, end1).
|
|
424
|
+
* - If it’s wrapped, there’s two: [start1, end1) and [start2, end2).
|
|
425
|
+
*
|
|
426
|
+
* We always do the conversion to bigints internally.
|
|
427
|
+
*/
|
|
428
|
+
export function toSegmentsBigInt<N extends NumericType>(
|
|
429
|
+
range: ReplicationRangeIndexable<N>,
|
|
430
|
+
): Array<[bigint, bigint]> {
|
|
431
|
+
// Safely convert the numeric fields to bigint
|
|
432
|
+
const s1: bigint =
|
|
433
|
+
typeof range.start1 === "number" ? BigInt(range.start1) : range.start1;
|
|
434
|
+
const e1: bigint =
|
|
435
|
+
typeof range.end1 === "number" ? BigInt(range.end1) : range.end1;
|
|
436
|
+
const s2: bigint =
|
|
437
|
+
typeof range.start2 === "number" ? BigInt(range.start2) : range.start2;
|
|
438
|
+
const e2: bigint =
|
|
439
|
+
typeof range.end2 === "number" ? BigInt(range.end2) : range.end2;
|
|
440
|
+
|
|
441
|
+
const segments: Array<[bigint, bigint]> = [];
|
|
442
|
+
|
|
443
|
+
segments.push([s1, e1]);
|
|
444
|
+
|
|
445
|
+
if (s2 !== s1 && s2 !== e2) {
|
|
446
|
+
segments.push([s2, e2]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return segments;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Build an array of new GeneralRange<N> objects from leftover `[bigint, bigint]` segments.
|
|
454
|
+
* We split them in pairs, each range can hold up to two segments:
|
|
455
|
+
*
|
|
456
|
+
* - [seg1Start, seg1End)
|
|
457
|
+
* - [seg2Start, seg2End) (if available)
|
|
458
|
+
*
|
|
459
|
+
* We convert bigints back to the correct numeric type, if needed.
|
|
460
|
+
*/
|
|
461
|
+
function buildRangesFromBigIntSegments<N extends NumericType>(
|
|
462
|
+
segments: Array<[bigint, bigint]>,
|
|
463
|
+
templateRange: ReplicationRangeIndexable<N>,
|
|
464
|
+
): Array<ReplicationRangeIndexable<N>> {
|
|
465
|
+
// Sort by start
|
|
466
|
+
segments.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
467
|
+
|
|
468
|
+
const result: Array<ReplicationRangeIndexable<N>> = [];
|
|
469
|
+
let i = 0;
|
|
470
|
+
const proto = Object.getPrototypeOf(templateRange);
|
|
471
|
+
|
|
472
|
+
while (i < segments.length) {
|
|
473
|
+
const seg1 = segments[i];
|
|
474
|
+
i++;
|
|
475
|
+
let seg2: [bigint, bigint] | null = null;
|
|
476
|
+
if (i < segments.length) {
|
|
477
|
+
seg2 = segments[i];
|
|
478
|
+
i++;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Convert back to the original numeric type
|
|
482
|
+
const [s1, e1] = toOriginalType<N>(seg1, templateRange);
|
|
483
|
+
const [s2, e2] = seg2
|
|
484
|
+
? toOriginalType<N>(seg2, templateRange)
|
|
485
|
+
: ([s1, e1] as [NumberFromType<N>, NumberFromType<N>]);
|
|
486
|
+
|
|
487
|
+
// Build a new range object. You can clone or replicate metadata as needed.
|
|
488
|
+
const newRange = Object.assign(Object.create(proto), {
|
|
489
|
+
...templateRange,
|
|
490
|
+
start1: s1,
|
|
491
|
+
end1: e1,
|
|
492
|
+
start2: s2,
|
|
493
|
+
end2: e2,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
result.push(newRange);
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Subtract one bigint segment [bStart, bEnd) from [aStart, aEnd).
|
|
503
|
+
* Returns 0..2 leftover segments in bigint form.
|
|
504
|
+
*/
|
|
505
|
+
|
|
506
|
+
function subtractBigIntSegment(
|
|
507
|
+
aStart: bigint,
|
|
508
|
+
aEnd: bigint,
|
|
509
|
+
bStart: bigint,
|
|
510
|
+
bEnd: bigint,
|
|
511
|
+
): Array<[bigint, bigint]> {
|
|
512
|
+
const result: Array<[bigint, bigint]> = [];
|
|
513
|
+
|
|
514
|
+
// No overlap
|
|
515
|
+
if (bEnd <= aStart || bStart >= aEnd) {
|
|
516
|
+
result.push([aStart, aEnd]);
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Fully contained
|
|
521
|
+
if (bStart <= aStart && bEnd >= aEnd) {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Partial overlaps
|
|
526
|
+
if (bStart > aStart) {
|
|
527
|
+
result.push([aStart, bStart]);
|
|
528
|
+
}
|
|
529
|
+
if (bEnd < aEnd) {
|
|
530
|
+
result.push([bEnd, aEnd]);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Helper: convert `[bigint, bigint]` to `[number, number]` if N is "u32",
|
|
538
|
+
* or keep as `[bigint, bigint]` if N is "u64".
|
|
539
|
+
*/
|
|
540
|
+
function toOriginalType<N extends NumericType>(
|
|
541
|
+
segment: [bigint, bigint],
|
|
542
|
+
templateRange: ReplicationRangeIndexable<N>,
|
|
543
|
+
): [NumberFromType<N>, NumberFromType<N>] {
|
|
544
|
+
const [start, end] = segment;
|
|
545
|
+
if (isU32Range(templateRange)) {
|
|
546
|
+
// Convert back to number
|
|
547
|
+
return [Number(start), Number(end)] as any;
|
|
548
|
+
} else {
|
|
549
|
+
// Keep as bigint
|
|
550
|
+
return [start, end] as any;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Merge any adjacent or overlapping `[bigint, bigint]` segments.
|
|
556
|
+
* E.g. [10,20) and [20,25) => [10,25)
|
|
557
|
+
*/
|
|
558
|
+
export function mergeBigIntSegments(
|
|
559
|
+
segments: Array<[bigint, bigint]>,
|
|
560
|
+
): Array<[bigint, bigint]> {
|
|
561
|
+
if (segments.length < 2) return segments;
|
|
562
|
+
|
|
563
|
+
// Sort by start
|
|
564
|
+
segments.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
565
|
+
|
|
566
|
+
const merged: Array<[bigint, bigint]> = [];
|
|
567
|
+
let current = segments[0];
|
|
568
|
+
|
|
569
|
+
for (let i = 1; i < segments.length; i++) {
|
|
570
|
+
const next = segments[i];
|
|
571
|
+
// If current overlaps or touches next
|
|
572
|
+
if (current[1] >= next[0]) {
|
|
573
|
+
// Merge
|
|
574
|
+
current = [current[0], current[1] > next[1] ? current[1] : next[1]];
|
|
575
|
+
} else {
|
|
576
|
+
merged.push(current);
|
|
577
|
+
current = next;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
merged.push(current);
|
|
581
|
+
|
|
582
|
+
return merged;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Figure out if a given range is "u32" or "u64".
|
|
587
|
+
* You might also store this in the object itself if you prefer.
|
|
588
|
+
*/
|
|
589
|
+
function isU32Range<N extends NumericType>(
|
|
590
|
+
range: ReplicationRangeIndexable<N>,
|
|
591
|
+
): boolean {
|
|
592
|
+
// If you store a separate `type: "u32" | "u64"` in the range, you can just check that:
|
|
593
|
+
// return range.type === "u32";
|
|
594
|
+
// or we do a hack by checking the type of start1, e.g.:
|
|
595
|
+
|
|
596
|
+
// If "start1" is a number (not a bigint), we treat it as u32
|
|
597
|
+
return typeof range.start1 === "number";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function symmetricDifferenceRanges<N extends NumericType>(
|
|
601
|
+
rangeA: ReplicationRangeIndexable<N>,
|
|
602
|
+
rangeB: ReplicationRangeIndexable<N>,
|
|
603
|
+
): {
|
|
604
|
+
rangesFromA: Array<ReplicationRangeIndexable<N>>;
|
|
605
|
+
rangesFromB: Array<ReplicationRangeIndexable<N>>;
|
|
606
|
+
} {
|
|
607
|
+
const segmentsA = toSegmentsBigInt(rangeA);
|
|
608
|
+
const segmentsB = toSegmentsBigInt(rangeB);
|
|
609
|
+
|
|
610
|
+
const resultSegmentsA: Array<[bigint, bigint]> = [];
|
|
611
|
+
const resultSegmentsB: Array<[bigint, bigint]> = [];
|
|
612
|
+
|
|
613
|
+
// Compute symmetric difference for A
|
|
614
|
+
for (const [aStart, aEnd] of segmentsA) {
|
|
615
|
+
let leftover = [[aStart, aEnd]] as Array<[bigint, bigint]>;
|
|
616
|
+
for (const [bStart, bEnd] of segmentsB) {
|
|
617
|
+
const newLeftover = [];
|
|
618
|
+
for (const [start, end] of leftover) {
|
|
619
|
+
newLeftover.push(...subtractBigIntSegment(start, end, bStart, bEnd));
|
|
620
|
+
}
|
|
621
|
+
leftover = newLeftover;
|
|
622
|
+
}
|
|
623
|
+
resultSegmentsA.push(...leftover);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Compute symmetric difference for B
|
|
627
|
+
for (const [bStart, bEnd] of segmentsB) {
|
|
628
|
+
let leftover = [[bStart, bEnd]] as Array<[bigint, bigint]>;
|
|
629
|
+
for (const [aStart, aEnd] of segmentsA) {
|
|
630
|
+
const newLeftover = [];
|
|
631
|
+
for (const [start, end] of leftover) {
|
|
632
|
+
newLeftover.push(...subtractBigIntSegment(start, end, aStart, aEnd));
|
|
633
|
+
}
|
|
634
|
+
leftover = newLeftover;
|
|
635
|
+
}
|
|
636
|
+
resultSegmentsB.push(...leftover);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Remove zero-length or invalid segments
|
|
640
|
+
const validSegmentsA = resultSegmentsA.filter(([start, end]) => start < end);
|
|
641
|
+
const validSegmentsB = resultSegmentsB.filter(([start, end]) => start < end);
|
|
642
|
+
|
|
643
|
+
// Merge and deduplicate segments
|
|
644
|
+
const mergedSegmentsA = mergeBigIntSegments(validSegmentsA);
|
|
645
|
+
const mergedSegmentsB = mergeBigIntSegments(validSegmentsB);
|
|
646
|
+
|
|
647
|
+
// Build ranges
|
|
648
|
+
const rangesFromA = buildRangesFromBigIntSegments(mergedSegmentsA, rangeA);
|
|
649
|
+
const rangesFromB = buildRangesFromBigIntSegments(mergedSegmentsB, rangeB);
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
rangesFromA,
|
|
653
|
+
rangesFromB,
|
|
654
|
+
};
|
|
413
655
|
}
|
|
414
656
|
|
|
415
657
|
export class ReplicationRangeIndexableU32
|
|
@@ -446,7 +688,7 @@ export class ReplicationRangeIndexableU32
|
|
|
446
688
|
properties: {
|
|
447
689
|
id?: Uint8Array;
|
|
448
690
|
offset: number;
|
|
449
|
-
|
|
691
|
+
width: number;
|
|
450
692
|
mode?: ReplicationIntent;
|
|
451
693
|
timestamp?: bigint;
|
|
452
694
|
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
@@ -455,16 +697,16 @@ export class ReplicationRangeIndexableU32
|
|
|
455
697
|
this.hash =
|
|
456
698
|
(properties as { publicKeyHash: string }).publicKeyHash ||
|
|
457
699
|
(properties as { publicKey: PublicSignKey }).publicKey.hashcode();
|
|
458
|
-
this.transform({
|
|
700
|
+
this.transform({ width: properties.width, offset: properties.offset });
|
|
459
701
|
|
|
460
702
|
this.mode = properties.mode ?? ReplicationIntent.NonStrict;
|
|
461
703
|
this.timestamp = properties.timestamp || BigInt(0);
|
|
462
704
|
}
|
|
463
705
|
|
|
464
|
-
private transform(properties: { offset: number;
|
|
706
|
+
private transform(properties: { offset: number; width: number }) {
|
|
465
707
|
const ranges = getSegmentsFromOffsetAndRange(
|
|
466
708
|
properties.offset,
|
|
467
|
-
properties.
|
|
709
|
+
properties.width,
|
|
468
710
|
0,
|
|
469
711
|
MAX_U32,
|
|
470
712
|
);
|
|
@@ -494,6 +736,11 @@ export class ReplicationRangeIndexableU32
|
|
|
494
736
|
return toBase64(this.id);
|
|
495
737
|
}
|
|
496
738
|
|
|
739
|
+
get rangeHash() {
|
|
740
|
+
const ser = serialize(this);
|
|
741
|
+
return sha256Base64Sync(ser);
|
|
742
|
+
}
|
|
743
|
+
|
|
497
744
|
contains(point: number) {
|
|
498
745
|
return (
|
|
499
746
|
(point >= this.start1 && point < this.end1) ||
|
|
@@ -554,6 +801,7 @@ export class ReplicationRangeIndexableU32
|
|
|
554
801
|
|
|
555
802
|
equalRange(other: ReplicationRangeIndexableU32) {
|
|
556
803
|
return (
|
|
804
|
+
this.hash === other.hash &&
|
|
557
805
|
this.start1 === other.start1 &&
|
|
558
806
|
this.end1 === other.end1 &&
|
|
559
807
|
this.start2 === other.start2 &&
|
|
@@ -588,7 +836,7 @@ export class ReplicationRangeIndexableU64
|
|
|
588
836
|
id: Uint8Array;
|
|
589
837
|
|
|
590
838
|
@field({ type: "string" })
|
|
591
|
-
hash: string;
|
|
839
|
+
hash: string; // publickey hash
|
|
592
840
|
|
|
593
841
|
@field({ type: "u64" })
|
|
594
842
|
timestamp: bigint;
|
|
@@ -615,7 +863,7 @@ export class ReplicationRangeIndexableU64
|
|
|
615
863
|
properties: {
|
|
616
864
|
id?: Uint8Array;
|
|
617
865
|
offset: bigint | number;
|
|
618
|
-
|
|
866
|
+
width: bigint | number;
|
|
619
867
|
mode?: ReplicationIntent;
|
|
620
868
|
timestamp?: bigint;
|
|
621
869
|
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
@@ -624,7 +872,7 @@ export class ReplicationRangeIndexableU64
|
|
|
624
872
|
this.hash =
|
|
625
873
|
(properties as { publicKeyHash: string }).publicKeyHash ||
|
|
626
874
|
(properties as { publicKey: PublicSignKey }).publicKey.hashcode();
|
|
627
|
-
this.transform({
|
|
875
|
+
this.transform({ width: properties.width, offset: properties.offset });
|
|
628
876
|
|
|
629
877
|
this.mode = properties.mode ?? ReplicationIntent.NonStrict;
|
|
630
878
|
this.timestamp = properties.timestamp || BigInt(0);
|
|
@@ -632,11 +880,11 @@ export class ReplicationRangeIndexableU64
|
|
|
632
880
|
|
|
633
881
|
private transform(properties: {
|
|
634
882
|
offset: bigint | number;
|
|
635
|
-
|
|
883
|
+
width: bigint | number;
|
|
636
884
|
}) {
|
|
637
885
|
const ranges = getSegmentsFromOffsetAndRange(
|
|
638
886
|
BigInt(properties.offset),
|
|
639
|
-
BigInt(properties.
|
|
887
|
+
BigInt(properties.width),
|
|
640
888
|
0n,
|
|
641
889
|
MAX_U64,
|
|
642
890
|
);
|
|
@@ -673,6 +921,11 @@ export class ReplicationRangeIndexableU64
|
|
|
673
921
|
);
|
|
674
922
|
}
|
|
675
923
|
|
|
924
|
+
get rangeHash() {
|
|
925
|
+
const ser = serialize(this);
|
|
926
|
+
return sha256Base64Sync(ser);
|
|
927
|
+
}
|
|
928
|
+
|
|
676
929
|
overlaps(other: ReplicationRangeIndexableU64, checkOther = true): boolean {
|
|
677
930
|
if (
|
|
678
931
|
this.contains(other.start1) ||
|
|
@@ -726,6 +979,7 @@ export class ReplicationRangeIndexableU64
|
|
|
726
979
|
|
|
727
980
|
equalRange(other: ReplicationRangeIndexableU64) {
|
|
728
981
|
return (
|
|
982
|
+
this.hash === other.hash &&
|
|
729
983
|
this.start1 === other.start1 &&
|
|
730
984
|
this.end1 === other.end1 &&
|
|
731
985
|
this.start2 === other.start2 &&
|
|
@@ -770,22 +1024,24 @@ export const mergeRanges = <R extends "u32" | "u64">(
|
|
|
770
1024
|
throw new Error("Segments have different publicKeyHash");
|
|
771
1025
|
}
|
|
772
1026
|
|
|
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
1027
|
const sorted = segments.sort((a, b) => Number(a.start1 - b.start1));
|
|
782
1028
|
|
|
783
|
-
let calculateLargeGap = (): [
|
|
1029
|
+
let calculateLargeGap = (): [
|
|
1030
|
+
NumberFromType<R>,
|
|
1031
|
+
number,
|
|
1032
|
+
ReplicationIntent,
|
|
1033
|
+
] => {
|
|
784
1034
|
let last = sorted[sorted.length - 1];
|
|
785
1035
|
let largestArc = numbers.zero;
|
|
786
1036
|
let largestArcIndex = -1;
|
|
1037
|
+
let mode = ReplicationIntent.NonStrict;
|
|
787
1038
|
for (let i = 0; i < sorted.length; i++) {
|
|
788
1039
|
const current = sorted[i];
|
|
1040
|
+
|
|
1041
|
+
if (current.mode === ReplicationIntent.Strict) {
|
|
1042
|
+
mode = ReplicationIntent.Strict;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
789
1045
|
if (current.start1 !== last.start1) {
|
|
790
1046
|
let arc = numbers.zero;
|
|
791
1047
|
if (current.start1 < last.end2) {
|
|
@@ -804,22 +1060,32 @@ export const mergeRanges = <R extends "u32" | "u64">(
|
|
|
804
1060
|
last = current;
|
|
805
1061
|
}
|
|
806
1062
|
|
|
807
|
-
return [largestArc, largestArcIndex];
|
|
1063
|
+
return [largestArc, largestArcIndex, mode];
|
|
808
1064
|
};
|
|
809
|
-
const [largestArc, largestArcIndex] = calculateLargeGap();
|
|
1065
|
+
const [largestArc, largestArcIndex, mode] = calculateLargeGap();
|
|
810
1066
|
|
|
811
1067
|
let totalLengthFinal: number = numbers.maxValue - largestArc;
|
|
812
1068
|
|
|
1069
|
+
const proto = segments[0].constructor;
|
|
1070
|
+
|
|
813
1071
|
if (largestArcIndex === -1) {
|
|
1072
|
+
if (mode !== segments[0].mode) {
|
|
1073
|
+
return new (proto as any)({
|
|
1074
|
+
width: segments[0].width,
|
|
1075
|
+
offset: segments[0].start1,
|
|
1076
|
+
publicKeyHash: segments[0].hash,
|
|
1077
|
+
mode,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
814
1080
|
return segments[0]; // all ranges are the same
|
|
815
1081
|
}
|
|
816
1082
|
// use segments[0] constructor to create a new object
|
|
817
1083
|
|
|
818
|
-
const proto = segments[0].constructor;
|
|
819
1084
|
return new (proto as any)({
|
|
820
|
-
|
|
1085
|
+
width: totalLengthFinal,
|
|
821
1086
|
offset: segments[largestArcIndex].start1,
|
|
822
1087
|
publicKeyHash: segments[0].hash,
|
|
1088
|
+
mode,
|
|
823
1089
|
});
|
|
824
1090
|
};
|
|
825
1091
|
|
|
@@ -1054,12 +1320,17 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1054
1320
|
direction: "above" | "below",
|
|
1055
1321
|
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1056
1322
|
point: NumberFromType<R>,
|
|
1057
|
-
roleAgeLimit: number,
|
|
1058
|
-
matured: boolean,
|
|
1059
|
-
now: number,
|
|
1060
1323
|
includeStrict: boolean,
|
|
1061
1324
|
numbers: Numbers<R>,
|
|
1062
|
-
options?: {
|
|
1325
|
+
options?: {
|
|
1326
|
+
shape?: S;
|
|
1327
|
+
hash?: string;
|
|
1328
|
+
time?: {
|
|
1329
|
+
roleAgeLimit: number;
|
|
1330
|
+
matured: boolean;
|
|
1331
|
+
now: number;
|
|
1332
|
+
};
|
|
1333
|
+
},
|
|
1063
1334
|
): IndexIterator<ReplicationRangeIndexable<R>, S> => {
|
|
1064
1335
|
const createQueries = (p: NumberFromType<R>, equality: boolean) => {
|
|
1065
1336
|
let queries: Query[];
|
|
@@ -1070,11 +1341,6 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1070
1341
|
compare: equality ? Compare.LessOrEqual : Compare.Less,
|
|
1071
1342
|
value: p,
|
|
1072
1343
|
}),
|
|
1073
|
-
new IntegerCompare({
|
|
1074
|
-
key: "timestamp",
|
|
1075
|
-
compare: matured ? Compare.LessOrEqual : Compare.GreaterOrEqual,
|
|
1076
|
-
value: BigInt(now - roleAgeLimit),
|
|
1077
|
-
}),
|
|
1078
1344
|
];
|
|
1079
1345
|
} else {
|
|
1080
1346
|
queries = [
|
|
@@ -1083,12 +1349,19 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1083
1349
|
compare: equality ? Compare.GreaterOrEqual : Compare.Greater,
|
|
1084
1350
|
value: p,
|
|
1085
1351
|
}),
|
|
1352
|
+
];
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (options?.time) {
|
|
1356
|
+
queries.push(
|
|
1086
1357
|
new IntegerCompare({
|
|
1087
1358
|
key: "timestamp",
|
|
1088
|
-
compare: matured
|
|
1089
|
-
|
|
1359
|
+
compare: options?.time?.matured
|
|
1360
|
+
? Compare.LessOrEqual
|
|
1361
|
+
: Compare.GreaterOrEqual,
|
|
1362
|
+
value: BigInt(options.time.now - options.time.roleAgeLimit),
|
|
1090
1363
|
}),
|
|
1091
|
-
|
|
1364
|
+
);
|
|
1092
1365
|
}
|
|
1093
1366
|
|
|
1094
1367
|
queries.push(
|
|
@@ -1104,6 +1377,9 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1104
1377
|
}),
|
|
1105
1378
|
);
|
|
1106
1379
|
}
|
|
1380
|
+
if (options?.hash) {
|
|
1381
|
+
queries.push(new StringMatch({ key: "hash", value: options.hash }));
|
|
1382
|
+
}
|
|
1107
1383
|
return queries;
|
|
1108
1384
|
};
|
|
1109
1385
|
|
|
@@ -1210,7 +1486,7 @@ export const getCoveringRangeQuery = (range: {
|
|
|
1210
1486
|
]),
|
|
1211
1487
|
];
|
|
1212
1488
|
};
|
|
1213
|
-
export const
|
|
1489
|
+
export const countCoveringRangesSameOwner = async <R extends "u32" | "u64">(
|
|
1214
1490
|
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1215
1491
|
range: ReplicationRangeIndexable<R>,
|
|
1216
1492
|
) => {
|
|
@@ -1234,6 +1510,35 @@ export const iHaveCoveringRange = async <R extends "u32" | "u64">(
|
|
|
1234
1510
|
);
|
|
1235
1511
|
};
|
|
1236
1512
|
|
|
1513
|
+
export const getCoveringRangesSameOwner = <R extends "u32" | "u64">(
|
|
1514
|
+
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1515
|
+
range: {
|
|
1516
|
+
start1: number | bigint;
|
|
1517
|
+
end1: number | bigint;
|
|
1518
|
+
start2: number | bigint;
|
|
1519
|
+
end2: number | bigint;
|
|
1520
|
+
hash: string;
|
|
1521
|
+
id: Uint8Array;
|
|
1522
|
+
},
|
|
1523
|
+
) => {
|
|
1524
|
+
return rects.iterate({
|
|
1525
|
+
query: [
|
|
1526
|
+
...getCoveringRangeQuery(range),
|
|
1527
|
+
new StringMatch({
|
|
1528
|
+
key: "hash",
|
|
1529
|
+
value: range.hash,
|
|
1530
|
+
}),
|
|
1531
|
+
// assume that we are looking for other ranges, not want to update an existing one
|
|
1532
|
+
new Not(
|
|
1533
|
+
new ByteMatchQuery({
|
|
1534
|
+
key: "id",
|
|
1535
|
+
value: range.id,
|
|
1536
|
+
}),
|
|
1537
|
+
),
|
|
1538
|
+
],
|
|
1539
|
+
});
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1237
1542
|
// TODO
|
|
1238
1543
|
export function getDistance(
|
|
1239
1544
|
from: any,
|
|
@@ -1397,26 +1702,29 @@ const joinIterator = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1397
1702
|
};
|
|
1398
1703
|
};
|
|
1399
1704
|
|
|
1400
|
-
const
|
|
1705
|
+
const getClosestAroundOrContaining = <
|
|
1401
1706
|
S extends (Shape & { timestamp: true }) | undefined,
|
|
1402
1707
|
R extends "u32" | "u64",
|
|
1403
1708
|
>(
|
|
1404
1709
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1405
1710
|
point: NumberFromType<R>,
|
|
1406
|
-
roleAge: number,
|
|
1407
|
-
now: number,
|
|
1408
1711
|
includeStrictBelow: boolean,
|
|
1409
1712
|
includeStrictAbove: boolean,
|
|
1410
1713
|
numbers: Numbers<R>,
|
|
1411
|
-
options?: {
|
|
1714
|
+
options?: {
|
|
1715
|
+
shape?: S;
|
|
1716
|
+
hash?: string;
|
|
1717
|
+
time?: {
|
|
1718
|
+
roleAgeLimit: number;
|
|
1719
|
+
matured: boolean;
|
|
1720
|
+
now: number;
|
|
1721
|
+
};
|
|
1722
|
+
},
|
|
1412
1723
|
) => {
|
|
1413
1724
|
const closestBelow = getClosest<S, R>(
|
|
1414
1725
|
"below",
|
|
1415
1726
|
peers,
|
|
1416
1727
|
point,
|
|
1417
|
-
roleAge,
|
|
1418
|
-
true,
|
|
1419
|
-
now,
|
|
1420
1728
|
includeStrictBelow,
|
|
1421
1729
|
numbers,
|
|
1422
1730
|
options,
|
|
@@ -1425,110 +1733,104 @@ const getClosestAround = <
|
|
|
1425
1733
|
"above",
|
|
1426
1734
|
peers,
|
|
1427
1735
|
point,
|
|
1428
|
-
roleAge,
|
|
1429
|
-
true,
|
|
1430
|
-
now,
|
|
1431
1736
|
includeStrictAbove,
|
|
1432
1737
|
numbers,
|
|
1433
1738
|
options,
|
|
1434
1739
|
);
|
|
1435
|
-
|
|
1436
|
-
peers,
|
|
1437
|
-
point,
|
|
1438
|
-
{
|
|
1439
|
-
time: {
|
|
1440
|
-
roleAgeLimit: roleAge,
|
|
1441
|
-
matured: true,
|
|
1442
|
-
now,
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
);
|
|
1740
|
+
const containing = iterateRangesContainingPoint<S, R>(peers, point, options);
|
|
1446
1741
|
|
|
1447
1742
|
return iteratorInSeries(
|
|
1448
1743
|
containing,
|
|
1449
1744
|
joinIterator<S, R>([closestBelow, closestAbove], point, "closest", numbers),
|
|
1450
|
-
); */
|
|
1451
|
-
return joinIterator<S, R>(
|
|
1452
|
-
[closestBelow, closestAbove],
|
|
1453
|
-
point,
|
|
1454
|
-
"closest",
|
|
1455
|
-
numbers,
|
|
1456
1745
|
);
|
|
1457
1746
|
};
|
|
1458
1747
|
|
|
1459
|
-
export const
|
|
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">(
|
|
1469
|
-
roleAge: number,
|
|
1748
|
+
export const getAdjecentSameOwner = async <R extends "u32" | "u64">(
|
|
1470
1749
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
now: number,
|
|
1750
|
+
range: {
|
|
1751
|
+
idString?: string;
|
|
1752
|
+
start1: NumberFromType<R>;
|
|
1753
|
+
end2: NumberFromType<R>;
|
|
1754
|
+
hash: string;
|
|
1755
|
+
},
|
|
1478
1756
|
numbers: Numbers<R>,
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
>(peers, point, 0, true, now, { shape: { timestamp: true, hash: true } as const });
|
|
1485
|
-
const allContaining = await containing.all();
|
|
1486
|
-
for (const rect of allContaining) {
|
|
1487
|
-
collector(rect.value, isMatured(rect.value, now, roleAge), true);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
if (done()) {
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
const closestBelow = getClosest<undefined, R>(
|
|
1757
|
+
): Promise<{
|
|
1758
|
+
below?: ReplicationRangeIndexable<R>;
|
|
1759
|
+
above?: ReplicationRangeIndexable<R>;
|
|
1760
|
+
}> => {
|
|
1761
|
+
const closestBelowIterator = getClosest<undefined, R>(
|
|
1495
1762
|
"below",
|
|
1496
1763
|
peers,
|
|
1497
|
-
|
|
1498
|
-
0,
|
|
1764
|
+
range.start1,
|
|
1499
1765
|
true,
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1766
|
+
numbers,
|
|
1767
|
+
{
|
|
1768
|
+
hash: range.hash,
|
|
1769
|
+
},
|
|
1503
1770
|
);
|
|
1504
|
-
const
|
|
1771
|
+
const closestBelow = await closestBelowIterator.next(1);
|
|
1772
|
+
closestBelowIterator.close();
|
|
1773
|
+
const closestAboveIterator = getClosest<undefined, R>(
|
|
1505
1774
|
"above",
|
|
1506
1775
|
peers,
|
|
1507
|
-
|
|
1508
|
-
0,
|
|
1776
|
+
range.end2,
|
|
1509
1777
|
true,
|
|
1510
|
-
now,
|
|
1511
|
-
false,
|
|
1512
|
-
numbers
|
|
1513
|
-
);
|
|
1514
|
-
const aroundIterator = joinIterator<undefined, R>(
|
|
1515
|
-
[closestBelow, closestAbove],
|
|
1516
|
-
point,
|
|
1517
|
-
"closest",
|
|
1518
1778
|
numbers,
|
|
1779
|
+
{
|
|
1780
|
+
hash: range.hash,
|
|
1781
|
+
},
|
|
1519
1782
|
);
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1783
|
+
const closestAbove = await closestAboveIterator.next(1);
|
|
1784
|
+
closestAboveIterator.close();
|
|
1785
|
+
return {
|
|
1786
|
+
below:
|
|
1787
|
+
range.idString === closestBelow[0]?.value.idString
|
|
1788
|
+
? undefined
|
|
1789
|
+
: closestBelow[0]?.value,
|
|
1790
|
+
above:
|
|
1791
|
+
closestBelow[0]?.id.primitive === closestAbove[0]?.id.primitive ||
|
|
1792
|
+
range.idString === closestBelow[0]?.value.idString
|
|
1793
|
+
? undefined
|
|
1794
|
+
: closestAbove[0]?.value,
|
|
1795
|
+
};
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
export const getAllMergeCandiates = async <R extends "u32" | "u64">(
|
|
1799
|
+
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1800
|
+
range: {
|
|
1801
|
+
idString?: string;
|
|
1802
|
+
start1: NumberFromType<R>;
|
|
1803
|
+
start2: NumberFromType<R>;
|
|
1804
|
+
end1: NumberFromType<R>;
|
|
1805
|
+
end2: NumberFromType<R>;
|
|
1806
|
+
hash: string;
|
|
1807
|
+
id: Uint8Array;
|
|
1808
|
+
},
|
|
1809
|
+
numbers: Numbers<R>,
|
|
1810
|
+
): Promise<MapIterator<ReplicationRangeIndexable<R>>> => {
|
|
1811
|
+
const adjacent = await getAdjecentSameOwner(peers, range, numbers);
|
|
1812
|
+
const covering = await getCoveringRangesSameOwner(peers, range).all();
|
|
1813
|
+
|
|
1814
|
+
let ret: Map<string, ReplicationRangeIndexable<R>> = new Map();
|
|
1815
|
+
if (adjacent.below) {
|
|
1816
|
+
ret.set(adjacent.below.idString, adjacent.below);
|
|
1528
1817
|
}
|
|
1818
|
+
if (adjacent.above) {
|
|
1819
|
+
ret.set(adjacent.above.idString, adjacent.above);
|
|
1820
|
+
}
|
|
1821
|
+
for (const range of covering) {
|
|
1822
|
+
ret.set(range.value.idString, range.value);
|
|
1823
|
+
}
|
|
1824
|
+
return ret.values();
|
|
1529
1825
|
};
|
|
1530
|
-
*/
|
|
1531
1826
|
|
|
1827
|
+
export const isMatured = (
|
|
1828
|
+
segment: { timestamp: bigint },
|
|
1829
|
+
now: number,
|
|
1830
|
+
minAge: number,
|
|
1831
|
+
) => {
|
|
1832
|
+
return now - Number(segment.timestamp) >= minAge;
|
|
1833
|
+
};
|
|
1532
1834
|
const collectClosestAround = async <R extends "u32" | "u64">(
|
|
1533
1835
|
roleAge: number,
|
|
1534
1836
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
@@ -1542,9 +1844,6 @@ const collectClosestAround = async <R extends "u32" | "u64">(
|
|
|
1542
1844
|
"below",
|
|
1543
1845
|
peers,
|
|
1544
1846
|
point,
|
|
1545
|
-
0,
|
|
1546
|
-
true,
|
|
1547
|
-
now,
|
|
1548
1847
|
false,
|
|
1549
1848
|
numbers,
|
|
1550
1849
|
);
|
|
@@ -1552,17 +1851,10 @@ const collectClosestAround = async <R extends "u32" | "u64">(
|
|
|
1552
1851
|
"above",
|
|
1553
1852
|
peers,
|
|
1554
1853
|
point,
|
|
1555
|
-
0,
|
|
1556
|
-
true,
|
|
1557
|
-
now,
|
|
1558
1854
|
false,
|
|
1559
1855
|
numbers,
|
|
1560
1856
|
);
|
|
1561
|
-
|
|
1562
|
-
peers,
|
|
1563
|
-
point,
|
|
1564
|
-
);
|
|
1565
|
-
*/
|
|
1857
|
+
|
|
1566
1858
|
const aroundIterator = joinIterator<undefined, R>(
|
|
1567
1859
|
[/* containingIterator, */ closestBelow, closestAbove],
|
|
1568
1860
|
point,
|
|
@@ -1714,7 +2006,13 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
1714
2006
|
const { startNode, startLocation, endLocation } = await getStartAndEnd<
|
|
1715
2007
|
undefined,
|
|
1716
2008
|
R
|
|
1717
|
-
>(peers, start, widthToCoverScaled,
|
|
2009
|
+
>(peers, start, widthToCoverScaled, properties.numbers, {
|
|
2010
|
+
time: {
|
|
2011
|
+
roleAgeLimit: roleAge,
|
|
2012
|
+
now,
|
|
2013
|
+
matured: true,
|
|
2014
|
+
},
|
|
2015
|
+
});
|
|
1718
2016
|
|
|
1719
2017
|
let ret = new Set<string>();
|
|
1720
2018
|
|
|
@@ -1778,16 +2076,13 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
1778
2076
|
) => {
|
|
1779
2077
|
// if not get closest from above
|
|
1780
2078
|
let next = await fetchOne<undefined, R>(
|
|
1781
|
-
getClosest(
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
true,
|
|
1789
|
-
properties.numbers,
|
|
1790
|
-
),
|
|
2079
|
+
getClosest("above", peers, nextLocation, true, properties.numbers, {
|
|
2080
|
+
time: {
|
|
2081
|
+
matured: true,
|
|
2082
|
+
roleAgeLimit: roleAge,
|
|
2083
|
+
now,
|
|
2084
|
+
},
|
|
2085
|
+
}),
|
|
1791
2086
|
);
|
|
1792
2087
|
return next;
|
|
1793
2088
|
};
|
|
@@ -1911,21 +2206,6 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
1911
2206
|
start instanceof PublicSignKey && ret.add(start.hashcode());
|
|
1912
2207
|
return ret;
|
|
1913
2208
|
};
|
|
1914
|
-
/* export const getReplicationDiff = (changes: ReplicationChange) => {
|
|
1915
|
-
// reduce the change set to only regions that are changed for each peer
|
|
1916
|
-
// i.e. subtract removed regions from added regions, and vice versa
|
|
1917
|
-
const result = new Map<string, { range: ReplicationRangeIndexable, added: boolean }[]>();
|
|
1918
|
-
|
|
1919
|
-
for (const addedChange of changes.added ?? []) {
|
|
1920
|
-
let prev = result.get(addedChange.hash) ?? [];
|
|
1921
|
-
for (const [_hash, ranges] of result.entries()) {
|
|
1922
|
-
for (const r of ranges) {
|
|
1923
|
-
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
*/
|
|
1929
2209
|
|
|
1930
2210
|
export const matchEntriesInRangeQuery = (range: {
|
|
1931
2211
|
start1: number | bigint;
|
|
@@ -1967,31 +2247,197 @@ export const matchEntriesInRangeQuery = (range: {
|
|
|
1967
2247
|
];
|
|
1968
2248
|
return new Or(ors);
|
|
1969
2249
|
};
|
|
2250
|
+
|
|
2251
|
+
export type ReplicationChanges<
|
|
2252
|
+
T extends ReplicationRangeIndexable<any> = ReplicationRangeIndexable<any>,
|
|
2253
|
+
> = ReplicationChange<T>[];
|
|
2254
|
+
export type ReplicationChange<
|
|
2255
|
+
T extends ReplicationRangeIndexable<any> = ReplicationRangeIndexable<any>,
|
|
2256
|
+
> = (
|
|
2257
|
+
| {
|
|
2258
|
+
type: "added";
|
|
2259
|
+
range: T;
|
|
2260
|
+
matured?: boolean;
|
|
2261
|
+
}
|
|
2262
|
+
| {
|
|
2263
|
+
type: "removed";
|
|
2264
|
+
range: T;
|
|
2265
|
+
}
|
|
2266
|
+
| {
|
|
2267
|
+
type: "replaced";
|
|
2268
|
+
range: T;
|
|
2269
|
+
}
|
|
2270
|
+
) & { timestamp: bigint };
|
|
2271
|
+
|
|
2272
|
+
export const debounceAggregationChanges = <
|
|
2273
|
+
T extends ReplicationRangeIndexable<any>,
|
|
2274
|
+
>(
|
|
2275
|
+
fn: (changeOrChanges: ReplicationChange<T>[]) => void,
|
|
2276
|
+
delay: number,
|
|
2277
|
+
) => {
|
|
2278
|
+
return debounceAccumulator(
|
|
2279
|
+
(result) => {
|
|
2280
|
+
return fn([...result.values()]);
|
|
2281
|
+
},
|
|
2282
|
+
() => {
|
|
2283
|
+
let aggregated: Map<string, ReplicationChange<T>> = new Map();
|
|
2284
|
+
return {
|
|
2285
|
+
add: (change: ReplicationChange<T>) => {
|
|
2286
|
+
const prev = aggregated.get(change.range.idString);
|
|
2287
|
+
if (prev) {
|
|
2288
|
+
if (prev.range.timestamp < change.range.timestamp) {
|
|
2289
|
+
aggregated.set(change.range.idString, change);
|
|
2290
|
+
}
|
|
2291
|
+
} else {
|
|
2292
|
+
aggregated.set(change.range.idString, change);
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
delete: (key: string) => {
|
|
2296
|
+
aggregated.delete(key);
|
|
2297
|
+
},
|
|
2298
|
+
size: () => aggregated.size,
|
|
2299
|
+
value: aggregated,
|
|
2300
|
+
};
|
|
2301
|
+
},
|
|
2302
|
+
delay,
|
|
2303
|
+
);
|
|
2304
|
+
};
|
|
2305
|
+
|
|
2306
|
+
export const mergeReplicationChanges = <R extends NumericType>(
|
|
2307
|
+
changesOrChangesArr:
|
|
2308
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>
|
|
2309
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>[],
|
|
2310
|
+
rebalanceHistory: Cache<string>,
|
|
2311
|
+
): ReplicationChange<ReplicationRangeIndexable<R>>[] => {
|
|
2312
|
+
let first = changesOrChangesArr[0];
|
|
2313
|
+
let changes: ReplicationChange<ReplicationRangeIndexable<R>>[];
|
|
2314
|
+
if (!Array.isArray(first)) {
|
|
2315
|
+
changes = changesOrChangesArr as ReplicationChange<
|
|
2316
|
+
ReplicationRangeIndexable<R>
|
|
2317
|
+
>[];
|
|
2318
|
+
} else {
|
|
2319
|
+
changes = changesOrChangesArr.flat() as ReplicationChange<
|
|
2320
|
+
ReplicationRangeIndexable<R>
|
|
2321
|
+
>[];
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// group by hash so we can cancel out changes
|
|
2325
|
+
const grouped = new Map<
|
|
2326
|
+
string,
|
|
2327
|
+
ReplicationChange<ReplicationRangeIndexable<R>>[]
|
|
2328
|
+
>();
|
|
2329
|
+
for (const change of changes) {
|
|
2330
|
+
const prev = grouped.get(change.range.hash);
|
|
2331
|
+
if (prev) {
|
|
2332
|
+
prev.push(change);
|
|
2333
|
+
} else {
|
|
2334
|
+
grouped.set(change.range.hash, [change]);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
let all: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
|
|
2339
|
+
for (const [_k, v] of grouped) {
|
|
2340
|
+
if (v.length > 1) {
|
|
2341
|
+
// sort by timestamp so newest is last
|
|
2342
|
+
v.sort((a, b) =>
|
|
2343
|
+
a.range.timestamp < b.range.timestamp
|
|
2344
|
+
? -1
|
|
2345
|
+
: a.range.timestamp > b.range.timestamp
|
|
2346
|
+
? 1
|
|
2347
|
+
: 0,
|
|
2348
|
+
);
|
|
2349
|
+
|
|
2350
|
+
let results: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
|
|
2351
|
+
let consumed: Set<number> = new Set();
|
|
2352
|
+
for (let i = 0; i < v.length; i++) {
|
|
2353
|
+
// if segment is removed and we have previously processed it
|
|
2354
|
+
// then go over each overlapping added segment add remove the removal,
|
|
2355
|
+
// equivalent is that this would represent (1 - 1 + 1) = 1
|
|
2356
|
+
if (v[i].type === "removed" || v[i].type === "replaced") {
|
|
2357
|
+
if (rebalanceHistory.has(v[i].range.rangeHash)) {
|
|
2358
|
+
let vStart = v.length;
|
|
2359
|
+
for (let j = i + 1; j < vStart; j++) {
|
|
2360
|
+
const newer = v[j];
|
|
2361
|
+
if (newer.type === "added" && !newer.matured) {
|
|
2362
|
+
const {
|
|
2363
|
+
rangesFromA: updatedRemoved,
|
|
2364
|
+
rangesFromB: updatedNewer,
|
|
2365
|
+
} = symmetricDifferenceRanges(v[i].range, newer.range);
|
|
2366
|
+
for (const diff of updatedRemoved) {
|
|
2367
|
+
results.push({
|
|
2368
|
+
range: diff,
|
|
2369
|
+
type: "removed" as const,
|
|
2370
|
+
timestamp: v[i].timestamp,
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
for (const diff of updatedNewer) {
|
|
2374
|
+
v.push({
|
|
2375
|
+
range: diff,
|
|
2376
|
+
type: "added" as const,
|
|
2377
|
+
timestamp: newer.timestamp,
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
consumed.add(j);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
rebalanceHistory.del(v[i].range.rangeHash);
|
|
2384
|
+
} else {
|
|
2385
|
+
results.push(v[i]);
|
|
2386
|
+
}
|
|
2387
|
+
} else if (v[i].type === "added") {
|
|
2388
|
+
// TODO should the below clause be used?
|
|
2389
|
+
// after testing it seems that certain changes are not propagating as expected using this
|
|
2390
|
+
/* if (rebalanceHistory.has(v[i].range.rangeHash)) {
|
|
2391
|
+
continue;
|
|
2392
|
+
} */
|
|
2393
|
+
|
|
2394
|
+
rebalanceHistory.add(v[i].range.rangeHash);
|
|
2395
|
+
if (!consumed.has(i)) {
|
|
2396
|
+
results.push(v[i]);
|
|
2397
|
+
}
|
|
2398
|
+
} else {
|
|
2399
|
+
results.push(v[i]);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
all.push(...results);
|
|
2404
|
+
} else {
|
|
2405
|
+
rebalanceHistory.add(v[0].range.rangeHash);
|
|
2406
|
+
all.push(v[0]);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return all;
|
|
2410
|
+
};
|
|
2411
|
+
|
|
1970
2412
|
export const toRebalance = <R extends "u32" | "u64">(
|
|
1971
|
-
|
|
2413
|
+
changeOrChanges:
|
|
2414
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>
|
|
2415
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>[],
|
|
1972
2416
|
index: Index<EntryReplicated<R>>,
|
|
2417
|
+
rebalanceHistory: Cache<string>,
|
|
1973
2418
|
): AsyncIterable<EntryReplicated<R>> => {
|
|
2419
|
+
const change = mergeReplicationChanges(changeOrChanges, rebalanceHistory);
|
|
2420
|
+
|
|
1974
2421
|
const assignedRangesQuery = (changes: ReplicationChanges) => {
|
|
1975
2422
|
let ors: Query[] = [];
|
|
2423
|
+
let onlyStrict = true;
|
|
1976
2424
|
for (const change of changes) {
|
|
1977
2425
|
const matchRange = matchEntriesInRangeQuery(change.range);
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
ors.push(prevMatchRange);
|
|
1982
|
-
ors.push(matchRange);
|
|
1983
|
-
} else {
|
|
1984
|
-
ors.push(matchRange);
|
|
2426
|
+
ors.push(matchRange);
|
|
2427
|
+
if (change.range.mode === ReplicationIntent.NonStrict) {
|
|
2428
|
+
onlyStrict = false;
|
|
1985
2429
|
}
|
|
1986
2430
|
}
|
|
1987
2431
|
|
|
1988
2432
|
// entry is assigned to a range boundary, meaning it is due to be inspected
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2433
|
+
if (!onlyStrict || changes.length === 0) {
|
|
2434
|
+
ors.push(
|
|
2435
|
+
new BoolQuery({
|
|
2436
|
+
key: "assignedToRangeBoundary",
|
|
2437
|
+
value: true,
|
|
2438
|
+
}),
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
1995
2441
|
|
|
1996
2442
|
// entry is not sufficiently replicated, and we are to still keep it
|
|
1997
2443
|
return new Or(ors);
|
|
@@ -1999,7 +2445,7 @@ export const toRebalance = <R extends "u32" | "u64">(
|
|
|
1999
2445
|
return {
|
|
2000
2446
|
[Symbol.asyncIterator]: async function* () {
|
|
2001
2447
|
const iterator = index.iterate({
|
|
2002
|
-
query: assignedRangesQuery(
|
|
2448
|
+
query: assignedRangesQuery(change),
|
|
2003
2449
|
});
|
|
2004
2450
|
|
|
2005
2451
|
while (iterator.done() !== true) {
|
|
@@ -2024,11 +2470,14 @@ export const fetchOneFromPublicKey = async <
|
|
|
2024
2470
|
>(
|
|
2025
2471
|
publicKey: PublicSignKey,
|
|
2026
2472
|
index: Index<ReplicationRangeIndexable<R>>,
|
|
2027
|
-
roleAge: number,
|
|
2028
|
-
now: number,
|
|
2029
2473
|
numbers: Numbers<R>,
|
|
2030
2474
|
options?: {
|
|
2031
|
-
shape
|
|
2475
|
+
shape?: S;
|
|
2476
|
+
time?: {
|
|
2477
|
+
roleAgeLimit: number;
|
|
2478
|
+
matured: boolean;
|
|
2479
|
+
now: number;
|
|
2480
|
+
};
|
|
2032
2481
|
},
|
|
2033
2482
|
) => {
|
|
2034
2483
|
let iterator = index.iterate<S>(
|
|
@@ -2041,13 +2490,14 @@ export const fetchOneFromPublicKey = async <
|
|
|
2041
2490
|
await iterator.close();
|
|
2042
2491
|
let node = result[0]?.value;
|
|
2043
2492
|
if (node) {
|
|
2044
|
-
if (
|
|
2493
|
+
if (
|
|
2494
|
+
options?.time &&
|
|
2495
|
+
!isMatured(node, options.time.now, options.time.roleAgeLimit)
|
|
2496
|
+
) {
|
|
2045
2497
|
const matured = await fetchOne(
|
|
2046
|
-
|
|
2498
|
+
getClosestAroundOrContaining<S, R>(
|
|
2047
2499
|
index,
|
|
2048
2500
|
node.start1,
|
|
2049
|
-
roleAge,
|
|
2050
|
-
now,
|
|
2051
2501
|
false,
|
|
2052
2502
|
false,
|
|
2053
2503
|
numbers,
|
|
@@ -2069,10 +2519,15 @@ export const getStartAndEnd = async <
|
|
|
2069
2519
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
2070
2520
|
start: NumberFromType<R> | PublicSignKey | undefined | undefined,
|
|
2071
2521
|
widthToCoverScaled: NumberFromType<R>,
|
|
2072
|
-
roleAge: number,
|
|
2073
|
-
now: number,
|
|
2074
2522
|
numbers: Numbers<R>,
|
|
2075
|
-
options?: {
|
|
2523
|
+
options?: {
|
|
2524
|
+
shape?: S;
|
|
2525
|
+
time?: {
|
|
2526
|
+
roleAgeLimit: number;
|
|
2527
|
+
matured: boolean;
|
|
2528
|
+
now: number;
|
|
2529
|
+
};
|
|
2530
|
+
},
|
|
2076
2531
|
): Promise<{
|
|
2077
2532
|
startNode: ReturnTypeFromShape<ReplicationRangeIndexable<R>, S> | undefined;
|
|
2078
2533
|
startLocation: NumberFromType<R>;
|
|
@@ -2089,8 +2544,6 @@ export const getStartAndEnd = async <
|
|
|
2089
2544
|
startNode = await fetchOneClosest<S, R>(
|
|
2090
2545
|
peers,
|
|
2091
2546
|
startLocation,
|
|
2092
|
-
roleAge,
|
|
2093
|
-
now,
|
|
2094
2547
|
false,
|
|
2095
2548
|
true,
|
|
2096
2549
|
numbers,
|
|
@@ -2100,14 +2553,7 @@ export const getStartAndEnd = async <
|
|
|
2100
2553
|
|
|
2101
2554
|
if (start instanceof PublicSignKey) {
|
|
2102
2555
|
// start at our node (local first)
|
|
2103
|
-
startNode = await fetchOneFromPublicKey(
|
|
2104
|
-
start,
|
|
2105
|
-
peers,
|
|
2106
|
-
roleAge,
|
|
2107
|
-
now,
|
|
2108
|
-
numbers,
|
|
2109
|
-
options,
|
|
2110
|
-
);
|
|
2556
|
+
startNode = await fetchOneFromPublicKey(start, peers, numbers, options);
|
|
2111
2557
|
if (!startNode) {
|
|
2112
2558
|
// fetch randomly
|
|
2113
2559
|
await nodeFromPoint();
|
|
@@ -2168,19 +2614,23 @@ export const fetchOneClosest = <
|
|
|
2168
2614
|
>(
|
|
2169
2615
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
2170
2616
|
point: NumberFromType<R>,
|
|
2171
|
-
roleAge: number,
|
|
2172
|
-
now: number,
|
|
2173
2617
|
includeStrictBelow: boolean,
|
|
2174
2618
|
includeStrictAbove: boolean,
|
|
2175
2619
|
numbers: Numbers<R>,
|
|
2176
|
-
options?: {
|
|
2620
|
+
options?: {
|
|
2621
|
+
shape?: S;
|
|
2622
|
+
time?: {
|
|
2623
|
+
roleAgeLimit: number;
|
|
2624
|
+
matured: boolean;
|
|
2625
|
+
now: number;
|
|
2626
|
+
};
|
|
2627
|
+
},
|
|
2177
2628
|
) => {
|
|
2178
2629
|
return fetchOne<S, R>(
|
|
2179
|
-
|
|
2630
|
+
getClosestAroundOrContaining<S, R>(
|
|
2180
2631
|
peers,
|
|
2181
2632
|
point,
|
|
2182
|
-
|
|
2183
|
-
now,
|
|
2633
|
+
|
|
2184
2634
|
includeStrictBelow,
|
|
2185
2635
|
includeStrictAbove,
|
|
2186
2636
|
numbers,
|