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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#endregion
3
3
  //#region src/index.ts
4
- const VERSION = "0.8.2";
4
+ const VERSION = "0.9.0";
5
5
  //#endregion
6
6
  exports.VERSION = VERSION;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/index.ts
3
- const VERSION = "0.8.2";
3
+ const VERSION = "0.9.0";
4
4
  //#endregion
5
5
  export { VERSION };
@@ -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, or when its
19
- * body length does not match the length its declared params imply. The bytes
20
- * were cut short in transit or storage; re-fetch the whole frame.
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 `AMQF` magic, so it
28
- * was never produced by `toBytes`. Check that the bytes really are a
29
- * distillate frame and not another payload, a text encoding of one, or a
30
- * slice taken at the wrong offset.
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, or when its
19
- * body length does not match the length its declared params imply. The bytes
20
- * were cut short in transit or storage; re-fetch the whole frame.
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 `AMQF` magic, so it
28
- * was never produced by `toBytes`. Check that the bytes really are a
29
- * distillate frame and not another payload, a text encoding of one, or a
30
- * slice taken at the wrong offset.
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 HEADER_SIZE = 8;
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
- frame[0] = 65;
313
- frame[1] = 77;
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
- new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(HEADER_SIZE + bodyLength, crc, true);
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: 3,
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 !== 3) throw new UnknownVersionError(`unsupported JSON version ${String(o.v)}, expected ${String(3)}`);
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 < 12) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(12)}`);
376
- if (frame[0] !== 65 || frame[1] !== 77 || frame[2] !== 81 || frame[3] !== 70) throw new BadMagicError("frame does not start with the AMQF magic");
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 !== 3) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
379
- if (new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
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, frame.length - TRAILER_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 HEADER_SIZE = 8;
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
- frame[0] = 65;
313
- frame[1] = 77;
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
- new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(HEADER_SIZE + bodyLength, crc, true);
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: 3,
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 !== 3) throw new UnknownVersionError(`unsupported JSON version ${String(o.v)}, expected ${String(3)}`);
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 < 12) throw new TruncatedError(`frame of ${String(frame.length)} bytes is shorter than the minimum ${String(12)}`);
376
- if (frame[0] !== 65 || frame[1] !== 77 || frame[2] !== 81 || frame[3] !== 70) throw new BadMagicError("frame does not start with the AMQF magic");
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 !== 3) throw new UnknownVersionError(`unsupported format version ${String(version)}`);
379
- if (new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(frame.length - TRAILER_SIZE, true) !== crc32(frame.subarray(0, frame.length - TRAILER_SIZE))) throw new ChecksumError("frame CRC32 does not match its contents");
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, frame.length - TRAILER_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 };