@timekeeper-countdown/core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +231 -2
  2. package/package.json +3 -2
package/README.md CHANGED
@@ -1,3 +1,232 @@
1
- # Timekeeper Countdown Core
1
+ # @timekeeper-countdown/core
2
2
 
3
- This package contains the framework-agnostic countdown engine that backs the `timekeeper-countdown` distribution. For full documentation, usage guides, and examples, see the root [README](../../README.md).
3
+ 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.
4
+
5
+ - Written in TypeScript with zero runtime dependencies.
6
+ - Ships modern ESM bundles and type definitions.
7
+ - Designed to run in browsers, Node.js, or custom runtimes.
8
+ - Tested with deterministic fake timers for reliable behavior.
9
+
10
+ > **Looking for the React hook?** Install [`@timekeeper-countdown/react`](https://www.npmjs.com/package/@timekeeper-countdown/react) for an idiomatic React API that wraps this engine.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @timekeeper-countdown/core
18
+ ```
19
+
20
+ The published bundle is pure ESM. When targeting CommonJS environments use a bundler that understands `type: "module"` packages.
21
+
22
+ Supported runtimes:
23
+
24
+ - Node.js 18+
25
+ - Modern browsers (ES2022 modules)
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ### High-level helper (`Countdown`)
32
+
33
+ ```ts
34
+ import { Countdown, TimerState } from '@timekeeper-countdown/core';
35
+
36
+ const countdown = Countdown(300, {
37
+ onUpdate: (minutes, seconds) => {
38
+ timerElement.textContent = `${minutes}:${seconds}`;
39
+ },
40
+ onStateChange: state => {
41
+ if (state === TimerState.STOPPED) {
42
+ console.log('Finished!');
43
+ }
44
+ },
45
+ });
46
+
47
+ countdown.start(); // Begin the countdown
48
+ ```
49
+
50
+ `Countdown` wraps the lower-level engine and returns convenient methods:
51
+
52
+ ```ts
53
+ countdown.start(); // boolean (false when invalid transition)
54
+ countdown.pause();
55
+ countdown.resume();
56
+ countdown.reset(nextInitialSeconds?);
57
+ countdown.stop();
58
+ countdown.destroy(); // dispose timers and listeners
59
+
60
+ countdown.getSnapshot(); // CountdownSnapshot
61
+ countdown.getCurrentState(); // TimerState
62
+ countdown.getMinutes(); // string e.g. "05"
63
+ ```
64
+
65
+ ### Low-level engine (`CountdownEngine`)
66
+
67
+ ```ts
68
+ import { CountdownEngine, TimerState } from '@timekeeper-countdown/core';
69
+
70
+ const engine = CountdownEngine(90, {
71
+ tickIntervalMs: 50,
72
+ onSnapshot: snapshot => {
73
+ console.log(snapshot.totalSeconds);
74
+ },
75
+ onStateChange: (state, snapshot) => {
76
+ if (state === TimerState.STOPPED && snapshot.isCompleted) {
77
+ console.log('Done!');
78
+ }
79
+ },
80
+ onError: error => {
81
+ console.error('Timer failure', error);
82
+ },
83
+ });
84
+
85
+ engine.start();
86
+ ```
87
+
88
+ `CountdownEngine` exposes fine-grained control:
89
+
90
+ - `start`, `pause`, `resume`, `reset(nextInitialSeconds?)`, `stop`, `setSeconds(value)`, `destroy`
91
+ - `getSnapshot()` returns the latest snapshot.
92
+ - `subscribe(listener)` emits the current snapshot immediately and on every tick.
93
+
94
+ Snapshot structure:
95
+
96
+ ```ts
97
+ interface CountdownSnapshot {
98
+ initialSeconds: number; // starting value
99
+ totalSeconds: number; // remaining seconds, floor-clamped
100
+ parts: {
101
+ years: number;
102
+ weeks: number;
103
+ days: number;
104
+ hours: number;
105
+ minutes: number;
106
+ seconds: number;
107
+ totalDays: number;
108
+ totalHours: number;
109
+ totalMinutes: number;
110
+ };
111
+ state: TimerState; // IDLE | RUNNING | PAUSED | STOPPED
112
+ isRunning: boolean;
113
+ isCompleted: boolean;
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Formatting Helpers
120
+
121
+ Use the helpers exported at `@timekeeper-countdown/core/format` to avoid reimplementing `padStart` logic.
122
+
123
+ ```ts
124
+ import { formatTime, formatMinutes, formatSeconds, Formatter } from '@timekeeper-countdown/core/format';
125
+
126
+ const snapshot = engine.getSnapshot();
127
+
128
+ formatTime(snapshot); // { minutes: "01", seconds: "30" }
129
+ formatMinutes(snapshot); // "01"
130
+
131
+ const formatter = Formatter();
132
+ formatter.formatHours(snapshot); // memoised string helpers
133
+ ```
134
+
135
+ All helpers accept either a snapshot or any object that exposes `totalSeconds`.
136
+
137
+ ---
138
+
139
+ ## Testing Utilities
140
+
141
+ The package includes utilities under `@timekeeper-countdown/core/testing-utils` to make unit tests deterministic.
142
+
143
+ ```ts
144
+ import {
145
+ createFakeTimeProvider,
146
+ toTimeProvider,
147
+ buildSnapshot,
148
+ assertSnapshotState,
149
+ } from '@timekeeper-countdown/core/testing-utils';
150
+
151
+ const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
152
+ const engine = CountdownEngine(5, {
153
+ timeProvider: toTimeProvider(fake),
154
+ tickIntervalMs: 5,
155
+ });
156
+
157
+ engine.start();
158
+ fake.advance(3000);
159
+
160
+ expect(engine.getSnapshot().totalSeconds).toBe(2);
161
+
162
+ const snapshot = buildSnapshot({ totalSeconds: 42 });
163
+ assertSnapshotState(snapshot, 'RUNNING');
164
+ ```
165
+
166
+ Utilities include:
167
+
168
+ - `createFakeTimeProvider` / `toTimeProvider` for manual clock control.
169
+ - `buildSnapshot`, `assertSnapshotState` helpers for quick snapshot fabrication.
170
+
171
+ ---
172
+
173
+ ## Custom Time Providers
174
+
175
+ `CountdownEngine` accepts either:
176
+
177
+ - A function: `() => number` returning milliseconds.
178
+ - An object implementing the `TimeProvider` interface: `{ now(): number; isHighResolution: boolean; type: string }`.
179
+
180
+ This allows plugging in custom schedulers or synchronizing multiple engines.
181
+
182
+ ```ts
183
+ const provider = {
184
+ now: () => performance.now(),
185
+ isHighResolution: true,
186
+ type: 'custom',
187
+ };
188
+ CountdownEngine(60, { timeProvider: provider });
189
+ ```
190
+
191
+ ---
192
+
193
+ ## TypeScript Support
194
+
195
+ All exports are fully typed. Useful entry points:
196
+
197
+ ```ts
198
+ import type {
199
+ CountdownSnapshot,
200
+ CountdownEngineOptions,
201
+ CountdownEngineInstance,
202
+ TimerState,
203
+ } from '@timekeeper-countdown/core';
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Documentation & Examples
209
+
210
+ - Monorepo overview: [GitHub repository](https://github.com/eagle-head/timekeeper-countdown)
211
+ - React hook guide: [`@timekeeper-countdown/react` README](https://github.com/eagle-head/timekeeper-countdown/tree/main/packages/react#readme)
212
+ - Complete docs site (guides, API reference, roadmap): <https://eagle-head.github.io/timekeeper-countdown/>
213
+
214
+ ---
215
+
216
+ ## Contributing
217
+
218
+ Issues and pull requests are welcome. Please review the [repository guidelines](https://github.com/eagle-head/timekeeper-countdown/blob/main/AGENTS.md) for more details and run:
219
+
220
+ ```bash
221
+ npm run lint --workspaces
222
+ npm run test --workspaces
223
+ npm run typecheck --workspaces
224
+ ```
225
+
226
+ before submitting changes.
227
+
228
+ ---
229
+
230
+ ## License
231
+
232
+ MIT © [Eduardo Kohn](https://www.linkedin.com/in/eduardo-kohn-56817b195/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekeeper-countdown/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Lightweight countdown engine that powers the Timekeeper packages. Pure TypeScript with no runtime dependencies.",
5
5
  "keywords": [
6
6
  "countdown",
@@ -41,7 +41,8 @@
41
41
  }
42
42
  },
43
43
  "files": [
44
- "dist"
44
+ "dist",
45
+ "README.md"
45
46
  ],
46
47
  "sideEffects": false,
47
48
  "scripts": {