Skip to content

Commit fba3f5d

Browse files
committed
refactor(timer): split optional runtime entrypoints
1 parent 8513d6c commit fba3f5d

34 files changed

+759
-400
lines changed

README.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# @crup/react-timer-hook
22

3-
> React timer hooks for countdowns, stopwatches, clocks, polling schedules, and many independent timer lifecycles.
3+
> Tiny React timer primitives for countdowns, stopwatches, clocks, polling schedules, and many independent timer lifecycles.
44
55
[![npm alpha](https://img.shields.io/npm/v/%40crup%2Freact-timer-hook/alpha?label=npm%20alpha&color=00b894)](https://www.npmjs.com/package/@crup/react-timer-hook?activeTab=versions)
66
[![npm downloads](https://img.shields.io/npm/dm/%40crup%2Freact-timer-hook?color=0f766e)](https://www.npmjs.com/package/@crup/react-timer-hook)
@@ -17,11 +17,12 @@
1717

1818
Timer hooks look simple until real apps need pause/resume semantics, Strict Mode cleanup, async callbacks, polling that does not overlap, and lists with dozens of independent timers.
1919

20-
`@crup/react-timer-hook` keeps the API small and lets your app decide what time means:
20+
`@crup/react-timer-hook` keeps the root import small and lets your app opt into heavier features only when needed:
2121

22-
- ⏱️ `useTimer()` for one lifecycle: stopwatch, countdown, clock, schedule, or custom flow.
23-
- 🧭 `useTimerGroup()` for many keyed lifecycles with one shared scheduler.
24-
- 🧩 `durationParts()` for display math without locale or timezone opinions.
22+
- ⏱️ `useTimer()` from the root package for one lifecycle: stopwatch, countdown, clock, or custom flow.
23+
- 🧭 `useTimerGroup()` from `/group` for many keyed lifecycles with one shared scheduler.
24+
- 📡 `useScheduledTimer()` from `/schedules` for polling, overdue timing context, and opt-in diagnostics.
25+
- 🧩 `durationParts()` from `/duration` for display math without locale or timezone opinions.
2526
- 🧼 No formatting, timezone, audio, retry, cache, or data-fetching policy baked in.
2627
- 🧪 Built for rerenders, Strict Mode, async callbacks, cleanup, and many timers.
2728
- 🤖 Agent-friendly docs through hosted `llms.txt`, `llms-full.txt`, and an optional MCP docs helper.
@@ -36,7 +37,13 @@ pnpm add @crup/react-timer-hook@alpha
3637
```
3738

3839
```tsx
39-
import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
40+
import { useTimer } from '@crup/react-timer-hook';
41+
import { durationParts } from '@crup/react-timer-hook/duration';
42+
import { useTimerGroup } from '@crup/react-timer-hook/group';
43+
import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
44+
45+
// Or choose convenience over minimum entry size:
46+
import { durationParts, useScheduledTimer, useTimer, useTimerGroup } from '@crup/react-timer-hook/full';
4047
```
4148

4249
## Live recipes
@@ -99,7 +106,9 @@ export function AuctionTimer({ auctionId, expiresAt }: {
99106
Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
100107

101108
```tsx
102-
const timer = useTimer({
109+
import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
110+
111+
const timer = useScheduledTimer({
103112
autoStart: true,
104113
updateIntervalMs: 1000,
105114
endWhen: snapshot => snapshot.now >= expiresAt,
@@ -123,6 +132,8 @@ const timer = useTimer({
123132
Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
124133

125134
```tsx
135+
import { useTimerGroup } from '@crup/react-timer-hook/group';
136+
126137
const timers = useTimerGroup({
127138
updateIntervalMs: 1000,
128139
items: auctions.map(auction => ({
@@ -138,11 +149,14 @@ const timers = useTimerGroup({
138149

139150
Current build:
140151

141-
| File | Raw | Gzip | Brotli |
152+
| Entry | Raw | Gzip | Brotli |
142153
| --- | ---: | ---: | ---: |
143-
| `dist/index.js` | 12.80 kB | 3.88 kB | 3.47 kB |
144-
| `dist/index.cjs` | 14.04 kB | 4.12 kB | 3.70 kB |
145-
| `dist/index.d.ts` | 4.32 kB | 1.04 kB | 951 B |
154+
| core | 3.91 kB | 1.34 kB | 1.23 kB |
155+
| full | 15.03 kB | 4.69 kB | 4.18 kB |
156+
| timer group add-on | 9.05 kB | 3.01 kB | 2.74 kB |
157+
| schedules add-on | 6.99 kB | 2.35 kB | 2.16 kB |
158+
| duration helper | 374 B | 262 B | 229 B |
159+
| diagnostics helper | 156 B | 155 B | 139 B |
146160

147161
CI writes a size summary to the GitHub Actions UI and posts bundle-size reports on pull requests.
148162

docs-site/docs/api/types.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type DurationParts = {
2020
```
2121

2222
```ts
23-
import { durationParts } from '@crup/react-timer-hook';
23+
import { durationParts } from '@crup/react-timer-hook/duration';
2424

2525
const parts = durationParts(90_500);
2626
```
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: useScheduledTimer
3+
description: Optional schedule-enabled timer API for polling and timing context.
4+
---
5+
6+
# useScheduledTimer
7+
8+
```ts
9+
import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
10+
```
11+
12+
`useScheduledTimer()` includes the lifecycle API from `useTimer()` plus schedule callbacks and opt-in debug events. It lives in a subpath so the root import stays small for clocks, stopwatches, and countdowns that do not need polling.
13+
14+
```ts
15+
type UseScheduledTimerOptions = UseTimerOptions & {
16+
schedules?: TimerSchedule[];
17+
debug?: TimerDebug;
18+
};
19+
```
20+
21+
Schedules run while active and default to `overlap: 'skip'`.
22+
23+
```ts
24+
const timer = useScheduledTimer({
25+
autoStart: true,
26+
schedules: [
27+
{
28+
id: 'poll',
29+
everyMs: 5000,
30+
overlap: 'skip',
31+
callback: async (snapshot, controls, context) => {
32+
console.log(context.overdueCount);
33+
},
34+
},
35+
],
36+
});
37+
```
38+
39+
The third callback argument contains `scheduledAt`, `firedAt`, `nextRunAt`, `overdueCount`, and `effectiveEveryMs`.

docs-site/docs/api/use-timer-group.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ description: API reference for many keyed independent timer lifecycles driven by
66
# useTimerGroup
77

88
```ts
9+
import { useTimerGroup } from '@crup/react-timer-hook/group';
10+
911
function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
1012
```
1113

1214
Use `useTimerGroup()` when every row needs independent pause, resume, cancel, restart, schedules, or `onEnd`.
1315

14-
Item schedules use the same `TimerSchedule` contract as `useTimer()`, including the third schedule context argument for intended fire time, actual fire time, next run time, and overdue interval count.
16+
Item schedules use the same `TimerSchedule` contract as `useScheduledTimer()`, including the third schedule context argument for intended fire time, actual fire time, next run time, and overdue interval count.
1517

1618
```ts
1719
type UseTimerGroupOptions = {

docs-site/docs/api/use-timer.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ type UseTimerOptions = {
1717
updateIntervalMs?: number;
1818
endWhen?: (snapshot: TimerSnapshot) => boolean;
1919
onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
20-
schedules?: TimerSchedule[];
21-
debug?: TimerDebug;
2220
};
2321
```
2422

@@ -28,7 +26,7 @@ type UseTimerOptions = {
2826

2927
`onEnd` fires once per generation. `restart()` creates a new generation.
3028

31-
Schedules run while active and default to `overlap: 'skip'`. The schedule callback receives a third `context` argument with `scheduledAt`, `firedAt`, `nextRunAt`, `overdueCount`, and `effectiveEveryMs`, so delayed browser timers can be measured without exposing timeout handles.
29+
The root `useTimer()` export is lifecycle-only to keep the default bundle small. Use `@crup/react-timer-hook/schedules` when you need polling schedules and schedule timing context.
3230

3331
## Controls
3432

docs-site/docs/getting-started.mdx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,24 @@ pnpm add @crup/react-timer-hook@alpha
1313
```
1414

1515
```tsx
16-
import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
16+
import { useTimer } from '@crup/react-timer-hook';
17+
import { durationParts } from '@crup/react-timer-hook/duration';
18+
import { useTimerGroup } from '@crup/react-timer-hook/group';
19+
import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
1720
import type { TimerSnapshot } from '@crup/react-timer-hook';
1821
```
1922

23+
Prefer one convenience import over the smallest entrypoints?
24+
25+
```tsx
26+
import { durationParts, useScheduledTimer, useTimer, useTimerGroup } from '@crup/react-timer-hook/full';
27+
```
28+
2029
## First timer
2130

2231
```tsx
23-
import { durationParts, useTimer } from '@crup/react-timer-hook';
32+
import { useTimer } from '@crup/react-timer-hook';
33+
import { durationParts } from '@crup/react-timer-hook/duration';
2434

2535
export function BreakTimer() {
2636
const durationMs = 5 * 60 * 1000;

docs-site/docs/index.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { StopwatchSample, AbsoluteCountdownSample, TimerGroupSample } from '../s
99

1010
# React timer hooks
1111

12-
`@crup/react-timer-hook` gives React apps a small timer lifecycle primitive instead of a formatting-heavy timer component.
12+
`@crup/react-timer-hook` gives React apps a small root timer primitive and optional subpath hooks for schedules, groups, diagnostics, and duration helpers.
1313

1414
It is built for Strict Mode, rerenders, async callbacks, cleanup, and pages with many independent timers.
1515

@@ -35,8 +35,9 @@ pnpm add @crup/react-timer-hook@alpha
3535
## Public API
3636

3737
- `useTimer()` for one lifecycle
38-
- `useTimerGroup()` for many keyed independent lifecycles
39-
- `durationParts()` for duration display math
38+
- `useScheduledTimer()` from `/schedules` for polling and schedule timing context
39+
- `useTimerGroup()` from `/group` for many keyed independent lifecycles
40+
- `durationParts()` from `/duration` for duration display math
4041

4142
The library owns scheduling and cleanup. Your app owns formatting, timezone, data fetching policy, and business rules.
4243

docs-site/docs/project/debug-logs.mdx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ description: Opt-in semantic diagnostics for timer lifecycles and schedules.
88
Debug logs are off by default.
99

1010
```tsx
11-
useTimer({
11+
import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
12+
import { consoleTimerDiagnostics } from '@crup/react-timer-hook/diagnostics';
13+
14+
useScheduledTimer({
1215
autoStart: true,
13-
debug: {
14-
label: 'auction-card',
15-
includeTicks: false,
16-
logger: event => console.debug('[timer]', event),
17-
},
16+
debug: consoleTimerDiagnostics({ label: 'auction-card' }),
1817
});
1918
```
2019

docs-site/docs/recipes/advanced/per-item-polling.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ Each item can define schedules with different cadence and callbacks.
1515
</RecipePlayground>
1616

1717
```tsx
18+
import { useTimerGroup } from '@crup/react-timer-hook/group';
19+
1820
items: jobs.map(job => ({
1921
id: job.id,
2022
autoStart: true,
2123
schedules: [{ id: 'status', everyMs: job.pollMs, callback: refresh }],
2224
}))
2325
```
24-

docs-site/docs/recipes/advanced/timer-group.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Use `useTimerGroup()` when every row needs its own pause, resume, restart, cance
1515
</RecipePlayground>
1616

1717
```tsx
18+
import { useTimerGroup } from '@crup/react-timer-hook/group';
19+
1820
const timers = useTimerGroup({
1921
items: jobs.map(job => ({
2022
id: job.id,
@@ -23,4 +25,3 @@ const timers = useTimerGroup({
2325
})),
2426
});
2527
```
26-

0 commit comments

Comments
 (0)