@cloudbitmaps/core 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/codec.d.ts +11 -0
- package/dist/core/engine.d.ts +20 -0
- package/dist/index.js +122 -19
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/core/codec.d.ts
CHANGED
|
@@ -52,6 +52,17 @@ export interface CodecBitmap {
|
|
|
52
52
|
/** Ascending iterator over the set values. */
|
|
53
53
|
[Symbol.iterator](): IterableIterator<number>;
|
|
54
54
|
toArray(): number[];
|
|
55
|
+
/**
|
|
56
|
+
* The set's values as one ascending `Uint32Array` the caller owns: what a batch read converts a chunk with, in one
|
|
57
|
+
* native step instead of one iterator step per value.
|
|
58
|
+
*
|
|
59
|
+
* **Optional.** A codec without it is read through its iterator, which is correct and slower.
|
|
60
|
+
*
|
|
61
|
+
* **It must return a NEW array on every call, which the caller owns:** the engine adds the chunk's base to each
|
|
62
|
+
* value in place and hands the array on, so an array that is shared, cached or a view of the bitmap's own storage
|
|
63
|
+
* would be corrupted, and would corrupt the bitmap. It holds exactly what the iterator yields, ascending.
|
|
64
|
+
*/
|
|
65
|
+
toUint32Array?(): Uint32Array;
|
|
55
66
|
/**
|
|
56
67
|
* The largest value in the set, or `undefined` when empty.
|
|
57
68
|
*
|
package/dist/core/engine.d.ts
CHANGED
|
@@ -128,6 +128,13 @@ export declare class SegmentEngine {
|
|
|
128
128
|
iterate(seg: SegmentRef, range?: IdRange): AsyncGenerator<number>;
|
|
129
129
|
private iterateAll;
|
|
130
130
|
private iterateRange;
|
|
131
|
+
/**
|
|
132
|
+
* {@link iterate}, one `Uint32Array` per chunk instead of one id at a time: the same chunks, in the same order, with
|
|
133
|
+
* the same ids (a range cut at its edges the same way), and no array for a chunk that holds none of them. The
|
|
134
|
+
* reads, the budget charge and the window are the per-id read's. Each array is at most 65,536 ids (256 KiB) and is
|
|
135
|
+
* the caller's to keep.
|
|
136
|
+
*/
|
|
137
|
+
iterateBatches(seg: SegmentRef, range?: IdRange): AsyncGenerator<Uint32Array>;
|
|
131
138
|
/**
|
|
132
139
|
* `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
|
|
133
140
|
*
|
|
@@ -176,7 +183,20 @@ export declare class SegmentEngine {
|
|
|
176
183
|
* the cost scales with the segment being filtered rather than with the size of the suppression list.
|
|
177
184
|
*/
|
|
178
185
|
andNot(seg: SegmentRef, excludes: readonly SegmentRef[], options?: Omit<CombineOptions, 'exclude'>): AsyncGenerator<number>;
|
|
186
|
+
/** {@link intersect}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
187
|
+
intersectBatches(segs: readonly SegmentRef[], options?: CombineOptions): AsyncGenerator<Uint32Array>;
|
|
188
|
+
/** {@link union}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
189
|
+
unionBatches(segs: readonly SegmentRef[], options?: CombineOptions): AsyncGenerator<Uint32Array>;
|
|
190
|
+
/** {@link andNot}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
191
|
+
andNotBatches(seg: SegmentRef, excludes: readonly SegmentRef[], options?: Omit<CombineOptions, 'exclude'>): AsyncGenerator<Uint32Array>;
|
|
192
|
+
/**
|
|
193
|
+
* Everything a combine decides before it reads a chunk: validation, each operand's keys and generation, the
|
|
194
|
+
* absent-operand refusal, key alignment, the budget and the metric. `null` when the range is empty. The per-id
|
|
195
|
+
* read and the batch read share it, so the two cannot disagree about what is read or charged.
|
|
196
|
+
*/
|
|
197
|
+
private combinePlan;
|
|
179
198
|
private combine;
|
|
199
|
+
private combineBatches;
|
|
180
200
|
/**
|
|
181
201
|
* The AND (or OR) of one chunk key across all operands, minus the excludes that hold it, or `null` if empty.
|
|
182
202
|
* Operand chunks are fetched **in parallel** (the spec's parallel byte-range reads).
|
package/dist/index.js
CHANGED
|
@@ -372,6 +372,65 @@ function keysWithin(keys, w) {
|
|
|
372
372
|
};
|
|
373
373
|
return keys.slice(firstAtLeast(w.loKey), firstAtLeast(w.hiKey + 1));
|
|
374
374
|
}
|
|
375
|
+
var CombineWindow = class {
|
|
376
|
+
constructor(keys, limit, read) {
|
|
377
|
+
this.keys = keys;
|
|
378
|
+
this.limit = limit;
|
|
379
|
+
this.read = read;
|
|
380
|
+
this.fill();
|
|
381
|
+
}
|
|
382
|
+
keys;
|
|
383
|
+
limit;
|
|
384
|
+
read;
|
|
385
|
+
inFlight = [];
|
|
386
|
+
next_ = 0;
|
|
387
|
+
taken = 0;
|
|
388
|
+
fill() {
|
|
389
|
+
const width = Math.min(
|
|
390
|
+
this.limit,
|
|
391
|
+
Math.max(COMBINE_WINDOW_START, 2 ** Math.min(this.taken, 30))
|
|
392
|
+
);
|
|
393
|
+
while (this.next_ < this.keys.length && this.inFlight.length < width) {
|
|
394
|
+
const key = this.keys[this.next_++];
|
|
395
|
+
this.inFlight.push(
|
|
396
|
+
this.read(key).then(
|
|
397
|
+
(result) => ({ key, result }),
|
|
398
|
+
(error) => ({ key, result: null, error })
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** The next key's chunk in ascending key order, `undefined` when the keys are used up; throws that key's error. */
|
|
404
|
+
async next() {
|
|
405
|
+
if (this.inFlight.length === 0) return void 0;
|
|
406
|
+
const slot = await this.inFlight.shift();
|
|
407
|
+
this.taken += 1;
|
|
408
|
+
this.fill();
|
|
409
|
+
if (slot.error !== void 0) throw slot.error;
|
|
410
|
+
return slot;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
function chunkIds(chunk, chunkKey, w) {
|
|
414
|
+
let rem = chunk.toUint32Array ? chunk.toUint32Array() : Uint32Array.from(chunk);
|
|
415
|
+
if (w !== null && isEdge(chunkKey, w)) {
|
|
416
|
+
const from = chunkKey === w.loKey ? w.loRem : 0;
|
|
417
|
+
const to = chunkKey === w.hiKey ? w.hiRem : MAX_REMAINDER;
|
|
418
|
+
rem = rem.slice(firstAbove(rem, from - 1), firstAbove(rem, to));
|
|
419
|
+
}
|
|
420
|
+
const base = chunkKey * CHUNK_COUNT;
|
|
421
|
+
for (let i = 0; i < rem.length; i++) rem[i] = base + (rem[i] & MAX_REMAINDER);
|
|
422
|
+
return rem;
|
|
423
|
+
}
|
|
424
|
+
function firstAbove(a, value) {
|
|
425
|
+
let lo = 0;
|
|
426
|
+
let hi = a.length;
|
|
427
|
+
while (lo < hi) {
|
|
428
|
+
const mid = lo + hi >>> 1;
|
|
429
|
+
if (a[mid] <= value) lo = mid + 1;
|
|
430
|
+
else hi = mid;
|
|
431
|
+
}
|
|
432
|
+
return lo;
|
|
433
|
+
}
|
|
375
434
|
var SegmentEngine = class {
|
|
376
435
|
storage;
|
|
377
436
|
cache;
|
|
@@ -507,6 +566,26 @@ var SegmentEngine = class {
|
|
|
507
566
|
}
|
|
508
567
|
}
|
|
509
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* {@link iterate}, one `Uint32Array` per chunk instead of one id at a time: the same chunks, in the same order, with
|
|
571
|
+
* the same ids (a range cut at its edges the same way), and no array for a chunk that holds none of them. The
|
|
572
|
+
* reads, the budget charge and the window are the per-id read's. Each array is at most 65,536 ids (256 KiB) and is
|
|
573
|
+
* the caller's to keep.
|
|
574
|
+
*/
|
|
575
|
+
async *iterateBatches(seg, range) {
|
|
576
|
+
const w = windowOf(range);
|
|
577
|
+
if (w === "empty") return;
|
|
578
|
+
const chunkKeys = w === null ? await this.chunkKeys(seg) : keysWithin(await this.chunkKeys(seg), w);
|
|
579
|
+
checkBudget(this.budget, chunkKeys.length, "iterate");
|
|
580
|
+
const gen = await this.cacheVersion(seg);
|
|
581
|
+
const window = this.chunkWindow(seg, chunkKeys, gen, true);
|
|
582
|
+
for (const chunkKey of chunkKeys) {
|
|
583
|
+
const chunk = await window.take();
|
|
584
|
+
if (chunk === null) continue;
|
|
585
|
+
const ids = chunkIds(chunk, chunkKey, w);
|
|
586
|
+
if (ids.length > 0) yield ids;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
510
589
|
/**
|
|
511
590
|
* `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
|
|
512
591
|
*
|
|
@@ -561,7 +640,24 @@ var SegmentEngine = class {
|
|
|
561
640
|
andNot(seg, excludes, options) {
|
|
562
641
|
return this.combine([seg], excludes, "all", "andNot", options);
|
|
563
642
|
}
|
|
564
|
-
|
|
643
|
+
/** {@link intersect}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
644
|
+
intersectBatches(segs, options) {
|
|
645
|
+
return this.combineBatches(segs, options?.exclude ?? [], "all", "intersect", options);
|
|
646
|
+
}
|
|
647
|
+
/** {@link union}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
648
|
+
unionBatches(segs, options) {
|
|
649
|
+
return this.combineBatches(segs, options?.exclude ?? [], "any", "union", options);
|
|
650
|
+
}
|
|
651
|
+
/** {@link andNot}, one `Uint32Array` per chunk — see {@link iterateBatches}. */
|
|
652
|
+
andNotBatches(seg, excludes, options) {
|
|
653
|
+
return this.combineBatches([seg], excludes, "all", "andNot", options);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Everything a combine decides before it reads a chunk: validation, each operand's keys and generation, the
|
|
657
|
+
* absent-operand refusal, key alignment, the budget and the metric. `null` when the range is empty. The per-id
|
|
658
|
+
* read and the batch read share it, so the two cannot disagree about what is read or charged.
|
|
659
|
+
*/
|
|
660
|
+
async combinePlan(segs, excludeSegs, mode, op, options) {
|
|
565
661
|
if (segs.length === 0) {
|
|
566
662
|
throw new ValidationError(`${op} requires at least one segment`);
|
|
567
663
|
}
|
|
@@ -576,7 +672,7 @@ var SegmentEngine = class {
|
|
|
576
672
|
}
|
|
577
673
|
const budget = resolvePerOpBudget(options?.budget, this.budget);
|
|
578
674
|
const w = windowOf(options);
|
|
579
|
-
if (w === "empty") return;
|
|
675
|
+
if (w === "empty") return null;
|
|
580
676
|
const extract = async (seg) => {
|
|
581
677
|
const all = await this.chunkKeys(seg);
|
|
582
678
|
const gen = await this.cacheVersion(seg);
|
|
@@ -621,24 +717,20 @@ var SegmentEngine = class {
|
|
|
621
717
|
skippedChunks: distinctKeys.size - common.length
|
|
622
718
|
});
|
|
623
719
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const fill = () => {
|
|
632
|
-
const width = Math.min(limit, Math.max(COMBINE_WINDOW_START, 2 ** Math.min(taken, 30)));
|
|
633
|
-
while (next < common.length && inFlight.length < width)
|
|
634
|
-
inFlight.push(startAt(common[next++]));
|
|
720
|
+
return {
|
|
721
|
+
w,
|
|
722
|
+
window: new CombineWindow(
|
|
723
|
+
common,
|
|
724
|
+
limit,
|
|
725
|
+
(key) => this.combineChunk(operands, excludes, mode, key)
|
|
726
|
+
)
|
|
635
727
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
728
|
+
}
|
|
729
|
+
async *combine(segs, excludeSegs, mode, op, options) {
|
|
730
|
+
const plan = await this.combinePlan(segs, excludeSegs, mode, op, options);
|
|
731
|
+
if (plan === null) return;
|
|
732
|
+
const { w, window } = plan;
|
|
733
|
+
for (let slot = await window.next(); slot !== void 0; slot = await window.next()) {
|
|
642
734
|
if (slot.result && !slot.result.isEmpty) {
|
|
643
735
|
if (w !== null && isEdge(slot.key, w)) {
|
|
644
736
|
for (const id of edgeIds(slot.result, slot.key, w)) yield id;
|
|
@@ -648,6 +740,17 @@ var SegmentEngine = class {
|
|
|
648
740
|
}
|
|
649
741
|
}
|
|
650
742
|
}
|
|
743
|
+
async *combineBatches(segs, excludeSegs, mode, op, options) {
|
|
744
|
+
const plan = await this.combinePlan(segs, excludeSegs, mode, op, options);
|
|
745
|
+
if (plan === null) return;
|
|
746
|
+
const { w, window } = plan;
|
|
747
|
+
for (let slot = await window.next(); slot !== void 0; slot = await window.next()) {
|
|
748
|
+
if (slot.result) {
|
|
749
|
+
const ids = chunkIds(slot.result, slot.key, w);
|
|
750
|
+
if (ids.length > 0) yield ids;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
651
754
|
/**
|
|
652
755
|
* The AND (or OR) of one chunk key across all operands, minus the excludes that hold it, or `null` if empty.
|
|
653
756
|
* Operand chunks are fetched **in parallel** (the spec's parallel byte-range reads).
|