distillate 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/blocked/index.cjs +115 -46
- package/dist/blocked/index.d.cts +40 -3
- package/dist/blocked/index.d.ts +40 -3
- package/dist/blocked/index.js +115 -46
- package/dist/bloom/index.cjs +69 -26
- package/dist/bloom/index.d.cts +34 -2
- package/dist/bloom/index.d.ts +34 -2
- package/dist/bloom/index.js +69 -26
- package/dist/fuse/index.cjs +73 -77
- package/dist/fuse/index.d.cts +34 -2
- package/dist/fuse/index.d.ts +34 -2
- package/dist/fuse/index.js +63 -67
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{params-J8p3bKq5.cjs → params--8CNXYWu.cjs} +10 -0
- package/dist/{params-ChTRNxM9.js → params-BrWuBC2A.js} +5 -1
- package/dist/serialize-BIIKUHH6.cjs +515 -0
- package/dist/serialize-BnwtzcMw.js +414 -0
- package/dist/serialize-DRKh6QOr.d.cts +15 -0
- package/dist/serialize-DRKh6QOr.d.ts +15 -0
- package/package.json +5 -28
- package/dist/bytes-DCuYtUVS.d.cts +0 -4
- package/dist/bytes-DCuYtUVS.d.ts +0 -4
- package/dist/serialize-DPdiySxq.cjs +0 -355
- package/dist/serialize-DkkBKDT0.js +0 -308
package/dist/blocked/index.js
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
1
|
+
import { _ as reduce, a as bytesEqual, c as toJSONEnvelope, i as assertMinBodyLength, l as writeFrame, m as hash32x2Into, n as UnknownHashVariantError, o as fromJSONEnvelope, r as assertBodyLength, s as readHeader, t as SerializationError } from "../serialize-BnwtzcMw.js";
|
|
2
|
+
import { i as assertProbability, n as assertPositiveFinite, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-BrWuBC2A.js";
|
|
3
3
|
//#region src/blocked/blocked.ts
|
|
4
4
|
const TYPE = 2;
|
|
5
5
|
const SALT = Uint32Array.of(1203114875, 1150766481, 2284105051, 2729912477, 1884591559, 770785867, 2667333959, 1550580529);
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const scratch2 = /* @__PURE__ */ new Uint32Array(2);
|
|
7
|
+
const BLOCK_BITS = 256;
|
|
8
|
+
const LANE_BITS = 32;
|
|
9
|
+
const LANES = 8;
|
|
10
|
+
/**
|
|
11
|
+
* Modeled false-positive rate of a split-block filter at `bitsPerKey`. A block
|
|
12
|
+
* holding `j` keys has FPR `(1 - (1 - 1/32)^j)^8` (8 lanes of 32 bits, one probe
|
|
13
|
+
* each); the filter's rate averages that over the Poisson block load
|
|
14
|
+
* `lambda = 256 / bitsPerKey`. This clustering average is why the blocked curve
|
|
15
|
+
* is not linear in `log10(1/epsilon)`.
|
|
16
|
+
*/
|
|
17
|
+
function blockedFprAt(bitsPerKey) {
|
|
18
|
+
const lambda = BLOCK_BITS / bitsPerKey;
|
|
19
|
+
let fpr = 0;
|
|
20
|
+
let p = Math.exp(-lambda);
|
|
21
|
+
for (let j = 0;; j++) {
|
|
22
|
+
if (j > 0) p *= lambda / j;
|
|
23
|
+
fpr += p * (1 - (1 - 1 / LANE_BITS) ** j) ** LANES;
|
|
24
|
+
if (j > lambda && p < 1e-15) break;
|
|
25
|
+
}
|
|
26
|
+
return fpr;
|
|
27
|
+
}
|
|
28
|
+
const MAX_BITS_PER_KEY = 128;
|
|
29
|
+
/**
|
|
30
|
+
* Minimal integer bits-per-key whose modeled split-block FPR is at or below
|
|
31
|
+
* `epsilon`. Throws {@link ParamError} when even the densest supported filter
|
|
32
|
+
* cannot reach the target, so callers get a typed rejection instead of a
|
|
33
|
+
* silently under-provisioned filter.
|
|
34
|
+
*/
|
|
35
|
+
function blockedBitsPerKey(epsilon) {
|
|
36
|
+
for (let bpk = 1; bpk <= MAX_BITS_PER_KEY; bpk++) if (blockedFprAt(bpk) <= epsilon) return bpk;
|
|
37
|
+
throw new ParamError(`epsilon ${String(epsilon)} is below the blocked-filter floor; use a classic or fuse filter`);
|
|
38
|
+
}
|
|
12
39
|
/** Thrown when an operation requires two filters built with identical parameters. */
|
|
13
40
|
var BlockedBloomParamMismatchError = class extends Error {
|
|
14
41
|
/** Discriminates this error from other `Error`s. */
|
|
@@ -16,7 +43,7 @@ var BlockedBloomParamMismatchError = class extends Error {
|
|
|
16
43
|
};
|
|
17
44
|
/**
|
|
18
45
|
* A blocked (split-block) Bloom filter: confines every lookup to a single cache
|
|
19
|
-
* line, trading ~
|
|
46
|
+
* line, trading ~15% more space for higher lookup throughput and a lower FPR.
|
|
20
47
|
*
|
|
21
48
|
* @example
|
|
22
49
|
* ```ts
|
|
@@ -32,11 +59,6 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
32
59
|
#n;
|
|
33
60
|
#words = /* @__PURE__ */ new Uint32Array(8);
|
|
34
61
|
#bits = /* @__PURE__ */ new Uint32Array(8);
|
|
35
|
-
static #ANCHORS = [
|
|
36
|
-
[2, 10.5],
|
|
37
|
-
[3, 16.9],
|
|
38
|
-
[4, 26.4]
|
|
39
|
-
];
|
|
40
62
|
/**
|
|
41
63
|
* Creates a filter sized for `n` expected keys at a target false-positive rate.
|
|
42
64
|
*
|
|
@@ -47,35 +69,62 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
47
69
|
static create(n, epsilon) {
|
|
48
70
|
assertPositiveInt(n, "n");
|
|
49
71
|
assertProbability(epsilon, "epsilon");
|
|
50
|
-
const t = Math.log10(1 / epsilon);
|
|
51
|
-
const a = BlockedBloomFilter.#ANCHORS;
|
|
52
|
-
let seg = a.findIndex((p) => t <= p[0]);
|
|
53
|
-
if (seg < 1) seg = seg === -1 ? a.length - 1 : 1;
|
|
54
|
-
const [t0, b0] = a[seg - 1] ?? [0, 0];
|
|
55
|
-
const [t1, b1] = a[seg] ?? [0, 0];
|
|
56
|
-
const bitsPerKey = b0 + (b1 - b0) / (t1 - t0) * (t - t0);
|
|
57
72
|
return new BlockedBloomFilter({
|
|
58
|
-
bitsPerKey:
|
|
73
|
+
bitsPerKey: blockedBitsPerKey(epsilon),
|
|
59
74
|
capacity: n
|
|
60
75
|
});
|
|
61
76
|
}
|
|
62
77
|
/**
|
|
78
|
+
* Builds a filter from `keys`, sized for their count at the target
|
|
79
|
+
* false-positive rate. The ergonomic entry point when the key set is already
|
|
80
|
+
* in hand; use {@link BlockedBloomFilter.create} to size for a count known
|
|
81
|
+
* ahead.
|
|
82
|
+
*
|
|
83
|
+
* @param keys - The keys to insert.
|
|
84
|
+
* @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
|
|
85
|
+
* @returns A new filter containing every key.
|
|
86
|
+
*/
|
|
87
|
+
static from(keys, epsilon) {
|
|
88
|
+
const arr = [...keys];
|
|
89
|
+
const f = BlockedBloomFilter.create(Math.max(1, arr.length), epsilon);
|
|
90
|
+
for (const k of arr) f.add(k);
|
|
91
|
+
return f;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
63
94
|
* Constructs a filter from low-level {@link BlockedBloomParams}. Prefer
|
|
64
95
|
* {@link BlockedBloomFilter.create} unless restoring a specific configuration.
|
|
65
96
|
*/
|
|
66
97
|
constructor({ bitsPerKey, capacity, seed = 0 }) {
|
|
67
98
|
assertPositiveFinite(bitsPerKey, "bitsPerKey");
|
|
68
99
|
assertPositiveInt(capacity, "capacity");
|
|
100
|
+
assertUint32(capacity, "capacity");
|
|
69
101
|
assertUint32(seed, "seed");
|
|
70
102
|
this.#numBlocks = Math.max(1, Math.ceil(bitsPerKey * capacity / 256));
|
|
71
103
|
this.#lanes = new Uint32Array(this.#numBlocks * 8);
|
|
72
104
|
this.#seed = seed;
|
|
73
105
|
this.#n = capacity;
|
|
74
106
|
}
|
|
107
|
+
static #fromNumBlocks(numBlocks, seed, n) {
|
|
108
|
+
const f = new BlockedBloomFilter({
|
|
109
|
+
bitsPerKey: 256,
|
|
110
|
+
capacity: numBlocks,
|
|
111
|
+
seed
|
|
112
|
+
});
|
|
113
|
+
f.#n = n;
|
|
114
|
+
return f;
|
|
115
|
+
}
|
|
75
116
|
/** Actual bits allocated per key (`total bits / capacity`). */
|
|
76
117
|
get bitsPerKey() {
|
|
77
118
|
return this.#numBlocks * 256 / this.#n;
|
|
78
119
|
}
|
|
120
|
+
/** Number of 256-bit blocks; one of the two fields `union` requires to match. */
|
|
121
|
+
get numBlocks() {
|
|
122
|
+
return this.#numBlocks;
|
|
123
|
+
}
|
|
124
|
+
/** Hash seed; the other field `union` requires to match. */
|
|
125
|
+
get seed() {
|
|
126
|
+
return this.#seed;
|
|
127
|
+
}
|
|
79
128
|
/** Number of bits currently set across all lanes. */
|
|
80
129
|
get length() {
|
|
81
130
|
let bits = 0;
|
|
@@ -103,20 +152,17 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
103
152
|
* @returns The reconstructed filter.
|
|
104
153
|
*/
|
|
105
154
|
static fromBytes(bytes) {
|
|
106
|
-
const { type, body } = readHeader(bytes);
|
|
155
|
+
const { type, flags, body } = readHeader(bytes);
|
|
107
156
|
if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
|
|
157
|
+
if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
|
|
108
158
|
assertMinBodyLength(body.length, 12, "blocked");
|
|
109
159
|
const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
110
160
|
const numBlocks = dv.getUint32(0, true);
|
|
111
161
|
const seed = dv.getUint32(4, true);
|
|
112
162
|
const n = dv.getUint32(8, true);
|
|
163
|
+
if (numBlocks === 0 || n === 0) throw new SerializationError(`blocked frame declares numBlocks=${String(numBlocks)}, n=${String(n)}; both must be positive`);
|
|
113
164
|
assertBodyLength(body.length, 12 + numBlocks * 32, "blocked");
|
|
114
|
-
const f =
|
|
115
|
-
bitsPerKey: 256,
|
|
116
|
-
capacity: numBlocks,
|
|
117
|
-
seed
|
|
118
|
-
});
|
|
119
|
-
f.#n = n;
|
|
165
|
+
const f = BlockedBloomFilter.#fromNumBlocks(numBlocks, seed, n);
|
|
120
166
|
new Uint8Array(f.#lanes.buffer).set(body.subarray(12));
|
|
121
167
|
return f;
|
|
122
168
|
}
|
|
@@ -127,17 +173,44 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
127
173
|
*/
|
|
128
174
|
toBytes() {
|
|
129
175
|
const lanes = new Uint8Array(this.#lanes.buffer, this.#lanes.byteOffset, this.#lanes.byteLength);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
dv.setUint32(0, this.#numBlocks, true);
|
|
133
|
-
dv.setUint32(4, this.#seed, true);
|
|
134
|
-
dv.setUint32(8, this.#n, true);
|
|
135
|
-
body.set(lanes, 12);
|
|
136
|
-
return writeHeader({
|
|
137
|
-
version: 2,
|
|
176
|
+
return writeFrame({
|
|
177
|
+
version: 3,
|
|
138
178
|
type: TYPE,
|
|
139
179
|
flags: 0
|
|
140
|
-
}, body)
|
|
180
|
+
}, 12 + lanes.length, (body, dv) => {
|
|
181
|
+
dv.setUint32(0, this.#numBlocks, true);
|
|
182
|
+
dv.setUint32(4, this.#seed, true);
|
|
183
|
+
dv.setUint32(8, this.#n, true);
|
|
184
|
+
body.set(lanes, 12);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Tests structural equality: `true` when `other` serializes to identical
|
|
189
|
+
* bytes, meaning identical parameters and set bits.
|
|
190
|
+
*
|
|
191
|
+
* @param other - The filter to compare against.
|
|
192
|
+
* @returns `true` if the two filters are byte-for-byte identical.
|
|
193
|
+
*/
|
|
194
|
+
equals(other) {
|
|
195
|
+
return bytesEqual(this.toBytes(), other.toBytes());
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
199
|
+
* {@link BlockedBloomFilter.toBytes}.
|
|
200
|
+
*
|
|
201
|
+
* @returns The envelope, readable by {@link BlockedBloomFilter.fromJSON}.
|
|
202
|
+
*/
|
|
203
|
+
toJSON() {
|
|
204
|
+
return toJSONEnvelope(this.toBytes());
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Restores a filter from its {@link BlockedBloomFilter.toJSON} envelope.
|
|
208
|
+
*
|
|
209
|
+
* @param value - The JSON envelope.
|
|
210
|
+
* @returns The reconstructed filter.
|
|
211
|
+
*/
|
|
212
|
+
static fromJSON(value) {
|
|
213
|
+
return BlockedBloomFilter.fromBytes(fromJSONEnvelope(value));
|
|
141
214
|
}
|
|
142
215
|
/**
|
|
143
216
|
* Returns a new filter containing the union of this filter and `other`.
|
|
@@ -148,11 +221,7 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
148
221
|
*/
|
|
149
222
|
union(other) {
|
|
150
223
|
if (this.#numBlocks !== other.#numBlocks || this.#seed !== other.#seed) throw new BlockedBloomParamMismatchError("cannot union blocked Bloom filters whose parameters do not match");
|
|
151
|
-
const r =
|
|
152
|
-
bitsPerKey: this.bitsPerKey,
|
|
153
|
-
capacity: this.#n,
|
|
154
|
-
seed: this.#seed
|
|
155
|
-
});
|
|
224
|
+
const r = BlockedBloomFilter.#fromNumBlocks(this.#numBlocks, this.#seed, this.#n);
|
|
156
225
|
for (let i = 0; i < this.#lanes.length; i++) r.#lanes[i] = (this.#lanes[i] ?? 0) | (other.#lanes[i] ?? 0);
|
|
157
226
|
return r;
|
|
158
227
|
}
|
|
@@ -184,9 +253,9 @@ var BlockedBloomFilter = class BlockedBloomFilter {
|
|
|
184
253
|
}
|
|
185
254
|
};
|
|
186
255
|
function fillBlock(key, numBlocks, seed, outWords, outBits) {
|
|
187
|
-
|
|
188
|
-
const block = reduce(
|
|
189
|
-
const x =
|
|
256
|
+
hash32x2Into(key, seed, scratch2);
|
|
257
|
+
const block = reduce(scratch2[0] ?? 0, numBlocks);
|
|
258
|
+
const x = scratch2[1] ?? 0;
|
|
190
259
|
const base = block * 8;
|
|
191
260
|
for (let i = 0; i < 8; i++) {
|
|
192
261
|
outWords[i] = base + i;
|
package/dist/bloom/index.cjs
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_serialize = require("../serialize-
|
|
3
|
-
const require_params = require("../params
|
|
2
|
+
const require_serialize = require("../serialize-BIIKUHH6.cjs");
|
|
3
|
+
const require_params = require("../params--8CNXYWu.cjs");
|
|
4
4
|
//#region src/core/bitset.ts
|
|
5
5
|
var BitSetRangeError = class extends RangeError {
|
|
6
6
|
name = "BitSetRangeError";
|
|
7
7
|
};
|
|
8
8
|
const MAX_BITS = 2 ** 32;
|
|
9
|
-
var BitSet = class
|
|
9
|
+
var BitSet = class {
|
|
10
10
|
#bits;
|
|
11
|
-
static fromBytes(bytes) {
|
|
12
|
-
const bs = new BitSet(bytes.length * 8);
|
|
13
|
-
bs.#bits.set(bytes);
|
|
14
|
-
return bs;
|
|
15
|
-
}
|
|
16
11
|
constructor(nbits) {
|
|
17
12
|
if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
|
|
18
13
|
this.#bits = new Uint8Array(Math.ceil(nbits / 8));
|
|
@@ -82,9 +77,23 @@ var BloomFilter = class BloomFilter {
|
|
|
82
77
|
*/
|
|
83
78
|
static create(n, epsilon) {
|
|
84
79
|
require_params.assertPositiveInt(n, "n");
|
|
80
|
+
require_params.assertUint32(n, "n");
|
|
85
81
|
require_params.assertProbability(epsilon, "epsilon");
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
return BloomFilter.#withN(optimal(n, epsilon), n);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Builds a filter from `keys`, sized for their count at the target
|
|
86
|
+
* false-positive rate. The ergonomic entry point when the key set is already
|
|
87
|
+
* in hand; use {@link BloomFilter.create} to size for a count known ahead.
|
|
88
|
+
*
|
|
89
|
+
* @param keys - The keys to insert.
|
|
90
|
+
* @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
|
|
91
|
+
* @returns A new filter containing every key.
|
|
92
|
+
*/
|
|
93
|
+
static from(keys, epsilon) {
|
|
94
|
+
const arr = [...keys];
|
|
95
|
+
const f = BloomFilter.create(Math.max(1, arr.length), epsilon);
|
|
96
|
+
for (const k of arr) f.add(k);
|
|
88
97
|
return f;
|
|
89
98
|
}
|
|
90
99
|
/**
|
|
@@ -94,8 +103,9 @@ var BloomFilter = class BloomFilter {
|
|
|
94
103
|
* @returns The reconstructed filter.
|
|
95
104
|
*/
|
|
96
105
|
static fromBytes(bytes) {
|
|
97
|
-
const { type, body } = require_serialize.readHeader(bytes);
|
|
106
|
+
const { type, flags, body } = require_serialize.readHeader(bytes);
|
|
98
107
|
if (type !== TYPE) throw new require_serialize.SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
|
|
108
|
+
if ((flags & 15) !== 0) throw new require_serialize.UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
|
|
99
109
|
require_serialize.assertMinBodyLength(body.length, 14, "bloom");
|
|
100
110
|
const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
101
111
|
const m = dv.getUint32(0, true);
|
|
@@ -103,12 +113,11 @@ var BloomFilter = class BloomFilter {
|
|
|
103
113
|
const seed = dv.getUint32(6, true);
|
|
104
114
|
const n = dv.getUint32(10, true);
|
|
105
115
|
require_serialize.assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
|
|
106
|
-
const f =
|
|
116
|
+
const f = BloomFilter.#withN({
|
|
107
117
|
m,
|
|
108
118
|
k,
|
|
109
119
|
seed
|
|
110
|
-
});
|
|
111
|
-
f.#n = n;
|
|
120
|
+
}, n);
|
|
112
121
|
f.#bits.bytes.set(body.subarray(14));
|
|
113
122
|
return f;
|
|
114
123
|
}
|
|
@@ -118,7 +127,9 @@ var BloomFilter = class BloomFilter {
|
|
|
118
127
|
*/
|
|
119
128
|
constructor({ m, k, seed = 0 }) {
|
|
120
129
|
require_params.assertPositiveInt(m, "m");
|
|
130
|
+
require_params.assertUint32(m, "m");
|
|
121
131
|
require_params.assertPositiveInt(k, "k");
|
|
132
|
+
require_params.assertUint16(k, "k");
|
|
122
133
|
require_params.assertUint32(seed, "seed");
|
|
123
134
|
this.#bits = new BitSet(m);
|
|
124
135
|
this.#m = m;
|
|
@@ -127,6 +138,11 @@ var BloomFilter = class BloomFilter {
|
|
|
127
138
|
this.#scratch = new Uint32Array(k);
|
|
128
139
|
this.#n = Math.round(m * Math.LN2 / k);
|
|
129
140
|
}
|
|
141
|
+
static #withN(params, n) {
|
|
142
|
+
const f = new BloomFilter(params);
|
|
143
|
+
f.#n = n;
|
|
144
|
+
return f;
|
|
145
|
+
}
|
|
130
146
|
/** Number of bits in the filter. */
|
|
131
147
|
get m() {
|
|
132
148
|
return this.#m;
|
|
@@ -164,18 +180,45 @@ var BloomFilter = class BloomFilter {
|
|
|
164
180
|
*/
|
|
165
181
|
toBytes() {
|
|
166
182
|
const payload = this.#bits.bytes;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
dv.setUint32(0, this.#m, true);
|
|
170
|
-
dv.setUint16(4, this.#k, true);
|
|
171
|
-
dv.setUint32(6, this.#seed, true);
|
|
172
|
-
dv.setUint32(10, this.#n, true);
|
|
173
|
-
body.set(payload, 14);
|
|
174
|
-
return require_serialize.writeHeader({
|
|
175
|
-
version: 2,
|
|
183
|
+
return require_serialize.writeFrame({
|
|
184
|
+
version: 3,
|
|
176
185
|
type: TYPE,
|
|
177
186
|
flags: 0
|
|
178
|
-
}, body)
|
|
187
|
+
}, 14 + payload.length, (body, dv) => {
|
|
188
|
+
dv.setUint32(0, this.#m, true);
|
|
189
|
+
dv.setUint16(4, this.#k, true);
|
|
190
|
+
dv.setUint32(6, this.#seed, true);
|
|
191
|
+
dv.setUint32(10, this.#n, true);
|
|
192
|
+
body.set(payload, 14);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Tests structural equality: `true` when `other` serializes to identical
|
|
197
|
+
* bytes, meaning identical parameters and set bits.
|
|
198
|
+
*
|
|
199
|
+
* @param other - The filter to compare against.
|
|
200
|
+
* @returns `true` if the two filters are byte-for-byte identical.
|
|
201
|
+
*/
|
|
202
|
+
equals(other) {
|
|
203
|
+
return require_serialize.bytesEqual(this.toBytes(), other.toBytes());
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
207
|
+
* {@link BloomFilter.toBytes}.
|
|
208
|
+
*
|
|
209
|
+
* @returns The envelope, readable by {@link BloomFilter.fromJSON}.
|
|
210
|
+
*/
|
|
211
|
+
toJSON() {
|
|
212
|
+
return require_serialize.toJSONEnvelope(this.toBytes());
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Restores a filter from its {@link BloomFilter.toJSON} envelope.
|
|
216
|
+
*
|
|
217
|
+
* @param value - The JSON envelope.
|
|
218
|
+
* @returns The reconstructed filter.
|
|
219
|
+
*/
|
|
220
|
+
static fromJSON(value) {
|
|
221
|
+
return BloomFilter.fromBytes(require_serialize.fromJSONEnvelope(value));
|
|
179
222
|
}
|
|
180
223
|
/**
|
|
181
224
|
* Returns a new filter containing the union of this filter and `other`.
|
|
@@ -190,11 +233,11 @@ var BloomFilter = class BloomFilter {
|
|
|
190
233
|
const b = other.#bits.bytes;
|
|
191
234
|
const merged = new Uint8Array(a.length);
|
|
192
235
|
for (let i = 0; i < a.length; i++) merged[i] = (a[i] ?? 0) | (b[i] ?? 0);
|
|
193
|
-
const r =
|
|
236
|
+
const r = BloomFilter.#withN({
|
|
194
237
|
m: this.#m,
|
|
195
238
|
k: this.#k,
|
|
196
239
|
seed: this.#seed
|
|
197
|
-
});
|
|
240
|
+
}, this.#n);
|
|
198
241
|
r.#bits.bytes.set(merged);
|
|
199
242
|
return r;
|
|
200
243
|
}
|
package/dist/bloom/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as BytesLike, t as FilterJSON } from "../serialize-DRKh6QOr.cjs";
|
|
2
2
|
import { t as ParamError } from "../params-DnqJBqLS.cjs";
|
|
3
3
|
//#region src/bloom/bloom.d.ts
|
|
4
4
|
/** Thrown when an operation requires two filters built with identical parameters. */
|
|
@@ -37,6 +37,16 @@ declare class BloomFilter {
|
|
|
37
37
|
* @returns A new, empty filter.
|
|
38
38
|
*/
|
|
39
39
|
static create(n: number, epsilon: number): BloomFilter;
|
|
40
|
+
/**
|
|
41
|
+
* Builds a filter from `keys`, sized for their count at the target
|
|
42
|
+
* false-positive rate. The ergonomic entry point when the key set is already
|
|
43
|
+
* in hand; use {@link BloomFilter.create} to size for a count known ahead.
|
|
44
|
+
*
|
|
45
|
+
* @param keys - The keys to insert.
|
|
46
|
+
* @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
|
|
47
|
+
* @returns A new filter containing every key.
|
|
48
|
+
*/
|
|
49
|
+
static from(keys: Iterable<BytesLike>, epsilon: number): BloomFilter;
|
|
40
50
|
/**
|
|
41
51
|
* Restores a filter from its {@link BloomFilter.toBytes} serialization.
|
|
42
52
|
*
|
|
@@ -73,6 +83,28 @@ declare class BloomFilter {
|
|
|
73
83
|
* @returns The serialized filter, readable by {@link BloomFilter.fromBytes}.
|
|
74
84
|
*/
|
|
75
85
|
toBytes(): Uint8Array;
|
|
86
|
+
/**
|
|
87
|
+
* Tests structural equality: `true` when `other` serializes to identical
|
|
88
|
+
* bytes, meaning identical parameters and set bits.
|
|
89
|
+
*
|
|
90
|
+
* @param other - The filter to compare against.
|
|
91
|
+
* @returns `true` if the two filters are byte-for-byte identical.
|
|
92
|
+
*/
|
|
93
|
+
equals(other: BloomFilter): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
96
|
+
* {@link BloomFilter.toBytes}.
|
|
97
|
+
*
|
|
98
|
+
* @returns The envelope, readable by {@link BloomFilter.fromJSON}.
|
|
99
|
+
*/
|
|
100
|
+
toJSON(): FilterJSON;
|
|
101
|
+
/**
|
|
102
|
+
* Restores a filter from its {@link BloomFilter.toJSON} envelope.
|
|
103
|
+
*
|
|
104
|
+
* @param value - The JSON envelope.
|
|
105
|
+
* @returns The reconstructed filter.
|
|
106
|
+
*/
|
|
107
|
+
static fromJSON(value: unknown): BloomFilter;
|
|
76
108
|
/**
|
|
77
109
|
* Returns a new filter containing the union of this filter and `other`.
|
|
78
110
|
*
|
|
@@ -96,4 +128,4 @@ declare class BloomFilter {
|
|
|
96
128
|
has(key: BytesLike): boolean;
|
|
97
129
|
}
|
|
98
130
|
//#endregion
|
|
99
|
-
export { BloomFilter, BloomParamMismatchError, type BloomParams, ParamError };
|
|
131
|
+
export { BloomFilter, BloomParamMismatchError, type BloomParams, type FilterJSON, ParamError };
|
package/dist/bloom/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as BytesLike, t as FilterJSON } from "../serialize-DRKh6QOr.js";
|
|
2
2
|
import { t as ParamError } from "../params-DnqJBqLS.js";
|
|
3
3
|
//#region src/bloom/bloom.d.ts
|
|
4
4
|
/** Thrown when an operation requires two filters built with identical parameters. */
|
|
@@ -37,6 +37,16 @@ declare class BloomFilter {
|
|
|
37
37
|
* @returns A new, empty filter.
|
|
38
38
|
*/
|
|
39
39
|
static create(n: number, epsilon: number): BloomFilter;
|
|
40
|
+
/**
|
|
41
|
+
* Builds a filter from `keys`, sized for their count at the target
|
|
42
|
+
* false-positive rate. The ergonomic entry point when the key set is already
|
|
43
|
+
* in hand; use {@link BloomFilter.create} to size for a count known ahead.
|
|
44
|
+
*
|
|
45
|
+
* @param keys - The keys to insert.
|
|
46
|
+
* @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
|
|
47
|
+
* @returns A new filter containing every key.
|
|
48
|
+
*/
|
|
49
|
+
static from(keys: Iterable<BytesLike>, epsilon: number): BloomFilter;
|
|
40
50
|
/**
|
|
41
51
|
* Restores a filter from its {@link BloomFilter.toBytes} serialization.
|
|
42
52
|
*
|
|
@@ -73,6 +83,28 @@ declare class BloomFilter {
|
|
|
73
83
|
* @returns The serialized filter, readable by {@link BloomFilter.fromBytes}.
|
|
74
84
|
*/
|
|
75
85
|
toBytes(): Uint8Array;
|
|
86
|
+
/**
|
|
87
|
+
* Tests structural equality: `true` when `other` serializes to identical
|
|
88
|
+
* bytes, meaning identical parameters and set bits.
|
|
89
|
+
*
|
|
90
|
+
* @param other - The filter to compare against.
|
|
91
|
+
* @returns `true` if the two filters are byte-for-byte identical.
|
|
92
|
+
*/
|
|
93
|
+
equals(other: BloomFilter): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
96
|
+
* {@link BloomFilter.toBytes}.
|
|
97
|
+
*
|
|
98
|
+
* @returns The envelope, readable by {@link BloomFilter.fromJSON}.
|
|
99
|
+
*/
|
|
100
|
+
toJSON(): FilterJSON;
|
|
101
|
+
/**
|
|
102
|
+
* Restores a filter from its {@link BloomFilter.toJSON} envelope.
|
|
103
|
+
*
|
|
104
|
+
* @param value - The JSON envelope.
|
|
105
|
+
* @returns The reconstructed filter.
|
|
106
|
+
*/
|
|
107
|
+
static fromJSON(value: unknown): BloomFilter;
|
|
76
108
|
/**
|
|
77
109
|
* Returns a new filter containing the union of this filter and `other`.
|
|
78
110
|
*
|
|
@@ -96,4 +128,4 @@ declare class BloomFilter {
|
|
|
96
128
|
has(key: BytesLike): boolean;
|
|
97
129
|
}
|
|
98
130
|
//#endregion
|
|
99
|
-
export { BloomFilter, BloomParamMismatchError, type BloomParams, ParamError };
|
|
131
|
+
export { BloomFilter, BloomParamMismatchError, type BloomParams, type FilterJSON, ParamError };
|
package/dist/bloom/index.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as
|
|
1
|
+
import { a as bytesEqual, c as toJSONEnvelope, g as probeInto, i as assertMinBodyLength, l as writeFrame, n as UnknownHashVariantError, o as fromJSONEnvelope, r as assertBodyLength, s as readHeader, t as SerializationError } from "../serialize-BnwtzcMw.js";
|
|
2
|
+
import { a as assertUint16, i as assertProbability, o as assertUint32, r as assertPositiveInt, t as ParamError } from "../params-BrWuBC2A.js";
|
|
3
3
|
//#region src/core/bitset.ts
|
|
4
4
|
var BitSetRangeError = class extends RangeError {
|
|
5
5
|
name = "BitSetRangeError";
|
|
6
6
|
};
|
|
7
7
|
const MAX_BITS = 2 ** 32;
|
|
8
|
-
var BitSet = class
|
|
8
|
+
var BitSet = class {
|
|
9
9
|
#bits;
|
|
10
|
-
static fromBytes(bytes) {
|
|
11
|
-
const bs = new BitSet(bytes.length * 8);
|
|
12
|
-
bs.#bits.set(bytes);
|
|
13
|
-
return bs;
|
|
14
|
-
}
|
|
15
10
|
constructor(nbits) {
|
|
16
11
|
if (nbits > MAX_BITS) throw new BitSetRangeError(`BitSet capacity ${String(nbits)} exceeds the 2^32-bit limit`);
|
|
17
12
|
this.#bits = new Uint8Array(Math.ceil(nbits / 8));
|
|
@@ -81,9 +76,23 @@ var BloomFilter = class BloomFilter {
|
|
|
81
76
|
*/
|
|
82
77
|
static create(n, epsilon) {
|
|
83
78
|
assertPositiveInt(n, "n");
|
|
79
|
+
assertUint32(n, "n");
|
|
84
80
|
assertProbability(epsilon, "epsilon");
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
return BloomFilter.#withN(optimal(n, epsilon), n);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds a filter from `keys`, sized for their count at the target
|
|
85
|
+
* false-positive rate. The ergonomic entry point when the key set is already
|
|
86
|
+
* in hand; use {@link BloomFilter.create} to size for a count known ahead.
|
|
87
|
+
*
|
|
88
|
+
* @param keys - The keys to insert.
|
|
89
|
+
* @param epsilon - Target false-positive rate, e.g. `0.01` for 1%.
|
|
90
|
+
* @returns A new filter containing every key.
|
|
91
|
+
*/
|
|
92
|
+
static from(keys, epsilon) {
|
|
93
|
+
const arr = [...keys];
|
|
94
|
+
const f = BloomFilter.create(Math.max(1, arr.length), epsilon);
|
|
95
|
+
for (const k of arr) f.add(k);
|
|
87
96
|
return f;
|
|
88
97
|
}
|
|
89
98
|
/**
|
|
@@ -93,8 +102,9 @@ var BloomFilter = class BloomFilter {
|
|
|
93
102
|
* @returns The reconstructed filter.
|
|
94
103
|
*/
|
|
95
104
|
static fromBytes(bytes) {
|
|
96
|
-
const { type, body } = readHeader(bytes);
|
|
105
|
+
const { type, flags, body } = readHeader(bytes);
|
|
97
106
|
if (type !== TYPE) throw new SerializationError(`expected AMQF type ${String(TYPE)}, got ${String(type)}`);
|
|
107
|
+
if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
|
|
98
108
|
assertMinBodyLength(body.length, 14, "bloom");
|
|
99
109
|
const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
100
110
|
const m = dv.getUint32(0, true);
|
|
@@ -102,12 +112,11 @@ var BloomFilter = class BloomFilter {
|
|
|
102
112
|
const seed = dv.getUint32(6, true);
|
|
103
113
|
const n = dv.getUint32(10, true);
|
|
104
114
|
assertBodyLength(body.length, 14 + Math.ceil(m / 8), "bloom");
|
|
105
|
-
const f =
|
|
115
|
+
const f = BloomFilter.#withN({
|
|
106
116
|
m,
|
|
107
117
|
k,
|
|
108
118
|
seed
|
|
109
|
-
});
|
|
110
|
-
f.#n = n;
|
|
119
|
+
}, n);
|
|
111
120
|
f.#bits.bytes.set(body.subarray(14));
|
|
112
121
|
return f;
|
|
113
122
|
}
|
|
@@ -117,7 +126,9 @@ var BloomFilter = class BloomFilter {
|
|
|
117
126
|
*/
|
|
118
127
|
constructor({ m, k, seed = 0 }) {
|
|
119
128
|
assertPositiveInt(m, "m");
|
|
129
|
+
assertUint32(m, "m");
|
|
120
130
|
assertPositiveInt(k, "k");
|
|
131
|
+
assertUint16(k, "k");
|
|
121
132
|
assertUint32(seed, "seed");
|
|
122
133
|
this.#bits = new BitSet(m);
|
|
123
134
|
this.#m = m;
|
|
@@ -126,6 +137,11 @@ var BloomFilter = class BloomFilter {
|
|
|
126
137
|
this.#scratch = new Uint32Array(k);
|
|
127
138
|
this.#n = Math.round(m * Math.LN2 / k);
|
|
128
139
|
}
|
|
140
|
+
static #withN(params, n) {
|
|
141
|
+
const f = new BloomFilter(params);
|
|
142
|
+
f.#n = n;
|
|
143
|
+
return f;
|
|
144
|
+
}
|
|
129
145
|
/** Number of bits in the filter. */
|
|
130
146
|
get m() {
|
|
131
147
|
return this.#m;
|
|
@@ -163,18 +179,45 @@ var BloomFilter = class BloomFilter {
|
|
|
163
179
|
*/
|
|
164
180
|
toBytes() {
|
|
165
181
|
const payload = this.#bits.bytes;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
dv.setUint32(0, this.#m, true);
|
|
169
|
-
dv.setUint16(4, this.#k, true);
|
|
170
|
-
dv.setUint32(6, this.#seed, true);
|
|
171
|
-
dv.setUint32(10, this.#n, true);
|
|
172
|
-
body.set(payload, 14);
|
|
173
|
-
return writeHeader({
|
|
174
|
-
version: 2,
|
|
182
|
+
return writeFrame({
|
|
183
|
+
version: 3,
|
|
175
184
|
type: TYPE,
|
|
176
185
|
flags: 0
|
|
177
|
-
}, body)
|
|
186
|
+
}, 14 + payload.length, (body, dv) => {
|
|
187
|
+
dv.setUint32(0, this.#m, true);
|
|
188
|
+
dv.setUint16(4, this.#k, true);
|
|
189
|
+
dv.setUint32(6, this.#seed, true);
|
|
190
|
+
dv.setUint32(10, this.#n, true);
|
|
191
|
+
body.set(payload, 14);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Tests structural equality: `true` when `other` serializes to identical
|
|
196
|
+
* bytes, meaning identical parameters and set bits.
|
|
197
|
+
*
|
|
198
|
+
* @param other - The filter to compare against.
|
|
199
|
+
* @returns `true` if the two filters are byte-for-byte identical.
|
|
200
|
+
*/
|
|
201
|
+
equals(other) {
|
|
202
|
+
return bytesEqual(this.toBytes(), other.toBytes());
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
206
|
+
* {@link BloomFilter.toBytes}.
|
|
207
|
+
*
|
|
208
|
+
* @returns The envelope, readable by {@link BloomFilter.fromJSON}.
|
|
209
|
+
*/
|
|
210
|
+
toJSON() {
|
|
211
|
+
return toJSONEnvelope(this.toBytes());
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Restores a filter from its {@link BloomFilter.toJSON} envelope.
|
|
215
|
+
*
|
|
216
|
+
* @param value - The JSON envelope.
|
|
217
|
+
* @returns The reconstructed filter.
|
|
218
|
+
*/
|
|
219
|
+
static fromJSON(value) {
|
|
220
|
+
return BloomFilter.fromBytes(fromJSONEnvelope(value));
|
|
178
221
|
}
|
|
179
222
|
/**
|
|
180
223
|
* Returns a new filter containing the union of this filter and `other`.
|
|
@@ -189,11 +232,11 @@ var BloomFilter = class BloomFilter {
|
|
|
189
232
|
const b = other.#bits.bytes;
|
|
190
233
|
const merged = new Uint8Array(a.length);
|
|
191
234
|
for (let i = 0; i < a.length; i++) merged[i] = (a[i] ?? 0) | (b[i] ?? 0);
|
|
192
|
-
const r =
|
|
235
|
+
const r = BloomFilter.#withN({
|
|
193
236
|
m: this.#m,
|
|
194
237
|
k: this.#k,
|
|
195
238
|
seed: this.#seed
|
|
196
|
-
});
|
|
239
|
+
}, this.#n);
|
|
197
240
|
r.#bits.bytes.set(merged);
|
|
198
241
|
return r;
|
|
199
242
|
}
|