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
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { _ as hash128KeyInto, a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, i as TruncatedError, 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
|
+
import { o as assertUint32, t as ParamError } from "../params-CeajSrwv.js";
|
|
3
|
+
import { n as hllSizing } from "../sizing-BtUrtvF_.js";
|
|
4
|
+
//#region src/hll/estimate.ts
|
|
5
|
+
const ALPHA_INF = .5 / Math.LN2;
|
|
6
|
+
function sigma(x) {
|
|
7
|
+
if (x === 1) return Infinity;
|
|
8
|
+
let y = 1;
|
|
9
|
+
let z = x;
|
|
10
|
+
let previous;
|
|
11
|
+
do {
|
|
12
|
+
x *= x;
|
|
13
|
+
previous = z;
|
|
14
|
+
z += x * y;
|
|
15
|
+
y += y;
|
|
16
|
+
} while (z !== previous);
|
|
17
|
+
return z;
|
|
18
|
+
}
|
|
19
|
+
function tau(x) {
|
|
20
|
+
if (x === 0 || x === 1) return 0;
|
|
21
|
+
let y = 1;
|
|
22
|
+
let z = 1 - x;
|
|
23
|
+
let previous;
|
|
24
|
+
do {
|
|
25
|
+
x = Math.sqrt(x);
|
|
26
|
+
previous = z;
|
|
27
|
+
y *= .5;
|
|
28
|
+
const gap = 1 - x;
|
|
29
|
+
z -= gap * gap * y;
|
|
30
|
+
} while (z !== previous);
|
|
31
|
+
return z / 3;
|
|
32
|
+
}
|
|
33
|
+
function estimate(hist, p) {
|
|
34
|
+
const m = 2 ** p;
|
|
35
|
+
const q = 64 - p;
|
|
36
|
+
let z = m * tau((m - (hist[q + 1] ?? 0)) / m);
|
|
37
|
+
for (let k = q; k >= 1; k--) z = .5 * (z + (hist[k] ?? 0));
|
|
38
|
+
z += m * sigma((hist[0] ?? 0) / m);
|
|
39
|
+
return ALPHA_INF * m * m / z;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/hll/fold.ts
|
|
43
|
+
function foldRho(index, rho, d) {
|
|
44
|
+
const mid = index & (1 << d) - 1;
|
|
45
|
+
if (mid === 0) return d + rho;
|
|
46
|
+
return Math.clz32(mid) - 32 + d + 1;
|
|
47
|
+
}
|
|
48
|
+
function foldDense(src, srcP, dst, dstP) {
|
|
49
|
+
const d = srcP - dstP;
|
|
50
|
+
for (let i = 0; i < 2 ** srcP; i++) {
|
|
51
|
+
const rho = src.get(i);
|
|
52
|
+
if (rho === 0) continue;
|
|
53
|
+
dst.raise(i >>> d, foldRho(i, rho, d));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/hll/registers.ts
|
|
58
|
+
const WIDTH = 6;
|
|
59
|
+
const MASK = 63;
|
|
60
|
+
var Registers = class {
|
|
61
|
+
#bytes;
|
|
62
|
+
constructor(p) {
|
|
63
|
+
this.#bytes = new Uint8Array(WIDTH * 2 ** p / 8);
|
|
64
|
+
}
|
|
65
|
+
get bytes() {
|
|
66
|
+
return this.#bytes;
|
|
67
|
+
}
|
|
68
|
+
max() {
|
|
69
|
+
const bytes = this.#bytes;
|
|
70
|
+
let max = 0;
|
|
71
|
+
for (let at = 0; at < bytes.length; at += 3) {
|
|
72
|
+
const word = (bytes[at] ?? 0) | (bytes[at + 1] ?? 0) << 8 | (bytes[at + 2] ?? 0) << 16;
|
|
73
|
+
const a = word & MASK;
|
|
74
|
+
const b = word >>> 6 & MASK;
|
|
75
|
+
const c = word >>> 12 & MASK;
|
|
76
|
+
const d = word >>> 18 & MASK;
|
|
77
|
+
if (a > max) max = a;
|
|
78
|
+
if (b > max) max = b;
|
|
79
|
+
if (c > max) max = c;
|
|
80
|
+
if (d > max) max = d;
|
|
81
|
+
}
|
|
82
|
+
return max;
|
|
83
|
+
}
|
|
84
|
+
get(i) {
|
|
85
|
+
const bit = i * WIDTH;
|
|
86
|
+
const at = bit >>> 3;
|
|
87
|
+
return ((this.#bytes[at] ?? 0) | (this.#bytes[at + 1] ?? 0) << 8) >>> (bit & 7) & MASK;
|
|
88
|
+
}
|
|
89
|
+
raise(i, v) {
|
|
90
|
+
if (v > this.get(i)) this.set(i, v);
|
|
91
|
+
}
|
|
92
|
+
set(i, v) {
|
|
93
|
+
const bit = i * WIDTH;
|
|
94
|
+
const at = bit >>> 3;
|
|
95
|
+
const off = bit & 7;
|
|
96
|
+
const word = ((this.#bytes[at] ?? 0) | (this.#bytes[at + 1] ?? 0) << 8) & ~(MASK << off) | (v & MASK) << off;
|
|
97
|
+
this.#bytes[at] = word & 255;
|
|
98
|
+
if (at + 1 < this.#bytes.length) this.#bytes[at + 1] = word >>> 8 & 255;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const RHO_BITS = 6;
|
|
102
|
+
const RHO_MASK = (1 << RHO_BITS) - 1;
|
|
103
|
+
function encodeSparse(index, rho) {
|
|
104
|
+
return index << RHO_BITS | rho;
|
|
105
|
+
}
|
|
106
|
+
function sparseIndex(entry) {
|
|
107
|
+
return entry >>> RHO_BITS;
|
|
108
|
+
}
|
|
109
|
+
function sparseRho(entry) {
|
|
110
|
+
return entry & RHO_MASK;
|
|
111
|
+
}
|
|
112
|
+
function foldSparse(buf, len, fromP, registers, toP) {
|
|
113
|
+
const d = fromP - toP;
|
|
114
|
+
for (let i = 0; i < len; i++) {
|
|
115
|
+
const entry = buf[i] ?? 0;
|
|
116
|
+
const at = sparseIndex(entry) >>> 25 - fromP;
|
|
117
|
+
registers.raise(at >>> d, foldRho(at, sparseRho(entry), d));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function refoldSparse(entry, fromP, toP) {
|
|
121
|
+
const index = sparseIndex(entry);
|
|
122
|
+
return encodeSparse(index, foldRho(index >>> 25 - fromP, sparseRho(entry), fromP - toP));
|
|
123
|
+
}
|
|
124
|
+
function compact(buf, len) {
|
|
125
|
+
if (len === 0) return 0;
|
|
126
|
+
buf.subarray(0, len).sort();
|
|
127
|
+
let out = 0;
|
|
128
|
+
for (let i = 0; i < len; i++) {
|
|
129
|
+
const entry = buf[i] ?? 0;
|
|
130
|
+
const next = buf[i + 1] ?? 0;
|
|
131
|
+
if (i + 1 < len && sparseIndex(next) === sparseIndex(entry)) continue;
|
|
132
|
+
buf[out++] = entry;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/hll/hll.ts
|
|
138
|
+
const ERROR_CONSTANT = 1.04;
|
|
139
|
+
const TYPE = 5;
|
|
140
|
+
const PARAMS_SIZE = 6;
|
|
141
|
+
const DENSE = 0;
|
|
142
|
+
const SPARSE = 1;
|
|
143
|
+
const ENTRY_SIZE = 4;
|
|
144
|
+
function assertPrecision(p) {
|
|
145
|
+
if (!Number.isInteger(p) || p < 4 || p > 18) throw new ParamError(`p must be an integer in [${String(4)}, ${String(18)}], got ${String(p)}`);
|
|
146
|
+
}
|
|
147
|
+
function sparseCapacity(registers) {
|
|
148
|
+
return registers.bytes.length >> 2;
|
|
149
|
+
}
|
|
150
|
+
var HyperLogLog = class HyperLogLog {
|
|
151
|
+
#registers;
|
|
152
|
+
#p;
|
|
153
|
+
#seed;
|
|
154
|
+
#scratch = {
|
|
155
|
+
w0: 0,
|
|
156
|
+
w1: 0,
|
|
157
|
+
w2: 0,
|
|
158
|
+
w3: 0
|
|
159
|
+
};
|
|
160
|
+
#sparse;
|
|
161
|
+
#len = 0;
|
|
162
|
+
constructor({ p, seed = 0 }) {
|
|
163
|
+
assertPrecision(p);
|
|
164
|
+
assertUint32(seed, "seed");
|
|
165
|
+
this.#registers = new Registers(p);
|
|
166
|
+
this.#p = p;
|
|
167
|
+
this.#seed = seed;
|
|
168
|
+
this.#sparse = new Int32Array(sparseCapacity(this.#registers));
|
|
169
|
+
}
|
|
170
|
+
static create(relativeError) {
|
|
171
|
+
return new HyperLogLog(hllSizing(relativeError));
|
|
172
|
+
}
|
|
173
|
+
static from(keys, relativeError) {
|
|
174
|
+
const sketch = HyperLogLog.create(relativeError);
|
|
175
|
+
for (const key of keys) sketch.add(key);
|
|
176
|
+
return sketch;
|
|
177
|
+
}
|
|
178
|
+
static fromBytes(bytes) {
|
|
179
|
+
const { type, flags, body } = readHeader(bytes);
|
|
180
|
+
if (type !== TYPE) throw new SerializationError(`expected DSTL type ${String(TYPE)}, got ${String(type)}`);
|
|
181
|
+
if ((flags & 15) !== 0) throw new UnknownHashVariantError(`unsupported hash variant ${String(flags & 15)}`);
|
|
182
|
+
assertMinBodyLength(body.length, PARAMS_SIZE, "hll");
|
|
183
|
+
const dv = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
184
|
+
const p = body[0] ?? 0;
|
|
185
|
+
const encoding = body[1] ?? 0;
|
|
186
|
+
const seed = dv.getUint32(2, true);
|
|
187
|
+
if (encoding !== DENSE && encoding !== SPARSE) throw new SerializationError(`unknown hll encoding ${String(encoding)}`);
|
|
188
|
+
const sketch = new HyperLogLog({
|
|
189
|
+
p,
|
|
190
|
+
seed
|
|
191
|
+
});
|
|
192
|
+
const payload = body.length - PARAMS_SIZE;
|
|
193
|
+
const maxRho = 65 - p;
|
|
194
|
+
if (encoding === DENSE) {
|
|
195
|
+
assertBodyLength(body.length, PARAMS_SIZE + sketch.#registers.bytes.length, "hll");
|
|
196
|
+
sketch.#goDense();
|
|
197
|
+
sketch.#registers.bytes.set(body.subarray(PARAMS_SIZE));
|
|
198
|
+
const largest = sketch.#registers.max();
|
|
199
|
+
if (largest > maxRho) throw new SerializationError(`hll: register holds ${String(largest)}, above the maximum ${String(maxRho)} at p ${String(p)}`);
|
|
200
|
+
return sketch;
|
|
201
|
+
}
|
|
202
|
+
const capacity = sparseCapacity(sketch.#registers);
|
|
203
|
+
if (payload % ENTRY_SIZE !== 0 || payload / ENTRY_SIZE > capacity) throw new TruncatedError(`hll: sparse payload of ${String(payload)} bytes is not up to ${String(capacity)} whole entries`);
|
|
204
|
+
const entries = payload / ENTRY_SIZE;
|
|
205
|
+
const buffer = new Int32Array(capacity);
|
|
206
|
+
for (let i = 0; i < entries; i++) {
|
|
207
|
+
const entry = dv.getUint32(PARAMS_SIZE + i * ENTRY_SIZE, true);
|
|
208
|
+
if (entry > 2147483647) throw new SerializationError(`hll: sparse entry ${String(entry)} does not fit the 31-bit encoding`);
|
|
209
|
+
const rho = sparseRho(entry);
|
|
210
|
+
if (rho > maxRho) throw new SerializationError(`hll: sparse entry holds rho ${String(rho)}, above the maximum ${String(maxRho)} at p ${String(p)}`);
|
|
211
|
+
buffer[i] = entry;
|
|
212
|
+
}
|
|
213
|
+
sketch.#sparse = buffer;
|
|
214
|
+
sketch.#len = entries;
|
|
215
|
+
return sketch;
|
|
216
|
+
}
|
|
217
|
+
toBytes() {
|
|
218
|
+
const sparse = this.#sparse;
|
|
219
|
+
if (sparse !== null) {
|
|
220
|
+
const entries = compact(sparse, this.#len);
|
|
221
|
+
this.#len = entries;
|
|
222
|
+
return writeFrame({
|
|
223
|
+
version: 4,
|
|
224
|
+
type: TYPE,
|
|
225
|
+
flags: 0
|
|
226
|
+
}, PARAMS_SIZE + entries * ENTRY_SIZE, (body, dv) => {
|
|
227
|
+
body[0] = this.#p;
|
|
228
|
+
body[1] = SPARSE;
|
|
229
|
+
dv.setUint32(2, this.#seed, true);
|
|
230
|
+
for (let i = 0; i < entries; i++) dv.setUint32(PARAMS_SIZE + i * ENTRY_SIZE, sparse[i] ?? 0, true);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const payload = this.#registers.bytes;
|
|
234
|
+
return writeFrame({
|
|
235
|
+
version: 4,
|
|
236
|
+
type: TYPE,
|
|
237
|
+
flags: 0
|
|
238
|
+
}, PARAMS_SIZE + payload.length, (body, dv) => {
|
|
239
|
+
body[0] = this.#p;
|
|
240
|
+
body[1] = DENSE;
|
|
241
|
+
dv.setUint32(2, this.#seed, true);
|
|
242
|
+
body.set(payload, PARAMS_SIZE);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
toJSON() {
|
|
246
|
+
return toJSONEnvelope(this.toBytes());
|
|
247
|
+
}
|
|
248
|
+
static fromJSON(value) {
|
|
249
|
+
return HyperLogLog.fromBytes(fromJSONEnvelope(value));
|
|
250
|
+
}
|
|
251
|
+
get p() {
|
|
252
|
+
return this.#p;
|
|
253
|
+
}
|
|
254
|
+
get seed() {
|
|
255
|
+
return this.#seed;
|
|
256
|
+
}
|
|
257
|
+
get standardError() {
|
|
258
|
+
return ERROR_CONSTANT / Math.sqrt(2 ** this.#p);
|
|
259
|
+
}
|
|
260
|
+
add(key) {
|
|
261
|
+
const s = this.#scratch;
|
|
262
|
+
hash128KeyInto(key, this.#seed, s);
|
|
263
|
+
const p = this.#p;
|
|
264
|
+
const tail = s.w0 << p >>> 0;
|
|
265
|
+
const rho = tail !== 0 ? Math.clz32(tail) + 1 : 32 - p + Math.clz32(s.w1) + 1;
|
|
266
|
+
const sparse = this.#sparse;
|
|
267
|
+
if (sparse === null) {
|
|
268
|
+
this.#registers.raise(s.w0 >>> 32 - p, rho);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
sparse[this.#len++] = encodeSparse(s.w0 >>> 7, rho);
|
|
272
|
+
if (this.#len === sparse.length) this.#collapse(sparse);
|
|
273
|
+
}
|
|
274
|
+
#collapse(sparse) {
|
|
275
|
+
const distinct = compact(sparse, this.#len);
|
|
276
|
+
this.#len = distinct;
|
|
277
|
+
const slack = Math.max(1, sparse.length >> 2);
|
|
278
|
+
if (distinct > sparse.length - slack) this.#goDense();
|
|
279
|
+
}
|
|
280
|
+
union(other) {
|
|
281
|
+
if (other.#seed !== this.#seed) throw new ParamError(`cannot merge sketches with different seeds, got ${String(this.#seed)} and ${String(other.#seed)}`);
|
|
282
|
+
const p = Math.min(this.#p, other.#p);
|
|
283
|
+
const merged = new HyperLogLog({
|
|
284
|
+
p,
|
|
285
|
+
seed: this.#seed
|
|
286
|
+
});
|
|
287
|
+
merged.#drain(this);
|
|
288
|
+
merged.#drain(other);
|
|
289
|
+
return merged;
|
|
290
|
+
}
|
|
291
|
+
#drain(src) {
|
|
292
|
+
const sparse = src.#sparse;
|
|
293
|
+
if (sparse === null) {
|
|
294
|
+
this.#goDense();
|
|
295
|
+
foldDense(src.#registers, src.#p, this.#registers, this.#p);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const distinct = compact(sparse, src.#len);
|
|
299
|
+
src.#len = distinct;
|
|
300
|
+
for (let i = 0; i < distinct; i++) this.#absorb(refoldSparse(sparse[i] ?? 0, src.#p, this.#p));
|
|
301
|
+
}
|
|
302
|
+
#absorb(entry) {
|
|
303
|
+
const sparse = this.#sparse;
|
|
304
|
+
if (sparse === null) {
|
|
305
|
+
const index = sparseIndex(entry) >>> 25 - this.#p;
|
|
306
|
+
this.#registers.raise(index, sparseRho(entry));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
sparse[this.#len++] = entry;
|
|
310
|
+
if (this.#len === sparse.length) this.#collapse(sparse);
|
|
311
|
+
}
|
|
312
|
+
#goDense() {
|
|
313
|
+
const sparse = this.#sparse;
|
|
314
|
+
if (sparse === null) return;
|
|
315
|
+
foldSparse(sparse, this.#len, this.#p, this.#registers, this.#p);
|
|
316
|
+
this.#sparse = null;
|
|
317
|
+
this.#len = 0;
|
|
318
|
+
}
|
|
319
|
+
#dense() {
|
|
320
|
+
const sparse = this.#sparse;
|
|
321
|
+
if (sparse === null) return this.#registers;
|
|
322
|
+
const out = new Registers(this.#p);
|
|
323
|
+
foldSparse(sparse, this.#len, this.#p, out, this.#p);
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
equals(other) {
|
|
327
|
+
if (other.#p !== this.#p || other.#seed !== this.#seed) return false;
|
|
328
|
+
const mine = this.#dense().bytes;
|
|
329
|
+
const theirs = other.#dense().bytes;
|
|
330
|
+
for (let i = 0; i < mine.length; i++) if (mine[i] !== theirs[i]) return false;
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
count() {
|
|
334
|
+
const sparse = this.#sparse;
|
|
335
|
+
if (sparse !== null) {
|
|
336
|
+
const distinct = compact(sparse, this.#len);
|
|
337
|
+
this.#len = distinct;
|
|
338
|
+
const buckets = 2 ** 25;
|
|
339
|
+
return Math.round(buckets * Math.log(buckets / (buckets - distinct)));
|
|
340
|
+
}
|
|
341
|
+
const p = this.#p;
|
|
342
|
+
const q = 64 - p;
|
|
343
|
+
const m = 2 ** p;
|
|
344
|
+
const hist = new Int32Array(q + 2);
|
|
345
|
+
for (let i = 0; i < m; i++) {
|
|
346
|
+
const value = this.#registers.get(i);
|
|
347
|
+
hist[value] = (hist[value] ?? 0) + 1;
|
|
348
|
+
}
|
|
349
|
+
return Math.round(estimate(hist, p));
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
//#endregion
|
|
353
|
+
export { BadMagicError, ChecksumError, HyperLogLog, ParamError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, hllSizing };
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,19 +15,22 @@ declare class SerializationError extends Error {
|
|
|
15
15
|
override readonly name: string;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Thrown when a frame is shorter than its header plus trailer,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Thrown when a frame is shorter than its header plus trailer, when the body
|
|
19
|
+
* length its header declares disagrees with the bytes actually present, or
|
|
20
|
+
* when its body length does not match the length its declared params imply.
|
|
21
|
+
* The bytes were cut short in transit or storage; re-fetch the whole frame.
|
|
21
22
|
*/
|
|
22
23
|
declare class TruncatedError extends SerializationError {
|
|
23
24
|
/** Discriminates this error from other `Error`s. */
|
|
24
25
|
override readonly name = "TruncatedError";
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
* Thrown when a frame does not start with the four-byte `
|
|
28
|
-
* was never produced by `toBytes`.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Thrown when a frame does not start with the four-byte `DSTL` magic, so it
|
|
29
|
+
* was never produced by `toBytes`. Frames written before format version 4
|
|
30
|
+
* carry the older `AMQF` magic and are rejected here; re-serialize them with
|
|
31
|
+
* the version you run. Otherwise check that the bytes really are a distillate
|
|
32
|
+
* frame and not another payload, a text encoding of one, or a slice taken at
|
|
33
|
+
* the wrong offset.
|
|
31
34
|
*/
|
|
32
35
|
declare class BadMagicError extends SerializationError {
|
|
33
36
|
/** Discriminates this error from other `Error`s. */
|
|
@@ -15,19 +15,22 @@ declare class SerializationError extends Error {
|
|
|
15
15
|
override readonly name: string;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Thrown when a frame is shorter than its header plus trailer,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Thrown when a frame is shorter than its header plus trailer, when the body
|
|
19
|
+
* length its header declares disagrees with the bytes actually present, or
|
|
20
|
+
* when its body length does not match the length its declared params imply.
|
|
21
|
+
* The bytes were cut short in transit or storage; re-fetch the whole frame.
|
|
21
22
|
*/
|
|
22
23
|
declare class TruncatedError extends SerializationError {
|
|
23
24
|
/** Discriminates this error from other `Error`s. */
|
|
24
25
|
override readonly name = "TruncatedError";
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
* Thrown when a frame does not start with the four-byte `
|
|
28
|
-
* was never produced by `toBytes`.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Thrown when a frame does not start with the four-byte `DSTL` magic, so it
|
|
29
|
+
* was never produced by `toBytes`. Frames written before format version 4
|
|
30
|
+
* carry the older `AMQF` magic and are rejected here; re-serialize them with
|
|
31
|
+
* the version you run. Otherwise check that the bytes really are a distillate
|
|
32
|
+
* frame and not another payload, a text encoding of one, or a slice taken at
|
|
33
|
+
* the wrong offset.
|
|
31
34
|
*/
|
|
32
35
|
declare class BadMagicError extends SerializationError {
|
|
33
36
|
/** Discriminates this error from other `Error`s. */
|
|
@@ -305,21 +305,22 @@ function crc32(bytes) {
|
|
|
305
305
|
for (; i < len; i++) crc = (at(t, (crc ^ dv.getUint8(i)) & 255) ^ crc >>> 8) >>> 0;
|
|
306
306
|
return (crc ^ 4294967295) >>> 0;
|
|
307
307
|
}
|
|
308
|
-
const
|
|
308
|
+
const MAGIC = Uint8Array.of(68, 83, 84, 76);
|
|
309
|
+
const HEADER_SIZE = 16;
|
|
310
|
+
const BODY_LENGTH_OFFSET = 8;
|
|
309
311
|
const TRAILER_SIZE = 4;
|
|
310
312
|
function writeFrame(header, bodyLength, fill) {
|
|
311
313
|
const frame = new Uint8Array(HEADER_SIZE + bodyLength + TRAILER_SIZE);
|
|
312
|
-
|
|
313
|
-
frame
|
|
314
|
-
frame[2] = 81;
|
|
315
|
-
frame[3] = 70;
|
|
314
|
+
const frameView = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
315
|
+
frame.set(MAGIC, 0);
|
|
316
316
|
frame[4] = header.version;
|
|
317
317
|
frame[5] = header.type;
|
|
318
318
|
frame[6] = header.flags;
|
|
319
319
|
frame[7] = 0;
|
|
320
|
+
frameView.setUint32(BODY_LENGTH_OFFSET, bodyLength, true);
|
|
320
321
|
fill(frame.subarray(HEADER_SIZE, HEADER_SIZE + bodyLength), new DataView(frame.buffer, HEADER_SIZE, bodyLength));
|
|
321
322
|
const crc = crc32(frame.subarray(0, HEADER_SIZE + bodyLength));
|
|
322
|
-
|
|
323
|
+
frameView.setUint32(HEADER_SIZE + bodyLength, crc, true);
|
|
323
324
|
return frame;
|
|
324
325
|
}
|
|
325
326
|
var SerializationError = class extends Error {
|
|
@@ -344,7 +345,7 @@ const JSON_TAG = "distillate";
|
|
|
344
345
|
function toJSONEnvelope(bytes) {
|
|
345
346
|
return {
|
|
346
347
|
$: JSON_TAG,
|
|
347
|
-
v:
|
|
348
|
+
v: 4,
|
|
348
349
|
data: toBase64(bytes)
|
|
349
350
|
};
|
|
350
351
|
}
|
|
@@ -352,7 +353,7 @@ function fromJSONEnvelope(value) {
|
|
|
352
353
|
if (value === null || typeof value !== "object") throw new SerializationError("not a distillate filter JSON object");
|
|
353
354
|
const o = value;
|
|
354
355
|
if (o.$ !== JSON_TAG) throw new SerializationError(`expected "$":"${JSON_TAG}"`);
|
|
355
|
-
if (o.v !==
|
|
356
|
+
if (o.v !== 4) throw new UnknownVersionError(`unsupported JSON version ${String(o.v)}, expected ${String(4)}`);
|
|
356
357
|
if (typeof o.data !== "string") throw new SerializationError("missing string \"data\"");
|
|
357
358
|
try {
|
|
358
359
|
return fromBase64(o.data);
|
|
@@ -372,16 +373,19 @@ function assertBodyLength(actual, expected, context) {
|
|
|
372
373
|
if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
|
|
373
374
|
}
|
|
374
375
|
function readHeader(frame) {
|
|
375
|
-
if (frame.length <
|
|
376
|
-
|
|
376
|
+
if (frame.length < 20) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(20)}`);
|
|
377
|
+
for (let i = 0; i < MAGIC.length; i++) if (frame[i] !== MAGIC[i]) throw new BadMagicError("frame does not start with the DSTL magic");
|
|
377
378
|
const version = frame[4] ?? 0;
|
|
378
|
-
if (version !==
|
|
379
|
-
|
|
379
|
+
if (version !== 4) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
|
|
380
|
+
const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
381
|
+
const bodyLength = view.getUint32(BODY_LENGTH_OFFSET, true);
|
|
382
|
+
if (HEADER_SIZE + bodyLength + TRAILER_SIZE !== frame.length) throw new TruncatedError(`frame declares a ${String(bodyLength)}-byte body but holds ${String(frame.length - HEADER_SIZE - TRAILER_SIZE)}`);
|
|
383
|
+
if (view.getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
|
|
380
384
|
return {
|
|
381
385
|
version,
|
|
382
386
|
type: frame[5] ?? 0,
|
|
383
387
|
flags: frame[6] ?? 0,
|
|
384
|
-
body: frame.subarray(HEADER_SIZE,
|
|
388
|
+
body: frame.subarray(HEADER_SIZE, HEADER_SIZE + bodyLength)
|
|
385
389
|
};
|
|
386
390
|
}
|
|
387
391
|
//#endregion
|
|
@@ -305,21 +305,22 @@ function crc32(bytes) {
|
|
|
305
305
|
for (; i < len; i++) crc = (at(t, (crc ^ dv.getUint8(i)) & 255) ^ crc >>> 8) >>> 0;
|
|
306
306
|
return (crc ^ 4294967295) >>> 0;
|
|
307
307
|
}
|
|
308
|
-
const
|
|
308
|
+
const MAGIC = Uint8Array.of(68, 83, 84, 76);
|
|
309
|
+
const HEADER_SIZE = 16;
|
|
310
|
+
const BODY_LENGTH_OFFSET = 8;
|
|
309
311
|
const TRAILER_SIZE = 4;
|
|
310
312
|
function writeFrame(header, bodyLength, fill) {
|
|
311
313
|
const frame = new Uint8Array(HEADER_SIZE + bodyLength + TRAILER_SIZE);
|
|
312
|
-
|
|
313
|
-
frame
|
|
314
|
-
frame[2] = 81;
|
|
315
|
-
frame[3] = 70;
|
|
314
|
+
const frameView = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
315
|
+
frame.set(MAGIC, 0);
|
|
316
316
|
frame[4] = header.version;
|
|
317
317
|
frame[5] = header.type;
|
|
318
318
|
frame[6] = header.flags;
|
|
319
319
|
frame[7] = 0;
|
|
320
|
+
frameView.setUint32(BODY_LENGTH_OFFSET, bodyLength, true);
|
|
320
321
|
fill(frame.subarray(HEADER_SIZE, HEADER_SIZE + bodyLength), new DataView(frame.buffer, HEADER_SIZE, bodyLength));
|
|
321
322
|
const crc = crc32(frame.subarray(0, HEADER_SIZE + bodyLength));
|
|
322
|
-
|
|
323
|
+
frameView.setUint32(HEADER_SIZE + bodyLength, crc, true);
|
|
323
324
|
return frame;
|
|
324
325
|
}
|
|
325
326
|
var SerializationError = class extends Error {
|
|
@@ -344,7 +345,7 @@ const JSON_TAG = "distillate";
|
|
|
344
345
|
function toJSONEnvelope(bytes) {
|
|
345
346
|
return {
|
|
346
347
|
$: JSON_TAG,
|
|
347
|
-
v:
|
|
348
|
+
v: 4,
|
|
348
349
|
data: toBase64(bytes)
|
|
349
350
|
};
|
|
350
351
|
}
|
|
@@ -352,7 +353,7 @@ function fromJSONEnvelope(value) {
|
|
|
352
353
|
if (value === null || typeof value !== "object") throw new SerializationError("not a distillate filter JSON object");
|
|
353
354
|
const o = value;
|
|
354
355
|
if (o.$ !== JSON_TAG) throw new SerializationError(`expected "$":"${JSON_TAG}"`);
|
|
355
|
-
if (o.v !==
|
|
356
|
+
if (o.v !== 4) throw new UnknownVersionError(`unsupported JSON version ${String(o.v)}, expected ${String(4)}`);
|
|
356
357
|
if (typeof o.data !== "string") throw new SerializationError("missing string \"data\"");
|
|
357
358
|
try {
|
|
358
359
|
return fromBase64(o.data);
|
|
@@ -372,16 +373,19 @@ function assertBodyLength(actual, expected, context) {
|
|
|
372
373
|
if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
|
|
373
374
|
}
|
|
374
375
|
function readHeader(frame) {
|
|
375
|
-
if (frame.length <
|
|
376
|
-
|
|
376
|
+
if (frame.length < 20) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(20)}`);
|
|
377
|
+
for (let i = 0; i < MAGIC.length; i++) if (frame[i] !== MAGIC[i]) throw new BadMagicError("frame does not start with the DSTL magic");
|
|
377
378
|
const version = frame[4] ?? 0;
|
|
378
|
-
if (version !==
|
|
379
|
-
|
|
379
|
+
if (version !== 4) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
|
|
380
|
+
const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
381
|
+
const bodyLength = view.getUint32(BODY_LENGTH_OFFSET, true);
|
|
382
|
+
if (HEADER_SIZE + bodyLength + TRAILER_SIZE !== frame.length) throw new TruncatedError(`frame declares a ${String(bodyLength)}-byte body but holds ${String(frame.length - HEADER_SIZE - TRAILER_SIZE)}`);
|
|
383
|
+
if (view.getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
|
|
380
384
|
return {
|
|
381
385
|
version,
|
|
382
386
|
type: frame[5] ?? 0,
|
|
383
387
|
flags: frame[6] ?? 0,
|
|
384
|
-
body: frame.subarray(HEADER_SIZE,
|
|
388
|
+
body: frame.subarray(HEADER_SIZE, HEADER_SIZE + bodyLength)
|
|
385
389
|
};
|
|
386
390
|
}
|
|
387
391
|
//#endregion
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const require_params = require("./params-UTZbJ22c.cjs");
|
|
2
|
+
//#region src/core/sizing.ts
|
|
3
|
+
function bloomSizing(n, epsilon) {
|
|
4
|
+
const m = Math.ceil(-n * Math.log(epsilon) / (Math.LN2 * Math.LN2));
|
|
5
|
+
return {
|
|
6
|
+
m,
|
|
7
|
+
k: Math.max(1, Math.round(m / n * Math.LN2))
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function hllSizing(relativeError) {
|
|
11
|
+
require_params.assertProbability(relativeError, "relativeError");
|
|
12
|
+
const p = Math.max(4, Math.ceil(2 * Math.log2(1.04 / relativeError)));
|
|
13
|
+
if (p > 18) throw new require_params.ParamError(`relativeError ${String(relativeError)} needs precision ${String(p)}, above the maximum ${String(18)}`);
|
|
14
|
+
return { p };
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
Object.defineProperty(exports, "bloomSizing", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function() {
|
|
20
|
+
return bloomSizing;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports, "hllSizing", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function() {
|
|
26
|
+
return hllSizing;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { i as assertProbability, t as ParamError } from "./params-CeajSrwv.js";
|
|
2
|
+
//#region src/core/sizing.ts
|
|
3
|
+
function bloomSizing(n, epsilon) {
|
|
4
|
+
const m = Math.ceil(-n * Math.log(epsilon) / (Math.LN2 * Math.LN2));
|
|
5
|
+
return {
|
|
6
|
+
m,
|
|
7
|
+
k: Math.max(1, Math.round(m / n * Math.LN2))
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function hllSizing(relativeError) {
|
|
11
|
+
assertProbability(relativeError, "relativeError");
|
|
12
|
+
const p = Math.max(4, Math.ceil(2 * Math.log2(1.04 / relativeError)));
|
|
13
|
+
if (p > 18) throw new ParamError(`relativeError ${String(relativeError)} needs precision ${String(p)}, above the maximum ${String(18)}`);
|
|
14
|
+
return { p };
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { hllSizing as n, bloomSizing as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/core/sizing.d.ts
|
|
2
|
+
/** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
|
|
3
|
+
interface BloomSizing {
|
|
4
|
+
/** Number of bits in the filter. */
|
|
5
|
+
m: number;
|
|
6
|
+
/** Number of hash probes per key. */
|
|
7
|
+
k: number;
|
|
8
|
+
}
|
|
9
|
+
/** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
|
|
10
|
+
declare function bloomSizing(n: number, epsilon: number): BloomSizing;
|
|
11
|
+
/** HyperLogLog geometry: the `HllParams` fields a sizing solve determines. */
|
|
12
|
+
interface HllSizing {
|
|
13
|
+
/** Precision: the sketch holds `2 ** p` registers. */
|
|
14
|
+
p: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Smallest precision whose standard error `1.04 / sqrt(2 ** p)` meets
|
|
18
|
+
* `relativeError`. Throws {@link ParamError} when even `HLL_MAX_P` cannot.
|
|
19
|
+
*/
|
|
20
|
+
declare function hllSizing(relativeError: number): HllSizing;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { hllSizing as i, HllSizing as n, bloomSizing as r, BloomSizing as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/core/sizing.d.ts
|
|
2
|
+
/** Bloom filter geometry: the `BloomParams` fields a sizing solve determines. */
|
|
3
|
+
interface BloomSizing {
|
|
4
|
+
/** Number of bits in the filter. */
|
|
5
|
+
m: number;
|
|
6
|
+
/** Number of hash probes per key. */
|
|
7
|
+
k: number;
|
|
8
|
+
}
|
|
9
|
+
/** Optimal Bloom-filter sizing: `m` bits and `k` hashes for `n` items at target FPR `epsilon`. */
|
|
10
|
+
declare function bloomSizing(n: number, epsilon: number): BloomSizing;
|
|
11
|
+
/** HyperLogLog geometry: the `HllParams` fields a sizing solve determines. */
|
|
12
|
+
interface HllSizing {
|
|
13
|
+
/** Precision: the sketch holds `2 ** p` registers. */
|
|
14
|
+
p: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Smallest precision whose standard error `1.04 / sqrt(2 ** p)` meets
|
|
18
|
+
* `relativeError`. Throws {@link ParamError} when even `HLL_MAX_P` cannot.
|
|
19
|
+
*/
|
|
20
|
+
declare function hllSizing(relativeError: number): HllSizing;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { hllSizing as i, HllSizing as n, bloomSizing as r, BloomSizing as t };
|