sia-reactor 0.0.20 → 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 +120 -96
- package/dist/{TimeTravelOverlay-BYSnHBXx.d.cts → TimeTravelOverlay-DiXUgbUU.d.cts} +9 -8
- package/dist/{TimeTravelOverlay-DoNrZwvX.d.ts → TimeTravelOverlay-eWjAy0yr.d.ts} +9 -8
- package/dist/adapters/react.cjs +140 -176
- package/dist/adapters/react.d.cts +59 -17
- package/dist/adapters/react.d.ts +59 -17
- package/dist/adapters/react.js +35 -11
- package/dist/adapters/vanilla.cjs +107 -169
- 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-VPR5SP3E.js → chunk-3SKLWTEA.js} +80 -110
- package/dist/{chunk-DP74DVRT.js → chunk-BTA6MIQ6.js} +40 -8
- package/dist/{chunk-RFQ2JJSV.js → chunk-CS3FOV6J.js} +27 -58
- package/dist/{index-DCG3sacH.d.cts → index-BgbbNXTW.d.cts} +312 -212
- package/dist/{index-DCG3sacH.d.ts → index-BgbbNXTW.d.ts} +312 -212
- package/dist/index.cjs +82 -115
- 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} +476 -210
- package/dist/modules.d.cts +53 -0
- package/dist/modules.d.ts +53 -0
- package/dist/modules.js +627 -0
- package/dist/styles/time-travel-overlay.css +1 -1
- package/dist/super.d.ts +620 -289
- package/dist/super.global.js +497 -263
- 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 -363
- package/dist/timeTravel-Bv_u5M1D.d.ts +0 -75
- package/dist/timeTravel-L8CEhHIo.d.cts +0 -75
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>
|
|
@@ -143,8 +147,8 @@ getReactor(state); state.__Reactor__; // Reference to the underlying reactor
|
|
|
143
147
|
|
|
144
148
|
Alternatively, you can instantiate the `Reactor` class directly to keep the API from interfering with your data or [try this](#reactive-preferences-method-naming):
|
|
145
149
|
```javascript
|
|
146
|
-
const reactor = new Reactor({ player: { volume: 50 } }, { debug: true
|
|
147
|
-
reactor.core.player.volume = 100;
|
|
150
|
+
const reactor = new Reactor({ player: { volume: 50 } }, { debug: true });
|
|
151
|
+
reactor.core.player.volume = 100; // re-assign core if desired
|
|
148
152
|
```
|
|
149
153
|
|
|
150
154
|
### Core Methods
|
|
@@ -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,26 +196,26 @@ 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, useSelector, 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
|
-
const sameState = useReactor(state); //
|
|
207
|
-
useAnyReactor(); //
|
|
209
|
+
const sameState = useReactor(state); // `useReactorSnapshot()` if mutable issues arise
|
|
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
|
|
211
214
|
|
|
212
215
|
// 2. The Slice Selector (Zustand-style)
|
|
213
216
|
function Theme() {
|
|
214
|
-
const theme = useSelector(state, (s) => s.theme); //
|
|
215
|
-
const newName = useAnySelector(() => state.user.name + spouseState.user.name); //
|
|
217
|
+
const theme = useSelector(state, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
|
|
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
|
|
|
@@ -223,56 +226,59 @@ function AgeObserver() {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
// 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
|
|
226
|
-
const stopTracking = effect(() =>
|
|
227
|
-
console.log("User name changed to:", state.user.name);
|
|
228
|
-
});
|
|
229
|
+
const stopTracking = effect(() => console.log("User name changed to:", state.user.name)); // read or write as you wish
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
###
|
|
232
|
+
### Modules: The Extension Port
|
|
232
233
|
|
|
233
|
-
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.
|
|
234
235
|
|
|
235
|
-
#### The Persistence
|
|
236
|
-
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.
|
|
237
238
|
|
|
238
239
|
```javascript
|
|
239
|
-
import { reactive, Reactor, getReactor } from
|
|
240
|
-
import {
|
|
240
|
+
import { reactive, Reactor, getReactor } from "sia-reactor";
|
|
241
|
+
import { PersistModule, LocalStorageAdapter, IndexedDBAdapter, SessionStorageAdapter, CookieAdapter, MemoryAdapter } from "sia-reactor/modules";
|
|
241
242
|
|
|
242
243
|
const state = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
|
|
243
|
-
|
|
244
|
+
const persist = new PersistModule({ // Plug it in. State is now automatically hydrated and throttled-saved.
|
|
244
245
|
key: "APP_PREFS",
|
|
245
246
|
paths: ["theme", "settings.brightness"],
|
|
246
247
|
throttle: 5000,
|
|
247
|
-
|
|
248
|
-
|
|
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.
|
|
249
252
|
```
|
|
250
253
|
|
|
251
|
-
#### The Time Travel
|
|
254
|
+
#### The Time Travel Module
|
|
252
255
|
Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay.
|
|
253
256
|
|
|
254
257
|
```javascript
|
|
255
|
-
import {
|
|
256
|
-
import {
|
|
257
|
-
import
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
|
|
263
269
|
const overlay = new TimeTravelOverlay(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visulazation
|
|
264
270
|
```
|
|
265
271
|
```jsx
|
|
266
|
-
import { TimeTravelOverlay } from
|
|
272
|
+
import { TimeTravelOverlay } from "sia-reactor/adapters/react";
|
|
267
273
|
|
|
268
274
|
<TimeTravelOverlay time={time} color="#e26e02" startOpen devOnly /> // react-safe instance lifecycle management, e.g. for HMR predictability.
|
|
269
275
|
```
|
|
270
276
|
|
|
271
|
-
Useful
|
|
277
|
+
Useful methods: `play()`, `pause()`, `rewind()`, `clear()`, `undo()`, `redo()`, `step(n, forward)`, `jumpTo(frame)`, `export(replacer)`, `import(json, reviver)`.
|
|
272
278
|
|
|
273
279
|
### Reactor Build Options
|
|
274
280
|
|
|
275
|
-
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`.
|
|
276
282
|
|
|
277
283
|
- **`debug?`**: 1-time set. Enables debug logging and diagnostics of core operations. (default: `false`)
|
|
278
284
|
- **`crossRealms?`**: Enables cross-realm object detection support by using slower but safer type checks. (e.g. iframes) (default: `false`).
|
|
@@ -283,8 +289,6 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
|
|
|
283
289
|
- **`equalityFunction?`**: Custom equality used by setters and adapter comparisons (default: `Object.is`).
|
|
284
290
|
- **`batchingFunction?`**: Custom batching scheduler for listener notification flushes (default: `queueMicrotask`)
|
|
285
291
|
- **`referenceTracking?`**: Enables identity/reference tracking features in the runtime. (default: `false`).
|
|
286
|
-
|
|
287
|
-
*NOTE: those not marked as 1-time set are configurable via `Reactor.config`. Also, plugs and hooks turn on whatever they need automatically.*
|
|
288
292
|
|
|
289
293
|
### Reactive Preferences (Method Naming)
|
|
290
294
|
|
|
@@ -295,7 +299,7 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
|
|
|
295
299
|
- **`whitelist?`**: Keeps specific methods on their original names while others get affixed.
|
|
296
300
|
|
|
297
301
|
```javascript
|
|
298
|
-
import { reactive } from
|
|
302
|
+
import { reactive } from "sia-reactor";
|
|
299
303
|
|
|
300
304
|
const state = reactive(
|
|
301
305
|
{ count: 0 },
|
|
@@ -341,9 +345,10 @@ player.on("intent.playing", (e) => {
|
|
|
341
345
|
|
|
342
346
|
### Troubleshooting
|
|
343
347
|
|
|
344
|
-
- Listener timing feels late: `on` is microtask-batched by design; use `watch` only for strict immediate engine sync.
|
|
345
|
-
-
|
|
346
|
-
-
|
|
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.
|
|
347
352
|
- Cross-frame data is skipped: enable `crossRealms: true` for iframe/other realm objects.
|
|
348
353
|
- Class/prototype behavior is odd: enable `preserveContext: true` (tradeoff: slower hot paths).
|
|
349
354
|
- Working with symbol keys and you want blind writes/reads: unwrap first with `getRaw` or `RAW` and operate on the raw object.
|
|
@@ -355,9 +360,9 @@ player.on("intent.playing", (e) => {
|
|
|
355
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.
|
|
356
361
|
|
|
357
362
|
### 1. The Synchronous Dimension: The `Payload`
|
|
358
|
-
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).
|
|
359
364
|
|
|
360
|
-
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`.
|
|
361
366
|
|
|
362
367
|
#### The `Payload` Anatomy
|
|
363
368
|
```javascript
|
|
@@ -390,7 +395,7 @@ The `target` and `currentTarget` objects give you absolute surgical awareness of
|
|
|
390
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.
|
|
391
396
|
|
|
392
397
|
```javascript
|
|
393
|
-
import { TERMINATOR } from
|
|
398
|
+
import { TERMINATOR } from "sia-reactor";
|
|
394
399
|
|
|
395
400
|
// Example: Data Sanitization & Blocking
|
|
396
401
|
rtr.set("user.age", (value) => {
|
|
@@ -400,14 +405,14 @@ rtr.set("user.age", (value) => {
|
|
|
400
405
|
```
|
|
401
406
|
|
|
402
407
|
### 2. The Asynchronous Dimension: The S.I.A. Event Loop
|
|
403
|
-
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.
|
|
404
409
|
|
|
405
|
-
If you mutate `state.user.profile.name = "
|
|
410
|
+
If you mutate `state.user.profile.name = "Kosi"`, the event wave travels like this:
|
|
406
411
|
1. **Capture Phase:** `*` (Root) ➔ `user` ➔ `user.profile`
|
|
407
412
|
2. **Target Phase:** `user.profile.name`
|
|
408
413
|
3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
|
|
409
414
|
|
|
410
|
-
*NOTE: Only `on` does this since it is batched
|
|
415
|
+
*NOTE: Only `on` does this since it is batched to stay within recursive limits.*
|
|
411
416
|
|
|
412
417
|
#### The Event Anatomy (`REvent` type)
|
|
413
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.
|
|
@@ -419,13 +424,13 @@ rtr.on("user.profile", (e) => {
|
|
|
419
424
|
console.log(e.staticType); // "set" (The original action)
|
|
420
425
|
console.log(e.path); // "user.profile.name" (The actual property changed)
|
|
421
426
|
console.log(e.currentTarget); // { path: "user.profile", value: {...} } (Where we are listening)
|
|
422
|
-
console.log(e.value); // "
|
|
427
|
+
console.log(e.value); // "Kosi" (The new value)
|
|
423
428
|
console.log(e.oldValue); // "John" (The previous value)
|
|
424
429
|
// 2. Political Routing
|
|
425
430
|
console.log(e.eventPhase); // 3 (Bubbling Phase)
|
|
426
431
|
console.log(e.bubbles); // true/false
|
|
427
432
|
// 3. Misc
|
|
428
|
-
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)
|
|
429
434
|
}); // you could use external callbacks but typed with `REvent<T, "user.age">`
|
|
430
435
|
```
|
|
431
436
|
|
|
@@ -454,9 +459,29 @@ When you listen to a parent object (like `"user.profile"`), you will naturally c
|
|
|
454
459
|
|
|
455
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`:
|
|
456
461
|
* If `state.user.profile = {}` happens, the listener receives `e.type === "set"`.
|
|
457
|
-
* If `state.user.profile.name = "
|
|
462
|
+
* If `state.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
|
|
458
463
|
|
|
459
|
-
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
|
+
```
|
|
460
485
|
|
|
461
486
|
---
|
|
462
487
|
|
|
@@ -464,7 +489,7 @@ This allows for highly fine-grained syncing bridges across your application with
|
|
|
464
489
|
|
|
465
490
|
### The CSS Black Box
|
|
466
491
|
|
|
467
|
-
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.
|
|
468
493
|
|
|
469
494
|
Instead, we use the **Root Wildcard** (`"*"`) for both Reading (`get`) and Writing (`watch`).
|
|
470
495
|
|
|
@@ -486,8 +511,8 @@ this.ctlr.config.get("*", (val, { target: { key, path } }) => {
|
|
|
486
511
|
});
|
|
487
512
|
```
|
|
488
513
|
#### Why this pattern is elite:
|
|
489
|
-
1. **Synchronous Execution (`watch`):** CSSOM needs immediate updates. If you used an
|
|
490
|
-
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.
|
|
491
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.
|
|
492
517
|
|
|
493
518
|
---
|
|
@@ -499,7 +524,6 @@ S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media
|
|
|
499
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.
|
|
500
525
|
* **Video.js (VJS):** The philosophy of "Intent vs. State" MEDIATION, ensuring UI actions only commit when the underlying engine allows it.
|
|
501
526
|
* **The Browser DOM:** Treating a raw JSON state tree like HTML nodes, complete with deep, path-based event bubbling.
|
|
502
|
-
* **The JavaScript Event Loop:** Utilizing `queueMicrotask` to batch thousands of synchronous state mutations into a single, noiseless render tick.
|
|
503
527
|
* **Vue, MobX & Valtio:** Leveraging native ES6 Proxies for instant, deep reactivity without forcing clunky `get()` or `set()` wrapper functions.
|
|
504
528
|
|
|
505
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
|
|