distillate 0.8.2 → 0.9.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,11 +42,16 @@ 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 | Mutable? | Use for |
46
- | -------------------- | ------------- | -------- | -------------------------------------------------------- |
47
- | `distillate/bloom` | Classic Bloom | yes | Familiar default, migration from `bloom-filters` |
48
- | `distillate/blocked` | Blocked Bloom | yes | Faster lookups and a lower FPR for a small space premium |
49
- | `distillate/fuse` | Binary Fuse | no | Static set built once and queried a lot; least space |
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 |
51
+
52
+ The filters are mutable except Binary Fuse, which is built once from the whole
53
+ key set. HyperLogLog is not a filter: it counts distinct keys and cannot report
54
+ whether it saw any particular one.
50
55
 
51
56
  ### Classic Bloom (`distillate/bloom`)
52
57
 
@@ -86,7 +91,7 @@ import { BinaryFuse8, BinaryFuse16 } from "distillate/fuse";
86
91
  const filter = BinaryFuse8.from(["alice", "bob", "carol"]);
87
92
  filter.has("alice"); // true
88
93
  filter.size; // 3
89
- filter.bitsPerKey; // 64 at this size; see below
94
+ filter.bitsPerKey; // 64
90
95
 
91
96
  // Lower false-positive rate, twice the space:
92
97
  const precise = BinaryFuse16.from(["alice", "bob", "carol"]);
@@ -96,6 +101,29 @@ const precise = BinaryFuse16.from(["alice", "bob", "carol"]);
96
101
 
97
102
  Also: `toBytes` / `fromBytes`. No `add` / `delete`; rebuild `from` the new set to change membership.
98
103
 
104
+ ### HyperLogLog (`distillate/hll`)
105
+
106
+ A **sketch**, not a filter: it counts distinct keys in space fixed by precision rather than by the answer, and cannot report whether it saw any particular key. 12 KiB counts a thousand distinct keys or a billion, at about 0.8% relative error. Full API and sizing: [HyperLogLog guide](https://distillate.akxp.net/guides/hll/).
107
+
108
+ ```ts
109
+ import { HyperLogLog } from "distillate/hll";
110
+
111
+ const sketch = HyperLogLog.create(0.01); // target relative error
112
+ sketch.add("alice");
113
+ sketch.add("bob");
114
+ sketch.add("alice"); // already counted
115
+
116
+ sketch.count(); // 2
117
+
118
+ // Merge per-shard sketches without double-counting the overlap:
119
+ const other = HyperLogLog.from(["bob", "carol"], 0.01);
120
+ sketch.union(other).count(); // 3
121
+ ```
122
+
123
+ Below a few thousand distinct keys the sketch counts rather than estimates, so small answers are exact. It switches to fixed-size registers on its own once that stops paying, with nothing to configure.
124
+
125
+ Also: `equals`, `toBytes` / `fromBytes`, `toJSON` / `fromJSON`, `standardError`, `hllSizing(relativeError)`, and a low-level `new HyperLogLog({ p, seed })`.
126
+
99
127
  ## Performance
100
128
 
101
129
  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_serialize = require("../serialize-CXnRWItH.cjs");
2
+ const require_serialize = require("../serialize-Ul2LKFJU.cjs");
3
3
  const require_params = require("../params-UTZbJ22c.cjs");
4
4
  //#region src/blocked/blocked.ts
5
5
  const TYPE = 2;
@@ -89,7 +89,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
89
89
  }
