@hardlydifficult/poller 1.0.7 → 1.0.9

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 +41 -206
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hardlydifficult/poller
2
2
 
3
- A generic polling utility that periodically fetches data, detects changes via deep equality comparison, and supports debounced manual triggers with overlap handling.
3
+ A lightweight polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,246 +13,81 @@ npm install @hardlydifficult/poller
13
13
  ```typescript
14
14
  import { Poller } from "@hardlydifficult/poller";
15
15
 
16
- const poller = new Poller(
17
- async () => {
18
- const res = await fetch("https://api.example.com/status");
19
- return res.json();
20
- },
21
- (current, previous) => {
22
- console.log("Status changed:", current);
23
- },
24
- 5000 // poll every 5 seconds
25
- );
26
-
27
- await poller.start();
28
- // Polls start immediately
16
+ const fetchFn = async () => {
17
+ const response = await fetch("https://api.example.com/data");
18
+ return response.json();
19
+ };
29
20
 
30
- // Optionally, manually trigger a debounced poll
31
- poller.trigger(1000); // fires once after 1s of inactivity
21
+ const onChange = (current: unknown, previous: unknown) => {
22
+ console.log("Data changed:", current);
23
+ };
32
24
 
33
- // Stop polling when done
34
- poller.stop();
35
- ```
36
-
37
- ### Basic Usage
38
-
39
- ```typescript
40
- import { Poller } from "@hardlydifficult/poller";
41
-
42
- const poller = new Poller(
43
- async () => await fetchCurrentState(),
44
- (current, previous) => console.log("State changed!", current),
45
- 5 * 60 * 1000 // poll every 5 minutes
46
- );
25
+ const poller = new Poller(fetchFn, onChange, 5000);
47
26
 
48
27
  await poller.start();
28
+ // => Starts polling every 5 seconds, firing onChange on first fetch and any change
49
29
 
50
- // Manual trigger with debounce (e.g. from a webhook)
51
30
  poller.trigger(1000);
52
-
53
- // Stop polling
54
- poller.stop();
31
+ // => Triggers a debounced poll after 1 second (overriding any previous trigger call)
55
32
  ```
56
33
 
57
- ## Polling Behavior
58
-
59
- The `Poller` executes a fetch function at a fixed interval and invokes a callback only when the returned value changes.
34
+ ## Poller Class
60
35
 
61
- - Starts polling immediately on `start()`
62
- - Fetches at the configured `intervalMs`
63
- - Skips overlapping fetches (if a previous fetch is still in progress)
36
+ A generic polling utility that periodically fetches data and invokes a callback when the result changes.
64
37
 
65
- ```typescript
66
- const poller = new Poller(
67
- async () => fetchJson("/api/data"),
68
- (current, previous) => console.log("Changed:", current),
69
- 10_000
70
- );
71
- await poller.start();
72
- ```
38
+ ### Constructor
73
39
 
74
- ## Change Detection
40
+ | Parameter | Type | Description |
41
+ |--------------|-----------------------------------|----------------------------------------------|
42
+ | `fetchFn` | `() => Promise<T>` | Async function that fetches the latest data |
43
+ | `onChange` | `(current: T, previous: T | undefined) => void` | Callback invoked when data changes |
44
+ | `intervalMs` | `number` | Polling interval in milliseconds |
45
+ | `onError?` | `(error: unknown) => void` | Optional callback invoked on fetch errors |
75
46
 
76
- Changes are detected using deep equality via `JSON.stringify`. Structural equality means that objects with identical content—even different references—won’t trigger unnecessary callbacks.
77
-
78
- ```typescript
79
- const poller = new Poller(
80
- async () => ({ items: [1, 2, 3] }),
81
- (current, previous) => console.log("Changed:", current),
82
- 1000
83
- );
47
+ ### Methods
84
48
 
85
- // Even if a new object instance is returned, same structure = no change
86
- await poller.start();
87
- // onChange fires only when structure changes
88
- ```
49
+ #### `start(): Promise<void>`
89
50
 
90
- - On first poll, `onChange(current, undefined)` is invoked.
91
- - On subsequent polls, `onChange(current, previous)` is invoked only if the new value differs structurally.
51
+ Starts polling at the configured interval. Does nothing if already running.
92
52
 
93
53
  ```typescript
94
- const poller = new Poller(
95
- async () => ({ count: 5, items: [1, 2, 3] }),
96
- (current, previous) => {
97
- // Only fires when the JSON representation changes
98
- console.log("Changed from", previous, "to", current);
99
- },
100
- 5000
101
- );
102
-
103
54
  await poller.start();
55
+ // => Begins polling; invokes onChange immediately on first fetch
104
56
  ```
105
57
 
106
- ## Lifecycle Management
107
-
108
- Polling can be started and stopped at any time. The `stop()` method cancels both the periodic timer and any pending debounced triggers.
109
-
110
- ```typescript
111
- await poller.start(); // begins polling
112
- poller.stop(); // cancels all timers, stops future polls
113
- ```
114
-
115
- ### `start()`
116
-
117
- Starts polling. Fetches immediately, then at the configured interval. Calling `start()` multiple times is safe (idempotent).
118
-
119
- ```typescript
120
- await poller.start(); // Fetches immediately
121
- await poller.start(); // No-op, already running
122
- ```
123
-
124
- ### `stop()`
58
+ #### `stop(): void`
125
59
 
126
- Stops polling and cleans up all timers.
60
+ Stops polling and clears all timers (including any pending debounced trigger).
127
61
 
128
62
  ```typescript
129
63
  poller.stop();
130
- // No more polls will fire
64
+ // => Immediately halts polling and cancels pending triggers
131
65
  ```
