distillate 0.13.0 → 0.14.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.
Files changed (40) hide show
  1. package/README.md +38 -12
  2. package/dist/blocked/index.cjs +1 -1
  3. package/dist/blocked/index.js +1 -1
  4. package/dist/bloom/index.cjs +3 -4
  5. package/dist/bloom/index.d.cts +12 -2
  6. package/dist/bloom/index.d.ts +12 -2
  7. package/dist/bloom/index.js +2 -3
  8. package/dist/bytes-Be7tq1F7.js +9 -0
  9. package/dist/bytes-D2duG1jq.cjs +14 -0
  10. package/dist/countmin/index.cjs +13 -4
  11. package/dist/countmin/index.d.cts +15 -2
  12. package/dist/countmin/index.d.ts +15 -2
  13. package/dist/countmin/index.js +12 -3
  14. package/dist/cuckoo/index.cjs +20 -9
  15. package/dist/cuckoo/index.js +20 -9
  16. package/dist/distinct-BhxDB8mW.js +61 -0
  17. package/dist/distinct-Ck5PKuCS.cjs +66 -0
  18. package/dist/fuse/index.cjs +6 -11
  19. package/dist/fuse/index.js +6 -11
  20. package/dist/{hasher-3Cyn7Yg-.js → hasher-DOdmZ-LI.js} +1 -8
  21. package/dist/{hasher-MhY7aQ8F.cjs → hasher-DPXmP7SH.cjs} +2 -9
  22. package/dist/hll/index.cjs +10 -4
  23. package/dist/hll/index.d.cts +13 -2
  24. package/dist/hll/index.d.ts +13 -2
  25. package/dist/hll/index.js +9 -3
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/scalable/index.cjs +5 -6
  29. package/dist/scalable/index.js +2 -3
  30. package/dist/{bitset-QYGqxnEf.cjs → sizing-Ch0SDdza.cjs} +15 -0
  31. package/dist/{bitset-Cf2jwRJX.js → sizing-zxCBlyhO.js} +10 -1
  32. package/dist/topk/index.cjs +423 -0
  33. package/dist/topk/index.d.cts +207 -0
  34. package/dist/topk/index.d.ts +207 -0
  35. package/dist/topk/index.js +411 -0
  36. package/package.json +15 -7
  37. package/dist/sizing-BmKfHf_V.d.cts +0 -34
  38. package/dist/sizing-BmKfHf_V.d.ts +0 -34
  39. package/dist/sizing-D3-_7pdP.js +0 -25
  40. package/dist/sizing-R1pwbzjC.cjs +0 -42
package/README.md CHANGED
@@ -42,20 +42,22 @@ 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/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/cuckoo` | Cuckoo | seen this key? | Keys come and go; the one filter with delete |
52
- | `distillate/hll` | HyperLogLog | how many distinct? | Counting distinct users, IPs, or keys in fixed space |
53
- | `distillate/countmin` | Count-Min | how many times? | Counting events per key 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/cuckoo` | Cuckoo | seen this key? | Keys come and go; the one filter with delete |
52
+ | `distillate/hll` | HyperLogLog | how many distinct? | Counting distinct users, IPs, or keys in fixed space |
53
+ | `distillate/countmin` | Count-Min | how many times? | Counting events per key in fixed space |
54
+ | `distillate/topk` | Top-K | which are heaviest? | Finding the most frequent keys in fixed space |
54
55
 
55
56
  The filters are mutable except Binary Fuse, which is built once from the whole
56
- key set. HyperLogLog and Count-Min are sketches rather than filters: neither
57
- reports whether it saw any particular key. HyperLogLog counts distinct keys,
58
- Count-Min counts how often each key appeared.
57
+ key set. HyperLogLog, Count-Min and Top-K are sketches rather than filters:
58
+ none reports whether it saw any particular key. HyperLogLog counts distinct
59
+ keys, Count-Min counts how often each key appeared, and Top-K reports which
60
+ keys appeared most.
59
61
 
60
62
  ### Classic Bloom (`distillate/bloom`)
61
63
 
@@ -189,6 +191,30 @@ hits.union(other).count("/login"); // 7
189
191
 
