@hardlydifficult/poller 1.0.24 → 1.0.26

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @hardlydifficult/poller
2
2
 
3
- A generic polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
3
+ Opinionated polling with one entrypoint: `watch()`.
4
+
5
+ The package starts immediately, emits only when the value changes, and keeps the returned handle small.
4
6
 
5
7
  ## Installation
6
8
 
@@ -10,149 +12,73 @@ npm install @hardlydifficult/poller
10
12
 
11
13
  ## Quick Start
12
14
 
13
- ```typescript
14
- import { Poller } from "@hardlydifficult/poller";
15
+ ```ts
16
+ import { watch } from "@hardlydifficult/poller";
15
17
 
16
- const poller = Poller.create({
17
- fetch: async () => {
18
- const res = await fetch("https://api.example.com/user");
19
- return res.json();
20
- },
21
- onChange: (user, previousUser) => {
22
- console.log("User changed:", user, previousUser);
18
+ const userWatcher = await watch({
19
+ everyMs: 5_000,
20
+ read: () => fetch("/api/user").then((r) => r.json()),
21
+ onChange: (user, previous) => {
22
+ console.log("User changed", user, previous);
23
23
  },
24
- intervalMs: 5000,
25
24
  });
26
25
 
27
- await poller.start();
28
- poller.trigger();
29
- poller.stop();
26
+ await userWatcher.refresh();
27
+ userWatcher.stop();
30
28
  ```
31
29
 
32
- ## Poller Creation
33
-
34
- The `Poller` class supports two constructor styles: a modern options-based approach and a deprecated positional constructor for backward compatibility.
35
-
36
- ### Options-based constructor (recommended)
30
+ ## API
37
31
 
38
- ```typescript
39
- import { Poller } from "@hardlydifficult/poller";
32
+ ### `watch(options)`
40
33
 
41
- const poller = Poller.create({
42
- fetch: async () => fetch("/api/data").then(r => r.json()),
43
- onChange: (current, previous) => console.log("Changed:", current),
44
- intervalMs: 3000,
45
- debounceMs: 1000, // optional, defaults to 1000
46
- onError: (error) => console.error("Poll error:", error), // optional
47
- comparator: (curr, prev) => curr.id === prev?.id, // optional
48
- });
49
- ```
50
-
51
- | Option | Type | Description |
52
- |--------|------|-------------|
53
- | `fetch` | `() => Promise<T>` | Async function to fetch data |
54
- | `onChange` | `(current: T, previous: T \| undefined) => void` | Callback when data changes |
55
- | `intervalMs` | `number` | Polling interval in milliseconds |
56
- | `debounceMs` | `number` | Debounce delay for manual triggers (default: `1000`) |
57
- | `onError` | `(error: unknown) => void` | Error handler (optional) |
58
- | `comparator` | `(current: T, previous: T \| undefined) => boolean` | Custom change detection (default: deep equality) |
59
-
60
- ### Deprecated positional constructor
61
-
62
- ```typescript
63
- const poller = new Poller(
64
- async () => fetch("/api/data").then(r => r.json()),
65
- (current, previous) => console.log("Changed:", current),
66
- 3000,
67
- (error) => console.error("Poll error:", error)
68
- );
69
- ```
34
+ Starts reading immediately and resolves after the first read attempt completes.
70
35
 
71
- > Note: This style is deprecated; use `Poller.create(options)` or `new Poller(options)` instead.
72
-
73
- ## Polling Control
74
-
75
- ### `start()`
76
- Starts polling at the configured interval. Returns immediately after the first fetch completes.
77
-
78
- ```typescript
79
- await poller.start();
80
- ```
81
-
82
- ### `stop()`
83
- Stops all timers and prevents further polling.
84
-
85
- ```typescript
86
- poller.stop();
87
- ```
88
-
89
- ### `trigger(debounceMs?)`
90
- Manually triggers a poll, debouncing multiple calls.
91
-
92
- ```typescript
93
- poller.trigger(); // Uses default debounceMs
94
- poller.trigger(2000); // Override debounce delay
95
- ```
96
-
97
- ## Change Detection
98
-
99
- By default, the `Poller` uses deep equality to detect changes, supporting primitives, arrays, and plain objects—even when new references are returned.
100
-
101
- ```typescript
102
- const poller = Poller.create({
103
- fetch: async () => ({ count: 42 }),
104
- onChange: (data, prev) => console.log("Changed:", data),
105
- intervalMs: 2000,
36
+ ```ts
37
+ const watcher = await watch({
38
+ everyMs: 30_000,
39
+ read: () => fetch("/api/data").then((r) => r.json()),
40
+ onChange: (current, previous) => {
41
+ console.log("Changed:", current, previous);
42
+ },
43
+ onError: (error) => {
44
+ console.error("Read failed:", error.message);
45
+ },
46
+ isEqual: (current, previous) => current.id === previous?.id,
106
47
  });
107
-
108
- // Even if fetch returns a new object with same content, onChange won't fire
109
48
  ```
110
49
 
111
- ### Custom comparator
50
+ Options:
112
51
 
113
- You can supply a custom comparator to control change detection logic:
52
+ - `read: () => Promise<T>`
53
+ - `onChange: (current: T, previous: T | undefined) => void`
54
+ - `everyMs: number`
55
+ - `isEqual?: (current: T, previous: T | undefined) => boolean`
56
+ - `onError?: (error: Error) => void`
114
57
 
115
- ```typescript
116
- const poller = Poller.create({
117
- fetch: async () => ({ id: 1, name: "Alice" }),
118
- onChange: (data, prev) => console.log("Changed:", data.name),
119
- intervalMs: 2000,
120
- comparator: (current, previous) => current.name === previous?.name,
121
- });
122
- ```
58
+ ### `watcher.current`
123
59
 
124
- ## Error Handling
60
+ The last emitted value. This stays `undefined` until a read succeeds and is accepted as changed.
125
61
 
126
- Errors during fetch or comparator execution are routed to the `onError` callback, if provided. Polling continues after errors.
62
+ ### `watcher.refresh()`
127
63
 
128
- ```typescript
129
- const poller = Poller.create({
130
- fetch: async () => { throw new Error("Network issue"); },
131
- onChange: () => {},
132
- intervalMs: 1000,
133
- onError: (err) => console.warn("Caught error:", err),
134
- });
64
+ Runs a read immediately and returns the current emitted value. If a read is already in flight, the same promise is reused.
135
65
 
136
- await poller.start(); // Continues polling despite error
66
+ ```ts
67
+ const current = await watcher.refresh();
137
68
  ```
138
69
 
139
- ## Overlapping Request Handling
70
+ ### `watcher.stop()`
140
71
 
141
- If a fetch is still pending when the next interval arrives, the new poll is skipped to avoid overlapping requests.
72
+ Stops the interval and makes future `refresh()` calls a no-op.
142
73
 
143
- ```typescript
144
- const poller = Poller.create({
145
- fetch: async () => {
146
- await new Promise(r => setTimeout(r, 2000)); // Simulate slow fetch
147
- return "data";
148
- },
149
- onChange: () => {},
150
- intervalMs: 1000, // Won't trigger overlapping fetch
151
- });
74
+ ```ts
75
+ watcher.stop();
152
76
  ```
153
77
 
154
- ## API Reference
78
+ ## Behavior
155
79
 
156
- - `Poller<T>`: Generic polling class
157
- - `PollerOptions<T>`: Options interface for configuring the poller
158
- - `Poller.create<T>(options: PollerOptions<T>): Poller<T>`: Static factory method (recommended)
80
+ - The first successful read calls `onChange(current, undefined)`.
81
+ - Reads never overlap.
82
+ - Deep equality is the default change detection for primitives, arrays, and plain objects.
83
+ - Polling continues after read failures.
84
+ - If `onError` is omitted, errors are logged with `console.error`.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { Poller, type PollerOptions } from "./Poller.js";
1
+ export { watch, type WatchHandle, type WatchOptions } from "./watch.js";
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
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; } });
3
+ exports.watch = void 0;
4
+ var watch_js_1 = require("./watch.js");
5
+ Object.defineProperty(exports, "watch", { enumerable: true, get: function () { return watch_js_1.watch; } });
6
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAyD;AAAhD,mGAAA,MAAM,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAAwE;AAA/D,iGAAA,KAAK,OAAA"}
@@ -0,0 +1,17 @@
1
+ /** Small opinionated polling utility that reads periodically and emits only when the value changes. */
2
+ export interface WatchOptions<T> {
3
+ readonly read: () => Promise<T>;
4
+ readonly onChange: (current: T, previous: T | undefined) => void;
5
+ readonly everyMs: number;
6
+ readonly isEqual?: (current: T, previous: T | undefined) => boolean;
7
+ readonly onError?: (error: Error) => void;
8
+ }
9
+ /** Live watcher handle returned by {@link watch}. */
10
+ export interface WatchHandle<T> {
11
+ readonly current: T | undefined;
12
+ stop(): void;
13
+ refresh(): Promise<T | undefined>;
14
+ }
15
+ /** Starts watching immediately and resolves after the first read attempt completes. */
16
+ export declare function watch<T>(options: WatchOptions<T>): Promise<WatchHandle<T>>;
17
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC3C;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,CAAC;IAChC,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CACnC;AAgGD,uFAAuF;AACvF,wBAAsB,KAAK,CAAC,CAAC,EAC3B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAEzB"}
package/dist/watch.js ADDED
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.watch = watch;
4
+ class Watcher {
5
+ readFn;
6
+ onChange;
7
+ everyMs;
8
+ isEqual;
9
+ onError;
10
+ timer;
11
+ currentValue;
12
+ stopped = false;
13
+ inFlight;
14
+ constructor(options) {
15
+ this.readFn = options.read;
16
+ this.onChange = options.onChange;
17
+ this.everyMs = options.everyMs;
18
+ this.isEqual = options.isEqual ?? defaultIsEqual;
19
+ this.onError = options.onError;
20
+ }
21
+ get current() {
22
+ return this.currentValue;
23
+ }
24
+ async start() {
25
+ await this.refresh();
26
+ if (!this.stopped) {
27
+ this.timer = setInterval(() => {
28
+ void this.refresh();
29
+ }, this.everyMs);
30
+ this.timer.unref();
31
+ }
32
+ return this;
33
+ }
34
+ stop() {
35
+ this.stopped = true;
36
+ if (this.timer) {
37
+ clearInterval(this.timer);
38
+ this.timer = undefined;
39
+ }
40
+ }
41
+ refresh() {
42
+ if (this.stopped) {
43
+ return Promise.resolve(this.currentValue);
44
+ }
45
+ if (this.inFlight) {
46
+ return this.inFlight;
47
+ }
48
+ this.inFlight = this.readCurrentValue().finally(() => {
49
+ this.inFlight = undefined;
50
+ });
51
+ return this.inFlight;
52
+ }
53
+ async readCurrentValue() {
54
+ try {
55
+ const current = await this.readFn();
56
+ if (this.stopped) {
57
+ return this.currentValue;
58
+ }
59
+ if (!this.isEqual(current, this.currentValue)) {
60
+ const previous = this.currentValue;
61
+ this.onChange(current, previous);
62
+ this.currentValue = current;
63
+ }
64
+ }
65
+ catch (error) {
66
+ this.handleError(error);
67
+ }
68
+ return this.currentValue;
69
+ }
70
+ handleError(error) {
71
+ const normalizedError = normalizeError(error);
72
+ if (this.onError) {
73
+ this.onError(normalizedError);
74
+ return;
75
+ }
76
+ console.error("watch() read failed:", normalizedError);
77
+ }
78
+ }
79
+ /** Starts watching immediately and resolves after the first read attempt completes. */
80
+ async function watch(options) {
81
+ return new Watcher(options).start();
82
+ }
83
+ function normalizeError(error) {
84
+ return error instanceof Error ? error : new Error(String(error));
85
+ }
86
+ function defaultIsEqual(a, b) {
87
+ if (Object.is(a, b)) {
88
+ return true;
89
+ }
90
+ if (isPrimitive(a) || isPrimitive(b) || b === undefined) {
91
+ return false;
92
+ }
93
+ if (isPlainObjectOrArray(a) && isPlainObjectOrArray(b)) {
94
+ return deepEqual(a, b);
95
+ }
96
+ try {
97
+ return JSON.stringify(a) === JSON.stringify(b);
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ function isPrimitive(value) {
104
+ return (value === null || (typeof value !== "object" && typeof value !== "function"));
105
+ }
106
+ function isPlainObjectOrArray(value) {
107
+ if (Array.isArray(value)) {
108
+ return true;
109
+ }
110
+ if (value === null || typeof value !== "object") {
111
+ return false;
112
+ }
113
+ const prototype = Object.getPrototypeOf(value);
114
+ return prototype === Object.prototype || prototype === null;
115
+ }
116
+ function deepEqual(a, b) {
117
+ if (Object.is(a, b)) {
118
+ return true;
119
+ }
120
+ if (Array.isArray(a) && Array.isArray(b)) {
121
+ if (a.length !== b.length) {
122
+ return false;
123
+ }
124
+ return a.every((item, index) => deepEqual(item, b[index]));
125
+ }
126
+ if (!isPlainObjectOrArray(a) || !isPlainObjectOrArray(b)) {
127
+ return false;
128
+ }
129
+ const aEntries = Object.entries(a);
130
+ const bEntries = Object.entries(b);
131
+ if (aEntries.length !== bEntries.length) {
132
+ return false;
133
+ }
134
+ return aEntries.every(([key, value]) => {
135
+ if (!(key in b)) {
136
+ return false;
137
+ }
138
+ return deepEqual(value, b[key]);
139
+ });
140
+ }
141
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/poller",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -15,7 +15,7 @@
15
15
  "clean": "node --eval \"fs.rmSync('dist', { recursive: true, force: true })\""
16
16
  },
17
17
  "devDependencies": {
18
- "@types/node": "25.3.2",
18
+ "@types/node": "25.3.5",
19
19
  "typescript": "5.9.3",
20
20
  "vitest": "4.0.18",
21
21
  "@vitest/coverage-v8": "4.0.18"
package/dist/Poller.d.ts DELETED
@@ -1,35 +0,0 @@
1
- /** Generic polling utility that periodically fetches data and invokes a callback when the result changes. */
2
- export interface PollerOptions<T> {
3
- fetch: () => Promise<T>;
4
- onChange: (current: T, previous: T | undefined) => void;
5
- intervalMs: number;
6
- onError?: (error: unknown) => void;
7
- debounceMs?: number;
8
- comparator?: (current: T, previous: T | undefined) => boolean;
9
- }
10
- /**
11
- *
12
- */
13
- export declare class Poller<T> {
14
- private readonly fetchFn;
15
- private readonly onChange;
16
- private readonly intervalMs;
17
- private readonly onError?;
18
- private readonly debounceMs;
19
- private readonly comparator;
20
- private timer;
21
- private previous;
22
- private running;
23
- private fetching;
24
- private triggerTimeout;
25
- constructor(options: PollerOptions<T>);
26
- /** @deprecated Use `Poller.create(options)` or `new Poller({ ...options })` instead. */
27
- constructor(fetchFn: () => Promise<T>, onChange: (current: T, previous: T | undefined) => void, intervalMs: number, onError?: (error: unknown) => void);
28
- static create<T>(options: PollerOptions<T>): Poller<T>;
29
- start(): Promise<void>;
30
- stop(): void;
31
- trigger(debounceMs?: number): void;
32
- private poll;
33
- private static resolveOptions;
34
- }
35
- //# sourceMappingURL=Poller.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Poller.d.ts","sourceRoot":"","sources":["../src/Poller.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC;CAC/D;AAED;;GAEG;AACH,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;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmD;IAE9E,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA4C;gBAEtD,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;IACrC,wFAAwF;gBAEtF,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;IAuBpC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAIhD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,IAAI,IAAI,IAAI;IAYZ,OAAO,CAAC,UAAU,SAAkB,GAAG,IAAI;YAc7B,IAAI;IAqBlB,OAAO,CAAC,MAAM,CAAC,cAAc;CAuB9B"}
package/dist/Poller.js DELETED
@@ -1,158 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Poller = void 0;
4
- /**
5
- *
6
- */
7
- class Poller {
8
- fetchFn;
9
- onChange;
10
- intervalMs;
11
- onError;
12
- debounceMs;
13
- comparator;
14
- timer;
15
- previous;
16
- running = false;
17
- fetching = false;
18
- triggerTimeout;
19
- constructor(optionsOrFetch, onChange, intervalMs, onError) {
20
- const options = Poller.resolveOptions(optionsOrFetch, onChange, intervalMs, onError);
21
- this.fetchFn = options.fetch;
22
- this.onChange = options.onChange;
23
- this.intervalMs = options.intervalMs;
24
- this.onError = options.onError;
25
- this.debounceMs = options.debounceMs ?? 1_000;
26
- this.comparator = options.comparator ?? defaultIsEqual;
27
- }
28
- static create(options) {
29
- return new Poller(options);
30
- }
31
- async start() {
32
- if (this.running) {
33
- return;
34
- }
35
- this.running = true;
36
- await this.poll();
37
- this.timer = setInterval(() => {
38
- void this.poll();
39
- }, this.intervalMs);
40
- this.timer.unref();
41
- }
42
- stop() {
43
- this.running = false;
44
- if (this.timer) {
45
- clearInterval(this.timer);
46
- this.timer = undefined;
47
- }
48
- if (this.triggerTimeout) {
49
- clearTimeout(this.triggerTimeout);
50
- this.triggerTimeout = undefined;
51
- }
52
- }
53
- trigger(debounceMs = this.debounceMs) {
54
- if (!this.running) {
55
- return;
56
- }
57
- if (this.triggerTimeout) {
58
- clearTimeout(this.triggerTimeout);
59
- }
60
- this.triggerTimeout = setTimeout(() => {
61
- this.triggerTimeout = undefined;
62
- void this.poll();
63
- }, debounceMs);
64
- this.triggerTimeout.unref();
65
- }
66
- async poll() {
67
- if (this.fetching) {
68
- return;
69
- }
70
- this.fetching = true;
71
- try {
72
- const current = await this.fetchFn();
73
- if (!this.comparator(current, this.previous)) {
74
- this.onChange(current, this.previous);
75
- this.previous = current;
76
- }
77
- }
78
- catch (error) {
79
- if (this.onError) {
80
- this.onError(error);
81
- }
82
- }
83
- finally {
84
- this.fetching = false;
85
- }
86
- }
87
- static resolveOptions(optionsOrFetch, onChange, intervalMs, onError) {
88
- if (typeof optionsOrFetch !== "function") {
89
- return optionsOrFetch;
90
- }
91
- if (!onChange || intervalMs === undefined) {
92
- throw new Error("Poller positional constructor requires fetch, onChange, and intervalMs.");
93
- }
94
- return {
95
- fetch: optionsOrFetch,
96
- onChange,
97
- intervalMs,
98
- onError,
99
- };
100
- }
101
- }
102
- exports.Poller = Poller;
103
- function defaultIsEqual(a, b) {
104
- if (Object.is(a, b)) {
105
- return true;
106
- }
107
- if (isPrimitive(a) || isPrimitive(b) || b === undefined) {
108
- return false;
109
- }
110
- if (isPlainObjectOrArray(a) && isPlainObjectOrArray(b)) {
111
- return deepEqual(a, b);
112
- }
113
- try {
114
- return JSON.stringify(a) === JSON.stringify(b);
115
- }
116
- catch {
117
- return false;
118
- }
119
- }
120
- function isPrimitive(value) {
121
- return (value === null || (typeof value !== "object" && typeof value !== "function"));
122
- }
123
- function isPlainObjectOrArray(value) {
124
- if (Array.isArray(value)) {
125
- return true;
126
- }
127
- if (value === null || typeof value !== "object") {
128
- return false;
129
- }
130
- const prototype = Object.getPrototypeOf(value);
131
- return prototype === Object.prototype || prototype === null;
132
- }
133
- function deepEqual(a, b) {
134
- if (Object.is(a, b)) {
135
- return true;
136
- }
137
- if (Array.isArray(a) && Array.isArray(b)) {
138
- if (a.length !== b.length) {
139
- return false;
140
- }
141
- return a.every((item, index) => deepEqual(item, b[index]));
142
- }
143
- if (!isPlainObjectOrArray(a) || !isPlainObjectOrArray(b)) {
144
- return false;
145
- }
146
- const aEntries = Object.entries(a);
147
- const bEntries = Object.entries(b);
148
- if (aEntries.length !== bEntries.length) {
149
- return false;
150
- }
151
- return aEntries.every(([key, value]) => {
152
- if (!(key in b)) {
153
- return false;
154
- }
155
- return deepEqual(value, b[key]);
156
- });
157
- }
158
- //# sourceMappingURL=Poller.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Poller.js","sourceRoot":"","sources":["../src/Poller.ts"],"names":[],"mappings":"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"}