132
66
 
133
- ## Manual Triggers
67
+ #### `trigger(debounceMs = 1000): void`
134
68
 
135
- You can manually trigger a debounced poll to force an immediate check after a period of inactivity.
136
-
137
- | Parameter | Type | Default | Description |
138
- |-------------|----------|---------|----------------------------------------|
139
- | `debounceMs`| `number` | `1000` | Milliseconds to wait before polling |
69
+ Triggers a debounced poll. Multiple rapid calls reset the debounce timer.
140
70
 
141
71
  ```typescript
142
- // Immediate trigger with default debounce
143
- poller.trigger();
144
-
145
- // Custom debounce
146
- poller.trigger(500);
147
-
148
- // If multiple triggers occur within debounceMs, only the last one fires
149
- poller.trigger();
150
- poller.trigger();
151
- poller.trigger(); // only this one will poll after 1s
72
+ poller.trigger(500); // Debounce: 500 ms
73
+ poller.trigger(200); // Resets to 200 ms from now
74
+ // Only one poll fires after the last trigger delay
152
75
  ```
153
76
 
154
77
  ### Behavior
155
78
 
156
- - Accepts a `debounceMs` parameter (default: `1000`).
157
- - Multiple rapid calls reset the debounce timer only the last trigger fires.
158
- - No-op if the poller is not running.
159
-
160
- ```typescript
161
- const poller = new Poller(
162
- async () => await fetchData(),
163
- (current, previous) => console.log("Updated:", current),
164
- 60000
165
- );
166
-
167
- await poller.start();
168
-
169
- // Trigger a poll with 1000ms debounce (default)
170
- poller.trigger();
171
-
172
- // Trigger with custom debounce
173
- poller.trigger(500);
174
-
175
- // Multiple rapid triggers coalesce into one poll
176
- poller.trigger(500);
177
- poller.trigger(500);
178
- poller.trigger(500); // Only one poll fires after 500ms
179
- ```
180
-
181
- ## Error Handling
182
-
183
- Fetch errors do not halt polling. An optional `onError` callback receives any errors, and polling continues at the next interval.
184
-
185
- ```typescript
186
- const poller = new Poller(
187
- async () => {
188
- if (Math.random() < 0.5) throw new Error("Network fail");
189
- return "ok";
190
- },
191
- (current) => console.log(current),
192
- 1000,
193
- (error) => console.error("Fetch failed:", error)
194
- );
195
-
196
- await poller.start();
197
- // Continues polling even after errors
198
- ```
199
-
200
- ### Error Handling Details
201
-
202
- - Errors during fetch are caught and passed to the optional `onError` callback.
203
- - Polling continues after errors — no automatic retry logic is applied.
79
+ - **Deep equality detection**: Uses `JSON.stringify` to detect structural changes, even with new object references.
80
+ - **Overlapping request handling**: Skips polls while a fetch is in progress.
81
+ - **Error resilience**: Continues polling on errors if `onError` is provided; otherwise, errors are silently ignored.
204
82
 
205
83
  ```typescript
206
84
  const poller = new Poller(
207
- async () => await fetchData(),
208
- (current, previous) => console.log("Updated:", current),
209
- 5000,
210
- (error) => {
211
- console.error("Fetch failed:", error);
212
- // Polling continues automatically
213
- }
85
+ async () => fetch("https://api.example.com/status").then(r => r.json()),
86
+ (current, previous) => console.log("Status changed:", current),
87
+ 10000, // 10 seconds
88
+ (error) => console.error("Polling error:", error)
214
89
  );
215
90
 
216
91
  await poller.start();
217
- ```
218
-
219
- ## Core Features
220
-
221
- ### Polling Lifecycle
222
-
223
- The `Poller` manages its lifecycle with `start()` and `stop()` methods.
224
-
225
- - `start()`: Begins polling immediately and then at the configured interval. Idempotent — calling it multiple times has no effect after the first call.
226
- - `stop()`: Clears the polling timer and any pending debounced triggers. No-op if already stopped.
227
-
228
- ### Concurrent Fetch Handling
229
-
230
- Overlapping fetches (e.g., due to slow network) are automatically skipped:
231
- - If `poll()` is already running when the interval fires, the next poll is skipped until the current one completes.
232
-
233
- ## API Reference
234
-
235
- ### `Poller<T>` Constructor
236
-
237
- | Parameter | Type | Description |
238
- |-------------|---------------------------------------|------------------------------------------|
239
- | `fetchFn` | `() => Promise<T>` | Async function returning the current state |
240
- | `onChange` | `(current: T, previous: T | undefined) => void` | Callback invoked on state changes |
241
- | `intervalMs`| `number` | Polling interval in milliseconds |
242
- | `onError?` | `(error: unknown) => void` | Optional callback for fetch errors |
243
-
244
- ### Methods
245
-
246
- | Method | Signature | Description |
247
- |--------------|----------------------------------------|----------------------------------------------|
248
- | `start()` | `(): Promise<void>` | Begins polling (immediate + interval-based) |
249
- | `stop()` | `(): void` | Stops polling and clears timers |
250
- | `trigger()` | `(debounceMs?: number) => void` | Triggers a debounced manual poll |
251
-
252
- ### Behavior
253
-
254
- - **Deep equality** — uses JSON serialization to detect structural changes
255
- - **Overlap prevention** — skips a poll if the previous fetch is still running
256
- - **Error resilience** — continues polling after fetch errors
257
- - **Idempotent start** — calling `start()` multiple times is safe
258
- - **Debounced triggers** — multiple `trigger()` calls coalesce into one poll
92
+ poller.trigger(500); // Manual override after 0.5s if triggered again
93
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/poller",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [