distillate 0.7.0 → 0.8.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 +22 -19
- package/dist/blocked/index.cjs +11 -112
- package/dist/blocked/index.d.cts +2 -2
- package/dist/blocked/index.d.ts +2 -2
- package/dist/blocked/index.js +6 -113
- package/dist/bloom/index.cjs +8 -97
- package/dist/bloom/index.d.cts +2 -2
- package/dist/bloom/index.d.ts +2 -2
- package/dist/bloom/index.js +3 -98
- package/dist/fuse/index.cjs +10 -111
- package/dist/fuse/index.d.cts +2 -2
- package/dist/fuse/index.d.ts +2 -2
- package/dist/fuse/index.js +5 -112
- package/dist/index.cjs +1 -2
- package/dist/index.js +1 -2
- package/dist/{params-BrWuBC2A.js → params-CeajSrwv.js} +0 -7
- package/dist/{params--8CNXYWu.cjs → params-UTZbJ22c.cjs} +0 -7
- package/dist/{serialize-BnwtzcMw.js → serialize-BqIcsR2J.js} +3 -29
- package/dist/{serialize-BIIKUHH6.cjs → serialize-CXnRWItH.cjs} +26 -28
- package/dist/serialize-ChyWpB9F.d.cts +73 -0
- package/dist/serialize-ChyWpB9F.d.ts +73 -0
- package/package.json +2 -3
- package/dist/serialize-DRKh6QOr.d.cts +0 -15
- package/dist/serialize-DRKh6QOr.d.ts +0 -15
package/dist/fuse/index.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { _ as hash128KeyInto, a as UnknownHashVariantError, c as assertMinBodyLength, d as readHeader, f as toJSONEnvelope, g as fmix64, h as RLO, i as TruncatedError, l as bytesEqual, m as RHI, n as ChecksumError, o as UnknownVersionError, p as writeFrame, r as SerializationError, s as assertBodyLength, t as BadMagicError, u as fromJSONEnvelope, y as mul64 } from "../serialize-BqIcsR2J.js";
|
|
2
2
|
//#region src/fuse/fuse.ts
|
|
3
3
|
const ARITY = 3;
|
|
4
4
|
const TYPE_FUSE8 = 3;
|
|
5
5
|
const TYPE_FUSE16 = 4;
|
|
6
|
-
/** Thrown when binary fuse construction fails to converge on the key set. */
|
|
7
6
|
var BinaryFuseBuildError = class extends Error {
|
|
8
|
-
/** Discriminates this error from other `Error`s. */
|
|
9
7
|
name = "BinaryFuseBuildError";
|
|
10
8
|
};
|
|
11
9
|
const scratchHash = {
|
|
@@ -58,24 +56,10 @@ function computeParams(size) {
|
|
|
58
56
|
arrayLength
|
|
59
57
|
};
|
|
60
58
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Bits stored per key by a binary fuse filter over `n` keys at a fingerprint
|
|
63
|
-
* width, without building one. Counts `n` as distinct keys, since a built
|
|
64
|
-
* filter sizes on its deduped hash count.
|
|
65
|
-
*
|
|
66
|
-
* @param n - Number of distinct keys.
|
|
67
|
-
* @param width - Fingerprint width in bits: `8` for {@link BinaryFuse8}, `16` for {@link BinaryFuse16}.
|
|
68
|
-
* @returns Bits per key (`0` for an empty filter).
|
|
69
|
-
*/
|
|
70
59
|
function fuseBitsPerKey(n, width) {
|
|
71
60
|
if (n === 0) return 0;
|
|
72
61
|
return computeParams(n).arrayLength * width / n;
|
|
73
62
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Peel the 3-hypergraph and assign fingerprints so every key's XOR of its 3
|
|
76
|
-
* lanes equals its fingerprint. Retries with a bumped seed on a stall; returns
|
|
77
|
-
* the seed that succeeded.
|
|
78
|
-
*/
|
|
79
63
|
function buildFingerprints(fp, hashes, params, maxAttempts = 100) {
|
|
80
64
|
const size = hashes.length / 2;
|
|
81
65
|
const { seg, segMask, segCountLen, arrayLength } = params;
|
|
@@ -86,7 +70,7 @@ function buildFingerprints(fp, hashes, params, maxAttempts = 100) {
|
|
|
86
70
|
const orderLo = new Uint32Array(size);
|
|
87
71
|
const orderHi = new Uint32Array(size);
|
|
88
72
|
const orderIdx = new Uint32Array(size);
|
|
89
|
-
const pos =
|
|
73
|
+
const pos = new Uint32Array(3);
|
|
90
74
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
91
75
|
const seed = attempt;
|
|
92
76
|
counts.fill(0);
|
|
@@ -145,7 +129,7 @@ function buildFingerprints(fp, hashes, params, maxAttempts = 100) {
|
|
|
145
129
|
}
|
|
146
130
|
function buildState(keys, alloc) {
|
|
147
131
|
const hashList = [];
|
|
148
|
-
const seen =
|
|
132
|
+
const seen = new Set();
|
|
149
133
|
for (const key of keys) {
|
|
150
134
|
hash128KeyInto(key, 0, scratchHash);
|
|
151
135
|
const lo = scratchHash.w0 >>> 0;
|
|
@@ -203,10 +187,6 @@ function fuseStateFromBytes(bytes, expectedType) {
|
|
|
203
187
|
size
|
|
204
188
|
};
|
|
205
189
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Shared behavior for the static binary fuse filters: an immutable,
|
|
208
|
-
* space-efficient membership filter built once from a fixed key set.
|
|
209
|
-
*/
|
|
210
190
|
var BinaryFuse = class {
|
|
211
191
|
#fp;
|
|
212
192
|
#seed;
|
|
@@ -214,7 +194,7 @@ var BinaryFuse = class {
|
|
|
214
194
|
#segMask;
|
|
215
195
|
#segCountLen;
|
|
216
196
|
#size;
|
|
217
|
-
#pos =
|
|
197
|
+
#pos = new Uint32Array(3);
|
|
218
198
|
constructor(state) {
|
|
219
199
|
this.#fp = state.fp;
|
|
220
200
|
this.#seed = state.seed;
|
|
@@ -223,23 +203,15 @@ var BinaryFuse = class {
|
|
|
223
203
|
this.#segCountLen = state.params.segCountLen;
|
|
224
204
|
this.#size = state.size;
|
|
225
205
|
}
|
|
226
|
-
/** Number of distinct keys the filter was built from. */
|
|
227
206
|
get size() {
|
|
228
207
|
return this.#size;
|
|
229
208
|
}
|
|
230
|
-
/** Hash seed selected during construction (may differ from 0 after a peel retry). */
|
|
231
209
|
get seed() {
|
|
232
210
|
return this.#seed;
|
|
233
211
|
}
|
|
234
|
-
/** Actual bits stored per key (`0` for an empty filter). */
|
|
235
212
|
get bitsPerKey() {
|
|
236
213
|
return this.#size === 0 ? 0 : this.#fp.byteLength * 8 / this.#size;
|
|
237
214
|
}
|
|
238
|
-
/**
|
|
239
|
-
* Serializes the filter to a portable little-endian byte layout.
|
|
240
|
-
*
|
|
241
|
-
* @returns The serialized filter, readable by the matching `fromBytes`.
|
|
242
|
-
*/
|
|
243
215
|
toBytes() {
|
|
244
216
|
const laneBytes = new Uint8Array(this.#fp.buffer, this.#fp.byteOffset, this.#fp.byteLength);
|
|
245
217
|
return writeFrame({
|
|
@@ -254,12 +226,6 @@ var BinaryFuse = class {
|
|
|
254
226
|
body.set(laneBytes, 16);
|
|
255
227
|
});
|
|
256
228
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Tests whether a key is in the set.
|
|
259
|
-
*
|
|
260
|
-
* @param key - The key to test.
|
|
261
|
-
* @returns `true` if present (possibly a false positive); `false` guarantees absence.
|
|
262
|
-
*/
|
|
263
229
|
has(key) {
|
|
264
230
|
if (this.#fp.length === 0) return false;
|
|
265
231
|
hash128KeyInto(key, 0, scratchHash);
|
|
@@ -273,107 +239,34 @@ var BinaryFuse = class {
|
|
|
273
239
|
const p2 = this.#pos[2] ?? 0;
|
|
274
240
|
return ((mlo ^ mhi) & mask) === (((this.#fp[p0] ?? 0) ^ (this.#fp[p1] ?? 0) ^ (this.#fp[p2] ?? 0)) & mask);
|
|
275
241
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Tests structural equality: `true` when `other` serializes to identical
|
|
278
|
-
* bytes. A {@link BinaryFuse8} and a {@link BinaryFuse16} are never equal,
|
|
279
|
-
* since their frames carry different type bytes.
|
|
280
|
-
*
|
|
281
|
-
* @param other - The filter to compare against.
|
|
282
|
-
* @returns `true` if the two filters are byte-for-byte identical.
|
|
283
|
-
*/
|
|
284
242
|
equals(other) {
|
|
285
243
|
return bytesEqual(this.toBytes(), other.toBytes());
|
|
286
244
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Serializes the filter to a JSON-friendly envelope wrapping the base64 of
|
|
289
|
-
* the `toBytes` frame.
|
|
290
|
-
*
|
|
291
|
-
* @returns The envelope, readable by the matching `fromJSON`.
|
|
292
|
-
*/
|
|
293
245
|
toJSON() {
|
|
294
246
|
return toJSONEnvelope(this.toBytes());
|
|
295
247
|
}
|
|
296
248
|
};
|
|
297
|
-
/**
|
|
298
|
-
* A static 8-bit binary fuse filter: built once from a key set, then immutable.
|
|
299
|
-
* The most space-efficient option (~9 bits/key at ~0.39% false-positive rate).
|
|
300
|
-
*
|
|
301
|
-
* @example
|
|
302
|
-
* ```ts
|
|
303
|
-
* const filter = BinaryFuse8.from(["alice", "bob", "carol"]);
|
|
304
|
-
* filter.has("alice"); // true
|
|
305
|
-
* filter.size; // 3
|
|
306
|
-
* ```
|
|
307
|
-
*/
|
|
308
249
|
var BinaryFuse8 = class BinaryFuse8 extends BinaryFuse {
|
|
309
|
-
/**
|
|
310
|
-
* Builds a filter from the given keys; duplicates are ignored.
|
|
311
|
-
*
|
|
312
|
-
* @param keys - The complete set of keys to store.
|
|
313
|
-
* @returns A new immutable filter.
|
|
314
|
-
* @throws {@link BinaryFuseBuildError} if construction fails to converge.
|
|
315
|
-
*/
|
|
316
250
|
static from(keys) {
|
|
317
251
|
return new BinaryFuse8(buildState(keys, (n) => new Uint8Array(n)));
|
|
318
252
|
}
|
|
319
|
-
/**
|
|
320
|
-
* Restores a filter from its {@link BinaryFuse8.toBytes} serialization.
|
|
321
|
-
*
|
|
322
|
-
* @param bytes - The serialized filter.
|
|
323
|
-
* @returns The reconstructed filter.
|
|
324
|
-
*/
|
|
325
253
|
static fromBytes(bytes) {
|
|
326
254
|
return new BinaryFuse8(fuseStateFromBytes(bytes, TYPE_FUSE8));
|
|
327
255
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Restores a filter from its {@link BinaryFuse8.toJSON} envelope.
|
|
330
|
-
*
|
|
331
|
-
* @param value - The JSON envelope.
|
|
332
|
-
* @returns The reconstructed filter.
|
|
333
|
-
*/
|
|
334
256
|
static fromJSON(value) {
|
|
335
257
|
return BinaryFuse8.fromBytes(fromJSONEnvelope(value));
|
|
336
258
|
}
|
|
337
259
|
};
|
|
338
|
-
/**
|
|
339
|
-
* A static 16-bit binary fuse filter: like {@link BinaryFuse8} but twice the
|
|
340
|
-
* space (~18 bits/key) for a far lower false-positive rate (~1/65536).
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```ts
|
|
344
|
-
* const filter = BinaryFuse16.from(["alice", "bob", "carol"]);
|
|
345
|
-
* filter.has("alice"); // true
|
|
346
|
-
* ```
|
|
347
|
-
*/
|
|
348
260
|
var BinaryFuse16 = class BinaryFuse16 extends BinaryFuse {
|
|
349
|
-
/**
|
|
350
|
-
* Builds a filter from the given keys; duplicates are ignored.
|
|
351
|
-
*
|
|
352
|
-
* @param keys - The complete set of keys to store.
|
|
353
|
-
* @returns A new immutable filter.
|
|
354
|
-
* @throws {@link BinaryFuseBuildError} if construction fails to converge.
|
|
355
|
-
*/
|
|
356
261
|
static from(keys) {
|
|
357
262
|
return new BinaryFuse16(buildState(keys, (n) => new Uint16Array(n)));
|
|
358
263
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Restores a filter from its {@link BinaryFuse16.toBytes} serialization.
|
|
361
|
-
*
|
|
362
|
-
* @param bytes - The serialized filter.
|
|
363
|
-
* @returns The reconstructed filter.
|
|
364
|
-
*/
|
|
365
264
|
static fromBytes(bytes) {
|
|
366
265
|
return new BinaryFuse16(fuseStateFromBytes(bytes, TYPE_FUSE16));
|
|
367
266
|
}
|
|
368
|
-
/**
|
|
369
|
-
* Restores a filter from its {@link BinaryFuse16.toJSON} envelope.
|
|
370
|
-
*
|
|
371
|
-
* @param value - The JSON envelope.
|
|
372
|
-
* @returns The reconstructed filter.
|
|
373
|
-
*/
|
|
374
267
|
static fromJSON(value) {
|
|
375
268
|
return BinaryFuse16.fromBytes(fromJSONEnvelope(value));
|
|
376
269
|
}
|
|
377
270
|
};
|
|
378
271
|
//#endregion
|
|
379
|
-
export { BinaryFuse16, BinaryFuse8, BinaryFuseBuildError, fuseBitsPerKey };
|
|
272
|
+
export { BadMagicError, BinaryFuse16, BinaryFuse8, BinaryFuseBuildError, ChecksumError, SerializationError, TruncatedError, UnknownHashVariantError, UnknownVersionError, fuseBitsPerKey };
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
//#region src/core/params.ts
|
|
2
|
-
/** Thrown when a structure is constructed with invalid parameters. */
|
|
3
2
|
var ParamError = class extends RangeError {
|
|
4
|
-
/** Discriminates this error from other `Error`s. */
|
|
5
3
|
name = "ParamError";
|
|
6
4
|
};
|
|
7
|
-
/** Asserts `value` is an integer greater than or equal to 1. */
|
|
8
5
|
function assertPositiveInt(value, label) {
|
|
9
6
|
if (!Number.isInteger(value) || value < 1) throw new ParamError(`${label} must be a positive integer, got ${String(value)}`);
|
|
10
7
|
}
|
|
11
|
-
/** Asserts `value` is a finite number greater than 0 (a positive real). */
|
|
12
8
|
function assertPositiveFinite(value, label) {
|
|
13
9
|
if (!Number.isFinite(value) || value <= 0) throw new ParamError(`${label} must be a positive number, got ${String(value)}`);
|
|
14
10
|
}
|
|
15
|
-
/** Asserts `value` is an integer in the uint16 range `[0, 65535]`. */
|
|
16
11
|
function assertUint16(value, label) {
|
|
17
12
|
if (!Number.isInteger(value) || value < 0 || value > 65535) throw new ParamError(`${label} must be a uint16, got ${String(value)}`);
|
|
18
13
|
}
|
|
19
|
-
/** Asserts `value` is an integer in the uint32 range `[0, 2^32 - 1]`. */
|
|
20
14
|
function assertUint32(value, label) {
|
|
21
15
|
if (!Number.isInteger(value) || value < 0 || value > 4294967295) throw new ParamError(`${label} must be a uint32, got ${String(value)}`);
|
|
22
16
|
}
|
|
23
|
-
/** Asserts `value` is a finite number in the open interval `(0, 1)`. */
|
|
24
17
|
function assertProbability(value, label) {
|
|
25
18
|
if (!Number.isFinite(value) || value <= 0 || value >= 1) throw new ParamError(`${label} must be in the open interval (0, 1), got ${String(value)}`);
|
|
26
19
|
}
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
//#region src/core/params.ts
|
|
2
|
-
/** Thrown when a structure is constructed with invalid parameters. */
|
|
3
2
|
var ParamError = class extends RangeError {
|
|
4
|
-
/** Discriminates this error from other `Error`s. */
|
|
5
3
|
name = "ParamError";
|
|
6
4
|
};
|
|
7
|
-
/** Asserts `value` is an integer greater than or equal to 1. */
|
|
8
5
|
function assertPositiveInt(value, label) {
|
|
9
6
|
if (!Number.isInteger(value) || value < 1) throw new ParamError(`${label} must be a positive integer, got ${String(value)}`);
|
|
10
7
|
}
|
|
11
|
-
/** Asserts `value` is a finite number greater than 0 (a positive real). */
|
|
12
8
|
function assertPositiveFinite(value, label) {
|
|
13
9
|
if (!Number.isFinite(value) || value <= 0) throw new ParamError(`${label} must be a positive number, got ${String(value)}`);
|
|
14
10
|
}
|
|
15
|
-
/** Asserts `value` is an integer in the uint16 range `[0, 65535]`. */
|
|
16
11
|
function assertUint16(value, label) {
|
|
17
12
|
if (!Number.isInteger(value) || value < 0 || value > 65535) throw new ParamError(`${label} must be a uint16, got ${String(value)}`);
|
|
18
13
|
}
|
|
19
|
-
/** Asserts `value` is an integer in the uint32 range `[0, 2^32 - 1]`. */
|
|
20
14
|
function assertUint32(value, label) {
|
|
21
15
|
if (!Number.isInteger(value) || value < 0 || value > 4294967295) throw new ParamError(`${label} must be a uint32, got ${String(value)}`);
|
|
22
16
|
}
|
|
23
|
-
/** Asserts `value` is a finite number in the open interval `(0, 1)`. */
|
|
24
17
|
function assertProbability(value, label) {
|
|
25
18
|
if (!Number.isFinite(value) || value <= 0 || value >= 1) throw new ParamError(`${label} must be in the open interval (0, 1), got ${String(value)}`);
|
|
26
19
|
}
|
|
@@ -174,7 +174,7 @@ function computeLanes(bytes, seed, len) {
|
|
|
174
174
|
LANES.w3 = h4;
|
|
175
175
|
}
|
|
176
176
|
const keyEncoder = new TextEncoder();
|
|
177
|
-
let keyBuf =
|
|
177
|
+
let keyBuf = new Uint8Array(256);
|
|
178
178
|
let encBytes = keyBuf;
|
|
179
179
|
let encLen = 0;
|
|
180
180
|
function encodeKey(key) {
|
|
@@ -216,11 +216,6 @@ function hash32x2Into(key, seed, out) {
|
|
|
216
216
|
out[0] = LANES.w0;
|
|
217
217
|
out[1] = LANES.w1;
|
|
218
218
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Derive `count` bucket indices in `[0, range)` from a key using
|
|
221
|
-
* Kirsch-Mitzenmacher enhanced double hashing `g_i = h1 + i*h2 + i^2`
|
|
222
|
-
* (the RocksDB `+i^2` fix), reduced into range with Lemire multiply-shift.
|
|
223
|
-
*/
|
|
224
219
|
function probeInto(key, count, range, seed, out) {
|
|
225
220
|
keyToLanes(key, seed);
|
|
226
221
|
const a = LANES.w0;
|
|
@@ -230,7 +225,7 @@ function probeInto(key, count, range, seed, out) {
|
|
|
230
225
|
//#endregion
|
|
231
226
|
//#region src/core/base64.ts
|
|
232
227
|
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
233
|
-
const DECODE =
|
|
228
|
+
const DECODE = new Int16Array(128).fill(-1);
|
|
234
229
|
for (let i = 0; i < 64; i++) DECODE[ALPHABET.charCodeAt(i)] = i;
|
|
235
230
|
const sym = (n) => ALPHABET.charAt(n & 63);
|
|
236
231
|
function toBase64(bytes) {
|
|
@@ -312,12 +307,6 @@ function crc32(bytes) {
|
|
|
312
307
|
}
|
|
313
308
|
const HEADER_SIZE = 8;
|
|
314
309
|
const TRAILER_SIZE = 4;
|
|
315
|
-
/**
|
|
316
|
-
* Allocates a full frame once, hands `fill` a writable view over the body region
|
|
317
|
-
* (and a `DataView` scoped to it), then seals the CRC trailer. The body view
|
|
318
|
-
* aliases the frame's buffer, so callers write fields and payload straight into
|
|
319
|
-
* the frame with no intermediate body allocation or copy.
|
|
320
|
-
*/
|
|
321
310
|
function writeFrame(header, bodyLength, fill) {
|
|
322
311
|
const frame = new Uint8Array(HEADER_SIZE + bodyLength + TRAILER_SIZE);
|
|
323
312
|
frame[0] = 65;
|
|
@@ -352,7 +341,6 @@ var ChecksumError = class extends SerializationError {
|
|
|
352
341
|
name = "ChecksumError";
|
|
353
342
|
};
|
|
354
343
|
const JSON_TAG = "distillate";
|
|
355
|
-
/** Wraps a serialized frame in the JSON envelope. */
|
|
356
344
|
function toJSONEnvelope(bytes) {
|
|
357
345
|
return {
|
|
358
346
|
$: JSON_TAG,
|
|
@@ -360,11 +348,6 @@ function toJSONEnvelope(bytes) {
|
|
|
360
348
|
data: toBase64(bytes)
|
|
361
349
|
};
|
|
362
350
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Validates a JSON envelope and returns the raw frame bytes for a structure's
|
|
365
|
-
* own `fromBytes` to decode. Throws {@link SerializationError} on any envelope
|
|
366
|
-
* defect; the frame itself is checked downstream.
|
|
367
|
-
*/
|
|
368
351
|
function fromJSONEnvelope(value) {
|
|
369
352
|
if (value === null || typeof value !== "object") throw new SerializationError("not a distillate filter JSON object");
|
|
370
353
|
const o = value;
|
|
@@ -377,23 +360,14 @@ function fromJSONEnvelope(value) {
|
|
|
377
360
|
throw new SerializationError("envelope data is not valid base64");
|
|
378
361
|
}
|
|
379
362
|
}
|
|
380
|
-
/** Byte-wise equality of two frames; the basis for structure `equals`. */
|
|
381
363
|
function bytesEqual(a, b) {
|
|
382
364
|
if (a.length !== b.length) return false;
|
|
383
365
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
384
366
|
return true;
|
|
385
367
|
}
|
|
386
|
-
/**
|
|
387
|
-
* Asserts a frame body is long enough to hold its fixed params block, so the
|
|
388
|
-
* params can be read without running off the end.
|
|
389
|
-
*/
|
|
390
368
|
function assertMinBodyLength(actual, min, context) {
|
|
391
369
|
if (actual < min) throw new TruncatedError(`${context}: body of ${String(actual)} bytes is shorter than the ${String(min)}-byte params block`);
|
|
392
370
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Asserts a frame body is exactly the length its declared params imply, so a
|
|
395
|
-
* hostile or truncated frame is rejected before any backing store is allocated.
|
|
396
|
-
*/
|
|
397
371
|
function assertBodyLength(actual, expected, context) {
|
|
398
372
|
if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
|
|
399
373
|
}
|
|
@@ -411,4 +385,4 @@ function readHeader(frame) {
|
|
|
411
385
|
};
|
|
412
386
|
}
|
|
413
387
|
//#endregion
|
|
414
|
-
export {
|
|
388
|
+
export { hash128KeyInto as _, UnknownHashVariantError as a, probeInto as b, assertMinBodyLength as c, readHeader as d, toJSONEnvelope as f, fmix64 as g, RLO as h, TruncatedError as i, bytesEqual as l, RHI as m, ChecksumError as n, UnknownVersionError as o, writeFrame as p, SerializationError as r, assertBodyLength as s, BadMagicError as t, fromJSONEnvelope as u, hash32x2Into as v, reduce as x, mul64 as y };
|
|
@@ -174,7 +174,7 @@ function computeLanes(bytes, seed, len) {
|
|
|
174
174
|
LANES.w3 = h4;
|
|
175
175
|
}
|
|
176
176
|
const keyEncoder = new TextEncoder();
|
|
177
|
-
let keyBuf =
|
|
177
|
+
let keyBuf = new Uint8Array(256);
|
|
178
178
|
let encBytes = keyBuf;
|
|
179
179
|
let encLen = 0;
|
|
180
180
|
function encodeKey(key) {
|
|
@@ -216,11 +216,6 @@ function hash32x2Into(key, seed, out) {
|
|
|
216
216
|
out[0] = LANES.w0;
|
|
217
217
|
out[1] = LANES.w1;
|
|
218
218
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Derive `count` bucket indices in `[0, range)` from a key using
|
|
221
|
-
* Kirsch-Mitzenmacher enhanced double hashing `g_i = h1 + i*h2 + i^2`
|
|
222
|
-
* (the RocksDB `+i^2` fix), reduced into range with Lemire multiply-shift.
|
|
223
|
-
*/
|
|
224
219
|
function probeInto(key, count, range, seed, out) {
|
|
225
220
|
keyToLanes(key, seed);
|
|
226
221
|
const a = LANES.w0;
|
|
@@ -230,7 +225,7 @@ function probeInto(key, count, range, seed, out) {
|
|
|
230
225
|
//#endregion
|
|
231
226
|
//#region src/core/base64.ts
|
|
232
227
|
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
233
|
-
const DECODE =
|
|
228
|
+
const DECODE = new Int16Array(128).fill(-1);
|
|
234
229
|
for (let i = 0; i < 64; i++) DECODE[ALPHABET.charCodeAt(i)] = i;
|
|
235
230
|
const sym = (n) => ALPHABET.charAt(n & 63);
|
|
236
231
|
function toBase64(bytes) {
|
|
@@ -312,12 +307,6 @@ function crc32(bytes) {
|
|
|
312
307
|
}
|
|
313
308
|
const HEADER_SIZE = 8;
|
|
314
309
|
const TRAILER_SIZE = 4;
|
|
315
|
-
/**
|
|
316
|
-
* Allocates a full frame once, hands `fill` a writable view over the body region
|
|
317
|
-
* (and a `DataView` scoped to it), then seals the CRC trailer. The body view
|
|
318
|
-
* aliases the frame's buffer, so callers write fields and payload straight into
|
|
319
|
-
* the frame with no intermediate body allocation or copy.
|
|
320
|
-
*/
|
|
321
310
|
function writeFrame(header, bodyLength, fill) {
|
|
322
311
|
const frame = new Uint8Array(HEADER_SIZE + bodyLength + TRAILER_SIZE);
|
|
323
312
|
frame[0] = 65;
|
|
@@ -352,7 +341,6 @@ var ChecksumError = class extends SerializationError {
|
|
|
352
341
|
name = "ChecksumError";
|
|
353
342
|
};
|
|
354
343
|
const JSON_TAG = "distillate";
|
|
355
|
-
/** Wraps a serialized frame in the JSON envelope. */
|
|
356
344
|
function toJSONEnvelope(bytes) {
|
|
357
345
|
return {
|
|
358
346
|
$: JSON_TAG,
|
|
@@ -360,11 +348,6 @@ function toJSONEnvelope(bytes) {
|
|
|
360
348
|
data: toBase64(bytes)
|
|
361
349
|
};
|
|
362
350
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Validates a JSON envelope and returns the raw frame bytes for a structure's
|
|
365
|
-
* own `fromBytes` to decode. Throws {@link SerializationError} on any envelope
|
|
366
|
-
* defect; the frame itself is checked downstream.
|
|
367
|
-
*/
|
|
368
351
|
function fromJSONEnvelope(value) {
|
|
369
352
|
if (value === null || typeof value !== "object") throw new SerializationError("not a distillate filter JSON object");
|
|
370
353
|
const o = value;
|
|
@@ -377,23 +360,14 @@ function fromJSONEnvelope(value) {
|
|
|
377
360
|
throw new SerializationError("envelope data is not valid base64");
|
|
378
361
|
}
|
|
379
362
|
}
|
|
380
|
-
/** Byte-wise equality of two frames; the basis for structure `equals`. */
|
|
381
363
|
function bytesEqual(a, b) {
|
|
382
364
|
if (a.length !== b.length) return false;
|
|
383
365
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
384
366
|
return true;
|
|
385
367
|
}
|
|
386
|
-
/**
|
|
387
|
-
* Asserts a frame body is long enough to hold its fixed params block, so the
|
|
388
|
-
* params can be read without running off the end.
|
|
389
|
-
*/
|
|
390
368
|
function assertMinBodyLength(actual, min, context) {
|
|
391
369
|
if (actual < min) throw new TruncatedError(`${context}: body of ${String(actual)} bytes is shorter than the ${String(min)}-byte params block`);
|
|
392
370
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Asserts a frame body is exactly the length its declared params imply, so a
|
|
395
|
-
* hostile or truncated frame is rejected before any backing store is allocated.
|
|
396
|
-
*/
|
|
397
371
|
function assertBodyLength(actual, expected, context) {
|
|
398
372
|
if (actual !== expected) throw new TruncatedError(`${context}: body of ${String(actual)} bytes does not match the declared params (expected ${String(expected)})`);
|
|
399
373
|
}
|
|
@@ -411,6 +385,18 @@ function readHeader(frame) {
|
|
|
411
385
|
};
|
|
412
386
|
}
|
|
413
387
|
//#endregion
|
|
388
|
+
Object.defineProperty(exports, "BadMagicError", {
|
|
389
|
+
enumerable: true,
|
|
390
|
+
get: function() {
|
|
391
|
+
return BadMagicError;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
Object.defineProperty(exports, "ChecksumError", {
|
|
395
|
+
enumerable: true,
|
|
396
|
+
get: function() {
|
|
397
|
+
return ChecksumError;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
414
400
|
Object.defineProperty(exports, "RHI", {
|
|
415
401
|
enumerable: true,
|
|
416
402
|
get: function() {
|
|
@@ -429,12 +415,24 @@ Object.defineProperty(exports, "SerializationError", {
|
|
|
429
415
|
return SerializationError;
|
|
430
416
|
}
|
|
431
417
|
});
|
|
418
|
+
Object.defineProperty(exports, "TruncatedError", {
|
|
419
|
+
enumerable: true,
|
|
420
|
+
get: function() {
|
|
421
|
+
return TruncatedError;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
432
424
|
Object.defineProperty(exports, "UnknownHashVariantError", {
|
|
433
425
|
enumerable: true,
|
|
434
426
|
get: function() {
|
|
435
427
|
return UnknownHashVariantError;
|
|
436
428
|
}
|
|
437
429
|
});
|
|
430
|
+
Object.defineProperty(exports, "UnknownVersionError", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
get: function() {
|
|
433
|
+
return UnknownVersionError;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
438
436
|
Object.defineProperty(exports, "assertBodyLength", {
|
|
439
437
|
enumerable: true,
|
|
440
438
|
get: function() {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/core/bytes.d.ts
|
|
2
|
+
type BytesLike = string | Uint8Array | ArrayBuffer;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/core/serialize.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Base class for every defect `fromBytes` and `fromJSON` reject. Thrown
|
|
7
|
+
* directly when a JSON envelope is malformed: not an object, missing the
|
|
8
|
+
* `"distillate"` tag, missing `data`, or `data` that is not valid base64.
|
|
9
|
+
* Catch this to handle any decode failure at once, or a subclass to tell the
|
|
10
|
+
* causes apart. The input is corrupt or foreign, so discard it; retrying the
|
|
11
|
+
* same bytes cannot succeed.
|
|
12
|
+
*/
|
|
13
|
+
declare class SerializationError extends Error {
|
|
14
|
+
/** Discriminates this error from other `Error`s. */
|
|
15
|
+
override readonly name: string;
|
|
16
|
+
}
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
declare class TruncatedError extends SerializationError {
|
|
23
|
+
/** Discriminates this error from other `Error`s. */
|
|
24
|
+
override readonly name = "TruncatedError";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
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.
|
|
31
|
+
*/
|
|
32
|
+
declare class BadMagicError extends SerializationError {
|
|
33
|
+
/** Discriminates this error from other `Error`s. */
|
|
34
|
+
override readonly name = "BadMagicError";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when a frame or JSON envelope declares a format version this release
|
|
38
|
+
* does not read. A reader must be at least as new as the producer, so upgrade
|
|
39
|
+
* `distillate` or re-serialize the data with the version you run.
|
|
40
|
+
*/
|
|
41
|
+
declare class UnknownVersionError extends SerializationError {
|
|
42
|
+
/** Discriminates this error from other `Error`s. */
|
|
43
|
+
override readonly name = "UnknownVersionError";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Thrown when a frame's flags nibble names a hash this release cannot
|
|
47
|
+
* reproduce, so its stored bits are unreadable. Rebuild the filter from the
|
|
48
|
+
* source keys with the version you run.
|
|
49
|
+
*/
|
|
50
|
+
declare class UnknownHashVariantError extends SerializationError {
|
|
51
|
+
/** Discriminates this error from other `Error`s. */
|
|
52
|
+
override readonly name = "UnknownHashVariantError";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when a frame's CRC32 trailer does not match its contents, so the
|
|
56
|
+
* bytes were corrupted after they were written. Discard them and re-fetch;
|
|
57
|
+
* the payload cannot be trusted even where it still parses.
|
|
58
|
+
*/
|
|
59
|
+
declare class ChecksumError extends SerializationError {
|
|
60
|
+
/** Discriminates this error from other `Error`s. */
|
|
61
|
+
override readonly name = "ChecksumError";
|
|
62
|
+
}
|
|
63
|
+
/** JSON-friendly envelope for a filter: the binary frame, base64-encoded. */
|
|
64
|
+
interface FilterJSON {
|
|
65
|
+
/** Format tag; always `"distillate"`. */
|
|
66
|
+
$: string;
|
|
67
|
+
/** Binary format version. */
|
|
68
|
+
v: number;
|
|
69
|
+
/** Base64 of the `toBytes` frame. */
|
|
70
|
+
data: string;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { TruncatedError as a, BytesLike as c, SerializationError as i, ChecksumError as n, UnknownHashVariantError as o, FilterJSON as r, UnknownVersionError as s, BadMagicError as t };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/core/bytes.d.ts
|
|
2
|
+
type BytesLike = string | Uint8Array | ArrayBuffer;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/core/serialize.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Base class for every defect `fromBytes` and `fromJSON` reject. Thrown
|
|
7
|
+
* directly when a JSON envelope is malformed: not an object, missing the
|
|
8
|
+
* `"distillate"` tag, missing `data`, or `data` that is not valid base64.
|
|
9
|
+
* Catch this to handle any decode failure at once, or a subclass to tell the
|
|
10
|
+
* causes apart. The input is corrupt or foreign, so discard it; retrying the
|
|
11
|
+
* same bytes cannot succeed.
|
|
12
|
+
*/
|
|
13
|
+
declare class SerializationError extends Error {
|
|
14
|
+
/** Discriminates this error from other `Error`s. */
|
|
15
|
+
override readonly name: string;
|
|
16
|
+
}
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
declare class TruncatedError extends SerializationError {
|
|
23
|
+
/** Discriminates this error from other `Error`s. */
|
|
24
|
+
override readonly name = "TruncatedError";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
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.
|
|
31
|
+
*/
|
|
32
|
+
declare class BadMagicError extends SerializationError {
|
|
33
|
+
/** Discriminates this error from other `Error`s. */
|
|
34
|
+
override readonly name = "BadMagicError";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when a frame or JSON envelope declares a format version this release
|
|
38
|
+
* does not read. A reader must be at least as new as the producer, so upgrade
|
|
39
|
+
* `distillate` or re-serialize the data with the version you run.
|
|
40
|
+
*/
|
|
41
|
+
declare class UnknownVersionError extends SerializationError {
|
|
42
|
+
/** Discriminates this error from other `Error`s. */
|
|
43
|
+
override readonly name = "UnknownVersionError";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Thrown when a frame's flags nibble names a hash this release cannot
|
|
47
|
+
* reproduce, so its stored bits are unreadable. Rebuild the filter from the
|
|
48
|
+
* source keys with the version you run.
|
|
49
|
+
*/
|
|
50
|
+
declare class UnknownHashVariantError extends SerializationError {
|
|
51
|
+
/** Discriminates this error from other `Error`s. */
|
|
52
|
+
override readonly name = "UnknownHashVariantError";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when a frame's CRC32 trailer does not match its contents, so the
|
|
56
|
+
* bytes were corrupted after they were written. Discard them and re-fetch;
|
|
57
|
+
* the payload cannot be trusted even where it still parses.
|
|
58
|
+
*/
|
|
59
|
+
declare class ChecksumError extends SerializationError {
|
|
60
|
+
/** Discriminates this error from other `Error`s. */
|
|
61
|
+
override readonly name = "ChecksumError";
|
|
62
|
+
}
|
|
63
|
+
/** JSON-friendly envelope for a filter: the binary frame, base64-encoded. */
|
|
64
|
+
interface FilterJSON {
|
|
65
|
+
/** Format tag; always `"distillate"`. */
|
|
66
|
+
$: string;
|
|
67
|
+
/** Binary format version. */
|
|
68
|
+
v: number;
|
|
69
|
+
/** Base64 of the `toBytes` frame. */
|
|
70
|
+
data: string;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { TruncatedError as a, BytesLike as c, SerializationError as i, ChecksumError as n, UnknownHashVariantError as o, FilterJSON as r, UnknownVersionError as s, BadMagicError as t };
|