@hardlydifficult/poller 1.0.8 → 1.0.10

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 +52 -96
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hardlydifficult/poller
2
2
 
3
- A lightweight, generic polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
3
+ Lightweight polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,132 +13,88 @@ npm install @hardlydifficult/poller
13
13
  ```typescript
14
14
  import { Poller } from "@hardlydifficult/poller";
15
15
 
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();
16
+ const fetchFn = async () => {
17
+ const response = await fetch("https://api.example.com/status");
18
+ return response.json();
20
19
  };
21
20
 
22
- // Create and start the poller
23
21
  const poller = new Poller(
24
- fetchUser,
25
- (current, previous) => {
26
- console.log("User updated:", current);
27
- },
28
- 5000 // Poll every 5 seconds
22
+ fetchFn,
23
+ (data, prev) => console.log("Data changed:", data),
24
+ 5000 // 5-second interval
29
25
  );
30
26
 
31
27
  await poller.start();
32
- // Polls every 5s, fires onChange only when JSON data changes
33
-
34
- // Manually trigger a debounced poll (e.g., after user action)
35
- poller.trigger(1000); // Waits 1s, then polls once
28
+ // Polls every 5 seconds and logs only when data changes
36
29
 
37
- // Stop polling when no longer needed
30
+ // Later, stop polling
38
31
  poller.stop();
39
32
  ```
40
33
 
41
- ## Polling Lifecycle
34
+ ## Polling with Change Detection
35
+
36
+ The `Poller` class polls a fetch function periodically and invokes a callback only when the result changes. Change detection uses deep equality via `JSON.stringify` comparison, ensuring that structurally identical values (even with different object references) do not trigger redundant callbacks.
37
+
38
+ ### Constructor Parameters
42
39
 
43
- ### Start and Stop
40
+ | Parameter | Type | Description |
41
+ |-------------|-------------------------------------|-----------------------------------------------------|
42
+ | `fetchFn` | `() => Promise<T>` | Async function that returns the data to poll |
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) | Optional error handler for fetch failures |
44
46
 
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.
47
+ ### Poller API
47
48
 
48
49
  ```typescript
50
+ // Start polling (idempotent — safe to call multiple times)
49
51
  await poller.start();
50
- await poller.start(); // No-op
51
52
 
53
+ // Stop polling and clear timers
52
54
  poller.stop();
53
- poller.stop(); // No-op
54
- ```
55
-
56
- ## Change Detection
57
-
58
- ### Deep Equality via JSON
59
-
60
- Change detection uses `JSON.stringify()` comparison, enabling structural equality checks for objects and arrays—even when references differ.
61
55
 
62
- ```typescript
63
- const fetchFn = async () => ({ items: [1, 2, 3] });
64
- const onChange = (current, previous) => {
65
- // Fires only when structure changes
66
- };
67
-
68
- const poller = new Poller(fetchFn, onChange, 1000);
69
- // Even if new object reference returned, onChange won’t fire unless JSON differs
56
+ // Manually trigger a poll (debounced by default)
57
+ poller.trigger(1000); // Debounced 1s (default 1000ms)
70
58
  ```
71
59
 
72
- ### On Change Callback
60
+ ### Debounced Manual Trigger
61
+
62
+ The `trigger()` method allows manually forcing a poll while debouncing multiple rapid calls:
73
63
 
74
64
  ```typescript
75
- onChange(current: T, previous: T | undefined): void
65
+ await poller.start();
66
+ poller.trigger(500); // Schedules a poll after 500ms
67
+ poller.trigger(500); // Resets debounce — only one poll fires
76
68
  ```
77
69
 
78
- - `current`: Most recently fetched value
79
- - `previous`: Prior value, or `undefined` on first poll
80
-
81
- ## Error Handling
70
+ ### Error Handling
82
71
 
83
- ### Optional Error Callback
84
-
85
- Provide an `onError` handler to manage fetch failures; polling continues regardless.
72
+ Errors during polling do not halt the poller. They are optionally reported via `onError`, if provided.
86
73
 
87
74
  ```typescript
88
75
  const poller = new Poller(
89
- fetchFn,
90
- onChange,
91
- 5000,
92
- (error) => {
93
- console.warn("Polling error:", error);
94
- }
76
+ async () => {
77
+ throw new Error("Network failure");
78
+ },
79
+ (data) => console.log(data),
80
+ 2000,
81
+ (error) => console.error("Poll failed:", error)
95
82
  );
83
+ await poller.start();
84
+ // Logs: Poll failed: Error: Network failure
85
+ // Continues polling after each error
96
86
  ```
97
87
 
98
- - Errors do not halt the polling interval.
99
- - If `onError` is omitted, errors are silently suppressed.
100
-
101
- ## Manual Triggering
88
+ ## Overlapping Request Handling
102
89
 
103
- ### Debounced Triggers
104
-
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.
90
+ The `Poller` skips new polls while a fetch is still in progress, preventing overlapping requests.
107
91
 
108
92
  ```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
- ```
113
-
114
- - No-op if `poller` is not running.
115
-
116
- ## Overlap Handling
117
-
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
- ```
126
-
127
- ## API Reference
128
-
129
- ### `Poller<T>`
130
-
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 |
137
-
138
- ### Methods
139
-
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 |
93
+ const fetchFn = vi.fn().mockImplementation(() => {
94
+ // Simulates slow network never resolves before interval
95
+ return new Promise((resolve) => setTimeout(() => resolve("data"), 6000));
96
+ });
97
+ const poller = new Poller(fetchFn, () => {}, 1000);
98
+ await poller.start();
99
+ // Only one fetch runs at a time — subsequent intervals are skipped until it completes
100
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/poller",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [