distillate 0.1.1 → 0.2.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
@@ -92,6 +92,19 @@ const precise = BinaryFuse16.from(["alice", "bob", "carol"]);
92
92
 
93
93
  Also: `toBytes` / `fromBytes`. No `add` / `delete`; rebuild `from` the new set to change membership.
94
94
 
95
+ ## Performance
96
+
97
+ Classic Bloom head-to-head at a **matched 1% false-positive rate** over the same 100k keys, measured by identical code (Node, Apple M5):
98
+
99
+ | Classic Bloom | bits/key | measured FPR | `has` throughput |
100
+ | -------------- | -------- | ------------ | ---------------- |
101
+ | **distillate** | 9.59 | 1.03% | ~7.0 M ops/s |
102
+ | bloom-filters | 9.59 | 0.99% | ~0.29 M ops/s |
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.
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).
107
+
95
108
  ## Docs
96
109
 
97
110
  Design notes, the structure decision matrix, hashing, and the binary format live in [`docs/`](./docs):
@@ -99,6 +112,8 @@ Design notes, the structure decision matrix, hashing, and the binary format live
99
112
  - [overview](./docs/overview.md): what and why
100
113
  - [structures](./docs/structures.md): decision matrix and the full lineup
101
114
  - [architecture](./docs/architecture.md), [hashing](./docs/hashing.md), [serialization](./docs/serialization.md)
115
+ - [versioning](./docs/versioning.md): SemVer policy and supported-runtime baseline
116
+ - [API reference](./docs/api): generated from TSDoc (per entry point)
102
117
 
103
118
  ## License
104
119
 
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_serialize = require("../serialize-CHHDM4TQ.cjs");
3
+ const require_params = require("../params-J8p3bKq5.cjs");
3
4
  //#region src/blocked/blocked.ts
4
5
  const TYPE = 2;
5
6
  const SALT = Uint32Array.of(1203114875, 1150766481, 2284105051, 2729912477, 1884591559, 770785867, 2667333959, 1550580529);
@@ -9,15 +10,22 @@ const scratchHash = {
9
10
  h2lo: 0,
10
11
  h2hi: 0
11
12
  };
12
- /**
13
- * Split-block probe: derive one block index and 8 single-bit lane masks for
14
- * `key`. Writes lane word indices into `outWords` and their masks into
15
- * `outBits` (both length 8, caller-owned so no per-call allocation). A block is
16
- * 256 bits = 8 contiguous 32-bit lanes; word `block*8 + i` gets one bit set.
17
- */
13
+ /** Thrown when an operation requires two filters built with identical parameters. */
18
14
  var BlockedBloomParamMismatchError = class extends Error {
15
+ /** Discriminates this error from other `Error`s. */
19
16
  name = "BlockedBloomParamMismatchError";
20
17
  };
