jw-automator 2.0.0 → 3.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 +76 -0
- package/README.md +366 -192
- package/docs/ARCHITECTURE.md +342 -0
- package/docs/MIGRATION.md +407 -0
- package/docs/QUICKSTART.md +350 -0
- package/examples/basic-example.js +135 -0
- package/examples/hello-world.js +40 -0
- package/examples/iot-sensor-example.js +149 -0
- package/index.js +7 -0
- package/package.json +53 -19
- package/src/Automator.js +429 -0
- package/src/core/CoreEngine.js +210 -0
- package/src/core/RecurrenceEngine.js +237 -0
- package/src/host/SchedulerHost.js +174 -0
- package/src/storage/FileStorage.js +59 -0
- package/src/storage/MemoryStorage.js +27 -0
- package/.actions.json +0 -1
- package/.jshintrc +0 -16
- package/.vscode/settings.json +0 -6
- package/LICENSE +0 -674
- package/automator.js +0 -696
- package/demo.js +0 -76
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Migration Guide: v2 to v3
|
|
2
|
+
|
|
3
|
+
This guide helps you migrate from jw-automator v2 to v3.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
jw-automator v3 is a **complete clean-room rewrite** with improved semantics, better DST handling, and a cleaner API. While the core concepts remain the same, there are breaking changes from v2.
|
|
10
|
+
|
|
11
|
+
v2 was the widely-deployed production version. v3 represents a ground-up reimplementation that preserves the philosophy while modernizing the architecture.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Breaking Changes
|
|
16
|
+
|
|
17
|
+
### 1. Constructor and Initialization
|
|
18
|
+
|
|
19
|
+
**v2:**
|
|
20
|
+
```javascript
|
|
21
|
+
const automator = require('jw-automator');
|
|
22
|
+
automator.init({ file: './actions.json' });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**v3:**
|
|
26
|
+
```javascript
|
|
27
|
+
const Automator = require('jw-automator');
|
|
28
|
+
const automator = new Automator({
|
|
29
|
+
storage: Automator.storage.file('./actions.json')
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Action Structure
|
|
34
|
+
|
|
35
|
+
**v2:**
|
|
36
|
+
Action structure was less formalized.
|
|
37
|
+
|
|
38
|
+
**v3:**
|
|
39
|
+
```javascript
|
|
40
|
+
{
|
|
41
|
+
id: 1, // Auto-generated
|
|
42
|
+
name: 'My Action', // Optional
|
|
43
|
+
cmd: 'commandName', // Required
|
|
44
|
+
payload: {}, // Optional
|
|
45
|
+
date: Date, // Required (or null for immediate)
|
|
46
|
+
unBuffered: false, // Default false
|
|
47
|
+
repeat: { // Optional
|
|
48
|
+
type: 'day',
|
|
49
|
+
interval: 1,
|
|
50
|
+
limit: null,
|
|
51
|
+
endDate: null,
|
|
52
|
+
dstPolicy: 'once' // NEW in v3
|
|
53
|
+
},
|
|
54
|
+
count: 0 // Execution counter
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. DST Policy
|
|
59
|
+
|
|
60
|
+
**v2:**
|
|
61
|
+
DST behavior was implicit and sometimes unpredictable.
|
|
62
|
+
|
|
63
|
+
**v3:**
|
|
64
|
+
Explicit DST policy for fall-back scenarios:
|
|
65
|
+
```javascript
|
|
66
|
+
repeat: {
|
|
67
|
+
type: 'hour',
|
|
68
|
+
interval: 1,
|
|
69
|
+
dstPolicy: 'once' // or 'twice'
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 4. Event Names
|
|
74
|
+
|
|
75
|
+
**v2:**
|
|
76
|
+
Various event names.
|
|
77
|
+
|
|
78
|
+
**v3:**
|
|
79
|
+
Standardized events:
|
|
80
|
+
- `ready` - Scheduler started
|
|
81
|
+
- `action` - Action executed
|
|
82
|
+
- `update` - Action added/updated/removed
|
|
83
|
+
- `error` - Error occurred
|
|
84
|
+
- `debug` - Debug information
|
|
85
|
+
|
|
86
|
+
### 5. API Methods
|
|
87
|
+
|
|
88
|
+
#### Adding Actions
|
|
89
|
+
|
|
90
|
+
**v2:**
|
|
91
|
+
```javascript
|
|
92
|
+
automator.addAction(actionObject);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**v3:**
|
|
96
|
+
```javascript
|
|
97
|
+
const id = automator.addAction(actionSpec);
|
|
98
|
+
// Returns the action ID
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Getting Actions
|
|
102
|
+
|
|
103
|
+
**v2:**
|
|
104
|
+
```javascript
|
|
105
|
+
const actions = automator.getActions();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**v3:**
|
|
109
|
+
```javascript
|
|
110
|
+
const actions = automator.getActions(); // Deep copy
|
|
111
|
+
const action = automator.getActionByID(id);
|
|
112
|
+
const actions = automator.getActionsByName('name');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Removing Actions
|
|
116
|
+
|
|
117
|
+
**v2:**
|
|
118
|
+
```javascript
|
|
119
|
+
automator.removeAction(id);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**v3:**
|
|
123
|
+
```javascript
|
|
124
|
+
automator.removeActionByID(id);
|
|
125
|
+
automator.removeActionByName('name'); // Returns count removed
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Updating Actions
|
|
129
|
+
|
|
130
|
+
**v2:**
|
|
131
|
+
Limited update capability.
|
|
132
|
+
|
|
133
|
+
**v3:**
|
|
134
|
+
```javascript
|
|
135
|
+
automator.updateActionByID(id, {
|
|
136
|
+
name: 'New Name',
|
|
137
|
+
repeat: { type: 'hour', interval: 2 }
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## New Features in v3
|
|
144
|
+
|
|
145
|
+
### 1. Simulation
|
|
146
|
+
|
|
147
|
+
Preview future schedules without running them:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const events = automator.getActionsInRange(
|
|
151
|
+
new Date('2025-05-01'),
|
|
152
|
+
new Date('2025-05-07')
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log(`${events.length} events will occur`);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. Pluggable Storage
|
|
159
|
+
|
|
160
|
+
Choose or create storage backends:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// Memory storage (no persistence)
|
|
164
|
+
const automator = new Automator({
|
|
165
|
+
storage: Automator.storage.memory()
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// File storage
|
|
169
|
+
const automator = new Automator({
|
|
170
|
+
storage: Automator.storage.file('./actions.json')
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Custom storage
|
|
174
|
+
const automator = new Automator({
|
|
175
|
+
storage: {
|
|
176
|
+
load: () => { /* load from DB */ },
|
|
177
|
+
save: (state) => { /* save to DB */ }
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 3. Action Description
|
|
183
|
+
|
|
184
|
+
Human-readable action summaries:
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
console.log(automator.describeAction(1));
|
|
188
|
+
// Action #1 - Morning Lights
|
|
189
|
+
// Command: turnLightOn
|
|
190
|
+
// Next run: 5/1/2025, 7:00:00 AM
|
|
191
|
+
// Executions: 15
|
|
192
|
+
// Buffered: true
|
|
193
|
+
// Recurrence: day
|
|
194
|
+
// DST policy: once
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 4. Better Event Payloads
|
|
198
|
+
|
|
199
|
+
Action events include rich metadata:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
automator.on('action', (event) => {
|
|
203
|
+
console.log(event);
|
|
204
|
+
// {
|
|
205
|
+
// type: 'action',
|
|
206
|
+
// actionId: 1,
|
|
207
|
+
// name: 'My Action',
|
|
208
|
+
// cmd: 'myCmd',
|
|
209
|
+
// payload: {},
|
|
210
|
+
// scheduledTime: Date,
|
|
211
|
+
// actualTime: Date,
|
|
212
|
+
// count: 5
|
|
213
|
+
// }
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 5. Auto-Save Control
|
|
218
|
+
|
|
219
|
+
Fine-tune persistence:
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
const automator = new Automator({
|
|
223
|
+
storage: Automator.storage.file('./actions.json'),
|
|
224
|
+
autoSave: true,
|
|
225
|
+
saveInterval: 10000 // Save every 10 seconds
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Migration Steps
|
|
232
|
+
|
|
233
|
+
### Step 1: Update Initialization
|
|
234
|
+
|
|
235
|
+
Replace your v2 initialization with v3 constructor:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Before
|
|
239
|
+
const automator = require('jw-automator');
|
|
240
|
+
automator.init({ file: './actions.json' });
|
|
241
|
+
|
|
242
|
+
// After
|
|
243
|
+
const Automator = require('jw-automator');
|
|
244
|
+
const automator = new Automator({
|
|
245
|
+
storage: Automator.storage.file('./actions.json')
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Step 2: Update Action Definitions
|
|
250
|
+
|
|
251
|
+
Add `dstPolicy` to actions with repeat:
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// Before
|
|
255
|
+
automator.addAction({
|
|
256
|
+
cmd: 'myCmd',
|
|
257
|
+
date: new Date(),
|
|
258
|
+
repeat: { type: 'day', interval: 1 }
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// After
|
|
262
|
+
automator.addAction({
|
|
263
|
+
cmd: 'myCmd',
|
|
264
|
+
date: new Date(),
|
|
265
|
+
repeat: {
|
|
266
|
+
type: 'day',
|
|
267
|
+
interval: 1,
|
|
268
|
+
dstPolicy: 'once' // Explicitly choose DST behavior
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Step 3: Update Event Listeners
|
|
274
|
+
|
|
275
|
+
Standardize event names:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// Before
|
|
279
|
+
automator.on('actionExecuted', (data) => { ... });
|
|
280
|
+
|
|
281
|
+
// After
|
|
282
|
+
automator.on('action', (event) => { ... });
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Step 4: Update API Calls
|
|
286
|
+
|
|
287
|
+
Use new method names:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
// Before
|
|
291
|
+
automator.removeAction(id);
|
|
292
|
+
|
|
293
|
+
// After
|
|
294
|
+
automator.removeActionByID(id);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Step 5: Test DST Behavior
|
|
298
|
+
|
|
299
|
+
Review actions that run during DST transitions and set appropriate `dstPolicy`:
|
|
300
|
+
|
|
301
|
+
- `'once'` - Run only the first occurrence during fall-back (recommended default)
|
|
302
|
+
- `'twice'` - Run both occurrences during fall-back
|
|
303
|
+
|
|
304
|
+
### Step 6: Leverage New Features
|
|
305
|
+
|
|
306
|
+
Consider using:
|
|
307
|
+
- `getActionsInRange()` for calendar previews
|
|
308
|
+
- `describeAction()` for debugging
|
|
309
|
+
- Custom storage adapters for database persistence
|
|
310
|
+
- Update events for logging changes
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Compatibility Notes
|
|
315
|
+
|
|
316
|
+
### What's the Same
|
|
317
|
+
|
|
318
|
+
- **Core concept**: Schedule actions with recurrence
|
|
319
|
+
- **Recurrence types**: second, minute, hour, day, week, month, year
|
|
320
|
+
- **Local time**: Still operates in local time
|
|
321
|
+
- **Buffered/unbuffered**: Still supported (as `unBuffered` flag)
|
|
322
|
+
- **Function registration**: Still use `addFunction()`
|
|
323
|
+
|
|
324
|
+
### What's Different
|
|
325
|
+
|
|
326
|
+
- **Constructor**: Now uses `new Automator()`
|
|
327
|
+
- **Storage**: Explicitly configured
|
|
328
|
+
- **DST**: Explicit policy required
|
|
329
|
+
- **Events**: Standardized names and payloads
|
|
330
|
+
- **API**: More consistent naming (`ByID`, `ByName`)
|
|
331
|
+
- **IDs**: Auto-generated, not user-provided
|
|
332
|
+
- **State**: Better separation of spec vs. state
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Example: Complete Migration
|
|
337
|
+
|
|
338
|
+
**v2 Code:**
|
|
339
|
+
```javascript
|
|
340
|
+
const automator = require('jw-automator');
|
|
341
|
+
automator.init({ file: './actions.json' });
|
|
342
|
+
|
|
343
|
+
automator.addFunction('turnLightOn', () => {
|
|
344
|
+
console.log('Light on');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
automator.addAction({
|
|
348
|
+
cmd: 'turnLightOn',
|
|
349
|
+
date: new Date('2025-05-01T07:00:00'),
|
|
350
|
+
repeat: { type: 'day', interval: 1 }
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
automator.start();
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**v3 Code:**
|
|
357
|
+
```javascript
|
|
358
|
+
const Automator = require('jw-automator');
|
|
359
|
+
|
|
360
|
+
const automator = new Automator({
|
|
361
|
+
storage: Automator.storage.file('./actions.json')
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
automator.addFunction('turnLightOn', () => {
|
|
365
|
+
console.log('Light on');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
automator.addAction({
|
|
369
|
+
name: 'Morning Lights',
|
|
370
|
+
cmd: 'turnLightOn',
|
|
371
|
+
date: new Date('2025-05-01T07:00:00'),
|
|
372
|
+
unBuffered: false,
|
|
373
|
+
repeat: {
|
|
374
|
+
type: 'day',
|
|
375
|
+
interval: 1,
|
|
376
|
+
dstPolicy: 'once'
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
automator.start();
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Getting Help
|
|
386
|
+
|
|
387
|
+
If you encounter issues during migration:
|
|
388
|
+
|
|
389
|
+
1. Check the [README](../README.md) for full API documentation
|
|
390
|
+
2. Review the [Architecture](./ARCHITECTURE.md) for design understanding
|
|
391
|
+
3. Run the examples in the `examples/` directory
|
|
392
|
+
4. File an issue on GitHub
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Why Rewrite?
|
|
397
|
+
|
|
398
|
+
v3 addresses several issues from v2:
|
|
399
|
+
|
|
400
|
+
- **Infinite loops**: Better safety guards
|
|
401
|
+
- **DST bugs**: Explicit, predictable handling
|
|
402
|
+
- **Catch-up logic**: More reliable offline behavior
|
|
403
|
+
- **Testability**: Deterministic core engine
|
|
404
|
+
- **Maintainability**: Cleaner architecture
|
|
405
|
+
- **Extensibility**: Pluggable storage, better API
|
|
406
|
+
|
|
407
|
+
The rewrite provides a solid foundation for long-term reliability.
|