sia-reactor 0.0.12 → 0.0.16
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 +318 -120
- package/dist/adapters/react.cjs +1063 -0
- package/dist/adapters/react.d.cts +88 -0
- package/dist/adapters/react.d.ts +88 -0
- package/dist/adapters/react.js +73 -0
- package/dist/adapters/vanilla.cjs +223 -0
- package/dist/adapters/vanilla.d.cts +16 -0
- package/dist/adapters/vanilla.d.ts +16 -0
- package/dist/adapters/vanilla.js +20 -0
- package/dist/{chunk-DW7Z7YEA.js → chunk-4MJUBEI7.js} +86 -46
- package/dist/chunk-FGUCKMLH.js +154 -0
- package/dist/chunk-KIQP7G7W.js +80 -0
- package/dist/chunk-Q2AKMIHN.js +721 -0
- package/dist/chunk-UQIMMJTY.js +70 -0
- package/dist/index-jlxXiocy.d.cts +1068 -0
- package/dist/index-jlxXiocy.d.ts +1068 -0
- package/dist/index.cjs +441 -188
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +36 -5
- package/dist/plugins.cjs +1278 -0
- package/dist/plugins.d.cts +183 -0
- package/dist/plugins.d.ts +183 -0
- package/dist/plugins.js +365 -0
- package/dist/super.global.js +1625 -0
- package/dist/utils.cjs +218 -50
- package/dist/utils.d.cts +171 -1
- package/dist/utils.d.ts +171 -1
- package/dist/utils.js +96 -8
- package/package.json +41 -13
- package/dist/chunk-KFPA5OPK.js +0 -576
- package/dist/index-Bmuqp8Xy.d.cts +0 -497
- package/dist/index-Bmuqp8Xy.d.ts +0 -497
- package/dist/index.global.js +0 -696
- package/dist/react.cjs +0 -690
- package/dist/react.d.cts +0 -11
- package/dist/react.d.ts +0 -11
- package/dist/react.global.js +0 -2883
- package/dist/react.js +0 -70
- package/dist/utils.global.js +0 -167
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/sia-reactor)
|
|
7
|
+
[](https://bundlephobia.com/package/sia-reactor)
|
|
7
8
|
|
|
8
9
|
[Live Demo & Benchmarks](https://tobi007-del.github.io/t007-tools/packages/sia-reactor/src/index.html) | [Report Bug](https://github.com/Tobi007-del/t007-tools/issues)
|
|
9
10
|
|
|
10
|
-
[
|
|
11
|
+
[Chronicles](https://github.com/Tobi007-del/tmg-media-player/blob/main/CHRONICLES.md) | [Interaction Folklore](https://github.com/Tobi007-del/tmg-media-player/blob/main/FOLKLORE.md)
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
@@ -15,15 +16,12 @@
|
|
|
15
16
|
|
|
16
17
|
- [sia-reactor](#sia-reactor)
|
|
17
18
|
- [Table of contents](#table-of-contents)
|
|
18
|
-
- [
|
|
19
|
-
- [The Philosophy: Collecting Like Terms](#the-philosophy-collecting-like-terms)
|
|
20
|
-
- [State vs. Intent](#state-vs-intent)
|
|
21
|
-
- [The Art of Resolution: The Power Line](#the-art-of-resolution-the-power-line)
|
|
22
|
-
- [The Triad of Notifications](#the-triad-of-notifications)
|
|
23
|
-
- [Tech Stack](#tech-stack)
|
|
19
|
+
- [Reading Paths](#reading-paths)
|
|
24
20
|
- [Getting Started](#getting-started)
|
|
25
21
|
- [Usage](#usage)
|
|
26
22
|
- [API Reference](#api-reference)
|
|
23
|
+
- [Notification Physics](#notification-physics)
|
|
24
|
+
- [Architectural Tricks](#architectural-tricks)
|
|
27
25
|
- [Inspirations](#inspirations)
|
|
28
26
|
- [Benchmarks](#benchmarks)
|
|
29
27
|
- [Author](#author)
|
|
@@ -32,119 +30,44 @@
|
|
|
32
30
|
|
|
33
31
|
---
|
|
34
32
|
|
|
35
|
-
##
|
|
33
|
+
## Reading Paths
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
Choose your reading mode:
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
- **I want to understand the architecture shift first**:
|
|
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).
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
### Concept Snapshot
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## The Philosophy: Collecting Like Terms
|
|
46
|
-
|
|
47
|
-
Most developers build systems that are entirely too rigid. They create Adapters, Contexts, and Request Managers just to make two different entities behave the same way.
|
|
48
|
-
|
|
49
|
-
> Picture a room with air conditioners. When the entity inside gets hot, the AC should turn on.
|
|
50
|
-
> A standard developer sees a human in the room and writes: *"When human sweats, AC on."*
|
|
51
|
-
> Then, the human is replaced by a pig. Pigs don't sweat. Now the developer has to build an entire adapter layer just to simulate fake sweat so the AC will trigger.
|
|
52
|
-
|
|
53
|
-
**The S.I.A. approach is declarative state pluralism.** We do not care *why* or *how* the entity gets hot. We establish a stable core and collect like terms: **"If entity is hot, turn AC on."** The adapter simply gives the entity a state (`hot = false`). When the entity gets hot, it flips the state to `true`. We just listen.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## State vs. Intent
|
|
58
|
-
|
|
59
|
-
We divide the world into two concepts to completely eliminate the need for bloated class methods (`makeNervous()`, `straightenNose()`).
|
|
60
|
-
|
|
61
|
-
1. **State (Fact):** The current reality of the system. It can only be determined *after* something happens. The UI is a mirror reflecting this state.
|
|
62
|
-
2. **Intent (Request/Wish):** A request to change reality.
|
|
63
|
-
|
|
64
|
-
Instead of learning complex APIs to trigger actions, you simply declare your intent:
|
|
65
|
-
`pig.intent.hot = true` (The Request)
|
|
66
|
-
↳ *The system internally evaluates* ↳ `pig.state.hot = true` (The Fact).
|
|
67
|
-
|
|
68
|
-
This brings the "appeal" directly into the state tree.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
### Semantic Structuring: Plain State vs. Intentful State
|
|
44
|
+
`sia-reactor` treats nested data like a programmable evented tree.
|
|
73
45
|
|
|
74
|
-
|
|
46
|
+
- **State** = factual reality.
|
|
47
|
+
- **Intent** = requested reality.
|
|
48
|
+
- **Mediators** (`get|set|delete`) = synchronous gatekeepers.
|
|
49
|
+
- **Watchers** (`watch`) = synchronous post-mutation observers.
|
|
50
|
+
- **Listeners** (`on`) = microtask-batched event observers.
|
|
75
51
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- **`intent`**: For asynchronous requests or delayed validations (e.g., `intent.playing = true`).
|
|
82
|
-
- **`state`**: The factual mirror of granted intents (e.g., `state.playing = true`).
|
|
83
|
-
- **`settings` / `config`**: For immediate, undisputed user preferences (e.g., `settings.playbackRate = 2`).
|
|
84
|
-
- **`status`**: For read-only system facts (e.g., `status.network = "offline"`).
|
|
52
|
+
Semantic split recommendation:
|
|
53
|
+
- `intent`: async/delayed requests.
|
|
54
|
+
- `state`: granted facts.
|
|
55
|
+
- `settings/config`: immediate user prefs.
|
|
56
|
+
- `status`: read-only system facts.
|
|
85
57
|
|
|
86
58
|
```javascript
|
|
87
59
|
import { reactive, intent } from 'sia-reactor';
|
|
88
60
|
|
|
89
|
-
// A perfectly structured S.I.A. Data DOM
|
|
90
61
|
const player = reactive({
|
|
91
|
-
intent: intent({ playing: false, fullscreen: false }),
|
|
92
|
-
state: { playing: false, fullscreen: false },
|
|
93
|
-
settings: {
|
|
94
|
-
status: { buffering: false, duration: 120 }
|
|
62
|
+
intent: intent({ playing: false, fullscreen: false }),
|
|
63
|
+
state: { playing: false, fullscreen: false },
|
|
64
|
+
settings: { theme: "dark", defaultPlaybackRate: 1 },
|
|
65
|
+
status: { buffering: false, duration: 120 }
|
|
95
66
|
});
|
|
96
67
|
```
|
|
97
68
|
|
|
98
69
|
---
|
|
99
70
|
|
|
100
|
-
## The Art of Resolution: The Power Line
|
|
101
|
-
|
|
102
|
-
Because an **Intent** is just a wish, the system must be able to reject it. This introduces a political hierarchy; a Chain of Responsibility driven by the Event Loop.
|
|
103
|
-
|
|
104
|
-
### The Parable of the King
|
|
105
|
-
Imagine a King wishes to fly: `man.intent.flying = true`.
|
|
106
|
-
You cannot stop him from wishing it. But the system can determine if it will grant the wish.
|
|
107
|
-
|
|
108
|
-
Everything crucial happens in the **Capture Phase**:
|
|
109
|
-
1. **The Higher Power:** A plugin that registers first. It intercepts the wish and can choose to `resolve(message)` or `reject(reason)`.
|
|
110
|
-
2. **The Adviser (The Tech):** Listens further down the capture line. If it sees `e.resolved`, it stands down. If it sees `e.rejected`, it knows the Higher Power failed and can attempt to save the situation or allow the failure.
|
|
111
|
-
3. **The Observers (The UI):** Listens on the **Bubble Phase**. They don't get involved in politics; they just watch the aftermath.
|
|
112
|
-
|
|
113
|
-
#### The Observer Types
|
|
114
|
-
- **The Smart Optimist (Court Man):** Checks if the intent was rejected before updating the UI.
|
|
115
|
-
- **The Reckless Optimist (Artist):** Doesn't care about rejections and paints the wish immediately, trusting the system will snap back later if needed.
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## The Triad of Notifications
|
|
120
|
-
|
|
121
|
-
This architecture replaces the chaos of traditional state management with three distinct layers of surgical precision.
|
|
122
|
-
|
|
123
|
-
### 1. The Gatekeepers (Mediators) - `get`, `set`, `delete`
|
|
124
|
-
Synchronous operations that occur *before* or *during* a state change.
|
|
125
|
-
- Use `.set()` for data integrity and sanitization. You can intercept a value, modify it, or completely block the memory write by returning the `TERMINATOR` symbol.
|
|
126
|
-
- Use `.get()` to format or derive output on the fly without altering the underlying data.
|
|
127
|
-
|
|
128
|
-
### 2. The Rule of Survival (Watchers) - `watch`
|
|
129
|
-
Synchronous operations that occur *immediately after* a state change.
|
|
130
|
-
- Use `.watch()` when the very next line of code will crash if a system isn't updated instantly (e.g., `video.src`, internal engine states). This bypasses the async event loop for immediate execution.
|
|
131
|
-
|
|
132
|
-
### 3. The Rule of the Cloud (Listeners) - `on`
|
|
133
|
-
Asynchronous operations that run in the next microtask.
|
|
134
|
-
- Use `.on()` for **all UI updates** (e.g., `volume`, `brightness`). The reactor will automatically smash 1,000 synchronous mutations into a single `queueMicrotask` UI render tick.
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## Tech Stack
|
|
139
|
-
|
|
140
|
-
### Built with
|
|
141
|
-
- ECMAScript 2015+ Proxy API
|
|
142
|
-
- `queueMicrotask` Async Batching
|
|
143
|
-
- Bitwise Operations & Zero-Allocation Caching
|
|
144
|
-
- Bundled via `tsup` (ESM, CJS, IIFE outputs)
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
71
|
## Getting Started
|
|
149
72
|
|
|
150
73
|
### Installation
|
|
@@ -174,25 +97,42 @@ import { setAny, getAny, mergeObjs } from 'sia-reactor/utils';
|
|
|
174
97
|
### Modern Bundlers (ESM)
|
|
175
98
|
|
|
176
99
|
```javascript
|
|
177
|
-
import { reactive, Reactor } from 'sia-reactor';
|
|
100
|
+
import { reactive, Reactor } from 'sia-reactor';
|
|
101
|
+
import 'sia-reactor/utils'; // deep object helpers (setAny/getAny/mergeObjs/...)
|
|
102
|
+
import 'sia-reactor/plugins'; // built-in plugins + storage adapters
|
|
103
|
+
import 'sia-reactor/adapters/vanilla'; // Autotracker + effect API
|
|
104
|
+
import 'sia-reactor/adapters/react'; // useReactor/useSelector/usePath hooks
|
|
178
105
|
```
|
|
179
106
|
|
|
180
107
|
### CDN / Browser (Global)
|
|
181
108
|
|
|
182
|
-
```
|
|
183
|
-
|
|
109
|
+
```html
|
|
110
|
+
<!DOCTYPE html>
|
|
111
|
+
<html>
|
|
112
|
+
<body>
|
|
113
|
+
<script src="https://cdn.jsdelivr.net/npm/sia-reactor@latest"></script>
|
|
114
|
+
<script>
|
|
115
|
+
const { reactive, Reactor } = window.sia;
|
|
116
|
+
window.sia.utils;
|
|
117
|
+
window.sia.plugins;
|
|
118
|
+
window.sia.adapters.vanilla;
|
|
119
|
+
</script>
|
|
120
|
+
</body>
|
|
121
|
+
</html>
|
|
184
122
|
```
|
|
185
123
|
|
|
186
124
|
## API Reference
|
|
187
125
|
|
|
188
126
|
### Initialization (`reactive` & `Reactor`)
|
|
189
127
|
|
|
190
|
-
The primary way to use the
|
|
128
|
+
The primary way to use the reactor is to wrap an object using `reactive()`, which directly mixes the reactor methods into your target object for a pristine, flat API.
|
|
129
|
+
|
|
130
|
+
*NOTE: `.` and `*` are engine reserved so don't use them as object keys*
|
|
191
131
|
|
|
192
132
|
```javascript
|
|
193
|
-
const state = reactive({ player: { volume: 50 } });
|
|
133
|
+
const state = reactive({ player: { volume: 50 } }, { smartCloning: true, referenceTracking: true });
|
|
194
134
|
|
|
195
|
-
// Methods are attached directly to the object
|
|
135
|
+
// Methods are attached directly to the object with `reactive()`!
|
|
196
136
|
state.set("player.volume", (val) => Math.min(val, 100));
|
|
197
137
|
state.on("player.volume", (e) => console.log(e.value));
|
|
198
138
|
|
|
@@ -201,17 +141,35 @@ state.player.volume = 150; // Triggers mediation, clamps to 100, fires listener.
|
|
|
201
141
|
|
|
202
142
|
Alternatively, you can instantiate the `Reactor` class directly to keep the API separate from your data:
|
|
203
143
|
```javascript
|
|
204
|
-
const
|
|
205
|
-
|
|
144
|
+
const reactor = new Reactor({ player: { volume: 50 } }, { debug: true, referenceTracking: true });
|
|
145
|
+
reactor.core.player.volume = 100;
|
|
146
|
+
const state = reactive(reactor); // Methods are now attached to the core and returned.
|
|
147
|
+
state.__Reactor__ // Reference to the underlying reactor
|
|
206
148
|
```
|
|
207
149
|
|
|
150
|
+
### Reactor Build Options
|
|
151
|
+
|
|
152
|
+
These are some core build options accepted by `new Reactor(core, build)` and `reactive(core, build)`.
|
|
153
|
+
|
|
154
|
+
- **`debug`**: 1-time set. Enables debug logging and diagnostics of core operations. (default: `false`)
|
|
155
|
+
- **`crossRealms`**: Enables cross-realm object detection support by using slower but safer type checks. (e.g. iframes) (default: `false`).
|
|
156
|
+
- **`smartCloning`**: Enables structural-sharing snapshot behavior (requires `referenceTracking: true`) (default: `false`).
|
|
157
|
+
- **`eventBubbling`**: Enables event bubbling across ancestor paths (default: `true`) (default: `true`).
|
|
158
|
+
- **`lineageTracing`**: Enables path lineage tracing for reference lookups on property access (requires `referenceTracking: true`) (default: `false`).
|
|
159
|
+
- **`preserveContext`**: Preserves Reflect trap context; safer with ~8x slowdown in hot paths, allows more types to be proxied (e.g. classes) (default: `false`).
|
|
160
|
+
- **`equalityFunction`**: Custom equality used by setters and adapter comparisons (default: `Object.is`).
|
|
161
|
+
- **`batchingFunction`**: Custom batching scheduler for listener notification flushes (default: `queueMicrotask`)
|
|
162
|
+
- **`referenceTracking`**: Enables identity/reference tracking features in the runtime. (default: `false`).
|
|
163
|
+
|
|
164
|
+
*NOTE: those not marked as 1-time set are configurable via `Reactor.config`.*
|
|
165
|
+
|
|
208
166
|
### Memory & Granular Control Flags
|
|
209
167
|
|
|
210
168
|
You can wrap properties in special flags *before* initializing the reactor to dictate exactly how the Proxy treats them.
|
|
211
169
|
|
|
212
170
|
- **`inert(obj)` / `live(obj)`**: Tells the proxy to completely ignore an object. It will not be deeply tracked.
|
|
213
171
|
- **`intent(obj)` / `state(obj)`**: Marks an object as rejectable. Allows listeners to call `e.reject()` during the Capture phase.
|
|
214
|
-
- **`volatile(obj)` / `stable(obj)`**: Forces the
|
|
172
|
+
- **`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).
|
|
215
173
|
|
|
216
174
|
```javascript
|
|
217
175
|
import { reactive, intent, volatile, inert } from 'sia-reactor';
|
|
@@ -243,8 +201,249 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
|
|
|
243
201
|
#### **Lifecycle & Utilities**
|
|
244
202
|
- **`tick(path)`**: Forces a synchronous flush of the batch queue for a specific path.
|
|
245
203
|
- **`stall(task)` / `nostall(task)`**: Manually stall the queue to wait for calculations before rendering.
|
|
246
|
-
- **`cascade(
|
|
204
|
+
- **`cascade(eventOrPayload, objectSafe)`**: Manually trigger deep-object event waves, bypassing strict unchanged-proxy traps. Perfect for dumping massive API payloads into the tree, `objectSafe` merges `value` with `oldValue`.
|
|
247
205
|
- **`snapshot(raw)`**: Generates a strict, structurally-shared, un-proxied clone of the current state tree.
|
|
206
|
+
- **`plugIn(new ReactorPlugin(config))`**: Allows extended behaviour with external logic.
|
|
207
|
+
- **`reset()`**: Clears all records bringing everything back to a clean slate.
|
|
208
|
+
- **`destroy()`**: Last resort destruction, nukes everything by nullifying it's properties for full disposal.
|
|
209
|
+
|
|
210
|
+
### Plugins: The Extension Port
|
|
211
|
+
|
|
212
|
+
The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Plugins. It ships with a suite of built-in plugins for common architectural needs. like a Persist and TimeTravel plugin.
|
|
213
|
+
|
|
214
|
+
#### The Persistence Plugin
|
|
215
|
+
Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB. It respects the async nature of IDB while keeping your app synchronous.
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
import { reactive, Reactor } from 'sia-reactor';
|
|
219
|
+
import { PersistPlugin, LocalStorageAdapter, IndexedDBAdapter } from 'sia-reactor/plugins';
|
|
220
|
+
|
|
221
|
+
const state = reactive({ theme: "dark", volume: 50 });
|
|
222
|
+
state.__Reactor__.plugIn(new PersistPlugin({
|
|
223
|
+
key: "APP_PREFS",
|
|
224
|
+
throttle: 5000,
|
|
225
|
+
adapter: LocalStorageAdapter
|
|
226
|
+
}); // Plug it in. State is now automatically hydrated and throttled-saved.
|
|
227
|
+
|
|
228
|
+
const reactor = new Reactor({ theme: "dark", settings: { volume: 50, brightness: 30 } });
|
|
229
|
+
reactor.plugIn(new PersistPlugin({
|
|
230
|
+
key: "APP_PREFS",
|
|
231
|
+
paths: ["theme", "settings.brightness"],
|
|
232
|
+
adapter: new IndexedDBAdapter({ dbName: "Session", version: 1, onversionchange: () => location.reload() })
|
|
233
|
+
}, reactor)); // Put Reactor as second arg if you want type inference, e.g. for the paths in the array.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### React Hooks & Effects
|
|
237
|
+
|
|
238
|
+
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).
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
import { reactive } from 'sia-reactor';
|
|
242
|
+
import { useReactor, useSelector, usePath, effect } from 'sia-reactor/react';
|
|
243
|
+
|
|
244
|
+
const state = reactive({ user: { name: "Ada", age: 25 }, theme: "dark" });
|
|
245
|
+
|
|
246
|
+
// 1. The Tracked State (Valtio-style)
|
|
247
|
+
function Profile() {
|
|
248
|
+
const sameState = useReactor(state); // pass in a normal object for an auto-scoped instance
|
|
249
|
+
useAnyReactor(); // use this when you just want to use any state from any reactor
|
|
250
|
+
// Only re-renders if state.user.name mutates. Completely ignores age and theme!
|
|
251
|
+
return <div>{sameState.user.name + otherState.user.name}</div>;
|
|
252
|
+
} // no snapshots like Valtio, you can read or write to anything
|
|
253
|
+
|
|
254
|
+
// 2. The Slice Selector (Zustand-style)
|
|
255
|
+
function Theme() {
|
|
256
|
+
const theme = useSelector(state, (s) => s.theme); // pass in a normal object for an auto-scoped instance
|
|
257
|
+
const newName = useAnySelector(() => state.user.name + spouseState.user.name); // use this when you just want to derive any state from any reactor
|
|
258
|
+
return <div>Theme: {theme}</div>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 3. The Direct Path Observer
|
|
262
|
+
function AgeObserver() {
|
|
263
|
+
const age = usePath(state, "user.age"); // pass in a normal object for an auto-scoped instance
|
|
264
|
+
return <div>Age: {age}</div>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
|
|
268
|
+
const stopTracking = effect(() => {
|
|
269
|
+
console.log("User name changed to:", state.user.name);
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Migration: Method API to State/Event Protocol
|
|
274
|
+
|
|
275
|
+
If you are moving from command-style APIs (`play()`, `pause()`, `setVolume(x)`), map them into intent/state flows.
|
|
276
|
+
|
|
277
|
+
- `player.play()` -> `player.intent.playing = true`
|
|
278
|
+
- `player.pause()` -> `player.intent.playing = false`
|
|
279
|
+
- `player.setVolume(80)` -> `player.intent.volume = 80`
|
|
280
|
+
- tech/system confirmation -> `player.state.playing = true`, `player.state.volume = 80`
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
// old style
|
|
284
|
+
player.play();
|
|
285
|
+
player.setVolume(80);
|
|
286
|
+
|
|
287
|
+
// S.I.A protocol
|
|
288
|
+
player.intent.playing = true;
|
|
289
|
+
player.intent.volume = 80;
|
|
290
|
+
|
|
291
|
+
player.set("intent.volume", (v) => Math.max(0, Math.min(100, v))); // gatekeeper
|
|
292
|
+
player.on("intent.playing", (e) => {
|
|
293
|
+
if (!player.status.ready) return e.reject("media not ready");
|
|
294
|
+
player.state.playing = true; // factual mirror
|
|
295
|
+
}, { capture: true });
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Troubleshooting
|
|
299
|
+
|
|
300
|
+
- Listener timing feels late: `on` is microtask-batched by design; use `watch` only for strict immediate engine sync.
|
|
301
|
+
- `reject()` appears ignored: call it in capture phase and ensure branch is wrapped in `intent(...)`, remember it's the listener's choice to listen also.
|
|
302
|
+
- Snapshot behavior feels stale: enable `referenceTracking: true` with `smartCloning: true`.
|
|
303
|
+
- Cross-frame data is skipped: enable `crossRealms: true` for iframe/other realm objects.
|
|
304
|
+
- Class/prototype behavior is odd: enable `preserveContext: true` (tradeoff: slower hot paths).
|
|
305
|
+
- Working with symbol keys and you want blind writes/reads: unwrap first with `getRaw` or `RAW` and operate on the raw object.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Notification Physics
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
### 1. The Synchronous Dimension: The `Payload`
|
|
314
|
+
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).
|
|
315
|
+
|
|
316
|
+
Because there is no "bubbling" or "event wave" yet, these methods do not receive a complex event object. They receive a lightweight, factual `Payload`.
|
|
317
|
+
|
|
318
|
+
#### The `Payload` Anatomy
|
|
319
|
+
```javascript
|
|
320
|
+
rtr.set("user.age", (value, terminated, payload) => {
|
|
321
|
+
console.log(payload.type); // "set" | "get" | "delete"
|
|
322
|
+
console.log(payload.target); // The exact anatomy of the mutation (see below)
|
|
323
|
+
console.log(payload.root); // Reference to the entire `state` tree
|
|
324
|
+
console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
|
|
325
|
+
console.log(payload.rejectable); // Boolean: Is this target wrapped in `intent()`?
|
|
326
|
+
}); // you could use external callbacks but typed with `Payload<T, "user.age">`
|
|
327
|
+
rtr.get("user.age", (value, payload) => {});
|
|
328
|
+
rtr.delete("user.age", (terminated, payload) => {});
|
|
329
|
+
rtr.watch("user.age", (value, payload) => {});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### The `Target` Anatomy (Inside the Payload)
|
|
333
|
+
The `target` and `currentTarget` objects give you absolute surgical awareness of the memory reference:
|
|
334
|
+
```javascript
|
|
335
|
+
{
|
|
336
|
+
path: "user.age", // The full dot-path being accessed
|
|
337
|
+
key: "age", // The specific property key
|
|
338
|
+
value: 26, // The NEW value attempting to be written
|
|
339
|
+
oldValue: 25, // The CURRENT value sitting in memory
|
|
340
|
+
object: { age: 25 } // The actual memory reference of the parent object
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### The Power of the `TERMINATOR` (Symbol.for("S.I.A_TERMINATOR"))
|
|
345
|
+
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.
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
import { TERMINATOR } from 'sia-reactor';
|
|
349
|
+
|
|
350
|
+
// Example: Data Sanitization & Blocking
|
|
351
|
+
rtr.set("user.age", (value) => {
|
|
352
|
+
if (typeof value !== "number") return TERMINATOR; // 🛡️ Kills the memory write entirely!
|
|
353
|
+
return Math.max(0, value); // Modifies the value before it hits memory
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 2. The Asynchronous Dimension: The S.I.A. Event Loop
|
|
358
|
+
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.
|
|
359
|
+
|
|
360
|
+
If you mutate `state.user.profile.name = "Ada"`, the event wave travels like this:
|
|
361
|
+
1. **Capture Phase:** `*` (Root) ➔ `user` ➔ `user.profile`
|
|
362
|
+
2. **Target Phase:** `user.profile.name`
|
|
363
|
+
3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
|
|
364
|
+
|
|
365
|
+
*NOTE: Only `on` does this since it is batched tree walking to stay within recursive limits.*
|
|
366
|
+
|
|
367
|
+
#### The Event Anatomy (`REvent` type)
|
|
368
|
+
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.
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
rtr.on("user.profile", (e) => {
|
|
372
|
+
// 1. Inherited Facts
|
|
373
|
+
console.log(e.type); // "update" (Because a child mutated)
|
|
374
|
+
console.log(e.staticType); // "set" (The original action)
|
|
375
|
+
console.log(e.path); // "user.profile.name" (The actual property changed)
|
|
376
|
+
console.log(e.currentTarget); // { path: "user.profile", value: {...} } (Where we are listening)
|
|
377
|
+
console.log(e.value); // "Ada" (The new value)
|
|
378
|
+
console.log(e.oldValue); // "John" (The previous value)
|
|
379
|
+
// 2. Political Routing
|
|
380
|
+
console.log(e.eventPhase); // 3 (Bubbling Phase)
|
|
381
|
+
console.log(e.bubbles); // true/false
|
|
382
|
+
// 3. Misc
|
|
383
|
+
console.log(e.composedPath()); // ["Ada", { name: "Ada", age: 26 }, { profile: { name: "Ada", age: 26 } }, { user: { profile: { name: "Ada", age: 26 } } }] (refs, target -> root)
|
|
384
|
+
}); // you could use external callbacks but typed with `REvent<T, "user.age">`
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Event Control Flow
|
|
388
|
+
|
|
389
|
+
Just like the browser DOM, you have absolute political control over the event wave:
|
|
390
|
+
|
|
391
|
+
* **`e.stopPropagation()`**: Stops the wave from traveling to the next node in the chain.
|
|
392
|
+
* **`e.stopImmediatePropagation()`**: Stops the wave instantly, preventing even other listeners on the *current* node from hearing it.
|
|
393
|
+
* **`e.reject("Reason")`**: Used exclusively during the Capture Phase on `intent()` objects to formally deny a state request.
|
|
394
|
+
* **`e.resolve("Message")`**: Formally grants an intent (optional, as intents naturally resolve if unrejected).
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
// A Higher Power blocking an intent
|
|
398
|
+
rtr.on("intent.playing", (e) => {
|
|
399
|
+
if (!user.isLoggedIn) {
|
|
400
|
+
e.reject("User must be logged in to play media.");
|
|
401
|
+
e.stopPropagation(); // Kill the wave here
|
|
402
|
+
}
|
|
403
|
+
}, { capture: true }); // Must listen on the Capture Phase!
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### The Magic of `e.type === "update"`
|
|
407
|
+
|
|
408
|
+
When you listen to a parent object (like `"user.profile"`), you will naturally catch all mutations to its children.
|
|
409
|
+
|
|
410
|
+
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`:
|
|
411
|
+
* If `state.user.profile = {}` happens, the listener receives `e.type === "set"`.
|
|
412
|
+
* If `state.user.profile.name = "Ada"` happens, the parent listener receives `e.type === "update"`.
|
|
413
|
+
|
|
414
|
+
This allows for highly fine-grained syncing bridges across your application without writing heavy, manual for-loop diffing algorithms!
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Architectural Tricks
|
|
419
|
+
|
|
420
|
+
### The CSS Black Box
|
|
421
|
+
|
|
422
|
+
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.
|
|
423
|
+
|
|
424
|
+
Instead, we use the **Root Wildcard** (`"*"`) for both Reading (`get`) and Writing (`watch`).
|
|
425
|
+
|
|
426
|
+
#### 1. The Write (State -> DOM)
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
// Intercept EVERY mutation, but quickly filter for our CSS namespace
|
|
430
|
+
this.ctlr.config.watch("*", (val, { target: { key, path } }) => {
|
|
431
|
+
if (path.startsWith("settings.css.")) this.updateActualCSSVariable(key, val); // Paint to the DOM instantly
|
|
432
|
+
}, { signal: this.signal });
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### 2. The Read (DOM -> State)
|
|
436
|
+
```javascript
|
|
437
|
+
this.ctlr.config.get("*", (val, { target: { key, path } }) => {
|
|
438
|
+
if (!path.startsWith("settings.css.")) return val;
|
|
439
|
+
// Intercept the read, and return it. store in a CSSOM cache once if you want to reset later.
|
|
440
|
+
return ((this._cache[key] ||= val = this.getActualCSSVariable(key)), val);
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
#### Why this pattern is elite:
|
|
444
|
+
1. **Synchronous Execution (`watch`):** CSSOM needs immediate updates. If you used an asynchronous `.on()` listener, the browser might paint the old frame before the microtask resolves, causing UI flicker. `.watch()` executes synchronously during the proxy trap.
|
|
445
|
+
2. **The Wildcard Tradeoff:** By listening to `*`, this callback runs synchronously on *every single mutation* in the entire reactor.
|
|
446
|
+
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.
|
|
248
447
|
|
|
249
448
|
---
|
|
250
449
|
|
|
@@ -252,6 +451,7 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
|
|
|
252
451
|
|
|
253
452
|
S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media engineering into a single, zero-allocation engine:
|
|
254
453
|
|
|
454
|
+
* **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.
|
|
255
455
|
* **Video.js (VJS):** The philosophy of "Intent vs. State" MEDIATION, ensuring UI actions only commit when the underlying engine allows it.
|
|
256
456
|
* **The Browser DOM:** Treating a raw JSON state tree like HTML nodes, complete with deep, path-based event bubbling.
|
|
257
457
|
* **The JavaScript Event Loop:** Utilizing `queueMicrotask` to batch thousands of synchronous state mutations into a single, noiseless render tick.
|
|
@@ -263,7 +463,9 @@ S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media
|
|
|
263
463
|
|
|
264
464
|
No fancy screenshots here. True engineers look at performance metrics.
|
|
265
465
|
|
|
266
|
-
To see the
|
|
466
|
+
To see the Reactor handle deep DAG mutations, DOM-style event routing, and microtask batching in real-time, visit the **[Live Demo](https://tobi007-del.github.io/t007-tools/packages/sia-reactor/src/index.html)**, open your DevTools console, and run the built-in Grand Master Stress Suite directly on your own CPU.
|
|
467
|
+
|
|
468
|
+
*NOTE: The reactor is progressively enhanced so it's performance depends on how you use it and the options you toggle, it's base form is incredibly light.*
|
|
267
469
|
|
|
268
470
|
---
|
|
269
471
|
|
|
@@ -272,13 +474,9 @@ To see the S.I.A Engine handle deep DAG mutations, DOM-style event routing, and
|
|
|
272
474
|
- Architect & Developer - [Oketade Oluwatobiloba (Tobi007-del)](https://github.com/Tobi007-del)
|
|
273
475
|
- Project - [t007-tools](https://github.com/Tobi007-del/t007-tools)
|
|
274
476
|
|
|
275
|
-
Ah, my bad bro! You want it punchy and straight to the point. I got you. Let's strip away the essays and just hit them with the heavy one-liners.
|
|
276
|
-
|
|
277
|
-
Copy and paste this clean, stripped-down version into your README:
|
|
278
|
-
|
|
279
477
|
## Acknowledgments
|
|
280
478
|
|
|
281
|
-
Designed to bring absolute architectural dominance and rendering efficiency to complex front-end systems. The foundational data layer of the `@t007` ecosystem.
|
|
479
|
+
Designed to bring absolute architectural dominance and rendering efficiency to complex front-end systems. The foundational data layer of the `@t007` and `tmg` ecosystem.
|
|
282
480
|
|
|
283
481
|
## Star History
|
|
284
482
|
|