JSPM

@cch137/every

0.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 18
  • Score
    100M100P100Q71548F
  • License MIT

Zero-dependency, wall-clock-aligned interval scheduler — every("5m", fn).

Package Exports

  • @cch137/every

Readme

@cch137/every

Zero-dependency, wall-clock-aligned interval scheduler. One verb — every — schedules recurring work on clock boundaries (:00, :05, :10, …) instead of relative to when you subscribed. A single timer sleeps until the next boundary (no per-second polling) and re-arms from Date.now() each tick, so it self-corrects for drift. Runs on Node ≥ 20, Deno, and edge runtimes.

See DESIGN.md for goals, non-goals, and engine details.

Install

npm i @cch137/every
import { every, everyMinute, createClock } from "@cch137/every";
// Deno: import { every } from "npm:@cch137/every";

Usage

import { every, everySecond, everyMinute, everyHour, everyDay } from "@cch137/every";

// Generic — interval as a duration string, ms number, or { minutes: 5 }.
const stop = every("5m", () => syncStuff());
stop(); // unsubscribe

// Named shortcuts for the common units.
everyMinute((tick) => console.log(tick.seq, tick.t));

// Options: dispatch mode, overlap guard, error handling, alignment.
every("10s", doWork, {
  preventOverlap: true,            // skip if the previous async run is still going
  onError: (err) => log.warn(err), // isolate handler errors
});

interval accepts 500ms / 30s / 5m / 2h / 1d / 1h30m, a number of milliseconds, or { days, hours, minutes, seconds, ms }.

Heartbeat (onTick)

onTick fires tps times per second (default 1) — sugar for every at the 1000 / tps ms step, same handler and options as the other helpers:

const clock = createClock({ tps: 10 }); // 10 ticks/sec
clock.onTick((tick) => step(tick.delta), { preventOverlap: true });

Pick a tps that divides 1000 evenly (2, 4, 5, 10, 20, 50, 100 …) for clean boundaries.

Isolated clocks

The top-level helpers are bound to a shared default clock. For tests, multiple independent schedules, or to wire in latency reporting, create your own:

import { createClock } from "@cch137/every";

const clock = createClock({
  onLatency: (info) => log.warn(`clock late by ${info.lateBy}ms`),
});

clock.every("2h", purge);
clock.everyDay(rollup);   // aligned to UTC midnight
clock.stop();             // pause; subscriptions resume on start()

createClock uses autoStart by default: the first subscription starts the timer, the last to leave stops it, so an idle clock keeps no timer and never holds the process open on its own.

Behavior

  • Alignment — default align: "wall" fires on epoch multiples of the interval. align: "relative" behaves like setInterval (interval from registration). everyDay / "1d" align to UTC midnight.
  • Coalescing — if the loop is delayed past several boundaries (sleep, blocked event loop), each subscription fires once and re-anchors; missed boundaries are not replayed.
  • Latency — pass onLatency to createClock to observe late wake-ups (default threshold 1500ms).

License

MIT