jw-automator 1.0.2 โ†’ 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 ADDED
@@ -0,0 +1,76 @@
1
+ # Changelog
2
+
3
+ All notable changes to jw-automator will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.0.0] - 2025-11-17
9
+
10
+ ### Added (v3 Complete Rewrite)
11
+
12
+ - **Core Engine**: Pure deterministic `step()` function for scheduling
13
+ - **1-Second Precision**: Fixed 1-second tick interval with zero drift
14
+ - **Local-Time Semantics**: All recurrence rules operate in local wall-clock time
15
+ - **DST Handling**: Configurable DST policies (`once`/`twice`) for fall-back scenarios
16
+ - **Offline Catch-Up**: Buffered/unBuffered execution semantics for resilience
17
+ - **Recurrence Types**:
18
+ - `second`, `minute`, `hour`
19
+ - `day`, `weekday`, `weekend`, `week`
20
+ - `month`, `year`
21
+ - **Simulation API**: `getActionsInRange()` for future schedule preview
22
+ - **Pluggable Storage**: File-based and memory-based storage adapters
23
+ - **Custom Storage Interface**: Support for custom persistence backends
24
+ - **Event System**:
25
+ - `ready` - Scheduler started
26
+ - `action` - Action executed
27
+ - `update` - Actions added/updated/removed
28
+ - `error` - Error events
29
+ - `debug` - Debug information
30
+ - **Action Management**:
31
+ - `addAction()` - Add new scheduled actions
32
+ - `updateActionByID()` - Update existing actions
33
+ - `removeActionByID()` - Remove by ID
34
+ - `removeActionByName()` - Remove by name
35
+ - `getActions()` - Get all actions
36
+ - `getActionsByName()` - Query by name
37
+ - `describeAction()` - Human-readable description
38
+ - **Meta-Actions**: Actions can create/modify other actions
39
+ - **Auto-Save**: Configurable auto-save with custom intervals
40
+ - **Safety Guards**:
41
+ - Maximum iteration limits to prevent infinite loops
42
+ - Monotonic time progression guarantees
43
+ - Bounded per-tick execution
44
+ - **Comprehensive Tests**: Full test suite with Jest
45
+ - **Examples**: Basic and IoT sensor examples included
46
+
47
+ ### Changed from v2
48
+
49
+ - Complete clean-room rewrite
50
+ - Improved DST handling with explicit policies
51
+ - Better separation of action spec vs. state
52
+ - Deterministic core suitable for testing and simulation
53
+ - More predictable catch-up behavior
54
+ - Enhanced error handling and reporting
55
+ - Improved API ergonomics
56
+
57
+ ### Technical Improvements
58
+
59
+ - Pure functional core engine
60
+ - Environment-agnostic architecture
61
+ - Reduced edge-case bugs
62
+ - Better maintainability
63
+ - Comprehensive documentation
64
+ - Type-safe action specifications
65
+
66
+ ---
67
+
68
+ ## [2.x] - Legacy
69
+
70
+ **Production version - widely deployed**
71
+
72
+ Version 2.x was the stable, production release of jw-automator that was actively used in home automation, IoT projects, and personal servers. Available in git history.
73
+
74
+ ---
75
+
76
+ For upgrade guidance from v2 to v3, see the migration guide in the documentation.
package/README.md CHANGED
@@ -1 +1,452 @@
1
- # Automator
1
+ # ๐Ÿ“š **jw-automator v3**
2
+
3
+ ### A resilient, local-time, 1-second precision automation scheduler for Node.js
4
+
5
+ **Human-friendly recurrence rules. Offline catch-up. DST-safe. Predictable. Extensible.**
6
+
7
+ ---
8
+
9
+ ## โญ๏ธ Overview
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.
12
+
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
+
15
+ * **1-second granularity** with zero drift
16
+ * **Local calendar semantics** (weekday/weekend, monthly, yearly)
17
+ * **Configurable DST policies** (fall-back once/twice)
18
+ * **Offline resiliency & catch-up logic**
19
+ * **Buffered/unBuffered execution policies**
20
+ * **Rich introspection and event lifecycle**
21
+ * **Meta-actions** that can dynamically create/update other actions
22
+ * **Pluggable persistence** (file, memory, custom storage)
23
+ * **Deterministic step engine** suitable for simulation/testing
24
+
25
+ This makes jw-automator ideal for:
26
+
27
+ * Small Raspberry Pi home automation hubs
28
+ * IoT applications
29
+ * Sensor sampling / periodic readings
30
+ * Daily/weekly routines
31
+ * "Smart home" orchestrations
32
+ * Systems that must *survive restarts, reboots, offline gaps, and DST transitions*
33
+
34
+ jw-automator v3 is a **clean-room re-architecture** of the original library, keeping its best ideas while formalizing its semantics, improving correctness, and providing a crisp developer experience.
35
+
36
+ ---
37
+
38
+ ## ๐Ÿš€ Quick Start
39
+
40
+ ### Installation
41
+
42
+ ```bash
43
+ npm install jw-automator
44
+ ```
45
+
46
+ ### Basic Usage
47
+
48
+ ```js
49
+ const Automator = require('jw-automator');
50
+
51
+ // Create an automator with file-based persistence
52
+ const automator = new Automator({
53
+ storage: Automator.storage.file('./actions.json')
54
+ });
55
+
56
+ // Register a command function
57
+ automator.addFunction('turnLightOn', function(payload) {
58
+ console.log('Turning light on');
59
+ });
60
+
61
+ // Add an action
62
+ automator.addAction({
63
+ name: 'Morning Lights',
64
+ cmd: 'turnLightOn',
65
+ date: new Date('2025-05-01T07:00:00'),
66
+ payload: null,
67
+ unBuffered: false,
68
+ repeat: {
69
+ type: 'day',
70
+ interval: 1,
71
+ limit: null,
72
+ endDate: null,
73
+ dstPolicy: 'once'
74
+ }
75
+ });
76
+
77
+ // Start the scheduler
78
+ automator.start();
79
+ ```
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ”ฅ Features
84
+
85
+ ### 1. **True 1-Second Precision**
86
+
87
+ * Scheduler tick interval is fixed at **1 second**.
88
+ * Execution times are aligned to the nearest whole second.
89
+ * No promise of sub-second timing (by design).
90
+ * Ideal for low-power hardware prone to event-loop delays.
91
+
92
+ > **Why?**
93
+ > A scheduler that *promises less* is dramatically more reliable.
94
+
95
+ ---
96
+
97
+ ### 2. **Human-Friendly Recurrence Rules**
98
+
99
+ Each action can specify a recurrence like:
100
+
101
+ ```js
102
+ repeat: {
103
+ type: 'weekday', // or: second, minute, hour, day, week,
104
+ // month, year, weekend
105
+ interval: 1, // every N occurrences
106
+ limit: null, // optional max count
107
+ endDate: null, // optional cutoff date
108
+ dstPolicy: 'once', // or 'twice'
109
+ }
110
+ ```
111
+
112
+ Examples:
113
+
114
+ * Every day at 7:00 AM
115
+ * Every 15 minutes
116
+ * Every weekend at 10:00
117
+ * Every month on the 1st
118
+ * Every weekday at market open
119
+ * Once per second for 5 minutes (limit-based)
120
+
121
+ ---
122
+
123
+ ### 3. **Local-Time First, DST-Aware**
124
+
125
+ jw-automator's recurrence rules operate in **local wall-clock time**, not UTC.
126
+
127
+ This means:
128
+
129
+ * "7:00 AM" always means **local** 7:00 AM.
130
+ * Weekdays/weekends follow the user's locale.
131
+ * DST transitions are **explicit and predictable**:
132
+
133
+ * **Spring forward:** missing hour handled via buffered/unBuffered rules
134
+ * **Fall back:** user chooses `dstPolicy: 'once' | 'twice'`
135
+
136
+ This avoids cron's silent-but-surprising behaviors.
137
+
138
+ ---
139
+
140
+ ### 4. **Resilient Offline Catch-Up**
141
+
142
+ If the device is offline or delayed (e.g., blocked by CPU load):
143
+
144
+ ```js
145
+ unBuffered: false // default: catch up missed executions
146
+ unBuffered: true // skip missed executions
147
+ ```
148
+
149
+ For example:
150
+
151
+ * A job scheduled at 09:00 will still run when the device restarts at 10:00 (if buffered).
152
+ * A sequence of per-second readings will "compress" naturally after a delay.
153
+
154
+ This feature is ideal for:
155
+
156
+ * Home automation logic ("turn heater off at 9 even if offline")
157
+ * Sensor sampling
158
+ * Data collection pipelines
159
+
160
+ ---
161
+
162
+ ### 5. **Deterministic "Step Engine"**
163
+
164
+ The heart of jw-automator is a pure scheduling primitive:
165
+
166
+ ```
167
+ step(state, lastTick, now) โ†’ { newState, events }
168
+ ```
169
+
170
+ This powers:
171
+
172
+ * Real-time ticking
173
+ * Offline catch-up
174
+ * Future schedule simulation
175
+ * Testing
176
+ * Meta-scheduling (actions that schedule other actions)
177
+
178
+ Because `step` is deterministic, you can:
179
+
180
+ * Test schedules without time passing
181
+ * Generate "what would happen tomorrow"
182
+ * Debug recurrence rules
183
+ * Build custom visual schedulers
184
+
185
+ ---
186
+
187
+ ### 6. **Meta-Actions (Actions that Create Actions)**
188
+
189
+ jw-automator treats actions as **data**, enabling higher-order patterns:
190
+
191
+ * A daily 7:00 AM action can spawn a sequence of 60 one-per-second actions.
192
+ * A monthly billing action can create daily reminder actions.
193
+ * A multi-step animation (e.g., dimming a light) can create timed sub-actions.
194
+
195
+ Actions have a `repeat.count` that can be pre-set or manipulated intentionally.
196
+
197
+ This makes jw-automator more like a *mini automation runtime* than just a cron clone.
198
+
199
+ ---
200
+
201
+ ## ๐Ÿ“ API Reference
202
+
203
+ ### Constructor
204
+
205
+ ```js
206
+ new Automator(options)
207
+ ```
208
+
209
+ Options:
210
+ * `storage` - Storage adapter (default: memory)
211
+ * `autoSave` - Auto-save state (default: true)
212
+ * `saveInterval` - Save interval in ms (default: 5000)
213
+
214
+ ### Methods
215
+
216
+ #### `start()`
217
+ Start the scheduler.
218
+
219
+ #### `stop()`
220
+ Stop the scheduler and save state.
221
+
222
+ #### `addFunction(name, fn)`
223
+ Register a command function.
224
+
225
+ ```js
226
+ automator.addFunction('myCommand', function(payload, event) {
227
+ console.log('Executing command with payload:', payload);
228
+ });
229
+ ```
230
+
231
+ #### `addAction(actionSpec)`
232
+ Add a new action. Returns the action ID.
233
+
234
+ ```js
235
+ const id = automator.addAction({
236
+ name: 'My Action',
237
+ cmd: 'myCommand',
238
+ date: new Date('2025-05-01T10:00:00'),
239
+ payload: { data: 'value' },
240
+ unBuffered: false,
241
+ repeat: {
242
+ type: 'hour',
243
+ interval: 2,
244
+ limit: 10,
245
+ dstPolicy: 'once'
246
+ }
247
+ });
248
+ ```
249
+
250
+ #### `updateActionByID(id, updates)`
251
+ Update an existing action.
252
+
253
+ ```js
254
+ automator.updateActionByID(1, {
255
+ name: 'Updated Name',
256
+ repeat: { type: 'day', interval: 1 }
257
+ });
258
+ ```
259
+
260
+ #### `removeActionByID(id)`
261
+ Remove an action by ID.
262
+
263
+ #### `removeActionByName(name)`
264
+ Remove all actions with the given name.
265
+
266
+ #### `getActions()`
267
+ Get all actions (deep copy).
268
+
269
+ #### `getActionsByName(name)`
270
+ Get actions by name.
271
+
272
+ #### `getActionByID(id)`
273
+ Get a specific action by ID.
274
+
275
+ #### `getActionsInRange(startDate, endDate, callback)`
276
+ Simulate actions in a time range.
277
+
278
+ ```js
279
+ const events = automator.getActionsInRange(
280
+ new Date('2025-05-01'),
281
+ new Date('2025-05-07')
282
+ );
283
+
284
+ console.log(events); // Array of scheduled events
285
+ ```
286
+
287
+ #### `describeAction(id)`
288
+ Get a human-readable description of an action.
289
+
290
+ ### Events
291
+
292
+ Listen to events using `automator.on(event, callback)`:
293
+
294
+ * `ready` - Scheduler started
295
+ * `action` - Action executed
296
+ * `update` - Action added/updated/removed
297
+ * `error` - Error occurred
298
+ * `debug` - Debug information
299
+
300
+ ```js
301
+ automator.on('action', (event) => {
302
+ console.log('Action executed:', event.name);
303
+ console.log('Scheduled:', event.scheduledTime);
304
+ console.log('Actual:', event.actualTime);
305
+ });
306
+ ```
307
+
308
+ ### Storage Adapters
309
+
310
+ #### File Storage
311
+
312
+ ```js
313
+ const automator = new Automator({
314
+ storage: Automator.storage.file('./actions.json')
315
+ });
316
+ ```
317
+
318
+ #### Memory Storage
319
+
320
+ ```js
321
+ const automator = new Automator({
322
+ storage: Automator.storage.memory()
323
+ });
324
+ ```
325
+
326
+ #### Custom Storage
327
+
328
+ ```js
329
+ const automator = new Automator({
330
+ storage: {
331
+ load: function() {
332
+ // Return { actions: [...] }
333
+ },
334
+ save: function(state) {
335
+ // Save state
336
+ }
337
+ }
338
+ });
339
+ ```
340
+
341
+ ---
342
+
343
+ ## ๐Ÿ“Š Example: Sensor Reading Every Second
344
+
345
+ ```js
346
+ automator.addAction({
347
+ name: 'TempSensor',
348
+ cmd: 'readTemp',
349
+ date: null, // run immediately
350
+ payload: null,
351
+ unBuffered: false, // catch up if delayed
352
+ repeat: {
353
+ type: 'second',
354
+ interval: 1
355
+ }
356
+ });
357
+ ```
358
+
359
+ If the system stalls:
360
+
361
+ * At 00:00:00 โ†’ reading #1
362
+ * Heavy load โ†’ no ticks for 5 seconds
363
+ * At 00:00:06 โ†’ automator triggers readings #2โ€“#6, advancing schedule
364
+
365
+ Your "60 readings per minute" pattern is preserved logically.
366
+
367
+ ---
368
+
369
+ ## ๐Ÿ•ฐ DST Behavior Examples
370
+
371
+ ### Fall Back (Repeated Hour)
372
+
373
+ 07:30 happens twice:
374
+
375
+ ```
376
+ 1) 07:30 (DST)
377
+ 2) 07:30 (Standard)
378
+ ```
379
+
380
+ User chooses:
381
+
382
+ * `dstPolicy: 'twice'` โ†’ run both
383
+ * `dstPolicy: 'once'` โ†’ run only the first instance
384
+
385
+ ### Spring Forward (Missing Hour)
386
+
387
+ 02:30 does not exist.
388
+
389
+ * Buffered โ†’ run as soon as possible after the jump
390
+ * Unbuffered โ†’ skip silently
391
+
392
+ ---
393
+
394
+ ## ๐Ÿงช Testing
395
+
396
+ ```bash
397
+ npm test
398
+ npm run test:coverage
399
+ ```
400
+
401
+ ---
402
+
403
+ ## ๐Ÿ“ฆ Action Specification
404
+
405
+ ### Top-level action fields:
406
+
407
+ | Field | Description |
408
+ | ------------ | ------------------------------------------------- |
409
+ | `id` | Unique internal identifier (auto-generated) |
410
+ | `name` | User label (optional) |
411
+ | `cmd` | Name of registered function to execute |
412
+ | `payload` | Data passed to the command |
413
+ | `date` | Next scheduled run time (local `Date`) |
414
+ | `unBuffered` | Skip missed events (`true`) or catch up (`false`) |
415
+
416
+ ### Repeat block:
417
+
418
+ | Field | Description |
419
+ | ----------- | --------------------------------- |
420
+ | `type` | Recurrence unit |
421
+ | `interval` | Nth occurrence |
422
+ | `limit` | Number of times to run, or `null` |
423
+ | `endDate` | Max date, or `null` |
424
+ | `count` | Execution counter (internal) |
425
+ | `dstPolicy` | `'once'` or `'twice'` |
426
+
427
+ ---
428
+
429
+ ## ๐ŸŽฏ Project Goals (v3)
430
+
431
+ * Deterministic behavior
432
+ * Rock-solid DST handling
433
+ * Predictable local-time recurrence
434
+ * Resilience to offline and delays
435
+ * Developer-friendly ergonomics
436
+ * Suitable for small devices
437
+ * Approachable but powerful API
438
+ * Long-term maintainability
439
+
440
+ ---
441
+
442
+ ## ๐Ÿ“ License
443
+
444
+ MIT
445
+
446
+ ---
447
+
448
+ ## โค๏ธ Acknowledgments
449
+
450
+ jw-automator v3 is a ground-up rethinking of the original jw-automator library, preserving the spirit while strengthening the foundations.
451
+
452
+ If you're building automation logic and want predictable, human-friendly scheduling that survives the real world โ€” **welcome.**