@peerbit/shared-log 9.0.9 → 9.0.10-ccaf4f4

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 (44) hide show
  1. package/dist/benchmark/index.js +2 -2
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/replication.js +3 -3
  4. package/dist/benchmark/replication.js.map +1 -1
  5. package/dist/src/index.d.ts +46 -32
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +432 -231
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/pid.d.ts.map +1 -1
  10. package/dist/src/pid.js +20 -19
  11. package/dist/src/pid.js.map +1 -1
  12. package/dist/src/ranges.d.ts +13 -3
  13. package/dist/src/ranges.d.ts.map +1 -1
  14. package/dist/src/ranges.js +207 -335
  15. package/dist/src/ranges.js.map +1 -1
  16. package/dist/src/replication-domain-hash.d.ts +5 -0
  17. package/dist/src/replication-domain-hash.d.ts.map +1 -0
  18. package/dist/src/replication-domain-hash.js +30 -0
  19. package/dist/src/replication-domain-hash.js.map +1 -0
  20. package/dist/src/replication-domain-time.d.ts +14 -0
  21. package/dist/src/replication-domain-time.d.ts.map +1 -0
  22. package/dist/src/replication-domain-time.js +59 -0
  23. package/dist/src/replication-domain-time.js.map +1 -0
  24. package/dist/src/replication-domain.d.ts +33 -0
  25. package/dist/src/replication-domain.d.ts.map +1 -0
  26. package/dist/src/replication-domain.js +6 -0
  27. package/dist/src/replication-domain.js.map +1 -0
  28. package/dist/src/replication.d.ts +10 -8
  29. package/dist/src/replication.d.ts.map +1 -1
  30. package/dist/src/replication.js +64 -46
  31. package/dist/src/replication.js.map +1 -1
  32. package/dist/src/role.d.ts +2 -1
  33. package/dist/src/role.d.ts.map +1 -1
  34. package/dist/src/role.js +6 -5
  35. package/dist/src/role.js.map +1 -1
  36. package/package.json +70 -70
  37. package/src/index.ts +609 -317
  38. package/src/pid.ts +20 -19
  39. package/src/ranges.ts +291 -371
  40. package/src/replication-domain-hash.ts +43 -0
  41. package/src/replication-domain-time.ts +85 -0
  42. package/src/replication-domain.ts +50 -0
  43. package/src/replication.ts +50 -46
  44. package/src/role.ts +6 -5
package/src/ranges.ts CHANGED
@@ -1,12 +1,14 @@
1
- import { type PublicSignKey, equals } from "@peerbit/crypto";
1
+ import { PublicSignKey, equals } from "@peerbit/crypto";
2
2
  import {
3
3
  And,
4
+ ByteMatchQuery,
4
5
  Compare,
5
6
  type Index,
6
7
  type IndexIterator,
7
8
  type IndexedResult,
8
9
  type IndexedResults,
9
10
  IntegerCompare,
11
+ Not,
10
12
  Or,
11
13
  type Query,
12
14
  SearchRequest,
@@ -16,95 +18,12 @@ import {
16
18
  iterate,
17
19
  iteratorInSeries,
18
20
  } from "@peerbit/indexer-interface";
21
+ import type { u32 } from "./replication-domain.js";
19
22
  import {
20
- /* getSegmentsFromOffsetAndRange, */
23
+ ReplicationIntent,
21
24
  type ReplicationRangeIndexable,
22
25
  } from "./replication.js";
23
- import { SEGMENT_COORDINATE_SCALE } from "./role.js";
24
-
25
- /*
26
- export const containsPoint = (
27
- rect: { offset: number; length: number },
28
- point: number,
29
- eps = 0.00001 // we do this to handle numerical errors
30
- ) => {
31
- if (rect.factor === 0) {
32
- return false;
33
- }
34
- const start = rect.offset;
35
- const width = rect.factor + eps; // we do this to handle numerical errors. It is better to be more inclusive
36
- const endUnwrapped = rect.offset + width;
37
- let end = endUnwrapped;
38
- let wrapped = false;
39
- if (endUnwrapped > 1) {
40
- end = endUnwrapped % 1;
41
- wrapped = true;
42
- }
43
-
44
- const inFirstInterval = point >= start && point < Math.min(endUnwrapped, 1);
45
- const inSecondInterval =
46
- !inFirstInterval && wrapped && point >= 0 && point < end;
47
-
48
- return inFirstInterval || inSecondInterval;
49
- }; */
50
-
51
- /* const resolveRectsThatContainPoint = async (
52
- rects: Index<ReplicationRangeIndexable>,
53
- point: number,
54
- roleAgeLimit: number,
55
- matured: boolean
56
- ): Promise<ReplicationRangeIndexable[]> => {
57
- // 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
58
- // so we need to query for all ranges that contain the point
59
- const scaledPoint = Math.round(point * SEGMENT_COORDINATE_SCALE)
60
- let queries = [
61
- new IntegerCompare({ key: 'start', compare: Compare.LessOrEqual, value: scaledPoint }),
62
- new IntegerCompare({ key: 'end', compare: Compare.Greater, value: scaledPoint }),
63
- new IntegerCompare({ key: 'timestamp', compare: matured ? Compare.LessOrEqual : Compare.Greater, value: Date.now() - roleAgeLimit })
64
- ]
65
-
66
- const results = await rects.query(new SearchRequest({
67
- query: [
68
- new Nested({
69
- path: 'segments',
70
- query: queries
71
- })
72
- ]
73
- }))
74
- return results.results.map(x => x.value)
75
- } */
76
-
77
- /* const resolveRectsInRange = async (rects: Index<ReplicationRangeIndexable>,
78
- start: number,
79
- end: number,
80
- roleAgeLimit: number,
81
- matured: boolean
82
- ): Promise<ReplicationRangeIndexable[]> => {
83
- // 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
84
- // so we need to query for all ranges that contain the point
85
- let endScaled = Math.round(end * SEGMENT_COORDINATE_SCALE);
86
- let startScaled = Math.round(start * SEGMENT_COORDINATE_SCALE);
87
- let queries = [
88
- new Or([
89
- new And([
90
- new IntegerCompare({ key: 'start1', compare: Compare.Less, value: endScaled }),
91
- new IntegerCompare({ key: 'end1', compare: Compare.GreaterOrEqual, value: startScaled }),
92
- ]),
93
- new And([
94
- new IntegerCompare({ key: 'start2', compare: Compare.Less, value: endScaled }),
95
- new IntegerCompare({ key: 'end2', compare: Compare.GreaterOrEqual, value: startScaled }),
96
- ])
97
- ]),
98
- new IntegerCompare({ key: 'timestamp', compare: matured ? Compare.LessOrEqual : Compare.Greater, value: BigInt(+new Date - roleAgeLimit) })
99
- ]
100
-
101
- const results = await rects.query(new SearchRequest({
102
- query: queries,
103
- sort: [new Sort({ key: "start1" }), new Sort({ key: "start2" })],
104
- fetch: 0xffffffff
105
- }))
106
- return results.results.map(x => x.value)
107
- } */
26
+ import { MAX_U32, scaleToU32 } from "./role.js";
108
27
 
109
28
  const containingPoint = (
110
29
  rects: Index<ReplicationRangeIndexable>,
@@ -114,38 +33,34 @@ const containingPoint = (
114
33
  now: number,
115
34
  options?: {
116
35
  sort?: Sort[];
117
- scaled?: boolean;
118
36
  },
119
37
  ): IndexIterator<ReplicationRangeIndexable> => {
120
38
  // 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
121
- // so we need to query for all ranges that contain the point
122
- let pointScaled = Math.round(
123
- point * (options?.scaled ? 1 : SEGMENT_COORDINATE_SCALE),
124
- );
39
+
125
40
  let queries = [
126
41
  new Or([
127
42
  new And([
128
43
  new IntegerCompare({
129
44
  key: "start1",
130
45
  compare: Compare.LessOrEqual,
131
- value: pointScaled,
46
+ value: point,
132
47
  }),
133
48
  new IntegerCompare({
134
49
  key: "end1",
135
50
  compare: Compare.Greater,
136
- value: pointScaled,
51
+ value: point,
137
52
  }),
138
53
  ]),
139
54
  new And([
140
55
  new IntegerCompare({
141
56
  key: "start2",
142
57
  compare: Compare.LessOrEqual,
143
- value: pointScaled,
58
+ value: point,
144
59
  }),
145
60
  new IntegerCompare({
146
61
  key: "end2",
147
62
  compare: Compare.Greater,
148
- value: pointScaled,
63
+ value: point,
149
64
  }),
150
65
  ]),
151
66
  ]),
@@ -178,11 +93,8 @@ const getClosest = (
178
93
  roleAgeLimit: number,
179
94
  matured: boolean,
180
95
  now: number,
181
- scaled: boolean = false,
96
+ includeStrict: boolean,
182
97
  ): IndexIterator<ReplicationRangeIndexable> => {
183
- const scaledPoint = Math.round(
184
- point * (scaled ? 1 : SEGMENT_COORDINATE_SCALE),
185
- );
186
98
  const createQueries = (p: number, equality: boolean) => {
187
99
  let queries: Query[];
188
100
  if (direction === "below") {
@@ -215,13 +127,23 @@ const getClosest = (
215
127
  queries.push(
216
128
  new IntegerCompare({ key: "width", compare: Compare.Greater, value: 0 }),
217
129
  );
130
+
131
+ if (!includeStrict) {
132
+ queries.push(
133
+ new IntegerCompare({
134
+ key: "mode",
135
+ compare: Compare.Equal,
136
+ value: ReplicationIntent.NonStrict,
137
+ }),
138
+ );
139
+ }
218
140
  return queries;
219
141
  };
220
142
 
221
143
  const iterator = iterate(
222
144
  rects,
223
145
  new SearchRequest({
224
- query: createQueries(scaledPoint, false),
146
+ query: createQueries(point, false),
225
147
  sort:
226
148
  direction === "below"
227
149
  ? new Sort({ key: ["end2"], direction: "desc" })
@@ -231,10 +153,7 @@ const getClosest = (
231
153
  const iteratorWrapped = iterate(
232
154
  rects,
233
155
  new SearchRequest({
234
- query: createQueries(
235
- direction === "below" ? SEGMENT_COORDINATE_SCALE : 0,
236
- true,
237
- ),
156
+ query: createQueries(direction === "below" ? MAX_U32 : 0, true),
238
157
  sort:
239
158
  direction === "below"
240
159
  ? new Sort({ key: ["end2"], direction: "desc" })
@@ -242,11 +161,83 @@ const getClosest = (
242
161
  }),
243
162
  );
244
163
 
245
- return joinIterator(
246
- [iterator, iteratorWrapped],
247
- scaledPoint,
248
- true,
249
- direction,
164
+ return joinIterator([iterator, iteratorWrapped], point, direction);
165
+ };
166
+
167
+ export const hasCoveringRange = async (
168
+ rects: Index<ReplicationRangeIndexable>,
169
+ range: ReplicationRangeIndexable,
170
+ ) => {
171
+ return (
172
+ (await rects.count(
173
+ new SearchRequest({
174
+ query: [
175
+ new Or([
176
+ new And([
177
+ new IntegerCompare({
178
+ key: "start1",
179
+ compare: Compare.LessOrEqual,
180
+ value: range.start1,
181
+ }),
182
+ new IntegerCompare({
183
+ key: "end1",
184
+ compare: Compare.GreaterOrEqual,
185
+ value: range.end1,
186
+ }),
187
+ ]),
188
+ new And([
189
+ new IntegerCompare({
190
+ key: "start2",
191
+ compare: Compare.LessOrEqual,
192
+ value: range.start1,
193
+ }),
194
+ new IntegerCompare({
195
+ key: "end2",
196
+ compare: Compare.GreaterOrEqual,
197
+ value: range.end1,
198
+ }),
199
+ ]),
200
+ ]),
201
+ new Or([
202
+ new And([
203
+ new IntegerCompare({
204
+ key: "start1",
205
+ compare: Compare.LessOrEqual,
206
+ value: range.start2,
207
+ }),
208
+ new IntegerCompare({
209
+ key: "end1",
210
+ compare: Compare.GreaterOrEqual,
211
+ value: range.end2,
212
+ }),
213
+ ]),
214
+ new And([
215
+ new IntegerCompare({
216
+ key: "start2",
217
+ compare: Compare.LessOrEqual,
218
+ value: range.start2,
219
+ }),
220
+ new IntegerCompare({
221
+ key: "end2",
222
+ compare: Compare.GreaterOrEqual,
223
+ value: range.end2,
224
+ }),
225
+ ]),
226
+ ]),
227
+ new StringMatch({
228
+ key: "hash",
229
+ value: range.hash,
230
+ }),
231
+ // assume that we are looking for other ranges, not want to update an existing one
232
+ new Not(
233
+ new ByteMatchQuery({
234
+ key: "id",
235
+ value: range.id,
236
+ }),
237
+ ),
238
+ ],
239
+ }),
240
+ )) > 0
250
241
  );
251
242
  };
252
243
 
@@ -254,7 +245,7 @@ export const getDistance = (
254
245
  from: number,
255
246
  to: number,
256
247
  direction: "above" | "below" | "closest",
257
- end = SEGMENT_COORDINATE_SCALE,
248
+ end = MAX_U32,
258
249
  ) => {
259
250
  // if direction is 'above' only measure distance from 'from to 'to' from above.
260
251
  // i.e if from < to, then from needs to wrap around 0 to 1 and then to to
@@ -292,12 +283,8 @@ export const getDistance = (
292
283
  const joinIterator = (
293
284
  iterators: IndexIterator<ReplicationRangeIndexable>[],
294
285
  point: number,
295
- scaled: boolean,
296
286
  direction: "above" | "below" | "closest",
297
287
  ) => {
298
- const scaledPoint = Math.round(
299
- point * (scaled ? 1 : SEGMENT_COORDINATE_SCALE),
300
- );
301
288
  let queues: {
302
289
  kept: number;
303
290
  elements: {
@@ -331,13 +318,13 @@ const joinIterator = (
331
318
  let dist: number;
332
319
  if (direction === "closest") {
333
320
  dist = Math.min(
334
- getDistance(closest.start1, scaledPoint, direction),
335
- getDistance(closest.end2, scaledPoint, direction),
321
+ getDistance(closest.start1, point, direction),
322
+ getDistance(closest.end2, point, direction),
336
323
  );
337
324
  } else if (direction === "above") {
338
- dist = getDistance(closest.start1, scaledPoint, direction);
325
+ dist = getDistance(closest.start1, point, direction);
339
326
  } else if (direction === "below") {
340
- dist = getDistance(closest.end2, scaledPoint, direction);
327
+ dist = getDistance(closest.end2, point, direction);
341
328
  } else {
342
329
  throw new Error("Invalid direction");
343
330
  }
@@ -401,7 +388,8 @@ const getClosestAround = (
401
388
  point: number,
402
389
  roleAge: number,
403
390
  now: number,
404
- scaled: boolean,
391
+ includeStrictBelow: boolean,
392
+ includeStrictAbove: boolean,
405
393
  ) => {
406
394
  const closestBelow = getClosest(
407
395
  "below",
@@ -410,7 +398,7 @@ const getClosestAround = (
410
398
  roleAge,
411
399
  true,
412
400
  now,
413
- scaled,
401
+ includeStrictBelow,
414
402
  );
415
403
  const closestAbove = getClosest(
416
404
  "above",
@@ -419,15 +407,13 @@ const getClosestAround = (
419
407
  roleAge,
420
408
  true,
421
409
  now,
422
- scaled,
410
+ includeStrictAbove,
423
411
  );
424
- const containing = containingPoint(peers, point, roleAge, true, now, {
425
- scaled: scaled,
426
- });
412
+ const containing = containingPoint(peers, point, roleAge, true, now);
427
413
 
428
414
  return iteratorInSeries(
429
415
  containing,
430
- joinIterator([closestBelow, closestAbove], point, scaled, "closest"),
416
+ joinIterator([closestBelow, closestAbove], point, "closest"),
431
417
  );
432
418
  };
433
419
 
@@ -435,13 +421,11 @@ const collectNodesAroundPoint = async (
435
421
  roleAge: number,
436
422
  peers: Index<ReplicationRangeIndexable>,
437
423
  collector: (rect: ReplicationRangeIndexable, matured: boolean) => void,
438
- point: number,
424
+ point: u32,
439
425
  now: number,
440
426
  done: () => boolean = () => true,
441
427
  ) => {
442
- const containing = containingPoint(peers, point, 0, true, now, {
443
- scaled: false,
444
- });
428
+ const containing = containingPoint(peers, point, 0, true, now);
445
429
 
446
430
  const allContaining = await containing.next(0xffffffff);
447
431
  for (const rect of allContaining.results) {
@@ -457,7 +441,6 @@ const collectNodesAroundPoint = async (
457
441
  const aroundIterator = joinIterator(
458
442
  [closestBelow, closestAbove],
459
443
  point,
460
- false,
461
444
  "closest",
462
445
  );
463
446
  while (aroundIterator.done() === false && done() === false) {
@@ -480,13 +463,12 @@ export const isMatured = (
480
463
  };
481
464
 
482
465
  export const getSamples = async (
483
- cursor: number,
466
+ cursor: u32,
484
467
  peers: Index<ReplicationRangeIndexable>,
485
468
  amount: number,
486
469
  roleAge: number,
487
470
  ) => {
488
471
  const leaders: Set<string> = new Set();
489
- const width = 1;
490
472
  if (!peers) {
491
473
  return [];
492
474
  }
@@ -504,14 +486,13 @@ export const getSamples = async (
504
486
  const maturedLeaders = new Set();
505
487
  for (let i = 0; i < amount; i++) {
506
488
  // evenly distributed
507
- const point = ((cursor + i / amount) % 1) * width;
489
+ const point = Math.round(cursor + (i * MAX_U32) / amount) % MAX_U32;
508
490
 
509
491
  // aquire at least one unique node for each point
510
492
  await collectNodesAroundPoint(
511
493
  roleAge,
512
494
  peers,
513
495
  (rect, m) => {
514
- // console.log(m, rect.start1 / SEGMENT_COORDINATE_SCALE, rect.width / SEGMENT_COORDINATE_SCALE)
515
496
  if (m) {
516
497
  maturedLeaders.add(rect.hash);
517
498
  }
@@ -537,82 +518,50 @@ const fetchOne = async (iterator: IndexIterator<ReplicationRangeIndexable>) => {
537
518
  return value.results[0]?.value;
538
519
  };
539
520
 
521
+ export const minimumWidthToCover = async (
522
+ minReplicas: number /* , replicatorCount: number */,
523
+ ) => {
524
+ /* minReplicas = Math.min(minReplicas, replicatorCount); */ // TODO do we need this?
525
+
526
+ // If min replicas = 2
527
+ // then we need to make sure we cover 0.5 of the total 'width' of the replication space
528
+ // to make sure we reach sufficient amount of nodes such that at least one one has
529
+ // the entry we are looking for
530
+
531
+ let widthToCoverScaled = Math.round(MAX_U32 / minReplicas);
532
+ return widthToCoverScaled;
533
+ };
534
+
540
535
  export const getCoverSet = async (
541
- coveringWidth: number,
542
536
  peers: Index<ReplicationRangeIndexable>,
543
537
  roleAge: number,
544
- startNodeIdentity?: PublicSignKey,
538
+ start: number | PublicSignKey | undefined,
539
+ widthToCoverScaled: number,
540
+ intervalWidth: number = MAX_U32,
545
541
  ): Promise<Set<string>> => {
546
- let now = +new Date();
547
-
548
- // find a good starting point
549
- let startNode: ReplicationRangeIndexable | undefined = undefined;
550
- if (startNodeIdentity) {
551
- // start at our node (local first)
552
- let result = await peers.query(
553
- new SearchRequest({
554
- query: [
555
- new StringMatch({ key: "hash", value: startNodeIdentity.hashcode() }),
556
- ],
557
- fetch: 1,
558
- }),
559
- );
560
- startNode = result.results[0]?.value;
561
-
562
- if (startNode) {
563
- if (!isMatured(startNode, now, roleAge)) {
564
- const matured = await fetchOne(
565
- getClosestAround(peers, startNode.start1, roleAge, now, true),
566
- );
567
- if (matured) {
568
- startNode = matured;
569
- }
570
- }
571
- }
572
- }
573
- let startLocation: number;
574
-
575
- if (!startNode) {
576
- startLocation = Math.random() * SEGMENT_COORDINATE_SCALE;
577
- startNode = await fetchOne(
578
- getClosestAround(peers, startLocation, roleAge, now, true),
579
- );
580
- } else {
581
- // TODO choose start location as the point with the longest range?
582
- startLocation =
583
- startNode.start1 ?? Math.random() * SEGMENT_COORDINATE_SCALE;
584
- }
585
-
586
- if (!startNode) {
587
- return new Set();
588
- }
542
+ const { startNode, startLocation, endLocation } = await getStartAndEnd(
543
+ peers,
544
+ start,
545
+ widthToCoverScaled,
546
+ roleAge,
547
+ Date.now(),
548
+ intervalWidth,
549
+ );
589
550
 
590
551
  let results: ReplicationRangeIndexable[] = [];
591
552
 
592
- let widthToCoverScaled = coveringWidth * SEGMENT_COORDINATE_SCALE;
593
- const endLocation =
594
- (startLocation + widthToCoverScaled) % SEGMENT_COORDINATE_SCALE;
595
- const endIsWrapped = endLocation <= startLocation;
553
+ let now = +new Date();
596
554
 
597
- const endRect =
598
- (await fetchOne(
599
- getClosestAround(peers, endLocation, roleAge, now, true),
600
- )) || (await fetchOne(getClosestAround(peers, endLocation, 0, now, true))); // (await getClosest('above', peers, nextLocation, roleAge, true, 1, true))[0]
555
+ const endIsWrapped = endLocation <= startLocation;
601
556
 
602
- if (!endRect) {
557
+ if (!startNode) {
603
558
  return new Set();
604
559
  }
605
560
 
606
- let current =
607
- /* (await getClosestAround(peers, startLocation, roleAge, 1, true))[0] */ startNode ||
608
- (await fetchOne(getClosestAround(peers, startLocation, 0, now, true))); //(await getClosest('above', peers, startLocation, roleAge, true, 1, true))[0]
609
- let coveredLength = current.width;
610
- let nextLocation = current.end2;
561
+ let current = startNode;
611
562
 
612
563
  // push edges
613
- results.push(endRect);
614
564
  results.push(current);
615
- /* const endIsSameAsStart = equals(endRect.id, current.id); */
616
565
 
617
566
  const resolveNextContaining = async (
618
567
  nextLocation: number,
@@ -620,7 +569,6 @@ export const getCoverSet = async (
620
569
  ) => {
621
570
  let next = await fetchOne(
622
571
  containingPoint(peers, nextLocation, roleAge, true, now, {
623
- scaled: true,
624
572
  sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
625
573
  }),
626
574
  ); // get entersecting sort by largest end2
@@ -649,13 +597,24 @@ export const getCoverSet = async (
649
597
  // fill the middle
650
598
  let wrappedOnce = current.end2 < current.end1;
651
599
 
600
+ let coveredLength = 0;
601
+ const addLength = (from: number) => {
602
+ if (current.end2 < from || current.wrapped) {
603
+ wrappedOnce = true;
604
+ coveredLength += MAX_U32 - from;
605
+ coveredLength += current.end2;
606
+ } else {
607
+ coveredLength += current.end1 - from;
608
+ }
609
+ };
610
+ addLength(startLocation);
611
+
652
612
  let maturedCoveredLength = coveredLength;
653
- /* let lastMatured = isMatured(startNode, now, roleAge) ? startNode : undefined;
654
- */
613
+ let nextLocation = current.end2;
655
614
 
656
615
  while (
657
- maturedCoveredLength < widthToCoverScaled &&
658
- coveredLength <= SEGMENT_COORDINATE_SCALE
616
+ maturedCoveredLength < widthToCoverScaled && // eslint-disable-line no-unmodified-loop-condition
617
+ coveredLength <= MAX_U32 // eslint-disable-line no-unmodified-loop-condition
659
618
  ) {
660
619
  let nextCandidate = await resolveNext(nextLocation, roleAge);
661
620
  /* let fromAbove = false; */
@@ -671,53 +630,44 @@ export const getCoverSet = async (
671
630
  break;
672
631
  }
673
632
 
633
+ let nextIsCurrent = equals(nextCandidate[0].id, current.id);
634
+ if (nextIsCurrent) {
635
+ break;
636
+ }
637
+ let last = current;
674
638
  current = nextCandidate[0];
675
639
 
676
640
  let distanceBefore = coveredLength;
677
641
 
678
- if (current.end2 < nextLocation) {
679
- wrappedOnce = true;
680
- coveredLength += SEGMENT_COORDINATE_SCALE - nextLocation;
681
- coveredLength += current.end2;
682
- } else {
683
- coveredLength += current.end1 - nextLocation;
684
- }
642
+ addLength(nextLocation);
685
643
 
686
644
  let isLast =
687
645
  distanceBefore < widthToCoverScaled &&
688
646
  coveredLength >= widthToCoverScaled;
647
+
689
648
  if (
690
- (isLast &&
691
- !nextCandidate[1]) /* || Math.min(current.start1, current.start2) > Math.min(endRect.start1, endRect.start2) */ /* (Math.min(current.start1, current.start2) > endLocation) */ ||
692
- equals(endRect.id, current.id)
649
+ !isLast ||
650
+ nextCandidate[1] ||
651
+ Math.min(
652
+ getDistance(last.start1, endLocation, "closest"),
653
+ getDistance(last.end2, endLocation, "closest"),
654
+ ) >
655
+ Math.min(
656
+ getDistance(current.start1, endLocation, "closest"),
657
+ getDistance(current.end2, endLocation, "closest"),
658
+ )
693
659
  ) {
694
- /* if ((isLast && ((endIsWrapped && wrappedOnce) || (!endIsWrapped))) && (current.start1 > endLocation || equals(current.id, endRect.id))) { */
695
- // this is the end!
696
- /* if (lastMatured && lastMatured.distanceTo(endLocation) < current.distanceTo(endLocation)) {
697
- breaks;
698
- } */
699
- break;
660
+ results.push(current);
700
661
  }
701
662
 
702
- /* if (fromAbove && next && next.start1 > endRect.start1 && (endIsWrapped === false || wrappedOnce)) {
663
+ if (isLast && !nextCandidate[1] /* || equals(endRect.id, current.id) */) {
703
664
  break;
704
- } */
705
-
706
- // this is a skip condition to not include too many rects
665
+ }
707
666
 
708
667
  if (matured) {
709
668
  maturedCoveredLength = coveredLength;
710
- /* lastMatured = current; */
711
669
  }
712
670
 
713
- results.push(current);
714
- /*
715
-
716
- if (current.start1 > endLocation && (wrappedOnce || !endIsWrapped)) {
717
- break;
718
- }
719
-
720
- */
721
671
  nextLocation = endIsWrapped
722
672
  ? wrappedOnce
723
673
  ? Math.min(current.end2, endLocation)
@@ -726,153 +676,123 @@ export const getCoverSet = async (
726
676
  }
727
677
 
728
678
  const res = new Set(results.map((x) => x.hash));
729
- startNodeIdentity && res.add(startNodeIdentity.hashcode());
679
+
680
+ start instanceof PublicSignKey && res.add(start.hashcode());
730
681
  return res;
682
+ };
731
683
 
732
- //
733
- /* const set: Set<string> = new Set();
734
- let currentNode = startNode;
735
- const t = +new Date();
736
-
737
- let wrappedOnce = false;
738
- const startPoint = startNode.segment.offset;
739
-
740
- const getNextPoint = (): [number, number, number, boolean] => {
741
- let nextPoint =
742
- currentNode.segment.offset + currentNode.segment.factor;
743
-
744
- if (nextPoint > 1 || nextPoint < startPoint) {
745
- wrappedOnce = true;
746
- }
747
-
748
- nextPoint = nextPoint % 1;
749
- let distanceStart: number;
750
-
751
- if (wrappedOnce) {
752
- distanceStart = (1 - startPoint + currentNode.segment.offset) % 1;
753
- } else {
754
- distanceStart = (currentNode.segment.offset - startPoint) % 1;
755
- }
756
-
757
- const distanceEnd = distanceStart + currentNode.segment.factor;
758
-
759
- return [nextPoint, distanceStart, distanceEnd, wrappedOnce];
760
- };
761
-
762
- const getNextMatured = async (from: ReplicatorRect) => {
763
- let next = (await peers.query(new SearchRequest({ query: [new IntegerCompare({ key: ['segment', 'offset'], compare: Compare.Greater, value: from.segment.offset })], fetch: 1 })))?.results[0]?.value // (from.next || peers.head)!;
764
- while (
765
- next.hash !== from.hash &&
766
- next.hash !== startNode.hash
767
- ) {
768
- if (isMatured(next.segment, t, roleAge)) {
769
- return next;
770
- }
771
- next = (next.next || peers.head)!;
772
- }
773
- return undefined;
774
- }; */
775
-
776
- /**
777
- * The purpose of this loop is to cover at least coveringWidth
778
- * so that if we query all nodes in this range, we know we will
779
- * "query" all data in that range
780
- */
781
-
782
- /* let isPastThePoint = false;
783
- outer: while (currentNode) {
784
- if (set.has(currentNode.hash)) break;
785
-
786
- const [nextPoint, distanceStart, distanceEnd, wrapped] = getNextPoint();
787
-
788
- if (distanceStart <= coveringWidth) {
789
- set.add(currentNode.hash);
790
- }
791
-
792
- if (distanceEnd >= coveringWidth) {
793
- break;
794
- }
795
-
796
- let next = currentNode.next || peers.head;
797
- while (next) {
798
- if (next.value.publicKey.equals(startNode.value.publicKey)) {
799
- break outer;
800
- }
801
-
802
- const prevOffset = (next.prev || peers.tail)!.value.role.offset;
803
- const nextOffset = next.value.role.offset;
804
- const nextHasWrapped = nextOffset < prevOffset;
805
-
806
- if (
807
- (!wrapped && nextOffset > nextPoint) ||
808
- (nextHasWrapped &&
809
- (wrapped ? nextOffset > nextPoint : prevOffset < nextPoint)) ||
810
- (!nextHasWrapped && prevOffset < nextPoint && nextPoint <= nextOffset)
811
- ) {
812
- isPastThePoint = true;
813
- }
814
-
815
- if (isPastThePoint) {
816
- break; // include this next in the set;
817
- }
818
-
819
- const overlapsRange = containsPoint(next.value.role, nextPoint);
820
-
821
- if (overlapsRange) {
822
- // Find out if there is a better choice ahead of us
823
- const nextNext = await getNextMatured(next);
824
- if (
825
- nextNext &&
826
- nextNext.hash === currentNode.hash &&
827
- nextNext.segment.offset < nextPoint &&
828
- nextNext.segment.offset + nextNext.segment.factor > nextPoint
829
- ) {
830
- // nextNext is better (continue to iterate)
831
- } else {
832
- // done
833
- break;
834
- }
835
- } else {
836
- // (continue to iterate)
684
+ export const fetchOneFromPublicKey = async (
685
+ publicKey: PublicSignKey,
686
+ index: Index<ReplicationRangeIndexable>,
687
+ roleAge: number,
688
+ now: number,
689
+ ) => {
690
+ let result = await index.query(
691
+ new SearchRequest({
692
+ query: [new StringMatch({ key: "hash", value: publicKey.hashcode() })],
693
+ fetch: 1,
694
+ }),
695
+ );
696
+ let node = result.results[0]?.value;
697
+ if (node) {
698
+ if (!isMatured(node, now, roleAge)) {
699
+ const matured = await fetchOne(
700
+ getClosestAround(index, node.start1, roleAge, now, false, false),
701
+ );
702
+ if (matured) {
703
+ node = matured;
837
704
  }
838
-
839
- next = next.next || peers.head;
840
705
  }
841
- currentNode = next!;
842
- } */
843
-
844
- // collect 1 point around the boundary of the start and one at the end,
845
- // preferrd matured and that we already have it
846
- /* for (const point of [
847
- startNode.segment.offset,
848
- (startNode.segment.offset + coveringWidth) % 1
849
- ]) {
850
- let done = false;
851
- const unmatured: string[] = [];
852
- collectNodesAroundPoint(
853
- t,
854
- roleAge,
706
+ }
707
+ return node;
708
+ };
709
+
710
+ export const getStartAndEnd = async (
711
+ peers: Index<ReplicationRangeIndexable>,
712
+ start: number | PublicSignKey | undefined | undefined,
713
+ widthToCoverScaled: number,
714
+ roleAge: number,
715
+ now: number,
716
+ intervalWidth: number,
717
+ ) => {
718
+ // find a good starting point
719
+ let startNode: ReplicationRangeIndexable | undefined = undefined;
720
+ let startLocation: number | undefined = undefined;
721
+
722
+ const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
723
+ startLocation = point;
724
+ startNode = await fetchOneClosest(
855
725
  peers,
856
- isMatured(startNode.segment, t, roleAge) ? startNode : peers.head, // start at startNode is matured, else start at head (we only seek to find one matured node at the point)
857
- (rect, matured) => {
858
- if (matured) {
859
- if (set.has(rect.hash)) {
860
- // great!
861
- } else {
862
- set.add(rect.hash);
863
- }
864
- done = true;
865
- } else {
866
- unmatured.push(rect.hash);
867
- }
868
- },
869
- point,
870
- () => done
726
+ startLocation,
727
+ roleAge,
728
+ now,
729
+ false,
730
+ true,
871
731
  );
872
- if (!done && unmatured.length > 0) {
873
- set.add(unmatured[0]);
874
- // TODO add more elements?
732
+ };
733
+
734
+ if (start instanceof PublicSignKey) {
735
+ // start at our node (local first)
736
+ startNode = await fetchOneFromPublicKey(start, peers, roleAge, now);
737
+ if (!startNode) {
738
+ // fetch randomly
739
+ await nodeFromPoint();
740
+ } else {
741
+ startLocation = startNode.start1;
875
742
  }
743
+ } else if (typeof start === "number") {
744
+ await nodeFromPoint(start);
745
+ } else {
746
+ await nodeFromPoint();
747
+ }
748
+
749
+ if (!startNode || startLocation == null) {
750
+ return { startNode: undefined, startLocation: 0, endLocation: 0 };
751
+ }
752
+
753
+ let endLocation = startLocation + widthToCoverScaled;
754
+ if (intervalWidth != null) {
755
+ endLocation = endLocation % intervalWidth;
876
756
  }
877
- return set; */
757
+
758
+ // if start location is after endLocation and startNode is strict then return undefined because this is not a node we want to choose
759
+ let coveredDistanceToStart = 0;
760
+ if (startNode.start1 < startLocation) {
761
+ coveredDistanceToStart += intervalWidth - startLocation + startNode.start1;
762
+ } else {
763
+ coveredDistanceToStart += startNode.start1 - startLocation;
764
+ }
765
+
766
+ if (
767
+ startNode.mode === ReplicationIntent.Strict &&
768
+ coveredDistanceToStart > widthToCoverScaled
769
+ ) {
770
+ return { startNode: undefined, startLocation: 0, endLocation: 0 };
771
+ }
772
+
773
+ return {
774
+ startNode,
775
+ startLocation: Math.round(startLocation),
776
+ endLocation: Math.round(endLocation),
777
+ };
778
+ };
779
+
780
+ export const fetchOneClosest = (
781
+ peers: Index<ReplicationRangeIndexable>,
782
+ point: number,
783
+ roleAge: number,
784
+ now: number,
785
+ includeStrictBelow: boolean,
786
+ includeStrictAbove: boolean,
787
+ ) => {
788
+ return fetchOne(
789
+ getClosestAround(
790
+ peers,
791
+ point,
792
+ roleAge,
793
+ now,
794
+ includeStrictBelow,
795
+ includeStrictAbove,
796
+ ),
797
+ );
878
798
  };