distillate 0.4.0 → 0.6.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
@@ -24,17 +24,17 @@ npm install distillate
24
24
  # or: pnpm add distillate / bun add distillate / deno add npm:distillate
25
25
  ```
26
26
 
27
- Requires Node 20+ (or any modern Bun/Deno/browser/edge runtime).
27
+ Requires Node 22+ (or any modern Bun/Deno/browser/edge runtime).
28
28
 
29
29
  ## Runtime support
30
30
 
31
31
  `distillate` targets ES2022 with zero runtime dependencies and no `eval`, so it runs on every modern JavaScript runtime:
32
32
 
33
- - **Node.js** 20, 22, 24 (LTS and current)
33
+ - **Node.js** 22, 24 (LTS and current)
34
34
  - **Bun** and **Deno**
35
35
  - Browsers and Cloudflare/Vercel edge
36
36
 
37
- Every push runs a CI smoke matrix that imports the built package on Node 20/22/24, Bun, and Deno, so cross-runtime support is verified, not assumed.
37
+ Every push runs a CI smoke matrix that imports the built package on Node 22/24, Bun, and Deno, so cross-runtime support is verified, not assumed.
38
38
 
39
39
  ## Structures
40
40
 
@@ -43,7 +43,7 @@ Each structure ships as its own subpath, so you only bundle what you import.
43
43
  | Import | Structure | Mutable? | Use for |
44
44
  | -------------------- | ------------- | -------- | ---------------------------------------------------- |
45
45
  | `distillate/bloom` | Classic Bloom | yes | Familiar default, migration from `bloom-filters` |
46
- | `distillate/blocked` | Blocked Bloom | yes | Streaming inserts, speed-first, cache-friendly |
46
+ | `distillate/blocked` | Blocked Bloom | yes | Faster lookups and a lower FPR for ~15% more space |
47
47
  | `distillate/fuse` | Binary Fuse | no | Static set built once and queried a lot; least space |
48
48
 
49
49
  ### Classic Bloom (`distillate/bloom`)
@@ -72,11 +72,11 @@ filter.add("alice");
72
72
  filter.has("alice"); // true
73
73
  ```
74
74
 
75
- Same surface as Classic Bloom (`add` / `has` / `union` / `toBytes` / `fromBytes` / `bitsPerKey`). Confines every lookup to one cache line, trading ~20-30% more space for cache-friendly throughput.
75
+ Same surface as Classic Bloom (`add` / `has` / `union` / `toBytes` / `fromBytes` / `bitsPerKey`). Confines every lookup to a single cache line, so it is consistently faster than Classic across sizes (measured on Apple M5: ~7% faster at 100k keys, widening to ~1.4x once the filter outgrows CPU cache at tens of millions), and its sizing gives a lower false-positive rate. The cost is ~15% more space. Reach for it when lookup throughput matters; prefer Classic when space is tight.
76
76
 
77
77
  ### Binary Fuse (`distillate/fuse`)
78
78
 
79
- A **static** filter: built once from the full key set, then immutable. The most space-efficient option (~9 bits/key at ~0.39% FPR for 8-bit; ~19 bits/key at ~1/65536 for 16-bit).
79
+ A **static** filter: built once from the full key set, then immutable. The most space-efficient option (~9 bits/key at ~0.39% FPR for 8-bit; ~19 bits/key at ~1/65536 for 16-bit), and it queries at ~11 M ops/s (Apple M5, 100k keys).
80
80
 
