@peerbit/shared-log 9.1.2 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/benchmark/get-samples.js +2 -3
  2. package/dist/benchmark/get-samples.js.map +1 -1
  3. package/dist/benchmark/index.js +4 -6
  4. package/dist/benchmark/index.js.map +1 -1
  5. package/dist/benchmark/memory/child.d.ts +2 -0
  6. package/dist/benchmark/memory/child.d.ts.map +1 -0
  7. package/dist/benchmark/memory/child.js +149 -0
  8. package/dist/benchmark/memory/child.js.map +1 -0
  9. package/dist/benchmark/memory/index.d.ts +2 -0
  10. package/dist/benchmark/memory/index.d.ts.map +1 -0
  11. package/dist/benchmark/memory/index.js +81 -0
  12. package/dist/benchmark/memory/index.js.map +1 -0
  13. package/dist/benchmark/memory/utils.d.ts +13 -0
  14. package/dist/benchmark/memory/utils.d.ts.map +1 -0
  15. package/dist/benchmark/memory/utils.js +2 -0
  16. package/dist/benchmark/memory/utils.js.map +1 -0
  17. package/dist/benchmark/replication-prune.js +27 -25
  18. package/dist/benchmark/replication-prune.js.map +1 -1
  19. package/dist/benchmark/replication.js +15 -16
  20. package/dist/benchmark/replication.js.map +1 -1
  21. package/dist/src/debounce.d.ts +25 -0
  22. package/dist/src/debounce.d.ts.map +1 -0
  23. package/dist/src/debounce.js +130 -0
  24. package/dist/src/debounce.js.map +1 -0
  25. package/dist/src/index.d.ts +55 -21
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +867 -390
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/pid.d.ts.map +1 -1
  30. package/dist/src/pid.js +23 -21
  31. package/dist/src/pid.js.map +1 -1
  32. package/dist/src/ranges.d.ts +104 -8
  33. package/dist/src/ranges.d.ts.map +1 -1
  34. package/dist/src/ranges.js +518 -76
  35. package/dist/src/ranges.js.map +1 -1
  36. package/dist/src/replication-domain-hash.d.ts.map +1 -1
  37. package/dist/src/replication-domain-hash.js.map +1 -1
  38. package/dist/src/replication-domain-time.d.ts.map +1 -1
  39. package/dist/src/replication-domain-time.js.map +1 -1
  40. package/dist/src/replication-domain.d.ts +22 -2
  41. package/dist/src/replication-domain.d.ts.map +1 -1
  42. package/dist/src/replication-domain.js +33 -0
  43. package/dist/src/replication-domain.js.map +1 -1
  44. package/dist/src/replication.d.ts +1 -55
  45. package/dist/src/replication.d.ts.map +1 -1
  46. package/dist/src/replication.js +5 -215
  47. package/dist/src/replication.js.map +1 -1
  48. package/dist/src/role.d.ts +1 -0
  49. package/dist/src/role.d.ts.map +1 -1
  50. package/dist/src/role.js +1 -0
  51. package/dist/src/role.js.map +1 -1
  52. package/dist/src/utils.d.ts +6 -0
  53. package/dist/src/utils.d.ts.map +1 -0
  54. package/dist/src/utils.js +39 -0
  55. package/dist/src/utils.js.map +1 -0
  56. package/package.json +5 -5
  57. package/src/debounce.ts +172 -0
  58. package/src/index.ts +1282 -562
  59. package/src/pid.ts +27 -25
  60. package/src/ranges.ts +794 -181
  61. package/src/replication-domain-hash.ts +3 -1
  62. package/src/replication-domain-time.ts +2 -1
  63. package/src/replication-domain.ts +68 -5
  64. package/src/replication.ts +9 -235
  65. package/src/role.ts +1 -0
  66. package/src/utils.ts +49 -0
