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 +34 -6
- package/dist/blocked/index.cjs +3 -3
- package/dist/blocked/index.d.cts +1 -1
- package/dist/blocked/index.d.ts +1 -1
- package/dist/blocked/index.js +3 -3
- package/dist/bloom/index.cjs +23 -27
- package/dist/bloom/index.d.cts +18 -14
- package/dist/bloom/index.d.ts +18 -14
- package/dist/bloom/index.js +22 -26
- package/dist/fuse/index.cjs +3 -3
- package/dist/fuse/index.d.cts +1 -1
- package/dist/fuse/index.d.ts +1 -1
- package/dist/fuse/index.js +3 -3
- package/dist/hll/index.cjs +362 -0
- package/dist/hll/index.d.cts +119 -0
- package/dist/hll/index.d.ts +119 -0
- package/dist/hll/index.js +353 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{serialize-ChyWpB9F.d.cts → serialize-Bv1zWzrh.d.cts} +10 -7
- package/dist/{serialize-ChyWpB9F.d.ts → serialize-Bv1zWzrh.d.ts} +10 -7
- package/dist/{serialize-BqIcsR2J.js → serialize-D59Ri81a.js} +17 -13
- package/dist/{serialize-CXnRWItH.cjs → serialize-Ul2LKFJU.cjs} +17 -13
- package/dist/sizing-BLzZk2zr.cjs +28 -0
- package/dist/sizing-BtUrtvF_.js +17 -0
- package/dist/sizing-wYOW27eY.d.cts +22 -0
- package/dist/sizing-wYOW27eY.d.ts +22 -0
- package/package.json +10 -2
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 |
|
|
46
|
-
| -------------------- | ------------- |
|
|
47
|
-
| `distillate/bloom` | Classic Bloom |
|
|
48
|
-
| `distillate/blocked` | Blocked Bloom |
|
|
49
|
-
| `distillate/fuse` | Binary Fuse |
|
|
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
|
|
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):
|
package/dist/blocked/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_serialize = require("../serialize-
|
|
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
|
|
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:
|
|
108
|
+
version: 4,
|
|
109
109
|
type: TYPE,
|
|
110
110
|
flags: 0
|
|
111
111
|
}, 12 + lanes.length, (body, dv) => {
|
package/dist/blocked/index.d.cts
CHANGED
|
@@ -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-
|
|
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
|
/**
|
package/dist/blocked/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
/**
|
package/dist/blocked/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
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:
|
|
107
|
+
version: 4,
|
|
108
108
|
type: TYPE,
|
|
109
109
|
flags: 0
|
|
110
110
|
}, 12 + lanes.length, (body, dv) => {
|
package/dist/bloom/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_serialize = require("../serialize-
|
|
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
|
|
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
|
|
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
|
|
74
|
+
const f = new BloomFilter({
|
|
80
75
|
m,
|
|
81
76
|
k,
|
|
82
|
-
seed
|
|
83
|
-
|
|
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:
|
|
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
|
|
147
|
+
const r = new BloomFilter({
|
|
153
148
|
m: this.#m,
|
|
154
149
|
k: this.#k,
|
|
155
|
-
seed: this.#seed
|
|
156
|
-
|
|
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;
|
package/dist/bloom/index.d.cts
CHANGED
|
@@ -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-
|
|
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
|
-
/**
|
|
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 };
|
package/dist/bloom/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
/**
|
|
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 };
|
package/dist/bloom/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
|
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
|
|
73
|
+
const f = new BloomFilter({
|
|
79
74
|
m,
|
|
80
75
|
k,
|
|
81
|
-
seed
|
|
82
|
-
|
|
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:
|
|
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
|
|
146
|
+
const r = new BloomFilter({
|
|
152
147
|
m: this.#m,
|
|
153
148
|
k: this.#k,
|
|
154
|
-
seed: this.#seed
|
|
155
|
-
|
|
149
|
+
seed: this.#seed,
|
|
150
|
+
n: this.#n
|
|
151
|
+
});
|
|
156
152
|
r.#bits.bytes.set(merged);
|
|
157
153
|
return r;
|
|
158
154
|
}
|
package/dist/fuse/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_serialize = require("../serialize-
|
|
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
|
|
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:
|
|
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) => {
|
package/dist/fuse/index.d.cts
CHANGED
|
@@ -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-
|
|
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 {
|
package/dist/fuse/index.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
package/dist/fuse/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
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:
|
|
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) => {
|