@cloudbitmaps/roaring 0.15.0 → 0.16.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/README.md +3 -4
- package/dist/bin/export-segments.js +73 -23
- package/dist/bin/export-segments.js.map +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +73 -23
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,11 +69,10 @@ name.
|
|
|
69
69
|
|
|
70
70
|
## What it costs
|
|
71
71
|
|
|
72
|
-
A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks
|
|
73
|
-
million
|
|
72
|
+
A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks made 6 GETs and took 95.03 ms at the median, **$2.40 per
|
|
73
|
+
million** at list prices: measured on S3 in-region, in the run of 2026-10-04, whose client had 128 sockets. On a spread layout it reads more
|
|
74
74
|
bytes than it needs, which cross-region can cost more than the requests it saves. A segment's first `store.load()` is
|
|
75
|
-
**$11.
|
|
76
|
-
client had 50 sockets; the client this release builds allows 128, and its effect is not measured). A cold `count()` is one
|
|
75
|
+
expected at **$11.20 per million**, pointer included: counted by running the engine and priced at list, not yet measured on a cloud. A cold `count()` is one
|
|
77
76
|
pointer read: it reads no payload and no object.
|
|
78
77
|
|
|
79
78
|
The trade is stated plainly. A membership check that misses the cache costs a ranged GET against object storage,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
estimateCost,
|
|
34
34
|
groundedReport,
|
|
35
35
|
loadSegment,
|
|
36
|
+
loadSegmentChunks,
|
|
36
37
|
mapWithConcurrency,
|
|
37
38
|
resolveBudget,
|
|
38
39
|
resolvePerOpBudget,
|
|
@@ -460,7 +461,8 @@ function bitmapAsLoadInput(input) {
|
|
|
460
461
|
var roaringCodec = {
|
|
461
462
|
empty: () => SafeBitmap.empty(),
|
|
462
463
|
fromValues: (values) => SafeBitmap.fromValues(values),
|
|
463
|
-
safeDeserialize: (bytes, maxBytes, options) => SafeBitmap.safeDeserialize(bytes, maxBytes, options)
|
|
464
|
+
safeDeserialize: (bytes, maxBytes, options) => SafeBitmap.safeDeserialize(bytes, maxBytes, options),
|
|
465
|
+
owns: (bitmap) => bitmap instanceof SafeBitmap
|
|
464
466
|
};
|
|
465
467
|
|
|
466
468
|
// src/system-clock.ts
|
|
@@ -885,7 +887,7 @@ var CloudRoaring = class _CloudRoaring {
|
|
|
885
887
|
const metadata = options?.metadata;
|
|
886
888
|
let result;
|
|
887
889
|
try {
|
|
888
|
-
|
|
890
|
+
const loadOptions = {
|
|
889
891
|
...options?.allowEmpty === void 0 ? {} : { allowEmpty: options.allowEmpty },
|
|
890
892
|
...options?.guard === void 0 ? {} : { guard: options.guard },
|
|
891
893
|
...metadata === void 0 ? {} : { metadata },
|
|
@@ -898,7 +900,8 @@ var CloudRoaring = class _CloudRoaring {
|
|
|
898
900
|
// as a side effect of adding a guard whose entire purpose is preventing data loss. Opt in with `keep`.
|
|
899
901
|
keep: options?.keep ?? KEEP_EVERY_GENERATION,
|
|
900
902
|
...options?.audit === void 0 ? {} : { audit: options.audit }
|
|
901
|
-
}
|
|
903
|
+
};
|
|
904
|
+
result = ids instanceof CombineChunks ? await loadSegmentChunks(dest, ids.chunks, deps, loadOptions) : await loadSegment(dest, ids, deps, loadOptions);
|
|
902
905
|
} finally {
|
|
903
906
|
this.engine.invalidate(dest);
|
|
904
907
|
}
|
|
@@ -1702,6 +1705,32 @@ var EMPTY_IDS = {
|
|
|
1702
1705
|
async *batches() {
|
|
1703
1706
|
}
|
|
1704
1707
|
};
|
|
1708
|
+
var AS_IDS = {
|
|
1709
|
+
none: EMPTY_IDS,
|
|
1710
|
+
failing,
|
|
1711
|
+
intersect: (engine, refs, opts) => withBatches(engine.intersect(refs, opts), () => engine.intersectBatches(refs, opts)),
|
|
1712
|
+
union: (engine, refs, opts) => withBatches(engine.union(refs, opts), () => engine.unionBatches(refs, opts)),
|
|
1713
|
+
andNot: (engine, base, refs, opts) => withBatches(engine.andNot(base, refs, opts), () => engine.andNotBatches(base, refs, opts))
|
|
1714
|
+
};
|
|
1715
|
+
var NO_CHUNKS = {
|
|
1716
|
+
async *[Symbol.asyncIterator]() {
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
var CombineChunks = class {
|
|
1720
|
+
constructor(chunks) {
|
|
1721
|
+
this.chunks = chunks;
|
|
1722
|
+
}
|
|
1723
|
+
chunks;
|
|
1724
|
+
};
|
|
1725
|
+
var AS_CHUNKS = {
|
|
1726
|
+
none: NO_CHUNKS,
|
|
1727
|
+
failing: (err) => ({
|
|
1728
|
+
[Symbol.asyncIterator]: () => ({ next: () => Promise.reject(err) })
|
|
1729
|
+
}),
|
|
1730
|
+
intersect: (engine, refs, opts) => engine.intersectChunks(refs, opts),
|
|
1731
|
+
union: (engine, refs, opts) => engine.unionChunks(refs, opts),
|
|
1732
|
+
andNot: (engine, base, refs, opts) => engine.andNotChunks(base, refs, opts)
|
|
1733
|
+
};
|
|
1705
1734
|
var KEEP_EVERY_GENERATION = Number.MAX_SAFE_INTEGER;
|
|
1706
1735
|
var makeSegment;
|
|
1707
1736
|
var minting = false;
|
|
@@ -2000,17 +2029,20 @@ var Segment = class _Segment {
|
|
|
2000
2029
|
* override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
|
|
2001
2030
|
*/
|
|
2002
2031
|
intersect(others, options) {
|
|
2003
|
-
|
|
2032
|
+
return this.intersectAs(AS_IDS, others, options);
|
|
2033
|
+
}
|
|
2034
|
+
intersectAs(out, others, options) {
|
|
2035
|
+
if (this.expired() || others.some((o) => o.expired())) return out.none;
|
|
2004
2036
|
const exclude = this.liveExcludes(options);
|
|
2005
2037
|
let engine;
|
|
2006
2038
|
try {
|
|
2007
2039
|
engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
|
|
2008
2040
|
} catch (err) {
|
|
2009
|
-
return failing(err);
|
|
2041
|
+
return out.failing(err);
|
|
2010
2042
|
}
|
|
2011
2043
|
const refs = [this.ref, ...others.map((o) => o.ref)];
|
|
2012
2044
|
const opts = this.refsIn(options, exclude);
|
|
2013
|
-
return
|
|
2045
|
+
return out.intersect(engine, refs, opts);
|
|
2014
2046
|
}
|
|
2015
2047
|
/**
|
|
2016
2048
|
* Materialize `this ∩ others…` (minus `exclude`) as a **new generation of `dest`** — `dest`'s previous contents
|
|
@@ -2037,7 +2069,12 @@ var Segment = class _Segment {
|
|
|
2037
2069
|
this.combineEngine([this, ...others, ...options?.exclude ?? []]);
|
|
2038
2070
|
return this.timed(
|
|
2039
2071
|
"intersectInto",
|
|
2040
|
-
() => this.materialize(
|
|
2072
|
+
() => this.materialize(
|
|
2073
|
+
dest.ref,
|
|
2074
|
+
new CombineChunks(this.intersectAs(AS_CHUNKS, others, options)),
|
|
2075
|
+
"intersectInto",
|
|
2076
|
+
options
|
|
2077
|
+
)
|
|
2041
2078
|
);
|
|
2042
2079
|
}
|
|
2043
2080
|
/**
|
|
@@ -2051,26 +2088,29 @@ var Segment = class _Segment {
|
|
|
2051
2088
|
* segment once (`unionInto`, or a load) is the cheaper shape.
|
|
2052
2089
|
*/
|
|
2053
2090
|
union(others, options) {
|
|
2091
|
+
return this.unionAs(AS_IDS, others, options);
|
|
2092
|
+
}
|
|
2093
|
+
unionAs(out, others, options) {
|
|
2054
2094
|
const live = others.filter((o) => !o.expired());
|
|
2055
2095
|
if (this.expired()) {
|
|
2056
|
-
if (live.length === 0) return
|
|
2057
|
-
return live[0].
|
|
2096
|
+
if (live.length === 0) return out.none;
|
|
2097
|
+
return live[0].unionAs(out, live.slice(1), options);
|
|
2058
2098
|
}
|
|
2059
2099
|
if (live.length === 0 && others.length > 0) {
|
|
2060
2100
|
const exclude2 = this.liveExcludes(options);
|
|
2061
|
-
return exclude2.length > 0 ? this.
|
|
2101
|
+
return exclude2.length > 0 ? this.andNotAs(out, [...exclude2], options) : this.unionAs(out, [], options);
|
|
2062
2102
|
}
|
|
2063
|
-
if (live.length !== others.length) return this.
|
|
2103
|
+
if (live.length !== others.length) return this.unionAs(out, live, options);
|
|
2064
2104
|
const exclude = this.liveExcludes(options);
|
|
2065
2105
|
let engine;
|
|
2066
2106
|
try {
|
|
2067
2107
|
engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
|
|
2068
2108
|
} catch (err) {
|
|
2069
|
-
return failing(err);
|
|
2109
|
+
return out.failing(err);
|
|
2070
2110
|
}
|
|
2071
2111
|
const refs = [this.ref, ...others.map((o) => o.ref)];
|
|
2072
2112
|
const opts = this.refsIn(options, exclude);
|
|
2073
|
-
return
|
|
2113
|
+
return out.union(engine, refs, opts);
|
|
2074
2114
|
}
|
|
2075
2115
|
/** Materialize `this ∪ others…` (minus `exclude`) as a **new generation of `dest`** — see {@link intersectInto}. */
|
|
2076
2116
|
async unionInto(dest, others, options) {
|
|
@@ -2078,7 +2118,12 @@ var Segment = class _Segment {
|
|
|
2078
2118
|
this.combineEngine([this, ...others, ...options?.exclude ?? []]);
|
|
2079
2119
|
return this.timed(
|
|
2080
2120
|
"unionInto",
|
|
2081
|
-
() => this.materialize(
|
|
2121
|
+
() => this.materialize(
|
|
2122
|
+
dest.ref,
|
|
2123
|
+
new CombineChunks(this.unionAs(AS_CHUNKS, others, options)),
|
|
2124
|
+
"unionInto",
|
|
2125
|
+
options
|
|
2126
|
+
)
|
|
2082
2127
|
);
|
|
2083
2128
|
}
|
|
2084
2129
|
/**
|
|
@@ -2097,25 +2142,25 @@ var Segment = class _Segment {
|
|
|
2097
2142
|
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
2098
2143
|
*/
|
|
2099
2144
|
andNot(excludes, options) {
|
|
2100
|
-
|
|
2145
|
+
return this.andNotAs(AS_IDS, excludes, options);
|
|
2146
|
+
}
|
|
2147
|
+
andNotAs(out, excludes, options) {
|
|
2148
|
+
if (this.expired()) return out.none;
|
|
2101
2149
|
const liveExcludes = excludes.filter((e) => !e.expired());
|
|
2102
2150
|
if (liveExcludes.length === 0 && excludes.length > 0) {
|
|
2103
|
-
return this.
|
|
2151
|
+
return this.unionAs(out, [], options == null ? void 0 : readOptions(options));
|
|
2104
2152
|
}
|
|
2105
|
-
if (liveExcludes.length !== excludes.length) return this.
|
|
2153
|
+
if (liveExcludes.length !== excludes.length) return this.andNotAs(out, liveExcludes, options);
|
|
2106
2154
|
let engine;
|
|
2107
2155
|
try {
|
|
2108
2156
|
engine = this.combineEngine([this, ...excludes]) ?? this.engine;
|
|
2109
2157
|
} catch (err) {
|
|
2110
|
-
return failing(err);
|
|
2158
|
+
return out.failing(err);
|
|
2111
2159
|
}
|
|
2112
2160
|
const base = this.ref;
|
|
2113
2161
|
const refs = excludes.map((o) => o.ref);
|
|
2114
2162
|
const opts = options == null ? void 0 : readOptions(options);
|
|
2115
|
-
return
|
|
2116
|
-
engine.andNot(base, refs, opts),
|
|
2117
|
-
() => engine.andNotBatches(base, refs, opts)
|
|
2118
|
-
);
|
|
2163
|
+
return out.andNot(engine, base, refs, opts);
|
|
2119
2164
|
}
|
|
2120
2165
|
/** Materialize `this \ (excludes…)` as a **new generation of `dest`** — see {@link intersectInto}. */
|
|
2121
2166
|
async andNotInto(dest, excludes, options) {
|
|
@@ -2123,7 +2168,12 @@ var Segment = class _Segment {
|
|
|
2123
2168
|
this.combineEngine([this, ...excludes]);
|
|
2124
2169
|
return this.timed(
|
|
2125
2170
|
"andNotInto",
|
|
2126
|
-
() => this.materialize(
|
|
2171
|
+
() => this.materialize(
|
|
2172
|
+
dest.ref,
|
|
2173
|
+
new CombineChunks(this.andNotAs(AS_CHUNKS, excludes, options)),
|
|
2174
|
+
"andNotInto",
|
|
2175
|
+
options
|
|
2176
|
+
)
|
|
2127
2177
|
);
|
|
2128
2178
|
}
|
|
2129
2179
|
/**
|