Errors
distillate exports ten error classes. Each has a name that discriminates it
from a plain Error, so you can narrow with instanceof or switch on name.
They fall into three groups by cause: bad parameters, an operation two filters cannot support, and a frame that will not decode.
| Error | Thrown by | Cause |
|---|---|---|
ParamError |
constructors, create, sizing |
A parameter is out of range |
BloomParamMismatchError |
BloomFilter.union |
Filters disagree on geometry |
BlockedBloomParamMismatchError |
BlockedBloomFilter.union |
Filters disagree on geometry |
BinaryFuseBuildError |
BinaryFuse8.from, BinaryFuse16.from |
The peel stalled on every seed |
SerializationError |
fromJSON, and the base of the five below |
The envelope is malformed |
TruncatedError |
fromBytes |
The frame is short |
BadMagicError |
fromBytes |
Not an AMQF frame |
UnknownVersionError |
fromBytes, fromJSON |
A format version this build cannot read |
UnknownHashVariantError |
fromBytes |
A hash this build cannot reproduce |
ChecksumError |
fromBytes |
CRC32 does not match |
The six serialization errors are exported from all three subpaths. The rest
are exported from the subpath of the structure that throws them, except
ParamError, which is exported from distillate/bloom and
distillate/blocked.
Parameter errors
Section titled “Parameter errors”ParamError
Section titled “ParamError”API reference. Extends RangeError.
Thrown when a structure is constructed or sized with a parameter outside its valid range. Specifically:
n,m,k, orcapacityis not an integer of at least 1.epsilonis not a finite number strictly between 0 and 1.seedis outside the uint32 range, orkoutside uint16.blockedBitsPerKey(epsilon)orBlockedBloomFilter.createis asked for a rate below the blocked floor near 1e-8.
What to do: this is a programming error, not a runtime condition. The message names the offending parameter and its value. Validate at your own boundary if the value comes from configuration or user input.
import { BloomFilter, ParamError } from "distillate/bloom";
try { BloomFilter.create(1000, 1.5); // epsilon must be in (0, 1)} catch (error) { if (error instanceof ParamError) { error.name; // "ParamError" error.message; // "epsilon must be in the open interval (0, 1), got 1.5" }}For the blocked floor specifically, the fix is a different structure rather than a different number. See sizing and tuning.
Merge errors
Section titled “Merge errors”Both union implementations require the two filters to be structurally
identical. They do not silently reshape.
BloomParamMismatchError
Section titled “BloomParamMismatchError”Thrown when BloomFilter.union is given a filter whose m, k, or
seed differs from the receiver’s.
What to do: build both sides with the same parameters. Passing the same
n and epsilon to create is enough, since the geometry is a pure function
of those two. If you construct directly, share one BloomParams object.
import { BloomFilter, BloomParamMismatchError } from "distillate/bloom";
const a = BloomFilter.create(1000, 0.01);const b = BloomFilter.create(2000, 0.01); // different capacity, different m
try { a.union(b);} catch (error) { if (error instanceof BloomParamMismatchError) { // Rebuild b at a's capacity, or merge from the source keys instead. }}BlockedBloomParamMismatchError
Section titled “BlockedBloomParamMismatchError”Thrown when BlockedBloomFilter.union is given a filter whose numBlocks
or seed differs from the receiver’s.
What to do: the same. Size both sides identically. Note the check is on
numBlocks, not on the bitsPerKey you asked for, so two filters created
with different capacities can still round to the same block count and merge
fine.
import { BlockedBloomFilter, BlockedBloomParamMismatchError,} from "distillate/blocked";
const a = BlockedBloomFilter.create(1000, 0.01);const b = BlockedBloomFilter.create(100_000, 0.01);
try { a.union(b);} catch (error) { error instanceof BlockedBloomParamMismatchError; // true}Build errors
Section titled “Build errors”BinaryFuseBuildError
Section titled “BinaryFuseBuildError”Thrown when the Binary Fuse peel fails on all 100 seeds it tries. The build hashes the keys, then peels a 3-hypergraph; a peel can stall, so it retries with a bumped seed rather than returning a corrupt filter.
What to do: in practice, nothing, because you will not see this. The probability of 100 consecutive stalls is astronomically small for any real key set. If it does happen, treat it as a bug report rather than something to retry, and include the key count. The one thing it guarantees is that you never receive a filter that would produce false negatives.
import { BinaryFuse8, BinaryFuseBuildError } from "distillate/fuse";
try { const filter = BinaryFuse8.from(["alice", "bob", "carol"]); filter.has("alice"); // true} catch (error) { if (error instanceof BinaryFuseBuildError) { // Astronomically unlikely, and never a silently wrong filter. }}Serialization errors
Section titled “Serialization errors”Five specific errors, all extending SerializationError. Catch the base class
to handle any decode failure at once, or a subclass to tell the causes apart.
import { BloomFilter, SerializationError } from "distillate/bloom";
function load(bytes: Uint8Array): BloomFilter | null { try { return BloomFilter.fromBytes(bytes); } catch (error) { if (error instanceof SerializationError) return null; // rebuild instead throw error; }}None of them are retryable. The same bytes fail the same way every time, so the recovery is always to get different bytes or to rebuild from the source keys.
SerializationError
Section titled “SerializationError”Thrown directly when a JSON envelope is malformed: not an object, missing
the "distillate" tag, missing a string data field, or data that is not
valid base64. It is also the base class of the five below.
What to do: check that you passed the object toJSON produced, and that
it survived whatever transport carried it. An envelope that has been through a
schema mapper or had its keys renamed will fail here.
TruncatedError
Section titled “TruncatedError”Thrown when a frame is shorter than its 8-byte header plus 4-byte trailer, or when the body length does not match what its declared params imply.
What to do: the bytes were cut short in transit or storage. Re-fetch the whole frame. This check runs before any allocation, so a hostile length field cannot make the reader allocate.
BadMagicError
Section titled “BadMagicError”Thrown when a frame does not start with the four-byte AMQF magic, so it
was never produced by toBytes.
What to do: check what you are actually handing it. Common causes are a
frame from another library (a bloom-filters dump lands here), a base64
string that was never decoded, or a Uint8Array sliced at the wrong offset.
fromBytes respects byteOffset and byteLength, so a correct subarray view
is fine; an incorrect one is not.
UnknownVersionError
Section titled “UnknownVersionError”Thrown when a frame’s version byte, or a JSON envelope’s v field, is not
the FORMAT_VERSION this build reads. That is 3 today.
What to do: upgrade distillate on the reading side, or re-serialize the
data with the version you run. A reader must be at least as new as the
producer: the version check protects a newer reader from an older frame, and
cannot protect an older reader from a newer one. If you build filters in one
service and read them in another, upgrade the readers first. See
serialization and
versioning.
UnknownHashVariantError
Section titled “UnknownHashVariantError”Thrown when the low nibble of a frame’s flags byte names a hash this build
cannot reproduce. Version 3 defines exactly one variant, 0, which is
murmur3_x86_128 for every structure.
What to do: rebuild the filter from the source keys with the version you run. The stored bits are unreadable without the hash that produced them, so there is nothing to recover from the frame itself.
ChecksumError
Section titled “ChecksumError”Thrown when a frame’s CRC32 trailer does not match its contents. The header and params parsed, so this is corruption in the bytes rather than a foreign format.
What to do: discard the bytes and re-fetch. Do not use the filter anyway: a corrupted payload can produce false negatives, which is the one guarantee a filter is supposed to keep. Check the storage or transport path, since a CRC mismatch means something wrote or copied the frame incorrectly.