@randajan/pulse 1.1.4 → 2.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 +64 -15
- package/dist/cjs/index.cjs +181 -111
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/esm/index.mjs +179 -109
- package/dist/esm/index.mjs.map +4 -4
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
|
-
**@randajan/pulse** is a tiny, zero‑dependency scheduler that fires your callback
|
|
12
|
+
**@randajan/pulse** is a tiny, zero‑dependency scheduler that fires your callback on aligned or relative intervals. It focuses on precision, clarity and graceful error handling, so you can drive heart‑beat logic, telemetry or background maintenance tasks with just a few lines of code.
|
|
13
13
|
|
|
14
14
|
The package ships dual builds—**ESM** *and* **CommonJS**—and works the same in Node.js and the browser.
|
|
15
15
|
|
|
@@ -32,10 +32,11 @@ import createPulse from "@randajan/pulse";
|
|
|
32
32
|
|
|
33
33
|
const pulse = createPulse({
|
|
34
34
|
interval: 1000, // 1 second
|
|
35
|
-
|
|
35
|
+
align: true, // align to wall-clock time
|
|
36
|
+
onPulse: (pulse, meta) => {
|
|
36
37
|
console.log("tick", meta.runtime, "ms");
|
|
37
38
|
},
|
|
38
|
-
onError:
|
|
39
|
+
onError: (pulse, meta) => console.error("Pulse error:", meta.error),
|
|
39
40
|
autoStart: true // start immediately
|
|
40
41
|
});
|
|
41
42
|
|
|
@@ -43,12 +44,14 @@ const pulse = createPulse({
|
|
|
43
44
|
setTimeout(() => pulse.stop(), 10_000);
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
Every time **onPulse** runs it receives a fresh metadata object:
|
|
47
|
+
Every time **onPulse** runs it receives the pulse instance and a fresh metadata object:
|
|
47
48
|
|
|
48
49
|
```ts
|
|
49
50
|
interface PulseMeta {
|
|
51
|
+
readonly id: number;
|
|
50
52
|
readonly started: Date;
|
|
51
53
|
readonly runtime: number; // ms since started
|
|
54
|
+
readonly lateBy: number; // ms over interval runtime
|
|
52
55
|
readonly warnings: string[];
|
|
53
56
|
warn(w: string | Error): void; // push warning
|
|
54
57
|
}
|
|
@@ -61,17 +64,61 @@ interface PulseMeta {
|
|
|
61
64
|
| Option | Type | Required | Default | Description |
|
|
62
65
|
| ----------- | ---------- | -------- | ------------------ | ------------------------------------------------------------------ |
|
|
63
66
|
| `interval` | `number` | ✔︎ | — | Period in ms. Min **10 ms**, max **2 147 483 647 ms** (\~24 days). |
|
|
64
|
-
| `
|
|
67
|
+
| `align` | `boolean` | ✔︎ | — | If `true`, pulse is aligned to wall-clock interval edges. If `false`, pulse runs relatively from start. |
|
|
68
|
+
| `offset` | `number` | | `0` | Timing offset. Range and meaning depend on `align`; see below. |
|
|
69
|
+
| `maxRuns` | `number` | | `0` | Maximum number of pulses before automatic stop. `0` means unlimited. |
|
|
65
70
|
| `getNow` | `function` | | `() => Date.now()` | Custom clock—handy for deterministic tests or time travel. |
|
|
66
71
|
| `autoStart` | `boolean` | | `false` | If `true`, the scheduler starts right after construction. |
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
72
|
+
| `runOnStart` | `boolean` | | `false` | If `true`, the first pulse is scheduled immediately on start. |
|
|
73
|
+
| `noMeta` | `boolean` | | `false` | If `true`, callbacks receive the numeric pulse id instead of `PulseMeta`. |
|
|
74
|
+
| `onPulse` | `function` | ✔︎ | — | Async/sync callback executed on each pulse. Receives `(pulse, meta)`. |
|
|
75
|
+
| `onError` | `function` | | `() => {}` | Called when `onPulse` throws or rejects. Receives `(pulse, meta)`, or `(pulse, error, id)` with `noMeta: true`. |
|
|
70
76
|
| `onStart` | `function` | | `() => {}` | Called right before pulse starts. |
|
|
71
77
|
| `onStop` | `function` | | `() => {}` | Called right after pulse stops. |
|
|
72
|
-
| `afterPulse` | `function` | | `() => {}` | Async/sync callback executed after each pulse even if
|
|
78
|
+
| `afterPulse` | `function` | | `() => {}` | Async/sync callback executed after each pulse even if `onPulse` raises an error. Receives `(pulse, meta)`. |
|
|
73
79
|
|
|
74
80
|
|
|
81
|
+
### `align` and `offset`
|
|
82
|
+
|
|
83
|
+
With `align: true`, pulses are scheduled on wall-clock interval edges shifted by `offset`. In this mode `offset` must be between `0` and `interval`.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
createPulse({
|
|
87
|
+
align: true,
|
|
88
|
+
interval: 60_000,
|
|
89
|
+
offset: 30_000,
|
|
90
|
+
onPulse: () => console.log("fires around every minute at :30")
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
With `align: false`, the first pulse is delayed by `interval + offset`; every following pulse uses `interval`. In this mode `offset` can be negative down to `-interval`, or positive as long as `interval + offset` does not exceed the maximum JavaScript timer delay.
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
createPulse({
|
|
98
|
+
align: false,
|
|
99
|
+
interval: 5_000,
|
|
100
|
+
offset: -4_000,
|
|
101
|
+
onPulse: () => console.log("first fire after 1s, then every 5s")
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
In Node.js, Pulse calls `timeout.unref?.()` for its internal timers. A running pulse will not keep an otherwise idle Node process alive.
|
|
106
|
+
|
|
107
|
+
### `runOnStart`
|
|
108
|
+
|
|
109
|
+
With `runOnStart: true`, the first pulse is scheduled with a `0` ms delay when `start()` is called. The callback still runs asynchronously through the scheduler, not directly inside `start()`.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
createPulse({
|
|
113
|
+
align: false,
|
|
114
|
+
interval: 60_000,
|
|
115
|
+
runOnStart: true,
|
|
116
|
+
onPulse: () => console.log("runs now, then every minute")
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`runOnStart` only affects the initial plan after `start()`. After the first pulse completes, the next pulse is scheduled normally: aligned to the wall-clock grid when `align: true`, or after `interval` when `align: false`.
|
|
121
|
+
|
|
75
122
|
---
|
|
76
123
|
|
|
77
124
|
## API
|
|
@@ -85,17 +132,19 @@ interface PulseMeta {
|
|
|
85
132
|
| `pulse.restart()` | `boolean` | Call pulse.stop() and then pulse.start() |
|
|
86
133
|
| `pulse.reset()` | `boolean` | Resets nextId counter |
|
|
87
134
|
| `pulse.state` | `boolean` | `true` = running, `false` = stopped (read-only) |
|
|
88
|
-
| `pulse.
|
|
135
|
+
| `pulse.align` | `boolean` | Alignment mode (read-only). |
|
|
89
136
|
| `pulse.interval` | `number` | Interval in ms (read‑only). |
|
|
90
137
|
| `pulse.offset` | `number` | Offset in ms (read‑only). |
|
|
91
|
-
| `pulse.countdown` | `number` |
|
|
92
|
-
| `pulse.
|
|
138
|
+
| `pulse.countdown` | `number\|undefined` | Real ms remaining to the currently scheduled pulse; `undefined` while stopped. |
|
|
139
|
+
| `pulse.potential` | `number` | Real countdown while running, otherwise ms that would be used by the next `start()`; returns `0` when `runOnStart` applies. |
|
|
140
|
+
| `pulse.nextAt` | `number\|undefined` | Timestamp of the next scheduled pulse while running. |
|
|
141
|
+
| `pulse.last` | `any\|PulseMeta` | Last callback result with `noMeta: true`; otherwise last metadata. |
|
|
93
142
|
|
|
94
143
|
---
|
|
95
144
|
|
|
96
145
|
## Why not `setInterval`?
|
|
97
146
|
|
|
98
|
-
`setInterval` drifts: extra work inside the callback pushes the next tick further, eventually desynchronising your schedule. **Pulse** recalculates the next edge *after* each run and keeps the loop tightly aligned to the
|
|
147
|
+
`setInterval` drifts: extra work inside the callback pushes the next tick further, eventually desynchronising your schedule. With `align: true`, **Pulse** recalculates the next edge *after* each run and keeps the loop tightly aligned to the wall-clock grid. With `align: false`, it runs as a promise-friendly relative scheduler.
|
|
99
148
|
|
|
100
149
|
---
|
|
101
150
|
|
|
@@ -107,8 +156,9 @@ JavaScript timers accept at most **2 147 483 647 ms**. To wait longer, ch
|
|
|
107
156
|
|
|
108
157
|
```js
|
|
109
158
|
createPulse({
|
|
159
|
+
align: false,
|
|
110
160
|
interval: 2_147_483_647,
|
|
111
|
-
onPulse: async meta => {
|
|
161
|
+
onPulse: async (pulse, meta) => {
|
|
112
162
|
if (stillWaiting()) return; // keep sleeping…
|
|
113
163
|
// …real logic here
|
|
114
164
|
},
|
|
@@ -122,4 +172,3 @@ createPulse({
|
|
|
122
172
|
## License
|
|
123
173
|
|
|
124
174
|
MIT © [randajan](https://github.com/randajan)
|
|
125
|
-
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
29
29
|
// src/Pulse.js
|
|
30
|
-
var
|
|
30
|
+
var import_props3 = require("@randajan/props");
|
|
31
31
|
|
|
32
32
|
// src/const.js
|
|
33
33
|
var _pulses = /* @__PURE__ */ new Map();
|
|
@@ -62,17 +62,172 @@ var validRange = (min, max, any, req = false, msg = "argument") => {
|
|
|
62
62
|
return num;
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
// src/
|
|
65
|
+
// src/static/options.js
|
|
66
|
+
var formatOptions = (options) => {
|
|
67
|
+
const o = {};
|
|
68
|
+
o.getNow = valid("function", options.getNow, false, "options.getNow") || ((_) => Date.now());
|
|
69
|
+
o.align = valid("boolean", options.align, true, "options.align");
|
|
70
|
+
const minInterval = 10;
|
|
71
|
+
const maxInterval = 2147483647;
|
|
72
|
+
o.interval = validRange(minInterval, maxInterval, options.interval, true, "options.interval");
|
|
73
|
+
o.runOnStart = valid("boolean", options.runOnStart, false, "options.runOnStart") || false;
|
|
74
|
+
const offsetMsg = `options.offset (align:${o.align})`;
|
|
75
|
+
if (o.align) {
|
|
76
|
+
o.offset = validRange(0, o.interval, options.offset, false, offsetMsg) ?? 0;
|
|
77
|
+
} else if (!o.runOnStart) {
|
|
78
|
+
o.offset = validRange(-o.interval, maxInterval - o.interval, options.offset, false, offsetMsg) ?? 0;
|
|
79
|
+
} else if (options.offset != null) {
|
|
80
|
+
throw new Error(`${offsetMsg} can't be set if runOnStart:true`);
|
|
81
|
+
} else {
|
|
82
|
+
o.offset = -o.interval;
|
|
83
|
+
}
|
|
84
|
+
o.maxRuns = validRange(0, Infinity, options.maxRuns, false, "options.maxRuns") ?? 0;
|
|
85
|
+
o.onPulse = valid("function", options.onPulse, true, "options.onPulse");
|
|
86
|
+
o.onStart = valid("function", options.onStart, false, "options.onStart") || blankFn;
|
|
87
|
+
o.onStop = valid("function", options.onStop, false, "options.onStop") || blankFn;
|
|
88
|
+
o.onError = valid("function", options.onError, false, "options.onError") || blankFn;
|
|
89
|
+
o.afterPulse = valid("function", options.afterPulse, false, "options.afterPulse") || blankFn;
|
|
90
|
+
o.autoStart = valid("boolean", options.autoStart, false, "options.autoStart") || false;
|
|
91
|
+
o.noMeta = valid("boolean", options.noMeta, false, "option.noMeta") || false;
|
|
92
|
+
return Object.freeze(o);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/static/PulsePrivate.js
|
|
96
|
+
var import_props2 = require("@randajan/props");
|
|
97
|
+
|
|
98
|
+
// src/static/metadata.js
|
|
66
99
|
var import_props = require("@randajan/props");
|
|
67
|
-
var createMetaData = (
|
|
68
|
-
const
|
|
100
|
+
var createMetaData = (_p, id) => {
|
|
101
|
+
const md = {};
|
|
69
102
|
const w = [];
|
|
70
|
-
(0, import_props.solid)(
|
|
71
|
-
(0, import_props.solid)(
|
|
72
|
-
(0, import_props.virtual)(
|
|
73
|
-
(0, import_props.virtual)(
|
|
74
|
-
(0, import_props.
|
|
75
|
-
|
|
103
|
+
(0, import_props.solid)(md, "id", id);
|
|
104
|
+
(0, import_props.solid)(md, "started", _p.opt.getNow());
|
|
105
|
+
(0, import_props.virtual)(md, "warnings", (_) => [...w]);
|
|
106
|
+
(0, import_props.virtual)(md, "runtime", (_) => (md.ended ? md.ended : _p.opt.getNow()) - md.started);
|
|
107
|
+
(0, import_props.virtual)(md, "lateBy", (_) => Math.max(0, md.runtime - _p.opt.interval));
|
|
108
|
+
(0, import_props.solid)(md, "warn", (warning) => w.push(warning), false);
|
|
109
|
+
return md;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/static/PulsePrivate.js
|
|
113
|
+
var PulsePrivate = class {
|
|
114
|
+
constructor(pulse, options) {
|
|
115
|
+
this.pulse = pulse;
|
|
116
|
+
this.state = false;
|
|
117
|
+
this.nextId = 0;
|
|
118
|
+
this.opt = formatOptions(options);
|
|
119
|
+
this.run = this.run.bind(this);
|
|
120
|
+
}
|
|
121
|
+
getCountdown() {
|
|
122
|
+
const { state, fireAt, opt: { getNow } } = this;
|
|
123
|
+
if (!state || fireAt == null) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
return Math.max(0, fireAt - getNow());
|
|
127
|
+
}
|
|
128
|
+
getPotential(isInit = true) {
|
|
129
|
+
const { opt: { align, runOnStart, interval, offset, getNow } } = this;
|
|
130
|
+
if (isInit && runOnStart) {
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (!align) {
|
|
134
|
+
return isInit ? interval + offset : interval;
|
|
135
|
+
}
|
|
136
|
+
const now = getNow() - offset;
|
|
137
|
+
const rest = (now % interval + interval) % interval;
|
|
138
|
+
return rest === 0 ? interval : interval - rest;
|
|
139
|
+
}
|
|
140
|
+
reset() {
|
|
141
|
+
this.nextId = 0;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
start(reset = false) {
|
|
145
|
+
const { pulse, state, timeoutId, opt: { onStart } } = this;
|
|
146
|
+
if (state) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
onStart(pulse);
|
|
150
|
+
if (reset) {
|
|
151
|
+
this.reset();
|
|
152
|
+
}
|
|
153
|
+
this.state = true;
|
|
154
|
+
return this.plan(true);
|
|
155
|
+
}
|
|
156
|
+
stop(reset = false) {
|
|
157
|
+
const { pulse, state, timeoutId, opt: { onStop } } = this;
|
|
158
|
+
if (!state) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
clearTimeout(timeoutId);
|
|
162
|
+
this.state = false;
|
|
163
|
+
delete this.fireAt;
|
|
164
|
+
if (reset) {
|
|
165
|
+
this.reset();
|
|
166
|
+
}
|
|
167
|
+
onStop(pulse);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
plan(isInit) {
|
|
171
|
+
const { state, metaData, nextId, opt: { maxRuns, getNow } } = this;
|
|
172
|
+
if (!state || metaData != null) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (maxRuns && maxRuns <= nextId) {
|
|
176
|
+
this.stop(true);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const fireIn = this.getPotential(isInit);
|
|
180
|
+
this.fireAt = getNow() + fireIn;
|
|
181
|
+
this.timeoutId = setTimeout(this.run, fireIn);
|
|
182
|
+
this.timeoutId.unref?.();
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
async run() {
|
|
186
|
+
const { pulse, state, metaData, opt: { noMeta, getNow, onPulse, onError, afterPulse } } = this;
|
|
187
|
+
if (!state || metaData) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const id = this.nextId++;
|
|
191
|
+
const md = this.metaData = noMeta ? id : createMetaData(this, id);
|
|
192
|
+
let errA, errB, result;
|
|
193
|
+
try {
|
|
194
|
+
result = await onPulse(pulse, md);
|
|
195
|
+
if (!noMeta) {
|
|
196
|
+
(0, import_props2.solid)(md, "result", result);
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
errA = err;
|
|
200
|
+
if (!noMeta) {
|
|
201
|
+
(0, import_props2.solid)(md, "error", errA);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!noMeta) {
|
|
205
|
+
(0, import_props2.solid)(md, "ended", getNow());
|
|
206
|
+
}
|
|
207
|
+
if (errA != null) {
|
|
208
|
+
if (noMeta) {
|
|
209
|
+
onError(pulse, errA, md);
|
|
210
|
+
} else {
|
|
211
|
+
onError(pulse, md);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
await afterPulse(pulse, md);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
errB = err;
|
|
218
|
+
}
|
|
219
|
+
this.last = noMeta ? result : md;
|
|
220
|
+
delete this.metaData;
|
|
221
|
+
if (errB != null) {
|
|
222
|
+
this.stop();
|
|
223
|
+
throw errB;
|
|
224
|
+
}
|
|
225
|
+
this.plan(false);
|
|
226
|
+
}
|
|
227
|
+
restart(reset) {
|
|
228
|
+
this.stop(reset);
|
|
229
|
+
return this.start(reset);
|
|
230
|
+
}
|
|
76
231
|
};
|
|
77
232
|
|
|
78
233
|
// src/Pulse.js
|
|
@@ -82,78 +237,19 @@ var Pulse = class {
|
|
|
82
237
|
* @param {PulseOptions} [options={}] See {@link PulseOptions}.
|
|
83
238
|
*/
|
|
84
239
|
constructor(options = {}) {
|
|
85
|
-
const _p =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
const getNow = valid("function", options.getNow, false, "options.getNow") || ((_) => Date.now());
|
|
90
|
-
const interval = validRange(10, 2147483647, options.interval, true, "options.interval");
|
|
91
|
-
const offset = validRange(0, interval, options.offset, false, "options.offset") || 0;
|
|
92
|
-
const onPulse = valid("function", options.onPulse, true, "options.onPulse");
|
|
93
|
-
_p.onStart = valid("function", options.onStart, false, "options.onStart") || blankFn;
|
|
94
|
-
_p.onStop = valid("function", options.onStop, false, "options.onStop") || blankFn;
|
|
95
|
-
const onError = valid("function", options.onError, false, "options.onError") || blankFn;
|
|
96
|
-
const afterPulse = valid("function", options.afterPulse, false, "options.afterPulse") || blankFn;
|
|
97
|
-
const autoStart = valid("boolean", options.autoStart, false, "options.autoStart") || false;
|
|
98
|
-
const noMeta = valid("boolean", options.noMeta, false, "option.noMeta") || false;
|
|
99
|
-
_p.reset = (_) => {
|
|
100
|
-
_p.nextId = 0;
|
|
101
|
-
};
|
|
102
|
-
_p.plan = (_) => {
|
|
103
|
-
if (!_p.state || _p.metaData != null) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
_p.timeoutId = setTimeout(_p.run, this.countdown);
|
|
107
|
-
};
|
|
108
|
-
_p.run = async (_) => {
|
|
109
|
-
if (!_p.state || _p.metaData) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const id = _p.nextId++;
|
|
113
|
-
const md = _p.metaData = noMeta ? id : createMetaData(id, getNow);
|
|
114
|
-
let errA, errB, result;
|
|
115
|
-
try {
|
|
116
|
-
result = await onPulse(this, md);
|
|
117
|
-
if (!noMeta) {
|
|
118
|
-
(0, import_props2.solid)(md, "result", result);
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
errA = err;
|
|
122
|
-
if (!noMeta) {
|
|
123
|
-
(0, import_props2.solid)(md, "error", errA);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (!noMeta) {
|
|
127
|
-
(0, import_props2.solid)(md, "ended", getNow());
|
|
128
|
-
}
|
|
129
|
-
if (errA != null) {
|
|
130
|
-
if (noMeta) {
|
|
131
|
-
onError(this, errA, md);
|
|
132
|
-
} else {
|
|
133
|
-
onError(this, md);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
await afterPulse(this, md);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
errB = err;
|
|
140
|
-
}
|
|
141
|
-
_p.last = noMeta ? result : md;
|
|
142
|
-
delete _p.metaData;
|
|
143
|
-
if (errB != null) {
|
|
144
|
-
_p.state = false;
|
|
145
|
-
throw errB;
|
|
146
|
-
}
|
|
147
|
-
_p.plan();
|
|
148
|
-
};
|
|
149
|
-
(0, import_props2.virtuals)(this, {
|
|
240
|
+
const _p = new PulsePrivate(this, options);
|
|
241
|
+
const { getNow, align, interval, offset, autoStart } = _p.opt;
|
|
242
|
+
(0, import_props3.virtuals)(this, {
|
|
150
243
|
state: (_) => _p.state,
|
|
151
244
|
last: (_) => _p.last,
|
|
152
|
-
countdown: (_) =>
|
|
245
|
+
countdown: (_) => _p.getCountdown(),
|
|
246
|
+
potential: (_) => _p.getCountdown() ?? _p.getPotential(),
|
|
247
|
+
nextAt: (_) => _p.state ? this.potential + getNow() : void 0
|
|
153
248
|
});
|
|
154
|
-
(0,
|
|
249
|
+
(0, import_props3.solids)(this, {
|
|
155
250
|
interval,
|
|
156
|
-
offset
|
|
251
|
+
offset,
|
|
252
|
+
align
|
|
157
253
|
});
|
|
158
254
|
_pulses.set(this, _p);
|
|
159
255
|
if (autoStart) {
|
|
@@ -165,55 +261,29 @@ var Pulse = class {
|
|
|
165
261
|
* @returns {boolean} `true` if it started now, `false` if it was already running.
|
|
166
262
|
*/
|
|
167
263
|
start(reset = false) {
|
|
168
|
-
|
|
169
|
-
if (state) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
const _p = _pulses.get(this);
|
|
173
|
-
_p.onStart(this);
|
|
174
|
-
if (reset) {
|
|
175
|
-
_p.reset();
|
|
176
|
-
}
|
|
177
|
-
_p.state = true;
|
|
178
|
-
_p.plan();
|
|
179
|
-
return true;
|
|
264
|
+
return _pulses.get(this).start(reset);
|
|
180
265
|
}
|
|
181
266
|
/**
|
|
182
267
|
* Stop the pulse loop (no-op if already stopped).
|
|
183
268
|
* @returns {boolean} `true` if it stopped now, `false` if it was already stopped.
|
|
184
269
|
*/
|
|
185
270
|
stop(reset = false) {
|
|
186
|
-
|
|
187
|
-
if (!state) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
const _p = _pulses.get(this);
|
|
191
|
-
clearTimeout(_p.timeoutId);
|
|
192
|
-
_p.state = false;
|
|
193
|
-
if (reset) {
|
|
194
|
-
_p.reset();
|
|
195
|
-
}
|
|
196
|
-
_p.onStop(this);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
reset() {
|
|
200
|
-
_pulses.get(this).reset();
|
|
201
|
-
return true;
|
|
271
|
+
return _pulses.get(this).stop(reset);
|
|
202
272
|
}
|
|
203
273
|
restart(reset = false) {
|
|
204
|
-
this.
|
|
205
|
-
|
|
274
|
+
return _pulses.get(this).restart(reset);
|
|
275
|
+
}
|
|
276
|
+
reset() {
|
|
277
|
+
return _pulses.get(this).reset();
|
|
206
278
|
}
|
|
207
279
|
};
|
|
208
280
|
|
|
209
|
-
// src/
|
|
281
|
+
// src/index.js
|
|
210
282
|
var createPulse = (options = {}) => new Pulse(options);
|
|
211
283
|
var stopAllPulses = () => {
|
|
212
284
|
for (const [pulse, _pulse] of _pulses.entries()) {
|
|
213
285
|
pulse.stop();
|
|
214
286
|
}
|
|
215
287
|
};
|
|
216
|
-
|
|
217
|
-
// src/index.js
|
|
218
288
|
var index_default = createPulse;
|
|
219
289
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.js", "../../src/Pulse.js", "../../src/const.js", "../../src/tools.js", "../../src/
|
|
4
|
-
"sourcesContent": ["import { Pulse } from \"./Pulse\";\nimport { createPulse, stopAllPulses } from \"./static\";\n\nexport default createPulse;\n\nexport { createPulse, stopAllPulses, Pulse }\n", "import { solid, solids, virtuals } from \"@randajan/props\";\r\nimport { blankFn, _pulses } from \"./const\";\r\nimport { valid, validRange } from \"./tools\";\r\nimport { createMetaData } from \"./metadata\";\r\n\r\n/**\r\n * Periodic scheduler that aligns each callback to exact multiples\r\n * of the given interval (optionally shifted by `offset`).\r\n */\r\nexport class Pulse {\r\n\r\n /**\r\n * Create a new Pulse instance.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n */\r\n constructor(options = {}) {\r\n const _p = {\r\n state: false,\r\n nextId:0\r\n }\r\n\r\n const getNow = valid(\"function\", options.getNow, false, \"options.getNow\") || (_ => Date.now());\r\n\r\n const interval = validRange(10, 2_147_483_647, options.interval, true, \"options.interval\");\r\n const offset = (validRange(0, interval, options.offset, false, \"options.offset\") || 0);\r\n\r\n const onPulse = valid(\"function\", options.onPulse, true, \"options.onPulse\");\r\n _p.onStart = valid(\"function\", options.onStart, false, \"options.onStart\") || blankFn;\r\n _p.onStop = valid(\"function\", options.onStop, false, \"options.onStop\") || blankFn;\r\n \r\n const onError = valid(\"function\", options.onError, false, \"options.onError\") || blankFn;\r\n \r\n const afterPulse = valid(\"function\", options.afterPulse, false, \"options.afterPulse\") || blankFn;\r\n const autoStart = valid(\"boolean\", options.autoStart, false, \"options.autoStart\") || false;\r\n const noMeta = valid(\"boolean\", options.noMeta, false, \"option.noMeta\") || false;\r\n\r\n _p.reset = _=>{\r\n _p.nextId = 0;\r\n }\r\n\r\n _p.plan = _ => {\r\n if (!_p.state || _p.metaData != null) { return; }\r\n _p.timeoutId = setTimeout(_p.run, this.countdown);\r\n }\r\n\r\n _p.run = async _ => {\r\n if (!_p.state || _p.metaData) { return; }\r\n\r\n const id = _p.nextId++;\r\n const md = _p.metaData = noMeta ? id : createMetaData(id, getNow);\r\n let errA, errB, result;\r\n\r\n try {\r\n result = await onPulse(this, md);\r\n if (!noMeta) { solid(md, \"result\", result); }\r\n }\r\n catch (err) {\r\n errA = err;\r\n if (!noMeta) { solid(md, \"error\", errA); }\r\n }\r\n\r\n if (!noMeta) { solid(md, \"ended\", getNow()); }\r\n\r\n if (errA != null) {\r\n if (noMeta) { onError(this, errA, md); }\r\n else { onError(this, md); }\r\n }\r\n\r\n try { await afterPulse(this, md); }\r\n catch(err) { errB = err; }\r\n\r\n _p.last = noMeta ? result : md;\r\n delete _p.metaData;\r\n\r\n if (errB != null) {\r\n _p.state = false;\r\n throw errB;\r\n }\r\n \r\n _p.plan();\r\n }\r\n\r\n virtuals(this, {\r\n state:_=>_p.state,\r\n last:_=>_p.last,\r\n countdown:_=>((interval - getNow() % interval) + offset)\r\n });\r\n\r\n solids(this, {\r\n interval,\r\n offset\r\n });\r\n\r\n _pulses.set(this, _p);\r\n\r\n if (autoStart) { this.start(); }\r\n }\r\n\r\n /**\r\n * Start the pulse loop (no-op if already running).\r\n * @returns {boolean} `true` if it started now, `false` if it was already running.\r\n */\r\n start(reset=false) {\r\n const { state } = this;\r\n if (state) { return false; }\r\n const _p = _pulses.get(this);\r\n _p.onStart(this);\r\n if (reset) { _p.reset(); }\r\n _p.state = true;\r\n _p.plan();\r\n return true;\r\n }\r\n\r\n /**\r\n * Stop the pulse loop (no-op if already stopped).\r\n * @returns {boolean} `true` if it stopped now, `false` if it was already stopped.\r\n */\r\n stop(reset=false) {\r\n const { state } = this;\r\n if (!state) { return false; }\r\n const _p = _pulses.get(this);\r\n clearTimeout(_p.timeoutId);\r\n _p.state = false;\r\n if (reset) { _p.reset(); }\r\n _p.onStop(this);\r\n return true;\r\n }\r\n\r\n reset() {\r\n _pulses.get(this).reset();\r\n return true;\r\n }\r\n\r\n restart(reset=false) {\r\n this.stop(reset);\r\n return this.start(reset);\r\n }\r\n\r\n\r\n}", "\r\n\r\n\r\nexport const _pulses = new Map();\r\n\r\nexport const blankFn = ()=>{};", "\r\n\r\nexport const is = (type, any)=>typeof any === type;\r\n\r\nexport const valid = (type, any, req=false, msg=\"argument\")=>{\r\n if (any == null) {\r\n if (!req) { return; }\r\n throw new Error(`${msg} require typeof '${type}'`);\r\n }\r\n if (is(type, any)) { return any; }\r\n throw new Error(`${msg} is not typeof '${type}'`);\r\n}\r\n\r\nexport const validRange = (min, max, any, req=false, msg=\"argument\")=>{\r\n const num = valid(\"number\", any, req, msg);\r\n if (num == null) { return; }\r\n if (num < min) { throw new Error(`${msg} must be greater than ${min}`); }\r\n if (num > max) { throw new Error(`${msg} must be less than ${max}`); }\r\n return num;\r\n}", "import { solid, virtual } from \"@randajan/props\";\r\n\r\n\r\n/**\r\n * Metadata object passed to every pulse callback.\r\n * @typedef {Object} PulseMeta\r\n * @property {Date} started Timestamp when this pulse began.\r\n * @property {string[]} warnings Snapshot of all warnings collected so far.\r\n * @property {number} runtime Milliseconds elapsed since `started`.\r\n * @property {(warning: string|Error) => void} warn Push a warning into the internal list.\r\n */\r\nexport const createMetaData = (id, getNow) => {\r\n const p = {};\r\n const w = [];\r\n solid(p, \"id\", id);\r\n solid(p, \"started\", getNow());\r\n virtual(p, \"warnings\", _ => [...w]);\r\n virtual(p, \"runtime\", _ => (p.ended ? p.ended : getNow()) - p.started);\r\n solid(p, \"warn\", warning => w.push(warning), false);\r\n return p;\r\n}\r\n\r\n/**\r\n * Configuration object for {@link Pulse}.\r\n * @typedef {Object} PulseOptions\r\n * @property {(meta: PulseMeta) => (void|Promise<void>)} onPulse **required** \u2013 user callback invoked on every pulse.\r\n * @property {number} interval **required** \u2013 period length in ms (50 \u2013 2 147 483 647).\r\n * @property {number} [offset=0] Shift in ms applied to the start of each interval (0 \u2264 offset < interval).\r\n * @property {() => number} [getNow=() => Date.now()] Custom time source, useful for tests or time travel.\r\n * @property {(err: unknown) => void} [onError=blankFn] Error handler for exceptions or rejected `onPulse` promises.\r\n * @property {boolean} [autoStart=false] If true, the loop starts immediately after construction.\r\n */", "import { _pulses } from \"./const\";\r\nimport { Pulse } from \"./Pulse.js\";\r\n\r\n\r\n/**\r\n * Factory helper \u2013 simply calls `new Pulse(options)`.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n * @returns {Pulse}\r\n */\r\nexport const createPulse = (options = {}) => new Pulse(options);\r\n\r\n/**\r\n * Process exit helper \u2013 stops all pending pulses.\r\n */\r\n\r\nexport const stopAllPulses = ()=>{\r\n for (const [pulse, _pulse] of _pulses.entries()) {\r\n pulse.stop();\r\n }\r\n}\r\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,gBAAwC;;;ACGjC,IAAM,UAAU,oBAAI,IAAI;AAExB,IAAM,UAAU,MAAI;AAAC;;;ACHrB,IAAM,KAAK,CAAC,MAAM,QAAM,OAAO,QAAQ;AAEvC,IAAM,QAAQ,CAAC,MAAM,KAAK,MAAI,OAAO,MAAI,eAAa;AACzD,MAAI,OAAO,MAAM;AACb,QAAI,CAAC,KAAK;AAAE;AAAA,IAAQ;AACpB,UAAM,IAAI,MAAM,GAAG,GAAG,oBAAoB,IAAI,GAAG;AAAA,EACrD;AACA,MAAI,GAAG,MAAM,GAAG,GAAG;AAAE,WAAO;AAAA,EAAK;AACjC,QAAM,IAAI,MAAM,GAAG,GAAG,mBAAmB,IAAI,GAAG;AACpD;AAEO,IAAM,aAAa,CAAC,KAAK,KAAK,KAAK,MAAI,OAAO,MAAI,eAAa;AAClE,QAAM,MAAM,MAAM,UAAU,KAAK,KAAK,GAAG;AACzC,MAAI,OAAO,MAAM;AAAE;AAAA,EAAQ;AAC3B,MAAI,MAAM,KAAK;AAAE,UAAM,IAAI,MAAM,GAAG,GAAG,yBAAyB,GAAG,EAAE;AAAA,EAAG;AACxE,MAAI,MAAM,KAAK;AAAE,UAAM,IAAI,MAAM,GAAG,GAAG,sBAAsB,GAAG,EAAE;AAAA,EAAG;AACrE,SAAO;AACX;;;
|
|
6
|
-
"names": ["import_props"]
|
|
3
|
+
"sources": ["../../src/index.js", "../../src/Pulse.js", "../../src/const.js", "../../src/tools.js", "../../src/static/options.js", "../../src/static/PulsePrivate.js", "../../src/static/metadata.js"],
|
|
4
|
+
"sourcesContent": ["import { Pulse } from \"./Pulse\";\nimport { _pulses } from \"./const\";\n\n\nexport {\n Pulse\n}\n\n/**\n * Factory helper \u2013 simply calls `new Pulse(options)`.\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\n * @returns {Pulse}\n */\nexport const createPulse = (options = {}) => new Pulse(options);\n\n\n/**\n * Process exit helper \u2013 stops all pending pulses.\n */\nexport const stopAllPulses = ()=>{\n for (const [pulse, _pulse] of _pulses.entries()) {\n pulse.stop();\n }\n}\n\nexport default createPulse;\n", "import { solid, solids, virtuals } from \"@randajan/props\";\nimport { _pulses } from \"./const\";\nimport { formatOptions } from \"./static/options\";\nimport { PulsePrivate } from \"./static/PulsePrivate\";\n\r\n/**\r\n * Periodic scheduler that aligns each callback to exact multiples\r\n * of the given interval (optionally shifted by `offset`).\r\n */\r\nexport class Pulse {\r\n\r\n /**\r\n * Create a new Pulse instance.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n */\r\n constructor(options = {}) {\r\n const _p = new PulsePrivate(this, options);\r\n\r\n const { getNow, align, interval, offset, autoStart } = _p.opt;\r\n\r\n virtuals(this, {\r\n state:_=>_p.state,\r\n last:_=>_p.last,\r\n countdown:_=>_p.getCountdown(),\r\n potential:_=>_p.getCountdown() ?? _p.getPotential(),\r\n nextAt:_=>_p.state ? this.potential + getNow() : undefined\r\n });\r\n\r\n solids(this, {\r\n interval,\r\n offset,\r\n align\r\n });\r\n\r\n _pulses.set(this, _p);\r\n\r\n if (autoStart) { this.start(); }\r\n }\r\n\r\n /**\r\n * Start the pulse loop (no-op if already running).\r\n * @returns {boolean} `true` if it started now, `false` if it was already running.\r\n */\r\n start(reset=false) {\r\n return _pulses.get(this).start(reset);\r\n }\r\n\r\n /**\r\n * Stop the pulse loop (no-op if already stopped).\r\n * @returns {boolean} `true` if it stopped now, `false` if it was already stopped.\r\n */\r\n stop(reset=false) {\r\n return _pulses.get(this).stop(reset);\r\n }\r\n\r\n restart(reset=false) {\r\n return _pulses.get(this).restart(reset);\r\n }\r\n\r\n reset() {\r\n return _pulses.get(this).reset();\r\n }\r\n\r\n}\r\n", "\r\n\r\n\r\nexport const _pulses = new Map();\r\n\r\nexport const blankFn = ()=>{};", "\n\nexport const is = (type, any)=>typeof any === type;\n\nexport const valid = (type, any, req=false, msg=\"argument\")=>{\n if (any == null) {\n if (!req) { return; }\n throw new Error(`${msg} require typeof '${type}'`);\n }\n if (is(type, any)) { return any; }\n throw new Error(`${msg} is not typeof '${type}'`);\n}\n\nexport const validRange = (min, max, any, req=false, msg=\"argument\")=>{\n const num = valid(\"number\", any, req, msg);\n if (num == null) { return; }\n if (num < min) { throw new Error(`${msg} must be greater than ${min}`); }\n if (num > max) { throw new Error(`${msg} must be less than ${max}`); }\n return num;\n}", "import { blankFn } from \"../const\";\nimport { valid, validRange } from \"../tools\";\n\n\n\n/**\n * Configuration object for {@link Pulse}.\n * @typedef {Object} PulseOptions\n * @property {(pulse: import(\"../Pulse\").Pulse, meta: import(\"./metadata\").PulseMeta|number) => (*|Promise<*>)} onPulse **required** \u2013 user callback invoked on every pulse.\n * @property {boolean} align **required** \u2013 align to wall-clock interval edges when true, run relatively when false.\n * @property {number} interval **required** \u2013 period length in ms (10 \u2013 2 147 483 647).\n * @property {number} [offset=0] Timing offset. Range and meaning depend on `align` and `runOnStart`.\n * @property {boolean} [runOnStart=false] Schedule the first pulse immediately on start.\n * @property {number} [maxRuns=0] Maximum number of pulses before automatic stop. `0` means unlimited.\n * @property {() => number} [getNow=() => Date.now()] Custom time source, useful for tests or time travel.\n * @property {boolean} [autoStart=false] If true, the loop starts immediately after construction.\n * @property {boolean} [noMeta=false] If true, callbacks receive the numeric pulse id instead of `PulseMeta`.\n * @property {(pulse: import(\"../Pulse\").Pulse) => void} [onStart=blankFn] Called right before the pulse loop starts.\n * @property {(pulse: import(\"../Pulse\").Pulse) => void} [onStop=blankFn] Called right after the pulse loop stops.\n * @property {Function} [onError=blankFn] Error handler for exceptions or rejected `onPulse` promises.\n * @property {(pulse: import(\"../Pulse\").Pulse, meta: import(\"./metadata\").PulseMeta|number) => (void|Promise<void>)} [afterPulse=blankFn] Callback executed after every pulse.\n */\nexport const formatOptions = (options) => {\n const o = {};\r\n\r\n o.getNow = valid(\"function\", options.getNow, false, \"options.getNow\") || (_ => Date.now());\r\n\r\n o.align = valid(\"boolean\", options.align, true, \"options.align\");\r\n\r\n const minInterval = 10;\r\n const maxInterval = 2_147_483_647;\r\n o.interval = validRange(minInterval, maxInterval, options.interval, true, \"options.interval\");\r\n\r\n o.runOnStart = valid(\"boolean\", options.runOnStart, false, \"options.runOnStart\") || false;\r\n\r\n const offsetMsg = `options.offset (align:${o.align})`;\r\n\r\n if (o.align) {\r\n o.offset = (validRange(0, o.interval, options.offset, false, offsetMsg) ?? 0);\r\n } else if (!o.runOnStart) {\r\n o.offset = (validRange(-o.interval, (maxInterval - o.interval), options.offset, false, offsetMsg) ?? 0);\r\n } else if (options.offset != null) {\n throw new Error(`${offsetMsg} can't be set if runOnStart:true`);\n } else {\n o.offset = -o.interval;\n }\r\n\r\n o.maxRuns = validRange(0, Infinity, options.maxRuns, false, \"options.maxRuns\") ?? 0;\r\n\r\n o.onPulse = valid(\"function\", options.onPulse, true, \"options.onPulse\");\r\n o.onStart = valid(\"function\", options.onStart, false, \"options.onStart\") || blankFn;\r\n o.onStop = valid(\"function\", options.onStop, false, \"options.onStop\") || blankFn;\r\n o.onError = valid(\"function\", options.onError, false, \"options.onError\") || blankFn;\r\n\r\n o.afterPulse = valid(\"function\", options.afterPulse, false, \"options.afterPulse\") || blankFn;\r\n o.autoStart = valid(\"boolean\", options.autoStart, false, \"options.autoStart\") || false;\r\n o.noMeta = valid(\"boolean\", options.noMeta, false, \"option.noMeta\") || false;\r\n\r\n return Object.freeze(o);\r\n}\n", "import { solid } from \"@randajan/props\";\r\nimport { createMetaData } from \"./metadata\";\r\nimport { formatOptions } from \"./options\";\r\n\r\n\r\nexport class PulsePrivate {\r\n\r\n constructor(pulse, options) {\r\n this.pulse = pulse;\r\n this.state = false;\r\n this.nextId = 0;\r\n this.opt = formatOptions(options);\r\n\r\n this.run = this.run.bind(this);\r\n }\r\n\r\n getCountdown() {\r\n const { state, fireAt, opt:{ getNow } } = this;\r\n if (!state || fireAt == null) { return; }\r\n return Math.max(0, fireAt - getNow());\r\n }\r\n\r\n getPotential(isInit = true) {\r\n const { opt:{ align, runOnStart, interval, offset, getNow } } = this;\r\n if (isInit && runOnStart) { return 0; }\r\n if (!align) { return isInit ? (interval + offset) : interval; }\r\n const now = getNow() - offset;\r\n const rest = ((now % interval) + interval) % interval;\r\n return rest === 0 ? interval : interval - rest;\r\n }\r\n\r\n reset() {\r\n this.nextId = 0;\r\n return true;\r\n }\r\n\r\n start(reset=false) {\r\n const { pulse, state, timeoutId, opt:{ onStart } } = this;\r\n if (state) { return false; }\r\n onStart(pulse);\r\n if (reset) { this.reset(); }\r\n this.state = true;\r\n return this.plan(true);\r\n }\r\n\r\n stop(reset=false) {\r\n const { pulse, state, timeoutId, opt:{ onStop } } = this;\r\n if (!state) { return false; }\r\n clearTimeout(timeoutId);\r\n this.state = false;\r\n delete this.fireAt;\r\n if (reset) { this.reset(); }\r\n onStop(pulse);\r\n return true;\r\n }\r\n\r\n plan(isInit) {\r\n const { state, metaData, nextId, opt:{ maxRuns, getNow } } = this;\r\n if (!state || metaData != null) { return false; }\r\n if (maxRuns && maxRuns <= nextId) { this.stop(true); return false; }\r\n const fireIn = this.getPotential(isInit);\r\n this.fireAt = getNow() + fireIn;\r\n this.timeoutId = setTimeout(this.run, fireIn);\r\n this.timeoutId.unref?.();\r\n return true;\r\n }\r\n\r\n async run() {\r\n const { pulse, state, metaData, opt:{ noMeta, getNow, onPulse, onError, afterPulse } } = this;\r\n if (!state || metaData) { return; }\r\n\r\n const id = this.nextId++;\r\n const md = this.metaData = noMeta ? id : createMetaData(this, id);\n let errA, errB, result;\r\n\r\n try {\r\n result = await onPulse(pulse, md);\r\n if (!noMeta) { solid(md, \"result\", result); }\r\n }\r\n catch (err) {\r\n errA = err;\r\n if (!noMeta) { solid(md, \"error\", errA); }\r\n }\r\n\r\n if (!noMeta) { solid(md, \"ended\", getNow()); }\r\n\r\n if (errA != null) {\r\n if (noMeta) { onError(pulse, errA, md); }\r\n else { onError(pulse, md); }\r\n }\r\n\r\n try { await afterPulse(pulse, md); }\r\n catch (err) { errB = err; }\r\n\r\n this.last = noMeta ? result : md;\r\n delete this.metaData;\r\n\r\n if (errB != null) {\r\n this.stop();\r\n throw errB;\r\n }\r\n\r\n this.plan(false);\r\n }\r\n\r\n restart(reset) {\r\n this.stop(reset);\r\n return this.start(reset);\r\n }\r\n\r\n}\n", "import { solid, virtual } from \"@randajan/props\";\r\n\r\n\r\n/**\n * Metadata object passed to every pulse callback.\n * @typedef {Object} PulseMeta\n * @property {number} id Sequential pulse id.\n * @property {number} started Timestamp when this pulse began.\n * @property {number} [ended] Timestamp when this pulse finished.\n * @property {number} runtime Milliseconds elapsed since `started`.\n * @property {number} lateBy Milliseconds over the configured interval.\n * @property {string[]|Error[]} warnings Snapshot of all warnings collected so far.\n * @property {*} [result] Value returned by `onPulse`.\n * @property {*} [error] Error thrown or rejected by `onPulse`.\n * @property {(warning: string|Error) => void} warn Push a warning into the internal list.\n */\nexport const createMetaData = (_p, id) => {\n const md = {};\n const w = [];\n solid(md, \"id\", id);\n solid(md, \"started\", _p.opt.getNow());\r\n virtual(md, \"warnings\", _ => [...w]);\r\n virtual(md, \"runtime\", _ => (md.ended ? md.ended : _p.opt.getNow()) - md.started);\r\n virtual(md, \"lateBy\", _=>Math.max(0, md.runtime-_p.opt.interval));\r\n solid(md, \"warn\", warning => w.push(warning), false);\r\n return md;\r\n}\r\n\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_props", "import_props"]
|
|
7
7
|
}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/Pulse.js
|
|
2
|
-
import { solid as
|
|
2
|
+
import { solid as solid3, solids, virtuals } from "@randajan/props";
|
|
3
3
|
|
|
4
4
|
// src/const.js
|
|
5
5
|
var _pulses = /* @__PURE__ */ new Map();
|
|
@@ -34,17 +34,172 @@ var validRange = (min, max, any, req = false, msg = "argument") => {
|
|
|
34
34
|
return num;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
// src/
|
|
37
|
+
// src/static/options.js
|
|
38
|
+
var formatOptions = (options) => {
|
|
39
|
+
const o = {};
|
|
40
|
+
o.getNow = valid("function", options.getNow, false, "options.getNow") || ((_) => Date.now());
|
|
41
|
+
o.align = valid("boolean", options.align, true, "options.align");
|
|
42
|
+
const minInterval = 10;
|
|
43
|
+
const maxInterval = 2147483647;
|
|
44
|
+
o.interval = validRange(minInterval, maxInterval, options.interval, true, "options.interval");
|
|
45
|
+
o.runOnStart = valid("boolean", options.runOnStart, false, "options.runOnStart") || false;
|
|
46
|
+
const offsetMsg = `options.offset (align:${o.align})`;
|
|
47
|
+
if (o.align) {
|
|
48
|
+
o.offset = validRange(0, o.interval, options.offset, false, offsetMsg) ?? 0;
|
|
49
|
+
} else if (!o.runOnStart) {
|
|
50
|
+
o.offset = validRange(-o.interval, maxInterval - o.interval, options.offset, false, offsetMsg) ?? 0;
|
|
51
|
+
} else if (options.offset != null) {
|
|
52
|
+
throw new Error(`${offsetMsg} can't be set if runOnStart:true`);
|
|
53
|
+
} else {
|
|
54
|
+
o.offset = -o.interval;
|
|
55
|
+
}
|
|
56
|
+
o.maxRuns = validRange(0, Infinity, options.maxRuns, false, "options.maxRuns") ?? 0;
|
|
57
|
+
o.onPulse = valid("function", options.onPulse, true, "options.onPulse");
|
|
58
|
+
o.onStart = valid("function", options.onStart, false, "options.onStart") || blankFn;
|
|
59
|
+
o.onStop = valid("function", options.onStop, false, "options.onStop") || blankFn;
|
|
60
|
+
o.onError = valid("function", options.onError, false, "options.onError") || blankFn;
|
|
61
|
+
o.afterPulse = valid("function", options.afterPulse, false, "options.afterPulse") || blankFn;
|
|
62
|
+
o.autoStart = valid("boolean", options.autoStart, false, "options.autoStart") || false;
|
|
63
|
+
o.noMeta = valid("boolean", options.noMeta, false, "option.noMeta") || false;
|
|
64
|
+
return Object.freeze(o);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/static/PulsePrivate.js
|
|
68
|
+
import { solid as solid2 } from "@randajan/props";
|
|
69
|
+
|
|
70
|
+
// src/static/metadata.js
|
|
38
71
|
import { solid, virtual } from "@randajan/props";
|
|
39
|
-
var createMetaData = (
|
|
40
|
-
const
|
|
72
|
+
var createMetaData = (_p, id) => {
|
|
73
|
+
const md = {};
|
|
41
74
|
const w = [];
|
|
42
|
-
solid(
|
|
43
|
-
solid(
|
|
44
|
-
virtual(
|
|
45
|
-
virtual(
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
solid(md, "id", id);
|
|
76
|
+
solid(md, "started", _p.opt.getNow());
|
|
77
|
+
virtual(md, "warnings", (_) => [...w]);
|
|
78
|
+
virtual(md, "runtime", (_) => (md.ended ? md.ended : _p.opt.getNow()) - md.started);
|
|
79
|
+
virtual(md, "lateBy", (_) => Math.max(0, md.runtime - _p.opt.interval));
|
|
80
|
+
solid(md, "warn", (warning) => w.push(warning), false);
|
|
81
|
+
return md;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/static/PulsePrivate.js
|
|
85
|
+
var PulsePrivate = class {
|
|
86
|
+
constructor(pulse, options) {
|
|
87
|
+
this.pulse = pulse;
|
|
88
|
+
this.state = false;
|
|
89
|
+
this.nextId = 0;
|
|
90
|
+
this.opt = formatOptions(options);
|
|
91
|
+
this.run = this.run.bind(this);
|
|
92
|
+
}
|
|
93
|
+
getCountdown() {
|
|
94
|
+
const { state, fireAt, opt: { getNow } } = this;
|
|
95
|
+
if (!state || fireAt == null) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return Math.max(0, fireAt - getNow());
|
|
99
|
+
}
|
|
100
|
+
getPotential(isInit = true) {
|
|
101
|
+
const { opt: { align, runOnStart, interval, offset, getNow } } = this;
|
|
102
|
+
if (isInit && runOnStart) {
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
if (!align) {
|
|
106
|
+
return isInit ? interval + offset : interval;
|
|
107
|
+
}
|
|
108
|
+
const now = getNow() - offset;
|
|
109
|
+
const rest = (now % interval + interval) % interval;
|
|
110
|
+
return rest === 0 ? interval : interval - rest;
|
|
111
|
+
}
|
|
112
|
+
reset() {
|
|
113
|
+
this.nextId = 0;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
start(reset = false) {
|
|
117
|
+
const { pulse, state, timeoutId, opt: { onStart } } = this;
|
|
118
|
+
if (state) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
onStart(pulse);
|
|
122
|
+
if (reset) {
|
|
123
|
+
this.reset();
|
|
124
|
+
}
|
|
125
|
+
this.state = true;
|
|
126
|
+
return this.plan(true);
|
|
127
|
+
}
|
|
128
|
+
stop(reset = false) {
|
|
129
|
+
const { pulse, state, timeoutId, opt: { onStop } } = this;
|
|
130
|
+
if (!state) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
this.state = false;
|
|
135
|
+
delete this.fireAt;
|
|
136
|
+
if (reset) {
|
|
137
|
+
this.reset();
|
|
138
|
+
}
|
|
139
|
+
onStop(pulse);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
plan(isInit) {
|
|
143
|
+
const { state, metaData, nextId, opt: { maxRuns, getNow } } = this;
|
|
144
|
+
if (!state || metaData != null) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (maxRuns && maxRuns <= nextId) {
|
|
148
|
+
this.stop(true);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const fireIn = this.getPotential(isInit);
|
|
152
|
+
this.fireAt = getNow() + fireIn;
|
|
153
|
+
this.timeoutId = setTimeout(this.run, fireIn);
|
|
154
|
+
this.timeoutId.unref?.();
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
async run() {
|
|
158
|
+
const { pulse, state, metaData, opt: { noMeta, getNow, onPulse, onError, afterPulse } } = this;
|
|
159
|
+
if (!state || metaData) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const id = this.nextId++;
|
|
163
|
+
const md = this.metaData = noMeta ? id : createMetaData(this, id);
|
|
164
|
+
let errA, errB, result;
|
|
165
|
+
try {
|
|
166
|
+
result = await onPulse(pulse, md);
|
|
167
|
+
if (!noMeta) {
|
|
168
|
+
solid2(md, "result", result);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
errA = err;
|
|
172
|
+
if (!noMeta) {
|
|
173
|
+
solid2(md, "error", errA);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!noMeta) {
|
|
177
|
+
solid2(md, "ended", getNow());
|
|
178
|
+
}
|
|
179
|
+
if (errA != null) {
|
|
180
|
+
if (noMeta) {
|
|
181
|
+
onError(pulse, errA, md);
|
|
182
|
+
} else {
|
|
183
|
+
onError(pulse, md);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await afterPulse(pulse, md);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
errB = err;
|
|
190
|
+
}
|
|
191
|
+
this.last = noMeta ? result : md;
|
|
192
|
+
delete this.metaData;
|
|
193
|
+
if (errB != null) {
|
|
194
|
+
this.stop();
|
|
195
|
+
throw errB;
|
|
196
|
+
}
|
|
197
|
+
this.plan(false);
|
|
198
|
+
}
|
|
199
|
+
restart(reset) {
|
|
200
|
+
this.stop(reset);
|
|
201
|
+
return this.start(reset);
|
|
202
|
+
}
|
|
48
203
|
};
|
|
49
204
|
|
|
50
205
|
// src/Pulse.js
|
|
@@ -54,78 +209,19 @@ var Pulse = class {
|
|
|
54
209
|
* @param {PulseOptions} [options={}] See {@link PulseOptions}.
|
|
55
210
|
*/
|
|
56
211
|
constructor(options = {}) {
|
|
57
|
-
const _p =
|
|
58
|
-
|
|
59
|
-
nextId: 0
|
|
60
|
-
};
|
|
61
|
-
const getNow = valid("function", options.getNow, false, "options.getNow") || ((_) => Date.now());
|
|
62
|
-
const interval = validRange(10, 2147483647, options.interval, true, "options.interval");
|
|
63
|
-
const offset = validRange(0, interval, options.offset, false, "options.offset") || 0;
|
|
64
|
-
const onPulse = valid("function", options.onPulse, true, "options.onPulse");
|
|
65
|
-
_p.onStart = valid("function", options.onStart, false, "options.onStart") || blankFn;
|
|
66
|
-
_p.onStop = valid("function", options.onStop, false, "options.onStop") || blankFn;
|
|
67
|
-
const onError = valid("function", options.onError, false, "options.onError") || blankFn;
|
|
68
|
-
const afterPulse = valid("function", options.afterPulse, false, "options.afterPulse") || blankFn;
|
|
69
|
-
const autoStart = valid("boolean", options.autoStart, false, "options.autoStart") || false;
|
|
70
|
-
const noMeta = valid("boolean", options.noMeta, false, "option.noMeta") || false;
|
|
71
|
-
_p.reset = (_) => {
|
|
72
|
-
_p.nextId = 0;
|
|
73
|
-
};
|
|
74
|
-
_p.plan = (_) => {
|
|
75
|
-
if (!_p.state || _p.metaData != null) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
_p.timeoutId = setTimeout(_p.run, this.countdown);
|
|
79
|
-
};
|
|
80
|
-
_p.run = async (_) => {
|
|
81
|
-
if (!_p.state || _p.metaData) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const id = _p.nextId++;
|
|
85
|
-
const md = _p.metaData = noMeta ? id : createMetaData(id, getNow);
|
|
86
|
-
let errA, errB, result;
|
|
87
|
-
try {
|
|
88
|
-
result = await onPulse(this, md);
|
|
89
|
-
if (!noMeta) {
|
|
90
|
-
solid2(md, "result", result);
|
|
91
|
-
}
|
|
92
|
-
} catch (err) {
|
|
93
|
-
errA = err;
|
|
94
|
-
if (!noMeta) {
|
|
95
|
-
solid2(md, "error", errA);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (!noMeta) {
|
|
99
|
-
solid2(md, "ended", getNow());
|
|
100
|
-
}
|
|
101
|
-
if (errA != null) {
|
|
102
|
-
if (noMeta) {
|
|
103
|
-
onError(this, errA, md);
|
|
104
|
-
} else {
|
|
105
|
-
onError(this, md);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
await afterPulse(this, md);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
errB = err;
|
|
112
|
-
}
|
|
113
|
-
_p.last = noMeta ? result : md;
|
|
114
|
-
delete _p.metaData;
|
|
115
|
-
if (errB != null) {
|
|
116
|
-
_p.state = false;
|
|
117
|
-
throw errB;
|
|
118
|
-
}
|
|
119
|
-
_p.plan();
|
|
120
|
-
};
|
|
212
|
+
const _p = new PulsePrivate(this, options);
|
|
213
|
+
const { getNow, align, interval, offset, autoStart } = _p.opt;
|
|
121
214
|
virtuals(this, {
|
|
122
215
|
state: (_) => _p.state,
|
|
123
216
|
last: (_) => _p.last,
|
|
124
|
-
countdown: (_) =>
|
|
217
|
+
countdown: (_) => _p.getCountdown(),
|
|
218
|
+
potential: (_) => _p.getCountdown() ?? _p.getPotential(),
|
|
219
|
+
nextAt: (_) => _p.state ? this.potential + getNow() : void 0
|
|
125
220
|
});
|
|
126
221
|
solids(this, {
|
|
127
222
|
interval,
|
|
128
|
-
offset
|
|
223
|
+
offset,
|
|
224
|
+
align
|
|
129
225
|
});
|
|
130
226
|
_pulses.set(this, _p);
|
|
131
227
|
if (autoStart) {
|
|
@@ -137,56 +233,30 @@ var Pulse = class {
|
|
|
137
233
|
* @returns {boolean} `true` if it started now, `false` if it was already running.
|
|
138
234
|
*/
|
|
139
235
|
start(reset = false) {
|
|
140
|
-
|
|
141
|
-
if (state) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
const _p = _pulses.get(this);
|
|
145
|
-
_p.onStart(this);
|
|
146
|
-
if (reset) {
|
|
147
|
-
_p.reset();
|
|
148
|
-
}
|
|
149
|
-
_p.state = true;
|
|
150
|
-
_p.plan();
|
|
151
|
-
return true;
|
|
236
|
+
return _pulses.get(this).start(reset);
|
|
152
237
|
}
|
|
153
238
|
/**
|
|
154
239
|
* Stop the pulse loop (no-op if already stopped).
|
|
155
240
|
* @returns {boolean} `true` if it stopped now, `false` if it was already stopped.
|
|
156
241
|
*/
|
|
157
242
|
stop(reset = false) {
|
|
158
|
-
|
|
159
|
-
if (!state) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
const _p = _pulses.get(this);
|
|
163
|
-
clearTimeout(_p.timeoutId);
|
|
164
|
-
_p.state = false;
|
|
165
|
-
if (reset) {
|
|
166
|
-
_p.reset();
|
|
167
|
-
}
|
|
168
|
-
_p.onStop(this);
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
reset() {
|
|
172
|
-
_pulses.get(this).reset();
|
|
173
|
-
return true;
|
|
243
|
+
return _pulses.get(this).stop(reset);
|
|
174
244
|
}
|
|
175
245
|
restart(reset = false) {
|
|
176
|
-
this.
|
|
177
|
-
|
|
246
|
+
return _pulses.get(this).restart(reset);
|
|
247
|
+
}
|
|
248
|
+
reset() {
|
|
249
|
+
return _pulses.get(this).reset();
|
|
178
250
|
}
|
|
179
251
|
};
|
|
180
252
|
|
|
181
|
-
// src/
|
|
253
|
+
// src/index.js
|
|
182
254
|
var createPulse = (options = {}) => new Pulse(options);
|
|
183
255
|
var stopAllPulses = () => {
|
|
184
256
|
for (const [pulse, _pulse] of _pulses.entries()) {
|
|
185
257
|
pulse.stop();
|
|
186
258
|
}
|
|
187
259
|
};
|
|
188
|
-
|
|
189
|
-
// src/index.js
|
|
190
260
|
var index_default = createPulse;
|
|
191
261
|
export {
|
|
192
262
|
Pulse,
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/Pulse.js", "../../src/const.js", "../../src/tools.js", "../../src/
|
|
4
|
-
"sourcesContent": ["import { solid, solids, virtuals } from \"@randajan/props\";\r\nimport { blankFn, _pulses } from \"./const\";\r\nimport { valid, validRange } from \"./tools\";\r\nimport { createMetaData } from \"./metadata\";\r\n\r\n/**\r\n * Periodic scheduler that aligns each callback to exact multiples\r\n * of the given interval (optionally shifted by `offset`).\r\n */\r\nexport class Pulse {\r\n\r\n /**\r\n * Create a new Pulse instance.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n */\r\n constructor(options = {}) {\r\n const _p = {\r\n state: false,\r\n nextId:0\r\n }\r\n\r\n const getNow = valid(\"function\", options.getNow, false, \"options.getNow\") || (_ => Date.now());\r\n\r\n const interval = validRange(10, 2_147_483_647, options.interval, true, \"options.interval\");\r\n const offset = (validRange(0, interval, options.offset, false, \"options.offset\") || 0);\r\n\r\n const onPulse = valid(\"function\", options.onPulse, true, \"options.onPulse\");\r\n _p.onStart = valid(\"function\", options.onStart, false, \"options.onStart\") || blankFn;\r\n _p.onStop = valid(\"function\", options.onStop, false, \"options.onStop\") || blankFn;\r\n \r\n const onError = valid(\"function\", options.onError, false, \"options.onError\") || blankFn;\r\n \r\n const afterPulse = valid(\"function\", options.afterPulse, false, \"options.afterPulse\") || blankFn;\r\n const autoStart = valid(\"boolean\", options.autoStart, false, \"options.autoStart\") || false;\r\n const noMeta = valid(\"boolean\", options.noMeta, false, \"option.noMeta\") || false;\r\n\r\n _p.reset = _=>{\r\n _p.nextId = 0;\r\n }\r\n\r\n _p.plan = _ => {\r\n if (!_p.state || _p.metaData != null) { return; }\r\n _p.timeoutId = setTimeout(_p.run, this.countdown);\r\n }\r\n\r\n _p.run = async _ => {\r\n if (!_p.state || _p.metaData) { return; }\r\n\r\n const id = _p.nextId++;\r\n const md = _p.metaData = noMeta ? id : createMetaData(id, getNow);\r\n let errA, errB, result;\r\n\r\n try {\r\n result = await onPulse(this, md);\r\n if (!noMeta) { solid(md, \"result\", result); }\r\n }\r\n catch (err) {\r\n errA = err;\r\n if (!noMeta) { solid(md, \"error\", errA); }\r\n }\r\n\r\n if (!noMeta) { solid(md, \"ended\", getNow()); }\r\n\r\n if (errA != null) {\r\n if (noMeta) { onError(this, errA, md); }\r\n else { onError(this, md); }\r\n }\r\n\r\n try { await afterPulse(this, md); }\r\n catch(err) { errB = err; }\r\n\r\n _p.last = noMeta ? result : md;\r\n delete _p.metaData;\r\n\r\n if (errB != null) {\r\n _p.state = false;\r\n throw errB;\r\n }\r\n \r\n _p.plan();\r\n }\r\n\r\n virtuals(this, {\r\n state:_=>_p.state,\r\n last:_=>_p.last,\r\n countdown:_=>((interval - getNow() % interval) + offset)\r\n });\r\n\r\n solids(this, {\r\n interval,\r\n offset\r\n });\r\n\r\n _pulses.set(this, _p);\r\n\r\n if (autoStart) { this.start(); }\r\n }\r\n\r\n /**\r\n * Start the pulse loop (no-op if already running).\r\n * @returns {boolean} `true` if it started now, `false` if it was already running.\r\n */\r\n start(reset=false) {\r\n const { state } = this;\r\n if (state) { return false; }\r\n const _p = _pulses.get(this);\r\n _p.onStart(this);\r\n if (reset) { _p.reset(); }\r\n _p.state = true;\r\n _p.plan();\r\n return true;\r\n }\r\n\r\n /**\r\n * Stop the pulse loop (no-op if already stopped).\r\n * @returns {boolean} `true` if it stopped now, `false` if it was already stopped.\r\n */\r\n stop(reset=false) {\r\n const { state } = this;\r\n if (!state) { return false; }\r\n const _p = _pulses.get(this);\r\n clearTimeout(_p.timeoutId);\r\n _p.state = false;\r\n if (reset) { _p.reset(); }\r\n _p.onStop(this);\r\n return true;\r\n }\r\n\r\n reset() {\r\n _pulses.get(this).reset();\r\n return true;\r\n }\r\n\r\n restart(reset=false) {\r\n this.stop(reset);\r\n return this.start(reset);\r\n }\r\n\r\n\r\n}", "\r\n\r\n\r\nexport const _pulses = new Map();\r\n\r\nexport const blankFn = ()=>{};", "\r\n\r\nexport const is = (type, any)=>typeof any === type;\r\n\r\nexport const valid = (type, any, req=false, msg=\"argument\")=>{\r\n if (any == null) {\r\n if (!req) { return; }\r\n throw new Error(`${msg} require typeof '${type}'`);\r\n }\r\n if (is(type, any)) { return any; }\r\n throw new Error(`${msg} is not typeof '${type}'`);\r\n}\r\n\r\nexport const validRange = (min, max, any, req=false, msg=\"argument\")=>{\r\n const num = valid(\"number\", any, req, msg);\r\n if (num == null) { return; }\r\n if (num < min) { throw new Error(`${msg} must be greater than ${min}`); }\r\n if (num > max) { throw new Error(`${msg} must be less than ${max}`); }\r\n return num;\r\n}", "import { solid, virtual } from \"@randajan/props\";\r\n\r\n\r\n/**\r\n * Metadata object passed to every pulse callback.\r\n * @typedef {Object} PulseMeta\r\n * @property {Date} started Timestamp when this pulse began.\r\n * @property {string[]} warnings Snapshot of all warnings collected so far.\r\n * @property {number} runtime Milliseconds elapsed since `started`.\r\n * @property {(warning: string|Error) => void} warn Push a warning into the internal list.\r\n */\r\nexport const createMetaData = (id, getNow) => {\r\n const p = {};\r\n const w = [];\r\n solid(p, \"id\", id);\r\n solid(p, \"started\", getNow());\r\n virtual(p, \"warnings\", _ => [...w]);\r\n virtual(p, \"runtime\", _ => (p.ended ? p.ended : getNow()) - p.started);\r\n solid(p, \"warn\", warning => w.push(warning), false);\r\n return p;\r\n}\r\n\r\n/**\r\n * Configuration object for {@link Pulse}.\r\n * @typedef {Object} PulseOptions\r\n * @property {(meta: PulseMeta) => (void|Promise<void>)} onPulse **required** \u2013 user callback invoked on every pulse.\r\n * @property {number} interval **required** \u2013 period length in ms (50 \u2013 2 147 483 647).\r\n * @property {number} [offset=0] Shift in ms applied to the start of each interval (0 \u2264 offset < interval).\r\n * @property {() => number} [getNow=() => Date.now()] Custom time source, useful for tests or time travel.\r\n * @property {(err: unknown) => void} [onError=blankFn] Error handler for exceptions or rejected `onPulse` promises.\r\n * @property {boolean} [autoStart=false] If true, the loop starts immediately after construction.\r\n */", "import { _pulses } from \"./const\";\r\nimport { Pulse } from \"./Pulse.js\";\r\n\r\n\r\n/**\r\n * Factory helper \u2013 simply calls `new Pulse(options)`.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n * @returns {Pulse}\r\n */\r\nexport const createPulse = (options = {}) => new Pulse(options);\r\n\r\n/**\r\n * Process exit helper \u2013 stops all pending pulses.\r\n */\r\n\r\nexport const stopAllPulses = ()=>{\r\n for (const [pulse, _pulse] of _pulses.entries()) {\r\n pulse.stop();\r\n }\r\n}\r\n", "import { Pulse } from \"./Pulse\";\nimport { createPulse, stopAllPulses } from \"./static\";\n\nexport default createPulse;\n\nexport { createPulse, stopAllPulses, Pulse }\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,SAAAA,QAAO,QAAQ,gBAAgB;;;ACGjC,IAAM,UAAU,oBAAI,IAAI;AAExB,IAAM,UAAU,MAAI;AAAC;;;ACHrB,IAAM,KAAK,CAAC,MAAM,QAAM,OAAO,QAAQ;AAEvC,IAAM,QAAQ,CAAC,MAAM,KAAK,MAAI,OAAO,MAAI,eAAa;AACzD,MAAI,OAAO,MAAM;AACb,QAAI,CAAC,KAAK;AAAE;AAAA,IAAQ;AACpB,UAAM,IAAI,MAAM,GAAG,GAAG,oBAAoB,IAAI,GAAG;AAAA,EACrD;AACA,MAAI,GAAG,MAAM,GAAG,GAAG;AAAE,WAAO;AAAA,EAAK;AACjC,QAAM,IAAI,MAAM,GAAG,GAAG,mBAAmB,IAAI,GAAG;AACpD;AAEO,IAAM,aAAa,CAAC,KAAK,KAAK,KAAK,MAAI,OAAO,MAAI,eAAa;AAClE,QAAM,MAAM,MAAM,UAAU,KAAK,KAAK,GAAG;AACzC,MAAI,OAAO,MAAM;AAAE;AAAA,EAAQ;AAC3B,MAAI,MAAM,KAAK;AAAE,UAAM,IAAI,MAAM,GAAG,GAAG,yBAAyB,GAAG,EAAE;AAAA,EAAG;AACxE,MAAI,MAAM,KAAK;AAAE,UAAM,IAAI,MAAM,GAAG,GAAG,sBAAsB,GAAG,EAAE;AAAA,EAAG;AACrE,SAAO;AACX;;;
|
|
6
|
-
"names": ["solid", "solid"]
|
|
3
|
+
"sources": ["../../src/Pulse.js", "../../src/const.js", "../../src/tools.js", "../../src/static/options.js", "../../src/static/PulsePrivate.js", "../../src/static/metadata.js", "../../src/index.js"],
|
|
4
|
+
"sourcesContent": ["import { solid, solids, virtuals } from \"@randajan/props\";\nimport { _pulses } from \"./const\";\nimport { formatOptions } from \"./static/options\";\nimport { PulsePrivate } from \"./static/PulsePrivate\";\n\r\n/**\r\n * Periodic scheduler that aligns each callback to exact multiples\r\n * of the given interval (optionally shifted by `offset`).\r\n */\r\nexport class Pulse {\r\n\r\n /**\r\n * Create a new Pulse instance.\r\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\r\n */\r\n constructor(options = {}) {\r\n const _p = new PulsePrivate(this, options);\r\n\r\n const { getNow, align, interval, offset, autoStart } = _p.opt;\r\n\r\n virtuals(this, {\r\n state:_=>_p.state,\r\n last:_=>_p.last,\r\n countdown:_=>_p.getCountdown(),\r\n potential:_=>_p.getCountdown() ?? _p.getPotential(),\r\n nextAt:_=>_p.state ? this.potential + getNow() : undefined\r\n });\r\n\r\n solids(this, {\r\n interval,\r\n offset,\r\n align\r\n });\r\n\r\n _pulses.set(this, _p);\r\n\r\n if (autoStart) { this.start(); }\r\n }\r\n\r\n /**\r\n * Start the pulse loop (no-op if already running).\r\n * @returns {boolean} `true` if it started now, `false` if it was already running.\r\n */\r\n start(reset=false) {\r\n return _pulses.get(this).start(reset);\r\n }\r\n\r\n /**\r\n * Stop the pulse loop (no-op if already stopped).\r\n * @returns {boolean} `true` if it stopped now, `false` if it was already stopped.\r\n */\r\n stop(reset=false) {\r\n return _pulses.get(this).stop(reset);\r\n }\r\n\r\n restart(reset=false) {\r\n return _pulses.get(this).restart(reset);\r\n }\r\n\r\n reset() {\r\n return _pulses.get(this).reset();\r\n }\r\n\r\n}\r\n", "\r\n\r\n\r\nexport const _pulses = new Map();\r\n\r\nexport const blankFn = ()=>{};", "\n\nexport const is = (type, any)=>typeof any === type;\n\nexport const valid = (type, any, req=false, msg=\"argument\")=>{\n if (any == null) {\n if (!req) { return; }\n throw new Error(`${msg} require typeof '${type}'`);\n }\n if (is(type, any)) { return any; }\n throw new Error(`${msg} is not typeof '${type}'`);\n}\n\nexport const validRange = (min, max, any, req=false, msg=\"argument\")=>{\n const num = valid(\"number\", any, req, msg);\n if (num == null) { return; }\n if (num < min) { throw new Error(`${msg} must be greater than ${min}`); }\n if (num > max) { throw new Error(`${msg} must be less than ${max}`); }\n return num;\n}", "import { blankFn } from \"../const\";\nimport { valid, validRange } from \"../tools\";\n\n\n\n/**\n * Configuration object for {@link Pulse}.\n * @typedef {Object} PulseOptions\n * @property {(pulse: import(\"../Pulse\").Pulse, meta: import(\"./metadata\").PulseMeta|number) => (*|Promise<*>)} onPulse **required** \u2013 user callback invoked on every pulse.\n * @property {boolean} align **required** \u2013 align to wall-clock interval edges when true, run relatively when false.\n * @property {number} interval **required** \u2013 period length in ms (10 \u2013 2 147 483 647).\n * @property {number} [offset=0] Timing offset. Range and meaning depend on `align` and `runOnStart`.\n * @property {boolean} [runOnStart=false] Schedule the first pulse immediately on start.\n * @property {number} [maxRuns=0] Maximum number of pulses before automatic stop. `0` means unlimited.\n * @property {() => number} [getNow=() => Date.now()] Custom time source, useful for tests or time travel.\n * @property {boolean} [autoStart=false] If true, the loop starts immediately after construction.\n * @property {boolean} [noMeta=false] If true, callbacks receive the numeric pulse id instead of `PulseMeta`.\n * @property {(pulse: import(\"../Pulse\").Pulse) => void} [onStart=blankFn] Called right before the pulse loop starts.\n * @property {(pulse: import(\"../Pulse\").Pulse) => void} [onStop=blankFn] Called right after the pulse loop stops.\n * @property {Function} [onError=blankFn] Error handler for exceptions or rejected `onPulse` promises.\n * @property {(pulse: import(\"../Pulse\").Pulse, meta: import(\"./metadata\").PulseMeta|number) => (void|Promise<void>)} [afterPulse=blankFn] Callback executed after every pulse.\n */\nexport const formatOptions = (options) => {\n const o = {};\r\n\r\n o.getNow = valid(\"function\", options.getNow, false, \"options.getNow\") || (_ => Date.now());\r\n\r\n o.align = valid(\"boolean\", options.align, true, \"options.align\");\r\n\r\n const minInterval = 10;\r\n const maxInterval = 2_147_483_647;\r\n o.interval = validRange(minInterval, maxInterval, options.interval, true, \"options.interval\");\r\n\r\n o.runOnStart = valid(\"boolean\", options.runOnStart, false, \"options.runOnStart\") || false;\r\n\r\n const offsetMsg = `options.offset (align:${o.align})`;\r\n\r\n if (o.align) {\r\n o.offset = (validRange(0, o.interval, options.offset, false, offsetMsg) ?? 0);\r\n } else if (!o.runOnStart) {\r\n o.offset = (validRange(-o.interval, (maxInterval - o.interval), options.offset, false, offsetMsg) ?? 0);\r\n } else if (options.offset != null) {\n throw new Error(`${offsetMsg} can't be set if runOnStart:true`);\n } else {\n o.offset = -o.interval;\n }\r\n\r\n o.maxRuns = validRange(0, Infinity, options.maxRuns, false, \"options.maxRuns\") ?? 0;\r\n\r\n o.onPulse = valid(\"function\", options.onPulse, true, \"options.onPulse\");\r\n o.onStart = valid(\"function\", options.onStart, false, \"options.onStart\") || blankFn;\r\n o.onStop = valid(\"function\", options.onStop, false, \"options.onStop\") || blankFn;\r\n o.onError = valid(\"function\", options.onError, false, \"options.onError\") || blankFn;\r\n\r\n o.afterPulse = valid(\"function\", options.afterPulse, false, \"options.afterPulse\") || blankFn;\r\n o.autoStart = valid(\"boolean\", options.autoStart, false, \"options.autoStart\") || false;\r\n o.noMeta = valid(\"boolean\", options.noMeta, false, \"option.noMeta\") || false;\r\n\r\n return Object.freeze(o);\r\n}\n", "import { solid } from \"@randajan/props\";\r\nimport { createMetaData } from \"./metadata\";\r\nimport { formatOptions } from \"./options\";\r\n\r\n\r\nexport class PulsePrivate {\r\n\r\n constructor(pulse, options) {\r\n this.pulse = pulse;\r\n this.state = false;\r\n this.nextId = 0;\r\n this.opt = formatOptions(options);\r\n\r\n this.run = this.run.bind(this);\r\n }\r\n\r\n getCountdown() {\r\n const { state, fireAt, opt:{ getNow } } = this;\r\n if (!state || fireAt == null) { return; }\r\n return Math.max(0, fireAt - getNow());\r\n }\r\n\r\n getPotential(isInit = true) {\r\n const { opt:{ align, runOnStart, interval, offset, getNow } } = this;\r\n if (isInit && runOnStart) { return 0; }\r\n if (!align) { return isInit ? (interval + offset) : interval; }\r\n const now = getNow() - offset;\r\n const rest = ((now % interval) + interval) % interval;\r\n return rest === 0 ? interval : interval - rest;\r\n }\r\n\r\n reset() {\r\n this.nextId = 0;\r\n return true;\r\n }\r\n\r\n start(reset=false) {\r\n const { pulse, state, timeoutId, opt:{ onStart } } = this;\r\n if (state) { return false; }\r\n onStart(pulse);\r\n if (reset) { this.reset(); }\r\n this.state = true;\r\n return this.plan(true);\r\n }\r\n\r\n stop(reset=false) {\r\n const { pulse, state, timeoutId, opt:{ onStop } } = this;\r\n if (!state) { return false; }\r\n clearTimeout(timeoutId);\r\n this.state = false;\r\n delete this.fireAt;\r\n if (reset) { this.reset(); }\r\n onStop(pulse);\r\n return true;\r\n }\r\n\r\n plan(isInit) {\r\n const { state, metaData, nextId, opt:{ maxRuns, getNow } } = this;\r\n if (!state || metaData != null) { return false; }\r\n if (maxRuns && maxRuns <= nextId) { this.stop(true); return false; }\r\n const fireIn = this.getPotential(isInit);\r\n this.fireAt = getNow() + fireIn;\r\n this.timeoutId = setTimeout(this.run, fireIn);\r\n this.timeoutId.unref?.();\r\n return true;\r\n }\r\n\r\n async run() {\r\n const { pulse, state, metaData, opt:{ noMeta, getNow, onPulse, onError, afterPulse } } = this;\r\n if (!state || metaData) { return; }\r\n\r\n const id = this.nextId++;\r\n const md = this.metaData = noMeta ? id : createMetaData(this, id);\n let errA, errB, result;\r\n\r\n try {\r\n result = await onPulse(pulse, md);\r\n if (!noMeta) { solid(md, \"result\", result); }\r\n }\r\n catch (err) {\r\n errA = err;\r\n if (!noMeta) { solid(md, \"error\", errA); }\r\n }\r\n\r\n if (!noMeta) { solid(md, \"ended\", getNow()); }\r\n\r\n if (errA != null) {\r\n if (noMeta) { onError(pulse, errA, md); }\r\n else { onError(pulse, md); }\r\n }\r\n\r\n try { await afterPulse(pulse, md); }\r\n catch (err) { errB = err; }\r\n\r\n this.last = noMeta ? result : md;\r\n delete this.metaData;\r\n\r\n if (errB != null) {\r\n this.stop();\r\n throw errB;\r\n }\r\n\r\n this.plan(false);\r\n }\r\n\r\n restart(reset) {\r\n this.stop(reset);\r\n return this.start(reset);\r\n }\r\n\r\n}\n", "import { solid, virtual } from \"@randajan/props\";\r\n\r\n\r\n/**\n * Metadata object passed to every pulse callback.\n * @typedef {Object} PulseMeta\n * @property {number} id Sequential pulse id.\n * @property {number} started Timestamp when this pulse began.\n * @property {number} [ended] Timestamp when this pulse finished.\n * @property {number} runtime Milliseconds elapsed since `started`.\n * @property {number} lateBy Milliseconds over the configured interval.\n * @property {string[]|Error[]} warnings Snapshot of all warnings collected so far.\n * @property {*} [result] Value returned by `onPulse`.\n * @property {*} [error] Error thrown or rejected by `onPulse`.\n * @property {(warning: string|Error) => void} warn Push a warning into the internal list.\n */\nexport const createMetaData = (_p, id) => {\n const md = {};\n const w = [];\n solid(md, \"id\", id);\n solid(md, \"started\", _p.opt.getNow());\r\n virtual(md, \"warnings\", _ => [...w]);\r\n virtual(md, \"runtime\", _ => (md.ended ? md.ended : _p.opt.getNow()) - md.started);\r\n virtual(md, \"lateBy\", _=>Math.max(0, md.runtime-_p.opt.interval));\r\n solid(md, \"warn\", warning => w.push(warning), false);\r\n return md;\r\n}\r\n\r\n", "import { Pulse } from \"./Pulse\";\nimport { _pulses } from \"./const\";\n\n\nexport {\n Pulse\n}\n\n/**\n * Factory helper \u2013 simply calls `new Pulse(options)`.\n * @param {PulseOptions} [options={}] See {@link PulseOptions}.\n * @returns {Pulse}\n */\nexport const createPulse = (options = {}) => new Pulse(options);\n\n\n/**\n * Process exit helper \u2013 stops all pending pulses.\n */\nexport const stopAllPulses = ()=>{\n for (const [pulse, _pulse] of _pulses.entries()) {\n pulse.stop();\n }\n}\n\nexport default createPulse;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["solid", "solid", "solid"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@randajan/pulse",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Lightweight, promise-friendly scheduler that outperforms classic cron for any interval shorter than one month.",
|
|
5
5
|
"repository": "randajan/pulse",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"require": "./dist/cjs/index.cjs"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "node index.js isBuild=true"
|
|
17
|
+
},
|
|
15
18
|
"license": "MIT",
|
|
16
19
|
"devDependencies": {
|
|
17
20
|
"@randajan/simple-lib": "^3.3.0"
|