distillate 0.10.0 → 0.11.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 CHANGED
@@ -42,12 +42,13 @@ Every push runs a CI smoke matrix that imports the built package on Node 22/24,
42
42
 
43
43
  Each structure ships as its own subpath, so you only bundle what you import.
44
44
 
45
- | Import | Structure | Answers | Use for |
46
- | -------------------- | ------------- | ------------------ | -------------------------------------------------------- |
47
- | `distillate/bloom` | Classic Bloom | seen this key? | Familiar default, migration from `bloom-filters` |
48
- | `distillate/blocked` | Blocked Bloom | seen this key? | Faster lookups and a lower FPR for a small space premium |
49
- | `distillate/fuse` | Binary Fuse | seen this key? | Static set built once and queried a lot; least space |
50
- | `distillate/hll` | HyperLogLog | how many distinct? | Counting distinct users, IPs, or keys in fixed space |
45
+ | Import | Structure | Answers | Use for |
46
+ | --------------------- | -------------- | ------------------ | -------------------------------------------------------- |
47
+ | `distillate/bloom` | Classic Bloom | seen this key? | Familiar default, migration from `bloom-filters` |
48
+ | `distillate/scalable` | Scalable Bloom | seen this key? | Key count unknown or growing; keeps its FPR bound |
49
+ | `distillate/blocked` | Blocked Bloom | seen this key? | Faster lookups and a lower FPR for a small space premium |
50
+ | `distillate/fuse` | Binary Fuse | seen this key? | Static set built once and queried a lot; least space |
51
+ | `distillate/hll` | HyperLogLog | how many distinct? | Counting distinct users, IPs, or keys in fixed space |
51
52
 
52
53
  The filters are mutable except Binary Fuse, which is built once from the whole
53
54
  key set. HyperLogLog is not a filter: it counts distinct keys and cannot report
@@ -69,6 +70,24 @@ const restored = BloomFilter.fromBytes(bytes);
69
70
 
70
71
  Also: `union(other)` (merge equal-parameter filters), `bitsPerKey`, and a low-level `new BloomFilter({ m, k, seed })`.
71
72
 