81
81
  ```ts
82
82
  import { BinaryFuse8, BinaryFuse16 } from "distillate/fuse";
@@ -98,12 +98,12 @@ Classic Bloom head-to-head at a **matched 1% false-positive rate** over the same
98
98
 
99
99
  | Classic Bloom | bits/key | measured FPR | `has` throughput |
100
100
  | -------------- | -------- | ------------ | ---------------- |
101
- | **distillate** | 9.59 | 1.03% | ~7.0 M ops/s |
101
+ | **distillate** | 9.59 | 1.03% | ~21 M ops/s |
102
102
  | bloom-filters | 9.59 | 0.99% | ~0.29 M ops/s |
103
103
 
104
- Same space, same accuracy, **~24x the lookup throughput** of [`bloom-filters`](https://www.npmjs.com/package/bloom-filters) (the package distillate replaces), while hashing UTF-8 bytes with MurmurHash3 so filters stay portable and cross-language readable.
104
+ Same space, same accuracy, **~72x the lookup throughput** of [`bloom-filters`](https://www.npmjs.com/package/bloom-filters) (the package distillate replaces), while hashing UTF-8 bytes with MurmurHash3 so filters stay portable and cross-language readable.
105
105
 
106
- These are a point-in-time snapshot on one machine. The full report (blocked/fuse, 1M capacity, the `bloomfilter` micro-package) and exactly how it is measured live in the [distillate-bench](https://github.com/akshay-xp/distillate-bench) repo: [RESULTS.md](https://github.com/akshay-xp/distillate-bench/blob/main/RESULTS.md), [METHODOLOGY.md](https://github.com/akshay-xp/distillate-bench/blob/main/METHODOLOGY.md).
106
+ These are a point-in-time snapshot on one machine. The full report (blocked/fuse, 1M capacity, the `bloomfilter` micro-package) and exactly how it is measured live in the [`apps/bench`](https://github.com/akshay-xp/distillate/tree/main/apps/bench) workspace: [RESULTS.md](https://github.com/akshay-xp/distillate/blob/main/apps/bench/RESULTS.md), [METHODOLOGY.md](https://github.com/akshay-xp/distillate/blob/main/apps/bench/METHODOLOGY.md).
107
107
 
108
108
  ## Docs
109
109
 
@@ -1,15 +1,42 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-DPdiySxq.cjs");
3
- const require_params = require("../params-J8p3bKq5.cjs");
2
+ const require_serialize = require("../serialize-BIIKUHH6.cjs");
3
+ const require_params = require("../params--8CNXYWu.cjs");
4
4
  //#region src/blocked/blocked.ts
5
5
  const TYPE = 2;
6
6
  const SALT = Uint32Array.of(1203114875, 1150766481, 2284105051, 2729912477, 1884591559, 770785867, 2667333959, 1550580529);
7
- const scratchHash = {
8
- h1lo: 0,
9
- h1hi: 0,
10
- h2lo: 0,
11
- h2hi: 0
12
- };
7
+ const scratch2 = /* @__PURE__ */ new Uint32Array(2);
8
+ const BLOCK_BITS = 256;
9
+ const LANE_BITS = 32;
10
+ const LANES = 8;
11
+ /**
12
+ * Modeled false-positive rate of a split-block filter at `bitsPerKey`. A block
13
+ * holding `j` keys has FPR `(1 - (1 - 1/32)^j)^8` (8 lanes of 32 bits, one probe
14
+ * each); the filter's rate averages that over the Poisson block load
15
+ * `lambda = 256 / bitsPerKey`. This clustering average is why the blocked curve
16
+ * is not linear in `log10(1/epsilon)`.
17
+ */
18
+ function blockedFprAt(bitsPerKey) {
19
+ const lambda = BLOCK_BITS / bitsPerKey;
20
+ let fpr = 0;
21
+ let p = Math.exp(-lambda);
22
+ for (let j = 0;; j++) {
23
+ if (j > 0) p *= lambda / j;
24
+ fpr += p * (1 - (1 - 1 / LANE_BITS) ** j) ** LANES;
25
+ if (j > lambda && p < 1e-15) break;
26
+ }
27
+ return fpr;
28
+ }
29
+ const MAX_BITS_PER_KEY = 128;
30
+ /**
31
+ * Minimal integer bits-per-key whose modeled split-block FPR is at or below
32
+ * `epsilon`. Throws {@link ParamError} when even the densest supported filter
33
+ * cannot reach the target, so callers get a typed rejection instead of a
34
+ * silently under-provisioned filter.
35
+ */
36
+ function blockedBitsPerKey(epsilon) {
37
+ for (let bpk = 1; bpk <= MAX_BITS_PER_KEY; bpk++) if (blockedFprAt(bpk) <= epsilon) return bpk;
38
+ throw new require_params.ParamError(`epsilon ${String(epsilon)} is below the blocked-filter floor; use a classic or fuse filter`);
39
+ }
13
40
  /** Thrown when an operation requires two filters built with identical parameters. */
14
41
  var BlockedBloomParamMismatchError = class extends Error {
15
42
  /** Discriminates this error from other `Error`s. */
@@ -17,7 +44,7 @@ var BlockedBloomParamMismatchError = class extends Error {
17
44
  };
18
45
  /**
19
46
  * A blocked (split-block) Bloom filter: confines every lookup to a single cache
20
- * line, trading ~20-30% more space for cache-friendly throughput.
47
+ * line, trading ~15% more space for higher lookup throughput and a lower FPR.
21
48
  *
22
49
  * @example
23
50
  * ```ts