190
192
  Also: `equals`, `toBytes` / `fromBytes`, `toJSON` / `fromJSON`, `error()`, `epsilon`, `delta`, `width`, `depth`, `countMinSizing(epsilon, delta)`, and a low-level `new CountMinSketch({ width, depth, seed })`.
191
193
 
194
+ ### Top-K (`distillate/topk`)
195
+
196
+ A **sketch** that lists the keys seen most often, in a table fixed by the error you ask for rather than by how many keys arrive. A key it holds never reads below its true count, and any key heavier than `error()` is guaranteed to be held. Full API and the bound: [Top-K guide](https://distillate.akxp.net/guides/topk/).
197
+
198
+ ```ts
199
+ import { TopK } from "distillate/topk";
200
+
201
+ const routes = TopK.create(0.01); // error factor
202
+ routes.add("/login", 5);
203
+ routes.add("/signup", 2);
204
+ routes.add("/about");
205
+
206
+ const decoder = new TextDecoder();
207
+ routes
208
+ .top(2)
209
+ .map((e) => decoder.decode(e.key))
210
+ .join(); // "/login,/signup"
211
+ routes.count("/login"); // 5
212
+ ```
213
+
214
+ Keys come back as `Uint8Array`, exactly as recorded, so a serialized Top-K sketch holds your keys verbatim, unlike every other frame here. `union` combines sketches soundly but, unlike Count-Min's, not byte for byte as one sketch fed both streams.
215
+
216
+ Also: `equals`, `toBytes` / `fromBytes`, `toJSON` / `fromJSON`, `total`, `error()`, `epsilon`, `capacity`, `topKSizing(epsilon)`, and a low-level `new TopK({ capacity, seed })`.
217
+
192
218
  ## Performance
193
219
 
194
220
  Classic Bloom head-to-head at a **matched 1% false-positive rate** over the same 100k keys, measured by identical code (cross-library harness, node v24.14.1, Apple M5):
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-MhY7aQ8F.cjs");
2
+ const require_hasher = require("../hasher-DPXmP7SH.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, u as reduce } from "../hasher-3Cyn7Yg-.js";
1
+ import { a as hash32x2Into, u as reduce } from "../hasher-DOdmZ-LI.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,9 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bitset = require("../bitset-QYGqxnEf.cjs");
3
- const require_hasher = require("../hasher-MhY7aQ8F.cjs");
2
+ const require_sizing = require("../sizing-Ch0SDdza.cjs");
3
+ const require_hasher = require("../hasher-DPXmP7SH.cjs");
4
4
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
5
5
  const require_params = require("../params-UTZbJ22c.cjs");
6
- const require_sizing = require("../sizing-R1pwbzjC.cjs");
7
6
  //#region src/bloom/bloom.ts
8
7
  const TYPE = 1;
9
8
  const PARAMS_SIZE = 16;
@@ -64,7 +63,7 @@ var BloomFilter = class BloomFilter {
64
63
  require_params.assertPositiveInt(n, "n");
65
64
  require_params.assertUint32(n, "n");
66
65
  }
67
- this.#bits = new require_bitset.BitSet(m);
66
+ this.#bits = new require_sizing.BitSet(m);
68
67
  this.#m = m;
69
68
  this.#k = k;
70
69
  this.#seed = seed;
@@ -1,7 +1,6 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.cjs";
2
2
  import { a as ReservedBitsError, c as UnknownHashVariantError, l as UnknownVersionError, n as ChecksumError, o as SerializationError, r as FilterJSON, s as TruncatedError, t as BadMagicError } from "../serialize--xR6vGSW.cjs";
3
3
  import { t as ParamError } from "../params-DnqJBqLS.cjs";
4
- import { i as bloomSizing, t as BloomSizing } from "../sizing-BmKfHf_V.cjs";
5
4
  //#region src/bloom/bloom.d.ts
6
5
  /** Thrown when an operation requires two filters built with identical parameters. */
7
6
  export declare class BloomParamMismatchError extends Error {
@@ -144,4 +143,15 @@ export declare class BloomFilter {
144
143
  has(key: BytesLike): boolean;
145
144
  }
146
145
  //#endregion
147
- export { BadMagicError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, bloomSizing };
146
+ //#region src/core/sizing.d.ts
147
+ /** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
148
+ interface BloomSizing {
149
+ /** Number of bits in the filter. */
150
+ m: number;
151
+ /** Number of hash probes per key. */
152
+ k: number;
153
+ }
154
+ /** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
155
+ export declare function bloomSizing(n: number, epsilon: number): BloomSizing;
156
+ //#endregion
157
+ export { BadMagicError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError };
@@ -1,7 +1,6 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.js";
2
2
  import { a as ReservedBitsError, c as UnknownHashVariantError, l as UnknownVersionError, n as ChecksumError, o as SerializationError, r as FilterJSON, s as TruncatedError, t as BadMagicError } from "../serialize--xR6vGSW.js";
3
3
  import { t as ParamError } from "../params-DnqJBqLS.js";
4
- import { i as bloomSizing, t as BloomSizing } from "../sizing-BmKfHf_V.js";
5
4
  //#region src/bloom/bloom.d.ts
6
5
  /** Thrown when an operation requires two filters built with identical parameters. */
7
6
  export declare class BloomParamMismatchError extends Error {
@@ -144,4 +143,15 @@ export declare class BloomFilter {
144
143
  has(key: BytesLike): boolean;
145
144
  }
146
145
  //#endregion
147
- export { BadMagicError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, bloomSizing };
146
+ //#region src/core/sizing.d.ts
147
+ /** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
148
+ interface BloomSizing {
149
+ /** Number of bits in the filter. */
150
+ m: number;
151
+ /** Number of hash probes per key. */
152
+ k: number;
153
+ }
154
+ /** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
155
+ export declare function bloomSizing(n: number, epsilon: number): BloomSizing;
156
+ //#endregion
157
+ export { BadMagicError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError };
@@ -1,8 +1,7 @@
1
- import { t as BitSet } from "../bitset-Cf2jwRJX.js";
2
- import { c as probeInto } from "../hasher-3Cyn7Yg-.js";
1
+ import { n as BitSet, t as bloomSizing } from "../sizing-zxCBlyhO.js";
2
+ import { c as probeInto } from "../hasher-DOdmZ-LI.js";
3
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";
4
4
  import { a as assertUint16, i as assertProbability, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
5
- import { t as bloomSizing } from "../sizing-D3-_7pdP.js";
6
5
  //#region src/bloom/bloom.ts
7
6
  const TYPE = 1;
8
7
  const PARAMS_SIZE = 16;
@@ -0,0 +1,9 @@
1
+ //#region src/core/bytes.ts
2
+ const encoder = new TextEncoder();
3
+ function normalize(input) {
4
+ if (typeof input === "string") return encoder.encode(input);
5
+ if (input instanceof Uint8Array) return input;
6
+ return new Uint8Array(input);
7
+ }
8
+ //#endregion
9
+ export { normalize as t };
@@ -0,0 +1,14 @@
1
+ //#region src/core/bytes.ts
2
+ const encoder = new TextEncoder();
3
+ function normalize(input) {
4
+ if (typeof input === "string") return encoder.encode(input);
5
+ if (input instanceof Uint8Array) return input;
6
+ return new Uint8Array(input);
7
+ }
8
+ //#endregion
9
+ Object.defineProperty(exports, "normalize", {
10
+ enumerable: true,
11
+ get: function() {
12
+ return normalize;
13
+ }
14
+ });
@@ -1,8 +1,17 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-MhY7aQ8F.cjs");
2
+ const require_hasher = require("../hasher-DPXmP7SH.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
4
  const require_params = require("../params-UTZbJ22c.cjs");
5
- const require_sizing = require("../sizing-R1pwbzjC.cjs");
5
+ //#region src/countmin/sizing.ts
6
+ function countMinSizing(epsilon, delta) {
7
+ require_params.assertProbability(epsilon, "epsilon");
8
+ require_params.assertProbability(delta, "delta");
9
+ return {
10
+ width: Math.ceil(Math.E / epsilon),
11
+ depth: Math.ceil(Math.log(1 / delta))
12
+ };
13
+ }
14
+ //#endregion
6
15
  //#region src/countmin/countmin.ts
7
16
  const TYPE = 8;
8
17
  const PARAMS_SIZE = 16;
@@ -37,7 +46,7 @@ var CountMinSketch = class CountMinSketch {
37
46
  static create(epsilon, delta, options = {}) {
38
47
  return new CountMinSketch({
39
48
  ...options,
40
- ...require_sizing.countMinSizing(epsilon, delta)
49
+ ...countMinSizing(epsilon, delta)
41
50
  });
42
51
  }
43
52
  static from(keys, epsilon, delta, options = {}) {
@@ -191,4 +200,4 @@ exports.SerializationError = require_serialize.SerializationError;
191
200
  exports.TruncatedError = require_serialize.TruncatedError;
192
201
  exports.UnknownHashVariantError = require_serialize.UnknownHashVariantError;
193
202
  exports.UnknownVersionError = require_serialize.UnknownVersionError;
194
- exports.countMinSizing = require_sizing.countMinSizing;
203
+ exports.countMinSizing = countMinSizing;
@@ -1,7 +1,6 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.cjs";
2
2
  import { a as ReservedBitsError, c as UnknownHashVariantError, l as UnknownVersionError, n as ChecksumError, o as SerializationError, r as FilterJSON, s as TruncatedError, t as BadMagicError } from "../serialize--xR6vGSW.cjs";
3
3
  import { t as ParamError } from "../params-DnqJBqLS.cjs";
4
- import { a as countMinSizing, n as CountMinSizing } from "../sizing-BmKfHf_V.cjs";
5
4
  //#region src/countmin/countmin.d.ts
6
5
  /** Thrown when an operation requires two sketches built with identical parameters. */
7
6
  export declare class CountMinParamMismatchError extends Error {
@@ -175,4 +174,18 @@ export declare class CountMinSketch {
175
174
  count(key: BytesLike): number;
176
175
  }
177
176
  //#endregion
178
- export { BadMagicError, ChecksumError, type CountMinOptions, type CountMinParams, type CountMinSizing, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, countMinSizing };
177
+ //#region src/countmin/sizing.d.ts
178
+ /** Count-Min geometry: the `CountMinParams` fields a sizing solve determines. */
179
+ interface CountMinSizing {
180
+ /** Counters per row. */
181
+ width: number;
182
+ /** Number of rows, one probe each. */
183
+ depth: number;
184
+ }
185
+ /**
186
+ * Count-Min sizing: `width` columns and `depth` rows for an estimate at most
187
+ * `epsilon * total` above the true count, with probability `1 - delta`.
188
+ */
189
+ export declare function countMinSizing(epsilon: number, delta: number): CountMinSizing;
190
+ //#endregion
191
+ export { BadMagicError, ChecksumError, type CountMinOptions, type CountMinParams, type CountMinSizing, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError };
@@ -1,7 +1,6 @@
1
1
  import { t as BytesLike } from "../bytes-DCuYtUVS.js";
2
2
  import { a as ReservedBitsError, c as UnknownHashVariantError, l as UnknownVersionError, n as ChecksumError, o as SerializationError, r as FilterJSON, s as TruncatedError, t as BadMagicError } from "../serialize--xR6vGSW.js";
3
3
  import { t as ParamError } from "../params-DnqJBqLS.js";
4
- import { a as countMinSizing, n as CountMinSizing } from "../sizing-BmKfHf_V.js";
5
4
  //#region src/countmin/countmin.d.ts
6
5
  /** Thrown when an operation requires two sketches built with identical parameters. */
7
6
  export declare class CountMinParamMismatchError extends Error {
@@ -175,4 +174,18 @@ export declare class CountMinSketch {
175
174
  count(key: BytesLike): number;
176
175
  }
177
176
  //#endregion
178
- export { BadMagicError, ChecksumError, type CountMinOptions, type CountMinParams, type CountMinSizing, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, countMinSizing };
177
+ //#region src/countmin/sizing.d.ts
178
+ /** Count-Min geometry: the `CountMinParams` fields a sizing solve determines. */
179
+ interface CountMinSizing {
180
+ /** Counters per row. */
181
+ width: number;
182
+ /** Number of rows, one probe each. */
183
+ depth: number;
184
+ }
185
+ /**
186
+ * Count-Min sizing: `width` columns and `depth` rows for an estimate at most
187
+ * `epsilon * total` above the true count, with probability `1 - delta`.
188
+ */
189
+ export declare function countMinSizing(epsilon: number, delta: number): CountMinSizing;
190
+ //#endregion
191
+ export { BadMagicError, ChecksumError, type CountMinOptions, type CountMinParams, type CountMinSizing, type FilterJSON, ParamError, ReservedBitsError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError };
@@ -1,7 +1,16 @@
1
- import { a as hash32x2Into, s as probeAt } from "../hasher-3Cyn7Yg-.js";
1
+ import { a as hash32x2Into, s as probeAt } from "../hasher-DOdmZ-LI.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
- import { o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
4
- import { n as countMinSizing } from "../sizing-D3-_7pdP.js";
3
+ import { i as assertProbability, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
4
+ //#region src/countmin/sizing.ts
5
+ function countMinSizing(epsilon, delta) {
6
+ assertProbability(epsilon, "epsilon");
7
+ assertProbability(delta, "delta");
8
+ return {
9
+ width: Math.ceil(Math.E / epsilon),
10
+ depth: Math.ceil(Math.log(1 / delta))
11
+ };
12
+ }
13
+ //#endregion
5
14
  //#region src/countmin/countmin.ts
6
15
  const TYPE = 8;
7
16
  const PARAMS_SIZE = 16;
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-MhY7aQ8F.cjs");
2
+ const require_hasher = require("../hasher-DPXmP7SH.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
4
  const require_params = require("../params-UTZbJ22c.cjs");
5
+ const require_distinct = require("../distinct-Ck5PKuCS.cjs");
5
6
  function cuckooSizing(n, epsilon) {
6
7
  require_params.assertProbability(epsilon, "epsilon");
7
8
  const f = Math.max(4, Math.ceil(Math.log2(8 / epsilon)));
@@ -54,17 +55,21 @@ var CuckooFilter = class CuckooFilter {
54
55
  });
55
56
  }
56
57
  static from(keys, epsilon, options = {}) {
57
- const seen = new Set();
58
- const distinct = [];
58
+ const distinct = new require_distinct.DistinctHashes(4);
59
59
  for (const key of keys) {
60
60
  require_hasher.hash128KeyInto(key, options.seed ?? 0, HASH);
61
- const id = `${String(HASH.w0)},${String(HASH.w1)},${String(HASH.w2)},${String(HASH.w3)}`;
62
- if (seen.has(id)) continue;
63
- seen.add(id);
64
- distinct.push(key);
61
+ distinct.add(HASH);
62
+ }
63
+ const f = CuckooFilter.create(Math.max(1, distinct.size), epsilon, options);
64
+ const words = distinct.words;
65
+ for (let at = 0; at < words.length; at += 4) {
66
+ HASH.w0 = words[at] ?? 0;
67
+ HASH.w1 = words[at + 1] ?? 0;
68
+ HASH.w2 = words[at + 2] ?? 0;
69
+ HASH.w3 = words[at + 3] ?? 0;
70
+ f.#derive();
71
+ f.#insert();
65
72
  }
66
- const f = CuckooFilter.create(Math.max(1, distinct.length), epsilon, options);
67
- for (const k of distinct) f.add(k);
68
73
  return f;
69
74
  }
70
75
  static fromBytes(bytes) {
@@ -134,6 +139,9 @@ var CuckooFilter = class CuckooFilter {
134
139
  }
135
140
  add(key) {
136
141
  this.#hash(key);
142
+ this.#insert();
143
+ }
144
+ #insert() {
137
145
  if (this.#replace(this.#i1, 0, this.#fp) || this.#replace(this.#i2, 0, this.#fp)) {
138
146
  this.#count++;
139
147
  return;
@@ -202,6 +210,9 @@ var CuckooFilter = class CuckooFilter {
202
210
  }
203
211
  #hash(key) {
204
212
  require_hasher.hash128KeyInto(key, this.#seed, HASH);
213
+ this.#derive();
214
+ }
215
+ #derive() {
205
216
  this.#fp = HASH.w1 >>> 32 - this.#f || 1;
206
217
  this.#i1 = require_hasher.reduce(HASH.w0, this.#buckets);
207
218
  this.#i2 = this.#alt(this.#i1, this.#fp);
@@ -1,6 +1,7 @@
1
- import { i as hash128KeyInto, u as reduce } from "../hasher-3Cyn7Yg-.js";
1
+ import { i as hash128KeyInto, u as reduce } from "../hasher-DOdmZ-LI.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, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
4
+ import { t as DistinctHashes } from "../distinct-BhxDB8mW.js";
4
5
  function cuckooSizing(n, epsilon) {
5
6
  assertProbability(epsilon, "epsilon");
6
7
  const f = Math.max(4, Math.ceil(Math.log2(8 / epsilon)));
@@ -53,17 +54,21 @@ var CuckooFilter = class CuckooFilter {
53
54
  });
54
55
  }
55
56
  static from(keys, epsilon, options = {}) {
56
- const seen = new Set();
57
- const distinct = [];
57
+ const distinct = new DistinctHashes(4);
58
58
  for (const key of keys) {
59
59
  hash128KeyInto(key, options.seed ?? 0, HASH);
60
- const id = `${String(HASH.w0)},${String(HASH.w1)},${String(HASH.w2)},${String(HASH.w3)}`;
61
- if (seen.has(id)) continue;
62
- seen.add(id);
63
- distinct.push(key);
60
+ distinct.add(HASH);
61
+ }
62
+ const f = CuckooFilter.create(Math.max(1, distinct.size), epsilon, options);
63
+ const words = distinct.words;
64
+ for (let at = 0; at < words.length; at += 4) {
65
+ HASH.w0 = words[at] ?? 0;
66
+ HASH.w1 = words[at + 1] ?? 0;
67
+ HASH.w2 = words[at + 2] ?? 0;
68
+ HASH.w3 = words[at + 3] ?? 0;
69
+ f.#derive();
70
+ f.#insert();
64
71
  }
65
- const f = CuckooFilter.create(Math.max(1, distinct.length), epsilon, options);
66
- for (const k of distinct) f.add(k);
67
72
  return f;
68
73
  }
69
74
  static fromBytes(bytes) {
@@ -133,6 +138,9 @@ var CuckooFilter = class CuckooFilter {
133
138
  }
134
139
  add(key) {
135
140
  this.#hash(key);
141
+ this.#insert();
142
+ }
143
+ #insert() {
136
144
  if (this.#replace(this.#i1, 0, this.#fp) || this.#replace(this.#i2, 0, this.#fp)) {
137
145
  this.#count++;
138
146
  return;
@@ -201,6 +209,9 @@ var CuckooFilter = class CuckooFilter {
201
209
  }
202
210
  #hash(key) {
203
211
  hash128KeyInto(key, this.#seed, HASH);
212
+ this.#derive();
213
+ }
214
+ #derive() {
204
215
  this.#fp = HASH.w1 >>> 32 - this.#f || 1;
205
216
  this.#i1 = reduce(HASH.w0, this.#buckets);
206
217
  this.#i2 = this.#alt(this.#i1, this.#fp);
@@ -0,0 +1,61 @@
1
+ //#region src/core/distinct.ts
2
+ const START = 1024;
3
+ var DistinctHashes = class {
4
+ #width;
5
+ #words;
6
+ #table = new Int32Array(2 * START);
7
+ #size = 0;
8
+ constructor(width) {
9
+ this.#width = width;
10
+ this.#words = new Uint32Array(START * width);
11
+ }
12
+ get size() {
13
+ return this.#size;
14
+ }
15
+ get words() {
16
+ return this.#words.subarray(0, this.#size * this.#width);
17
+ }
18
+ add(hash) {
19
+ const width = this.#width;
20
+ const w0 = hash.w0 >>> 0;
21
+ const w1 = hash.w1 >>> 0;
22
+ const w2 = hash.w2 >>> 0;
23
+ const w3 = hash.w3 >>> 0;
24
+ if ((this.#size + 1) * width > this.#words.length) this.#grow();
25
+ const words = this.#words;
26
+ const mask = this.#table.length - 1;
27
+ let slot = w0 & mask;
28
+ for (;;) {
29
+ const held = this.#table[slot] ?? 0;
30
+ if (held === 0) break;
31
+ const at = (held - 1) * width;
32
+ if (words[at] === w0 && words[at + 1] === w1 && (width === 2 || words[at + 2] === w2 && words[at + 3] === w3)) return false;
33
+ slot = slot + 1 & mask;
34
+ }
35
+ const at = this.#size * width;
36
+ words[at] = w0;
37
+ words[at + 1] = w1;
38
+ if (width === 4) {
39
+ words[at + 2] = w2;
40
+ words[at + 3] = w3;
41
+ }
42
+ this.#size++;
43
+ this.#table[slot] = this.#size;
44
+ return true;
45
+ }
46
+ #grow() {
47
+ const words = new Uint32Array(this.#words.length * 2);
48
+ words.set(this.#words);
49
+ this.#words = words;
50
+ this.#table = new Int32Array(this.#table.length * 2);
51
+ for (let i = 0; i < this.#size; i++) this.#place(i);
52
+ }
53
+ #place(i) {
54
+ const mask = this.#table.length - 1;
55
+ let slot = (this.#words[i * this.#width] ?? 0) & mask;
56
+ while ((this.#table[slot] ?? 0) !== 0) slot = slot + 1 & mask;
57
+ this.#table[slot] = i + 1;
58
+ }
59
+ };
60
+ //#endregion
61
+ export { DistinctHashes as t };
@@ -0,0 +1,66 @@
1
+ //#region src/core/distinct.ts
2
+ const START = 1024;
3
+ var DistinctHashes = class {
4
+ #width;
5
+ #words;
6
+ #table = new Int32Array(2 * START);
7
+ #size = 0;
8
+ constructor(width) {
9
+ this.#width = width;
10
+ this.#words = new Uint32Array(START * width);
11
+ }
12
+ get size() {
13
+ return this.#size;
14
+ }
15
+ get words() {
16
+ return this.#words.subarray(0, this.#size * this.#width);
17
+ }
18
+ add(hash) {
19
+ const width = this.#width;
20
+ const w0 = hash.w0 >>> 0;
21
+ const w1 = hash.w1 >>> 0;
22
+ const w2 = hash.w2 >>> 0;
23
+ const w3 = hash.w3 >>> 0;
24
+ if ((this.#size + 1) * width > this.#words.length) this.#grow();
25
+ const words = this.#words;
26
+ const mask = this.#table.length - 1;
27
+ let slot = w0 & mask;
28
+ for (;;) {
29
+ const held = this.#table[slot] ?? 0;
30
+ if (held === 0) break;
31
+ const at = (held - 1) * width;
32
+ if (words[at] === w0 && words[at + 1] === w1 && (width === 2 || words[at + 2] === w2 && words[at + 3] === w3)) return false;
33
+ slot = slot + 1 & mask;
34
+ }
35
+ const at = this.#size * width;
36
+ words[at] = w0;
37
+ words[at + 1] = w1;
38
+ if (width === 4) {
39
+ words[at + 2] = w2;
40
+ words[at + 3] = w3;
41
+ }
42
+ this.#size++;
43
+ this.#table[slot] = this.#size;
44
+ return true;
45
+ }
46
+ #grow() {
47
+ const words = new Uint32Array(this.#words.length * 2);
48
+ words.set(this.#words);
49
+ this.#words = words;
50
+ this.#table = new Int32Array(this.#table.length * 2);
51
+ for (let i = 0; i < this.#size; i++) this.#place(i);
52
+ }
53
+ #place(i) {
54
+ const mask = this.#table.length - 1;
55
+ let slot = (this.#words[i * this.#width] ?? 0) & mask;
56
+ while ((this.#table[slot] ?? 0) !== 0) slot = slot + 1 & mask;
57
+ this.#table[slot] = i + 1;
58
+ }
59
+ };
60
+ //#endregion
61
+ Object.defineProperty(exports, "DistinctHashes", {
62
+ enumerable: true,
63
+ get: function() {
64
+ return DistinctHashes;
65
+ }
66
+ });
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hasher = require("../hasher-MhY7aQ8F.cjs");
2
+ const require_hasher = require("../hasher-DPXmP7SH.cjs");
3
3
  const require_serialize = require("../serialize-CHikZ4GH.cjs");
4
+ const require_distinct = require("../distinct-Ck5PKuCS.cjs");
4
5
  //#region src/fuse/fuse.ts
5
6
  const ARITY = 3;
6
7
  const TYPE_FUSE8 = 3;
@@ -131,18 +132,12 @@ function buildFingerprints(fp, hashes, params, maxAttempts = 100) {
131
132
  throw new BinaryFuseBuildError("binary fuse construction failed");
132
133
  }
133
134
  function buildState(keys, alloc) {
134
- const hashList = [];
135
- const seen = new Set();
135
+ const distinct = new require_distinct.DistinctHashes(2);
136
136
  for (const key of keys) {
137
137
  require_hasher.hash128KeyInto(key, 0, scratchHash);
138
- const lo = scratchHash.w0 >>> 0;
139
- const hi = scratchHash.w1 >>> 0;
140
- const id = `${String(lo)},${String(hi)}`;
141
- if (seen.has(id)) continue;
142
- seen.add(id);
143
- hashList.push(lo, hi);
138
+ distinct.add(scratchHash);
144
139
  }
145
- const size = hashList.length / 2;
140
+ const size = distinct.size;
146
141
  const params = computeParams(size);
147
142
  if (size === 0) return {
148
143
  fp: alloc(0),
@@ -150,7 +145,7 @@ function buildState(keys, alloc) {
150
145
  params,
151
146
  size: 0
152
147
  };
153
- const hashes = Uint32Array.from(hashList);
148
+ const hashes = distinct.words;
154
149
  const fp = alloc(params.arrayLength);
155
150
  return {
156
151
  fp,
@@ -1,5 +1,6 @@
1
- import { i as hash128KeyInto, n as RLO, o as mul64, r as fmix64, t as RHI } from "../hasher-3Cyn7Yg-.js";
1
+ import { i as hash128KeyInto, n as RLO, o as mul64, r as fmix64, t as RHI } from "../hasher-DOdmZ-LI.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
+ import { t as DistinctHashes } from "../distinct-BhxDB8mW.js";
3
4
  //#region src/fuse/fuse.ts
4
5
  const ARITY = 3;
5
6
  const TYPE_FUSE8 = 3;
@@ -130,18 +131,12 @@ function buildFingerprints(fp, hashes, params, maxAttempts = 100) {
130
131
  throw new BinaryFuseBuildError("binary fuse construction failed");
131
132
  }
132
133
  function buildState(keys, alloc) {
133
- const hashList = [];
134
- const seen = new Set();
134
+ const distinct = new DistinctHashes(2);
135
135
  for (const key of keys) {
136
136
  hash128KeyInto(key, 0, scratchHash);
137
- const lo = scratchHash.w0 >>> 0;
138
- const hi = scratchHash.w1 >>> 0;
139
- const id = `${String(lo)},${String(hi)}`;
140
- if (seen.has(id)) continue;
141
- seen.add(id);
142
- hashList.push(lo, hi);
137
+ distinct.add(scratchHash);
143
138
  }
144
- const size = hashList.length / 2;
139
+ const size = distinct.size;
145
140
  const params = computeParams(size);
146
141
  if (size === 0) return {
147
142
  fp: alloc(0),
@@ -149,7 +144,7 @@ function buildState(keys, alloc) {
149
144
  params,
150
145
  size: 0
151
146
  };
152
- const hashes = Uint32Array.from(hashList);
147
+ const hashes = distinct.words;
153
148
  const fp = alloc(params.arrayLength);
154
149
  return {
155
150
  fp,
@@ -1,11 +1,4 @@
1
- //#region src/core/bytes.ts
2
- const encoder = new TextEncoder();
3
- function normalize(input) {
4
- if (typeof input === "string") return encoder.encode(input);
5
- if (input instanceof Uint8Array) return input;
6
- return new Uint8Array(input);
7
- }
8
- //#endregion
1
+ import { t as normalize } from "./bytes-Be7tq1F7.js";
9
2
  //#region src/core/hasher.ts
10
3
  let RLO = 0;
11
4
  let RHI = 0;