npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@timekeeper-countdown/core

v0.1.1

Published

Lightweight countdown engine that powers the Timekeeper packages. Pure TypeScript with no runtime dependencies.

Readme

@timekeeper-countdown/core

Lightweight countdown engine used by Timekeeper Countdown. This package exposes the finite-state machine, snapshot APIs, formatting helpers, and testing utilities that power the React hook and future framework adapters.

  • Written in TypeScript with zero runtime dependencies.
  • Ships modern ESM bundles and type definitions.
  • Designed to run in browsers, Node.js, or custom runtimes.
  • Tested with deterministic fake timers for reliable behavior.

Looking for the React hook? Install @timekeeper-countdown/react for an idiomatic React API that wraps this engine.


Installation

npm install @timekeeper-countdown/core

The published bundle is pure ESM. When targeting CommonJS environments use a bundler that understands type: "module" packages.

Supported runtimes:

  • Node.js 18+
  • Modern browsers (ES2022 modules)

Quick Start

High-level helper (Countdown)

import { Countdown, TimerState } from '@timekeeper-countdown/core';

const countdown = Countdown(300, {
  onUpdate: (minutes, seconds) => {
    timerElement.textContent = `${minutes}:${seconds}`;
  },
  onStateChange: state => {
    if (state === TimerState.STOPPED) {
      console.log('Finished!');
    }
  },
});

countdown.start(); // Begin the countdown

Countdown wraps the lower-level engine and returns convenient methods:

countdown.start();                   // boolean (false when invalid transition)
countdown.pause();
countdown.resume();
countdown.reset(nextInitialSeconds?);
countdown.stop();
countdown.destroy();                 // dispose timers and listeners

countdown.getSnapshot();             // CountdownSnapshot
countdown.getCurrentState();         // TimerState
countdown.getMinutes();              // string e.g. "05"

Low-level engine (CountdownEngine)

import { CountdownEngine, TimerState } from '@timekeeper-countdown/core';

const engine = CountdownEngine(90, {
  tickIntervalMs: 50,
  onSnapshot: snapshot => {
    console.log(snapshot.totalSeconds);
  },
  onStateChange: (state, snapshot) => {
    if (state === TimerState.STOPPED && snapshot.isCompleted) {
      console.log('Done!');
    }
  },
  onError: error => {
    console.error('Timer failure', error);
  },
});

engine.start();

CountdownEngine exposes fine-grained control:

  • start, pause, resume, reset(nextInitialSeconds?), stop, setSeconds(value), destroy
  • getSnapshot() returns the latest snapshot.
  • subscribe(listener) emits the current snapshot immediately and on every tick.

Snapshot structure:

interface CountdownSnapshot {
  initialSeconds: number; // starting value
  totalSeconds: number; // remaining seconds, floor-clamped
  parts: {
    years: number;
    weeks: number;
    days: number;
    hours: number;
    minutes: number;
    seconds: number;
    totalDays: number;
    totalHours: number;
    totalMinutes: number;
  };
  state: TimerState; // IDLE | RUNNING | PAUSED | STOPPED
  isRunning: boolean;
  isCompleted: boolean;
}

Formatting Helpers

Use the helpers exported at @timekeeper-countdown/core/format to avoid reimplementing padStart logic.

import { formatTime, formatMinutes, formatSeconds, Formatter } from '@timekeeper-countdown/core/format';

const snapshot = engine.getSnapshot();

formatTime(snapshot); // { minutes: "01", seconds: "30" }
formatMinutes(snapshot); // "01"

const formatter = Formatter();
formatter.formatHours(snapshot); // memoised string helpers

All helpers accept either a snapshot or any object that exposes totalSeconds.


Testing Utilities

The package includes utilities under @timekeeper-countdown/core/testing-utils to make unit tests deterministic.

import {
  createFakeTimeProvider,
  toTimeProvider,
  buildSnapshot,
  assertSnapshotState,
} from '@timekeeper-countdown/core/testing-utils';

const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
const engine = CountdownEngine(5, {
  timeProvider: toTimeProvider(fake),
  tickIntervalMs: 5,
});

engine.start();
fake.advance(3000);

expect(engine.getSnapshot().totalSeconds).toBe(2);

const snapshot = buildSnapshot({ totalSeconds: 42 });
assertSnapshotState(snapshot, 'RUNNING');

Utilities include:

  • createFakeTimeProvider / toTimeProvider for manual clock control.
  • buildSnapshot, assertSnapshotState helpers for quick snapshot fabrication.

Custom Time Providers

CountdownEngine accepts either:

  • A function: () => number returning milliseconds.
  • An object implementing the TimeProvider interface: { now(): number; isHighResolution: boolean; type: string }.

This allows plugging in custom schedulers or synchronizing multiple engines.

const provider = {
  now: () => performance.now(),
  isHighResolution: true,
  type: 'custom',
};
CountdownEngine(60, { timeProvider: provider });

TypeScript Support

All exports are fully typed. Useful entry points:

import type {
  CountdownSnapshot,
  CountdownEngineOptions,
  CountdownEngineInstance,
  TimerState,
} from '@timekeeper-countdown/core';

Documentation & Examples


Contributing

Issues and pull requests are welcome. Please review the repository guidelines for more details and run:

npm run lint --workspaces
npm run test --workspaces
npm run typecheck --workspaces

before submitting changes.


License

MIT © Eduardo Kohn