@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.
- package/README.md +231 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,3 +1,232 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @timekeeper-countdown/core
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
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": {
|