package/src/ranges.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { PublicSignKey, equals } from "@peerbit/crypto";
1
+ import { deserialize, field, serialize, variant } from "@dao-xyz/borsh";
2
+ import { PublicSignKey, equals, randomBytes, toBase64 } from "@peerbit/crypto";
2
3
  import {
3
4
  And,
5
+ BoolQuery,
4
6
  ByteMatchQuery,
5
7
  Compare,
6
8
  type Index,
@@ -11,30 +13,416 @@ import {
11
13
  Not,
12
14
  Or,
13
15
  type Query,
14
- SearchRequest,
16
+ type ReturnTypeFromShape,
17
+ type Shape,
15
18
  Sort,
16
19
  SortDirection,
17
20
  StringMatch,
18
- iterate,
19
21
  iteratorInSeries,
20
22
  } from "@peerbit/indexer-interface";
21
- import type { u32 } from "./replication-domain.js";
22
- import {
23
- ReplicationIntent,
24
- type ReplicationRangeIndexable,
25
- } from "./replication.js";
23
+ import { id } from "@peerbit/indexer-interface";
24
+ import { Meta, ShallowMeta } from "@peerbit/log";
25
+ import { type ReplicationChanges, type u32 } from "./replication-domain.js";
26
26
  import { MAX_U32, scaleToU32 } from "./role.js";
27
+ import { groupByGidSync } from "./utils.js";
28
+
29
+ export enum ReplicationIntent {
30
+ NonStrict = 0, // indicates that the segment will be replicated and nearby data might be replicated as well
31
+ Strict = 1, // only replicate data in the segment to the specified replicator, not any other data
32
+ }
33
+
34
+ export const getSegmentsFromOffsetAndRange = (
35
+ offset: number,
36
+ factor: number,
37
+ ): [[number, number], [number, number]] => {
38
+ let start1 = offset;
39
+ let end1Unscaled = offset + factor; // only add factor if it is not 1 to prevent numerical issues (like (0.9 + 1) % 1 => 0.8999999)
40
+ let end1 = Math.min(end1Unscaled, MAX_U32);
41
+ return [
42
+ [start1, end1],
43
+ end1Unscaled > MAX_U32
44
+ ? [0, (factor !== MAX_U32 ? offset + factor : offset) % MAX_U32]
45
+ : [start1, end1],
46
+ ];
47
+ };
48
+
49
+ export const shouldAssigneToRangeBoundary = (
50
+ leaders:
51
+ | Map<
52
+ string,
53
+ {
54
+ intersecting: boolean;
55
+ }
56
+ >
57
+ | false,
58
+ ) => {
59
+ let assignedToRangeBoundary = leaders === false || leaders.size === 0;
60
+ if (!assignedToRangeBoundary && leaders) {
61
+ for (const [_, { intersecting }] of leaders) {
62
+ if (!intersecting) {
63
+ assignedToRangeBoundary = true;
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ return assignedToRangeBoundary;
69
+ };
70
+ export class EntryReplicated {
71
+ @id({ type: "string" })
72
+ id: string; // hash + coordinate
73
+
74
+ @field({ type: "string" })
75
+ hash: string;
76
+
77
+ @field({ type: "string" })
78
+ gid: string;
79
+
80
+ @field({ type: "u32" })
81
+ coordinate: number;
82
+
83
+ @field({ type: "u64" })
84
+ wallTime: bigint;
85
+
86
+ @field({ type: "bool" })
87
+ assignedToRangeBoundary: boolean;
88
+
89
+ @field({ type: Uint8Array })
90
+ private _meta: Uint8Array;
91
+
92
+ private _metaResolved: ShallowMeta;
93
+
94
+ constructor(properties: {
95
+ coordinate: number;
96
+ hash: string;
97
+ meta: Meta;
98
+ assignedToRangeBoundary: boolean;
99
+ }) {
100
+ this.coordinate = properties.coordinate;
101
+ this.hash = properties.hash;
102
+ this.gid = properties.meta.gid;
103
+ this.id = this.hash + "-" + this.coordinate;
104
+ this.wallTime = properties.meta.clock.timestamp.wallTime;
105
+ const shallow =
106
+ properties.meta instanceof Meta
107
+ ? new ShallowMeta(properties.meta)
108
+ : properties.meta;
109
+ this._meta = serialize(shallow);
110
+ this._metaResolved = deserialize(this._meta, ShallowMeta);
111
+ this._metaResolved = properties.meta;
112
+ this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
113
+ }
114
+
115
+ get meta(): ShallowMeta {
116
+ if (!this._metaResolved) {
117
+ this._metaResolved = deserialize(this._meta, ShallowMeta);
118
+ }
119
+ return this._metaResolved;
120
+ }
121
+ }
122
+
123
+ @variant(0)
124
+ export class ReplicationRange {
125
+ @field({ type: Uint8Array })
126
+ id: Uint8Array;
127
+
128
+ @field({ type: "u64" })
129
+ timestamp: bigint;
130
+
131
+ @field({ type: "u32" })
132
+ private _offset: number;
133
+
134
+ @field({ type: "u32" })
135
+ private _factor: number;
136
+
137
+ @field({ type: "u8" })
138
+ mode: ReplicationIntent;
139
+
140
+ constructor(properties: {
141
+ id: Uint8Array;
142
+ offset: number;
143
+ factor: number;
144
+ timestamp: bigint;
145
+ mode: ReplicationIntent;
146
+ }) {
147
+ const { id, offset, factor, timestamp, mode } = properties;
148
+ this.id = id;
149
+ this._offset = offset;
150
+ this._factor = factor;
151
+ this.timestamp = timestamp;
152
+ this.mode = mode;
153
+ }
154
+
155
+ get factor(): number {
156
+ return this._factor;
157
+ }
158
+
159
+ get offset(): number {
160
+ return this._offset;
161
+ }
162
+
163
+ toReplicationRangeIndexable(key: PublicSignKey): ReplicationRangeIndexable {
164
+ return new ReplicationRangeIndexable({
165
+ id: this.id,
166
+ publicKeyHash: key.hashcode(),
167
+ offset: this.offset,
168
+ length: this.factor,
169
+ timestamp: this.timestamp,
170
+ mode: this.mode,
171
+ });
172
+ }
173
+ }
174
+
175
+ export class ReplicationRangeIndexable {
176
+ @id({ type: Uint8Array })
177
+ id: Uint8Array;
178
+
179
+ @field({ type: "string" })
180
+ hash: string;
181
+
182
+ @field({ type: "u64" })
183
+ timestamp: bigint;
184
+
185
+ @field({ type: "u32" })
186
+ start1!: number;
187
+
188
+ @field({ type: "u32" })
189
+ end1!: number;
190
+
191
+ @field({ type: "u32" })
192
+ start2!: number;
193
+
194
+ @field({ type: "u32" })
195
+ end2!: number;
196
+
197
+ @field({ type: "u32" })
198
+ width!: number;
199
+
200
+ @field({ type: "u8" })
201
+ mode: ReplicationIntent;
27
202
 
28
- const containingPoint = (
203
+ constructor(
204
+ properties: {
205
+ id?: Uint8Array;
206
+ normalized?: boolean;
207
+ offset: number;
208
+ length: number;
209
+ mode?: ReplicationIntent;
210
+ timestamp?: bigint;
211
+ } & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
212
+ ) {
213
+ this.id = properties.id ?? randomBytes(32);
214
+ this.hash =
215
+ (properties as { publicKeyHash: string }).publicKeyHash ||
216
+ (properties as { publicKey: PublicSignKey }).publicKey.hashcode();
217
+ if (!properties.normalized) {
218
+ this.transform({ length: properties.length, offset: properties.offset });
219
+ } else {
220
+ this.transform({
221
+ length: scaleToU32(properties.length),
222
+ offset: scaleToU32(properties.offset),
223
+ });
224
+ }
225
+
226
+ this.mode = properties.mode ?? ReplicationIntent.NonStrict;
227
+ this.timestamp = properties.timestamp || BigInt(0);
228
+ }
229
+
230
+ private transform(properties: { offset: number; length: number }) {
231
+ const ranges = getSegmentsFromOffsetAndRange(
232
+ properties.offset,
233
+ properties.length,
234
+ );
235
+ this.start1 = Math.round(ranges[0][0]);
236
+ this.end1 = Math.round(ranges[0][1]);
237
+ this.start2 = Math.round(ranges[1][0]);
238
+ this.end2 = Math.round(ranges[1][1]);
239
+
240
+ this.width =
241
+ this.end1 -
242
+ this.start1 +
243
+ (this.end2 < this.end1 ? this.end2 - this.start2 : 0);
244
+
245
+ if (
246
+ this.start1 > 0xffffffff ||
247
+ this.end1 > 0xffffffff ||
248
+ this.start2 > 0xffffffff ||
249
+ this.end2 > 0xffffffff ||
250
+ this.width > 0xffffffff ||
251
+ this.width < 0
252
+ ) {
253
+ throw new Error("Segment coordinate out of bounds");
254
+ }
255
+ }
256
+
257
+ get idString() {
258
+ return toBase64(this.id);
259
+ }
260
+
261
+ contains(point: number) {
262
+ return (
263
+ (point >= this.start1 && point < this.end1) ||
264
+ (point >= this.start2 && point < this.end2)
265
+ );
266
+ }
267
+
268
+ overlaps(other: ReplicationRangeIndexable, checkOther = true): boolean {
269
+ if (
270
+ this.contains(other.start1) ||
271
+ this.contains(other.start2) ||
272
+ this.contains(other.end1 - 1) ||
273
+ this.contains(other.end2 - 1)
274
+ ) {
275
+ return true;
276
+ }
277
+
278
+ if (checkOther) {
279
+ return other.overlaps(this, false);
280
+ }
281
+ return false;
282
+ }
283
+ toReplicationRange() {
284
+ return new ReplicationRange({
285
+ id: this.id,
286
+ offset: this.start1,
287
+ factor: this.width,
288
+ timestamp: this.timestamp,
289
+ mode: this.mode,
290
+ });
291
+ }
292
+
293
+ distanceTo(point: number) {
294
+ let wrappedPoint = MAX_U32 - point;
295
+ return Math.min(
296
+ Math.abs(this.start1 - point),
297
+ Math.abs(this.end2 - point),
298
+ Math.abs(this.start1 - wrappedPoint),
299
+ Math.abs(this.end2 - wrappedPoint),
300
+ );
301
+ }
302
+ get wrapped() {
303
+ return this.end2 < this.end1;
304
+ }
305
+
306
+ get widthNormalized() {
307
+ return this.width / MAX_U32;
308
+ }
309
+
310
+ equals(other: ReplicationRangeIndexable) {
311
+ if (
312
+ equals(this.id, other.id) &&
313
+ this.hash === other.hash &&
314
+ this.timestamp === other.timestamp &&
315
+ this.mode === other.mode &&
316
+ this.start1 === other.start1 &&
317
+ this.end1 === other.end1 &&
318
+ this.start2 === other.start2 &&
319
+ this.end2 === other.end2 &&
320
+ this.width === other.width
321
+ ) {
322
+ return true;
323
+ }
324
+
325
+ return false;
326
+ }
327
+
328
+ equalRange(other: ReplicationRangeIndexable) {
329
+ return (
330
+ this.start1 === other.start1 &&
331
+ this.end1 === other.end1 &&
332
+ this.start2 === other.start2 &&
333
+ this.end2 === other.end2
334
+ );
335
+ }
336
+
337
+ toString() {
338
+ let roundToTwoDecimals = (num: number) => Math.round(num * 100) / 100;
339
+
340
+ if (Math.abs(this.start1 - this.start2) < 0.0001) {
341
+ return `([${roundToTwoDecimals(this.start1 / MAX_U32)}, ${roundToTwoDecimals(this.end1 / MAX_U32)}])`;
342
+ }
343
+ return `([${roundToTwoDecimals(this.start1 / MAX_U32)}, ${roundToTwoDecimals(this.end1 / MAX_U32)}] [${roundToTwoDecimals(this.start2 / MAX_U32)}, ${roundToTwoDecimals(this.end2 / MAX_U32)}])`;
344
+ }
345
+
346
+ toStringDetailed() {
347
+ return `(hash ${this.hash} range: ${this.toString()})`;
348
+ }
349
+
350
+ /* removeRange(other: ReplicationRangeIndexable): ReplicationRangeIndexable | ReplicationRangeIndexable[] {
351
+ if (!this.overlaps(other)) {
352
+ return this
353
+ }
354
+
355
+ if (this.equalRange(other)) {
356
+ return []
357
+ }
358
+
359
+ let diff: ReplicationRangeIndexable[] = [];
360
+ let start1 = this.start1;
361
+ if (other.start1 > start1) {
362
+ diff.push(new ReplicationRangeIndexable({
363
+ id: this.id,
364
+ offset: this.start1,
365
+ length: other.start1 - this.start1,
366
+ mode: this.mode,
367
+ publicKeyHash: this.hash,
368
+ timestamp: this.timestamp,
369
+ normalized: false
370
+ }));
371
+
372
+ start1 = other.end2
373
+ }
374
+
375
+ if (other.end1 < this.end1) {
376
+ diff.push(new ReplicationRangeIndexable({
377
+ id: this.id,
378
+ offset: other.end1,
379
+ length: this.end1 - other.end1,
380
+ mode: this.mode,
381
+ publicKeyHash: this.hash,
382
+ timestamp: this.timestamp,
383
+ normalized: false
384
+ }));
385
+ }
386
+
387
+ if (other.start2 > this.start2) {
388
+ diff.push(new ReplicationRangeIndexable({
389
+ id: this.id,
390
+ offset: this.start2,
391
+ length: other.start2 - this.start2,
392
+ mode: this.mode,
393
+ publicKeyHash: this.hash,
394
+ timestamp: this.timestamp,
395
+ normalized: false
396
+ }));
397
+ }
398
+
399
+ if (other.end2 < this.end2) {
400
+ diff.push(new ReplicationRangeIndexable({
401
+ id: this.id,
402
+ offset: other.end2,
403
+ length: this.end2 - other.end2,
404
+ mode: this.mode,
405
+ publicKeyHash: this.hash,
406
+ timestamp: this.timestamp,
407
+ normalized: false
408
+ }));
409
+ }
410
+
411
+ return diff;
412
+ } */
413
+ }
414
+
415
+ const containingPoint = <S extends Shape | undefined = undefined>(
29
416
  rects: Index<ReplicationRangeIndexable>,
30
417
  point: number,
31
418
  roleAgeLimit: number,
32
419
  matured: boolean,
33
420
  now: number,
34
421
  options?: {
422
+ shape?: S;
35
423
  sort?: Sort[];
36
424
  },
37
- ): IndexIterator<ReplicationRangeIndexable> => {
425
+ ): IndexIterator<ReplicationRangeIndexable, S> => {
38
426
  // 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
39
427
 
40
428
  let queries = [
@@ -70,13 +458,12 @@ const containingPoint = (
70
458
  value: BigInt(now - roleAgeLimit),
71
459
  }),
72
460
  ];
73
- return iterate(
74
- rects,
75
- new SearchRequest({
461
+ return rects.iterate(
462
+ {
76
463
  query: queries,
77
464
  sort: options?.sort,
78
- fetch: 0xffffffff,
79
- }),
465
+ },
466
+ options,
80
467
  );
81
468
  /* const results = await rects.query(new SearchRequest({
82
469
  query: queries,
@@ -86,7 +473,7 @@ const containingPoint = (
86
473
  return results.results.map(x => x.value) */
87
474
  };
88
475
 
89
- const getClosest = (
476
+ const getClosest = <S extends Shape | undefined = undefined>(
90
477
  direction: "above" | "below",
91
478
  rects: Index<ReplicationRangeIndexable>,
92
479
  point: number,
@@ -94,7 +481,8 @@ const getClosest = (
94
481
  matured: boolean,
95
482
  now: number,
96
483
  includeStrict: boolean,
97
- ): IndexIterator<ReplicationRangeIndexable> => {
484
+ options?: { shape?: S },
485
+ ): IndexIterator<ReplicationRangeIndexable, S> => {
98
486
  const createQueries = (p: number, equality: boolean) => {
99
487
  let queries: Query[];
100
488
  if (direction === "below") {
@@ -140,28 +528,38 @@ const getClosest = (
140
528
  return queries;
141
529
  };
142
530
 
143
- const iterator = iterate(
144
- rects,
145
- new SearchRequest({
531
+ const sortByOldest = new Sort({ key: "timestamp", direction: "asc" });
532
+ const sortByHash = new Sort({ key: "hash", direction: "asc" }); // when breaking even
533
+
534
+ const iterator = rects.iterate(
535
+ {
146
536
  query: createQueries(point, false),
147
- sort:
537
+ sort: [
148
538
  direction === "below"
149
539
  ? new Sort({ key: ["end2"], direction: "desc" })
150
540
  : new Sort({ key: ["start1"], direction: "asc" }),
151
- }),
541
+ sortByOldest,
542
+ sortByHash,
543
+ ],
544
+ },
545
+ options,
152
546
  );
153
- const iteratorWrapped = iterate(
154
- rects,
155
- new SearchRequest({
547
+
548
+ const iteratorWrapped = rects.iterate(
549
+ {
156
550
  query: createQueries(direction === "below" ? MAX_U32 : 0, true),
157
- sort:
551
+ sort: [
158
552
  direction === "below"
159
553
  ? new Sort({ key: ["end2"], direction: "desc" })
160
554
  : new Sort({ key: ["start1"], direction: "asc" }),
161
- }),
555
+ sortByOldest,
556
+ sortByHash,
557
+ ],
558
+ },
559
+ options,
162
560
  );
163
561
 
164
- return joinIterator([iterator, iteratorWrapped], point, direction);
562
+ return joinIterator<S>([iterator, iteratorWrapped], point, direction);
165
563
  };
166
564
 
167
565
  export const hasCoveringRange = async (
@@ -169,75 +567,73 @@ export const hasCoveringRange = async (
169
567
  range: ReplicationRangeIndexable,
170
568
  ) => {
171
569
  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
- ]),
570
+ (await rects.count({
571
+ query: [
572
+ new Or([
573
+ new And([
574
+ new IntegerCompare({
575
+ key: "start1",
576
+ compare: Compare.LessOrEqual,
577
+ value: range.start1,
578
+ }),
579
+ new IntegerCompare({
580
+ key: "end1",
581
+ compare: Compare.GreaterOrEqual,
582
+ value: range.end1,
583
+ }),
200
584
  ]),
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
- ]),
585
+ new And([
586
+ new IntegerCompare({
587
+ key: "start2",
588
+ compare: Compare.LessOrEqual,
589
+ value: range.start1,
590
+ }),
591
+ new IntegerCompare({
592
+ key: "end2",
593
+ compare: Compare.GreaterOrEqual,
594
+ value: range.end1,
595
+ }),
226
596
  ]),
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,
597
+ ]),
598
+ new Or([
599
+ new And([
600
+ new IntegerCompare({
601
+ key: "start1",
602
+ compare: Compare.LessOrEqual,
603
+ value: range.start2,
236
604
  }),
237
- ),
238
- ],
239
- }),
240
- )) > 0
605
+ new IntegerCompare({
606
+ key: "end1",
607
+ compare: Compare.GreaterOrEqual,
608
+ value: range.end2,
609
+ }),
610
+ ]),
611
+ new And([
612
+ new IntegerCompare({
613
+ key: "start2",
614
+ compare: Compare.LessOrEqual,
615
+ value: range.start2,
616
+ }),
617
+ new IntegerCompare({
618
+ key: "end2",
619
+ compare: Compare.GreaterOrEqual,
620
+ value: range.end2,
621
+ }),
622
+ ]),
623
+ ]),
624
+ new StringMatch({
625
+ key: "hash",
626
+ value: range.hash,
627
+ }),
628
+ // assume that we are looking for other ranges, not want to update an existing one
629
+ new Not(
630
+ new ByteMatchQuery({
631
+ key: "id",
632
+ value: range.id,
633
+ }),
634
+ ),
635
+ ],
636
+ })) > 0
241
637
  );
242
638
  };
243
639
 
@@ -280,15 +676,14 @@ export const getDistance = (
280
676
  throw new Error("Invalid direction");
281
677
  };
282
678
 
283
- const joinIterator = (
284
- iterators: IndexIterator<ReplicationRangeIndexable>[],
679
+ const joinIterator = <S extends Shape | undefined = undefined>(
680
+ iterators: IndexIterator<ReplicationRangeIndexable, S>[],
285
681
  point: number,
286
682
  direction: "above" | "below" | "closest",
287
- ) => {
683
+ ): IndexIterator<ReplicationRangeIndexable, S> => {
288
684
  let queues: {
289
- kept: number;
290
685
  elements: {
291
- result: IndexedResult<ReplicationRangeIndexable>;
686
+ result: IndexedResult<ReturnTypeFromShape<ReplicationRangeIndexable, S>>;
292
687
  dist: number;
293
688
  }[];
294
689
  }[] = [];
@@ -296,23 +691,23 @@ const joinIterator = (
296
691
  return {
297
692
  next: async (
298
693
  count: number,
299
- ): Promise<IndexedResults<ReplicationRangeIndexable>> => {
300
- let results: IndexedResults<ReplicationRangeIndexable> = {
301
- kept: 0, // TODO
302
- results: [],
303
- };
694
+ ): Promise<
695
+ IndexedResults<ReturnTypeFromShape<ReplicationRangeIndexable, S>>
696
+ > => {
697
+ let results: IndexedResults<
698
+ ReturnTypeFromShape<ReplicationRangeIndexable, S>
699
+ > = [];
304
700
  for (let i = 0; i < iterators.length; i++) {
305
701
  let queue = queues[i];
306
702
  if (!queue) {
307
- queue = { elements: [], kept: 0 };
703
+ queue = { elements: [] };
308
704
  queues[i] = queue;
309
705
  }
310
706
  let iterator = iterators[i];
311
- if (queue.elements.length < count && iterator.done() === false) {
707
+ if (queue.elements.length < count && iterator.done() !== true) {
312
708
  let res = await iterator.next(count);
313
- queue.kept = res.kept;
314
709
 
315
- for (const el of res.results) {
710
+ for (const el of res) {
316
711
  const closest = el.value;
317
712
 
318
713
  let dist: number;
@@ -356,24 +751,25 @@ const joinIterator = (
356
751
 
357
752
  let closest = queues[closestQueue]?.elements.shift();
358
753
  if (closest) {
359
- results.results.push(closest.result);
754
+ results.push(closest.result);
360
755
  }
361
756
  }
362
-
363
- for (let i = 0; i < queues.length; i++) {
364
- results.kept += queues[i].elements.length + queues[i].kept;
365
- }
366
-
367
757
  return results;
368
758
  },
369
- done: () => iterators.every((x) => x.done()),
759
+ pending: async () => {
760
+ let allPending = await Promise.all(iterators.map((x) => x.pending()));
761
+ return allPending.reduce((acc, x) => acc + x, 0);
762
+ },
763
+ done: () => iterators.every((x) => x.done() === true),
370
764
  close: async () => {
371
765
  for (const iterator of iterators) {
372
766
  await iterator.close();
373
767
  }
374
768
  },
375
769
  all: async () => {
376
- let results: IndexedResult<ReplicationRangeIndexable>[] = [];
770
+ let results: IndexedResult<
771
+ ReturnTypeFromShape<ReplicationRangeIndexable, S>
772
+ >[] = [];
377
773
  for (const iterator of iterators) {
378
774
  let res = await iterator.all();
379
775
  results.push(...res);
@@ -383,15 +779,18 @@ const joinIterator = (
383
779
  };
384
780
  };
385
781
 
386
- const getClosestAround = (
782
+ const getClosestAround = <
783
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
784
+ >(
387
785
  peers: Index<ReplicationRangeIndexable>,
388
786
  point: number,
389
787
  roleAge: number,
390
788
  now: number,
391
789
  includeStrictBelow: boolean,
392
790
  includeStrictAbove: boolean,
791
+ options?: { shape?: S },
393
792
  ) => {
394
- const closestBelow = getClosest(
793
+ const closestBelow = getClosest<S>(
395
794
  "below",
396
795
  peers,
397
796
  point,
@@ -399,8 +798,9 @@ const getClosestAround = (
399
798
  true,
400
799
  now,
401
800
  includeStrictBelow,
801
+ options,
402
802
  );
403
- const closestAbove = getClosest(
803
+ const closestAbove = getClosest<S>(
404
804
  "above",
405
805
  peers,
406
806
  point,
@@ -408,45 +808,79 @@ const getClosestAround = (
408
808
  true,
409
809
  now,
410
810
  includeStrictAbove,
811
+ options,
812
+ );
813
+ const containing = containingPoint<S>(
814
+ peers,
815
+ point,
816
+ roleAge,
817
+ true,
818
+ now,
819
+ options,
411
820
  );
412
- const containing = containingPoint(peers, point, roleAge, true, now);
413
821
 
414
822
  return iteratorInSeries(
415
823
  containing,
416
- joinIterator([closestBelow, closestAbove], point, "closest"),
824
+ joinIterator<S>([closestBelow, closestAbove], point, "closest"),
417
825
  );
418
826
  };
419
827
 
420
828
  const collectNodesAroundPoint = async (
421
829
  roleAge: number,
422
830
  peers: Index<ReplicationRangeIndexable>,
423
- collector: (rect: ReplicationRangeIndexable, matured: boolean) => void,
831
+ collector: (
832
+ rect: { hash: string },
833
+ matured: boolean,
834
+ interescting: boolean,
835
+ ) => void,
424
836
  point: u32,
425
837
  now: number,
426
838
  done: () => boolean = () => true,
427
839
  ) => {
428
- const containing = containingPoint(peers, point, 0, true, now);
429
-
430
- const allContaining = await containing.next(0xffffffff);
431
- for (const rect of allContaining.results) {
432
- collector(rect.value, isMatured(rect.value, now, roleAge));
840
+ /* let shape = { timestamp: true, hash: true } as const */
841
+ const containing = containingPoint(
842
+ peers,
843
+ point,
844
+ 0,
845
+ true,
846
+ now /* , { shape } */,
847
+ );
848
+ const allContaining = await containing.all();
849
+ for (const rect of allContaining) {
850
+ collector(rect.value, isMatured(rect.value, now, roleAge), true);
433
851
  }
434
852
 
435
853
  if (done()) {
436
854
  return;
437
855
  }
438
856
 
439
- const closestBelow = getClosest("below", peers, point, 0, true, now, false);
440
- const closestAbove = getClosest("above", peers, point, 0, true, now, false);
857
+ const closestBelow = getClosest(
858
+ "below",
859
+ peers,
860
+ point,
861
+ 0,
862
+ true,
863
+ now,
864
+ false /* , { shape } */,
865
+ );
866
+ const closestAbove = getClosest(
867
+ "above",
868
+ peers,
869
+ point,
870
+ 0,
871
+ true,
872
+ now,
873
+ false /* , { shape } */,
874
+ );
441
875
  const aroundIterator = joinIterator(
442
876
  [closestBelow, closestAbove],
443
877
  point,
444
878
  "closest",
445
879
  );
446
- while (aroundIterator.done() === false && done() === false) {
880
+ while (aroundIterator.done() !== true && done() !== true) {
447
881
  const res = await aroundIterator.next(1);
448
- for (const rect of res.results) {
449
- collector(rect.value, isMatured(rect.value, now, roleAge));
882
+ for (const rect of res) {
883
+ collector(rect.value, isMatured(rect.value, now, roleAge), false);
450
884
  if (done()) {
451
885
  return;
452
886
  }
@@ -454,6 +888,14 @@ const collectNodesAroundPoint = async (
454
888
  }
455
889
  };
456
890
 
891
+ export const getEvenlySpacedU32 = (from: number, count: number) => {
892
+ let ret: number[] = new Array(count);
893
+ for (let i = 0; i < count; i++) {
894
+ ret[i] = Math.round(from + (i * MAX_U32) / count) % MAX_U32;
895
+ }
896
+ return ret;
897
+ };
898
+
457
899
  export const isMatured = (
458
900
  segment: { timestamp: bigint },
459
901
  now: number,
@@ -461,44 +903,42 @@ export const isMatured = (
461
903
  ) => {
462
904
  return now - Number(segment.timestamp) >= minAge;
463
905
  };
464
-
906
+ // get peer sample that are responsible for the cursor point
907
+ // will return a list of peers that want to replicate the data,
908
+ // but also if necessary a list of peers that are responsible for the data
909
+ // but have not explicitly replicating a range that cover the cursor point
465
910
  export const getSamples = async (
466
- cursor: u32,
911
+ cursor: u32[],
467
912
  peers: Index<ReplicationRangeIndexable>,
468
- amount: number,
469
913
  roleAge: number,
470
- ) => {
471
- const leaders: Set<string> = new Set();
914
+ ): Promise<Map<string, { intersecting: boolean }>> => {
915
+ const leaders: Map<string, { intersecting: boolean }> = new Map();
472
916
  if (!peers) {
473
- return [];
474
- }
475
-
476
- const size = await peers.getSize();
477
-
478
- amount = Math.min(amount, size);
479
-
480
- if (amount === 0) {
481
- return [];
917
+ return new Map();
482
918
  }
483
919
 
484
920
  const now = +new Date();
485
921
 
486
922
  const maturedLeaders = new Set();
487
- for (let i = 0; i < amount; i++) {
923
+ for (let i = 0; i < cursor.length; i++) {
488
924
  // evenly distributed
489
- const point = Math.round(cursor + (i * MAX_U32) / amount) % MAX_U32;
490
925
 
491
926
  // aquire at least one unique node for each point
492
927
  await collectNodesAroundPoint(
493
928
  roleAge,
494
929
  peers,
495
- (rect, m) => {
930
+ (rect, m, intersecting) => {
496
931
  if (m) {
497
932
  maturedLeaders.add(rect.hash);
498
933
  }
499
- leaders.add(rect.hash);
934
+
935
+ const prev = leaders.get(rect.hash);
936
+
937
+ if (!prev || (intersecting && !prev.intersecting)) {
938
+ leaders.set(rect.hash, { intersecting });
939
+ }
500
940
  },
501
- point,
941
+ cursor[i],
502
942
  now,
503
943
  () => {
504
944
  if (maturedLeaders.size > i) {
@@ -509,13 +949,15 @@ export const getSamples = async (
509
949
  );
510
950
  }
511
951
 
512
- return [...leaders];
952
+ return leaders;
513
953
  };
514
954
 
515
- const fetchOne = async (iterator: IndexIterator<ReplicationRangeIndexable>) => {
955
+ const fetchOne = async <S extends Shape | undefined>(
956
+ iterator: IndexIterator<ReplicationRangeIndexable, S>,
957
+ ) => {
516
958
  const value = await iterator.next(1);
517
959
  await iterator.close();
518
- return value.results[0]?.value;
960
+ return value[0]?.value;
519
961
  };
520
962
 
521
963
  export const minimumWidthToCover = async (
@@ -566,22 +1008,21 @@ export const getCoverSet = async (properties: {
566
1008
  const eagerFetch =
567
1009
  properties.eager === true
568
1010
  ? 1000
569
- : properties.eager.unmaturedFetchCoverSize;
1011
+ : (properties.eager.unmaturedFetchCoverSize ?? 1000);
570
1012
 
571
1013
  // pull all umatured
572
- const rects = await peers.query(
573
- new SearchRequest({
574
- fetch: eagerFetch,
575
- query: [
576
- new IntegerCompare({
577
- key: "timestamp",
578
- compare: Compare.GreaterOrEqual,
579
- value: BigInt(now - roleAge),
580
- }),
581
- ],
582
- }),
583
- );
584
- for (const rect of rects.results) {
1014
+ const iterator = peers.iterate({
1015
+ query: [
1016
+ new IntegerCompare({
1017
+ key: "timestamp",
1018
+ compare: Compare.GreaterOrEqual,
1019
+ value: BigInt(now - roleAge),
1020
+ }),
1021
+ ],
1022
+ });
1023
+ const rects = await iterator.next(eagerFetch);
1024
+ await iterator.close();
1025
+ for (const rect of rects) {
585
1026
  ret.add(rect.value.hash);
586
1027
  }
587
1028
  }
@@ -713,24 +1154,177 @@ export const getCoverSet = async (properties: {
713
1154
  start instanceof PublicSignKey && ret.add(start.hashcode());
714
1155
  return ret;
715
1156
  };
1157
+ /* export const getReplicationDiff = (changes: ReplicationChange) => {
1158
+ // reduce the change set to only regions that are changed for each peer
1159
+ // i.e. subtract removed regions from added regions, and vice versa
1160
+ const result = new Map<string, { range: ReplicationRangeIndexable, added: boolean }[]>();
1161
+
1162
+ for (const addedChange of changes.added ?? []) {
1163
+ let prev = result.get(addedChange.hash) ?? [];
1164
+ for (const [_hash, ranges] of result.entries()) {
1165
+ for (const r of ranges) {
1166
+
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+ */
1172
+
1173
+ const matchRangeQuery = (range: ReplicationRangeIndexable) => {
1174
+ let ors = [];
1175
+ ors.push(
1176
+ new And([
1177
+ new IntegerCompare({
1178
+ key: "coordinate",
1179
+ compare: "gte",
1180
+ value: range.start1,
1181
+ }),
1182
+ new IntegerCompare({
1183
+ key: "coordinate",
1184
+ compare: "lt",
1185
+ value: range.end1,
1186
+ }),
1187
+ ]),
1188
+ );
716
1189
 
717
- export const fetchOneFromPublicKey = async (
1190
+ ors.push(
1191
+ new And([
1192
+ new IntegerCompare({
1193
+ key: "coordinate",
1194
+ compare: "gte",
1195
+ value: range.start2,
1196
+ }),
1197
+ new IntegerCompare({
1198
+ key: "coordinate",
1199
+ compare: "lt",
1200
+ value: range.end2,
1201
+ }),
1202
+ ]),
1203
+ );
1204
+
1205
+ return new Or(ors);
1206
+ };
1207
+ export const toRebalance = (
1208
+ changes: ReplicationChanges,
1209
+ index: Index<EntryReplicated>,
1210
+ ): AsyncIterable<{ gid: string; entries: EntryReplicated[] }> => {
1211
+ const assignedRangesQuery = (changes: ReplicationChanges) => {
1212
+ let ors: Query[] = [];
1213
+ for (const change of changes) {
1214
+ const matchRange = matchRangeQuery(change.range);
1215
+ if (change.type === "updated") {
1216
+ // assuming a range is to be removed, is this entry still enoughly replicated
1217
+ const prevMatchRange = matchRangeQuery(change.prev);
1218
+ ors.push(prevMatchRange);
1219
+ ors.push(matchRange);
1220
+
1221
+ /* ors.push(
1222
+ new And([
1223
+ // not sufficiently replicated
1224
+ new IntegerCompare({
1225
+ key: "replicatorsMissingShifted",
1226
+ compare: Compare.Greater,
1227
+ value: HALF_MAX_U32 - 1, // + 1 since we are going to remove a replicator for this entry
1228
+ }),
1229
+
1230
+ // is not the current range
1231
+ new Not(matchRange),
1232
+
1233
+ // but was in the the previous range
1234
+ prevMatchRange,
1235
+ ]),
1236
+ ); */
1237
+ } else {
1238
+ ors.push(matchRange);
1239
+ }
1240
+ }
1241
+
1242
+ // entry is assigned to a range boundary, meaning it is due to be inspected
1243
+ ors.push(
1244
+ new BoolQuery({
1245
+ key: "assignedToRangeBoundary",
1246
+ value: true,
1247
+ }),
1248
+ );
1249
+
1250
+ // entry is not sufficiently replicated, and we are to still keep it
1251
+ return new Or(ors);
1252
+ };
1253
+
1254
+ return {
1255
+ [Symbol.asyncIterator]: async function* () {
1256
+ const iterator = index.iterate({
1257
+ query: assignedRangesQuery(changes),
1258
+ });
1259
+
1260
+ /* const iteratorFitlered = index.iterate({
1261
+ query: assignedRangesQuery(changes),
1262
+ }); */
1263
+
1264
+ while (iterator.done() !== true) {
1265
+ const entries = await iterator.next(0xffffffff); // TODO batch sizes
1266
+ /* const entriesFiltered = await iteratorFitlered.next(0xffffffff); // TODO batch sizes
1267
+
1268
+ console.log(
1269
+ "SIZE",
1270
+ await index.getSize(),
1271
+ entries.results.length,
1272
+ entriesFiltered.results.length,
1273
+ Math.min(
1274
+ ...entries.results
1275
+ .filter((x) => x.value.assignedToRangeBoundary === false)
1276
+ .map((x) => x.value.coordinate / 0xffffffff),
1277
+ ),
1278
+ Math.max(
1279
+ ...entries.results
1280
+ .filter((x) => x.value.assignedToRangeBoundary === false)
1281
+ .map((x) => x.value.coordinate / 0xffffffff),
1282
+ ),
1283
+ ); */
1284
+
1285
+ // TODO do we need this
1286
+ const grouped = await groupByGidSync(entries.map((x) => x.value));
1287
+
1288
+ for (const [gid, entries] of grouped.entries()) {
1289
+ yield { gid, entries };
1290
+ }
1291
+ }
1292
+ },
1293
+ };
1294
+ };
1295
+
1296
+ export const fetchOneFromPublicKey = async <
1297
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
1298
+ >(
718
1299
  publicKey: PublicSignKey,
719
1300
  index: Index<ReplicationRangeIndexable>,
720
1301
  roleAge: number,
721
1302
  now: number,
1303
+ options?: {
1304
+ shape: S;
1305
+ },
722
1306
  ) => {
723
- let result = await index.query(
724
- new SearchRequest({
1307
+ let iterator = index.iterate<S>(
1308
+ {
725
1309
  query: [new StringMatch({ key: "hash", value: publicKey.hashcode() })],
726
- fetch: 1,
727
- }),
1310
+ },
1311
+ options,
728
1312
  );
729
- let node = result.results[0]?.value;
1313
+ let result = await iterator.next(1);
1314
+ await iterator.close();
1315
+ let node = result[0]?.value;
730
1316
  if (node) {
731
1317
  if (!isMatured(node, now, roleAge)) {
732
1318
  const matured = await fetchOne(
733
- getClosestAround(index, node.start1, roleAge, now, false, false),
1319
+ getClosestAround<S>(
1320
+ index,
1321
+ node.start1,
1322
+ roleAge,
1323
+ now,
1324
+ false,
1325
+ false,
1326
+ options,
1327
+ ),
734
1328
  );
735
1329
  if (matured) {
736
1330
  node = matured;
@@ -740,16 +1334,24 @@ export const fetchOneFromPublicKey = async (
740
1334
  return node;
741
1335
  };
742
1336
 
743
- export const getStartAndEnd = async (
1337
+ export const getStartAndEnd = async <
1338
+ S extends (Shape & { timestamp: true }) | undefined,
1339
+ >(
744
1340
  peers: Index<ReplicationRangeIndexable>,
745
1341
  start: number | PublicSignKey | undefined | undefined,
746
1342
  widthToCoverScaled: number,
747
1343
  roleAge: number,
748
1344
  now: number,
749
1345
  intervalWidth: number,
750
- ) => {
1346
+ options?: { shape: S },
1347
+ ): Promise<{
1348
+ startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined;
1349
+ startLocation: number;
1350
+ endLocation: number;
1351
+ }> => {
751
1352
  // find a good starting point
752
- let startNode: ReplicationRangeIndexable | undefined = undefined;
1353
+ let startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined =
1354
+ undefined;
753
1355
  let startLocation: number | undefined = undefined;
754
1356
 
755
1357
  const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
@@ -761,12 +1363,19 @@ export const getStartAndEnd = async (
761
1363
  now,
762
1364
  false,
763
1365
  true,
1366
+ options,
764
1367
  );
765
1368
  };
766
1369
 
767
1370
  if (start instanceof PublicSignKey) {
768
1371
  // start at our node (local first)
769
- startNode = await fetchOneFromPublicKey(start, peers, roleAge, now);
1372
+ startNode = await fetchOneFromPublicKey(
1373
+ start,
1374
+ peers,
1375
+ roleAge,
1376
+ now,
1377
+ options,
1378
+ );
770
1379
  if (!startNode) {
771
1380
  // fetch randomly
772
1381
  await nodeFromPoint();
@@ -810,22 +1419,26 @@ export const getStartAndEnd = async (
810
1419
  };
811
1420
  };
812
1421
 
813
- export const fetchOneClosest = (
1422
+ export const fetchOneClosest = <
1423
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
1424
+ >(
814
1425
  peers: Index<ReplicationRangeIndexable>,
815
1426
  point: number,
816
1427
  roleAge: number,
817
1428
  now: number,
818
1429
  includeStrictBelow: boolean,
819
1430
  includeStrictAbove: boolean,
1431
+ options?: { shape?: S },
820
1432
  ) => {
821
1433
  return fetchOne(
822
- getClosestAround(
1434
+ getClosestAround<S>(
823
1435
  peers,
824
1436
  point,
825
1437
  roleAge,
826
1438
  now,
827
1439
  includeStrictBelow,
828
1440
  includeStrictAbove,
1441
+ options,
829
1442
  ),
830
1443
  );
831
1444
  };