@peerbit/shared-log 11.0.8 → 11.1.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.
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +154 -144
- package/dist/src/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +208 -181
package/src/index.ts
CHANGED
|
@@ -201,13 +201,21 @@ export type FixedReplicationOptions = {
|
|
|
201
201
|
offset?: number | bigint;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
type NewReplicationOptions<R extends "u32" | "u64" = any> =
|
|
205
205
|
| DynamicReplicationOptions<R>
|
|
206
206
|
| FixedReplicationOptions
|
|
207
207
|
| FixedReplicationOptions[]
|
|
208
208
|
| number
|
|
209
|
-
| boolean
|
|
210
|
-
|
|
209
|
+
| boolean;
|
|
210
|
+
|
|
211
|
+
type ExistingReplicationOptions<R extends "u32" | "u64" = any> = {
|
|
212
|
+
type: "resume";
|
|
213
|
+
default: NewReplicationOptions<R>;
|
|
214
|
+
};
|
|
215
|
+
export type ReplicationOptions<R extends "u32" | "u64" = any> =
|
|
216
|
+
| NewReplicationOptions<R>
|
|
217
|
+
| ExistingReplicationOptions<R>;
|
|
218
|
+
|
|
211
219
|
export { BlocksMessage };
|
|
212
220
|
|
|
213
221
|
const isAdaptiveReplicatorOption = (
|
|
@@ -234,15 +242,24 @@ const isUnreplicationOptions = (options?: ReplicationOptions<any>): boolean =>
|
|
|
234
242
|
((options as FixedReplicationOptions)?.offset === undefined &&
|
|
235
243
|
(options as FixedReplicationOptions)?.factor === 0);
|
|
236
244
|
|
|
237
|
-
const isReplicationOptionsDependentOnPreviousState = (
|
|
238
|
-
options
|
|
239
|
-
|
|
245
|
+
const isReplicationOptionsDependentOnPreviousState = async (
|
|
246
|
+
options: ReplicationOptions<any> | undefined,
|
|
247
|
+
index: Index<ReplicationRangeIndexable<any>>,
|
|
248
|
+
me: PublicSignKey,
|
|
249
|
+
): Promise<boolean> => {
|
|
240
250
|
if (options === true) {
|
|
241
251
|
return true;
|
|
242
252
|
}
|
|
243
253
|
|
|
244
|
-
if (options === "resume") {
|
|
245
|
-
|
|
254
|
+
if ((options as ExistingReplicationOptions<any>)?.type === "resume") {
|
|
255
|
+
// check if there is actually previous replication info
|
|
256
|
+
let countSegments = await index.count({
|
|
257
|
+
query: new StringMatch({
|
|
258
|
+
key: "hash",
|
|
259
|
+
value: me.hashcode(),
|
|
260
|
+
}),
|
|
261
|
+
});
|
|
262
|
+
return countSegments > 0;
|
|
246
263
|
}
|
|
247
264
|
|
|
248
265
|
if (options == null) {
|
|
@@ -338,7 +355,7 @@ export type SharedLogOptions<
|
|
|
338
355
|
distributionDebounceTime?: number;
|
|
339
356
|
compatibility?: number;
|
|
340
357
|
domain?: ReplicationDomainConstructor<D>;
|
|
341
|
-
|
|
358
|
+
eagerBlocks?: boolean | { cacheSize?: number };
|
|
342
359
|
};
|
|
343
360
|
|
|
344
361
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
@@ -618,194 +635,195 @@ export class SharedLog<
|
|
|
618
635
|
let offsetWasProvided = false;
|
|
619
636
|
if (isUnreplicationOptions(options)) {
|
|
620
637
|
await this.unreplicate();
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
if ((options as ExistingReplicationOptions).type === "resume") {
|
|
641
|
+
options = (options as ExistingReplicationOptions)
|
|
642
|
+
.default as ReplicationOptions<R>;
|
|
643
|
+
}
|
|
626
644
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
} else if (options === true) {
|
|
630
|
-
options = {};
|
|
631
|
-
}
|
|
645
|
+
let rangesToReplicate: ReplicationRangeIndexable<R>[] = [];
|
|
646
|
+
let rangesToUnreplicate: ReplicationRangeIndexable<R>[] = [];
|
|
632
647
|
|
|
633
|
-
|
|
648
|
+
if (options == null) {
|
|
649
|
+
options = {};
|
|
650
|
+
} else if (options === true) {
|
|
651
|
+
options = {};
|
|
652
|
+
}
|
|
634
653
|
|
|
635
|
-
|
|
636
|
-
this._isAdaptiveReplicating = true;
|
|
637
|
-
this.setupDebouncedRebalancing(options);
|
|
654
|
+
this._isReplicating = true;
|
|
638
655
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
// not allowed
|
|
643
|
-
return [];
|
|
644
|
-
}
|
|
645
|
-
rangesToReplicate = [maybeRange];
|
|
656
|
+
if (isAdaptiveReplicatorOption(options!)) {
|
|
657
|
+
this._isAdaptiveReplicating = true;
|
|
658
|
+
this.setupDebouncedRebalancing(options);
|
|
646
659
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
];
|
|
660
|
+
// initial role in a dynamic setup
|
|
661
|
+
const maybeRange = await this.getDynamicRange();
|
|
662
|
+
if (!maybeRange) {
|
|
663
|
+
// not allowed
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
rangesToReplicate = [maybeRange];
|
|
667
|
+
|
|
668
|
+
offsetWasProvided = true;
|
|
669
|
+
} else if (isReplicationRangeMessage(options)) {
|
|
670
|
+
rangesToReplicate = [
|
|
671
|
+
options.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
672
|
+
];
|
|
652
673
|
|
|
653
|
-
|
|
674
|
+
offsetWasProvided = true;
|
|
675
|
+
} else {
|
|
676
|
+
let rangeArgs: FixedReplicationOptions[];
|
|
677
|
+
if (typeof options === "number") {
|
|
678
|
+
rangeArgs = [
|
|
679
|
+
{
|
|
680
|
+
factor: options,
|
|
681
|
+
} as FixedReplicationOptions,
|
|
682
|
+
];
|
|
654
683
|
} else {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
factor: options,
|
|
660
|
-
} as FixedReplicationOptions,
|
|
661
|
-
];
|
|
662
|
-
} else {
|
|
663
|
-
rangeArgs = (
|
|
664
|
-
Array.isArray(options) ? options : [{ ...options }]
|
|
665
|
-
) as FixedReplicationOptions[];
|
|
666
|
-
}
|
|
684
|
+
rangeArgs = (
|
|
685
|
+
Array.isArray(options) ? options : [{ ...options }]
|
|
686
|
+
) as FixedReplicationOptions[];
|
|
687
|
+
}
|
|
667
688
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
689
|
+
if (rangeArgs.length === 0) {
|
|
690
|
+
// nothing to do
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
672
693
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
694
|
+
for (const rangeArg of rangeArgs) {
|
|
695
|
+
let timestamp: bigint | undefined = undefined;
|
|
696
|
+
if (rangeArg.id != null) {
|
|
697
|
+
// fetch the previous timestamp if it exists
|
|
698
|
+
const indexed = await this.replicationIndex.get(toId(rangeArg.id), {
|
|
699
|
+
shape: { id: true, timestamp: true },
|
|
700
|
+
});
|
|
701
|
+
if (indexed) {
|
|
702
|
+
timestamp = indexed.value.timestamp;
|
|
683
703
|
}
|
|
684
|
-
const normalized = rangeArg.normalized ?? true;
|
|
685
|
-
offsetWasProvided = rangeArg.offset != null;
|
|
686
|
-
const offset =
|
|
687
|
-
rangeArg.offset != null
|
|
688
|
-
? normalized
|
|
689
|
-
? this.indexableDomain.numbers.denormalize(
|
|
690
|
-
rangeArg.offset as number,
|
|
691
|
-
)
|
|
692
|
-
: rangeArg.offset
|
|
693
|
-
: this.indexableDomain.numbers.random();
|
|
694
|
-
let factor = rangeArg.factor;
|
|
695
|
-
let fullWidth = this.indexableDomain.numbers.maxValue;
|
|
696
|
-
|
|
697
|
-
let factorDenormalized = !normalized
|
|
698
|
-
? factor
|
|
699
|
-
: this.indexableDomain.numbers.denormalize(factor as number);
|
|
700
|
-
rangesToReplicate.push(
|
|
701
|
-
new this.indexableDomain.constructorRange({
|
|
702
|
-
id: rangeArg.id,
|
|
703
|
-
// @ts-ignore
|
|
704
|
-
offset: offset,
|
|
705
|
-
// @ts-ignore
|
|
706
|
-
width: (factor === "all"
|
|
707
|
-
? fullWidth
|
|
708
|
-
: factor === "right"
|
|
709
|
-
? // @ts-ignore
|
|
710
|
-
fullWidth - offset
|
|
711
|
-
: factorDenormalized) as NumberFromType<R>,
|
|
712
|
-
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
713
|
-
mode: rangeArg.strict
|
|
714
|
-
? ReplicationIntent.Strict
|
|
715
|
-
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
716
|
-
timestamp: timestamp ?? BigInt(+new Date()),
|
|
717
|
-
}),
|
|
718
|
-
);
|
|
719
704
|
}
|
|
705
|
+
const normalized = rangeArg.normalized ?? true;
|
|
706
|
+
offsetWasProvided = rangeArg.offset != null;
|
|
707
|
+
const offset =
|
|
708
|
+
rangeArg.offset != null
|
|
709
|
+
? normalized
|
|
710
|
+
? this.indexableDomain.numbers.denormalize(
|
|
711
|
+
rangeArg.offset as number,
|
|
712
|
+
)
|
|
713
|
+
: rangeArg.offset
|
|
714
|
+
: this.indexableDomain.numbers.random();
|
|
715
|
+
let factor = rangeArg.factor;
|
|
716
|
+
let fullWidth = this.indexableDomain.numbers.maxValue;
|
|
717
|
+
|
|
718
|
+
let factorDenormalized = !normalized
|
|
719
|
+
? factor
|
|
720
|
+
: this.indexableDomain.numbers.denormalize(factor as number);
|
|
721
|
+
rangesToReplicate.push(
|
|
722
|
+
new this.indexableDomain.constructorRange({
|
|
723
|
+
id: rangeArg.id,
|
|
724
|
+
// @ts-ignore
|
|
725
|
+
offset: offset,
|
|
726
|
+
// @ts-ignore
|
|
727
|
+
width: (factor === "all"
|
|
728
|
+
? fullWidth
|
|
729
|
+
: factor === "right"
|
|
730
|
+
? // @ts-ignore
|
|
731
|
+
fullWidth - offset
|
|
732
|
+
: factorDenormalized) as NumberFromType<R>,
|
|
733
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
734
|
+
mode: rangeArg.strict
|
|
735
|
+
? ReplicationIntent.Strict
|
|
736
|
+
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
737
|
+
timestamp: timestamp ?? BigInt(+new Date()),
|
|
738
|
+
}),
|
|
739
|
+
);
|
|
740
|
+
}
|
|
720
741
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// also merge segments that are already in the index
|
|
728
|
-
if (this.domain.canMerge) {
|
|
729
|
-
const mergeRangesThatAlreadyExist = await getAllMergeCandiates(
|
|
730
|
-
this.replicationIndex,
|
|
731
|
-
range,
|
|
732
|
-
this.indexableDomain.numbers,
|
|
733
|
-
);
|
|
734
|
-
const mergeableFiltered: ReplicationRangeIndexable<R>[] = [];
|
|
735
|
-
const toKeep: Set<string> = new Set();
|
|
742
|
+
if (mergeSegments) {
|
|
743
|
+
let range =
|
|
744
|
+
rangesToReplicate.length > 1
|
|
745
|
+
? mergeRanges(rangesToReplicate, this.indexableDomain.numbers)
|
|
746
|
+
: rangesToReplicate[0];
|
|
736
747
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
748
|
+
// also merge segments that are already in the index
|
|
749
|
+
if (this.domain.canMerge) {
|
|
750
|
+
const mergeRangesThatAlreadyExist = await getAllMergeCandiates(
|
|
751
|
+
this.replicationIndex,
|
|
752
|
+
range,
|
|
753
|
+
this.indexableDomain.numbers,
|
|
754
|
+
);
|
|
755
|
+
const mergeableFiltered: ReplicationRangeIndexable<R>[] = [];
|
|
756
|
+
const toKeep: Set<string> = new Set();
|
|
744
757
|
|
|
745
|
-
|
|
746
|
-
if (
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
this.indexableDomain.numbers,
|
|
751
|
-
);
|
|
758
|
+
for (const [_key, mergeCandidate] of mergeRangesThatAlreadyExist) {
|
|
759
|
+
if (this.domain.canMerge(mergeCandidate, range)) {
|
|
760
|
+
mergeableFiltered.push(mergeCandidate);
|
|
761
|
+
} else {
|
|
762
|
+
toKeep.add(mergeCandidate.idString);
|
|
752
763
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
mergeableFiltered.push(range); // * we push this last, because mergeRanges will reuse ids of the first elements
|
|
767
|
+
if (mergeableFiltered.length > 1) {
|
|
768
|
+
// ** this is important here as we want to reuse ids of what we already persist, not the new ranges, so we dont get a delet add op, but just a update op
|
|
769
|
+
range = mergeRanges(
|
|
770
|
+
mergeableFiltered,
|
|
771
|
+
this.indexableDomain.numbers,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
for (const [_key, mergeCandidate] of mergeRangesThatAlreadyExist) {
|
|
775
|
+
if (
|
|
776
|
+
mergeCandidate.idString !== range.idString &&
|
|
777
|
+
!toKeep.has(mergeCandidate.idString)
|
|
778
|
+
) {
|
|
779
|
+
rangesToUnreplicate.push(mergeCandidate);
|
|
760
780
|
}
|
|
761
781
|
}
|
|
762
|
-
rangesToReplicate = [range];
|
|
763
782
|
}
|
|
783
|
+
rangesToReplicate = [range];
|
|
764
784
|
}
|
|
785
|
+
}
|
|
765
786
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
let resetRanges = reset;
|
|
774
|
-
if (!resetRanges && !offsetWasProvided) {
|
|
775
|
-
resetRanges = true;
|
|
776
|
-
// because if we do something like replicate ({ factor: 0.5 }) it means that we want to replicate 50%
|
|
777
|
-
// but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
|
|
778
|
-
// TODO make behaviour more clear
|
|
779
|
-
}
|
|
780
|
-
if (rangesToUnreplicate.length > 0) {
|
|
781
|
-
await this.removeReplicationRanges(
|
|
782
|
-
rangesToUnreplicate,
|
|
783
|
-
this.node.identity.publicKey,
|
|
784
|
-
);
|
|
785
|
-
}
|
|
787
|
+
for (const range of rangesToReplicate) {
|
|
788
|
+
this.oldestOpenTime = Math.min(
|
|
789
|
+
Number(range.timestamp),
|
|
790
|
+
this.oldestOpenTime,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
786
793
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
794
|
+
let resetRanges = reset;
|
|
795
|
+
if (!resetRanges && !offsetWasProvided) {
|
|
796
|
+
resetRanges = true;
|
|
797
|
+
// because if we do something like replicate ({ factor: 0.5 }) it means that we want to replicate 50%
|
|
798
|
+
// but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
|
|
799
|
+
// TODO make behaviour more clear
|
|
800
|
+
}
|
|
801
|
+
if (rangesToUnreplicate.length > 0) {
|
|
802
|
+
await this.removeReplicationRanges(
|
|
803
|
+
rangesToUnreplicate,
|
|
804
|
+
this.node.identity.publicKey,
|
|
805
|
+
);
|
|
806
|
+
}
|
|
793
807
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
priority: 1,
|
|
801
|
-
},
|
|
802
|
-
);
|
|
803
|
-
}
|
|
808
|
+
await this.startAnnounceReplicating(rangesToReplicate, {
|
|
809
|
+
reset: resetRanges ?? false,
|
|
810
|
+
checkDuplicates,
|
|
811
|
+
announce,
|
|
812
|
+
rebalance,
|
|
813
|
+
});
|
|
804
814
|
|
|
805
|
-
|
|
815
|
+
if (rangesToUnreplicate.length > 0) {
|
|
816
|
+
await this.rpc.send(
|
|
817
|
+
new StoppedReplicating({
|
|
818
|
+
segmentIds: rangesToUnreplicate.map((x) => x.id),
|
|
819
|
+
}),
|
|
820
|
+
{
|
|
821
|
+
priority: 1,
|
|
822
|
+
},
|
|
823
|
+
);
|
|
806
824
|
}
|
|
807
825
|
|
|
808
|
-
return
|
|
826
|
+
return rangesToReplicate;
|
|
809
827
|
}
|
|
810
828
|
|
|
811
829
|
setupDebouncedRebalancing(options?: DynamicReplicationOptions<R>) {
|
|
@@ -1195,7 +1213,11 @@ export class SharedLog<
|
|
|
1195
1213
|
},
|
|
1196
1214
|
];
|
|
1197
1215
|
} else {
|
|
1198
|
-
return {
|
|
1216
|
+
return {
|
|
1217
|
+
range: x,
|
|
1218
|
+
timestamp: x.timestamp,
|
|
1219
|
+
type: "added" as const,
|
|
1220
|
+
};
|
|
1199
1221
|
}
|
|
1200
1222
|
})
|
|
1201
1223
|
.flat() as ReplicationChanges<ReplicationRangeIndexable<R>>;
|
|
@@ -1602,7 +1624,7 @@ export class SharedLog<
|
|
|
1602
1624
|
),
|
|
1603
1625
|
waitFor: this.rpc.waitFor.bind(this.rpc),
|
|
1604
1626
|
publicKey: this.node.identity.publicKey,
|
|
1605
|
-
|
|
1627
|
+
eagerBlocks: options?.eagerBlocks ?? true,
|
|
1606
1628
|
});
|
|
1607
1629
|
|
|
1608
1630
|
await this.remoteBlocks.start();
|
|
@@ -1804,12 +1826,17 @@ export class SharedLog<
|
|
|
1804
1826
|
let isUnreplicationOptionsDefined = isUnreplicationOptions(
|
|
1805
1827
|
options?.replicate,
|
|
1806
1828
|
);
|
|
1829
|
+
|
|
1830
|
+
const canResumeReplication =
|
|
1831
|
+
(await isReplicationOptionsDependentOnPreviousState(
|
|
1832
|
+
options?.replicate,
|
|
1833
|
+
this.replicationIndex,
|
|
1834
|
+
this.node.identity.publicKey,
|
|
1835
|
+
)) && hasIndexedReplicationInfo;
|
|
1836
|
+
|
|
1807
1837
|
if (hasIndexedReplicationInfo && isUnreplicationOptionsDefined) {
|
|
1808
1838
|
await this.replicate(options?.replicate, { checkDuplicates: true });
|
|
1809
|
-
} else if (
|
|
1810
|
-
isReplicationOptionsDependentOnPreviousState(options?.replicate) &&
|
|
1811
|
-
hasIndexedReplicationInfo
|
|
1812
|
-
) {
|
|
1839
|
+
} else if (canResumeReplication) {
|
|
1813
1840
|
// dont do anthing since we are alread replicating stuff
|
|
1814
1841
|
} else {
|
|
1815
1842
|
await this.replicate(options?.replicate, {
|
|
@@ -2540,13 +2567,13 @@ export class SharedLog<
|
|
|
2540
2567
|
context.from!.hashcode(),
|
|
2541
2568
|
);
|
|
2542
2569
|
|
|
2543
|
-
if (prev && prev > context.timestamp) {
|
|
2570
|
+
if (prev && prev > context.message.header.timestamp) {
|
|
2544
2571
|
return;
|
|
2545
2572
|
}
|
|
2546
2573
|
|
|
2547
2574
|
this.latestReplicationInfoMessage.set(
|
|
2548
2575
|
context.from!.hashcode(),
|
|
2549
|
-
context.timestamp,
|
|
2576
|
+
context.message.header.timestamp,
|
|
2550
2577
|
);
|
|
2551
2578
|
|
|
2552
2579
|
let reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
@@ -2563,7 +2590,7 @@ export class SharedLog<
|
|
|
2563
2590
|
{
|
|
2564
2591
|
reset,
|
|
2565
2592
|
checkDuplicates: true,
|
|
2566
|
-
timestamp: Number(context.timestamp),
|
|
2593
|
+
timestamp: Number(context.message.header.timestamp),
|
|
2567
2594
|
},
|
|
2568
2595
|
);
|
|
2569
2596
|
|