DeutschGitHub
partialpackages/stats-tracker/src

Stats Tracker

@ralphschuler/stats-tracker

Measures sliding-window event counts with an injectable monotonic clock.

eventssliding windowmetrics

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.

In this repository

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?

  1. Read the current clock.
  2. Append a typed event.
  3. Discard expired timestamps.
  4. Count matching events.
  5. 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.

  1. Read the current clock.
  2. Append a typed event.
  3. Discard expired timestamps.
  4. Count matching events.
  5. Convert the count using the documented window duration.
Concept sketch · may reference omitted helpers
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.
Important

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