@cloudbitmaps/roaring 0.3.0 → 0.4.1
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/bin/compact-segments.js +4 -3
- package/dist/bin/compact-segments.js.map +1 -1
- package/dist/bin/export-segments.js +1 -1
- package/dist/{chunk-HRS3Q6CJ.js → chunk-OVRYHJSQ.js} +135 -22
- package/dist/chunk-OVRYHJSQ.js.map +1 -0
- package/dist/index.cjs +125 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -21
- package/dist/index.d.ts +102 -21
- package/dist/index.js +125 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-HRS3Q6CJ.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink,
|
|
1
|
+
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,7 +21,16 @@ type BulkLoad = typeof bulkLoadCrbmGeneration$1;
|
|
|
21
21
|
type CompactSegment = typeof compactSegment$1;
|
|
22
22
|
type RunCompactionCycle = typeof runCompactionCycle$1;
|
|
23
23
|
type RunExport = typeof runExport$1;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* {@link coreBulkLoad} with the roaring codec **and a real clock** pre-bound.
|
|
26
|
+
*
|
|
27
|
+
* The clock is what makes a large load **cooperative**: bulk-load yields the event loop periodically instead of
|
|
28
|
+
* stalling the process for its whole duration (measured end-to-end at 450 ms of unbroken blocking for a 1M-id
|
|
29
|
+
* load, now ~19 ms worst case). `core/` cannot
|
|
30
|
+
* default it — it is timer-free by lint, which is precisely why waiting goes through the `Clock` seam — so the
|
|
31
|
+
* flavor package supplies it, exactly as it supplies the codec. A caller who passes their own clock (a virtual
|
|
32
|
+
* one in a simulation, say) keeps it.
|
|
33
|
+
*/
|
|
25
34
|
declare const bulkLoadCrbmGeneration: BulkLoad;
|
|
26
35
|
/** {@link coreCompactSegment} with the roaring codec pre-bound. */
|
|
27
36
|
declare const compactSegment: CompactSegment;
|
|
@@ -191,9 +200,18 @@ interface CloudRoaringOptions {
|
|
|
191
200
|
*/
|
|
192
201
|
readonly warmReadConsistency?: 'strong' | 'eventual';
|
|
193
202
|
/**
|
|
194
|
-
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **
|
|
195
|
-
*
|
|
196
|
-
*
|
|
203
|
+
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **4**
|
|
204
|
+
* (`DEFAULT_WRITE_CONCURRENCY`); set it to `1` for strictly serial writes.
|
|
205
|
+
*
|
|
206
|
+
* Distinct chunks are independent OCC rows, so fanning them out cannot make them conflict with each other.
|
|
207
|
+
* The bound exists for the *backend*: a provisioned-capacity store answers a burst by throttling, and 4 stays
|
|
208
|
+
* comfortably inside what the transient-retry path absorbs (measured 8x below the first observed failure).
|
|
209
|
+
* Raise it if your backend is provisioned for the fan-out — on-demand DynamoDB, or a connection pool sized to
|
|
210
|
+
* match. A pool *smaller* than this needs no action: `pg` and `mysql2` both queue rather than reject, so the
|
|
211
|
+
* flusher simply degrades toward serial.
|
|
212
|
+
*
|
|
213
|
+
* As before, a mid-flush failure can leave a **partial result**; `addMany`/`removeMany` are not atomic. What
|
|
214
|
+
* concurrency changes is only how much of the batch may already have landed when the first error surfaces.
|
|
197
215
|
*/
|
|
198
216
|
readonly writeConcurrency?: number;
|
|
199
217
|
/**
|
|
@@ -393,15 +411,38 @@ declare class CloudRoaring {
|
|
|
393
411
|
static estimateCost(input: EstimateInput): CostReport;
|
|
394
412
|
}
|
|
395
413
|
/**
|
|
396
|
-
* A handle bound to one segment — the
|
|
414
|
+
* A handle bound to one segment — the read/write ops.
|
|
397
415
|
*
|
|
398
416
|
* **IDs must be integers in `[0, 2^32)`** (dense 32-bit). A non-integer / negative / out-of-range id
|
|
399
417
|
* throws {@link ValidationError}.
|
|
400
418
|
*
|
|
401
|
-
* **`addMany`/`removeMany`/`intersectInto` are not atomic across chunks**: ids are grouped by chunk and
|
|
419
|
+
* **`addMany`/`removeMany`/`intersectInto`/`unionInto`/`andNotInto` are not atomic across chunks**: ids are grouped by chunk and
|
|
402
420
|
* applied one chunk at a time, so if a later chunk fails (e.g. {@link WriteConflictError} after retries)
|
|
403
421
|
* earlier chunks are already applied. Within a single chunk the update is atomic.
|
|
404
422
|
*/
|
|
423
|
+
/** Options common to every chunk-aligned combine (`intersect` / `union` / `andNot`). */
|
|
424
|
+
interface BaseCombineOptions {
|
|
425
|
+
/** Max chunk keys resolved concurrently — bounds the Cold footprint. A positive integer. */
|
|
426
|
+
readonly concurrency?: number;
|
|
427
|
+
/** Override the store's per-op denial-of-wallet budget for this call (`false` lifts it). */
|
|
428
|
+
readonly budget?: BudgetOption;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* A combine that can also subtract.
|
|
432
|
+
*
|
|
433
|
+
* `exclude` is why suppression does not need an intermediate segment: applied here it folds into the same
|
|
434
|
+
* chunk-aligned pass, and each exclude is read **only at the keys that survived** — so a large global opt-out
|
|
435
|
+
* list costs reads proportional to the audience, not to itself.
|
|
436
|
+
*/
|
|
437
|
+
interface CombineOptions extends BaseCombineOptions {
|
|
438
|
+
/** Segments whose ids are subtracted from the result. */
|
|
439
|
+
readonly exclude?: Segment[];
|
|
440
|
+
}
|
|
441
|
+
/** {@link CombineOptions} plus the write batching used by the `*Into` variants. */
|
|
442
|
+
interface CombineIntoOptions extends CombineOptions {
|
|
443
|
+
/** Ids buffered per `addMany` while materializing into `dest`. */
|
|
444
|
+
readonly batchSize?: number;
|
|
445
|
+
}
|
|
405
446
|
declare class Segment {
|
|
406
447
|
private readonly engine;
|
|
407
448
|
private readonly ref;
|
|
@@ -416,12 +457,24 @@ declare class Segment {
|
|
|
416
457
|
private timed;
|
|
417
458
|
/** Add an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
418
459
|
add(id: number): Promise<void>;
|
|
419
|
-
/**
|
|
420
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
462
|
+
*
|
|
463
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
464
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
465
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
466
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
467
|
+
*
|
|
468
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
469
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
470
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
471
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
472
|
+
*/
|
|
473
|
+
addMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
421
474
|
/** Remove an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
422
475
|
remove(id: number): Promise<void>;
|
|
423
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
424
|
-
removeMany(ids: Iterable<number>): Promise<void>;
|
|
476
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
477
|
+
removeMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
425
478
|
has(id: number): Promise<boolean>;
|
|
426
479
|
count(): Promise<number>;
|
|
427
480
|
iterate(): AsyncIterable<number>;
|
|
@@ -434,16 +487,44 @@ declare class Segment {
|
|
|
434
487
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
435
488
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
436
489
|
*/
|
|
437
|
-
intersect(others: Segment[], options?: {
|
|
438
|
-
concurrency?: number;
|
|
439
|
-
budget?: BudgetOption;
|
|
440
|
-
}): AsyncIterable<number>;
|
|
441
490
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
491
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
492
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
444
493
|
*/
|
|
445
|
-
|
|
446
|
-
|
|
494
|
+
private refsIn;
|
|
495
|
+
intersect(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
496
|
+
/**
|
|
497
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
498
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
499
|
+
*/
|
|
500
|
+
intersectInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
501
|
+
/**
|
|
502
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
503
|
+
*
|
|
504
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
505
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
506
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
507
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
508
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
509
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
510
|
+
*/
|
|
511
|
+
union(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
512
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
513
|
+
unionInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
514
|
+
/**
|
|
515
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
516
|
+
*
|
|
517
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
518
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
519
|
+
* suppression list: at most one read per surviving key of `this`, so subtracting a 61,000-chunk global
|
|
520
|
+
* opt-out list from a 40-chunk audience costs at most 40 reads, not 61,000.
|
|
521
|
+
*
|
|
522
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
523
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
524
|
+
*/
|
|
525
|
+
andNot(excludes: Segment[], options?: BaseCombineOptions): AsyncIterable<number>;
|
|
526
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
527
|
+
andNotInto(dest: Segment, excludes: Segment[], options?: BaseCombineOptions & {
|
|
447
528
|
batchSize?: number;
|
|
448
529
|
}): Promise<void>;
|
|
449
530
|
/**
|
|
@@ -459,6 +540,6 @@ declare class Segment {
|
|
|
459
540
|
}
|
|
460
541
|
|
|
461
542
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
462
|
-
declare const VERSION = "0.
|
|
543
|
+
declare const VERSION = "0.4.1";
|
|
463
544
|
|
|
464
|
-
export { CloudRoaring, type CloudRoaringOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
545
|
+
export { type BaseCombineOptions, CloudRoaring, type CloudRoaringOptions, type CombineIntoOptions, type CombineOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink,
|
|
1
|
+
import { bulkLoadCrbmGeneration as bulkLoadCrbmGeneration$1, compactSegment as compactSegment$1, runCompactionCycle as runCompactionCycle$1, runExport as runExport$1, CodecBitmap, CodecInterface, BudgetOption, IColdDriver, ColdChunkSource, IWarmDriver, IRegistryDriver, IKeystore, Clock, Rng, RetryPolicy, IMetricsSink, SegmentEngine, SegmentRef, PricingProfile, Workload, Topology, CostReport, IAuditSink, CompactionOptions, CompactionResult, ConsistencyReport, ExportSink, ExportOptions, ExportManifest, EstimateInput } from '@cloudbitmaps/core';
|
|
2
2
|
export * from '@cloudbitmaps/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,7 +21,16 @@ type BulkLoad = typeof bulkLoadCrbmGeneration$1;
|
|
|
21
21
|
type CompactSegment = typeof compactSegment$1;
|
|
22
22
|
type RunCompactionCycle = typeof runCompactionCycle$1;
|
|
23
23
|
type RunExport = typeof runExport$1;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* {@link coreBulkLoad} with the roaring codec **and a real clock** pre-bound.
|
|
26
|
+
*
|
|
27
|
+
* The clock is what makes a large load **cooperative**: bulk-load yields the event loop periodically instead of
|
|
28
|
+
* stalling the process for its whole duration (measured end-to-end at 450 ms of unbroken blocking for a 1M-id
|
|
29
|
+
* load, now ~19 ms worst case). `core/` cannot
|
|
30
|
+
* default it — it is timer-free by lint, which is precisely why waiting goes through the `Clock` seam — so the
|
|
31
|
+
* flavor package supplies it, exactly as it supplies the codec. A caller who passes their own clock (a virtual
|
|
32
|
+
* one in a simulation, say) keeps it.
|
|
33
|
+
*/
|
|
25
34
|
declare const bulkLoadCrbmGeneration: BulkLoad;
|
|
26
35
|
/** {@link coreCompactSegment} with the roaring codec pre-bound. */
|
|
27
36
|
declare const compactSegment: CompactSegment;
|
|
@@ -191,9 +200,18 @@ interface CloudRoaringOptions {
|
|
|
191
200
|
*/
|
|
192
201
|
readonly warmReadConsistency?: 'strong' | 'eventual';
|
|
193
202
|
/**
|
|
194
|
-
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **
|
|
195
|
-
*
|
|
196
|
-
*
|
|
203
|
+
* Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **4**
|
|
204
|
+
* (`DEFAULT_WRITE_CONCURRENCY`); set it to `1` for strictly serial writes.
|
|
205
|
+
*
|
|
206
|
+
* Distinct chunks are independent OCC rows, so fanning them out cannot make them conflict with each other.
|
|
207
|
+
* The bound exists for the *backend*: a provisioned-capacity store answers a burst by throttling, and 4 stays
|
|
208
|
+
* comfortably inside what the transient-retry path absorbs (measured 8x below the first observed failure).
|
|
209
|
+
* Raise it if your backend is provisioned for the fan-out — on-demand DynamoDB, or a connection pool sized to
|
|
210
|
+
* match. A pool *smaller* than this needs no action: `pg` and `mysql2` both queue rather than reject, so the
|
|
211
|
+
* flusher simply degrades toward serial.
|
|
212
|
+
*
|
|
213
|
+
* As before, a mid-flush failure can leave a **partial result**; `addMany`/`removeMany` are not atomic. What
|
|
214
|
+
* concurrency changes is only how much of the batch may already have landed when the first error surfaces.
|
|
197
215
|
*/
|
|
198
216
|
readonly writeConcurrency?: number;
|
|
199
217
|
/**
|
|
@@ -393,15 +411,38 @@ declare class CloudRoaring {
|
|
|
393
411
|
static estimateCost(input: EstimateInput): CostReport;
|
|
394
412
|
}
|
|
395
413
|
/**
|
|
396
|
-
* A handle bound to one segment — the
|
|
414
|
+
* A handle bound to one segment — the read/write ops.
|
|
397
415
|
*
|
|
398
416
|
* **IDs must be integers in `[0, 2^32)`** (dense 32-bit). A non-integer / negative / out-of-range id
|
|
399
417
|
* throws {@link ValidationError}.
|
|
400
418
|
*
|
|
401
|
-
* **`addMany`/`removeMany`/`intersectInto` are not atomic across chunks**: ids are grouped by chunk and
|
|
419
|
+
* **`addMany`/`removeMany`/`intersectInto`/`unionInto`/`andNotInto` are not atomic across chunks**: ids are grouped by chunk and
|
|
402
420
|
* applied one chunk at a time, so if a later chunk fails (e.g. {@link WriteConflictError} after retries)
|
|
403
421
|
* earlier chunks are already applied. Within a single chunk the update is atomic.
|
|
404
422
|
*/
|
|
423
|
+
/** Options common to every chunk-aligned combine (`intersect` / `union` / `andNot`). */
|
|
424
|
+
interface BaseCombineOptions {
|
|
425
|
+
/** Max chunk keys resolved concurrently — bounds the Cold footprint. A positive integer. */
|
|
426
|
+
readonly concurrency?: number;
|
|
427
|
+
/** Override the store's per-op denial-of-wallet budget for this call (`false` lifts it). */
|
|
428
|
+
readonly budget?: BudgetOption;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* A combine that can also subtract.
|
|
432
|
+
*
|
|
433
|
+
* `exclude` is why suppression does not need an intermediate segment: applied here it folds into the same
|
|
434
|
+
* chunk-aligned pass, and each exclude is read **only at the keys that survived** — so a large global opt-out
|
|
435
|
+
* list costs reads proportional to the audience, not to itself.
|
|
436
|
+
*/
|
|
437
|
+
interface CombineOptions extends BaseCombineOptions {
|
|
438
|
+
/** Segments whose ids are subtracted from the result. */
|
|
439
|
+
readonly exclude?: Segment[];
|
|
440
|
+
}
|
|
441
|
+
/** {@link CombineOptions} plus the write batching used by the `*Into` variants. */
|
|
442
|
+
interface CombineIntoOptions extends CombineOptions {
|
|
443
|
+
/** Ids buffered per `addMany` while materializing into `dest`. */
|
|
444
|
+
readonly batchSize?: number;
|
|
445
|
+
}
|
|
405
446
|
declare class Segment {
|
|
406
447
|
private readonly engine;
|
|
407
448
|
private readonly ref;
|
|
@@ -416,12 +457,24 @@ declare class Segment {
|
|
|
416
457
|
private timed;
|
|
417
458
|
/** Add an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
418
459
|
add(id: number): Promise<void>;
|
|
419
|
-
/**
|
|
420
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
462
|
+
*
|
|
463
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
464
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
465
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
466
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
467
|
+
*
|
|
468
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
469
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
470
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
471
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
472
|
+
*/
|
|
473
|
+
addMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
421
474
|
/** Remove an id (integer in `[0, 2^32)`). Throws {@link ValidationError} on a bad id. */
|
|
422
475
|
remove(id: number): Promise<void>;
|
|
423
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
424
|
-
removeMany(ids: Iterable<number>): Promise<void>;
|
|
476
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
477
|
+
removeMany(ids: Iterable<number> | AsyncIterable<number>): Promise<void>;
|
|
425
478
|
has(id: number): Promise<boolean>;
|
|
426
479
|
count(): Promise<number>;
|
|
427
480
|
iterate(): AsyncIterable<number>;
|
|
@@ -434,16 +487,44 @@ declare class Segment {
|
|
|
434
487
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
435
488
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
436
489
|
*/
|
|
437
|
-
intersect(others: Segment[], options?: {
|
|
438
|
-
concurrency?: number;
|
|
439
|
-
budget?: BudgetOption;
|
|
440
|
-
}): AsyncIterable<number>;
|
|
441
490
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
491
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
492
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
444
493
|
*/
|
|
445
|
-
|
|
446
|
-
|
|
494
|
+
private refsIn;
|
|
495
|
+
intersect(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
496
|
+
/**
|
|
497
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
498
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
499
|
+
*/
|
|
500
|
+
intersectInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
501
|
+
/**
|
|
502
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
503
|
+
*
|
|
504
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
505
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
506
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
507
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
508
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
509
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
510
|
+
*/
|
|
511
|
+
union(others: Segment[], options?: CombineOptions): AsyncIterable<number>;
|
|
512
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
513
|
+
unionInto(dest: Segment, others: Segment[], options?: CombineIntoOptions): Promise<void>;
|
|
514
|
+
/**
|
|
515
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
516
|
+
*
|
|
517
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
518
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
519
|
+
* suppression list: at most one read per surviving key of `this`, so subtracting a 61,000-chunk global
|
|
520
|
+
* opt-out list from a 40-chunk audience costs at most 40 reads, not 61,000.
|
|
521
|
+
*
|
|
522
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
523
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
524
|
+
*/
|
|
525
|
+
andNot(excludes: Segment[], options?: BaseCombineOptions): AsyncIterable<number>;
|
|
526
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
527
|
+
andNotInto(dest: Segment, excludes: Segment[], options?: BaseCombineOptions & {
|
|
447
528
|
batchSize?: number;
|
|
448
529
|
}): Promise<void>;
|
|
449
530
|
/**
|
|
@@ -459,6 +540,6 @@ declare class Segment {
|
|
|
459
540
|
}
|
|
460
541
|
|
|
461
542
|
/** Package version marker. Kept in sync with package.json at release. */
|
|
462
|
-
declare const VERSION = "0.
|
|
543
|
+
declare const VERSION = "0.4.1";
|
|
463
544
|
|
|
464
|
-
export { CloudRoaring, type CloudRoaringOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
545
|
+
export { type BaseCombineOptions, CloudRoaring, type CloudRoaringOptions, type CombineIntoOptions, type CombineOptions, type EraseSubjectResult, SafeBitmap, Segment, type SegmentOptions, type SubjectErasureEntry, type SubjectReport, type SubjectSegmentRef, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
package/dist/index.js
CHANGED
|
@@ -92,12 +92,8 @@ var roaringCodec = {
|
|
|
92
92
|
fromValues: (values) => SafeBitmap.fromValues(values),
|
|
93
93
|
safeDeserialize: (bytes, maxBytes) => SafeBitmap.safeDeserialize(bytes, maxBytes)
|
|
94
94
|
};
|
|
95
|
-
var bulkLoadCrbmGeneration = (driver, key, ids, options = {}) => bulkLoadCrbmGeneration$1(driver, key, ids, { ...options, codec: options.codec ?? roaringCodec });
|
|
96
|
-
var compactSegment = (ref, deps, options) => compactSegment$1(ref, { ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
97
|
-
var runCompactionCycle = (deps, options) => runCompactionCycle$1({ ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
98
|
-
var runExport = (reader, registry, sink, options = {}) => runExport$1(reader, registry, sink, { ...options, codec: options.codec ?? roaringCodec });
|
|
99
95
|
|
|
100
|
-
// src/
|
|
96
|
+
// src/system-clock.ts
|
|
101
97
|
var SystemClock = class {
|
|
102
98
|
now() {
|
|
103
99
|
return Date.now();
|
|
@@ -108,7 +104,45 @@ var SystemClock = class {
|
|
|
108
104
|
setTimeout(resolve, ms);
|
|
109
105
|
});
|
|
110
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* `setImmediate`, and it has to be — this is the one place where the choice of primitive is the whole feature.
|
|
109
|
+
*
|
|
110
|
+
* A long CPU-bound loop in `core/` (bulk-load is the one that matters) periodically hands the loop back so a
|
|
111
|
+
* co-resident HTTP server keeps answering. Measured on the **synthetic yield-primitive benchmark** — a
|
|
112
|
+
* 61,035-iteration loop yielding every 1,024, isolating the primitive rather than timing a real load (the
|
|
113
|
+
* end-to-end figures are 450 ms → 19 ms; see `cooperative.ts`, which lists all three experiments):
|
|
114
|
+
*
|
|
115
|
+
* ```text
|
|
116
|
+
* no yield 568 ms wall 568.0 ms worst event-loop gap
|
|
117
|
+
* sleep(0) 555 ms wall 555.2 ms ← a microtask. Yields NOTHING.
|
|
118
|
+
* setImmediate 569 ms wall 13.8 ms ← free, and 41x less starvation
|
|
119
|
+
* setTimeout(1) 625 ms wall 15.0 ms ← +10% wall for no extra relief
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* `sleep(0)` resolving on a microtask is correct for its own contract and useless for this: microtasks drain
|
|
123
|
+
* before the loop ever advances a phase, so `await sleep(0)` in a tight loop never lets a single pending I/O
|
|
124
|
+
* callback run. `setImmediate` fires in the check phase *after* pending I/O, which is exactly the ordering a
|
|
125
|
+
* server wants — inbound requests are serviced before the loop resumes chewing.
|
|
126
|
+
*
|
|
127
|
+
* A timer is not ref'd here (unlike {@link sleep}): a yield expresses "someone else may go first", never
|
|
128
|
+
* "there is outstanding work" — and `setImmediate` cannot outlive the turn that scheduled it anyway.
|
|
129
|
+
*/
|
|
130
|
+
yieldNow() {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
setImmediate(resolve);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
111
135
|
};
|
|
136
|
+
var bulkLoadCrbmGeneration = (driver, key, ids, options = {}) => bulkLoadCrbmGeneration$1(driver, key, ids, {
|
|
137
|
+
...options,
|
|
138
|
+
codec: options.codec ?? roaringCodec,
|
|
139
|
+
clock: options.clock ?? new SystemClock()
|
|
140
|
+
});
|
|
141
|
+
var compactSegment = (ref, deps, options) => compactSegment$1(ref, { ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
142
|
+
var runCompactionCycle = (deps, options) => runCompactionCycle$1({ ...deps, codec: deps.codec ?? roaringCodec }, options);
|
|
143
|
+
var runExport = (reader, registry, sink, options = {}) => runExport$1(reader, registry, sink, { ...options, codec: options.codec ?? roaringCodec });
|
|
144
|
+
|
|
145
|
+
// src/index.ts
|
|
112
146
|
var SystemRng = class {
|
|
113
147
|
next() {
|
|
114
148
|
return Math.random();
|
|
@@ -486,7 +520,19 @@ var Segment = class {
|
|
|
486
520
|
add(id) {
|
|
487
521
|
return this.timed("add", () => this.engine.add(this.ref, id));
|
|
488
522
|
}
|
|
489
|
-
/**
|
|
523
|
+
/**
|
|
524
|
+
* Add many ids, from a **sync or async** iterable. Not atomic across chunks — see the class note.
|
|
525
|
+
*
|
|
526
|
+
* An async source means a database cursor streams straight in — `addMany(athenaCursor())` rather than
|
|
527
|
+
* hand-batching `page → addMany(page)`. Ids are grouped by chunk and each chunk is written **exactly once**,
|
|
528
|
+
* however long the stream: pending ids are held compressed, not buffered-and-flushed, so a long stream costs
|
|
529
|
+
* no more backend writes than a short one. Peak memory is the roaring representation of the ids you pass.
|
|
530
|
+
*
|
|
531
|
+
* **For a very large set, reach for `bulkLoadCrbmGeneration` instead.** `addMany` expresses a *delta* and
|
|
532
|
+
* writes one warm row per touched chunk — around 61,000 of them for a set spread across the whole id space.
|
|
533
|
+
* Bulk-load *replaces* the segment with one immutable Cold object. See the guide's cost comparison; picking
|
|
534
|
+
* the wrong one of these is the most expensive mistake available in this library.
|
|
535
|
+
*/
|
|
490
536
|
addMany(ids) {
|
|
491
537
|
return this.timed("addMany", () => this.engine.addMany(this.ref, ids));
|
|
492
538
|
}
|
|
@@ -494,7 +540,7 @@ var Segment = class {
|
|
|
494
540
|
remove(id) {
|
|
495
541
|
return this.timed("remove", () => this.engine.remove(this.ref, id));
|
|
496
542
|
}
|
|
497
|
-
/** Remove many ids. Not atomic across chunks — see
|
|
543
|
+
/** Remove many ids, from a **sync or async** iterable. Not atomic across chunks — see {@link addMany}. */
|
|
498
544
|
removeMany(ids) {
|
|
499
545
|
return this.timed("removeMany", () => this.engine.removeMany(this.ref, ids));
|
|
500
546
|
}
|
|
@@ -516,17 +562,84 @@ var Segment = class {
|
|
|
516
562
|
* up front, so total memory also carries each operand's Warm size — negligible under Topology-A.) Pass
|
|
517
563
|
* `budget` to override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
518
564
|
*/
|
|
565
|
+
/**
|
|
566
|
+
* Map the facade's `Segment` handles in `exclude` down to the plain refs `core` takes. A method rather than
|
|
567
|
+
* a module function because `ref` is class-private — the encapsulation is worth more than the free function.
|
|
568
|
+
*/
|
|
569
|
+
refsIn(options) {
|
|
570
|
+
if (options === void 0) return void 0;
|
|
571
|
+
const { exclude, ...rest } = options;
|
|
572
|
+
return exclude === void 0 ? rest : { ...rest, exclude: exclude.map((o) => o.ref) };
|
|
573
|
+
}
|
|
519
574
|
intersect(others, options) {
|
|
520
|
-
return this.engine.intersect([this.ref, ...others.map((o) => o.ref)], options);
|
|
575
|
+
return this.engine.intersect([this.ref, ...others.map((o) => o.ref)], this.refsIn(options));
|
|
521
576
|
}
|
|
522
577
|
/**
|
|
523
|
-
* Materialize `this ∩ others…` **into** `dest` (added, not replaced). Streaming +
|
|
524
|
-
* **not atomic** across chunks — see {@link addMany}.
|
|
578
|
+
* Materialize `this ∩ others…` (minus `exclude`) **into** `dest` (added, not replaced). Streaming +
|
|
579
|
+
* bounded-memory, but **not atomic** across chunks — see {@link addMany}.
|
|
525
580
|
*/
|
|
526
581
|
intersectInto(dest, others, options) {
|
|
527
582
|
return this.timed(
|
|
528
583
|
"intersectInto",
|
|
529
|
-
() => this.engine.intersectInto(
|
|
584
|
+
() => this.engine.intersectInto(
|
|
585
|
+
dest.ref,
|
|
586
|
+
[this.ref, ...others.map((o) => o.ref)],
|
|
587
|
+
this.refsIn(options)
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* `this ∪ others…`, minus `options.exclude` — streamed ascending.
|
|
593
|
+
*
|
|
594
|
+
* **The one composite read with no chunk-skipping,** and that is inherent to union rather than a limitation
|
|
595
|
+
* here: an id in *any* operand belongs to the result, so every chunk of every operand must be read.
|
|
596
|
+
* `intersect` prunes any key missing from any operand; union has nothing to prune. It is charged against the
|
|
597
|
+
* same per-op budget, so a wide union is refused rather than quietly billed — pass `budget` to raise it
|
|
598
|
+
* deliberately. If you find yourself unioning the same segments on every read, materializing the combined
|
|
599
|
+
* segment once (`unionInto`, or a bulk-load rebuild) is the cheaper shape.
|
|
600
|
+
*/
|
|
601
|
+
union(others, options) {
|
|
602
|
+
return this.engine.union([this.ref, ...others.map((o) => o.ref)], this.refsIn(options));
|
|
603
|
+
}
|
|
604
|
+
/** Materialize `this ∪ others…` (minus `exclude`) **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
605
|
+
unionInto(dest, others, options) {
|
|
606
|
+
return this.timed(
|
|
607
|
+
"unionInto",
|
|
608
|
+
() => this.engine.unionInto(
|
|
609
|
+
dest.ref,
|
|
610
|
+
[this.ref, ...others.map((o) => o.ref)],
|
|
611
|
+
this.refsIn(options)
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* `this \ (excludes…)` — streamed ascending. Suppression on its own.
|
|
617
|
+
*
|
|
618
|
+
* Reads every chunk of `this` (any of them may survive the subtraction) but each exclude **only where it
|
|
619
|
+
* overlaps this segment**, so the cost tracks the segment being filtered rather than the size of the
|
|
620
|
+
* suppression list: at most one read per surviving key of `this`, so subtracting a 61,000-chunk global
|
|
621
|
+
* opt-out list from a 40-chunk audience costs at most 40 reads, not 61,000.
|
|
622
|
+
*
|
|
623
|
+
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
624
|
+
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
625
|
+
*/
|
|
626
|
+
andNot(excludes, options) {
|
|
627
|
+
return this.engine.andNot(
|
|
628
|
+
this.ref,
|
|
629
|
+
excludes.map((o) => o.ref),
|
|
630
|
+
options
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
/** Materialize `this \ (excludes…)` **into** `dest`. **Not atomic** — see {@link addMany}. */
|
|
634
|
+
andNotInto(dest, excludes, options) {
|
|
635
|
+
return this.timed(
|
|
636
|
+
"andNotInto",
|
|
637
|
+
() => this.engine.andNotInto(
|
|
638
|
+
dest.ref,
|
|
639
|
+
this.ref,
|
|
640
|
+
excludes.map((o) => o.ref),
|
|
641
|
+
options
|
|
642
|
+
)
|
|
530
643
|
);
|
|
531
644
|
}
|
|
532
645
|
/**
|
|
@@ -547,7 +660,7 @@ var Segment = class {
|
|
|
547
660
|
});
|
|
548
661
|
}
|
|
549
662
|
};
|
|
550
|
-
var VERSION = "0.
|
|
663
|
+
var VERSION = "0.4.1";
|
|
551
664
|
|
|
552
665
|
export { CloudRoaring, SafeBitmap, Segment, VERSION, bulkLoadCrbmGeneration, compactSegment, roaringCodec, runCompactionCycle, runExport };
|
|
553
666
|
//# sourceMappingURL=index.js.map
|