@hardlydifficult/poller 1.0.0
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 +53 -0
- package/dist/Poller.d.ts +17 -0
- package/dist/Poller.d.ts.map +1 -0
- package/dist/Poller.js +78 -0
- package/dist/Poller.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @hardlydifficult/poller
|
|
2
|
+
|
|
3
|
+
Generic state-change poller. Polls a function at an interval and fires a callback when the result changes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hardlydifficult/poller
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Poller } from "@hardlydifficult/poller";
|
|
15
|
+
|
|
16
|
+
const poller = new Poller(
|
|
17
|
+
async () => await fetchCurrentState(),
|
|
18
|
+
(current, previous) => console.log("State changed!", current),
|
|
19
|
+
5 * 60 * 1000 // poll every 5 minutes
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
await poller.start();
|
|
23
|
+
|
|
24
|
+
// Manual trigger with debounce (e.g. from a webhook)
|
|
25
|
+
poller.trigger(1000);
|
|
26
|
+
|
|
27
|
+
// Stop polling
|
|
28
|
+
poller.stop();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
### `new Poller<T>(fetchFn, onChange, intervalMs, onError?)`
|
|
34
|
+
|
|
35
|
+
| Parameter | Description |
|
|
36
|
+
|-----------|-------------|
|
|
37
|
+
| `fetchFn` | Async function that returns the current state |
|
|
38
|
+
| `onChange` | Called with `(current, previous)` when state changes |
|
|
39
|
+
| `intervalMs` | Polling interval in milliseconds |
|
|
40
|
+
| `onError` | Optional error handler; polling continues on errors |
|
|
41
|
+
|
|
42
|
+
| Method | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `start()` | Start polling (fetches immediately, then at interval) |
|
|
45
|
+
| `stop()` | Stop polling and clean up timers |
|
|
46
|
+
| `trigger(debounceMs?)` | Manually trigger a poll with debounce (default 1000ms) |
|
|
47
|
+
|
|
48
|
+
### Behavior
|
|
49
|
+
|
|
50
|
+
- **Deep equality** -- uses JSON serialization to detect changes
|
|
51
|
+
- **Overlap prevention** -- skips a poll if the previous fetch is still running
|
|
52
|
+
- **Error resilience** -- continues polling after fetch errors
|
|
53
|
+
- **Idempotent start** -- calling `start()` multiple times is safe
|
package/dist/Poller.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class Poller<T> {
|
|
2
|
+
private readonly fetchFn;
|
|
3
|
+
private readonly onChange;
|
|
4
|
+
private readonly intervalMs;
|
|
5
|
+
private readonly onError?;
|
|
6
|
+
private timer;
|
|
7
|
+
private previous;
|
|
8
|
+
private running;
|
|
9
|
+
private fetching;
|
|
10
|
+
private triggerTimeout;
|
|
11
|
+
constructor(fetchFn: () => Promise<T>, onChange: (current: T, previous: T | undefined) => void, intervalMs: number, onError?: (error: unknown) => void);
|
|
12
|
+
start(): Promise<void>;
|
|
13
|
+
stop(): void;
|
|
14
|
+
trigger(debounceMs?: number): void;
|
|
15
|
+
private poll;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=Poller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Poller.d.ts","sourceRoot":"","sources":["../src/Poller.ts"],"names":[],"mappings":"AAAA,qBAAa,MAAM,CAAC,CAAC;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA2B;IAEpD,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA4C;gBAGhE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,EACvD,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IAQ9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,IAAI,IAAI;IAYZ,OAAO,CAAC,UAAU,SAAO,GAAG,IAAI;YAalB,IAAI;CAuBnB"}
|
package/dist/Poller.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Poller = void 0;
|
|
4
|
+
class Poller {
|
|
5
|
+
fetchFn;
|
|
6
|
+
onChange;
|
|
7
|
+
intervalMs;
|
|
8
|
+
onError;
|
|
9
|
+
timer;
|
|
10
|
+
previous;
|
|
11
|
+
running = false;
|
|
12
|
+
fetching = false;
|
|
13
|
+
triggerTimeout;
|
|
14
|
+
constructor(fetchFn, onChange, intervalMs, onError) {
|
|
15
|
+
this.fetchFn = fetchFn;
|
|
16
|
+
this.onChange = onChange;
|
|
17
|
+
this.intervalMs = intervalMs;
|
|
18
|
+
this.onError = onError;
|
|
19
|
+
}
|
|
20
|
+
async start() {
|
|
21
|
+
if (this.running) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
this.running = true;
|
|
25
|
+
await this.poll();
|
|
26
|
+
this.timer = setInterval(() => {
|
|
27
|
+
void this.poll();
|
|
28
|
+
}, this.intervalMs);
|
|
29
|
+
}
|
|
30
|
+
stop() {
|
|
31
|
+
this.running = false;
|
|
32
|
+
if (this.timer) {
|
|
33
|
+
clearInterval(this.timer);
|
|
34
|
+
this.timer = undefined;
|
|
35
|
+
}
|
|
36
|
+
if (this.triggerTimeout) {
|
|
37
|
+
clearTimeout(this.triggerTimeout);
|
|
38
|
+
this.triggerTimeout = undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
trigger(debounceMs = 1000) {
|
|
42
|
+
if (!this.running) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (this.triggerTimeout) {
|
|
46
|
+
clearTimeout(this.triggerTimeout);
|
|
47
|
+
}
|
|
48
|
+
this.triggerTimeout = setTimeout(() => {
|
|
49
|
+
this.triggerTimeout = undefined;
|
|
50
|
+
void this.poll();
|
|
51
|
+
}, debounceMs);
|
|
52
|
+
}
|
|
53
|
+
async poll() {
|
|
54
|
+
if (this.fetching) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.fetching = true;
|
|
58
|
+
try {
|
|
59
|
+
const current = await this.fetchFn();
|
|
60
|
+
const currentJson = JSON.stringify(current);
|
|
61
|
+
const previousJson = JSON.stringify(this.previous);
|
|
62
|
+
if (currentJson !== previousJson) {
|
|
63
|
+
this.onChange(current, this.previous);
|
|
64
|
+
this.previous = current;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (this.onError) {
|
|
69
|
+
this.onError(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
this.fetching = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.Poller = Poller;
|
|
78
|
+
//# sourceMappingURL=Poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Poller.js","sourceRoot":"","sources":["../src/Poller.ts"],"names":[],"mappings":";;;AAAA,MAAa,MAAM;IACA,OAAO,CAAmB;IAC1B,QAAQ,CAAgD;IACxD,UAAU,CAAS;IACnB,OAAO,CAA4B;IAE5C,KAAK,CAA6C;IAClD,QAAQ,CAAgB;IACxB,OAAO,GAAG,KAAK,CAAC;IAChB,QAAQ,GAAG,KAAK,CAAC;IACjB,cAAc,CAA4C;IAElE,YACE,OAAyB,EACzB,QAAuD,EACvD,UAAkB,EAClB,OAAkC;QAElC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,UAAU,GAAG,IAAI;QACvB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;YAChC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEnD,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;gBACjC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACtC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;YAC1B,CAAC;QACH,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AAnFD,wBAmFC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Poller = void 0;
|
|
4
|
+
var Poller_js_1 = require("./Poller.js");
|
|
5
|
+
Object.defineProperty(exports, "Poller", { enumerable: true, get: function () { return Poller_js_1.Poller; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAqC;AAA5B,mGAAA,MAAM,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hardlydifficult/poller",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "tsc --noEmit",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "20.19.31",
|
|
19
|
+
"typescript": "5.8.3",
|
|
20
|
+
"vitest": "1.6.1"
|
|
21
|
+
}
|
|
22
|
+
}
|