@hardlydifficult/poller 1.0.6 → 1.0.8

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 +76 -136
  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 and invokes a callback only when the result changes, using JSON-based deep equality comparison.
3
+ A lightweight, generic polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,192 +13,132 @@ npm install @hardlydifficult/poller
13
13
  ```typescript
14
14
  import { Poller } from "@hardlydifficult/poller";
15
15
 
16
- // Create a poller that fetches mock API data every 5 seconds
16
+ // Define a fetch function (e.g., API call)
17
+ const fetchUser = async () => {
18
+ const res = await fetch("https://api.example.com/user");
19
+ return res.json();
20
+ };
21
+
22
+ // Create and start the poller
17
23
  const poller = new Poller(
18
- async () => {
19
- const response = await fetch("https://api.example.com/status");
20
- return await response.json();
21
- },
24
+ fetchUser,
22
25
  (current, previous) => {
23
- console.log("Status changed:", current);
26
+ console.log("User updated:", current);
24
27
  },
25
- 5000,
26
- (error) => {
27
- console.error("Polling error:", error);
28
- }
29
- );
30
-
31
- await poller.start(); // Begins polling immediately
32
- // ... later
33
- poller.stop(); // Stops polling
34
- ```
35
-
36
- ### Basic Usage
37
-
38
- ```typescript
39
- import { Poller } from "@hardlydifficult/poller";
40
-
41
- const poller = new Poller(
42
- async () => await fetchCurrentState(),
43
- (current, previous) => console.log("State changed!", current),
44
- 5 * 60 * 1000 // poll every 5 minutes
28
+ 5000 // Poll every 5 seconds
45
29
  );
46
30
 
47
31
  await poller.start();
32
+ // Polls every 5s, fires onChange only when JSON data changes
48
33
 
49
- // Manual trigger with debounce (e.g. from a webhook)
50
- poller.trigger(1000);
34
+ // Manually trigger a debounced poll (e.g., after user action)
35
+ poller.trigger(1000); // Waits 1s, then polls once
51
36
 
52
- // Stop polling
37
+ // Stop polling when no longer needed
53
38
  poller.stop();
54
39
  ```
55
40
 
56
- ## Polling
57
-
58
- The `Poller` class fetches data at a configurable interval and invokes a callback when the result changes. It fetches immediately on `start()`, then at the configured interval.
41
+ ## Polling Lifecycle
59
42
 
60
- ```typescript
61
- import { Poller } from "@hardlydifficult/poller";
43
+ ### Start and Stop
62
44
 
63
- const poller = new Poller(
64
- async () => {
65
- const response = await fetch("/api/status");
66
- return response.json();
67
- },
68
- (current, previous) => {
69
- console.log("Previous:", previous);
70
- console.log("Current:", current);
71
- },
72
- 10000 // poll every 10 seconds
73
- );
74
-
75
- await poller.start();
76
- // Fetches immediately, then every 10 seconds
77
- ```
78
-
79
- ### `start()`
80
-
81
- Starts polling. Fetches immediately, then at the configured interval. Calling `start()` multiple times is safe (idempotent).
45
+ - `start()`: Begins polling at the configured interval. Idempotent—safe to call multiple times.
46
+ - `stop()`: Cancels timers and clears any pending debounced trigger. Safe to call multiple times.
82
47
 
83
48
  ```typescript
84
- await poller.start(); // Fetches immediately
85
- await poller.start(); // No-op, already running
86
- ```
87
-
88
- ### `stop()`
89
-
90
- Stops polling and cleans up all timers.
49
+ await poller.start();
50
+ await poller.start(); // No-op
91
51
 
92
- ```typescript
93
52
  poller.stop();
94
- // No more polls will fire
53
+ poller.stop(); // No-op
95
54
  ```
96
55
 
97
56
  ## Change Detection
98
57
 
99
- Changes are detected using JSON serialization for deep equality. Structurally identical objects are considered unchanged—even if they are different references.
58
+ ### Deep Equality via JSON
100
59
 
101
- - On first poll, `onChange(current, undefined)` is invoked.
102
- - On subsequent polls, `onChange(current, previous)` is invoked only if the new value differs structurally.
60
+ Change detection uses `JSON.stringify()` comparison, enabling structural equality checks for objects and arrays—even when references differ.
103
61
 
104
62
  ```typescript
105
- const poller = new Poller(
106
- async () => ({ count: 5, items: [1, 2, 3] }),
107
- (current, previous) => {
108
- // Only fires when the JSON representation changes
109
- console.log("Changed from", previous, "to", current);
110
- },
111
- 5000
112
- );
63
+ const fetchFn = async () => ({ items: [1, 2, 3] });
64
+ const onChange = (current, previous) => {
65
+ // Fires only when structure changes
66
+ };
113
67
 
114
- await poller.start();
68
+ const poller = new Poller(fetchFn, onChange, 1000);
69
+ // Even if new object reference returned, onChange won’t fire unless JSON differs
115
70
  ```
116
71
 
117
- ## Manual Triggers
118
-
119
- Call `trigger()` to manually poll immediately, with optional debouncing. Multiple rapid triggers are coalesced into a single poll.
120
-
121
- - Accepts a `debounceMs` parameter (default: `1000`).
122
- - Multiple rapid calls reset the debounce timer — only the last trigger fires.
123
- - No-op if the poller is not running.
72
+ ### On Change Callback
124
73
 
125
74
  ```typescript
126
- const poller = new Poller(
127
- async () => await fetchData(),
128
- (current, previous) => console.log("Updated:", current),
129
- 60000
130
- );
131
-
132
- await poller.start();
133
-
134
- // Trigger a poll with 1000ms debounce (default)
135
- poller.trigger();
136
-
137
- // Trigger with custom debounce
138
- poller.trigger(500);
139
-
140
- // Multiple rapid triggers coalesce into one poll
141
- poller.trigger(500);
142
- poller.trigger(500);
143
- poller.trigger(500); // Only one poll fires after 500ms
75
+ onChange(current: T, previous: T | undefined): void
144
76
  ```
145
77
 
78
+ - `current`: Most recently fetched value
79
+ - `previous`: Prior value, or `undefined` on first poll
80
+
146
81
  ## Error Handling
147
82
 
148
- - Errors during fetch are caught and passed to the optional `onError` callback.
149
- - Polling continues after errors — no automatic retry logic is applied.
83
+ ### Optional Error Callback
84
+
85
+ Provide an `onError` handler to manage fetch failures; polling continues regardless.
150
86
 
151
87
  ```typescript
152
88
  const poller = new Poller(
153
- async () => await fetchData(),
154
- (current, previous) => console.log("Updated:", current),
89
+ fetchFn,
90
+ onChange,
155
91
  5000,
156
92
  (error) => {
157
- console.error("Fetch failed:", error);
158
- // Polling continues automatically
93
+ console.warn("Polling error:", error);
159
94
  }
160
95
  );
161
-
162
- await poller.start();
163
96
  ```
164
97
 
165
- ## Core Features
98
+ - Errors do not halt the polling interval.
99
+ - If `onError` is omitted, errors are silently suppressed.
100
+
101
+ ## Manual Triggering
102
+
103
+ ### Debounced Triggers
166
104
 
167
- ### Polling Lifecycle
105
+ - `trigger(debounceMs?: number)`: Schedules a one-time poll after a debounce delay (default: 1000ms).
106
+ - Multiple rapid calls cancel previous timeouts—only the last one fires.
168
107
 
169
- The `Poller` manages its lifecycle with `start()` and `stop()` methods.
108
+ ```typescript
109
+ poller.trigger(2000); // Polls in 2s
110
+ poller.trigger(2000); // Cancelled, re-schedule to 2s from now
111
+ poller.trigger(2000); // Cancelled again, final delay applies
112
+ ```
170
113
 
171
- - `start()`: Begins polling immediately and then at the configured interval. Idempotent — calling it multiple times has no effect after the first call.
172
- - `stop()`: Clears the polling timer and any pending debounced triggers. No-op if already stopped.
114
+ - No-op if `poller` is not running.
173
115
 
174
- ### Concurrent Fetch Handling
116
+ ## Overlap Handling
175
117
 
176
- Overlapping fetches (e.g., due to slow network) are automatically skipped:
177
- - If `poll()` is already running when the interval fires, the next poll is skipped until the current one completes.
118
+ - Concurrent fetches are skipped—only one in-flight request is allowed at a time.
119
+ - Prevents resource waste and race conditions during slow network calls.
120
+
121
+ ```typescript
122
+ // Interval fires every 5s; if fetch takes 6s:
123
+ // - Second interval fire is skipped
124
+ // - Third interval fire executes after first completes
125
+ ```
178
126
 
179
127
  ## API Reference
180
128
 
181
- ### `Poller<T>` Constructor
129
+ ### `Poller<T>`
182
130
 
183
- | Parameter | Type | Description |
184
- |-------------|---------------------------------------|------------------------------------------|
185
- | `fetchFn` | `() => Promise<T>` | Async function returning the current state |
186
- | `onChange` | `(current: T, previous: T | undefined) => void` | Callback invoked on state changes |
187
- | `intervalMs`| `number` | Polling interval in milliseconds |
188
- | `onError?` | `(error: unknown) => void` | Optional callback for fetch errors |
131
+ | Parameter | Type | Description |
132
+ |-----------|------|-------------|
133
+ | `fetchFn` | `() => Promise<T>` | Async function to fetch data |
134
+ | `onChange` | `(current: T, previous: T \| undefined) => void` | Callback on value change |
135
+ | `intervalMs` | `number` | Polling interval in milliseconds |
136
+ | `onError?` | `(error: unknown) => void` | Optional error handler |
189
137
 
190
138
  ### Methods
191
139
 
192
- | Method | Signature | Description |
193
- |--------------|----------------------------------------|----------------------------------------------|
194
- | `start()` | `(): Promise<void>` | Begins polling (immediate + interval-based) |
195
- | `stop()` | `(): void` | Stops polling and clears timers |
196
- | `trigger()` | `(debounceMs?: number) => void` | Triggers a debounced manual poll |
197
-
198
- ### Behavior
199
-
200
- - **Deep equality** — uses JSON serialization to detect structural changes
201
- - **Overlap prevention** — skips a poll if the previous fetch is still running
202
- - **Error resilience** — continues polling after fetch errors
203
- - **Idempotent start** — calling `start()` multiple times is safe
204
- - **Debounced triggers** — multiple `trigger()` calls coalesce into one poll
140
+ | Method | Signature | Description |
141
+ |--------|-----------|-------------|
142
+ | `start` | `(): Promise<void>` | Begin polling immediately and then at `intervalMs` |
143
+ | `stop` | `(): void` | Cancel all timers and pending triggers |
144
+ | `trigger` | `(debounceMs?: number) => void` | Trigger a one-time poll after debounce delay |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/poller",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [