jw-automator 3.1.0 → 4.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/CHANGELOG.md +61 -0
- package/README.md +77 -38
- package/docs/ARCHITECTURE.md +8 -6
- package/docs/MIGRATION.md +68 -160
- package/docs/QUICKSTART.md +29 -7
- package/docs/defensive-defaults.md +65 -0
- package/examples/basic-example.js +48 -45
- package/examples/hello-world.js +0 -0
- package/examples/iot-sensor-example.js +0 -0
- package/examples/seed-example.js +147 -0
- package/index.js +0 -0
- package/package.json +2 -2
- package/src/Automator.js +246 -16
- package/src/core/CoreEngine.js +108 -4
- package/src/core/RecurrenceEngine.js +0 -0
- package/src/host/SchedulerHost.js +0 -0
- package/src/storage/FileStorage.js +0 -0
- package/src/storage/MemoryStorage.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,67 @@ All notable changes to jw-automator will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.0.0] - 2025-11-19
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
- **Smart Default `catchUpWindow` Behavior**: The default behavior for `catchUpWindow` has fundamentally changed. Instead of defaulting to `"unlimited"`, it now defaults based on action type:
|
|
13
|
+
- For recurring actions, it defaults to the recurrence interval duration.
|
|
14
|
+
- For one-time actions, it defaults to `0` (no catch-up).
|
|
15
|
+
- **Impact:** Users relying on the previous `"unlimited"` default for actions where `catchUpWindow` was not explicitly set will experience different behavior.
|
|
16
|
+
- **Invalid `repeat.type` is now a Fatal Error**: Providing a missing or invalid `repeat.type` (e.g., a typo) will now throw a hard `Error` immediately when the action is added or updated.
|
|
17
|
+
- **Impact:** Previously, this would be defensively coerced to `'day'` with an `error` event. Code relying on this coercion will now crash.
|
|
18
|
+
- **Coercion Events changed from `error` to `warning`**: All defensive coercions (e.g., invalid `repeat.interval`, negative `catchUpWindow`, invalid `repeat.limit`) now emit a `warning` event instead of an `error` event.
|
|
19
|
+
- **Impact:** Users listening for `automator.on('error', ...)` to catch these specific coercion notifications will need to update their code to listen for `automator.on('warning', ...)` instead.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`warning` Event Type**: A new event type, `warning`, has been introduced for non-fatal data coercions and corrections during action validation.
|
|
24
|
+
- **Smart `catchUpWindow` Defaults**: Actions now automatically infer a sensible `catchUpWindow` default based on whether they are one-time (default `0`) or recurring (default to recurrence interval duration).
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- The "Defensive Validation" strategy has been refined to distinguish between fatal configuration errors and non-fatal coercions, using `Error` throws for the former and `warning` events for the latter.
|
|
29
|
+
|
|
30
|
+
## [3.2.0] - 2025-11-18
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`seed()` Method**: New bootstrapping method that runs initialization logic only when the database is empty
|
|
35
|
+
- Solves the "Bootstrapping Problem" by safely initializing actions without resetting schedules on restart
|
|
36
|
+
- Returns `true` if seeding ran, `false` if skipped
|
|
37
|
+
- Automatically saves state after seeding
|
|
38
|
+
- Perfect for system tasks that should be added once but preserved forever
|
|
39
|
+
|
|
40
|
+
- **`catchUpWindow` Property**: Time-based validity window for missed executions (replaces binary buffered/unBuffered)
|
|
41
|
+
- `catchUpWindow: "unlimited"` - Catch up ALL missed executions (like old `unBuffered: false`)
|
|
42
|
+
- `catchUpWindow: 0` - Skip ALL missed executions (like old `unBuffered: true`)
|
|
43
|
+
- `catchUpWindow: 5000` - Hybrid: tolerate 5s lag, skip if offline for hours
|
|
44
|
+
- Solves the "Thundering Herd Problem" by preventing thousands of queued executions after long downtime
|
|
45
|
+
- Fast-forward optimization uses mathematical projection to instantly advance high-frequency tasks
|
|
46
|
+
- Uses `"unlimited"` string literal instead of `Infinity` for clean JSON serialization
|
|
47
|
+
|
|
48
|
+
- **Defensive Validation (Initial Implementation)**: Implemented "Fail loudly, run defensively" philosophy with various coercions and `error` events for invalid inputs. This initial strategy was further refined in v4.0.0, which introduced a dedicated `warning` event and stricter validation for `repeat.type`.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **Backwards Compatibility**: `unBuffered` property is now a maintained alias that maps to `catchUpWindow`
|
|
53
|
+
- `unBuffered: true` → `catchUpWindow: 0`
|
|
54
|
+
- `unBuffered: false` → `catchUpWindow: "unlimited"`
|
|
55
|
+
- `catchUpWindow: Infinity` → automatically coerced to `"unlimited"` with DEBUG event
|
|
56
|
+
- Both properties supported indefinitely with zero breaking changes
|
|
57
|
+
- `catchUpWindow` takes precedence if both are specified
|
|
58
|
+
|
|
59
|
+
### Improved
|
|
60
|
+
|
|
61
|
+
- Clean JSON serialization: `"unlimited"` is a string, no special JSON handling required
|
|
62
|
+
- Enhanced action validation with comprehensive error/debug event emissions
|
|
63
|
+
- All invalid values coerced to sensible defaults - system keeps running
|
|
64
|
+
- Updated action loading to normalize `catchUpWindow` for backwards compatibility
|
|
65
|
+
- Comprehensive test coverage for both new features and defensive validation
|
|
66
|
+
- Updated examples to demonstrate `seed()` and `catchUpWindow` usage
|
|
67
|
+
- Updated documentation with detailed usage patterns and migration guidance
|
|
68
|
+
|
|
8
69
|
## [3.0.0] - 2025-11-17
|
|
9
70
|
|
|
10
71
|
### Added (v3 Complete Rewrite)
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 📚 **jw-automator
|
|
1
|
+
# 📚 **jw-automator v4**
|
|
2
2
|
|
|
3
3
|
### A resilient, local-time, 1-second precision automation scheduler for Node.js
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## ⭐️ Overview
|
|
10
10
|
|
|
11
|
-
**jw-automator** is a robust automation engine designed for small devices, home automation hubs, personal servers, and Node.js environments where **correctness, resilience, and local-time behavior** matter more than millisecond precision.
|
|
11
|
+
**jw-automator** is a robust automation engine designed for small devices, home automation hubs, personal servers, and Node.js environments where **correctness, resilience, and local-time behavior** matter more than millisecond precision. Version 4 introduces enhanced defensive defaults and clearer error handling, making it even more predictable and robust.
|
|
12
12
|
|
|
13
13
|
Where traditional cron falls short — missed executions, poor DST handling, limited recurrence, lack of catch-up semantics — **jw-automator** provides a predictable, human-centric scheduling model:
|
|
14
14
|
|
|
@@ -58,20 +58,22 @@ automator.addFunction('turnLightOn', function(payload) {
|
|
|
58
58
|
console.log('Turning light on');
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
automator.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
// Seed initial actions (runs only on first use)
|
|
62
|
+
automator.seed((auto) => {
|
|
63
|
+
auto.addAction({
|
|
64
|
+
name: 'Morning Lights',
|
|
65
|
+
cmd: 'turnLightOn',
|
|
66
|
+
date: new Date('2025-05-01T07:00:00'),
|
|
67
|
+
payload: null,
|
|
68
|
+
catchUpWindow: 60000, // Tolerate 1 minute of lag
|
|
69
|
+
repeat: {
|
|
70
|
+
type: 'day',
|
|
71
|
+
interval: 1,
|
|
72
|
+
limit: null,
|
|
73
|
+
endDate: null,
|
|
74
|
+
dstPolicy: 'once'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
// Start the scheduler
|
|
@@ -137,25 +139,37 @@ This avoids cron's silent-but-surprising behaviors.
|
|
|
137
139
|
|
|
138
140
|
---
|
|
139
141
|
|
|
140
|
-
### 4. **Resilient Offline Catch-Up**
|
|
142
|
+
### 4. **Resilient Offline Catch-Up with Smart Defaults**
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
When the device is offline or delayed, you can control exactly how far back to catch up using the `catchUpWindow` property. Automator v4 introduces **smart defaults** that infer the desired behavior based on your action's type, making the system more robust and predictable out-of-the-box.
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
**`catchUpWindow` Behavior:**
|
|
147
|
+
|
|
148
|
+
* **Explicitly Set (milliseconds or `"unlimited"`):** Your explicit `catchUpWindow` value always takes precedence.
|
|
149
|
+
* `catchUpWindow: "unlimited"`: Catch up ALL missed executions.
|
|
150
|
+
* `catchUpWindow: 0`: Skip ALL missed executions (real-time only).
|
|
151
|
+
* `catchUpWindow: 5000`: Catch up if missed by ≤5 seconds, skip if older.
|
|
152
|
+
* **Smart Default (Recurring Actions):** If not specified, `catchUpWindow` defaults to the **duration of the action's recurrence interval**.
|
|
153
|
+
* Example: A `repeat: { type: 'hour', interval: 1 }` action will default to a 1-hour `catchUpWindow`. If missed by less than an hour, it runs. If missed by more, it fast-forwards to the next scheduled interval.
|
|
154
|
+
* **Smart Default (One-Time Actions):** If not specified and the action has no `repeat` property, `catchUpWindow` defaults to **`0`**.
|
|
155
|
+
* Example: A one-time task scheduled for 2:00 AM that's missed due to downtime will not run when the server comes back online later.
|
|
148
156
|
|
|
149
|
-
|
|
157
|
+
**How it works:**
|
|
150
158
|
|
|
151
|
-
*
|
|
152
|
-
*
|
|
159
|
+
* If an action is missed by less than its effective `catchUpWindow`, it executes (recovers from brief glitches or short offline periods).
|
|
160
|
+
* If missed by more, it's skipped and fast-forwarded to its next future scheduled time (prevents "thundering herds" after extended outages).
|
|
161
|
+
* The fast-forward optimization uses mathematical projection to instantly advance high-frequency tasks.
|
|
153
162
|
|
|
154
|
-
|
|
163
|
+
**Events for Coercion & Validation:**
|
|
155
164
|
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
|
|
165
|
+
* **`warning` event:** Emitted when `catchUpWindow` or `repeat` properties are syntactically invalid and have been defensively coerced to a sensible default (e.g., negative interval becomes `1`).
|
|
166
|
+
* **`Error` (thrown):** For fundamental issues like an invalid `repeat.type` (e.g., typo like `'horu'`). This is a fatal error, as the user's intent cannot be reliably determined.
|
|
167
|
+
|
|
168
|
+
**Backwards compatibility:**
|
|
169
|
+
|
|
170
|
+
The legacy `unBuffered` property is still supported and maps directly to `catchUpWindow` behavior:
|
|
171
|
+
* `unBuffered: false` is equivalent to `catchUpWindow: "unlimited"`
|
|
172
|
+
* `unBuffered: true` is equivalent to `catchUpWindow: 0`
|
|
159
173
|
|
|
160
174
|
---
|
|
161
175
|
|
|
@@ -213,6 +227,30 @@ Options:
|
|
|
213
227
|
|
|
214
228
|
### Methods
|
|
215
229
|
|
|
230
|
+
#### `seed(callback)`
|
|
231
|
+
Seed the automator with initial actions. Runs only when the database is empty (first use).
|
|
232
|
+
|
|
233
|
+
**Returns:** `boolean` - `true` if seeding ran, `false` if skipped
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
automator.seed((auto) => {
|
|
237
|
+
auto.addAction({
|
|
238
|
+
name: 'Daily Report',
|
|
239
|
+
cmd: 'generateReport',
|
|
240
|
+
date: new Date('2025-01-01T09:00:00'),
|
|
241
|
+
catchUpWindow: "unlimited",
|
|
242
|
+
repeat: { type: 'day', interval: 1 }
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Why use seed()?**
|
|
248
|
+
|
|
249
|
+
* Solves the bootstrapping problem: safely initialize actions without resetting the schedule on every restart
|
|
250
|
+
* Preserves user-modified schedules perfectly
|
|
251
|
+
* Runs initialization logic only once in the application lifecycle
|
|
252
|
+
* Automatically saves state after seeding
|
|
253
|
+
|
|
216
254
|
#### `start()`
|
|
217
255
|
Start the scheduler.
|
|
218
256
|
|
|
@@ -304,6 +342,7 @@ Listen to events using `automator.on(event, callback)`:
|
|
|
304
342
|
* `action` - Action executed
|
|
305
343
|
* `update` - Action added/updated/removed
|
|
306
344
|
* `error` - Error occurred
|
|
345
|
+
* `warning` - Non-fatal data coercion or correction occurred
|
|
307
346
|
* `debug` - Debug information
|
|
308
347
|
|
|
309
348
|
```js
|
|
@@ -413,14 +452,14 @@ npm run test:coverage
|
|
|
413
452
|
|
|
414
453
|
### Top-level action fields:
|
|
415
454
|
|
|
416
|
-
| Field
|
|
417
|
-
|
|
|
418
|
-
| `id`
|
|
419
|
-
| `name`
|
|
420
|
-
| `cmd`
|
|
421
|
-
| `payload`
|
|
422
|
-
| `date`
|
|
423
|
-
| `
|
|
455
|
+
| Field | Description |
|
|
456
|
+
| --------------- | --------------------------------------------------------------------- |
|
|
457
|
+
| `id` | Unique internal identifier (auto-generated) |
|
|
458
|
+
| `name` | User label (optional) |
|
|
459
|
+
| `cmd` | Name of registered function to execute |
|
|
460
|
+
| `payload` | Data passed to the command |
|
|
461
|
+
| `date` | Next scheduled run time (local `Date`) |
|
|
462
|
+
| `catchUpWindow` | Time window for catching up missed executions (smart default based on action type, or milliseconds number) |
|
|
424
463
|
|
|
425
464
|
### Repeat block:
|
|
426
465
|
|
|
@@ -435,7 +474,7 @@ npm run test:coverage
|
|
|
435
474
|
|
|
436
475
|
---
|
|
437
476
|
|
|
438
|
-
## 🎯 Project Goals (
|
|
477
|
+
## 🎯 Project Goals (v4)
|
|
439
478
|
|
|
440
479
|
* Deterministic behavior
|
|
441
480
|
* Rock-solid DST handling
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Architecture Overview
|
|
1
|
+
# Architecture Overview (v4)
|
|
2
2
|
|
|
3
|
-
## jw-automator
|
|
3
|
+
## jw-automator v4 Architecture
|
|
4
4
|
|
|
5
5
|
This document describes the internal architecture of jw-automator v3.
|
|
6
6
|
|
|
@@ -244,11 +244,13 @@ Returns event list (state unchanged)
|
|
|
244
244
|
- **Simulatable**: Preview future schedules
|
|
245
245
|
- **Catch-up**: Process offline gaps identically to real-time
|
|
246
246
|
|
|
247
|
-
### Why Buffered/UnBuffered?
|
|
247
|
+
### Why `catchUpWindow` (and Legacy Buffered/UnBuffered)?
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
-
|
|
249
|
+
The `catchUpWindow` property, supported by the CoreEngine, precisely defines the time window for recovering missed actions.
|
|
250
|
+
|
|
251
|
+
- **Smart Defaults**: For recurring actions, the `catchUpWindow` defaults to the action's interval (e.g., a 1-hour action has a 1-hour catch-up window). For one-time actions, it defaults to `0` (no catch-up). This prevents "thundering herd" issues by ensuring that actions too old are simply skipped.
|
|
252
|
+
- **Explicit Control**: Users can still set `catchUpWindow` to `0` (skip all missed), a specific millisecond value (tolerate N ms lag), or `"unlimited"` (catch up all, like old buffered behavior).
|
|
253
|
+
- **Legacy `unBuffered`**: The `unBuffered` flag (`true` or `false`) is now a legacy alias for `catchUpWindow: 0` and `catchUpWindow: "unlimited"` respectively. The system transparently maps it.
|
|
252
254
|
|
|
253
255
|
---
|
|
254
256
|
|
package/docs/MIGRATION.md
CHANGED
|
@@ -1,18 +1,45 @@
|
|
|
1
|
-
# Migration Guide:
|
|
1
|
+
# Migration Guide: v3 to v4
|
|
2
2
|
|
|
3
|
-
This guide helps you migrate from jw-automator
|
|
3
|
+
This guide helps you migrate from jw-automator v3 to v4.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
jw-automator
|
|
9
|
+
jw-automator v4 is a significant refinement of v3's architecture, focusing on making the scheduler's behavior even more predictable and robust out-of-the-box. While v3 represented a complete rewrite, v4 introduces breaking changes by refining default behaviors and error handling for action specifications.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
v3 was the widely-deployed production version. v4 builds upon that foundation with enhanced predictability.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## Breaking Changes
|
|
15
|
+
## Breaking Changes from v3 to v4
|
|
16
|
+
|
|
17
|
+
### 1. Default `catchUpWindow` Behavior Changed
|
|
18
|
+
|
|
19
|
+
**v3:** If `catchUpWindow` was not specified, it defaulted to `"unlimited"` (catch up all missed executions).
|
|
20
|
+
**v4:** If `catchUpWindow` is not specified, it now uses a **smart default** based on the action type:
|
|
21
|
+
- For **recurring actions**, it defaults to the **duration of the recurrence interval** (e.g., an hourly action gets a 1-hour `catchUpWindow`).
|
|
22
|
+
- For **one-time actions**, it defaults to **`0`** (skip all missed executions).
|
|
23
|
+
|
|
24
|
+
**Impact:** User applications that relied on the implicit "unlimited" catch-up for all actions in v3 might now see actions being skipped or fast-forwarded more aggressively. If you desire the old "unlimited" catch-up, you must explicitly set `catchUpWindow: "unlimited"`.
|
|
25
|
+
|
|
26
|
+
### 2. Invalid `repeat.type` Throws a Fatal Error
|
|
27
|
+
|
|
28
|
+
**v3:** If `repeat.type` was missing or invalid (e.g., a typo like `'horu'`), Automator would defensively coerce it to `'day'` and emit an `error` event.
|
|
29
|
+
**v4:** An invalid or missing `repeat.type` now throws a hard `Error` immediately.
|
|
30
|
+
|
|
31
|
+
**Impact:** Code that previously succeeded by silently allowing `repeat.type` coercions will now fail fast, forcing explicit correction. This ensures that the action's intent is never misinterpreted.
|
|
32
|
+
|
|
33
|
+
### 3. Coercion Events Changed from `error` to `warning`
|
|
34
|
+
|
|
35
|
+
**v3:** Non-fatal defensive coercions (e.g., an invalid `repeat.interval` being set to `1`, a negative `catchUpWindow` becoming `0`) would emit an `error` event.
|
|
36
|
+
**v4:** These same non-fatal coercions now emit a `warning` event. The `error` event is reserved for more critical issues (e.g., storage failures).
|
|
37
|
+
|
|
38
|
+
**Impact:** If your application was listening for `automator.on('error', ...)` to catch these coercion notifications, you must now update your event listener to `automator.on('warning', ...)` to continue receiving them.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Breaking Changes from v3 to v4
|
|
16
43
|
|
|
17
44
|
### 1. Constructor and Initialization
|
|
18
45
|
|
|
@@ -146,6 +173,20 @@ automator.updateActionByName('Old Name', {
|
|
|
146
173
|
|
|
147
174
|
## New Features in v3
|
|
148
175
|
|
|
176
|
+
## New Features in v4
|
|
177
|
+
|
|
178
|
+
### 1. Smart `catchUpWindow` Defaults
|
|
179
|
+
|
|
180
|
+
The new intelligent default system for `catchUpWindow` means that for most actions, you no longer need to explicitly define this property to get predictable, sensible behavior. It automatically adapts based on whether your action is one-time or recurring.
|
|
181
|
+
|
|
182
|
+
### 2. Dedicated `warning` Event
|
|
183
|
+
|
|
184
|
+
A new `warning` event (`automator.on('warning', ...)`) provides a clearer channel for non-fatal feedback about defensive coercions. This allows developers to distinguish between critical runtime errors and minor data corrections.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## New Features in v3
|
|
189
|
+
|
|
149
190
|
### 1. Simulation
|
|
150
191
|
|
|
151
192
|
Preview future schedules without running them:
|
|
@@ -232,180 +273,47 @@ const automator = new Automator({
|
|
|
232
273
|
|
|
233
274
|
---
|
|
234
275
|
|
|
235
|
-
## Migration Steps
|
|
236
|
-
|
|
237
|
-
### Step 1: Update Initialization
|
|
238
|
-
|
|
239
|
-
Replace your v2 initialization with v3 constructor:
|
|
276
|
+
## Migration Steps (v3 to v4)
|
|
240
277
|
|
|
241
|
-
|
|
242
|
-
// Before
|
|
243
|
-
const automator = require('jw-automator');
|
|
244
|
-
automator.init({ file: './actions.json' });
|
|
278
|
+
### Step 1: Understand Breaking Changes
|
|
245
279
|
|
|
246
|
-
|
|
247
|
-
const Automator = require('jw-automator');
|
|
248
|
-
const automator = new Automator({
|
|
249
|
-
storage: Automator.storage.file('./actions.json')
|
|
250
|
-
});
|
|
251
|
-
```
|
|
280
|
+
Thoroughly review the "Breaking Changes from v3 to v4" section above. Identify which changes affect your application.
|
|
252
281
|
|
|
253
|
-
### Step 2:
|
|
282
|
+
### Step 2: Review `catchUpWindow` Usage
|
|
254
283
|
|
|
255
|
-
|
|
284
|
+
If your v3 application relied on the implicit "unlimited" catch-up for actions where `catchUpWindow` was not explicitly set, you will need to:
|
|
256
285
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
automator.addAction({
|
|
260
|
-
cmd: 'myCmd',
|
|
261
|
-
date: new Date(),
|
|
262
|
-
repeat: { type: 'day', interval: 1 }
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// After
|
|
266
|
-
automator.addAction({
|
|
267
|
-
cmd: 'myCmd',
|
|
268
|
-
date: new Date(),
|
|
269
|
-
repeat: {
|
|
270
|
-
type: 'day',
|
|
271
|
-
interval: 1,
|
|
272
|
-
dstPolicy: 'once' // Explicitly choose DST behavior
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
```
|
|
286
|
+
- **Explicitly set `catchUpWindow: "unlimited"`** for those actions if you wish to maintain the old behavior.
|
|
287
|
+
- Otherwise, understand that these actions will now use the new smart defaults (recurrence interval for recurring, `0` for one-time actions).
|
|
276
288
|
|
|
277
289
|
### Step 3: Update Event Listeners
|
|
278
290
|
|
|
279
|
-
|
|
291
|
+
If your application was listening for `automator.on('error', ...)` to catch notifications about defensive coercions (e.g., invalid `repeat.interval`), you must now update your code to listen for `automator.on('warning', ...)` to receive these non-fatal messages.
|
|
280
292
|
|
|
281
|
-
|
|
282
|
-
// Before
|
|
283
|
-
automator.on('actionExecuted', (data) => { ... });
|
|
293
|
+
### Step 4: Correct Invalid `repeat.type` Definitions
|
|
284
294
|
|
|
285
|
-
|
|
286
|
-
automator.on('action', (event) => { ... });
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Step 4: Update API Calls
|
|
290
|
-
|
|
291
|
-
Use new method names:
|
|
292
|
-
|
|
293
|
-
```javascript
|
|
294
|
-
// Before
|
|
295
|
-
automator.removeAction(id);
|
|
296
|
-
|
|
297
|
-
// After
|
|
298
|
-
automator.removeActionByID(id);
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Step 5: Test DST Behavior
|
|
302
|
-
|
|
303
|
-
Review actions that run during DST transitions and set appropriate `dstPolicy`:
|
|
304
|
-
|
|
305
|
-
- `'once'` - Run only the first occurrence during fall-back (recommended default)
|
|
306
|
-
- `'twice'` - Run both occurrences during fall-back
|
|
307
|
-
|
|
308
|
-
### Step 6: Leverage New Features
|
|
309
|
-
|
|
310
|
-
Consider using:
|
|
311
|
-
- `getActionsInRange()` for calendar previews
|
|
312
|
-
- `describeAction()` for debugging
|
|
313
|
-
- Custom storage adapters for database persistence
|
|
314
|
-
- Update events for logging changes
|
|
295
|
+
If your application previously used actions with invalid or missing `repeat.type` values that were silently corrected in v3, you must now explicitly fix these `repeat.type` values. Failure to do so will result in a hard `Error` when the action is added or updated in v4.
|
|
315
296
|
|
|
316
297
|
---
|
|
317
298
|
|
|
318
299
|
## Compatibility Notes
|
|
319
300
|
|
|
320
|
-
### What's the Same
|
|
301
|
+
### What's the Same (v3 to v4)
|
|
321
302
|
|
|
322
|
-
-
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
-
|
|
303
|
+
- **Core concepts**: Schedule actions with recurrence
|
|
304
|
+
- **Recurrence types**: `second`, `minute`, `hour`, `day`, `week`, `month`, `year`
|
|
305
|
+
- **Local time**: Still operates in local time
|
|
306
|
+
- **API methods**: Names and signatures of `addAction()`, `updateActionByID()`, etc., remain the same.
|
|
307
|
+
- **Function registration**: Still use `addFunction()`
|
|
308
|
+
- **Legacy `unBuffered`**: Still supported as an alias, mapping to `catchUpWindow`.
|
|
327
309
|
|
|
328
|
-
### What's Different
|
|
310
|
+
### What's Different (v3 to v4)
|
|
329
311
|
|
|
330
|
-
-
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
- **API**: More consistent naming (`ByID`, `ByName`)
|
|
335
|
-
- **IDs**: Auto-generated, not user-provided
|
|
336
|
-
- **State**: Better separation of spec vs. state
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Example: Complete Migration
|
|
341
|
-
|
|
342
|
-
**v2 Code:**
|
|
343
|
-
```javascript
|
|
344
|
-
const automator = require('jw-automator');
|
|
345
|
-
automator.init({ file: './actions.json' });
|
|
346
|
-
|
|
347
|
-
automator.addFunction('turnLightOn', () => {
|
|
348
|
-
console.log('Light on');
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
automator.addAction({
|
|
352
|
-
cmd: 'turnLightOn',
|
|
353
|
-
date: new Date('2025-05-01T07:00:00'),
|
|
354
|
-
repeat: { type: 'day', interval: 1 }
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
automator.start();
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
**v3 Code:**
|
|
361
|
-
```javascript
|
|
362
|
-
const Automator = require('jw-automator');
|
|
363
|
-
|
|
364
|
-
const automator = new Automator({
|
|
365
|
-
storage: Automator.storage.file('./actions.json')
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
automator.addFunction('turnLightOn', () => {
|
|
369
|
-
console.log('Light on');
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
automator.addAction({
|
|
373
|
-
name: 'Morning Lights',
|
|
374
|
-
cmd: 'turnLightOn',
|
|
375
|
-
date: new Date('2025-05-01T07:00:00'),
|
|
376
|
-
unBuffered: false,
|
|
377
|
-
repeat: {
|
|
378
|
-
type: 'day',
|
|
379
|
-
interval: 1,
|
|
380
|
-
dstPolicy: 'once'
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
automator.start();
|
|
385
|
-
```
|
|
312
|
+
- **`catchUpWindow` Default Behavior**: Now smart and context-aware, instead of always `"unlimited"`.
|
|
313
|
+
- **`repeat.type` Validation**: Invalid types now throw a fatal `Error`.
|
|
314
|
+
- **Event Handling**: Coercions now emit `warning` events instead of `error` events.
|
|
315
|
+
- **Defensive Defaults Philosophy**: More refined and explicit.
|
|
386
316
|
|
|
387
317
|
---
|
|
388
318
|
|
|
389
319
|
## Getting Help
|
|
390
|
-
|
|
391
|
-
If you encounter issues during migration:
|
|
392
|
-
|
|
393
|
-
1. Check the [README](../README.md) for full API documentation
|
|
394
|
-
2. Review the [Architecture](./ARCHITECTURE.md) for design understanding
|
|
395
|
-
3. Run the examples in the `examples/` directory
|
|
396
|
-
4. File an issue on GitHub
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## Why Rewrite?
|
|
401
|
-
|
|
402
|
-
v3 addresses several issues from v2:
|
|
403
|
-
|
|
404
|
-
- **Infinite loops**: Better safety guards
|
|
405
|
-
- **DST bugs**: Explicit, predictable handling
|
|
406
|
-
- **Catch-up logic**: More reliable offline behavior
|
|
407
|
-
- **Testability**: Deterministic core engine
|
|
408
|
-
- **Maintainability**: Cleaner architecture
|
|
409
|
-
- **Extensibility**: Pluggable storage, better API
|
|
410
|
-
|
|
411
|
-
The rewrite provides a solid foundation for long-term reliability.
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Quick Start Guide
|
|
2
2
|
|
|
3
|
-
Get up and running with jw-automator
|
|
3
|
+
Get up and running with jw-automator v4 in 5 minutes.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -137,36 +137,53 @@ automator.addAction({
|
|
|
137
137
|
|
|
138
138
|
---
|
|
139
139
|
|
|
140
|
-
##
|
|
140
|
+
## Offline Catch-Up with `catchUpWindow`
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
Automator v4 manages offline catch-up using the `catchUpWindow` property, which has smart defaults for predictable behavior.
|
|
143
|
+
|
|
144
|
+
### Smart Defaults for `catchUpWindow`
|
|
145
|
+
|
|
146
|
+
- **Recurring Actions:** If `catchUpWindow` is not specified, it defaults to the **duration of the action's recurrence interval**. This ensures short delays are recovered, but long outages don't cause a "thundering herd."
|
|
147
|
+
- **One-Time Actions:** If `catchUpWindow` is not specified and the action has no `repeat` property, it defaults to **`0`** (skip if missed).
|
|
148
|
+
|
|
149
|
+
### Explicit Control
|
|
150
|
+
|
|
151
|
+
You can explicitly set `catchUpWindow`:
|
|
152
|
+
|
|
153
|
+
- `catchUpWindow: "unlimited"`: Catch up ALL missed executions.
|
|
154
|
+
- `catchUpWindow: 0`: Skip ALL missed executions (real-time only).
|
|
155
|
+
- `catchUpWindow: 5000`: Catch up if missed by ≤5 seconds, skip if older.
|
|
156
|
+
|
|
157
|
+
#### Example: Critical Task (unlimited catch-up)
|
|
143
158
|
|
|
144
159
|
```javascript
|
|
145
160
|
automator.addAction({
|
|
146
161
|
name: 'Critical Task',
|
|
147
162
|
cmd: 'criticalTask',
|
|
148
163
|
date: new Date('2025-05-01T10:00:00'),
|
|
149
|
-
|
|
164
|
+
catchUpWindow: "unlimited", // Execute all missed, even if offline for long
|
|
150
165
|
repeat: { type: 'hour', interval: 1 }
|
|
151
166
|
});
|
|
152
167
|
```
|
|
153
168
|
|
|
154
169
|
If the system is offline from 10:00 to 13:00, it will execute the 10:00, 11:00, and 12:00 occurrences when it comes back online.
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
#### Example: Animation Frame (skip missed)
|
|
157
172
|
|
|
158
173
|
```javascript
|
|
159
174
|
automator.addAction({
|
|
160
175
|
name: 'Animation Frame',
|
|
161
176
|
cmd: 'updateAnimation',
|
|
162
177
|
date: new Date(),
|
|
163
|
-
|
|
178
|
+
catchUpWindow: 0, // Only run if on time
|
|
164
179
|
repeat: { type: 'second', interval: 1 }
|
|
165
180
|
});
|
|
166
181
|
```
|
|
167
182
|
|
|
168
183
|
If the system is delayed, it won't execute missed animation frames.
|
|
169
184
|
|
|
185
|
+
**Legacy `unBuffered`**: The `unBuffered` property is still supported as a direct alias for `catchUpWindow` for backwards compatibility: `unBuffered: false` maps to `catchUpWindow: "unlimited"`, and `unBuffered: true` maps to `catchUpWindow: 0`.
|
|
186
|
+
|
|
170
187
|
---
|
|
171
188
|
|
|
172
189
|
## Simulation
|
|
@@ -249,6 +266,11 @@ automator.on('update', (event) => {
|
|
|
249
266
|
automator.on('error', (event) => {
|
|
250
267
|
console.error('Error:', event.message);
|
|
251
268
|
});
|
|
269
|
+
|
|
270
|
+
// Warnings (non-fatal coercions/corrections)
|
|
271
|
+
automator.on('warning', (event) => {
|
|
272
|
+
console.warn('Warning:', event.message);
|
|
273
|
+
});
|
|
252
274
|
```
|
|
253
275
|
|
|
254
276
|
---
|
|
@@ -349,7 +371,7 @@ process.on('SIGINT', () => {
|
|
|
349
371
|
- Read the full [README](../README.md)
|
|
350
372
|
- Check out [examples](../examples/)
|
|
351
373
|
- Review [Architecture](./ARCHITECTURE.md)
|
|
352
|
-
- See [Migration Guide](./MIGRATION.md) if upgrading from
|
|
374
|
+
- See [Migration Guide](./MIGRATION.md) if upgrading from v3
|
|
353
375
|
|
|
354
376
|
---
|
|
355
377
|
|