Releases: mauro-d/faircount
Release list
v1.1.0
Counting from a CommonJS project no longer needs a dynamic import: the package
answers require() as well, on the versions of Node that load ESM from it.
Added
- A CommonJS project can
require('faircount'). There is no CommonJS build: the
exports map answers therequirecondition with the same ESM file, which Node
reads fromrequire()on the versions below.
Changed
- Requires Node
^20.19.0 || >=22.12.0, the versions that load ESM from
require()without a flag. A single floor would have promised 22.0 through
22.11, where the feature sits behind a flag. Below the rangeimportstill
works, but it is no longer covered.
Fixed
- A fractional
seedis refused instead of seeding the run of its floor:1.5
and1produced the same sequence, while the README and the types promised an
integer.
The README now compares faircount with HyperLogLog, says that two estimators
never combine into one count, and gives what the sample weighs in bytes.
v1.0.0
The estimator is an object you create and keep: every counting function takes it
first, so one count can be saved, resumed, read at any point, and carried across
several sources.
Breaking
- Requires Node 20 or newer.
estimateDistinct(estimator, source, options).epsilon,delta,
expectedSize,seedandrandombelong tonew CVM()alone; the options of
the counting functions cover only how values reach it.- Sources are split by kind:
estimateDistinctSyncfor an iterable you already
hold,estimateDistinctfor an async iterable or aReadable. Each refuses
the other's sources and names the one to call, and the synchronous one takes no
signal. keyFn's parameter follows the element type of the source, so over anOrder[]
a wrong field is a compile error where it used to beany. TypeScript only:
the runtime is unchanged.DistinctEstimateStreamis replaced bycreateEstimatorSink(estimator, options).
The count is read from the estimator, so the sink has noresult().expectedSizeis required and must be at least 1. A snapshot written by 0.4
without it carries 0 and no longer restores.
Fixed
- An abort lands while the source is waiting, not only between values.
- A signal already aborted before the call rejects with
ABORT_ERRon a
Readablesource too, instead of Node 24'sERR_STREAM_UNABLE_TO_PIPE. createRandomthrows withCVM_INVALID_OPTIONon a bad seed, the one error
that carried no code.
The hand-written types are now typechecked as a consumer compiles them, in CI and
before publishing.
v0.4.0
toJSON hands over an estimator's state as a plain object and the static
fromJSON rebuilds one from it, so a long count survives a restart. Both APIs
now take an AbortSignal. Every error the library raises about its own use
carries a code (CVM_INVALID_OPTION, CVM_INVALID_SOURCE,
CVM_INVALID_SNAPSHOT, CVM_UNSERIALIZABLE_VALUE), while an aborted signal
follows Node with ABORT_ERR.
keyFn is now typed to the values a Set dedups and JSON returns unchanged:
a string, number, boolean or null. TypeScript users passing an optional
field will see an error where a missing value used to be counted silently as
one distinct value.
v0.3.1
Fixes an out-of-memory crash in estimateDistinct when the source is a Readable
stream. A Readable that hands over one value per read, Readable.from included,
left the loop holding one pending callback per value until the heap ran out. The
promise API now pipes those sources instead of iterating them.
Only estimateDistinct with a Readable was affected. The stream API, the core
CVM, and array or async-iterable sources were not.
v0.3.0
On skewed streams the delete branch churns on the same hot keys, and V8
keeps deleted entries in the Set's bucket chains until the table is
rebuilt, so lookups walk ever-longer chains. This copies the set once
holes pile up past max(|X|, 1024): membership, order and randomness are
untouched, so same-seed results are bit-identical to 0.2.0.
The bench gains a skewed scenario and a below-threshold one, and the
README a "stream shape" table that also says where the estimator buys
nothing over a plain Set. npm run bench reproduces the numbers.
v0.2.0
add()now follows Algorithm 3's branching exactly (insert with probability
p, remove otherwise) instead of an equivalent reformulation, cutting hash
operations in half per element. Up to ~50% faster while the sample stays
exact, ~7% faster once sub-sampling engages.- Benchmark output and README tables now label results "faircount" instead of
"CVM": the numbers measure this implementation, not the abstract algorithm.
v0.1.1
v0.1.0
First public release.
- Estimates the number of distinct values (F0) in a stream with bounded memory and (ε, δ) guarantees.
- Implements the total, unbiased CVM variant (Karayel, Watt, Khu, Meel & Tan, ITP 2025, Algorithm 3): it never fails, and the estimate's expected value is exactly the true count.
- APIs:
estimateDistinct(promise),DistinctEstimateStream(Writable sink),CVM(pure core), pluscomputeThresholdandcreateRandom. - ESM-only, zero runtime dependencies, hand-written TypeScript types, Node ≥ 18.