@@ -33,11 +60,6 @@ var BlockedBloomFilter = class BlockedBloomFilter {
33
60
  #n;
34
61
  #words = /* @__PURE__ */ new Uint32Array(8);
35
62
  #bits = /* @__PURE__ */ new Uint32Array(8);
36
- static #ANCHORS = [
37
- [2, 10.5],
38
- [3, 16.9],
39
- [4, 26.4]
40
- ];
41
63
  /**
42
64
  * Creates a filter sized for `n` expected keys at a target false-positive rate.
43
65
  *
@@ -48,35 +70,62 @@ var BlockedBloomFilter = class BlockedBloomFilter {
48
70
  static create(n, epsilon) {
49
71
  require_params.assertPositiveInt(n, "n");
50
72
  require_params.assertProbability(epsilon, "epsilon");
51
- const t = Math.log10(1 / epsilon);
52
- const a = BlockedBloomFilter.#ANCHORS;
53
- let seg = a.findIndex((p) => t <= p[0]);
54
- if (seg < 1) seg = seg === -1 ? a.length - 1 : 1;
55
- const [t0, b0] = a[seg - 1] ?? [0, 0];
56
- const [t1, b1] = a[seg] ?? [0, 0];
57
- const bitsPerKey = b0 + (b1 - b0) / (t1 - t0) * (t - t0);
58
73
  return new BlockedBloomFilter({
59
- bitsPerKey: Math.max(1, Math.ceil(bitsPerKey)),
74
+ bitsPerKey: blockedBitsPerKey(epsilon),
60
75
  capacity: n
61
76
  });
62
77
  }
63
78
  /**
79
+ * Builds a filter from `keys`, sized for their count at the target
80
+ * false-positive rate. The ergonomic entry point when the key set is already
81
+ * in hand; use {@link BlockedBloomFilter.create} to size for a count known
82
+ * ahead.
83
+ *
84
+ * @param keys - The keys to insert.
85
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
86
+ * @returns A new filter containing every key.
87
+ */
88
+ static from(keys, epsilon) {
89
+ const arr = [...keys];
90
+ const f = BlockedBloomFilter.create(Math.max(1, arr.length), epsilon);
91
+ for (const k of arr) f.add(k);
92
+ return f;
93
+ }
94
+ /**
64
95
  * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
65
96
  * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
66
97
  */
67
98
  constructor({ bitsPerKey, capacity, seed = 0 }) {
68
99
  require_params.assertPositiveFinite(bitsPerKey, "bitsPerKey");
69
100
  require_params.assertPositiveInt(capacity, "capacity");
101
+ require_params.assertUint32(capacity, "capacity");
70
102
  require_params.assertUint32(seed, "seed");
71
103
  this.#numBlocks = Math.max(1, Math.ceil(bitsPerKey * capacity / 256));
72
104
  this.#lanes = new Uint32Array(this.#numBlocks * 8);
73
105
  this.#seed = seed;
74
106
  this.#n = capacity;
75
107
  }
108
+ static #fromNumBlocks(numBlocks, seed, n) {
109
+ const f = new BlockedBloomFilter({
110
+ bitsPerKey: 256,
111
+ capacity: numBlocks,
112
+ seed
113
+ });
114
+ f.#n = n;
115
+ return f;
116
+ }
76
117
  /** Actual bits allocated per key (`total bits / capacity`). */
77
118
  get bitsPerKey() {
78
119
  return this.#numBlocks * 256 / this.#n;
79
120
  }
121
+ /** Number of 256-bit blocks; one of the two fields `union` requires to match. */
122
+ get numBlocks() {
123
+ return this.#numBlocks;
124
+ }
125
+ /** Hash seed; the other field `union` requires to match. */
126
+ get seed() {
127
+ return this.#seed;
128
+ }
80
129
  /** Number of bits currently set across all lanes. */
