jw-automator 4.0.1 → 6.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 +0 -0
- package/README.md +392 -99
- package/docs/11a-macro-fix.md +56 -0
- package/docs/ARCHITECTURE.md +88 -73
- package/docs/MIGRATION.md +218 -54
- package/docs/QUICKSTART.md +63 -48
- package/docs/defensive-defaults.md +9 -9
- package/examples/basic-example.js +24 -9
- package/examples/hello-world.js +2 -2
- package/examples/iot-sensor-example.js +6 -6
- package/examples/seed-example.js +6 -6
- package/index.js +0 -0
- package/package.json +2 -2
- package/src/Automator.js +556 -313
- package/src/core/CoreEngine.js +103 -159
- package/src/core/RecurrenceEngine.js +67 -6
- package/src/host/SchedulerHost.js +60 -14
- package/src/storage/FileStorage.js +0 -59
- package/src/storage/MemoryStorage.js +0 -27
package/docs/QUICKSTART.md
CHANGED
|
@@ -21,7 +21,7 @@ const Automator = require('jw-automator');
|
|
|
21
21
|
|
|
22
22
|
// Create automator with file-based storage
|
|
23
23
|
const automator = new Automator({
|
|
24
|
-
storage: Automator.storage.file('./my-
|
|
24
|
+
storage: Automator.storage.file('./my-tasks.json')
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
// Register a function
|
|
@@ -29,8 +29,8 @@ automator.addFunction('sayHello', function(payload) {
|
|
|
29
29
|
console.log(`Hello, ${payload.name}!`);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
// Add a repeating
|
|
33
|
-
automator.
|
|
32
|
+
// Add a repeating task
|
|
33
|
+
automator.addTask({
|
|
34
34
|
name: 'Greeting',
|
|
35
35
|
cmd: 'sayHello',
|
|
36
36
|
date: new Date(Date.now() + 2000), // Start in 2 seconds
|
|
@@ -43,8 +43,8 @@ automator.addAction({
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
// Listen to events
|
|
46
|
-
automator.on('
|
|
47
|
-
console.log(`
|
|
46
|
+
automator.on('task', (event) => {
|
|
47
|
+
console.log(`Task executed: ${event.name}`);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// Start the scheduler
|
|
@@ -66,7 +66,7 @@ node app.js
|
|
|
66
66
|
### Daily Task at Specific Time
|
|
67
67
|
|
|
68
68
|
```javascript
|
|
69
|
-
automator.
|
|
69
|
+
automator.addTask({
|
|
70
70
|
name: 'Daily Backup',
|
|
71
71
|
cmd: 'runBackup',
|
|
72
72
|
date: new Date('2025-05-01T02:00:00'), // 2:00 AM
|
|
@@ -80,7 +80,7 @@ automator.addAction({
|
|
|
80
80
|
### Weekday Task
|
|
81
81
|
|
|
82
82
|
```javascript
|
|
83
|
-
automator.
|
|
83
|
+
automator.addTask({
|
|
84
84
|
name: 'Weekday Reminder',
|
|
85
85
|
cmd: 'sendReminder',
|
|
86
86
|
date: new Date('2025-05-01T09:00:00'), // 9:00 AM
|
|
@@ -94,7 +94,7 @@ automator.addAction({
|
|
|
94
94
|
### Every N Minutes
|
|
95
95
|
|
|
96
96
|
```javascript
|
|
97
|
-
automator.
|
|
97
|
+
automator.addTask({
|
|
98
98
|
name: 'Health Check',
|
|
99
99
|
cmd: 'checkHealth',
|
|
100
100
|
date: new Date(),
|
|
@@ -108,7 +108,7 @@ automator.addAction({
|
|
|
108
108
|
### Limited Run Count
|
|
109
109
|
|
|
110
110
|
```javascript
|
|
111
|
-
automator.
|
|
111
|
+
automator.addTask({
|
|
112
112
|
name: 'Startup Sequence',
|
|
113
113
|
cmd: 'initSystem',
|
|
114
114
|
date: new Date(),
|
|
@@ -123,7 +123,7 @@ automator.addAction({
|
|
|
123
123
|
### End Date
|
|
124
124
|
|
|
125
125
|
```javascript
|
|
126
|
-
automator.
|
|
126
|
+
automator.addTask({
|
|
127
127
|
name: 'Summer Sprinklers',
|
|
128
128
|
cmd: 'waterLawn',
|
|
129
129
|
date: new Date('2025-06-01T06:00:00'),
|
|
@@ -143,8 +143,8 @@ Automator v4 manages offline catch-up using the `catchUpWindow` property, which
|
|
|
143
143
|
|
|
144
144
|
### Smart Defaults for `catchUpWindow`
|
|
145
145
|
|
|
146
|
-
- **Recurring
|
|
147
|
-
- **One-Time
|
|
146
|
+
- **Recurring Tasks:** If `catchUpWindow` is not specified, it defaults to the **duration of the task's recurrence interval**. This ensures short delays are recovered, but long outages don't cause a "thundering herd."
|
|
147
|
+
- **One-Time Tasks:** If `catchUpWindow` is not specified and the task has no `repeat` property, it defaults to **`0`** (skip if missed).
|
|
148
148
|
|
|
149
149
|
### Explicit Control
|
|
150
150
|
|
|
@@ -157,7 +157,7 @@ You can explicitly set `catchUpWindow`:
|
|
|
157
157
|
#### Example: Critical Task (unlimited catch-up)
|
|
158
158
|
|
|
159
159
|
```javascript
|
|
160
|
-
automator.
|
|
160
|
+
automator.addTask({
|
|
161
161
|
name: 'Critical Task',
|
|
162
162
|
cmd: 'criticalTask',
|
|
163
163
|
date: new Date('2025-05-01T10:00:00'),
|
|
@@ -171,7 +171,7 @@ If the system is offline from 10:00 to 13:00, it will execute the 10:00, 11:00,
|
|
|
171
171
|
#### Example: Animation Frame (skip missed)
|
|
172
172
|
|
|
173
173
|
```javascript
|
|
174
|
-
automator.
|
|
174
|
+
automator.addTask({
|
|
175
175
|
name: 'Animation Frame',
|
|
176
176
|
cmd: 'updateAnimation',
|
|
177
177
|
date: new Date(),
|
|
@@ -194,7 +194,7 @@ Preview what will happen in the future:
|
|
|
194
194
|
const tomorrow = new Date();
|
|
195
195
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
196
196
|
|
|
197
|
-
const events = automator.
|
|
197
|
+
const events = automator.getTasksInRange(new Date(), tomorrow);
|
|
198
198
|
|
|
199
199
|
console.log(`${events.length} events scheduled in next 24 hours`);
|
|
200
200
|
|
|
@@ -207,35 +207,50 @@ events.slice(0, 5).forEach(event => {
|
|
|
207
207
|
|
|
208
208
|
## Storage Options
|
|
209
209
|
|
|
210
|
-
### File
|
|
210
|
+
### File-Based Persistence
|
|
211
211
|
|
|
212
212
|
```javascript
|
|
213
213
|
const automator = new Automator({
|
|
214
|
-
|
|
214
|
+
storageFile: './tasks.json',
|
|
215
|
+
autoSave: true, // default: true
|
|
216
|
+
saveInterval: 15000 // default: 15000ms (15 seconds)
|
|
215
217
|
});
|
|
216
218
|
```
|
|
217
219
|
|
|
218
|
-
|
|
220
|
+
**Moratorium-Based Persistence:**
|
|
221
|
+
- CRUD operations (add/update/remove) save immediately and start a moratorium period
|
|
222
|
+
- Task execution marks state as dirty and saves if moratorium has expired
|
|
223
|
+
- `saveInterval` sets the moratorium period (minimum cooling time between saves)
|
|
224
|
+
- Reduces disk wear from task execution while ensuring CRUD changes persist immediately
|
|
225
|
+
- `stop()` always saves immediately if dirty, ignoring any active moratorium
|
|
226
|
+
|
|
227
|
+
### Memory-Only Mode (No Persistence)
|
|
219
228
|
|
|
220
229
|
```javascript
|
|
221
230
|
const automator = new Automator({
|
|
222
|
-
|
|
231
|
+
// No storageFile = memory-only mode
|
|
223
232
|
});
|
|
224
233
|
```
|
|
225
234
|
|
|
226
|
-
|
|
235
|
+
State is lost when the process ends.
|
|
236
|
+
|
|
237
|
+
### Custom Storage (Database, Cloud, etc.)
|
|
238
|
+
|
|
239
|
+
For custom persistence, use `getTasks()` and event listeners:
|
|
227
240
|
|
|
228
241
|
```javascript
|
|
229
|
-
const automator = new Automator(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
242
|
+
const automator = new Automator(); // Memory-only
|
|
243
|
+
|
|
244
|
+
// Load from custom source
|
|
245
|
+
automator.seed(async (auto) => {
|
|
246
|
+
const tasks = await loadFromDatabase();
|
|
247
|
+
tasks.forEach(task => auto.addTask(task));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Save on updates
|
|
251
|
+
automator.on('update', async () => {
|
|
252
|
+
const tasks = automator.getTasks();
|
|
253
|
+
await saveToDatabase(tasks);
|
|
239
254
|
});
|
|
240
255
|
```
|
|
241
256
|
|
|
@@ -249,17 +264,17 @@ automator.on('ready', () => {
|
|
|
249
264
|
console.log('Scheduler started');
|
|
250
265
|
});
|
|
251
266
|
|
|
252
|
-
//
|
|
253
|
-
automator.on('
|
|
254
|
-
console.log('
|
|
267
|
+
// Task executed
|
|
268
|
+
automator.on('task', (event) => {
|
|
269
|
+
console.log('Task:', event.name);
|
|
255
270
|
console.log('Scheduled:', event.scheduledTime);
|
|
256
271
|
console.log('Actual:', event.actualTime);
|
|
257
272
|
console.log('Count:', event.count);
|
|
258
273
|
});
|
|
259
274
|
|
|
260
|
-
//
|
|
275
|
+
// Task added/updated/removed
|
|
261
276
|
automator.on('update', (event) => {
|
|
262
|
-
console.log('Update:', event.operation, event.
|
|
277
|
+
console.log('Update:', event.operation, event.taskId);
|
|
263
278
|
});
|
|
264
279
|
|
|
265
280
|
// Errors
|
|
@@ -275,12 +290,12 @@ automator.on('warning', (event) => {
|
|
|
275
290
|
|
|
276
291
|
---
|
|
277
292
|
|
|
278
|
-
## Managing
|
|
293
|
+
## Managing Tasks
|
|
279
294
|
|
|
280
295
|
### Add
|
|
281
296
|
|
|
282
297
|
```javascript
|
|
283
|
-
const id = automator.
|
|
298
|
+
const id = automator.addTask({
|
|
284
299
|
name: 'My Task',
|
|
285
300
|
cmd: 'myCommand',
|
|
286
301
|
date: new Date(),
|
|
@@ -292,13 +307,13 @@ const id = automator.addAction({
|
|
|
292
307
|
|
|
293
308
|
```javascript
|
|
294
309
|
// By ID
|
|
295
|
-
automator.
|
|
310
|
+
automator.updateTaskByID(id, {
|
|
296
311
|
name: 'Updated Task',
|
|
297
312
|
repeat: { type: 'hour', interval: 2 }
|
|
298
313
|
});
|
|
299
314
|
|
|
300
315
|
// By name
|
|
301
|
-
automator.
|
|
316
|
+
automator.updateTaskByName('My Task', {
|
|
302
317
|
payload: { updated: true }
|
|
303
318
|
});
|
|
304
319
|
```
|
|
@@ -307,26 +322,26 @@ automator.updateActionByName('My Task', {
|
|
|
307
322
|
|
|
308
323
|
```javascript
|
|
309
324
|
// By ID
|
|
310
|
-
automator.
|
|
325
|
+
automator.removeTaskByID(id);
|
|
311
326
|
|
|
312
327
|
// By name
|
|
313
|
-
automator.
|
|
328
|
+
automator.removeTaskByName('My Task');
|
|
314
329
|
```
|
|
315
330
|
|
|
316
331
|
### Query
|
|
317
332
|
|
|
318
333
|
```javascript
|
|
319
|
-
// All
|
|
320
|
-
const all = automator.
|
|
334
|
+
// All tasks
|
|
335
|
+
const all = automator.getTasks();
|
|
321
336
|
|
|
322
337
|
// By name
|
|
323
|
-
const tasks = automator.
|
|
338
|
+
const tasks = automator.getTasksByName('My Task');
|
|
324
339
|
|
|
325
340
|
// By ID
|
|
326
|
-
const task = automator.
|
|
341
|
+
const task = automator.getTaskByID(id);
|
|
327
342
|
|
|
328
343
|
// Description
|
|
329
|
-
const desc = automator.
|
|
344
|
+
const desc = automator.describeTask(id);
|
|
330
345
|
console.log(desc);
|
|
331
346
|
```
|
|
332
347
|
|
|
@@ -334,12 +349,12 @@ console.log(desc);
|
|
|
334
349
|
|
|
335
350
|
## DST Handling
|
|
336
351
|
|
|
337
|
-
For
|
|
352
|
+
For tasks that run during daylight saving time transitions:
|
|
338
353
|
|
|
339
354
|
### Fall Back (Repeated Hour)
|
|
340
355
|
|
|
341
356
|
```javascript
|
|
342
|
-
automator.
|
|
357
|
+
automator.addTask({
|
|
343
358
|
name: 'DST Aware',
|
|
344
359
|
cmd: 'task',
|
|
345
360
|
date: new Date('2025-11-02T01:30:00'), // Falls in repeated hour
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Automator Defensive Defaults Strategy
|
|
2
2
|
|
|
3
|
-
The Automator's design philosophy for handling
|
|
3
|
+
The Automator's design philosophy for handling task specifications is **"Fail loudly, run defensively."**
|
|
4
4
|
|
|
5
|
-
The goal is to maximize system robustness. Rather than rejecting
|
|
5
|
+
The goal is to maximize system robustness. Rather than rejecting a task with minor errors or missing properties, the Automator will make reasonable, "defensive" assumptions to coerce the task into a valid, runnable state.
|
|
6
6
|
|
|
7
7
|
However, these corrections are never silent. Whenever a default is applied or a value is coerced due to invalid input, the Automator emits either a `warning` or `debug` event. This ensures that the developer is always aware of any assumptions the system has made on their behalf.
|
|
8
8
|
|
|
@@ -10,14 +10,14 @@ However, these corrections are never silent. Whenever a default is applied or a
|
|
|
10
10
|
|
|
11
11
|
### Property Default and Coercion Rules
|
|
12
12
|
|
|
13
|
-
The following rules are applied when
|
|
13
|
+
The following rules are applied when a task is added via `addTask()` or updated via `updateTask...()`.
|
|
14
14
|
|
|
15
15
|
#### **`cmd`**
|
|
16
|
-
- **Rule:**
|
|
17
|
-
- **Behavior:** Throws a hard `Error` if missing. This is the primary exception to the "run defensively" rule, as the
|
|
16
|
+
- **Rule:** A task without a command is not runnable.
|
|
17
|
+
- **Behavior:** Throws a hard `Error` if missing. This is the primary exception to the "run defensively" rule, as the task's intent cannot be determined.
|
|
18
18
|
|
|
19
19
|
#### **`date`** (Start Time)
|
|
20
|
-
- **Rule:**
|
|
20
|
+
- **Rule:** A task needs a starting time.
|
|
21
21
|
- **Default:** If `date` is not provided, it defaults to **5 seconds in the future** from the time it was added.
|
|
22
22
|
- **Event:** `debug`
|
|
23
23
|
|
|
@@ -32,15 +32,15 @@ The following rules are applied when an action is added via `addAction()` or upd
|
|
|
32
32
|
2. **Legacy `unBuffered`:** If `catchUpWindow` is absent but the legacy `unBuffered` property is present, it is mapped for backwards compatibility:
|
|
33
33
|
- `unBuffered: true` maps to `catchUpWindow: 0` (no catch-up).
|
|
34
34
|
- `unBuffered: false` maps to `catchUpWindow: "unlimited"`.
|
|
35
|
-
3. **Smart Default (Recurring):** If the
|
|
36
|
-
4. **Smart Default (One-Time):** If the
|
|
35
|
+
3. **Smart Default (Recurring):** If the task has a `repeat` property, `catchUpWindow` defaults to the **duration of the repeat interval** (e.g., an hourly task gets a 1-hour catch-up window).
|
|
36
|
+
4. **Smart Default (One-Time):** If the task does not have a `repeat` property, `catchUpWindow` defaults to **`0`** (no catch-up).
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
40
|
### Recurrence Rules (`repeat.*`)
|
|
41
41
|
|
|
42
42
|
#### **`repeat.type`**
|
|
43
|
-
- **Rule:** The recurrence `type` is fundamental to the
|
|
43
|
+
- **Rule:** The recurrence `type` is fundamental to the task's behavior and must be a valid string (e.g., 'hour', 'day', 'week').
|
|
44
44
|
- **Behavior:** Throws a hard `Error` if missing or invalid. Unlike other properties, the ambiguity of an invalid type is considered a fatal error, as the user's intent cannot be safely determined.
|
|
45
45
|
|
|
46
46
|
#### **`repeat.interval`**
|
|
@@ -8,7 +8,7 @@ const Automator = require('../index');
|
|
|
8
8
|
|
|
9
9
|
// Create automator with file-based persistence
|
|
10
10
|
const automator = new Automator({
|
|
11
|
-
|
|
11
|
+
storageFile: './example-actions.json',
|
|
12
12
|
autoSave: true,
|
|
13
13
|
saveInterval: 5000
|
|
14
14
|
});
|
|
@@ -36,7 +36,7 @@ automator.addFunction('weeklyBackup', function(payload) {
|
|
|
36
36
|
// Listen to events
|
|
37
37
|
automator.on('ready', () => {
|
|
38
38
|
console.log('Automator started!');
|
|
39
|
-
console.log('Current actions:', automator.
|
|
39
|
+
console.log('Current actions:', automator.getTasks().length);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
automator.on('action', (event) => {
|
|
@@ -51,11 +51,11 @@ automator.on('error', (event) => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
// Seed initial actions (runs only on first use)
|
|
54
|
-
automator.seed((auto) => {
|
|
54
|
+
const seedResult = automator.seed((auto) => {
|
|
55
55
|
console.log('Seeding initial actions...\n');
|
|
56
56
|
|
|
57
57
|
// 1. Every 10 seconds - demo message
|
|
58
|
-
auto.
|
|
58
|
+
const result1 = auto.addTask({
|
|
59
59
|
name: 'Demo Message',
|
|
60
60
|
cmd: 'logMessage',
|
|
61
61
|
date: new Date(Date.now() + 5000), // Start in 5 seconds
|
|
@@ -68,9 +68,12 @@ automator.seed((auto) => {
|
|
|
68
68
|
dstPolicy: 'once'
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
+
if (!result1.success) {
|
|
72
|
+
console.error('Failed to add demo task:', result1.error);
|
|
73
|
+
}
|
|
71
74
|
|
|
72
75
|
// 2. Daily morning routine at 7:00 AM
|
|
73
|
-
auto.
|
|
76
|
+
const result2 = auto.addTask({
|
|
74
77
|
name: 'Morning Routine',
|
|
75
78
|
cmd: 'morningRoutine',
|
|
76
79
|
date: new Date(new Date().setHours(7, 0, 0, 0)),
|
|
@@ -81,13 +84,16 @@ automator.seed((auto) => {
|
|
|
81
84
|
dstPolicy: 'once'
|
|
82
85
|
}
|
|
83
86
|
});
|
|
87
|
+
if (!result2.success) {
|
|
88
|
+
console.error('Failed to add morning routine:', result2.error);
|
|
89
|
+
}
|
|
84
90
|
|
|
85
91
|
// 3. Weekly backup every Sunday at 2:00 AM
|
|
86
92
|
const nextSunday = new Date();
|
|
87
93
|
nextSunday.setDate(nextSunday.getDate() + (7 - nextSunday.getDay()) % 7);
|
|
88
94
|
nextSunday.setHours(2, 0, 0, 0);
|
|
89
95
|
|
|
90
|
-
auto.
|
|
96
|
+
const result3 = auto.addTask({
|
|
91
97
|
name: 'Weekly Backup',
|
|
92
98
|
cmd: 'weeklyBackup',
|
|
93
99
|
date: nextSunday,
|
|
@@ -98,14 +104,23 @@ automator.seed((auto) => {
|
|
|
98
104
|
dstPolicy: 'once'
|
|
99
105
|
}
|
|
100
106
|
});
|
|
107
|
+
if (!result3.success) {
|
|
108
|
+
console.error('Failed to add weekly backup:', result3.error);
|
|
109
|
+
}
|
|
101
110
|
});
|
|
102
111
|
|
|
112
|
+
if (seedResult.seeded) {
|
|
113
|
+
console.log('✅ Database seeded successfully\n');
|
|
114
|
+
} else {
|
|
115
|
+
console.log('ℹ️ Database already populated - seeding skipped\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
// Demonstrate simulation - what will happen in the next 24 hours?
|
|
104
119
|
console.log('\n=== Simulation: Next 24 hours ===');
|
|
105
120
|
const now = new Date();
|
|
106
121
|
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
|
107
122
|
|
|
108
|
-
const futureEvents = automator.
|
|
123
|
+
const futureEvents = automator.getTasksInRange(now, tomorrow);
|
|
109
124
|
console.log(`${futureEvents.length} events scheduled in the next 24 hours\n`);
|
|
110
125
|
|
|
111
126
|
futureEvents.slice(0, 10).forEach((event, i) => {
|
|
@@ -117,8 +132,8 @@ if (futureEvents.length > 10) {
|
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
console.log('\n=== Current Actions ===');
|
|
120
|
-
automator.
|
|
121
|
-
console.log(automator.
|
|
135
|
+
automator.getTasks().forEach((action) => {
|
|
136
|
+
console.log(automator.describeTask(action.id));
|
|
122
137
|
console.log('---');
|
|
123
138
|
});
|
|
124
139
|
|
package/examples/hello-world.js
CHANGED
|
@@ -8,7 +8,7 @@ const Automator = require('../index');
|
|
|
8
8
|
|
|
9
9
|
// Create automator (in-memory, no persistence)
|
|
10
10
|
const automator = new Automator({
|
|
11
|
-
|
|
11
|
+
// No storageFile = memory-only mode
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
// Register a function
|
|
@@ -17,7 +17,7 @@ automator.addFunction('sayHello', function() {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
// Add an action that runs every 3 seconds, 5 times
|
|
20
|
-
automator.
|
|
20
|
+
automator.addTask({
|
|
21
21
|
cmd: 'sayHello',
|
|
22
22
|
date: new Date(Date.now() + 1000), // Start in 1 second
|
|
23
23
|
repeat: {
|
|
@@ -9,7 +9,7 @@ const Automator = require('../index');
|
|
|
9
9
|
|
|
10
10
|
// Create automator
|
|
11
11
|
const automator = new Automator({
|
|
12
|
-
|
|
12
|
+
// Memory-only mode (no persistence)
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
// Simulated sensor data
|
|
@@ -65,7 +65,7 @@ automator.addFunction('dailyReport', function() {
|
|
|
65
65
|
// Add sensor reading actions
|
|
66
66
|
|
|
67
67
|
// 1. Temperature reading every 5 seconds (for 1 minute demo)
|
|
68
|
-
automator.
|
|
68
|
+
automator.addTask({
|
|
69
69
|
name: 'Temperature Reading',
|
|
70
70
|
cmd: 'readTemperature',
|
|
71
71
|
date: new Date(Date.now() + 2000),
|
|
@@ -78,7 +78,7 @@ automator.addAction({
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
// 2. Humidity reading every 10 seconds
|
|
81
|
-
automator.
|
|
81
|
+
automator.addTask({
|
|
82
82
|
name: 'Humidity Reading',
|
|
83
83
|
cmd: 'readHumidity',
|
|
84
84
|
date: new Date(Date.now() + 3000),
|
|
@@ -91,7 +91,7 @@ automator.addAction({
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
// 3. Full sensor sweep every 30 seconds
|
|
94
|
-
automator.
|
|
94
|
+
automator.addTask({
|
|
95
95
|
name: 'Full Sensor Sweep',
|
|
96
96
|
cmd: 'fullSensorSweep',
|
|
97
97
|
date: new Date(Date.now() + 5000),
|
|
@@ -108,7 +108,7 @@ const tomorrow6AM = new Date();
|
|
|
108
108
|
tomorrow6AM.setDate(tomorrow6AM.getDate() + 1);
|
|
109
109
|
tomorrow6AM.setHours(6, 0, 0, 0);
|
|
110
110
|
|
|
111
|
-
automator.
|
|
111
|
+
automator.addTask({
|
|
112
112
|
name: 'Daily Report',
|
|
113
113
|
cmd: 'dailyReport',
|
|
114
114
|
date: tomorrow6AM,
|
|
@@ -141,7 +141,7 @@ setTimeout(() => {
|
|
|
141
141
|
automator.stop();
|
|
142
142
|
|
|
143
143
|
console.log('\nFinal action summary:');
|
|
144
|
-
automator.
|
|
144
|
+
automator.getTasks().forEach(action => {
|
|
145
145
|
console.log(`- ${action.name}: ${action.count} executions`);
|
|
146
146
|
});
|
|
147
147
|
|
package/examples/seed-example.js
CHANGED
|
@@ -22,7 +22,7 @@ if (fs.existsSync(STORAGE_FILE)) {
|
|
|
22
22
|
|
|
23
23
|
// Create automator with file-based persistence
|
|
24
24
|
const automator = new Automator({
|
|
25
|
-
|
|
25
|
+
storageFile: STORAGE_FILE,
|
|
26
26
|
autoSave: true,
|
|
27
27
|
saveInterval: 3000
|
|
28
28
|
});
|
|
@@ -49,7 +49,7 @@ automator.addFunction('flexibleTask', function(payload, event) {
|
|
|
49
49
|
// Listen to events
|
|
50
50
|
automator.on('ready', () => {
|
|
51
51
|
console.log('\n=== Automator Ready ===');
|
|
52
|
-
console.log(`Actions loaded: ${automator.
|
|
52
|
+
console.log(`Actions loaded: ${automator.getTasks().length}`);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
// SEED: Initialize actions (runs only on first use)
|
|
@@ -58,7 +58,7 @@ const didSeed = automator.seed((auto) => {
|
|
|
58
58
|
console.log('Creating initial system tasks...\n');
|
|
59
59
|
|
|
60
60
|
// Task 1: Critical billing task - NEVER miss an execution
|
|
61
|
-
auto.
|
|
61
|
+
auto.addTask({
|
|
62
62
|
name: 'Billing Task',
|
|
63
63
|
cmd: 'criticalTask',
|
|
64
64
|
date: new Date(Date.now() + 2000),
|
|
@@ -72,7 +72,7 @@ const didSeed = automator.seed((auto) => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
// Task 2: Real-time alert - only relevant "now"
|
|
75
|
-
auto.
|
|
75
|
+
auto.addTask({
|
|
76
76
|
name: 'Realtime Alert',
|
|
77
77
|
cmd: 'realtimeAlert',
|
|
78
78
|
date: new Date(Date.now() + 5000),
|
|
@@ -86,7 +86,7 @@ const didSeed = automator.seed((auto) => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
// Task 3: Flexible sensor reading - tolerate brief lag
|
|
89
|
-
auto.
|
|
89
|
+
auto.addTask({
|
|
90
90
|
name: 'Sensor Reading',
|
|
91
91
|
cmd: 'flexibleTask',
|
|
92
92
|
date: new Date(Date.now() + 8000),
|
|
@@ -109,7 +109,7 @@ if (didSeed) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
console.log('\n=== Current Schedule ===');
|
|
112
|
-
automator.
|
|
112
|
+
automator.getTasks().forEach((action) => {
|
|
113
113
|
console.log(`\n${action.name}:`);
|
|
114
114
|
console.log(` catchUpWindow: ${action.catchUpWindow === "unlimited" ? '"unlimited"' : action.catchUpWindow + 'ms'}`);
|
|
115
115
|
console.log(` Next run: ${action.date.toLocaleTimeString()}`);
|
package/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jw-automator",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A resilient, local-time, 1-second precision automation scheduler for Node.js with
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "A resilient, local-time, 1-second precision automation scheduler for Node.js with result-based error handling and defensive defaults",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"src/",
|