@peerbit/shared-log 9.0.10 → 9.1.0-57b8640

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 +51 -31
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +435 -230
  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 +22 -3
  13. package/dist/src/ranges.d.ts.map +1 -1
  14. package/dist/src/ranges.js +231 -336
  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 +621 -312
  38. package/src/pid.ts +20 -19
  39. package/src/ranges.ts +328 -375
  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,84 @@ const fetchOne = async (iterator: IndexIterator<ReplicationRangeIndexable>) => {
537
518
  return value.results[0]?.value;
538
519
  };
539
520
 
540
- export const getCoverSet = async (
541
- coveringWidth: number,
542
- peers: Index<ReplicationRangeIndexable>,
543
- roleAge: number,
544
- startNodeIdentity?: PublicSignKey,
545
- ): Promise<Set<string>> => {
546
- let now = +new Date();
521
+ export const minimumWidthToCover = async (
522
+ minReplicas: number /* , replicatorCount: number */,
523
+ ) => {
524
+ /* minReplicas = Math.min(minReplicas, replicatorCount); */ // TODO do we need this?
547
525
 
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(
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
+
535
+ export const getCoverSet = async (properties: {
536
+ peers: Index<ReplicationRangeIndexable>;
537
+ start: number | PublicSignKey | undefined;
538
+ widthToCoverScaled: number;
539
+ roleAge: number;
540
+ intervalWidth?: number;
541
+ eager?:
542
+ | {
543
+ unmaturedFetchCoverSize?: number;
544
+ }
545
+ | boolean;
546
+ }): Promise<Set<string>> => {
547
+ let intervalWidth: number = properties.intervalWidth ?? MAX_U32;
548
+ const { peers, start, widthToCoverScaled, roleAge } = properties;
549
+
550
+ const now = Date.now();
551
+ const { startNode, startLocation, endLocation } = await getStartAndEnd(
552
+ peers,
553
+ start,
554
+ widthToCoverScaled,
555
+ roleAge,
556
+ now,
557
+ intervalWidth,
558
+ );
559
+
560
+ let ret = new Set<string>();
561
+
562
+ // if start node (assume is self) and not mature, ask all known remotes if limited
563
+ // TODO consider a more robust stragety here in a scenario where there are many nodes, lets say
564
+ // a social media app with 1m user, then it does not makes sense to query "all" just because we started
565
+ if (properties.eager) {
566
+ const eagerFetch =
567
+ properties.eager === true
568
+ ? 1000
569
+ : properties.eager.unmaturedFetchCoverSize;
570
+
571
+ // pull all umatured
572
+ const rects = await peers.query(
553
573
  new SearchRequest({
574
+ fetch: eagerFetch,
554
575
  query: [
555
- new StringMatch({ key: "hash", value: startNodeIdentity.hashcode() }),
576
+ new IntegerCompare({
577
+ key: "timestamp",
578
+ compare: Compare.GreaterOrEqual,
579
+ value: BigInt(now - roleAge),
580
+ }),
556
581
  ],
557
- fetch: 1,
558
582
  }),
559
583
  );
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
- }
584
+ for (const rect of rects.results) {
585
+ ret.add(rect.value.hash);
571
586
  }
572
587
  }
573
- let startLocation: number;
574
588
 
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
- }
589
-
590
- let results: ReplicationRangeIndexable[] = [];
591
-
592
- let widthToCoverScaled = coveringWidth * SEGMENT_COORDINATE_SCALE;
593
- const endLocation =
594
- (startLocation + widthToCoverScaled) % SEGMENT_COORDINATE_SCALE;
595
589
  const endIsWrapped = endLocation <= startLocation;
596
590
 
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]
601
-
602
- if (!endRect) {
603
- return new Set();
591
+ if (!startNode) {
592
+ return ret;
604
593
  }
605
594
 
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;
595
+ let current = startNode;
611
596
 
612
597
  // push edges
613
- results.push(endRect);
614
- results.push(current);
615
- /* const endIsSameAsStart = equals(endRect.id, current.id); */
598
+ ret.add(current.hash);
616
599
 
617
600
  const resolveNextContaining = async (
618
601
  nextLocation: number,
@@ -620,7 +603,6 @@ export const getCoverSet = async (
620
603
  ) => {
621
604
  let next = await fetchOne(
622
605
  containingPoint(peers, nextLocation, roleAge, true, now, {
623
- scaled: true,
624
606
  sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
625
607
  }),
626
608
  ); // get entersecting sort by largest end2
@@ -649,13 +631,25 @@ export const getCoverSet = async (
649
631
  // fill the middle
650
632
  let wrappedOnce = current.end2 < current.end1;
651
633
 
652
- let maturedCoveredLength = coveredLength;
653
- /* let lastMatured = isMatured(startNode, now, roleAge) ? startNode : undefined;
654
- */
634
+ let coveredLength = 0;
635
+ const addLength = (from: number) => {
636
+ if (current.end2 < from || current.wrapped) {
637
+ wrappedOnce = true;
638
+ coveredLength += MAX_U32 - from;
639
+ coveredLength += current.end2;
640
+ } else {
641
+ coveredLength += current.end1 - from;
642
+ }
643
+ };
644
+ addLength(startLocation);
645
+
646
+ let maturedCoveredLength =
647
+ coveredLength; /* TODO only increase matured length when startNode is matured? i.e. do isMatured(startNode, now, roleAge) ? coveredLength : 0; */
648
+ let nextLocation = current.end2;
655
649
 
656
650
  while (
657
- maturedCoveredLength < widthToCoverScaled &&
658
- coveredLength <= SEGMENT_COORDINATE_SCALE
651
+ maturedCoveredLength < widthToCoverScaled && // eslint-disable-line no-unmodified-loop-condition
652
+ coveredLength <= MAX_U32 // eslint-disable-line no-unmodified-loop-condition
659
653
  ) {
660
654
  let nextCandidate = await resolveNext(nextLocation, roleAge);
661
655
  /* let fromAbove = false; */
@@ -671,53 +665,44 @@ export const getCoverSet = async (
671
665
  break;
672
666
  }
673
667
 
668
+ let nextIsCurrent = equals(nextCandidate[0].id, current.id);
669
+ if (nextIsCurrent) {
670
+ break;
671
+ }
672
+ let last = current;
674
673
  current = nextCandidate[0];
675
674
 
676
675
  let distanceBefore = coveredLength;
677
676
 
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
- }
677
+ addLength(nextLocation);
685
678
 
686
679
  let isLast =
687
680
  distanceBefore < widthToCoverScaled &&
688
681
  coveredLength >= widthToCoverScaled;
682
+
689
683
  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)
684
+ !isLast ||
685
+ nextCandidate[1] ||
686
+ Math.min(
687
+ getDistance(last.start1, endLocation, "closest"),
688
+ getDistance(last.end2, endLocation, "closest"),
689
+ ) >
690
+ Math.min(
691
+ getDistance(current.start1, endLocation, "closest"),
692
+ getDistance(current.end2, endLocation, "closest"),
693
+ )
693
694
  ) {
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;
695
+ ret.add(current.hash);
700
696
  }
701
697
 
702
- /* if (fromAbove && next && next.start1 > endRect.start1 && (endIsWrapped === false || wrappedOnce)) {
698
+ if (isLast && !nextCandidate[1] /* || equals(endRect.id, current.id) */) {
703
699
  break;
704
- } */
705
-
706
- // this is a skip condition to not include too many rects
700
+ }
707
701
 
708
702
  if (matured) {
709
703
  maturedCoveredLength = coveredLength;
710
- /* lastMatured = current; */
711
704
  }
712
705
 
713
- results.push(current);
714
- /*
715
-
716
- if (current.start1 > endLocation && (wrappedOnce || !endIsWrapped)) {
717
- break;
718
- }
719
-
720
- */
721
706
  nextLocation = endIsWrapped
722
707
  ? wrappedOnce
723
708
  ? Math.min(current.end2, endLocation)
@@ -725,154 +710,122 @@ export const getCoverSet = async (
725
710
  : Math.min(current.end2, endLocation);
726
711
  }
727
712
 
728
- const res = new Set(results.map((x) => x.hash));
729
- startNodeIdentity && res.add(startNodeIdentity.hashcode());
730
- return res;
731
-
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)
713
+ start instanceof PublicSignKey && ret.add(start.hashcode());
714
+ return ret;
715
+ };
716
+
717
+ export const fetchOneFromPublicKey = async (
718
+ publicKey: PublicSignKey,
719
+ index: Index<ReplicationRangeIndexable>,
720
+ roleAge: number,
721
+ now: number,
722
+ ) => {
723
+ let result = await index.query(
724
+ new SearchRequest({
725
+ query: [new StringMatch({ key: "hash", value: publicKey.hashcode() })],
726
+ fetch: 1,
727
+ }),
728
+ );
729
+ let node = result.results[0]?.value;
730
+ if (node) {
731
+ if (!isMatured(node, now, roleAge)) {
732
+ const matured = await fetchOne(
733
+ getClosestAround(index, node.start1, roleAge, now, false, false),
734
+ );
735
+ if (matured) {
736
+ node = matured;
837
737
  }
838
-
839
- next = next.next || peers.head;
840
738
  }
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,
739
+ }
740
+ return node;
741
+ };
742
+
743
+ export const getStartAndEnd = async (
744
+ peers: Index<ReplicationRangeIndexable>,
745
+ start: number | PublicSignKey | undefined | undefined,
746
+ widthToCoverScaled: number,
747
+ roleAge: number,
748
+ now: number,
749
+ intervalWidth: number,
750
+ ) => {
751
+ // find a good starting point
752
+ let startNode: ReplicationRangeIndexable | undefined = undefined;
753
+ let startLocation: number | undefined = undefined;
754
+
755
+ const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
756
+ startLocation = point;
757
+ startNode = await fetchOneClosest(
855
758
  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
759
+ startLocation,
760
+ roleAge,
761
+ now,
762
+ false,
763
+ true,
871
764
  );
872
- if (!done && unmatured.length > 0) {
873
- set.add(unmatured[0]);
874
- // TODO add more elements?
765
+ };
766
+
767
+ if (start instanceof PublicSignKey) {
768
+ // start at our node (local first)
769
+ startNode = await fetchOneFromPublicKey(start, peers, roleAge, now);
770
+ if (!startNode) {
771
+ // fetch randomly
772
+ await nodeFromPoint();
773
+ } else {
774
+ startLocation = startNode.start1;
875
775
  }
776
+ } else if (typeof start === "number") {
777
+ await nodeFromPoint(start);
778
+ } else {
779
+ await nodeFromPoint();
876
780
  }
877
- return set; */
781
+
782
+ if (!startNode || startLocation == null) {
783
+ return { startNode: undefined, startLocation: 0, endLocation: 0 };
784
+ }
785
+
786
+ let endLocation = startLocation + widthToCoverScaled;
787
+ if (intervalWidth != null) {
788
+ endLocation = endLocation % intervalWidth;
789
+ }
790
+
791
+ // if start location is after endLocation and startNode is strict then return undefined because this is not a node we want to choose
792
+ let coveredDistanceToStart = 0;
793
+ if (startNode.start1 < startLocation) {
794
+ coveredDistanceToStart += intervalWidth - startLocation + startNode.start1;
795
+ } else {
796
+ coveredDistanceToStart += startNode.start1 - startLocation;
797
+ }
798
+
799
+ if (
800
+ startNode.mode === ReplicationIntent.Strict &&
801
+ coveredDistanceToStart > widthToCoverScaled
802
+ ) {
803
+ return { startNode: undefined, startLocation: 0, endLocation: 0 };
804
+ }
805
+
806
+ return {
807
+ startNode,
808
+ startLocation: Math.round(startLocation),
809
+ endLocation: Math.round(endLocation),
810
+ };
811
+ };
812
+
813
+ export const fetchOneClosest = (
814
+ peers: Index<ReplicationRangeIndexable>,
815
+ point: number,
816
+ roleAge: number,
817
+ now: number,
818
+ includeStrictBelow: boolean,
819
+ includeStrictAbove: boolean,
820
+ ) => {
821
+ return fetchOne(
822
+ getClosestAround(
823
+ peers,
824
+ point,
825
+ roleAge,
826
+ now,
827
+ includeStrictBelow,
828
+ includeStrictAbove,
829
+ ),
830
+ );
878
831
  };