90
90
  static fromBytes(bytes) {
91
91
  const { type, flags, body } = require_serialize.readHeader(bytes);
92
- if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
92
+ if (type !== TYPE) throw new require_serialize.SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
93
93
  if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
94
94
  require_serialize.assertMinBodyLength(body.length, 12, "blocked");
95
95
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -105,7 +105,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
105
105
  toBytes() {
106
106
  const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
107
107
  return require_serialize.writeFrame({
108
- version: 3,
108
+ version: 4,
109
109
  type: TYPE,
110
110
  flags: 0
111
111
  }, 12 + lanes.length, (body, dv) => {
@@ -1,4 +1,4 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.cjs";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.cjs";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.cjs";
3
3
  //#region src/blocked/blocked.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.js";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.js";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.js";
3
3
  //#region src/blocked/blocked.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, i as TruncatedError, l as bytesEqual, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope, v as hash32x2Into, x as reduce } from "../serialize-BqIcsR2J.js";
1
+ import { a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, i as TruncatedError, l as bytesEqual, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope, v as hash32x2Into, x as reduce } from "../serialize-D59Ri81a.js";
2
2
  import { i as assertProbability, n as assertPositiveFinite, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
3
3
  //#region src/blocked/blocked.ts
4
4
  const TYPE = 2;
@@ -88,7 +88,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
88
88
  }
89
89
  static fromBytes(bytes) {
90
90
  const { type, flags, body } = readHeader(bytes);
91
- if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
91
+ if (type !== TYPE) throw new SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
92
92
  if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
93
93
  assertMinBodyLength(body.length, 12, "blocked");
94
94
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -104,7 +104,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
104
104
  toBytes() {
105
105
  const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
106
106
  return writeFrame({
107
- version: 3,
107
+ version: 4,
108
108
  type: TYPE,
109
109
  flags: 0
110
110
  }, 12 + lanes.length, (body, dv) => {
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-CXnRWItH.cjs");
2
+ const require_serialize = require("../serialize-Ul2LKFJU.cjs");
3
3
  const require_params = require("../params-UTZbJ22c.cjs");
4
+ const require_sizing = require("../sizing-BLzZk2zr.cjs");
4
5
  //#region src/core/bitset.ts
5
6
  var BitSetRangeError = class extends RangeError {
6
7
  name = "BitSetRangeError";
@@ -32,15 +33,6 @@ var BitSet = class {
32
33
  }
33
34
  };
34
35
  //#endregion
35
- //#region src/core/sizing.ts
36
- function bloomSizing(n, epsilon) {
37
- const m = Math.ceil(-n * Math.log(epsilon) / (Math.LN2 * Math.LN2));
38
- return {
39
- m,
40
- k: Math.max(1, Math.round(m / n * Math.LN2))
41
- };
42
- }
43
- //#endregion
44
36
  //#region src/bloom/bloom.ts
45
37
  const TYPE = 1;
46
38
  var BloomParamMismatchError = class extends Error {
@@ -57,7 +49,10 @@ var BloomFilter = class BloomFilter {
57
49
  require_params.assertPositiveInt(n, "n");
58
50
  require_params.assertUint32(n, "n");
59
51
  require_params.assertProbability(epsilon, "epsilon");
60
- return BloomFilter.#withN(bloomSizing(n, epsilon), n);
52
+ return new BloomFilter({
53
+ ...require_sizing.bloomSizing(n, epsilon),
54
+ n
55
+ });
61
56
  }
62
57
  static from(keys, epsilon) {
63
58
  const arr = [...keys];
@@ -67,7 +62,7 @@ var BloomFilter = class BloomFilter {
67
62
  }
68
63
  static fromBytes(bytes) {
69
64
  const { type, flags, body } = require_serialize.readHeader(bytes);
70
- if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
65
+ if (type !== TYPE) throw new require_serialize.SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
71
66
  if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
72
67
  require_serialize.assertMinBodyLength(body.length, 14, "bloom");
73
68
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -76,31 +71,31 @@ var BloomFilter = class BloomFilter {
76
71
  const seed = dv.getUint32(6, true);
77
72
  const n = dv.getUint32(10, true);
78
73
  require_serialize.assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
79
- const f = BloomFilter.#withN({
74
+ const f = new BloomFilter({
80
75
  m,
81
76
  k,
82
- seed
83
- }, n);
77
+ seed,
78
+ n
79
+ });
84
80
  f.#bits.bytes.set(body.subarray(14));
85
81
  return f;
86
82
  }
87
- constructor({ m, k, seed = 0 }) {
83
+ constructor({ m, k, seed = 0, n }) {
88
84
  require_params.assertPositiveInt(m, "m");
89
85
  require_params.assertUint32(m, "m");
90
86
  require_params.assertPositiveInt(k, "k");
91
87
  require_params.assertUint16(k, "k");
92
88
  require_params.assertUint32(seed, "seed");
89
+ if (n !== void 0) {
90
+ require_params.assertPositiveInt(n, "n");
91
+ require_params.assertUint32(n, "n");
92
+ }
93
93
  this.#bits = new BitSet(m);
94
94
  this.#m = m;
95
95
  this.#k = k;
96
96
  this.#seed = seed;
97
97
  this.#scratch = new Uint32Array(k);
98
- this.#n = Math.round(m * Math.LN2 / k);
99
- }
100
- static #withN(params, n) {
101
- const f = new BloomFilter(params);
102
- f.#n = n;
103
- return f;
98
+ this.#n = n ?? Math.max(1, Math.round(m * Math.LN2 / k));
104
99
  }
105
100
  get m() {
106
101
  return this.#m;
@@ -123,7 +118,7 @@ var BloomFilter = class BloomFilter {
123
118
  toBytes() {
124
119
  const payload = this.#bits.bytes;
125
120
  return require_serialize.writeFrame({
126
- version: 3,
121
+ version: 4,
127
122
  type: TYPE,
128
123
  flags: 0
129
124
  }, 14 + payload.length, (body, dv) => {
@@ -149,11 +144,12 @@ var BloomFilter = class BloomFilter {
149
144
  const b = other.#bits.bytes;
150
145
  const merged = new Uint8Array(a.length);
151
146
  for (let i = 0; i < a.length; i++) merged[i] = (a[i] ?? 0) | (b[i] ?? 0);
152
- const r = BloomFilter.#withN({
147
+ const r = new BloomFilter({
153
148
  m: this.#m,
154
149
  k: this.#k,
155
- seed: this.#seed
156
- }, this.#n);
150
+ seed: this.#seed,
151
+ n: this.#n
152
+ });
157
153
  r.#bits.bytes.set(merged);
158
154
  return r;
159
155
  }
@@ -177,4 +173,4 @@ exports.SerializationError = require_serialize.SerializationError;
177
173
  exports.TruncatedError = require_serialize.TruncatedError;
178
174
  exports.UnknownHashVariantError = require_serialize.UnknownHashVariantError;
179
175
  exports.UnknownVersionError = require_serialize.UnknownVersionError;
180
- exports.bloomSizing = bloomSizing;
176
+ exports.bloomSizing = require_sizing.bloomSizing;
@@ -1,5 +1,6 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.cjs";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.cjs";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.cjs";
3
+ import { r as bloomSizing, t as BloomSizing } from "../sizing-wYOW27eY.cjs";
3
4
  //#region src/bloom/bloom.d.ts
4
5
  /** Thrown when an operation requires two filters built with identical parameters. */
5
6
  declare class BloomParamMismatchError extends Error {
@@ -14,6 +15,12 @@ interface BloomParams {
14
15
  k: number;
15
16
  /** Hash seed; defaults to `0`. */
16
17
  seed?: number;
18
+ /**
19
+ * Expected number of keys. Omitted, it is derived as `m * ln2 / k`, the
20
+ * count this geometry is optimal for, which need not be the count the
21
+ * geometry was solved for.
22
+ */
23
+ n?: number;
17
24
  }
18
25
  /**
19
26
  * A classic Bloom filter: a space-efficient set with a tunable false-positive
@@ -58,7 +65,7 @@ declare class BloomFilter {
58
65
  * Constructs a filter from low-level {@link BloomParams}. Prefer
59
66
  * {@link BloomFilter.create} unless restoring a specific configuration.
60
67
  */
61
- constructor({ m, k, seed }: BloomParams);
68
+ constructor({ m, k, seed, n }: BloomParams);
62
69
  /** Number of bits in the filter. */
63
70
  get m(): number;
64
71
  /** Number of hash probes per key. */
@@ -67,7 +74,15 @@ declare class BloomFilter {
67
74
  get seed(): number;
68
75
  /** Number of bits currently set. */
69
76
  get length(): number;
70
- /** Analytic design bits-per-key `m / n`. */
77
+ /**
78
+ * Analytic design bits-per-key, `m / n`.
79
+ *
80
+ * A filter built without an explicit `n` derives one as `m * ln2 / k`, the
81
+ * count its geometry is optimal for. That need not be the count the
82
+ * geometry was solved for, so the same `m` and `k` can report a different
83
+ * cost depending on how the filter was constructed. `create` and
84
+ * `fromBytes` always carry the real `n`.
85
+ */
71
86
  get bitsPerKey(): number;
72
87
  /**
73
88
  * Estimates the current false-positive rate from the actual fill,
@@ -128,15 +143,4 @@ declare class BloomFilter {
128
143
  has(key: BytesLike): boolean;
129
144
  }
130
145
  //#endregion
131
- //#region src/core/sizing.d.ts
132
- /** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
133
- interface BloomSizing {
134
- /** Number of bits in the filter. */
135
- m: number;
136
- /** Number of hash probes per key. */
137
- k: number;
138
- }
139
- /** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
140
- declare function bloomSizing(n: number, epsilon: number): BloomSizing;
141
- //#endregion
142
146
  export { BadMagicError, BloomFilter, BloomParamMismatchError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, bloomSizing };
@@ -1,5 +1,6 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.js";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.js";
2
2
  import { t as ParamError } from "../params-DnqJBqLS.js";
3
+ import { r as bloomSizing, t as BloomSizing } from "../sizing-wYOW27eY.js";
3
4
  //#region src/bloom/bloom.d.ts
4
5
  /** Thrown when an operation requires two filters built with identical parameters. */
5
6
  declare class BloomParamMismatchError extends Error {
@@ -14,6 +15,12 @@ interface BloomParams {
14
15
  k: number;
15
16
  /** Hash seed; defaults to `0`. */
16
17
  seed?: number;
18
+ /**
19
+ * Expected number of keys. Omitted, it is derived as `m * ln2 / k`, the
20
+ * count this geometry is optimal for, which need not be the count the
21
+ * geometry was solved for.
22
+ */
23
+ n?: number;
17
24
  }
18
25
  /**
19
26
  * A classic Bloom filter: a space-efficient set with a tunable false-positive
@@ -58,7 +65,7 @@ declare class BloomFilter {
58
65
  * Constructs a filter from low-level {@link BloomParams}. Prefer
59
66
  * {@link BloomFilter.create} unless restoring a specific configuration.
60
67
  */
61
- constructor({ m, k, seed }: BloomParams);
68
+ constructor({ m, k, seed, n }: BloomParams);
62
69
  /** Number of bits in the filter. */
63
70
  get m(): number;
64
71
  /** Number of hash probes per key. */
@@ -67,7 +74,15 @@ declare class BloomFilter {
67
74
  get seed(): number;
68
75
  /** Number of bits currently set. */
69
76
  get length(): number;
70
- /** Analytic design bits-per-key `m / n`. */
77
+ /**
78
+ * Analytic design bits-per-key, `m / n`.
79
+ *
80
+ * A filter built without an explicit `n` derives one as `m * ln2 / k`, the
81
+ * count its geometry is optimal for. That need not be the count the
82
+ * geometry was solved for, so the same `m` and `k` can report a different
83
+ * cost depending on how the filter was constructed. `create` and
84
+ * `fromBytes` always carry the real `n`.
85
+ */
71
86
  get bitsPerKey(): number;
72
87
  /**
73
88
  * Estimates the current false-positive rate from the actual fill,
@@ -128,15 +143,4 @@ declare class BloomFilter {
128
143
  has(key: BytesLike): boolean;
129
144
  }
130
145
  //#endregion
131
- //#region src/core/sizing.d.ts
132
- /** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
133
- interface BloomSizing {
134
- /** Number of bits in the filter. */
135
- m: number;
136
- /** Number of hash probes per key. */
137
- k: number;
138
- }
139
- /** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
140
- declare function bloomSizing(n: number, epsilon: number): BloomSizing;
141
- //#endregion
142
146
  export { BadMagicError, BloomFilter, BloomParamMismatchError, type BloomParams, type BloomSizing, ChecksumError, type FilterJSON, ParamError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, bloomSizing };
@@ -1,5 +1,6 @@
1
- import { a as UnknownHashVariantError, b as probeInto, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, i as TruncatedError, l as bytesEqual, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope } from "../serialize-BqIcsR2J.js";
1
+ import { a as UnknownHashVariantError, b as probeInto, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, i as TruncatedError, l as bytesEqual, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope } from "../serialize-D59Ri81a.js";
2
2
  import { a as assertUint16, i as assertProbability, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-CeajSrwv.js";
3
+ import { t as bloomSizing } from "../sizing-BtUrtvF_.js";
3
4
  //#region src/core/bitset.ts
4
5
  var BitSetRangeError = class extends RangeError {
5
6
  name = "BitSetRangeError";
@@ -31,15 +32,6 @@ var BitSet = class {
31
32
  }
32
33
  };
33
34
  //#endregion
34
- //#region src/core/sizing.ts
35
- function bloomSizing(n, epsilon) {
36
- const m = Math.ceil(-n * Math.log(epsilon) / (Math.LN2 * Math.LN2));
37
- return {
38
- m,
39
- k: Math.max(1, Math.round(m / n * Math.LN2))
40
- };
41
- }
42
- //#endregion
43
35
  //#region src/bloom/bloom.ts
44
36
  const TYPE = 1;
45
37
  var BloomParamMismatchError = class extends Error {
@@ -56,7 +48,10 @@ var BloomFilter = class BloomFilter {
56
48
  assertPositiveInt(n, "n");
57
49
  assertUint32(n, "n");
58
50
  assertProbability(epsilon, "epsilon");
59
- return BloomFilter.#withN(bloomSizing(n, epsilon), n);
51
+ return new BloomFilter({
52
+ ...bloomSizing(n, epsilon),
53
+ n
54
+ });
60
55
  }
61
56
  static from(keys, epsilon) {
62
57
  const arr = [...keys];
@@ -66,7 +61,7 @@ var BloomFilter = class BloomFilter {
66
61
  }
67
62
  static fromBytes(bytes) {
68
63
  const { type, flags, body } = readHeader(bytes);
69
- if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
64
+ if (type !== TYPE) throw new SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
70
65
  if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
71
66
  assertMinBodyLength(body.length, 14, "bloom");
72
67
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -75,31 +70,31 @@ var BloomFilter = class BloomFilter {
75
70
  const seed = dv.getUint32(6, true);
76
71
  const n = dv.getUint32(10, true);
77
72
  assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
78
- const f = BloomFilter.#withN({
73
+ const f = new BloomFilter({
79
74
  m,
80
75
  k,
81
- seed
82
- }, n);
76
+ seed,
77
+ n
78
+ });
83
79
  f.#bits.bytes.set(body.subarray(14));
84
80
  return f;
85
81
  }
86
- constructor({ m, k, seed = 0 }) {
82
+ constructor({ m, k, seed = 0, n }) {
87
83
  assertPositiveInt(m, "m");
88
84
  assertUint32(m, "m");
89
85
  assertPositiveInt(k, "k");
90
86
  assertUint16(k, "k");
91
87
  assertUint32(seed, "seed");
88
+ if (n !== void 0) {
89
+ assertPositiveInt(n, "n");
90
+ assertUint32(n, "n");
91
+ }
92
92
  this.#bits = new BitSet(m);
93
93
  this.#m = m;
94
94
  this.#k = k;
95
95
  this.#seed = seed;
96
96
  this.#scratch = new Uint32Array(k);
97
- this.#n = Math.round(m * Math.LN2 / k);
98
- }
99
- static #withN(params, n) {
100
- const f = new BloomFilter(params);
101
- f.#n = n;
102
- return f;
97
+ this.#n = n ?? Math.max(1, Math.round(m * Math.LN2 / k));
103
98
  }
104
99
  get m() {
105
100
  return this.#m;
@@ -122,7 +117,7 @@ var BloomFilter = class BloomFilter {
122
117
  toBytes() {
123
118
  const payload = this.#bits.bytes;
124
119
  return writeFrame({
125
- version: 3,
120
+ version: 4,
126
121
  type: TYPE,
127
122
  flags: 0
128
123
  }, 14 + payload.length, (body, dv) => {
@@ -148,11 +143,12 @@ var BloomFilter = class BloomFilter {
148
143
  const b = other.#bits.bytes;
149
144
  const merged = new Uint8Array(a.length);
150
145
  for (let i = 0; i < a.length; i++) merged[i] = (a[i] ?? 0) | (b[i] ?? 0);
151
- const r = BloomFilter.#withN({
146
+ const r = new BloomFilter({
152
147
  m: this.#m,
153
148
  k: this.#k,
154
- seed: this.#seed
155
- }, this.#n);
149
+ seed: this.#seed,
150
+ n: this.#n
151
+ });
156
152
  r.#bits.bytes.set(merged);
157
153
  return r;
158
154
  }
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_serialize = require("../serialize-CXnRWItH.cjs");
2
+ const require_serialize = require("../serialize-Ul2LKFJU.cjs");
3
3
  //#region src/fuse/fuse.ts
4
4
  const ARITY = 3;
5
5
  const TYPE_FUSE8 = 3;
@@ -159,7 +159,7 @@ function buildState(keys, alloc) {
159
159
  }
160
160
  function fuseStateFromBytes(bytes, expectedType) {
161
161
  const { type, flags, body } = require_serialize.readHeader(bytes);
162
- if (type !== expectedType) throw new require_serialize.SerializationError(`expected AMQF type ${String(expectedType)}, got ${String(type)}`);
162
+ if (type !== expectedType) throw new require_serialize.SerializationError(`expected DSTL type ${String(expectedType)}, got ${String(type)}`);
163
163
  if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
164
164
  require_serialize.assertMinBodyLength(body.length, 16, "fuse");
165
165
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -216,7 +216,7 @@ var BinaryFuse = class {
216
216
  toBytes() {
217
217
  const laneBytes = new Uint8Array(this.#fp.buffer, this.#fp.byteOffset, this.#fp.byteLength);
218
218
  return require_serialize.writeFrame({
219
- version: 3,
219
+ version: 4,
220
220
  type: this.#fp.BYTES_PER_ELEMENT === 1 ? TYPE_FUSE8 : TYPE_FUSE16,
221
221
  flags: 0
222
222
  }, 16 + laneBytes.length, (body, dv) => {
@@ -1,4 +1,4 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.cjs";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.cjs";
2
2
  //#region src/fuse/fuse.d.ts
3
3
  /** Thrown when binary fuse construction fails to converge on the key set. */
4
4
  declare class BinaryFuseBuildError extends Error {
@@ -1,4 +1,4 @@
1
- import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-ChyWpB9F.js";
1
+ import { a as TruncatedError, c as BytesLike, i as SerializationError, n as ChecksumError, o as UnknownHashVariantError, r as FilterJSON, s as UnknownVersionError, t as BadMagicError } from "../serialize-Bv1zWzrh.js";
2
2
  //#region src/fuse/fuse.d.ts
3
3
  /** Thrown when binary fuse construction fails to converge on the key set. */
4
4
  declare class BinaryFuseBuildError extends Error {
@@ -1,4 +1,4 @@
1
- import { _ as hash128KeyInto, a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, g as fmix64, h as RLO, i as TruncatedError, l as bytesEqual, m as RHI, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope, y as mul64 } from "../serialize-BqIcsR2J.js";
1
+ import { _ as hash128KeyInto, a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, g as fmix64, h as RLO, i as TruncatedError, l as bytesEqual, m as RHI, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope, y as mul64 } from "../serialize-D59Ri81a.js";
2
2
  //#region src/fuse/fuse.ts
3
3
  const ARITY = 3;
4
4
  const TYPE_FUSE8 = 3;
@@ -158,7 +158,7 @@ function buildState(keys, alloc) {
158
158
  }
159
159
  function fuseStateFromBytes(bytes, expectedType) {
160
160
  const { type, flags, body } = readHeader(bytes);
161
- if (type !== expectedType) throw new SerializationError(`expected AMQF type ${String(expectedType)}, got ${String(type)}`);
161
+ if (type !== expectedType) throw new SerializationError(`expected DSTL type ${String(expectedType)}, got ${String(type)}`);
162
162
  if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
163
163
  assertMinBodyLength(body.length, 16, "fuse");
164
164
  const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
@@ -215,7 +215,7 @@ var BinaryFuse = class {
215
215
  toBytes() {
216
216
  const laneBytes = new Uint8Array(this.#fp.buffer, this.#fp.byteOffset, this.#fp.byteLength);
217
217
  return writeFrame({
218
- version: 3,
218
+ version: 4,
219
219
  type: this.#fp.BYTES_PER_ELEMENT === 1 ? TYPE_FUSE8 : TYPE_FUSE16,
220
220
  flags: 0
221
221
  }, 16 + laneBytes.length, (body, dv) => {