@peerbit/shared-log 9.1.2 → 9.2.0-0b8baa8

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 +497 -82
  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 +70 -70
  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 +772 -187
  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;
202
+
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
+ }
27
358
 
28
- const containingPoint = (
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,23 +458,16 @@ 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
- /* const results = await rects.query(new SearchRequest({
82
- query: queries,
83
- sort: options?.sort,
84
- fetch: 0xffffffff
85
- }))
86
- return results.results.map(x => x.value) */
87
468
  };
88
469
 
89
- const getClosest = (
470
+ const getClosest = <S extends Shape | undefined = undefined>(
90
471
  direction: "above" | "below",
91
472
  rects: Index<ReplicationRangeIndexable>,
92
473
  point: number,
@@ -94,7 +475,8 @@ const getClosest = (
94
475
  matured: boolean,
95
476
  now: number,
96
477
  includeStrict: boolean,
97
- ): IndexIterator<ReplicationRangeIndexable> => {
478
+ options?: { shape?: S },
479
+ ): IndexIterator<ReplicationRangeIndexable, S> => {
98
480
  const createQueries = (p: number, equality: boolean) => {
99
481
  let queries: Query[];
100
482
  if (direction === "below") {
@@ -140,28 +522,38 @@ const getClosest = (
140
522
  return queries;
141
523
  };
142
524
 
143
- const iterator = iterate(
144
- rects,
145
- new SearchRequest({
525
+ const sortByOldest = new Sort({ key: "timestamp", direction: "asc" });
526
+ const sortByHash = new Sort({ key: "hash", direction: "asc" }); // when breaking even
527
+
528
+ const iterator = rects.iterate(
529
+ {
146
530
  query: createQueries(point, false),
147
- sort:
531
+ sort: [
148
532
  direction === "below"
149
533
  ? new Sort({ key: ["end2"], direction: "desc" })
150
534
  : new Sort({ key: ["start1"], direction: "asc" }),
151
- }),
535
+ sortByOldest,
536
+ sortByHash,
537
+ ],
538
+ },
539
+ options,
152
540
  );
153
- const iteratorWrapped = iterate(
154
- rects,
155
- new SearchRequest({
541
+
542
+ const iteratorWrapped = rects.iterate(
543
+ {
156
544
  query: createQueries(direction === "below" ? MAX_U32 : 0, true),
157
- sort:
545
+ sort: [
158
546
  direction === "below"
159
547
  ? new Sort({ key: ["end2"], direction: "desc" })
160
548
  : new Sort({ key: ["start1"], direction: "asc" }),
161
- }),
549
+ sortByOldest,
550
+ sortByHash,
551
+ ],
552
+ },
553
+ options,
162
554
  );
163
555
 
164
- return joinIterator([iterator, iteratorWrapped], point, direction);
556
+ return joinIterator<S>([iterator, iteratorWrapped], point, direction);
165
557
  };
166
558
 
167
559
  export const hasCoveringRange = async (
@@ -169,75 +561,73 @@ export const hasCoveringRange = async (
169
561
  range: ReplicationRangeIndexable,
170
562
  ) => {
171
563
  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
- ]),
564
+ (await rects.count({
565
+ query: [
566
+ new Or([
567
+ new And([
568
+ new IntegerCompare({
569
+ key: "start1",
570
+ compare: Compare.LessOrEqual,
571
+ value: range.start1,
572
+ }),
573
+ new IntegerCompare({
574
+ key: "end1",
575
+ compare: Compare.GreaterOrEqual,
576
+ value: range.end1,
577
+ }),
200
578
  ]),
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
- ]),
579
+ new And([
580
+ new IntegerCompare({
581
+ key: "start2",
582
+ compare: Compare.LessOrEqual,
583
+ value: range.start1,
584
+ }),
585
+ new IntegerCompare({
586
+ key: "end2",
587
+ compare: Compare.GreaterOrEqual,
588
+ value: range.end1,
589
+ }),
226
590
  ]),
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,
591
+ ]),
592
+ new Or([
593
+ new And([
594
+ new IntegerCompare({
595
+ key: "start1",
596
+ compare: Compare.LessOrEqual,
597
+ value: range.start2,
236
598
  }),
237
- ),
238
- ],
239
- }),
240
- )) > 0
599
+ new IntegerCompare({
600
+ key: "end1",
601
+ compare: Compare.GreaterOrEqual,
602
+ value: range.end2,
603
+ }),
604
+ ]),
605
+ new And([
606
+ new IntegerCompare({
607
+ key: "start2",
608
+ compare: Compare.LessOrEqual,
609
+ value: range.start2,
610
+ }),
611
+ new IntegerCompare({
612
+ key: "end2",
613
+ compare: Compare.GreaterOrEqual,
614
+ value: range.end2,
615
+ }),
616
+ ]),
617
+ ]),
618
+ new StringMatch({
619
+ key: "hash",
620
+ value: range.hash,
621
+ }),
622
+ // assume that we are looking for other ranges, not want to update an existing one
623
+ new Not(
624
+ new ByteMatchQuery({
625
+ key: "id",
626
+ value: range.id,
627
+ }),
628
+ ),
629
+ ],
630
+ })) > 0
241
631
  );
