sia-reactor 0.0.21 → 0.0.22
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/README.md +115 -89
- package/dist/{TimeTravelOverlay-CJv-S_Km.d.cts → TimeTravelOverlay-DiXUgbUU.d.cts} +9 -8
- package/dist/{TimeTravelOverlay-DxqJL0Zk.d.ts → TimeTravelOverlay-eWjAy0yr.d.ts} +9 -8
- package/dist/adapters/react.cjs +66 -84
- package/dist/adapters/react.d.cts +23 -22
- package/dist/adapters/react.d.ts +23 -22
- package/dist/adapters/react.js +3 -3
- package/dist/adapters/vanilla.cjs +66 -84
- package/dist/adapters/vanilla.d.cts +4 -4
- package/dist/adapters/vanilla.d.ts +4 -4
- package/dist/adapters/vanilla.js +3 -3
- package/dist/{chunk-2WBPGSRL.js → chunk-3SKLWTEA.js} +52 -70
- package/dist/{chunk-DP74DVRT.js → chunk-BTA6MIQ6.js} +40 -8
- package/dist/{chunk-TFLYCXK4.js → chunk-CS3FOV6J.js} +14 -13
- package/dist/{index-Oie9hhE8.d.cts → index-BgbbNXTW.d.cts} +306 -207
- package/dist/{index-Oie9hhE8.d.ts → index-BgbbNXTW.d.ts} +306 -207
- package/dist/index.cjs +54 -75
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -4
- package/dist/{plugins.cjs → modules.cjs} +437 -166
- package/dist/modules.d.cts +53 -0
- package/dist/modules.d.ts +53 -0
- package/dist/modules.js +627 -0
- package/dist/super.d.ts +610 -281
- package/dist/super.global.js +445 -174
- package/dist/timeTravel-CraHdbXZ.d.cts +352 -0
- package/dist/timeTravel-YUxRHRgh.d.ts +352 -0
- package/dist/utils.cjs +41 -7
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -1
- package/package.json +6 -6
- package/dist/plugins.d.cts +0 -112
- package/dist/plugins.d.ts +0 -112
- package/dist/plugins.js +0 -370
- package/dist/timeTravel-B1vedDQc.d.ts +0 -76
- package/dist/timeTravel-WpgWmKu-.d.cts +0 -76
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
- [sia-reactor](#sia-reactor)
|
|
18
18
|
- [Table of contents](#table-of-contents)
|
|
19
|
-
- [
|
|
19
|
+
- [Why sia-reactor?](#why-sia-reactor)
|
|
20
20
|
- [Getting Started](#getting-started)
|
|
21
21
|
- [Usage](#usage)
|
|
22
22
|
- [API Reference](#api-reference)
|
|
@@ -30,41 +30,45 @@
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Why sia-reactor?
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Most state libraries react to changes.
|
|
36
36
|
|
|
37
|
-
-
|
|
38
|
-
Read [Chronicles](https://github.com/Tobi007-del/tmg-media-player/blob/main/CHRONICLES.md) and [Interaction Folklore](https://github.com/Tobi007-del/tmg-media-player/blob/main/FOLKLORE.md), then continue here.
|
|
39
|
-
- **I just need to use this fast**:
|
|
40
|
-
Jump directly to [Getting Started](#getting-started) and [API Reference](#api-reference).
|
|
37
|
+
`sia-reactor` lets you:
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
- intercept changes BEFORE they happen
|
|
40
|
+
- approve or reject user intent
|
|
41
|
+
- observe changes AFTER they settle
|
|
42
|
+
- treat your state like a programmable event system
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **Watchers** (`watch`) = synchronous post-mutation observers.
|
|
50
|
-
- **Listeners** (`on`) = microtask-batched event observers.
|
|
51
|
-
|
|
52
|
-
Semantic split recommendation:
|
|
53
|
-
- `intent`: async/delayed requests.
|
|
54
|
-
- `state`: granted facts.
|
|
55
|
-
- `settings/config (custom)`: immediate user prefs.
|
|
56
|
-
- `status (custom)`: read-only system facts.
|
|
44
|
+
```js
|
|
45
|
+
const player = reactive({
|
|
46
|
+
intent: intent({ playing: false }),
|
|
47
|
+
state: { playing: false }
|
|
48
|
+
});
|
|
57
49
|
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
// Logic layer (capture phase)
|
|
51
|
+
player.on("intent.playing", (e) => {
|
|
52
|
+
if (!ready) return e.reject();
|
|
53
|
+
player.state.playing = true;
|
|
54
|
+
}, { capture: true });
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
settings: { theme: "dark", defaultPlaybackRate: 1 },
|
|
65
|
-
status: { buffering: false, duration: 120 }
|
|
56
|
+
// UI layer
|
|
57
|
+
player.on("state.playing", (e) => {
|
|
58
|
+
console.log("Now playing:", e.value);
|
|
66
59
|
});
|
|
60
|
+
|
|
61
|
+
// User action
|
|
62
|
+
player.intent.playing = true;
|
|
67
63
|
```
|
|
64
|
+
***"This is the entire system."***
|
|
65
|
+
|
|
66
|
+
Choose your reading mode:
|
|
67
|
+
|
|
68
|
+
- **I want to understand the architecture shift first**:
|
|
69
|
+
Read [Chronicles](https://github.com/Tobi007-del/tmg-media-player/blob/main/CHRONICLES.md) and [Interaction Folklore](https://github.com/Tobi007-del/tmg-media-player/blob/main/FOLKLORE.md), then continue here.
|
|
70
|
+
- **I just need to use this fast**:
|
|
71
|
+
Jump directly to [Getting Started](#getting-started) and [API Reference](#api-reference).
|
|
68
72
|
|
|
69
73
|
---
|
|
70
74
|
|
|
@@ -84,10 +88,10 @@ pnpm add sia-reactor
|
|
|
84
88
|
|
|
85
89
|
```javascript
|
|
86
90
|
// 1. Core Engine
|
|
87
|
-
import { reactive, Reactor, TERMINATOR } from
|
|
91
|
+
import { reactive, Reactor, TERMINATOR } from "sia-reactor";
|
|
88
92
|
|
|
89
93
|
// 2. Deep Object Utilities
|
|
90
|
-
import { setAny, getAny, mergeObjs } from
|
|
94
|
+
import { setAny, getAny, mergeObjs } from "sia-reactor/utils";
|
|
91
95
|
```
|
|
92
96
|
|
|
93
97
|
---
|
|
@@ -97,12 +101,12 @@ import { setAny, getAny, mergeObjs } from 'sia-reactor/utils';
|
|
|
97
101
|
### Modern Bundlers (ESM)
|
|
98
102
|
|
|
99
103
|
```javascript
|
|
100
|
-
import { reactive, Reactor } from
|
|
101
|
-
import
|
|
102
|
-
import
|
|
103
|
-
import
|
|
104
|
-
import
|
|
105
|
-
import
|
|
104
|
+
import { reactive, Reactor } from "sia-reactor";
|
|
105
|
+
import "sia-reactor/utils"; // deep object helpers (setAny/getAny/deleteAny/inAny/parseAnyObj/fanout/mergeObjs/deepClone/nuke...)
|
|
106
|
+
import "sia-reactor/modules"; // built-in modules + storage adapters
|
|
107
|
+
import "sia-reactor/adapters/vanilla"; // Autotracker + effect API + TimeTravelOverlay class
|
|
108
|
+
import "sia-reactor/adapters/react"; // useReactor/useSelector/usePath hooks
|
|
109
|
+
import "sia-reactor/styles/time-travel-overlay.css"; // TimeTravelOverlay CSS
|
|
106
110
|
```
|
|
107
111
|
|
|
108
112
|
### CDN / Browser (Global)
|
|
@@ -115,7 +119,7 @@ import 'sia-reactor/adapters/react'; // useReactor/useSelector/usePath hooks
|
|
|
115
119
|
<script>
|
|
116
120
|
const { reactive, Reactor } = window.sia;
|
|
117
121
|
window.sia.utils;
|
|
118
|
-
window.sia.
|
|
122
|
+
window.sia.modules;
|
|
119
123
|
window.sia.adapters.vanilla;
|
|
120
124
|
</script>
|
|
121
125
|
</body>
|
|
@@ -157,10 +161,10 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
|
|
|
157
161
|
- **`delete(path, callback, options)`**: Intercept property deletion.
|
|
158
162
|
|
|
159
163
|
#### **Watchers (Synchronous Observers)**
|
|
160
|
-
- **`watch(path, callback, options)`**: Fires instantly after a mutation. Use strictly for critical internal engine syncing.
|
|
164
|
+
- **`watch(path, callback, options)`**: Fires instantly after a mutation. Use strictly for critical internal engine syncing on leaf paths preferably, sees only direct operations.
|
|
161
165
|
|
|
162
166
|
#### **Listeners (Asynchronous/Batched UI Observers)**
|
|
163
|
-
- **`on(path, callback, options)`**: Attach DOM-style event listeners
|
|
167
|
+
- **`on(path, callback, options)`**: Attach DOM-style event listeners that respect `depth`. Supports `{ capture: true, depth: 1, once: true, immediate: true }`.
|
|
164
168
|
- **`once(path, callback, options)`**: Fires once and self-destructs.
|
|
165
169
|
- **`off(path, callback, options)`**: Removes a listener.
|
|
166
170
|
|
|
@@ -168,8 +172,7 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
|
|
|
168
172
|
- **`tick(path)`**: Forces a synchronous flush of the batch queue for a specific path.
|
|
169
173
|
- **`stall(task)` / `nostall(task)`**: Manually stall the queue to wait for calculations before rendering.
|
|
170
174
|
- **`snapshot(raw)`**: Generates a strict, structurally-shared, un-proxied clone of the current state tree.
|
|
171
|
-
- **`
|
|
172
|
-
- **`plugIn(new ReactorPlugin(config))`**: Allows extended behaviour with external logic.
|
|
175
|
+
- **`use(new ReactorModule(config), id)`**: Allows extended behaviour with external logic.
|
|
173
176
|
- **`reset()`**: Clears all records bringing everything back to a clean slate.
|
|
174
177
|
- **`destroy()`**: Last resort destruction, nukes everything by nullifying it's properties for full disposal, lives on every class.
|
|
175
178
|
|
|
@@ -182,7 +185,7 @@ You can wrap properties in special flags *before* initializing the reactor to di
|
|
|
182
185
|
- **`volatile(obj)` / `stable(obj)`**: Forces the reactor to fire event waves even if the new value is identical to the old value (bypassing the Proxy's unchanged performance check).
|
|
183
186
|
|
|
184
187
|
```javascript
|
|
185
|
-
import { reactive, intent, volatile, inert } from
|
|
188
|
+
import { reactive, intent, volatile, inert } from "sia-reactor";
|
|
186
189
|
|
|
187
190
|
const data = reactive({
|
|
188
191
|
apiResponse: inert({ heavy: "data" }), // Proxy won't traverse this
|
|
@@ -193,18 +196,18 @@ const data = reactive({
|
|
|
193
196
|
|
|
194
197
|
### React Hooks & Effects
|
|
195
198
|
|
|
196
|
-
The engine provides native React bindings utilizing `useSyncExternalStore` and an internal `Autotracker` for concurrent-safe, surgically precise component re-renders. All hooks natively accept a `Reactor` instance, a `reactive()` proxy, or a plain object (which will be auto-wrapped on the fly).
|
|
199
|
+
The engine provides native React bindings utilizing `useSyncExternalStore` and an internal `Autotracker` for concurrent-safe, surgically precise component re-renders. All hooks natively accept a `Reactor` instance, a `reactive()` proxy, or a plain object (which will be auto-wrapped on the fly). Just import, your editor will reveal more details.
|
|
197
200
|
|
|
198
201
|
```javascript
|
|
199
|
-
import { reactive } from
|
|
200
|
-
import { useReactor, useAnyReactor, useSelector, useAnySelector, usePath, effect } from
|
|
202
|
+
import { reactive } from "sia-reactor";
|
|
203
|
+
import { useReactor, useAnyReactor, useSelector, useAnySelector, usePath, effect } from "sia-reactor/adapters/react";
|
|
201
204
|
|
|
202
|
-
const state = reactive({ user: { name: "
|
|
205
|
+
const state = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
|
|
203
206
|
|
|
204
207
|
// 1. The Tracked State (Valtio-style)
|
|
205
208
|
function Profile() {
|
|
206
209
|
const sameState = useReactor(state); // `useReactorSnapshot()` if mutable issues arise
|
|
207
|
-
useAnyReactor(); //
|
|
210
|
+
useAnyReactor(); // when you just want state from any reactor
|
|
208
211
|
// Only re-renders if state.user.name mutates. Completely ignores age and theme!
|
|
209
212
|
return <div>{sameState.user.name + otherState.user.name}</div>;
|
|
210
213
|
} // no snapshots like Valtio, you can read or write to anything
|
|
@@ -212,7 +215,7 @@ function Profile() {
|
|
|
212
215
|
// 2. The Slice Selector (Zustand-style)
|
|
213
216
|
function Theme() {
|
|
214
217
|
const theme = useSelector(state, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
|
|
215
|
-
const newName = useAnySelector(() => state.user.name + spouseState.user.name); //
|
|
218
|
+
const newName = useAnySelector(() => state.user.name + spouseState.user.name); // when you just want to derive any state from any reactor
|
|
216
219
|
return <div>Theme: {theme}</div>;
|
|
217
220
|
}
|
|
218
221
|
|
|
@@ -226,51 +229,56 @@ function AgeObserver() {
|
|
|
226
229
|
const stopTracking = effect(() => console.log("User name changed to:", state.user.name)); // read or write as you wish
|
|
227
230
|
```
|
|
228
231
|
|
|
229
|
-
###
|
|
232
|
+
### Modules: The Extension Port
|
|
230
233
|
|
|
231
|
-
The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via
|
|
234
|
+
The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Modules. It ships with a suite of powerful modules for common architectural needs.
|
|
232
235
|
|
|
233
|
-
#### The Persistence
|
|
234
|
-
Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB.
|
|
236
|
+
#### The Persistence Module
|
|
237
|
+
Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB. Always use this module first to avoid re-initialization issues.
|
|
235
238
|
|
|
236
239
|
```javascript
|
|
237
|
-
import { reactive, Reactor, getReactor } from
|
|
238
|
-
import {
|
|
240
|
+
import { reactive, Reactor, getReactor } from "sia-reactor";
|
|
241
|
+
import { PersistModule, LocalStorageAdapter, IndexedDBAdapter, SessionStorageAdapter, CookieAdapter, MemoryAdapter } from "sia-reactor/modules";
|
|
239
242
|
|
|
240
243
|
const state = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
|
|
241
|
-
|
|
244
|
+
const persist = new PersistModule({ // Plug it in. State is now automatically hydrated and throttled-saved.
|
|
242
245
|
key: "APP_PREFS",
|
|
243
246
|
paths: ["theme", "settings.brightness"],
|
|
244
247
|
throttle: 5000,
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
fanout: true, // async hydration should use leaf writes incase UI listeners already initialized.
|
|
249
|
+
adapter: new IndexedDBAdapter({ dbName: "Session", version: 1, onversionchange: () => location.reload(), useSnapshot: true }) // or `LocalStorageAdapter` (instance or signature)
|
|
250
|
+
};
|
|
251
|
+
state.use(persist, getReactor(state))); // Put `Reactor` as second constructor arg if you want type inference, e.g. for the paths in the array.
|
|
247
252
|
```
|
|
248
253
|
|
|
249
|
-
#### The Time Travel
|
|
254
|
+
#### The Time Travel Module
|
|
250
255
|
Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay.
|
|
251
256
|
|
|
252
257
|
```javascript
|
|
253
|
-
import {
|
|
254
|
-
import {
|
|
255
|
-
import
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
import { TimeTravelModule } from "sia-reactor/modules";
|
|
259
|
+
import { effect, TimeTravelOverlay } from "sia-reactor/adapters/vanilla";
|
|
260
|
+
import "sia-reactor/css/time-travel-overlay.css";
|
|
261
|
+
|
|
262
|
+
const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150 });
|
|
263
|
+
state.use(time);
|
|
264
|
+
|
|
265
|
+
// If persist uses an async adapter (e.g. IndexedDB), wait till after hydration:
|
|
266
|
+
persist.state.once("hydrated", () => state.use(time)); // starts `false`, one-time stall until it flips
|
|
267
|
+
effect(() => persist.state.hydrated && state.use(time), { once: true }) // same logic, different look :)
|
|
268
|
+
|
|
261
269
|
const overlay = new TimeTravelOverlay(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visulazation
|
|
262
270
|
```
|
|
263
271
|
```jsx
|
|
264
|
-
import { TimeTravelOverlay } from
|
|
272
|
+
import { TimeTravelOverlay } from "sia-reactor/adapters/react";
|
|
265
273
|
|
|
266
274
|
<TimeTravelOverlay time={time} color="#e26e02" startOpen devOnly /> // react-safe instance lifecycle management, e.g. for HMR predictability.
|
|
267
275
|
```
|
|
268
276
|
|
|
269
|
-
Useful
|
|
277
|
+
Useful methods: `play()`, `pause()`, `rewind()`, `clear()`, `undo()`, `redo()`, `step(n, forward)`, `jumpTo(frame)`, `export(replacer)`, `import(json, reviver)`.
|
|
270
278
|
|
|
271
279
|
### Reactor Build Options
|
|
272
280
|
|
|
273
|
-
These are some core build options accepted by `new Reactor(core, build)` and `reactive(core, build, preferences)`.
|
|
281
|
+
These are some core build options accepted by `new Reactor(core, build)` and `reactive(core, build, preferences)` configurable via `Reactor.config`.
|
|
274
282
|
|
|
275
283
|
- **`debug?`**: 1-time set. Enables debug logging and diagnostics of core operations. (default: `false`)
|
|
276
284
|
- **`crossRealms?`**: Enables cross-realm object detection support by using slower but safer type checks. (e.g. iframes) (default: `false`).
|
|
@@ -281,8 +289,6 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
|
|
|
281
289
|
- **`equalityFunction?`**: Custom equality used by setters and adapter comparisons (default: `Object.is`).
|
|
282
290
|
- **`batchingFunction?`**: Custom batching scheduler for listener notification flushes (default: `queueMicrotask`)
|
|
283
291
|
- **`referenceTracking?`**: Enables identity/reference tracking features in the runtime. (default: `false`).
|
|
284
|
-
|
|
285
|
-
*NOTE: those not marked as 1-time set are configurable via `Reactor.config`. Also, plugs and hooks turn on whatever they need automatically.*
|
|
286
292
|
|
|
287
293
|
### Reactive Preferences (Method Naming)
|
|
288
294
|
|
|
@@ -293,7 +299,7 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
|
|
|
293
299
|
- **`whitelist?`**: Keeps specific methods on their original names while others get affixed.
|
|
294
300
|
|
|
295
301
|
```javascript
|
|
296
|
-
import { reactive } from
|
|
302
|
+
import { reactive } from "sia-reactor";
|
|
297
303
|
|
|
298
304
|
const state = reactive(
|
|
299
305
|
{ count: 0 },
|
|
@@ -339,9 +345,10 @@ player.on("intent.playing", (e) => {
|
|
|
339
345
|
|
|
340
346
|
### Troubleshooting
|
|
341
347
|
|
|
342
|
-
- Listener timing feels late: `on` is microtask-batched by design; use `watch` only for strict immediate engine sync.
|
|
343
|
-
-
|
|
344
|
-
-
|
|
348
|
+
- Listener timing feels late: `on(path, ...)` is microtask-batched by design; use `watch(path, ...)` only for strict immediate engine sync on leaf paths preferably.
|
|
349
|
+
- Listeners don't react to changes: use `fanout(state, path, object, { depth: n })` instead of direct object sets to keep immutable semantics.
|
|
350
|
+
- `reject()` appears ignored: call it in capture phase and ensure branch is wrapped in `intent(...)`, also remember it's the listener's choice to comply.
|
|
351
|
+
- Snapshot behavior feels stale: enable `referenceTracking: true` with `smartCloning: true`, also use these when persisting to environments that don't take proxies, e.g. IndexedDB.
|
|
345
352
|
- Cross-frame data is skipped: enable `crossRealms: true` for iframe/other realm objects.
|
|
346
353
|
- Class/prototype behavior is odd: enable `preserveContext: true` (tradeoff: slower hot paths).
|
|
347
354
|
- Working with symbol keys and you want blind writes/reads: unwrap first with `getRaw` or `RAW` and operate on the raw object.
|
|
@@ -353,9 +360,9 @@ player.on("intent.playing", (e) => {
|
|
|
353
360
|
The S.I.A. Reactor operates in two distinct dimensions: **The Synchronous Dimension** (Gatekeepers & Watchers) and **The Asynchronous Dimension** (Listeners). Because they intercept data at entirely different points in time, they receive different objects and possess different capabilities.
|
|
354
361
|
|
|
355
362
|
### 1. The Synchronous Dimension: The `Payload`
|
|
356
|
-
When you use
|
|
363
|
+
When you use `.get()`, `.set()`, `.delete()`, or `.watch()`, you are sitting *directly inside the Javascript Proxy Trap*. The memory has not been written yet (or is being written right at that exact millisecond).
|
|
357
364
|
|
|
358
|
-
Because there is no "bubbling" or "event wave" yet, these methods do not receive
|
|
365
|
+
Because there is no "bubbling" or "event wave" yet, these methods do not receive an event object. They receive a lightweight, factual `Payload`.
|
|
359
366
|
|
|
360
367
|
#### The `Payload` Anatomy
|
|
361
368
|
```javascript
|
|
@@ -388,7 +395,7 @@ The `target` and `currentTarget` objects give you absolute surgical awareness of
|
|
|
388
395
|
Because `set` and `delete` mediators execute *before* the memory is written, you have the power to alter reality or stop it entirely using the `TERMINATOR` symbol.
|
|
389
396
|
|
|
390
397
|
```javascript
|
|
391
|
-
import { TERMINATOR } from
|
|
398
|
+
import { TERMINATOR } from "sia-reactor";
|
|
392
399
|
|
|
393
400
|
// Example: Data Sanitization & Blocking
|
|
394
401
|
rtr.set("user.age", (value) => {
|
|
@@ -398,14 +405,14 @@ rtr.set("user.age", (value) => {
|
|
|
398
405
|
```
|
|
399
406
|
|
|
400
407
|
### 2. The Asynchronous Dimension: The S.I.A. Event Loop
|
|
401
|
-
When you use
|
|
408
|
+
When you use `.on()` or `.once()` (Listeners), you are sitting in the **Microtask Queue**. The memory has already been safely written, the Proxy traps have closed, and the engine is now broadcasting a DOM-Style "Mutation Wave" across the state tree.
|
|
402
409
|
|
|
403
|
-
If you mutate `state.user.profile.name = "
|
|
410
|
+
If you mutate `state.user.profile.name = "Kosi"`, the event wave travels like this:
|
|
404
411
|
1. **Capture Phase:** `*` (Root) ➔ `user` ➔ `user.profile`
|
|
405
412
|
2. **Target Phase:** `user.profile.name`
|
|
406
413
|
3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
|
|
407
414
|
|
|
408
|
-
*NOTE: Only `on` does this since it is batched
|
|
415
|
+
*NOTE: Only `on` does this since it is batched to stay within recursive limits.*
|
|
409
416
|
|
|
410
417
|
#### The Event Anatomy (`REvent` type)
|
|
411
418
|
Listeners receive a `ReactorEvent` (`REvent`). This object *inherits* everything from the `Payload`, but adds **Political Event Routing**, providing absolute surgical awareness of what is happening in the tree.
|
|
@@ -417,13 +424,13 @@ rtr.on("user.profile", (e) => {
|
|
|
417
424
|
console.log(e.staticType); // "set" (The original action)
|
|
418
425
|
console.log(e.path); // "user.profile.name" (The actual property changed)
|
|
419
426
|
console.log(e.currentTarget); // { path: "user.profile", value: {...} } (Where we are listening)
|
|
420
|
-
console.log(e.value); // "
|
|
427
|
+
console.log(e.value); // "Kosi" (The new value)
|
|
421
428
|
console.log(e.oldValue); // "John" (The previous value)
|
|
422
429
|
// 2. Political Routing
|
|
423
430
|
console.log(e.eventPhase); // 3 (Bubbling Phase)
|
|
424
431
|
console.log(e.bubbles); // true/false
|
|
425
432
|
// 3. Misc
|
|
426
|
-
console.log(e.composedPath()); // ["
|
|
433
|
+
console.log(e.composedPath()); // ["Kosi", { name: "Kosi", age: 26 }, { profile: { name: "Kosi", age: 26 } }, { user: { profile: { name: "Kosi", age: 26 } } }] (refs, target -> root)
|
|
427
434
|
}); // you could use external callbacks but typed with `REvent<T, "user.age">`
|
|
428
435
|
```
|
|
429
436
|
|
|
@@ -452,9 +459,29 @@ When you listen to a parent object (like `"user.profile"`), you will naturally c
|
|
|
452
459
|
|
|
453
460
|
To help you instantly differentiate between the object *itself* being replaced, versus a *child* property mutating deep inside of it, the Reactor intelligently morphs the `e.type`:
|
|
454
461
|
* If `state.user.profile = {}` happens, the listener receives `e.type === "set"`.
|
|
455
|
-
* If `state.user.profile.name = "
|
|
462
|
+
* If `state.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
|
|
456
463
|
|
|
457
|
-
This allows for highly fine-grained syncing bridges across your application without writing heavy
|
|
464
|
+
This allows for highly fine-grained syncing bridges across your application without writing heavy for-loop diffing algorithms! Use `{ depth: n }` to control how deep the path bubbles you see are, i.e.
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
rtr.on("todos", (e) => console.log(e), { depth : 1 }); // only sees updates on direct children
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Typing tip (for depth-aware `update` narrowing): `depth` mainly affects inferred `target.key` unions. To preserve type narrowing where desired, avoid destructuring in the callback signature. Types can be too accurate thereby causing issues, cast where necessary. e.g. `e.value as any`.
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
// Less reliable inference for depth-aware unions
|
|
474
|
+
rtr.on("todos", ({ type, target: { path, key } }) => {
|
|
475
|
+
if (type === "update") console.log(path, key);
|
|
476
|
+
}, { depth: 1 });
|
|
477
|
+
// Better: narrow first, then destructure inside
|
|
478
|
+
rtr.on("todos", (e: REvent<user, "todos", 1>) => {
|
|
479
|
+
if (e.type === "update") {
|
|
480
|
+
const { path, key } = e.target;
|
|
481
|
+
console.log(path, key); // or e.target.path, e.target.key
|
|
482
|
+
}
|
|
483
|
+
}, { depth: 1 }); // you need the generic for external callbacks only
|
|
484
|
+
```
|
|
458
485
|
|
|
459
486
|
---
|
|
460
487
|
|
|
@@ -462,7 +489,7 @@ This allows for highly fine-grained syncing bridges across your application with
|
|
|
462
489
|
|
|
463
490
|
### The CSS Black Box
|
|
464
491
|
|
|
465
|
-
Imagine you have 50 different CSS variables in your state (`settings.css.containerWidth`, `settings.css.themeColor`, etc.). Registering 50 individual `watch()` or `on()` listeners would need manual css crawling that will be blind dynamically added variables.
|
|
492
|
+
Imagine you have 50 different CSS variables in your state (`settings.css.containerWidth`, `settings.css.themeColor`, etc.). Registering 50 individual `watch()` or `on()` listeners would need manual css crawling that will be blind to dynamically added variables.
|
|
466
493
|
|
|
467
494
|
Instead, we use the **Root Wildcard** (`"*"`) for both Reading (`get`) and Writing (`watch`).
|
|
468
495
|
|
|
@@ -484,8 +511,8 @@ this.ctlr.config.get("*", (val, { target: { key, path } }) => {
|
|
|
484
511
|
});
|
|
485
512
|
```
|
|
486
513
|
#### Why this pattern is elite:
|
|
487
|
-
1. **Synchronous Execution (`watch`):** CSSOM needs immediate updates. If you used an
|
|
488
|
-
2. **The Wildcard Tradeoff:** By listening to `*`, this callback runs synchronously on *every single mutation* in the entire reactor.
|
|
514
|
+
1. **Synchronous Execution (`watch`):** CSSOM needs immediate updates. If you used an `.on()` listener, a slow browser might paint the old frame before the microtask resolves, causing UI flicker. `.watch()` executes synchronously during the proxy trap.
|
|
515
|
+
2. **The Wildcard Tradeoff:** By listening to `*`, this callback runs synchronously on *every single mutation* in the entire reactor. This is the only synchronous way to catch deep nested updates.
|
|
489
516
|
3. **The Ultimate Illusion:** A developer writes `console.log(state.settings.css.themeColor)`. To them, it looks like a standard plain object property access. In reality, the Reactor just executed a surgical DOM read. It is a true black box.
|
|
490
517
|
|
|
491
518
|
---
|
|
@@ -497,7 +524,6 @@ S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media
|
|
|
497
524
|
* **The Native JavaScript Proxy API:** Arguably the most powerful, slept-on feature in the ECMAScript specification. The Reactor is essentially a love letter to the Proxy API, packaging its raw, interception-level power into a structured and safe Data DOM so the community can finally use what it's truly capable of.
|
|
498
525
|
* **Video.js (VJS):** The philosophy of "Intent vs. State" MEDIATION, ensuring UI actions only commit when the underlying engine allows it.
|
|
499
526
|
* **The Browser DOM:** Treating a raw JSON state tree like HTML nodes, complete with deep, path-based event bubbling.
|
|
500
|
-
* **The JavaScript Event Loop:** Utilizing `queueMicrotask` to batch thousands of synchronous state mutations into a single, noiseless render tick.
|
|
501
527
|
* **Vue, MobX & Valtio:** Leveraging native ES6 Proxies for instant, deep reactivity without forcing clunky `get()` or `set()` wrapper functions.
|
|
502
528
|
|
|
503
529
|
---
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { d as Reactive } from './index-BgbbNXTW.cjs';
|
|
2
|
+
import { m as TimeTravelModule } from './timeTravel-CraHdbXZ.cjs';
|
|
3
3
|
|
|
4
4
|
/** Reactive options for the TimeTravel overlay instance. */
|
|
5
5
|
interface TimeTravelConfig {
|
|
@@ -14,8 +14,9 @@ interface TimeTravelConfig {
|
|
|
14
14
|
/** Container element that owns the overlay layer and dock. */
|
|
15
15
|
container: HTMLElement;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
17
|
+
/**
|
|
18
|
+
* - Vanilla overlay controller for visual time-travel controls and timeline I/O.
|
|
19
|
+
* - Mounts a docked HUD into the configured container, syncs its UI with module state, and forwards keyboard/button actions to the TimeTravelModule.
|
|
19
20
|
* Supports reactive `config` updates (title/color/container/devOnly) and maintains local overlay UI state (`open` and `import` payload text).
|
|
20
21
|
*/
|
|
21
22
|
declare class TimeTravelOverlay {
|
|
@@ -26,15 +27,15 @@ declare class TimeTravelOverlay {
|
|
|
26
27
|
open: boolean;
|
|
27
28
|
import: string;
|
|
28
29
|
}, undefined>;
|
|
29
|
-
readonly time:
|
|
30
|
+
readonly time: TimeTravelModule;
|
|
30
31
|
readonly els: Record<string, HTMLElement>;
|
|
31
32
|
private clups;
|
|
32
33
|
private keyup?;
|
|
33
|
-
/** Creates a docked TimeTravel overlay bound to a
|
|
34
|
-
* @param time TimeTravel
|
|
34
|
+
/** Creates a docked TimeTravel overlay bound to a module instance.
|
|
35
|
+
* @param time TimeTravel module instance that owns timeline operations.
|
|
35
36
|
* @param build Optional initial overlay config overrides.
|
|
36
37
|
*/
|
|
37
|
-
constructor(time:
|
|
38
|
+
constructor(time: TimeTravelModule, build?: Partial<TimeTravelConfig>);
|
|
38
39
|
destroy(): void;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { d as Reactive } from './index-BgbbNXTW.js';
|
|
2
|
+
import { m as TimeTravelModule } from './timeTravel-YUxRHRgh.js';
|
|
3
3
|
|
|
4
4
|
/** Reactive options for the TimeTravel overlay instance. */
|
|
5
5
|
interface TimeTravelConfig {
|
|
@@ -14,8 +14,9 @@ interface TimeTravelConfig {
|
|
|
14
14
|
/** Container element that owns the overlay layer and dock. */
|
|
15
15
|
container: HTMLElement;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
17
|
+
/**
|
|
18
|
+
* - Vanilla overlay controller for visual time-travel controls and timeline I/O.
|
|
19
|
+
* - Mounts a docked HUD into the configured container, syncs its UI with module state, and forwards keyboard/button actions to the TimeTravelModule.
|
|
19
20
|
* Supports reactive `config` updates (title/color/container/devOnly) and maintains local overlay UI state (`open` and `import` payload text).
|
|
20
21
|
*/
|
|
21
22
|
declare class TimeTravelOverlay {
|
|
@@ -26,15 +27,15 @@ declare class TimeTravelOverlay {
|
|
|
26
27
|
open: boolean;
|
|
27
28
|
import: string;
|
|
28
29
|
}, undefined>;
|
|
29
|
-
readonly time:
|
|
30
|
+
readonly time: TimeTravelModule;
|
|
30
31
|
readonly els: Record<string, HTMLElement>;
|
|
31
32
|
private clups;
|
|
32
33
|
private keyup?;
|
|
33
|
-
/** Creates a docked TimeTravel overlay bound to a
|
|
34
|
-
* @param time TimeTravel
|
|
34
|
+
/** Creates a docked TimeTravel overlay bound to a module instance.
|
|
35
|
+
* @param time TimeTravel module instance that owns timeline operations.
|
|
35
36
|
* @param build Optional initial overlay config overrides.
|
|
36
37
|
*/
|
|
37
|
-
constructor(time:
|
|
38
|
+
constructor(time: TimeTravelModule, build?: Partial<TimeTravelConfig>);
|
|
38
39
|
destroy(): void;
|
|
39
40
|
}
|
|
40
41
|
|