73
+ ### Scalable Bloom (`distillate/scalable`)
74
+
75
+ For when you cannot say how many keys are coming. It grows by opening larger, tighter Bloom stages as keys arrive, and keeps its false-positive rate under the target however many open. Full API and trade-offs: [Scalable Bloom guide](https://distillate.akxp.net/guides/scalable/).
76
+
77
+ ```ts
78
+ import { ScalableBloomFilter } from "distillate/scalable";
79
+
80
+ const seen = ScalableBloomFilter.create(10, 0.01); // first stage, target FPR
81
+ for (let i = 0; i < 30; i++) seen.add(`key-${String(i)}`);
82
+
83
+ seen.stages; // 2
84
+ seen.has("key-29"); // true
85
+ ```
86
+
87
+ A key already held is not counted again, so duplicates never use up capacity. It costs more bits per key than a Classic Bloom sized for a known `n`; if you know `n`, use that.
88
+
89
+ Also: `from(keys, epsilon)`, `union`, `equals`, `rate`, `toBytes` / `fromBytes`, `toJSON` / `fromJSON`, and `growth` / `tightening` / `seed` options.
90
+
72
91
  ### Blocked Bloom (`distillate/blocked`)
73
92
 
74
93
  ```ts
@@ -0,0 +1,32 @@
1
+ //#region src/core/bitset.ts
2
+ var BitSetRangeError = class extends RangeError {
3
+ name = "BitSetRangeError";
4
+ };
5
+ const MAX_BITS = 2 ** 32;
6
+ var BitSet = class {
7
+ #bits;
8
+ constructor(nbits) {
9
+ if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
10
+ this.#bits = new Uint8Array(Math.ceil(nbits / 8));
11
+ }
12
+ set(i) {
13
+ const idx = i >>> 3;
14
+ this.#bits[idx] = (this.#bits[idx] ?? 0) | 1 << (i & 7);
15
+ }
16
+ get(i) {
17
+ return ((this.#bits[i >>> 3] ?? 0) & 1 << (i & 7)) !== 0;
18
+ }
19
+ get bytes() {
20
+ return this.#bits;
21
+ }
22
+ count() {
23
+ let total = 0;
24
+ for (let byte of this.#bits) while (byte) {
25
+ byte &= byte - 1;
26
+ total++;
27
+ }
28
+ return total;
29
+ }
30
+ };
31
+ //#endregion
32
+ export { BitSet as t };
@@ -0,0 +1,37 @@
1
+ //#region src/core/bitset.ts
2
+ var BitSetRangeError = class extends RangeError {
3
+ name = "BitSetRangeError";
4
+ };
5
+ const MAX_BITS = 2 ** 32;
6
+ var BitSet = class {
7
+ #bits;
8
+ constructor(nbits) {
9
+ if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
10
+ this.#bits = new Uint8Array(Math.ceil(nbits / 8));
11
+ }
12
+ set(i) {
13
+ const idx = i >>> 3;
14
+ this.#bits[idx] = (this.#bits[idx] ?? 0) | 1 << (i & 7);
15
+ }
16
+ get(i) {
17
+ return ((this.#bits[i >>> 3] ?? 0) & 1 << (i & 7)) !== 0;
18
+ }
19
+ get bytes() {
20
+ return this.#bits;
21
+ }
22
+ count() {
23
+ let total = 0;
24
+ for (let byte of this.#bits) while (byte) {
25
+ byte &= byte - 1;
26
+ total++;
27
+ }
28
+ return total;
29
+ }
30
+ };
31
+ //#endregion
32
+ Object.defineProperty(exports, "BitSet", {
33
+ enumerable: true,
34
+ get: function() {
35
+ return BitSet;
36
+ }
37
+ });
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-D8LCBTOM.cjs");
2
+ const require_hasher = require("../hasher-MhY7aQ8F.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
4
  const require_params = require("../params-UTZbJ22c.cjs");
5
5
  //#region src/blocked/blocked.ts
@@ -1,4 +1,4 @@
1
- import { a as hash32x2Into, c as reduce } from "../hasher-DVWXS_vJ.js";
1
+ import { a as hash32x2Into, u as reduce } from "../hasher-3Cyn7Yg-.js";
2
2
  import { a as TruncatedError, c as assertBodyLength, d as bytesEqual, f as fromJSONEnvelope, g as writeFrame, h as toJSONEnvelope, i as SerializationError, l as assertMinBodyLength, m as readHeader, n as ChecksumError, o as UnknownHashVariantError, r as ReservedBitsError, s as UnknownVersionError, t as BadMagicError, u as assertParamsPadding } from "../serialize-CMSYFym-.js";
3
3
  import { i as assertProbability, n as assertPositiveFinite, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
4
4
  //#region src/blocked/blocked.ts
@@ -1,39 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-D8LCBTOM.cjs");
2
+ const require_bitset = require("../bitset-QYGqxnEf.cjs");
3
+ const require_hasher = require("../hasher-MhY7aQ8F.cjs");
3
4
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
5
  const require_params = require("../params-UTZbJ22c.cjs");
5
6
  const require_sizing = require("../sizing-BLzZk2zr.cjs");
6
- //#region src/core/bitset.ts
7
- var BitSetRangeError = class extends RangeError {
8
- name = "BitSetRangeError";
9
- };
10
- const MAX_BITS = 2 ** 32;
11
- var BitSet = class {
12
- #bits;
13
- constructor(nbits) {
14
- if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
15
- this.#bits = new Uint8Array(Math.ceil(nbits / 8));
16
- }
17
- set(i) {
18
- const idx = i >>> 3;
19
- this.#bits[idx] = (this.#bits[idx] ?? 0) | 1 << (i & 7);
20
- }
21
- get(i) {
22
- return ((this.#bits[i >>> 3] ?? 0) & 1 << (i & 7)) !== 0;
23
- }
24
- get bytes() {
25
- return this.#bits;
26
- }
27
- count() {
28
- let total = 0;
29
- for (let byte of this.#bits) while (byte) {
30
- byte &= byte - 1;
31
- total++;
32
- }
33
- return total;
34
- }
35
- };
36
- //#endregion
37
7
  //#region src/bloom/bloom.ts
38
8
  const TYPE = 1;
39
9
  const PARAMS_SIZE = 16;
@@ -94,7 +64,7 @@ var BloomFilter = class BloomFilter {
94
64
  require_params.assertPositiveInt(n, "n");
95
65
  require_params.assertUint32(n, "n");
96
66
  }
97
- this.#bits = new BitSet(m);
67
+ this.#bits = new require_bitset.BitSet(m);
98
68
  this.#m = m;
99
69
  this.#k = k;
100
70
  this.#seed = seed;
@@ -1,38 +1,8 @@
1
- import { s as probeInto } from "../hasher-DVWXS_vJ.js";
1
+ import { t as BitSet } from "../bitset-Cf2jwRJX.js";
2
+ import { c as probeInto } from "../hasher-3Cyn7Yg-.js";
2
3
  import { a as TruncatedError, c as assertBodyLength, d as bytesEqual, f as fromJSONEnvelope, g as writeFrame, h as toJSONEnvelope, i as SerializationError, l as assertMinBodyLength, m as readHeader, n as ChecksumError, o as UnknownHashVariantError, r as ReservedBitsError, s as UnknownVersionError, t as BadMagicError, u as assertParamsPadding } from "../serialize-CMSYFym-.js";
3
4
  import { a as assertUint16, i as assertProbability, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
4
5
  import { t as bloomSizing } from "../sizing-BtUrtvF_.js";
5
- //#region src/core/bitset.ts
6
- var BitSetRangeError = class extends RangeError {
7
- name = "BitSetRangeError";
8
- };
9
- const MAX_BITS = 2 ** 32;
10
- var BitSet = class {
11
- #bits;
12
- constructor(nbits) {
13
- if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
14
- this.#bits = new Uint8Array(Math.ceil(nbits / 8));
15
- }
16
- set(i) {
17
- const idx = i >>> 3;
18
- this.#bits[idx] = (this.#bits[idx] ?? 0) | 1 << (i & 7);
19
- }
20
- get(i) {
21
- return ((this.#bits[i >>> 3] ?? 0) & 1 << (i & 7)) !== 0;
22
- }
23
- get bytes() {
24
- return this.#bits;
25
- }
26
- count() {
27
- let total = 0;
28
- for (let byte of this.#bits) while (byte) {
29
- byte &= byte - 1;
30
- total++;
31
- }
32
- return total;
33
- }
34
- };
35
- //#endregion
36
6
  //#region src/bloom/bloom.ts
37
7
  const TYPE = 1;
38
8
  const PARAMS_SIZE = 16;
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-D8LCBTOM.cjs");
2
+ const require_hasher = require("../hasher-MhY7aQ8F.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
4
  //#region src/fuse/fuse.ts
5
5
  const ARITY = 3;
@@ -1,4 +1,4 @@
1
- import { i as hash128KeyInto, n as RLO, o as mul64, r as fmix64, t as RHI } from "../hasher-DVWXS_vJ.js";
1
+ import { i as hash128KeyInto, n as RLO, o as mul64, r as fmix64, t as RHI } from "../hasher-3Cyn7Yg-.js";
2
2
  import { a as TruncatedError, c as assertBodyLength, d as bytesEqual, f as fromJSONEnvelope, g as writeFrame, h as toJSONEnvelope, i as SerializationError, l as assertMinBodyLength, m as readHeader, n as ChecksumError, o as UnknownHashVariantError, r as ReservedBitsError, s as UnknownVersionError, t as BadMagicError } from "../serialize-CMSYFym-.js";
3
3
  //#region src/fuse/fuse.ts
4
4
  const ARITY = 3;
@@ -216,14 +216,15 @@ function hash32x2Into(key, seed, out) {
216
216
  out[0] = LANES.w0;
217
217
  out[1] = LANES.w1;
218
218
  }
219
+ function probeAt(a, b, i, range) {
220
+ return reduce(a + Math.imul(i, b) + i * i >>> 0, range);
221
+ }
222
+ function probeLanesInto(a, b, count, range, out) {
223
+ for (let i = 0; i < count; i++) out[i] = probeAt(a, b, i, range);
224
+ }
219
225
  function probeInto(key, count, range, seed, out) {
220
226
  keyToLanes(key, seed);
221
- const a = LANES.w0;
222
- const b = LANES.w1;
223
- for (let i = 0; i < count; i++) {
224
- const x = a + Math.imul(i, b) + i * i >>> 0;
225
- out[i] = reduce(x, range);
226
- }
227
+ probeLanesInto(LANES.w0, LANES.w1, count, range, out);
227
228
  }
228
229
  //#endregion
229
- export { hash32x2Into as a, reduce as c, hash128KeyInto as i, RLO as n, mul64 as o, fmix64 as r, probeInto as s, RHI as t };
230
+ export { hash32x2Into as a, probeInto as c, hash128KeyInto as i, probeLanesInto as l, RLO as n, mul64 as o, fmix64 as r, probeAt as s, RHI as t, reduce as u };
@@ -216,14 +216,15 @@ function hash32x2Into(key, seed, out) {
216
216
  out[0] = LANES.w0;
217
217
  out[1] = LANES.w1;
218
218
  }
219
+ function probeAt(a, b, i, range) {
220
+ return reduce(a + Math.imul(i, b) + i * i >>> 0, range);
221
+ }
222
+ function probeLanesInto(a, b, count, range, out) {
223
+ for (let i = 0; i < count; i++) out[i] = probeAt(a, b, i, range);
224
+ }
219
225
  function probeInto(key, count, range, seed, out) {
220
226
  keyToLanes(key, seed);
221
- const a = LANES.w0;
222
- const b = LANES.w1;
223
- for (let i = 0; i < count; i++) {
224
- const x = a + Math.imul(i, b) + i * i >>> 0;
225
- out[i] = reduce(x, range);
226
- }
227
+ probeLanesInto(LANES.w0, LANES.w1, count, range, out);
227
228
  }
228
229
  //#endregion
229
230
  Object.defineProperty(exports, "RHI", {
@@ -262,12 +263,24 @@ Object.defineProperty(exports, "mul64", {
262
263
  return mul64;
263
264
  }
264
265
  });
266
+ Object.defineProperty(exports, "probeAt", {
267
+ enumerable: true,
268
+ get: function() {
269
+ return probeAt;
270
+ }
271
+ });
265
272
  Object.defineProperty(exports, "probeInto", {
266
273
  enumerable: true,
267
274
  get: function() {
268
275
  return probeInto;
269
276
  }
270
277
  });
278
+ Object.defineProperty(exports, "probeLanesInto", {
279
+ enumerable: true,
280
+ get: function() {
281
+ return probeLanesInto;
282
+ }
283
+ });
271
284
  Object.defineProperty(exports, "reduce", {
272
285
  enumerable: true,
273
286
  get: function() {
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-D8LCBTOM.cjs");
2
+ const require_hasher = require("../hasher-MhY7aQ8F.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
4
  const require_params = require("../params-UTZbJ22c.cjs");
5
5
  const require_sizing = require("../sizing-BLzZk2zr.cjs");
package/dist/hll/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as hash128KeyInto } from "../hasher-DVWXS_vJ.js";
1
+ import { i as hash128KeyInto } from "../hasher-3Cyn7Yg-.js";
2
2
  import { a as TruncatedError, c as assertBodyLength, f as fromJSONEnvelope, g as writeFrame, h as toJSONEnvelope, i as SerializationError, l as assertMinBodyLength, m as readHeader, n as ChecksumError, o as UnknownHashVariantError, r as ReservedBitsError, s as UnknownVersionError, t as BadMagicError, u as assertParamsPadding } from "../serialize-CMSYFym-.js";
3
3
  import { o as assertUint32, t as ParamError } from "../params-CeajSrwv.js";
4
4
  import { n as hllSizing } from "../sizing-BtUrtvF_.js";
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#endregion
3
3
  //#region src/index.ts
4
- const VERSION = "0.10.0";
4
+ const VERSION = "0.11.0";
5
5
  //#endregion
6
6
  exports.VERSION = VERSION;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/index.ts
3
- const VERSION = "0.10.0";
3
+ const VERSION = "0.11.0";
4
4
  //#endregion
5
5
  export { VERSION };
@@ -0,0 +1,340 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_bitset = require("../bitset-QYGqxnEf.cjs");
3
+ const require_hasher = require("../hasher-MhY7aQ8F.cjs");
4
+ const require_serialize = require("../serialize-CHikZ4GH.cjs");
5
+ const require_params = require("../params-UTZbJ22c.cjs");
6
+ const require_sizing = require("../sizing-BLzZk2zr.cjs");
7
+ //#region src/scalable/scalable.ts
8
+ const MAX_BITS = 4294967295;
9
+ const TYPE = 6;
10
+ const PARAMS_SIZE = 40;
11
+ const ENTRY_SIZE = 16;
12
+ const padded8 = (bytes) => Math.ceil(bytes / 8) * 8;
13
+ let restoring;
14
+ var ScalableParamMismatchError = class extends Error {
15
+ name = "ScalableParamMismatchError";
16
+ };
17
+ function readTable(view, stageCount) {
18
+ const entries = [];
19
+ for (let i = 0; i < stageCount; i++) {
20
+ const at = PARAMS_SIZE + ENTRY_SIZE * i;
21
+ const entry = {
22
+ m: view.getUint32(at, true),
23
+ k: view.getUint16(at + 4, true),
24
+ capacity: view.getUint32(at + 8, true),
25
+ count: view.getUint32(at + 12, true)
26
+ };
27
+ if (view.getUint16(at + 6, true) !== 0) throw new require_serialize.SerializationError(`scalable: stage ${String(i)} table padding is not zero`);
28
+ if (entry.m === 0 || entry.k === 0 || entry.capacity === 0) throw new require_serialize.SerializationError(`scalable: stage ${String(i)} declares m=${String(entry.m)}, k=${String(entry.k)}, capacity=${String(entry.capacity)}; all must be positive`);
29
+ if (entry.count > entry.capacity) throw new require_serialize.SerializationError(`scalable: stage ${String(i)} holds ${String(entry.count)} keys, above its capacity of ${String(entry.capacity)}`);
30
+ entries.push(entry);
31
+ }
32
+ return entries;
33
+ }
34
+ function readStages(body, entries, start) {
35
+ let at = start;
36
+ return entries.map((entry, i) => {
37
+ const length = Math.ceil(entry.m / 8);
38
+ const bits = new require_bitset.BitSet(entry.m);
39
+ bits.bytes.set(body.subarray(at, at + length));
40
+ for (let j = at + length; j < at + padded8(length); j++) if (body[j] !== 0) throw new require_serialize.SerializationError(`scalable: stage ${String(i)} bit padding is not zero`);
41
+ at += padded8(length);
42
+ return {
43
+ ...entry,
44
+ bits
45
+ };
46
+ });
47
+ }
48
+ var ScalableBloomFilter = class ScalableBloomFilter {
49
+ #n;
50
+ #epsilon;
51
+ #growth;
52
+ #tightening;
53
+ #seed;
54
+ #stages = [];
55
+ #hash = {
56
+ w0: 0,
57
+ w1: 0,
58
+ w2: 0,
59
+ w3: 0
60
+ };
61
+ #probes = new Uint32Array(0);
62
+ #newest;
63
+ static create(n, epsilon, options = {}) {
64
+ return new ScalableBloomFilter({
65
+ ...options,
66
+ n,
67
+ epsilon
68
+ });
69
+ }
70
+ static from(keys, epsilon, options = {}) {
71
+ const arr = [...keys];
72
+ const f = ScalableBloomFilter.create(Math.max(1, arr.length), epsilon, options);
73
+ for (const key of arr) f.add(key);
74
+ return f;
75
+ }
76
+ constructor({ n, epsilon, growth = 2, tightening = .85, seed = 0 }) {
77
+ require_params.assertPositiveInt(n, "n");
78
+ require_params.assertUint32(n, "n");
79
+ require_params.assertProbability(epsilon, "epsilon");
80
+ if (!Number.isFinite(growth) || growth <= 1) throw new require_params.ParamError(`growth must be a finite number greater than 1, got ${String(growth)}`);
81
+ require_params.assertProbability(tightening, "tightening");
82
+ require_params.assertUint32(seed, "seed");
83
+ this.#n = n;
84
+ this.#epsilon = epsilon;
85
+ this.#growth = growth;
86
+ this.#tightening = tightening;
87
+ this.#seed = seed;
88
+ if (restoring) {
89
+ this.#newest = this.#adopt(restoring);
90
+ return;
91
+ }
92
+ const first = this.#geometry(0);
93
+ if (first.m > MAX_BITS) throw new require_params.ParamError(`the first stage needs ${String(first.m)} bits, more than ${String(MAX_BITS)}`);
94
+ this.#newest = this.#open(first);
95
+ }
96
+ #geometry(i) {
97
+ const capacity = Math.ceil(this.#n * this.#growth ** i);
98
+ const target = this.#epsilon * (1 - this.#tightening) * this.#tightening ** i;
99
+ return {
100
+ capacity,
101
+ ...require_sizing.bloomSizing(capacity, target)
102
+ };
103
+ }
104
+ #open({ capacity, m, k }) {
105
+ const stage = {
106
+ bits: new require_bitset.BitSet(m),
107
+ m,
108
+ k,
109
+ capacity,
110
+ count: 0
111
+ };
112
+ this.#stages.push(stage);
113
+ if (k > this.#probes.length) this.#probes = new Uint32Array(k);
114
+ return stage;
115
+ }
116
+ #settingPairs(other) {
117
+ return [
118
+ [
119
+ "n",
120
+ this.#n,
121
+ other.#n
122
+ ],
123
+ [
124
+ "epsilon",
125
+ this.#epsilon,
126
+ other.#epsilon
127
+ ],
128
+ [
129
+ "growth",
130
+ this.#growth,
131
+ other.#growth
132
+ ],
133
+ [
134
+ "tightening",
135
+ this.#tightening,
136
+ other.#tightening
137
+ ],
138
+ [
139
+ "seed",
140
+ this.#seed,
141
+ other.#seed
142
+ ]
143
+ ];
144
+ }
145
+ #assertMergeable(other) {
146
+ for (const [name, a, b] of this.#settingPairs(other)) if (a !== b) throw new ScalableParamMismatchError(`cannot union scalable Bloom filters whose ${name} differs (${String(a)} vs ${String(b)})`);
147
+ this.#stages.forEach((s, i) => {
148
+ const t = other.#stages[i];
149
+ if (!t) return;
150
+ for (const field of [
151
+ "m",
152
+ "k",
153
+ "capacity"
154
+ ]) if (s[field] !== t[field]) throw new ScalableParamMismatchError(`cannot union scalable Bloom filters whose stage ${String(i)} ${field} differs (${String(s[field])} vs ${String(t[field])})`);
155
+ });
156
+ }
157
+ #adopt(stages) {
158
+ this.#stages.length = 0;
159
+ this.#stages.push(...stages);
160
+ const k = Math.max(...stages.map((s) => s.k));
161
+ if (k > this.#probes.length) this.#probes = new Uint32Array(k);
162
+ return stages.reduce((_, s) => s);
163
+ }
164
+ #probe(stage) {
165
+ require_hasher.probeLanesInto(this.#hash.w0, this.#hash.w1, stage.k, stage.m, this.#probes);
166
+ }
167
+ #holds(stage) {
168
+ const { w0, w1 } = this.#hash;
169
+ for (let i = 0; i < stage.k; i++) if (!stage.bits.get(require_hasher.probeAt(w0, w1, i, stage.m))) return false;
170
+ return true;
171
+ }
172
+ #anyHolds() {
173
+ for (let i = this.#stages.length - 1; i >= 0; i--) {
174
+ const stage = this.#stages[i];
175
+ if (stage && this.#holds(stage)) return true;
176
+ }
177
+ return false;
178
+ }
179
+ add(key) {
180
+ require_hasher.hash128KeyInto(key, this.#seed, this.#hash);
181
+ if (this.#anyHolds()) return;
182
+ if (this.#newest.count >= this.#newest.capacity) {
183
+ const i = this.#stages.length;
184
+ const next = this.#geometry(i);
185
+ if (next.m > MAX_BITS) throw new RangeError(`stage ${String(i)} needs ${String(next.m)} bits, more than ${String(MAX_BITS)}; the filter cannot grow further`);
186
+ this.#newest = this.#open(next);
187
+ }
188
+ const stage = this.#newest;
189
+ this.#probe(stage);
190
+ for (let i = 0; i < stage.k; i++) stage.bits.set(this.#probes[i] ?? 0);
191
+ stage.count++;
192
+ }
193
+ has(key) {
194
+ require_hasher.hash128KeyInto(key, this.#seed, this.#hash);
195
+ return this.#anyHolds();
196
+ }
197
+ rate() {
198
+ let miss = 1;
199
+ for (const s of this.#stages) miss *= 1 - (s.bits.count() / s.m) ** s.k;
200
+ return 1 - miss;
201
+ }
202
+ union(other) {
203
+ this.#assertMergeable(other);
204
+ const result = new ScalableBloomFilter({
205
+ n: this.#n,
206
+ epsilon: this.#epsilon,
207
+ growth: this.#growth,
208
+ tightening: this.#tightening,
209
+ seed: this.#seed
210
+ });
211
+ const [longer, shorter] = this.#stages.length >= other.#stages.length ? [this.#stages, other.#stages] : [other.#stages, this.#stages];
212
+ const stages = longer.map((stage, i) => {
213
+ const bits = new require_bitset.BitSet(stage.m);
214
+ bits.bytes.set(stage.bits.bytes);
215
+ const twin = shorter[i];
216
+ if (twin) {
217
+ const out = bits.bytes;
218
+ const bytes = twin.bits.bytes;
219
+ for (let j = 0; j < out.length; j++) out[j] = (out[j] ?? 0) | (bytes[j] ?? 0);
220
+ }
221
+ return {
222
+ bits,
223
+ m: stage.m,
224
+ k: stage.k,
225
+ capacity: stage.capacity,
226
+ count: Math.min(stage.capacity, stage.count + (twin?.count ?? 0))
227
+ };
228
+ });
229
+ result.#newest = result.#adopt(stages);
230
+ return result;
231
+ }
232
+ equals(other) {
233
+ if (this.#settingPairs(other).some(([, a, b]) => a !== b)) return false;
234
+ if (this.#stages.length !== other.#stages.length) return false;
235
+ return this.#stages.every((s, i) => {
236
+ const t = other.#stages[i];
237
+ if (!t) return false;
238
+ return s.m === t.m && s.k === t.k && s.capacity === t.capacity && s.count === t.count && require_serialize.bytesEqual(s.bits.bytes, t.bits.bytes);
239
+ });
240
+ }
241
+ static fromBytes(bytes) {
242
+ const { type, flags, body } = require_serialize.readHeader(bytes);
243
+ if (type !== TYPE) throw new require_serialize.SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
244
+ if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
245
+ require_serialize.assertMinBodyLength(body.length, PARAMS_SIZE, "scalable");
246
+ require_serialize.assertParamsPadding(body, 36, PARAMS_SIZE, "scalable");
247
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
248
+ const stageCount = view.getUint32(32, true);
249
+ if (stageCount === 0) throw new require_serialize.SerializationError("scalable: frame declares no stages");
250
+ const tableEnd = PARAMS_SIZE + ENTRY_SIZE * stageCount;
251
+ require_serialize.assertMinBodyLength(body.length, tableEnd, "scalable");
252
+ const entries = readTable(view, stageCount);
253
+ const expected = entries.reduce((sum, e) => sum + padded8(Math.ceil(e.m / 8)), tableEnd);
254
+ require_serialize.assertBodyLength(body.length, expected, "scalable");
255
+ restoring = readStages(body, entries, tableEnd);
256
+ try {
257
+ return new ScalableBloomFilter({
258
+ n: view.getUint32(0, true),
259
+ seed: view.getUint32(4, true),
260
+ epsilon: view.getFloat64(8, true),
261
+ growth: view.getFloat64(16, true),
262
+ tightening: view.getFloat64(24, true)
263
+ });
264
+ } catch (err) {
265
+ if (err instanceof require_params.ParamError) throw new require_serialize.SerializationError(`scalable: ${err.message}`);
266
+ throw err;
267
+ } finally {
268
+ restoring = void 0;
269
+ }
270
+ }
271
+ toBytes() {
272
+ const payload = this.#stages.reduce((sum, s) => sum + ENTRY_SIZE + padded8(s.bits.bytes.length), 0);
273
+ return require_serialize.writeFrame({
274
+ version: 5,
275
+ type: TYPE,
276
+ flags: 0
277
+ }, PARAMS_SIZE, payload, (body, view) => {
278
+ view.setUint32(0, this.#n, true);
279
+ view.setUint32(4, this.#seed, true);
280
+ view.setFloat64(8, this.#epsilon, true);
281
+ view.setFloat64(16, this.#growth, true);
282
+ view.setFloat64(24, this.#tightening, true);
283
+ view.setUint32(32, this.#stages.length, true);
284
+ let at = PARAMS_SIZE + ENTRY_SIZE * this.#stages.length;
285
+ this.#stages.forEach((s, i) => {
286
+ const entry = PARAMS_SIZE + ENTRY_SIZE * i;
287
+ view.setUint32(entry, s.m, true);
288
+ view.setUint16(entry + 4, s.k, true);
289
+ view.setUint32(entry + 8, s.capacity, true);
290
+ view.setUint32(entry + 12, s.count, true);
291
+ body.set(s.bits.bytes, at);
292
+ at += padded8(s.bits.bytes.length);
293
+ });
294
+ });
295
+ }
296
+ toJSON() {
297
+ return require_serialize.toJSONEnvelope(this.toBytes());
298
+ }
299
+ static fromJSON(value) {
300
+ return ScalableBloomFilter.fromBytes(require_serialize.fromJSONEnvelope(value));
301
+ }
302
+ get count() {
303
+ return this.#stages.reduce((sum, s) => sum + s.count, 0);
304
+ }
305
+ get capacity() {
306
+ return this.#stages.reduce((sum, s) => sum + s.capacity, 0);
307
+ }
308
+ get stages() {
309
+ return this.#stages.length;
310
+ }
311
+ get m() {
312
+ return this.#stages.reduce((sum, s) => sum + s.m, 0);
313
+ }
314
+ get bitsPerKey() {
315
+ return this.m / this.capacity;
316
+ }
317
+ get seed() {
318
+ return this.#seed;
319
+ }
320
+ get epsilon() {
321
+ return this.#epsilon;
322
+ }
323
+ get growth() {
324
+ return this.#growth;
325
+ }
326
+ get tightening() {
327
+ return this.#tightening;
328
+ }
329
+ };
330
+ //#endregion
331
+ exports.BadMagicError = require_serialize.BadMagicError;
332
+ exports.ChecksumError = require_serialize.ChecksumError;
333
+ exports.ParamError = require_params.ParamError;
334
+ exports.ReservedBitsError = require_serialize.ReservedBitsError;
335
+ exports.ScalableBloomFilter = ScalableBloomFilter;
336
+ exports.ScalableParamMismatchError = ScalableParamMismatchError;
337
+ exports.SerializationError = require_serialize.SerializationError;
338
+ exports.TruncatedError = require_serialize.TruncatedError;
339
+ exports.UnknownHashVariantError = require_serialize.UnknownHashVariantError;
340
+ exports.UnknownVersionError = require_serialize.UnknownVersionError;