242
632
  };
243
633
 
@@ -280,15 +670,14 @@ export const getDistance = (
280
670
  throw new Error("Invalid direction");
281
671
  };
282
672
 
283
- const joinIterator = (
284
- iterators: IndexIterator<ReplicationRangeIndexable>[],
673
+ const joinIterator = <S extends Shape | undefined = undefined>(
674
+ iterators: IndexIterator<ReplicationRangeIndexable, S>[],
285
675
  point: number,
286
676
  direction: "above" | "below" | "closest",
287
- ) => {
677
+ ): IndexIterator<ReplicationRangeIndexable, S> => {
288
678
  let queues: {
289
- kept: number;
290
679
  elements: {
291
- result: IndexedResult<ReplicationRangeIndexable>;
680
+ result: IndexedResult<ReturnTypeFromShape<ReplicationRangeIndexable, S>>;
292
681
  dist: number;
293
682
  }[];
294
683
  }[] = [];
@@ -296,23 +685,23 @@ const joinIterator = (
296
685
  return {
297
686
  next: async (
298
687
  count: number,
299
- ): Promise<IndexedResults<ReplicationRangeIndexable>> => {
300
- let results: IndexedResults<ReplicationRangeIndexable> = {
301
- kept: 0, // TODO
302
- results: [],
303
- };
688
+ ): Promise<
689
+ IndexedResults<ReturnTypeFromShape<ReplicationRangeIndexable, S>>
690
+ > => {
691
+ let results: IndexedResults<
692
+ ReturnTypeFromShape<ReplicationRangeIndexable, S>
693
+ > = [];
304
694
  for (let i = 0; i < iterators.length; i++) {
305
695
  let queue = queues[i];
306
696
  if (!queue) {
307
- queue = { elements: [], kept: 0 };
697
+ queue = { elements: [] };
308
698
  queues[i] = queue;
309
699
  }
310
700
  let iterator = iterators[i];
311
- if (queue.elements.length < count && iterator.done() === false) {
701
+ if (queue.elements.length < count && iterator.done() !== true) {
312
702
  let res = await iterator.next(count);
313
- queue.kept = res.kept;
314
703
 
315
- for (const el of res.results) {
704
+ for (const el of res) {
316
705
  const closest = el.value;
317
706
 
318
707
  let dist: number;
@@ -356,24 +745,25 @@ const joinIterator = (
356
745
 
357
746
  let closest = queues[closestQueue]?.elements.shift();
358
747
  if (closest) {
359
- results.results.push(closest.result);
748
+ results.push(closest.result);
360
749
  }
361
750
  }
362
-
363
- for (let i = 0; i < queues.length; i++) {
364
- results.kept += queues[i].elements.length + queues[i].kept;
365
- }
366
-
367
751
  return results;
368
752
  },
369
- done: () => iterators.every((x) => x.done()),
753
+ pending: async () => {
754
+ let allPending = await Promise.all(iterators.map((x) => x.pending()));
755
+ return allPending.reduce((acc, x) => acc + x, 0);
756
+ },
757
+ done: () => iterators.every((x) => x.done() === true),
370
758
  close: async () => {
371
759
  for (const iterator of iterators) {
372
760
  await iterator.close();
373
761
  }
374
762
  },
375
763
  all: async () => {
376
- let results: IndexedResult<ReplicationRangeIndexable>[] = [];
764
+ let results: IndexedResult<
765
+ ReturnTypeFromShape<ReplicationRangeIndexable, S>
766
+ >[] = [];
377
767
  for (const iterator of iterators) {
378
768
  let res = await iterator.all();
379
769
  results.push(...res);
@@ -383,15 +773,18 @@ const joinIterator = (
383
773
  };
384
774
  };
385
775
 
386
- const getClosestAround = (
776
+ const getClosestAround = <
777
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
778
+ >(
387
779
  peers: Index<ReplicationRangeIndexable>,
388
780
  point: number,
389
781
  roleAge: number,
390
782
  now: number,
391
783
  includeStrictBelow: boolean,
392
784
  includeStrictAbove: boolean,
785
+ options?: { shape?: S },
393
786
  ) => {
394
- const closestBelow = getClosest(
787
+ const closestBelow = getClosest<S>(
395
788
  "below",
396
789
  peers,
397
790
  point,
@@ -399,8 +792,9 @@ const getClosestAround = (
399
792
  true,
400
793
  now,
401
794
  includeStrictBelow,
795
+ options,
402
796
  );
403
- const closestAbove = getClosest(
797
+ const closestAbove = getClosest<S>(
404
798
  "above",
405
799
  peers,
406
800
  point,
@@ -408,45 +802,79 @@ const getClosestAround = (
408
802
  true,
409
803
  now,
410
804
  includeStrictAbove,
805
+ options,
806
+ );
807
+ const containing = containingPoint<S>(
808
+ peers,
809
+ point,
810
+ roleAge,
811
+ true,
812
+ now,
813
+ options,
411
814
  );
412
- const containing = containingPoint(peers, point, roleAge, true, now);
413
815
 
414
816
  return iteratorInSeries(
415
817
  containing,
416
- joinIterator([closestBelow, closestAbove], point, "closest"),
818
+ joinIterator<S>([closestBelow, closestAbove], point, "closest"),
417
819
  );
418
820
  };
419
821
 
420
822
  const collectNodesAroundPoint = async (
421
823
  roleAge: number,
422
824
  peers: Index<ReplicationRangeIndexable>,
423
- collector: (rect: ReplicationRangeIndexable, matured: boolean) => void,
825
+ collector: (
826
+ rect: { hash: string },
827
+ matured: boolean,
828
+ interescting: boolean,
829
+ ) => void,
424
830
  point: u32,
425
831
  now: number,
426
832
  done: () => boolean = () => true,
427
833
  ) => {
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));
834
+ /* let shape = { timestamp: true, hash: true } as const */
835
+ const containing = containingPoint(
836
+ peers,
837
+ point,
838
+ 0,
839
+ true,
840
+ now /* , { shape } */,
841
+ );
842
+ const allContaining = await containing.all();
843
+ for (const rect of allContaining) {
844
+ collector(rect.value, isMatured(rect.value, now, roleAge), true);
433
845
  }
434
846
 
435
847
  if (done()) {
436
848
  return;
437
849
  }
438
850
 
439
- const closestBelow = getClosest("below", peers, point, 0, true, now, false);
440
- const closestAbove = getClosest("above", peers, point, 0, true, now, false);
851
+ const closestBelow = getClosest(
852
+ "below",
853
+ peers,
854
+ point,
855
+ 0,
856
+ true,
857
+ now,
858
+ false /* , { shape } */,
859
+ );
860
+ const closestAbove = getClosest(
861
+ "above",
862
+ peers,
863
+ point,
864
+ 0,
865
+ true,
866
+ now,
867
+ false /* , { shape } */,
868
+ );
441
869
  const aroundIterator = joinIterator(
442
870
  [closestBelow, closestAbove],
443
871
  point,
444
872
  "closest",
445
873
  );
446
- while (aroundIterator.done() === false && done() === false) {
874
+ while (aroundIterator.done() !== true && done() !== true) {
447
875
  const res = await aroundIterator.next(1);
448
- for (const rect of res.results) {
449
- collector(rect.value, isMatured(rect.value, now, roleAge));
876
+ for (const rect of res) {
877
+ collector(rect.value, isMatured(rect.value, now, roleAge), false);
450
878
  if (done()) {
451
879
  return;
452
880
  }
@@ -454,6 +882,14 @@ const collectNodesAroundPoint = async (
454
882
  }
455
883
  };
456
884
 
885
+ export const getEvenlySpacedU32 = (from: number, count: number) => {
886
+ let ret: number[] = new Array(count);
887
+ for (let i = 0; i < count; i++) {
888
+ ret[i] = Math.round(from + (i * MAX_U32) / count) % MAX_U32;
889
+ }
890
+ return ret;
891
+ };
892
+
457
893
  export const isMatured = (
458
894
  segment: { timestamp: bigint },
459
895
  now: number,
@@ -461,44 +897,42 @@ export const isMatured = (
461
897
  ) => {
462
898
  return now - Number(segment.timestamp) >= minAge;
463
899
  };
464
-
900
+ // get peer sample that are responsible for the cursor point
901
+ // will return a list of peers that want to replicate the data,
902
+ // but also if necessary a list of peers that are responsible for the data
903
+ // but have not explicitly replicating a range that cover the cursor point
465
904
  export const getSamples = async (
466
- cursor: u32,
905
+ cursor: u32[],
467
906
  peers: Index<ReplicationRangeIndexable>,
468
- amount: number,
469
907
  roleAge: number,
470
- ) => {
471
- const leaders: Set<string> = new Set();
908
+ ): Promise<Map<string, { intersecting: boolean }>> => {
909
+ const leaders: Map<string, { intersecting: boolean }> = new Map();
472
910
  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 [];
911
+ return new Map();
482
912
  }
483
913
 
484
914
  const now = +new Date();
485
915
 
486
916
  const maturedLeaders = new Set();
487
- for (let i = 0; i < amount; i++) {
917
+ for (let i = 0; i < cursor.length; i++) {
488
918
  // evenly distributed
489
- const point = Math.round(cursor + (i * MAX_U32) / amount) % MAX_U32;
490
919
 
491
920
  // aquire at least one unique node for each point
492
921
  await collectNodesAroundPoint(
493
922
  roleAge,
494
923
  peers,
495
- (rect, m) => {
924
+ (rect, m, intersecting) => {
496
925
  if (m) {
497
926
  maturedLeaders.add(rect.hash);
498
927
  }
499
- leaders.add(rect.hash);
928
+
929
+ const prev = leaders.get(rect.hash);
930
+
931
+ if (!prev || (intersecting && !prev.intersecting)) {
932
+ leaders.set(rect.hash, { intersecting });
933
+ }
500
934
  },
501
- point,
935
+ cursor[i],
502
936
  now,
503
937
  () => {
504
938
  if (maturedLeaders.size > i) {
@@ -509,13 +943,15 @@ export const getSamples = async (
509
943
  );
510
944
  }
511
945
 
512
- return [...leaders];
946
+ return leaders;
513
947
  };
514
948
 
515
- const fetchOne = async (iterator: IndexIterator<ReplicationRangeIndexable>) => {
949
+ const fetchOne = async <S extends Shape | undefined>(
950
+ iterator: IndexIterator<ReplicationRangeIndexable, S>,
951
+ ) => {
516
952
  const value = await iterator.next(1);
517
953
  await iterator.close();
518
- return value.results[0]?.value;
954
+ return value[0]?.value;
519
955
  };
520
956
 
521
957
  export const minimumWidthToCover = async (
@@ -566,22 +1002,21 @@ export const getCoverSet = async (properties: {
566
1002
  const eagerFetch =
567
1003
  properties.eager === true
568
1004
  ? 1000
569
- : properties.eager.unmaturedFetchCoverSize;
1005
+ : (properties.eager.unmaturedFetchCoverSize ?? 1000);
570
1006
 
571
1007
  // 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) {
1008
+ const iterator = peers.iterate({
1009
+ query: [
1010
+ new IntegerCompare({
1011
+ key: "timestamp",
1012
+ compare: Compare.GreaterOrEqual,
1013
+ value: BigInt(now - roleAge),
1014
+ }),
1015
+ ],
1016
+ });
1017
+ const rects = await iterator.next(eagerFetch);
1018
+ await iterator.close();
1019
+ for (const rect of rects) {
585
1020
  ret.add(rect.value.hash);
586
1021
  }
587
1022
  }
@@ -713,24 +1148,155 @@ export const getCoverSet = async (properties: {
713
1148
  start instanceof PublicSignKey && ret.add(start.hashcode());
714
1149
  return ret;
715
1150
  };
1151
+ /* export const getReplicationDiff = (changes: ReplicationChange) => {
1152
+ // reduce the change set to only regions that are changed for each peer
1153
+ // i.e. subtract removed regions from added regions, and vice versa
1154
+ const result = new Map<string, { range: ReplicationRangeIndexable, added: boolean }[]>();
1155
+
1156
+ for (const addedChange of changes.added ?? []) {
1157
+ let prev = result.get(addedChange.hash) ?? [];
1158
+ for (const [_hash, ranges] of result.entries()) {
1159
+ for (const r of ranges) {
1160
+
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+ */
1166
+
1167
+ const matchRangeQuery = (range: ReplicationRangeIndexable) => {
1168
+ let ors = [];
1169
+ ors.push(
1170
+ new And([
1171
+ new IntegerCompare({
1172
+ key: "coordinate",
1173
+ compare: "gte",
1174
+ value: range.start1,
1175
+ }),
1176
+ new IntegerCompare({
1177
+ key: "coordinate",
1178
+ compare: "lt",
1179
+ value: range.end1,
1180
+ }),
1181
+ ]),
1182
+ );
716
1183
 
717
- export const fetchOneFromPublicKey = async (
1184
+ ors.push(
1185
+ new And([
1186
+ new IntegerCompare({
1187
+ key: "coordinate",
1188
+ compare: "gte",
1189
+ value: range.start2,
1190
+ }),
1191
+ new IntegerCompare({
1192
+ key: "coordinate",
1193
+ compare: "lt",
1194
+ value: range.end2,
1195
+ }),
1196
+ ]),
1197
+ );
1198
+
1199
+ return new Or(ors);
1200
+ };
1201
+ export const toRebalance = (
1202
+ changes: ReplicationChanges,
1203
+ index: Index<EntryReplicated>,
1204
+ ): AsyncIterable<{ gid: string; entries: EntryReplicated[] }> => {
1205
+ const assignedRangesQuery = (changes: ReplicationChanges) => {
1206
+ let ors: Query[] = [];
1207
+ for (const change of changes) {
1208
+ const matchRange = matchRangeQuery(change.range);
1209
+ if (change.type === "updated") {
1210
+ // assuming a range is to be removed, is this entry still enoughly replicated
1211
+ const prevMatchRange = matchRangeQuery(change.prev);
1212
+ ors.push(prevMatchRange);
1213
+ ors.push(matchRange);
1214
+
1215
+ /* ors.push(
1216
+ new And([
1217
+ // not sufficiently replicated
1218
+ new IntegerCompare({
1219
+ key: "replicatorsMissingShifted",
1220
+ compare: Compare.Greater,
1221
+ value: HALF_MAX_U32 - 1, // + 1 since we are going to remove a replicator for this entry
1222
+ }),
1223
+
1224
+ // is not the current range
1225
+ new Not(matchRange),
1226
+
1227
+ // but was in the the previous range
1228
+ prevMatchRange,
1229
+ ]),
1230
+ ); */
1231
+ } else {
1232
+ ors.push(matchRange);
1233
+ }
1234
+ }
1235
+
1236
+ // entry is assigned to a range boundary, meaning it is due to be inspected
1237
+ ors.push(
1238
+ new BoolQuery({
1239
+ key: "assignedToRangeBoundary",
1240
+ value: true,
1241
+ }),
1242
+ );
1243
+
1244
+ // entry is not sufficiently replicated, and we are to still keep it
1245
+ return new Or(ors);
1246
+ };
1247
+
1248
+ return {
1249
+ [Symbol.asyncIterator]: async function* () {
1250
+ const iterator = index.iterate({
1251
+ query: assignedRangesQuery(changes),
1252
+ });
1253
+
1254
+ while (iterator.done() !== true) {
1255
+ const entries = await iterator.next(1000); // TODO choose right batch sizes here for optimal memory usage / speed
1256
+
1257
+ // TODO do we need this
1258
+ const grouped = await groupByGidSync(entries.map((x) => x.value));
1259
+
1260
+ for (const [gid, entries] of grouped.entries()) {
1261
+ yield { gid, entries };
1262
+ }
1263
+ }
1264
+ },
1265
+ };
1266
+ };
1267
+
1268
+ export const fetchOneFromPublicKey = async <
1269
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
1270
+ >(
718
1271
  publicKey: PublicSignKey,
719
1272
  index: Index<ReplicationRangeIndexable>,
720
1273
  roleAge: number,
721
1274
  now: number,
1275
+ options?: {
1276
+ shape: S;
1277
+ },
722
1278
  ) => {
723
- let result = await index.query(
724
- new SearchRequest({
1279
+ let iterator = index.iterate<S>(
1280
+ {
725
1281
  query: [new StringMatch({ key: "hash", value: publicKey.hashcode() })],
726
- fetch: 1,
727
- }),
1282
+ },
1283
+ options,
728
1284
  );
729
- let node = result.results[0]?.value;
1285
+ let result = await iterator.next(1);
1286
+ await iterator.close();
1287
+ let node = result[0]?.value;
730
1288
  if (node) {
731
1289
  if (!isMatured(node, now, roleAge)) {
732
1290
  const matured = await fetchOne(
733
- getClosestAround(index, node.start1, roleAge, now, false, false),
1291
+ getClosestAround<S>(
1292
+ index,
1293
+ node.start1,
1294
+ roleAge,
1295
+ now,
1296
+ false,
1297
+ false,
1298
+ options,
1299
+ ),
734
1300
  );
735
1301
  if (matured) {
736
1302
  node = matured;
@@ -740,16 +1306,24 @@ export const fetchOneFromPublicKey = async (
740
1306
  return node;
741
1307
  };
742
1308
 
743
- export const getStartAndEnd = async (
1309
+ export const getStartAndEnd = async <
1310
+ S extends (Shape & { timestamp: true }) | undefined,
1311
+ >(
744
1312
  peers: Index<ReplicationRangeIndexable>,
745
1313
  start: number | PublicSignKey | undefined | undefined,
746
1314
  widthToCoverScaled: number,
747
1315
  roleAge: number,
748
1316
  now: number,
749
1317
  intervalWidth: number,
750
- ) => {
1318
+ options?: { shape: S },
1319
+ ): Promise<{
1320
+ startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined;
1321
+ startLocation: number;
1322
+ endLocation: number;
1323
+ }> => {
751
1324
  // find a good starting point
752
- let startNode: ReplicationRangeIndexable | undefined = undefined;
1325
+ let startNode: ReturnTypeFromShape<ReplicationRangeIndexable, S> | undefined =
1326
+ undefined;
753
1327
  let startLocation: number | undefined = undefined;
754
1328
 
755
1329
  const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
@@ -761,12 +1335,19 @@ export const getStartAndEnd = async (
761
1335
  now,
762
1336
  false,
763
1337
  true,
1338
+ options,
764
1339
  );
765
1340
  };
766
1341
 
767
1342
  if (start instanceof PublicSignKey) {
768
1343
  // start at our node (local first)
769
- startNode = await fetchOneFromPublicKey(start, peers, roleAge, now);
1344
+ startNode = await fetchOneFromPublicKey(
1345
+ start,
1346
+ peers,
1347
+ roleAge,
1348
+ now,
1349
+ options,
1350
+ );
770
1351
  if (!startNode) {
771
1352
  // fetch randomly
772
1353
  await nodeFromPoint();
@@ -810,22 +1391,26 @@ export const getStartAndEnd = async (
810
1391
  };
811
1392
  };
812
1393
 
813
- export const fetchOneClosest = (
1394
+ export const fetchOneClosest = <
1395
+ S extends (Shape & { timestamp: true }) | undefined = undefined,
1396
+ >(
814
1397
  peers: Index<ReplicationRangeIndexable>,
815
1398
  point: number,
816
1399
  roleAge: number,
817
1400
  now: number,
818
1401
  includeStrictBelow: boolean,
819
1402
  includeStrictAbove: boolean,
1403
+ options?: { shape?: S },
820
1404
  ) => {
821
1405
  return fetchOne(
822
- getClosestAround(
1406
+ getClosestAround<S>(
823
1407
  peers,
824
1408
  point,
825
1409
  roleAge,
826
1410
  now,
827
1411
  includeStrictBelow,
828
1412
  includeStrictAbove,
1413
+ options,
829
1414
  ),
830
1415
  );
831
1416
  };