18
+ /**
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.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const filter = BlockedBloomFilter.create(100_000, 0.01);
25
+ * filter.add("alice");
26
+ * filter.has("alice"); // true
27
+ * ```
28
+ */
21
29
  var BlockedBloomFilter = class BlockedBloomFilter {
22
30
  #lanes;
23
31
  #numBlocks;
@@ -30,7 +38,16 @@ var BlockedBloomFilter = class BlockedBloomFilter {
30
38
  [3, 16.9],
31
39
  [4, 26.4]
32
40
  ];
41
+ /**
42
+ * Creates a filter sized for `n` expected keys at a target false-positive rate.
43
+ *
44
+ * @param n - Expected number of keys.
45
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
46
+ * @returns A new, empty filter.
47
+ */
33
48
  static create(n, epsilon) {
49
+ require_params.assertPositiveInt(n, "n");
50
+ require_params.assertProbability(epsilon, "epsilon");
34
51
  const t = Math.log10(1 / epsilon);
35
52
  const a = BlockedBloomFilter.#ANCHORS;
36
53
  let seg = a.findIndex((p) => t <= p[0]);
@@ -39,19 +56,33 @@ var BlockedBloomFilter = class BlockedBloomFilter {
39
56
  const [t1, b1] = a[seg] ?? [0, 0];
40
57
  const bitsPerKey = b0 + (b1 - b0) / (t1 - t0) * (t - t0);
41
58
  return new BlockedBloomFilter({
42
- bitsPerKey: Math.ceil(bitsPerKey),
59
+ bitsPerKey: Math.max(1, Math.ceil(bitsPerKey)),
43
60
  capacity: n
44
61
  });
45
62
  }
63
+ /**
64
+ * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
65
+ * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
66
+ */
46
67
  constructor({ bitsPerKey, capacity, seed = 0 }) {
68
+ require_params.assertPositiveFinite(bitsPerKey, "bitsPerKey");
69
+ require_params.assertPositiveInt(capacity, "capacity");
70
+ require_params.assertUint32(seed, "seed");
47
71
  this.#numBlocks = Math.max(1, Math.ceil(bitsPerKey * capacity / 256));
48
72
  this.#lanes = new Uint32Array(this.#numBlocks * 8);
49
73
  this.#seed = seed;
50
74
  this.#n = capacity;
51
75
  }
76
+ /** Actual bits allocated per key (`total bits / capacity`). */
52
77
  get bitsPerKey() {
53
78
  return this.#numBlocks * 256 / this.#n;
54
79
  }
80
+ /**
81
+ * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
82
+ *
83
+ * @param bytes - The serialized filter.
84
+ * @returns The reconstructed filter.
85
+ */
55
86
  static fromBytes(bytes) {
56
87
  const { body } = require_serialize.readHeader(bytes);
57
88
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -67,6 +98,11 @@ var BlockedBloomFilter = class BlockedBloomFilter {
67
98
  new Uint8Array(f.#lanes.buffer).set(body.subarray(12));
68
99
  return f;
69
100
  }
101
+ /**
102
+ * Serializes the filter to a portable little-endian byte layout.
103
+ *
104
+ * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
105
+ */
70
106
  toBytes() {
71
107
  const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
72
108
  const body = new Uint8Array(12 + lanes.length);
@@ -81,6 +117,13 @@ var BlockedBloomFilter = class BlockedBloomFilter {
81
117
  flags: 0
82
118
  }, body);
83
119
  }
120
+ /**
121
+ * Returns a new filter containing the union of this filter and `other`.
122
+ *
123
+ * @param other - A filter built with identical parameters.
124
+ * @returns A new filter reporting membership for keys in either input.
125
+ * @throws {@link BlockedBloomParamMismatchError} if the parameters differ.
126
+ */
84
127
  union(other) {
85
128
  if (this.#numBlocks !== other.#numBlocks || this.#seed !== other.#seed) throw new BlockedBloomParamMismatchError("cannot union blocked Bloom filters whose parameters do not match");
86
129
  const r = new BlockedBloomFilter({
@@ -91,6 +134,11 @@ var BlockedBloomFilter = class BlockedBloomFilter {
91
134
  for (let i = 0; i < this.#lanes.length; i++) r.#lanes[i] = (this.#lanes[i] ?? 0) | (other.#lanes[i] ?? 0);
92
135
  return r;
93
136
  }
137
+ /**
138
+ * Adds a key to the set.
139
+ *
140
+ * @param key - The key to insert, as a string or bytes.
141
+ */
94
142
  add(key) {
95
143
  fillBlock(key, this.#numBlocks, this.#seed, this.#words, this.#bits);
96
144
  for (let i = 0; i < 8; i++) {
@@ -98,6 +146,12 @@ var BlockedBloomFilter = class BlockedBloomFilter {
98
146
  this.#lanes[w] = (this.#lanes[w] ?? 0) | (this.#bits[i] ?? 0);
99
147
  }
100
148
  }
149
+ /**
150
+ * Tests whether a key is in the set.
151
+ *
152
+ * @param key - The key to test.
153
+ * @returns `true` if present (possibly a false positive); `false` guarantees absence.
154
+ */
101
155
  has(key) {
102
156
  fillBlock(key, this.#numBlocks, this.#seed, this.#words, this.#bits);
103
157
  for (let i = 0; i < 8; i++) {
@@ -120,3 +174,4 @@ function fillBlock(key, numBlocks, seed, outWords, outBits) {
120
174
  //#endregion
121
175
  exports.BlockedBloomFilter = BlockedBloomFilter;
122
176
  exports.BlockedBloomParamMismatchError = BlockedBloomParamMismatchError;
177
+ exports.ParamError = require_params.ParamError;
@@ -1,29 +1,82 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.cjs";
2
+ import { t as ParamError } from "../params-DnqJBqLS.cjs";
2
3
  //#region src/blocked/blocked.d.ts
3
- /**
4
- * Split-block probe: derive one block index and 8 single-bit lane masks for
5
- * `key`. Writes lane word indices into `outWords` and their masks into
6
- * `outBits` (both length 8, caller-owned so no per-call allocation). A block is
7
- * 256 bits = 8 contiguous 32-bit lanes; word `block*8 + i` gets one bit set.
8
- */
4
+ /** Thrown when an operation requires two filters built with identical parameters. */
9
5
  declare class BlockedBloomParamMismatchError extends Error {
6
+ /** Discriminates this error from other `Error`s. */
10
7
  override readonly name = "BlockedBloomParamMismatchError";
11
8
  }
9
+ /** Low-level blocked Bloom filter parameters. */
12
10
  interface BlockedBloomParams {
11
+ /** Bits allocated per key; higher lowers the false-positive rate. */
13
12
  bitsPerKey: number;
13
+ /** Expected number of keys. */
14
14
  capacity: number;
15
+ /** Hash seed; defaults to `0`. */
15
16
  seed?: number;
16
17
  }
18
+ /**
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.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const filter = BlockedBloomFilter.create(100_000, 0.01);
25
+ * filter.add("alice");
26
+ * filter.has("alice"); // true
27
+ * ```
28
+ */
17
29
  declare class BlockedBloomFilter {
18
30
  #private;
31
+ /**
32
+ * Creates a filter sized for `n` expected keys at a target false-positive rate.
33
+ *
34
+ * @param n - Expected number of keys.
35
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
36
+ * @returns A new, empty filter.
37
+ */
19
38
  static create(n: number, epsilon: number): BlockedBloomFilter;
39
+ /**
40
+ * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
41
+ * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
42
+ */
20
43
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
+ /** Actual bits allocated per key (`total bits / capacity`). */
21
45
  get bitsPerKey(): number;
46
+ /**
47
+ * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
48
+ *
49
+ * @param bytes - The serialized filter.
50
+ * @returns The reconstructed filter.
51
+ */
22
52
  static fromBytes(bytes: Uint8Array): BlockedBloomFilter;
53
+ /**
54
+ * Serializes the filter to a portable little-endian byte layout.
55
+ *
56
+ * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
57
+ */
23
58
  toBytes(): Uint8Array;
59
+ /**
60
+ * Returns a new filter containing the union of this filter and `other`.
61
+ *
62
+ * @param other - A filter built with identical parameters.
63
+ * @returns A new filter reporting membership for keys in either input.
64
+ * @throws {@link BlockedBloomParamMismatchError} if the parameters differ.
65
+ */
24
66
  union(other: BlockedBloomFilter): BlockedBloomFilter;
67
+ /**
68
+ * Adds a key to the set.
69
+ *
70
+ * @param key - The key to insert, as a string or bytes.
71
+ */
25
72
  add(key: BytesLike): void;
73
+ /**
74
+ * Tests whether a key is in the set.
75
+ *
76
+ * @param key - The key to test.
77
+ * @returns `true` if present (possibly a false positive); `false` guarantees absence.
78
+ */
26
79
  has(key: BytesLike): boolean;
27
80
  }
28
81
  //#endregion
29
- export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams };
82
+ export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, ParamError };
@@ -1,29 +1,82 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.js";
2
+ import { t as ParamError } from "../params-DnqJBqLS.js";
2
3
  //#region src/blocked/blocked.d.ts
3
- /**
4
- * Split-block probe: derive one block index and 8 single-bit lane masks for
5
- * `key`. Writes lane word indices into `outWords` and their masks into
6
- * `outBits` (both length 8, caller-owned so no per-call allocation). A block is
7
- * 256 bits = 8 contiguous 32-bit lanes; word `block*8 + i` gets one bit set.
8
- */
4
+ /** Thrown when an operation requires two filters built with identical parameters. */
9
5
  declare class BlockedBloomParamMismatchError extends Error {
6
+ /** Discriminates this error from other `Error`s. */
10
7
  override readonly name = "BlockedBloomParamMismatchError";
11
8
  }
9
+ /** Low-level blocked Bloom filter parameters. */
12
10
  interface BlockedBloomParams {
11
+ /** Bits allocated per key; higher lowers the false-positive rate. */
13
12
  bitsPerKey: number;
13
+ /** Expected number of keys. */
14
14
  capacity: number;
15
+ /** Hash seed; defaults to `0`. */
15
16
  seed?: number;
16
17
  }
18
+ /**
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.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const filter = BlockedBloomFilter.create(100_000, 0.01);
25
+ * filter.add("alice");
26
+ * filter.has("alice"); // true
27
+ * ```
28
+ */
17
29
  declare class BlockedBloomFilter {
18
30
  #private;
31
+ /**
32
+ * Creates a filter sized for `n` expected keys at a target false-positive rate.
33
+ *
34
+ * @param n - Expected number of keys.
35
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
36
+ * @returns A new, empty filter.
37
+ */
19
38
  static create(n: number, epsilon: number): BlockedBloomFilter;
39
+ /**
40
+ * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
41
+ * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
42
+ */
20
43
  constructor({ bitsPerKey, capacity, seed }: BlockedBloomParams);
44
+ /** Actual bits allocated per key (`total bits / capacity`). */
21
45
  get bitsPerKey(): number;
46
+ /**
47
+ * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
48
+ *
49
+ * @param bytes - The serialized filter.
50
+ * @returns The reconstructed filter.
51
+ */
22
52
  static fromBytes(bytes: Uint8Array): BlockedBloomFilter;
53
+ /**
54
+ * Serializes the filter to a portable little-endian byte layout.
55
+ *
56
+ * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
57
+ */
23
58
  toBytes(): Uint8Array;
59
+ /**
60
+ * Returns a new filter containing the union of this filter and `other`.
61
+ *
62
+ * @param other - A filter built with identical parameters.
63
+ * @returns A new filter reporting membership for keys in either input.
64
+ * @throws {@link BlockedBloomParamMismatchError} if the parameters differ.
65
+ */
24
66
  union(other: BlockedBloomFilter): BlockedBloomFilter;
67
+ /**
68
+ * Adds a key to the set.
69
+ *
70
+ * @param key - The key to insert, as a string or bytes.
71
+ */
25
72
  add(key: BytesLike): void;
73
+ /**
74
+ * Tests whether a key is in the set.
75
+ *
76
+ * @param key - The key to test.
77
+ * @returns `true` if present (possibly a false positive); `false` guarantees absence.
78
+ */
26
79
  has(key: BytesLike): boolean;
27
80
  }
28
81
  //#endregion
29
- export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams };
82
+ export { BlockedBloomFilter, BlockedBloomParamMismatchError, type BlockedBloomParams, ParamError };
@@ -1,4 +1,5 @@
1
1
  import { i as hash128KeyInto, n as readHeader, o as reduce, r as writeHeader } from "../serialize-5XQ5y_R-.js";
2
+ import { a as assertUint32, i as assertProbability, n as assertPositiveFinite, r as assertPositiveInt, t as ParamError } from "../params-ChTRNxM9.js";
2
3
  //#region src/blocked/blocked.ts
3
4
  const TYPE = 2;
4
5
  const SALT = Uint32Array.of(1203114875, 1150766481, 2284105051, 2729912477, 1884591559, 770785867, 2667333959, 1550580529);
@@ -8,15 +9,22 @@ const scratchHash = {
8
9
  h2lo: 0,
9
10
  h2hi: 0
10
11
  };
11
- /**
12
- * Split-block probe: derive one block index and 8 single-bit lane masks for
13
- * `key`. Writes lane word indices into `outWords` and their masks into
14
- * `outBits` (both length 8, caller-owned so no per-call allocation). A block is
15
- * 256 bits = 8 contiguous 32-bit lanes; word `block*8 + i` gets one bit set.
16
- */
12
+ /** Thrown when an operation requires two filters built with identical parameters. */
17
13
  var BlockedBloomParamMismatchError = class extends Error {
14
+ /** Discriminates this error from other `Error`s. */
18
15
  name = "BlockedBloomParamMismatchError";
19
16
  };
17
+ /**
18
+ * A blocked (split-block) Bloom filter: confines every lookup to a single cache
19
+ * line, trading ~20-30% more space for cache-friendly throughput.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const filter = BlockedBloomFilter.create(100_000, 0.01);
24
+ * filter.add("alice");
25
+ * filter.has("alice"); // true
26
+ * ```
27
+ */
20
28
  var BlockedBloomFilter = class BlockedBloomFilter {
21
29
  #lanes;
22
30
  #numBlocks;
@@ -29,7 +37,16 @@ var BlockedBloomFilter = class BlockedBloomFilter {
29
37
  [3, 16.9],
30
38
  [4, 26.4]
31
39
  ];
40
+ /**
41
+ * Creates a filter sized for `n` expected keys at a target false-positive rate.
42
+ *
43
+ * @param n - Expected number of keys.
44
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
45
+ * @returns A new, empty filter.
46
+ */
32
47
  static create(n, epsilon) {
48
+ assertPositiveInt(n, "n");
49
+ assertProbability(epsilon, "epsilon");
33
50
  const t = Math.log10(1 / epsilon);
34
51
  const a = BlockedBloomFilter.#ANCHORS;
35
52
  let seg = a.findIndex((p) => t <= p[0]);
@@ -38,19 +55,33 @@ var BlockedBloomFilter = class BlockedBloomFilter {
38
55
  const [t1, b1] = a[seg] ?? [0, 0];
39
56
  const bitsPerKey = b0 + (b1 - b0) / (t1 - t0) * (t - t0);
40
57
  return new BlockedBloomFilter({
41
- bitsPerKey: Math.ceil(bitsPerKey),
58
+ bitsPerKey: Math.max(1, Math.ceil(bitsPerKey)),
42
59
  capacity: n
43
60
  });
44
61
  }
62
+ /**
63
+ * Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
64
+ * {@link BlockedBloomFilter.create} unless restoring a specific configuration.
65
+ */
45
66
  constructor({ bitsPerKey, capacity, seed = 0 }) {
67
+ assertPositiveFinite(bitsPerKey, "bitsPerKey");
68
+ assertPositiveInt(capacity, "capacity");
69
+ assertUint32(seed, "seed");
46
70
  this.#numBlocks = Math.max(1, Math.ceil(bitsPerKey * capacity / 256));
47
71
  this.#lanes = new Uint32Array(this.#numBlocks * 8);
48
72
  this.#seed = seed;
49
73
  this.#n = capacity;
50
74
  }
75
+ /** Actual bits allocated per key (`total bits / capacity`). */
51
76
  get bitsPerKey() {
52
77
  return this.#numBlocks * 256 / this.#n;
53
78
  }
79
+ /**
80
+ * Restores a filter from its {@link BlockedBloomFilter.toBytes} serialization.
81
+ *
82
+ * @param bytes - The serialized filter.
83
+ * @returns The reconstructed filter.
84
+ */
54
85
  static fromBytes(bytes) {
55
86
  const { body } = readHeader(bytes);
56
87
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -66,6 +97,11 @@ var BlockedBloomFilter = class BlockedBloomFilter {
66
97
  new Uint8Array(f.#lanes.buffer).set(body.subarray(12));
67
98
  return f;
68
99
  }
100
+ /**
101
+ * Serializes the filter to a portable little-endian byte layout.
102
+ *
103
+ * @returns The serialized filter, readable by {@link BlockedBloomFilter.fromBytes}.
104
+ */
69
105
  toBytes() {
70
106
  const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
71
107
  const body = new Uint8Array(12 + lanes.length);
@@ -80,6 +116,13 @@ var BlockedBloomFilter = class BlockedBloomFilter {
80
116
  flags: 0
81
117
  }, body);
82
118
  }
119
+ /**
120
+ * Returns a new filter containing the union of this filter and `other`.
121
+ *
122
+ * @param other - A filter built with identical parameters.
123
+ * @returns A new filter reporting membership for keys in either input.
124
+ * @throws {@link BlockedBloomParamMismatchError} if the parameters differ.
125
+ */
83
126
  union(other) {
84
127
  if (this.#numBlocks !== other.#numBlocks || this.#seed !== other.#seed) throw new BlockedBloomParamMismatchError("cannot union blocked Bloom filters whose parameters do not match");
85
128
  const r = new BlockedBloomFilter({
@@ -90,6 +133,11 @@ var BlockedBloomFilter = class BlockedBloomFilter {
90
133
  for (let i = 0; i < this.#lanes.length; i++) r.#lanes[i] = (this.#lanes[i] ?? 0) | (other.#lanes[i] ?? 0);
91
134
  return r;
92
135
  }
136
+ /**
137
+ * Adds a key to the set.
138
+ *
139
+ * @param key - The key to insert, as a string or bytes.
140
+ */
93
141
  add(key) {
94
142
  fillBlock(key, this.#numBlocks, this.#seed, this.#words, this.#bits);
95
143
  for (let i = 0; i < 8; i++) {
@@ -97,6 +145,12 @@ var BlockedBloomFilter = class BlockedBloomFilter {
97
145
  this.#lanes[w] = (this.#lanes[w] ?? 0) | (this.#bits[i] ?? 0);
98
146
  }
99
147
  }
148
+ /**
149
+ * Tests whether a key is in the set.
150
+ *
151
+ * @param key - The key to test.
152
+ * @returns `true` if present (possibly a false positive); `false` guarantees absence.
153
+ */
100
154
  has(key) {
101
155
  fillBlock(key, this.#numBlocks, this.#seed, this.#words, this.#bits);
102
156
  for (let i = 0; i < 8; i++) {
@@ -117,4 +171,4 @@ function fillBlock(key, numBlocks, seed, outWords, outBits) {
117
171
  }
118
172
  }
119
173
  //#endregion
120
- export { BlockedBloomFilter, BlockedBloomParamMismatchError };
174
+ export { BlockedBloomFilter, BlockedBloomParamMismatchError, ParamError };
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_serialize = require("../serialize-CHHDM4TQ.cjs");
3
+ const require_params = require("../params-J8p3bKq5.cjs");
3
4
  //#region src/core/bitset.ts
4
5
  var BitSetRangeError = class extends RangeError {
5
6
  name = "BitSetRangeError";
@@ -48,9 +49,23 @@ function optimal(n, epsilon) {
48
49
  //#endregion
49
50
  //#region src/bloom/bloom.ts
50
51
  const TYPE = 1;
52
+ /** Thrown when an operation requires two filters built with identical parameters. */
51
53
  var BloomParamMismatchError = class extends Error {
54
+ /** Discriminates this error from other `Error`s. */
52
55
  name = "BloomParamMismatchError";
53
56
  };
57
+ /**
58
+ * A classic Bloom filter: a space-efficient set with a tunable false-positive
59
+ * rate and zero false negatives.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const filter = BloomFilter.create(100_000, 0.01);
64
+ * filter.add("alice");
65
+ * filter.has("alice"); // true
66
+ * filter.has("bob"); // false (or a ~1% false positive)
67
+ * ```
68
+ */
54
69
  var BloomFilter = class BloomFilter {
55
70
  #bits;
56
71
  #m;
@@ -58,11 +73,26 @@ var BloomFilter = class BloomFilter {
58
73
  #seed;
59
74
  #scratch;
60
75
  #n;
76
+ /**
77
+ * Creates a filter sized for `n` expected keys at a target false-positive rate.
78
+ *
79
+ * @param n - Expected number of keys.
80
+ * @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
81
+ * @returns A new, empty filter.
82
+ */
61
83
  static create(n, epsilon) {
84
+ require_params.assertPositiveInt(n, "n");
85
+ require_params.assertProbability(epsilon, "epsilon");
62
86
  const f = new BloomFilter(optimal(n, epsilon));
63
87
  f.#n = n;
64
88
  return f;
65
89
  }
90
+ /**
91
+ * Restores a filter from its {@link BloomFilter.toBytes} serialization.
92
+ *
93
+ * @param bytes - The serialized filter.
94
+ * @returns The reconstructed filter.
95
+ */
66
96
  static fromBytes(bytes) {
67
97
  const { body } = require_serialize.readHeader(bytes);
68
98
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -77,7 +107,14 @@ var BloomFilter = class BloomFilter {
77
107
  f.#bits.bytes.set(body.subarray(9));
78
108
  return f;
79
109
  }
110
+ /**
111
+ * Constructs a filter from low-level {@link BloomParams}. Prefer
112
+ * {@link BloomFilter.create} unless restoring a specific configuration.
113
+ */
80
114
  constructor({ m, k, seed = 0 }) {
115
+ require_params.assertPositiveInt(m, "m");
116
+ require_params.assertPositiveInt(k, "k");
117
+ require_params.assertUint32(seed, "seed");
81
118
  this.#bits = new BitSet(m);
82
119
  this.#m = m;
83
120
  this.#k = k;
@@ -89,6 +126,11 @@ var BloomFilter = class BloomFilter {
89
126
  get bitsPerKey() {
90
127
  return this.#m / this.#n;
91
128
  }
129
+ /**
130
+ * Serializes the filter to a portable little-endian byte layout.
131
+ *
132
+ * @returns The serialized filter, readable by {@link BloomFilter.fromBytes}.
133
+ */
92
134
  toBytes() {
93
135
  const payload = this.#bits.bytes;
94
136
  const body = new Uint8Array(9 + payload.length);
@@ -103,6 +145,13 @@ var BloomFilter = class BloomFilter {
103
145
  flags: 0
104
146
  }, body);
105
147
  }
148
+ /**
149
+ * Returns a new filter containing the union of this filter and `other`.
150
+ *
151
+ * @param other - A filter built with identical parameters.
152
+ * @returns A new filter reporting membership for keys in either input.
153
+ * @throws {@link BloomParamMismatchError} if the parameters differ.
154
+ */
106
155
  union(other) {
107
156
  if (this.#m !== other.#m || this.#k !== other.#k || this.#seed !== other.#seed) throw new BloomParamMismatchError("cannot union Bloom filters whose parameters do not match");
108
157
  const a = this.#bits.bytes;
@@ -117,10 +166,21 @@ var BloomFilter = class BloomFilter {
117
166
  r.#bits.bytes.set(merged);
118
167
  return r;
119
168
  }
169
+ /**
170
+ * Adds a key to the set.
171
+ *
172
+ * @param key - The key to insert, as a string or bytes.
173
+ */
120
174
  add(key) {
121
175
  require_serialize.probeInto(key, this.#k, this.#m, this.#seed, this.#scratch);
122
176
  for (let i = 0; i < this.#k; i++) this.#bits.set(this.#scratch[i] ?? 0);
123
177
  }
178
+ /**
179
+ * Tests whether a key is in the set.
180
+ *
181
+ * @param key - The key to test.
182
+ * @returns `true` if present (possibly a false positive); `false` guarantees absence.
183
+ */
124
184
  has(key) {
125
185
  require_serialize.probeInto(key, this.#k, this.#m, this.#seed, this.#scratch);
126
186
  for (let i = 0; i < this.#k; i++) if (!this.#bits.get(this.#scratch[i] ?? 0)) return false;
@@ -130,3 +190,4 @@ var BloomFilter = class BloomFilter {
130
190
  //#endregion
131
191
  exports.BloomFilter = BloomFilter;
132
192
  exports.BloomParamMismatchError = BloomParamMismatchError;
193
+ exports.ParamError = require_params.ParamError;