81
130
  get length() {
82
131
  let bits = 0;
@@ -104,20 +153,17 @@ var BlockedBloomFilter = class BlockedBloomFilter {
104
153
  * @returns The reconstructed filter.
105
154
  */
106
155
  static fromBytes(bytes) {
107
- const { type, body } = require_serialize.readHeader(bytes);
156
+ const { type, flags, body } = require_serialize.readHeader(bytes);
108
157
  if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
158
+ if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
109
159
  require_serialize.assertMinBodyLength(body.length, 12, "blocked");
110
160
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
111
161
  const numBlocks = dv.getUint32(0, true);
112
162
  const seed = dv.getUint32(4, true);
113
163
  const n = dv.getUint32(8, true);
164
+ if (numBlocks === 0 || n === 0) throw new require_serialize.SerializationError(`blocked frame declares numBlocks=${String(numBlocks)}, n=${String(n)}; both must be positive`);
114
165
  require_serialize.assertBodyLength(body.length, 12 + numBlocks * 32, "blocked");
115
- const f = new BlockedBloomFilter({
116
- bitsPerKey: 256,
117
- capacity: numBlocks,
118
- seed
119
- });
120
- f.#n = n;
166
+ const f = BlockedBloomFilter.#fromNumBlocks(numBlocks, seed, n);
121
167
  new Uint8Array(f.#lanes.buffer).set(body.subarray(12));
122
168
  return f;
123
169
  }
@@ -128,17 +174,44 @@ var BlockedBloomFilter = class BlockedBloomFilter {
128
174
  */
129
175
  toBytes() {
130
176
  const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
131
- const body = new Uint8Array(12 + lanes.length);
132
- const dv = new DataView(body.buffer);
133
- dv.setUint32(0, this.#numBlocks, true);
134
- dv.setUint32(4, this.#seed, true);
135
- dv.setUint32(8, this.#n, true);
136
- body.set(lanes, 12);
137
- return require_serialize.writeHeader({
138
- version: 2,
177
+ return require_serialize.writeFrame({
178
+ version: 3,
139
179
  type: TYPE,
140
180
  flags: 0
141
- }, body);
181
+ }, 12 + lanes.length, (body, dv) => {
182
+ dv.setUint32(0, this.#numBlocks, true);
183
+ dv.setUint32(4, this.#seed, true);
184
+ dv.setUint32(8, this.#n, true);
185
+ body.set(lanes, 12);
186
+ });
187
+ }
188
+ /**
189
+ * Tests structural equality: `true` when `other` serializes to identical
190
+ * bytes, meaning identical parameters and set bits.
191
+ *
192
+ * @param other - The filter to compare against.
193
+ * @returns `true` if the two filters are byte-for-byte identical.
194
+ */
195
+ equals(other) {
196
+ return require_serialize.bytesEqual(this.toBytes(), other.toBytes());
197
+ }
198
+ /**
199
+ * Serializes the filter to a JSON-friendly envelope wrapping the base64 of
200
+ * {@link BlockedBloomFilter.toBytes}.
201
+ *
202
+ * @returns The envelope, readable by {@link BlockedBloomFilter.fromJSON}.
203
+ */
204
+ toJSON() {
205
+ return require_serialize.toJSONEnvelope(this.toBytes());
206
+ }
207
+ /**
208
+ * Restores a filter from its {@link BlockedBloomFilter.toJSON} envelope.
209
+ *
210
+ * @param value - The JSON envelope.
211
+ * @returns The reconstructed filter.
212
+ */
213
+ static fromJSON(value) {
214
+ return BlockedBloomFilter.fromBytes(require_serialize.fromJSONEnvelope(value));
142
215
  }
143
216
  /**
144
217
  * Returns a new filter containing the union of this filter and `other`.
@@ -149,11 +222,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
149
222
  */
150
223
  union(other) {
151
224
  if (this.#numBlocks !== other.#numBlocks || this.#seed !== other.#seed) throw new BlockedBloomParamMismatchError("cannot union blocked Bloom filters whose parameters do not match");
152
- const r = new BlockedBloomFilter({
153
- bitsPerKey: this.bitsPerKey,
154
- capacity: this.#n,
155
- seed: this.#seed
156
- });
225
+ const r = BlockedBloomFilter.#fromNumBlocks(this.#numBlocks, this.#seed, this.#n);
157
226
  for (let i = 0; i < this.#lanes.length; i++) r.#lanes[i] = (this.#lanes[i] ?? 0) | (other.#lanes[i] ?? 0);
158
227
  return r;
159
228
  }
@@ -185,9 +254,9 @@ var BlockedBloomFilter = class BlockedBloomFilter {
185
254
  }
186
255
  };
187
256
  function fillBlock(key, numBlocks, seed, outWords, outBits) {
188
- require_serialize.hash128KeyInto(key, seed, scratchHash);
189
- const block = require_serialize.reduce((scratchHash.h1lo ^ scratchHash.h1hi) >>> 0, numBlocks);
190
- const x = (scratchHash.h2lo ^ scratchHash.h2hi) >>> 0;
257
+ require_serialize.hash32x2Into(key, seed, scratch2);
258
+ const block = require_serialize.reduce(scratch2[0] ?? 0, numBlocks);
259
+ const x = scratch2[1] ?? 0;
191
260
  const base = block * 8;
192
261
  for (let i = 0; i < 8; i++) {
193
262
  outWords[i] = base + i;
@@ -1,4 +1,4 @@
1
- import { t as BytesLike } from "../bytes-DCuYtUVS.cjs";
1
+ import { n as BytesLike, t as FilterJSON } from "../serialize-DRKh6QOr.cjs";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.cjs";
3
3
  //#region src/blocked/blocked.d.ts
4
4
  /** Thrown when an operation requires two filters built with identical parameters. */
@@ -17,7 +17,7 @@ interface BlockedBloomParams {
17
17
  }
18
18
  /**
19
19
  * A blocked (split-block) Bloom filter: confines every lookup to a single cache
20
- * line, trading ~20-30% more space for cache-friendly throughput.
20
+ * line, trading ~15% more space for higher lookup throughput and a lower FPR.
21
21
  *
22
22
  * @example
23
23
  * ```ts
@@ -36,6 +36,17 @@ declare class BlockedBloomFilter {
36
36
  * @returns A new, empty filter.
37
37
  */
38
38
  static create(n: number, epsilon: number): BlockedBloomFilter;
39
+ /**
40
+ * Builds a filter from `keys`, sized for their count at the target
41
+ * false-positive rate. The ergonomic entry point when the key set is already
42
+ * in hand; use {@link BlockedBloomFilter.create} to size for a count known
43
+ * ahead.
44
+ *
45
+ * @param keys - The keys to insert.
46
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
47
+ * @returns A new filter containing every key.
48
+ */
49
+ static from(keys: Iterable<BytesLike>, epsilon: number): BlockedBloomFilter;
39
50
  /**
40
51
  * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
41
52
  * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
@@ -43,6 +54,10 @@ declare class BlockedBloomFilter {
43
54
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
55
  /** Actual bits allocated per key (`total bits / capacity`). */
45
56
  get bitsPerKey(): number;
57
+ /** Number of 256-bit blocks; one of the two fields `union` requires to match. */
58
+ get numBlocks(): number;
59
+ /** Hash seed; the other field `union` requires to match. */
60
+ get seed(): number;
46
61
  /** Number of bits currently set across all lanes. */
47
62
  get length(): number;
48
63
  /**
@@ -67,6 +82,28 @@ declare class BlockedBloomFilter {
67
82
  * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
68
83
  */
69
84
  toBytes(): Uint8Array;
85
+ /**
86
+ * Tests structural equality: `true` when `other` serializes to identical
87
+ * bytes, meaning identical parameters and set bits.
88
+ *
89
+ * @param other - The filter to compare against.
90
+ * @returns `true` if the two filters are byte-for-byte identical.
91
+ */
92
+ equals(other: BlockedBloomFilter): boolean;
93
+ /**
94
+ * Serializes the filter to a JSON-friendly envelope wrapping the base64 of
95
+ * {@link BlockedBloomFilter.toBytes}.
96
+ *
97
+ * @returns The envelope, readable by {@link BlockedBloomFilter.fromJSON}.
98
+ */
99
+ toJSON(): FilterJSON;
100
+ /**
101
+ * Restores a filter from its {@link BlockedBloomFilter.toJSON} envelope.
102
+ *
103
+ * @param value - The JSON envelope.
104
+ * @returns The reconstructed filter.
105
+ */
106
+ static fromJSON(value: unknown): BlockedBloomFilter;
70
107
  /**
71
108
  * Returns a new filter containing the union of this filter and `other`.
72
109
  *
@@ -90,4 +127,4 @@ declare class BlockedBloomFilter {
90
127
  has(key: BytesLike): boolean;
91
128
  }
92
129
  //#endregion
93
- export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, ParamError };
130
+ export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, type FilterJSON, ParamError };
@@ -1,4 +1,4 @@
1
- import { t as BytesLike } from "../bytes-DCuYtUVS.js";
1
+ import { n as BytesLike, t as FilterJSON } from "../serialize-DRKh6QOr.js";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.js";
3
3
  //#region src/blocked/blocked.d.ts
4
4
  /** Thrown when an operation requires two filters built with identical parameters. */
@@ -17,7 +17,7 @@ interface BlockedBloomParams {
17
17
  }
18
18
  /**
19
19
  * A blocked (split-block) Bloom filter: confines every lookup to a single cache
20
- * line, trading ~20-30% more space for cache-friendly throughput.
20
+ * line, trading ~15% more space for higher lookup throughput and a lower FPR.
21
21
  *
22
22
  * @example
23
23
  * ```ts
@@ -36,6 +36,17 @@ declare class BlockedBloomFilter {
36
36
  * @returns A new, empty filter.
37
37
  */
38
38
  static create(n: number, epsilon: number): BlockedBloomFilter;
39
+ /**
40
+ * Builds a filter from `keys`, sized for their count at the target
41
+ * false-positive rate. The ergonomic entry point when the key set is already
42
+ * in hand; use {@link BlockedBloomFilter.create} to size for a count known
43
+ * ahead.
44
+ *
45
+ * @param keys - The keys to insert.
46
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
47
+ * @returns A new filter containing every key.
48
+ */
49
+ static from(keys: Iterable<BytesLike>, epsilon: number): BlockedBloomFilter;
39
50
  /**
40
51
  * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
41
52
  * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
@@ -43,6 +54,10 @@ declare class BlockedBloomFilter {
43
54
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
55
  /** Actual bits allocated per key (`total bits / capacity`). */
45
56
  get bitsPerKey(): number;
57
+ /** Number of 256-bit blocks; one of the two fields `union` requires to match. */
58
+ get numBlocks(): number;
59
+ /** Hash seed; the other field `union` requires to match. */
60
+ get seed(): number;
46
61
  /** Number of bits currently set across all lanes. */
47
62
  get length(): number;
48
63
  /**
@@ -67,6 +82,28 @@ declare class BlockedBloomFilter {
67
82
  * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
68
83
  */
69
84
  toBytes(): Uint8Array;
85
+ /**
86
+ * Tests structural equality: `true` when `other` serializes to identical
87
+ * bytes, meaning identical parameters and set bits.
88
+ *
89
+ * @param other - The filter to compare against.
90
+ * @returns `true` if the two filters are byte-for-byte identical.
91
+ */
92
+ equals(other: BlockedBloomFilter): boolean;
93
+ /**
94
+ * Serializes the filter to a JSON-friendly envelope wrapping the base64 of
95
+ * {@link BlockedBloomFilter.toBytes}.
96
+ *
97
+ * @returns The envelope, readable by {@link BlockedBloomFilter.fromJSON}.
98
+ */
99
+ toJSON(): FilterJSON;
100
+ /**
101
+ * Restores a filter from its {@link BlockedBloomFilter.toJSON} envelope.
102
+ *
103
+ * @param value - The JSON envelope.
104
+ * @returns The reconstructed filter.
105
+ */
106
+ static fromJSON(value: unknown): BlockedBloomFilter;
70
107
  /**
71
108
  * Returns a new filter containing the union of this filter and `other`.
72
109
  *
@@ -90,4 +127,4 @@ declare class BlockedBloomFilter {
90
127
  has(key: BytesLike): boolean;
91
128
  }
92
129
  //#endregion
93
- export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, ParamError };
130
+ export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, type FilterJSON, ParamError };