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.
@@ -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.