01 · Problem
What is it for?
Statistics become stale if cleanup happens only when a new event is logged.
02 · Mental model
How should you think about it?
Store event timestamps, inject a monotonic clock and evaluate the same window boundary on writes and reads.
Measures sliding-window event counts with an injectable monotonic clock. Counts cover the configured window, not all history. Memory scales with event volume in that window.
03 · Control flow
What happens in which order?
- Read the current clock.
- Append a typed event.
- Discard expired timestamps.
- Count matching events.
- Convert the count using the documented window duration.
04 · Building blocks
The important contracts
logEventAppend an event using the injected clock.getCount / getRatePerMinuteRead a fresh window count or normalized rate.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Read the current clock.
- Append a typed event.
- Discard expired timestamps.
- Count matching events.
- Convert the count using the documented window duration.
class WindowCounter {
private points: number[] = [];
constructor(private windowMs: number, private now = Date.now) {}
add() { this.points.push(this.now()); }
count() {
const cutoff = this.now() - this.windowMs;
while (this.points[0] < cutoff) this.points.shift();
return this.points.length;
}
}06 · Verify
What should you test?
- Advance a fake clock without writing new events.
- Test exact expiry boundaries.
- Verify counts and rates for different event types.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Advance a fake clock without writing new events.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Memory grows with event volume.
- Window counts are not lifetime totals.
- Distributed rates require clock and aggregation policies.
Counts cover the configured window, not all history. Memory scales with event volume in that window.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/stats-tracker/examples/basic.ts