jw-automator 2.0.0 → 3.1.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 +375 -192
- package/docs/ARCHITECTURE.md +342 -0
- package/docs/MIGRATION.md +411 -0
- package/docs/QUICKSTART.md +356 -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 +476 -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,342 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
## jw-automator v3 Architecture
|
|
4
|
+
|
|
5
|
+
This document describes the internal architecture of jw-automator v3.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Design Principles
|
|
10
|
+
|
|
11
|
+
1. **Correctness over Precision**: Reliable 1-second scheduling beats unreliable sub-second timing
|
|
12
|
+
2. **Local Time First**: Human-centric time semantics, not UTC-based
|
|
13
|
+
3. **Deterministic Core**: Pure step function enables testing and simulation
|
|
14
|
+
4. **Resilience**: Survive offline gaps, DST transitions, and event loop stalls
|
|
15
|
+
5. **Separation of Concerns**: Spec vs. state, core vs. host, storage abstraction
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Component Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─────────────────────────────────────────┐
|
|
23
|
+
│ Automator (API) │
|
|
24
|
+
│ - User-facing API │
|
|
25
|
+
│ - Event management │
|
|
26
|
+
│ - Action CRUD operations │
|
|
27
|
+
│ - Persistence coordination │
|
|
28
|
+
└──────────────┬──────────────────────────┘
|
|
29
|
+
│
|
|
30
|
+
┌──────┴──────┐
|
|
31
|
+
│ │
|
|
32
|
+
┌───────▼──────┐ ┌──▼──────────────┐
|
|
33
|
+
│ SchedulerHost│ │ Storage Adapter │
|
|
34
|
+
│ │ │ │
|
|
35
|
+
│ - 1-sec tick │ │ - load() │
|
|
36
|
+
│ - Event emit │ │ - save() │
|
|
37
|
+
│ - Functions │ └─────────────────┘
|
|
38
|
+
└───────┬──────┘
|
|
39
|
+
│
|
|
40
|
+
┌───────▼──────────────────────────┐
|
|
41
|
+
│ CoreEngine │
|
|
42
|
+
│ │
|
|
43
|
+
│ step(state, lastTick, now) │
|
|
44
|
+
│ → { newState, events } │
|
|
45
|
+
│ │
|
|
46
|
+
│ - Deterministic │
|
|
47
|
+
│ - Pure function │
|
|
48
|
+
│ - Testable │
|
|
49
|
+
└──────────┬───────────────────────┘
|
|
50
|
+
│
|
|
51
|
+
┌──────▼────────┐
|
|
52
|
+
│RecurrenceEngine│
|
|
53
|
+
│ │
|
|
54
|
+
│ - Next occurrence calc │
|
|
55
|
+
│ - DST handling │
|
|
56
|
+
│ - Local-time logic │
|
|
57
|
+
└────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Core Components
|
|
63
|
+
|
|
64
|
+
### 1. CoreEngine (`src/core/CoreEngine.js`)
|
|
65
|
+
|
|
66
|
+
**Purpose**: Pure scheduling logic
|
|
67
|
+
|
|
68
|
+
**Responsibilities**:
|
|
69
|
+
- Process scheduling steps
|
|
70
|
+
- Manage catch-up logic
|
|
71
|
+
- Emit action events
|
|
72
|
+
- Enforce safety invariants (max iterations, monotonic time)
|
|
73
|
+
|
|
74
|
+
**Key Method**: `step(state, lastTick, now)`
|
|
75
|
+
- Input: Current state, previous tick time, current time
|
|
76
|
+
- Output: New state and array of events
|
|
77
|
+
- Guarantees: Pure, deterministic, no side effects
|
|
78
|
+
|
|
79
|
+
**Key Method**: `simulate(state, startDate, endDate)`
|
|
80
|
+
- Simulates scheduling without mutating state
|
|
81
|
+
- Used for preview/introspection
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 2. RecurrenceEngine (`src/core/RecurrenceEngine.js`)
|
|
86
|
+
|
|
87
|
+
**Purpose**: Calculate next occurrence times
|
|
88
|
+
|
|
89
|
+
**Responsibilities**:
|
|
90
|
+
- Implement recurrence types (second, minute, hour, day, weekday, weekend, week, month, year)
|
|
91
|
+
- Handle DST transitions
|
|
92
|
+
- Enforce monotonic time progression
|
|
93
|
+
- Check stop conditions (limit, endDate)
|
|
94
|
+
|
|
95
|
+
**Key Method**: `getNextOccurrence(currentTime, repeat, dstPolicy)`
|
|
96
|
+
- Input: Current scheduled time, repeat config, DST policy
|
|
97
|
+
- Output: Next scheduled time (always > current in UTC milliseconds)
|
|
98
|
+
- Guarantees: Monotonic progression, DST-aware
|
|
99
|
+
|
|
100
|
+
**Critical Invariant**: `nextTime.getTime() > currentTime.getTime()`
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### 3. SchedulerHost (`src/host/SchedulerHost.js`)
|
|
105
|
+
|
|
106
|
+
**Purpose**: Real-time scheduling host
|
|
107
|
+
|
|
108
|
+
**Responsibilities**:
|
|
109
|
+
- Manage 1-second aligned ticking
|
|
110
|
+
- Drive CoreEngine with wall-clock time
|
|
111
|
+
- Execute command functions
|
|
112
|
+
- Emit events to listeners
|
|
113
|
+
|
|
114
|
+
**Tick Alignment**:
|
|
115
|
+
```javascript
|
|
116
|
+
const wait = 1000 - now.getMilliseconds();
|
|
117
|
+
setTimeout(() => tick(), wait);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Why?**
|
|
121
|
+
- Prevents cumulative drift
|
|
122
|
+
- Ensures all actions check against stable boundaries
|
|
123
|
+
- Enables deterministic simulation
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### 4. Storage Adapters
|
|
128
|
+
|
|
129
|
+
**Purpose**: Pluggable persistence
|
|
130
|
+
|
|
131
|
+
**Interface**:
|
|
132
|
+
```javascript
|
|
133
|
+
{
|
|
134
|
+
load: () => state,
|
|
135
|
+
save: (state) => void
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Built-in Adapters**:
|
|
140
|
+
- `FileStorage`: JSON file persistence
|
|
141
|
+
- `MemoryStorage`: In-memory (no persistence)
|
|
142
|
+
|
|
143
|
+
**Custom Adapters**: Users can provide their own (database, cloud, etc.)
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### 5. Automator (`src/Automator.js`)
|
|
148
|
+
|
|
149
|
+
**Purpose**: Main API class
|
|
150
|
+
|
|
151
|
+
**Responsibilities**:
|
|
152
|
+
- Coordinate all components
|
|
153
|
+
- Provide user-facing API
|
|
154
|
+
- Manage action lifecycle
|
|
155
|
+
- Handle auto-save
|
|
156
|
+
- Event emission
|
|
157
|
+
|
|
158
|
+
**Key APIs**:
|
|
159
|
+
- Action management: `addAction`, `updateActionByID`, `updateActionByName`, `removeActionByID`, etc.
|
|
160
|
+
- Function registration: `addFunction`, `removeFunction`
|
|
161
|
+
- Introspection: `getActions`, `describeAction`, `getActionsInRange`
|
|
162
|
+
- Lifecycle: `start`, `stop`
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Data Flow
|
|
167
|
+
|
|
168
|
+
### Adding an Action
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
User calls addAction()
|
|
172
|
+
↓
|
|
173
|
+
Automator validates spec
|
|
174
|
+
↓
|
|
175
|
+
Creates action with ID and state
|
|
176
|
+
↓
|
|
177
|
+
Adds to SchedulerHost state
|
|
178
|
+
↓
|
|
179
|
+
Emits 'update' event
|
|
180
|
+
↓
|
|
181
|
+
Triggers auto-save (if enabled)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Tick Execution
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
SchedulerHost timer fires
|
|
188
|
+
↓
|
|
189
|
+
Calls CoreEngine.step(state, lastTick, now)
|
|
190
|
+
↓
|
|
191
|
+
CoreEngine processes each action:
|
|
192
|
+
- Is nextRun <= now?
|
|
193
|
+
- Buffered/unbuffered logic
|
|
194
|
+
- Execute or skip
|
|
195
|
+
- Advance recurrence (RecurrenceEngine)
|
|
196
|
+
- Check stop conditions
|
|
197
|
+
↓
|
|
198
|
+
Returns { newState, events }
|
|
199
|
+
↓
|
|
200
|
+
SchedulerHost updates state
|
|
201
|
+
↓
|
|
202
|
+
SchedulerHost executes action events:
|
|
203
|
+
- Call registered function
|
|
204
|
+
- Emit 'action' event
|
|
205
|
+
↓
|
|
206
|
+
Schedule next tick
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Simulation
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
User calls getActionsInRange(start, end)
|
|
213
|
+
↓
|
|
214
|
+
CoreEngine.simulate() clones state
|
|
215
|
+
↓
|
|
216
|
+
Steps through time second-by-second
|
|
217
|
+
↓
|
|
218
|
+
Collects all action events
|
|
219
|
+
↓
|
|
220
|
+
Returns event list (state unchanged)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Key Design Decisions
|
|
226
|
+
|
|
227
|
+
### Why 1-Second Granularity?
|
|
228
|
+
|
|
229
|
+
- **Reliability**: Event loops can stall, file I/O can block
|
|
230
|
+
- **Stability**: No cumulative drift
|
|
231
|
+
- **Simplicity**: Easier to reason about
|
|
232
|
+
- **Target Environment**: Small devices, Raspberry Pi, etc.
|
|
233
|
+
|
|
234
|
+
### Why Local Time?
|
|
235
|
+
|
|
236
|
+
- **Human Semantics**: "7 AM" means local 7 AM
|
|
237
|
+
- **Weekday Logic**: Monday means local Monday
|
|
238
|
+
- **DST Handling**: Only makes sense in local context
|
|
239
|
+
|
|
240
|
+
### Why Step Function?
|
|
241
|
+
|
|
242
|
+
- **Deterministic**: Same inputs → same outputs
|
|
243
|
+
- **Testable**: No time dependency
|
|
244
|
+
- **Simulatable**: Preview future schedules
|
|
245
|
+
- **Catch-up**: Process offline gaps identically to real-time
|
|
246
|
+
|
|
247
|
+
### Why Buffered/UnBuffered?
|
|
248
|
+
|
|
249
|
+
- **Buffered**: "I want this to happen even if delayed" (e.g., turn heater off)
|
|
250
|
+
- **UnBuffered**: "Only if on-time" (e.g., animations, rate limiting)
|
|
251
|
+
- Both advance the recurrence chain correctly
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Safety Mechanisms
|
|
256
|
+
|
|
257
|
+
### 1. Infinite Loop Prevention
|
|
258
|
+
|
|
259
|
+
- Maximum iteration limit per tick (default 10,000)
|
|
260
|
+
- Error event emitted if exceeded
|
|
261
|
+
- Monotonic time guarantee: `next.getTime() > current.getTime()`
|
|
262
|
+
- Forward correction if violation detected
|
|
263
|
+
|
|
264
|
+
### 2. DST Handling
|
|
265
|
+
|
|
266
|
+
**Spring Forward** (missing hour):
|
|
267
|
+
- Naturally handled by Date arithmetic
|
|
268
|
+
- Buffered: execute once after jump
|
|
269
|
+
- UnBuffered: skip silently
|
|
270
|
+
|
|
271
|
+
**Fall Back** (repeated hour):
|
|
272
|
+
- `dstPolicy: 'once'`: skip second occurrence
|
|
273
|
+
- `dstPolicy: 'twice'`: run both
|
|
274
|
+
|
|
275
|
+
### 3. State Integrity
|
|
276
|
+
|
|
277
|
+
- Actions cloned before mutation
|
|
278
|
+
- Spec vs. state separation
|
|
279
|
+
- Deep copies returned from getters
|
|
280
|
+
- Auto-save with configurable intervals
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Performance Characteristics
|
|
285
|
+
|
|
286
|
+
- **Per-Tick Complexity**: O(n) where n = number of actions
|
|
287
|
+
- **Recurrence Calculation**: O(1) per step
|
|
288
|
+
- **Memory**: O(n) for action storage
|
|
289
|
+
- **Disk I/O**: Configurable auto-save interval (default 5s)
|
|
290
|
+
|
|
291
|
+
**Scalability**: Designed for 10-1000 actions, not 100,000s
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Extension Points
|
|
296
|
+
|
|
297
|
+
### Custom Storage
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
new Automator({
|
|
301
|
+
storage: {
|
|
302
|
+
load: () => loadFromDatabase(),
|
|
303
|
+
save: (state) => saveToDatabase(state)
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Custom Recurrence Types
|
|
309
|
+
|
|
310
|
+
Extend `RecurrenceEngine` with new types (requires code modification)
|
|
311
|
+
|
|
312
|
+
### Meta-Actions
|
|
313
|
+
|
|
314
|
+
Actions can call `automator.addAction()` to create dynamic schedules
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Testing Strategy
|
|
319
|
+
|
|
320
|
+
1. **Unit Tests**: RecurrenceEngine, CoreEngine isolated
|
|
321
|
+
2. **Integration Tests**: Automator API end-to-end
|
|
322
|
+
3. **Deterministic Tests**: Use fixed dates, no real time passing
|
|
323
|
+
4. **Simulation Tests**: Verify step and simulate produce same results
|
|
324
|
+
5. **DST Tests**: Specific scenarios for spring/fall transitions
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Future Enhancements
|
|
329
|
+
|
|
330
|
+
Potential improvements for future versions:
|
|
331
|
+
|
|
332
|
+
- TypeScript definitions
|
|
333
|
+
- Timezone support (explicit tz parameter)
|
|
334
|
+
- Cron expression compatibility layer
|
|
335
|
+
- Web-based dashboard
|
|
336
|
+
- Action priorities
|
|
337
|
+
- Conditional execution (predicates)
|
|
338
|
+
- Action dependencies (chains)
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
For implementation details, see the inline code documentation.
|