reazor-react 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Delpi.Kye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,582 @@
1
+ # ⚛️⚡ reazor-react
2
+
3
+ [![NPM](https://img.shields.io/npm/v/reazor-react.svg)](https://www.npmjs.com/package/reazor-react)
4
+ ![Downloads](https://img.shields.io/npm/dt/reazor-react.svg)
5
+
6
+ <a href="https://codesandbox.io/p/sandbox/85f9pd" target="_blank">LIVE EXAMPLE</a>
7
+
8
+ `reazor-react` brings Solid-style fine-grained reactivity to React 18+.
9
+
10
+ It provides:
11
+
12
+ * Deep Proxy-based reactivity
13
+ * Path-level rendering updates
14
+ * Sync computed (derived) values
15
+ * Batched updates
16
+ * Path-based & auto-tracked effects
17
+ * Map / Set / Array support
18
+ * Fully type-safe inference
19
+ * Concurrent-safe React binding
20
+
21
+ > Fine-grained reactive state without atoms or reducers.
22
+
23
+ ---
24
+
25
+ ## ✨ Why reazor-react?
26
+
27
+ Unlike reducer or atom-based stores, `reazor-react`:
28
+
29
+ ✅ Mutates state directly
30
+ ✅ Tracks property access automatically
31
+ ✅ Re-renders only components that access changed paths
32
+ ✅ Supports deeply nested Map / Set / Array
33
+ ✅ Supports sync computed
34
+ ✅ Works in React 18 concurrent mode
35
+ ✅ Batches updates to reduce redundant renders
36
+
37
+ No boilerplate. No selectors required. No atoms.
38
+
39
+ ---
40
+
41
+ ## 🧠 Mental Model
42
+
43
+ ```txt
44
+ Component render
45
+
46
+ Proxy tracks accessed paths
47
+
48
+ Mutation triggers exact listeners
49
+
50
+ Computed values updated
51
+
52
+ Only affected components re-render
53
+ ```
54
+
55
+ Core principle:
56
+
57
+ > Property access defines dependency automatically.
58
+
59
+ ---
60
+
61
+ ## 📦 Installation
62
+
63
+ ```bash
64
+ npm install reazor-react
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 🚀 Quick Start
70
+
71
+ ```ts
72
+ import { createReactive } from "reazor-react"
73
+
74
+ const counter = createReactive({
75
+ count: 0,
76
+ todos: []
77
+ })
78
+
79
+ // define computed
80
+ counter.computed("double", s => s.count * 2, {
81
+ cache: true
82
+ })
83
+ ```
84
+
85
+ ### React Usage
86
+
87
+ ```tsx
88
+ function Counter() {
89
+ const state = counter.use()
90
+
91
+ return (
92
+ <div>
93
+ <h2>{state.count}</h2>
94
+ <button onClick={() => state.count++}>+</button>
95
+ </div>
96
+ )
97
+ }
98
+ ```
99
+
100
+ ✔ No dispatch
101
+ ✔ No reducer
102
+ ✔ No setState
103
+ ✔ Direct mutation
104
+
105
+ ---
106
+
107
+ ## 🎯 useSelector (Ultra Fine-Grained Rendering)
108
+
109
+ ```tsx
110
+ function CountOnly() {
111
+ const count = counter.useSelector(s => s.count)
112
+ return <h2>{count}</h2>
113
+ }
114
+ ```
115
+
116
+ ✔ Only re-renders when `count` changes
117
+ ✔ Does NOT re-render on unrelated state changes
118
+ ✔ No selector memo needed
119
+ ✔ Auto dependency tracking
120
+
121
+ ---
122
+
123
+ ## 🎯 useComputed (Ultra Fine-Grained Rendering for Computed)
124
+
125
+ ```tsx
126
+ function Double() {
127
+ const double = counter.useComputed(s => s.double)
128
+ return <h2>{double}</h2>
129
+ }
130
+ ```
131
+
132
+ ✔ Only re-renders when dependencies of double change
133
+ ✔ Does NOT re-render on unrelated state changes
134
+ ✔ No memo required
135
+ ✔ Uses computed dependency graph
136
+ ✔ Supports nested computed
137
+
138
+ ---
139
+
140
+ # 🧩 Examples
141
+
142
+ ## 1️⃣ Fine-Grained Multiple Components
143
+
144
+ ```ts
145
+ const store = createReactive({
146
+ count: 0,
147
+ name: "Vu"
148
+ })
149
+ ```
150
+
151
+ ```tsx
152
+ function CountView() {
153
+ const state = store.use()
154
+ return <h2>{state.count}</h2>
155
+ }
156
+
157
+ function NameView() {
158
+ const state = store.use()
159
+ return <h2>{state.name}</h2>
160
+ }
161
+
162
+ function Controls() {
163
+ const state = store.use()
164
+ return (
165
+ <>
166
+ <button onClick={() => state.count++}>+</button>
167
+ <button onClick={() => (state.name = "Tung")}>Rename</button>
168
+ </>
169
+ )
170
+ }
171
+ ```
172
+
173
+ Changing `count` does NOT re-render `NameView`.
174
+
175
+ Changing `name` does NOT re-render `CountView`.
176
+
177
+ ---
178
+
179
+ ## 2️⃣ Deep Nested Reactivity
180
+
181
+ ```ts
182
+ const store = createReactive({
183
+ user: {
184
+ profile: {
185
+ name: "Vu",
186
+ age: 28
187
+ }
188
+ }
189
+ })
190
+ ```
191
+
192
+ ```tsx
193
+ function Profile() {
194
+ const state = store.use()
195
+
196
+ return (
197
+ <div>
198
+ <h3>{state.user.profile.name}</h3>
199
+ <button onClick={() => state.user.profile.age++}>
200
+ Age++
201
+ </button>
202
+ </div>
203
+ )
204
+ }
205
+ ```
206
+
207
+ Only components accessing `user.profile.age` re-render.
208
+
209
+ ---
210
+
211
+ ## 3️⃣ Map / Set Support
212
+
213
+ ```ts
214
+ const store = createReactive({
215
+ settings: new Map([["theme", "light"]]),
216
+ tags: new Set(["react"])
217
+ })
218
+ ```
219
+
220
+ ```tsx
221
+ function ThemeToggle() {
222
+ const state = store.use()
223
+ const theme = state.settings.get("theme")
224
+
225
+ return (
226
+ <button
227
+ onClick={() =>
228
+ state.settings.set(
229
+ "theme",
230
+ theme === "light" ? "dark" : "light"
231
+ )
232
+ }
233
+ >
234
+ Theme: {theme}
235
+ </button>
236
+ )
237
+ }
238
+ ```
239
+
240
+ Mutating `Map` or `Set` triggers exact listeners.
241
+
242
+ ---
243
+
244
+ ## 4️⃣ Transaction (Batch Multiple Mutations)
245
+
246
+ ```ts
247
+ store.transaction(() => {
248
+ store.state.count++
249
+ store.state.count++
250
+ store.state.count++
251
+ })
252
+ ```
253
+
254
+ Only ONE render triggered.
255
+
256
+ Perfect for:
257
+
258
+ * Form updates
259
+ * Bulk mutations
260
+ * Async merges
261
+
262
+ ---
263
+
264
+ ## 5️⃣ Computed (Sync)
265
+
266
+ ```ts
267
+ const store = createReactive({
268
+ price: 10,
269
+ quantity: 2
270
+ })
271
+
272
+ store.computed("total", (s) => s.price * s.quantity, {
273
+ cache: true
274
+ })
275
+ ```
276
+
277
+ ```tsx
278
+ function Total() {
279
+ const state = store.use()
280
+ return <h3>Total: {state.total}</h3>
281
+ }
282
+ ```
283
+
284
+ ✔ Recomputed only when dependencies change
285
+ ✔ Supports nested computed
286
+ ✔ Topological scheduling
287
+ ✔ No stale reads
288
+
289
+ ---
290
+
291
+ ### Dynamic Dependency Tracking
292
+
293
+ ```ts
294
+ store.computed("displayName", (s) => {
295
+ return s.user.loggedIn
296
+ ? s.user.profile.name
297
+ : "Guest"
298
+ }, { cache: true })
299
+ ```
300
+
301
+ Behavior:
302
+
303
+ - Initially tracks `user.loggedIn`
304
+ - If `loggedIn` becomes true → also tracks `user.profile.name`
305
+ - If `loggedIn` becomes false → removes profile dependency
306
+
307
+ ---
308
+
309
+ ### Nested Computed Graph
310
+
311
+ ```ts
312
+ store.computed("subtotal", s => s.price * s.qty, { cache: true })
313
+ store.computed("tax", s => s.subtotal * 0.1, { cache: true })
314
+ store.computed("total", s => s.subtotal + s.tax, { cache: true })
315
+ ```
316
+
317
+ Execution order:
318
+
319
+ ```
320
+ price/qty change
321
+
322
+ subtotal recomputed
323
+
324
+ tax recomputed
325
+
326
+ total recomputed
327
+ ```
328
+
329
+ ---
330
+
331
+ ## 6️⃣ Effects
332
+
333
+ Two types:
334
+
335
+ - **Path-based effects**
336
+ - **Auto-tracked effects**
337
+
338
+ ---
339
+
340
+ ### Path-Based Effect
341
+
342
+ ```ts
343
+ store.effect("count", (newValue, oldValue) => {
344
+ console.log("Count changed:", oldValue, "→", newValue)
345
+ })
346
+ ```
347
+
348
+ Supports deep paths:
349
+
350
+ ```ts
351
+ store.effect("todos.0.done", (newValue, oldValue) => {
352
+ console.log("Todo #0 changed:", oldValue, "→", newValue)
353
+ })
354
+ ```
355
+
356
+ Best for:
357
+
358
+ - Logging
359
+ - Analytics
360
+ - Watching specific field
361
+
362
+ ---
363
+
364
+ ### Auto Effect (Fine-Grained)
365
+
366
+ ```ts
367
+ store.autoEffect(() => {
368
+ console.log("Count is:", store.state.count)
369
+ })
370
+ ```
371
+
372
+ Automatically tracks dependencies accessed during execution.
373
+
374
+ ---
375
+
376
+ ### Dynamic Dependencies
377
+
378
+ ```ts
379
+ store.autoEffect(() => {
380
+ if (store.state.user.loggedIn) {
381
+ console.log(store.state.user.profile.name)
382
+ }
383
+ })
384
+ ```
385
+
386
+ Dependencies adjust automatically when conditions change.
387
+
388
+ ---
389
+
390
+ ### Cleanup Support
391
+
392
+ ```ts
393
+ store.autoEffect(() => {
394
+ const id = setInterval(() => {
395
+ console.log(store.state.count)
396
+ }, 1000)
397
+
398
+ return () => clearInterval(id)
399
+ })
400
+ ```
401
+
402
+ Previous cleanup runs before re-execution.
403
+
404
+ ---
405
+
406
+ ### Infinite Loop Protection
407
+
408
+ ```ts
409
+ store.autoEffect(() => {
410
+ if (store.state.count < 5) {
411
+ store.state.count++
412
+ }
413
+ })
414
+ ```
415
+
416
+ Scheduler prevents runaway recursion.
417
+
418
+ ---
419
+
420
+ ## 7️⃣ Large List Performance
421
+
422
+ ```ts
423
+ const store = createReactive({
424
+ items: Array.from({ length: 1000 }, (_, i) => ({
425
+ id: i,
426
+ done: false
427
+ }))
428
+ })
429
+ ```
430
+
431
+ ```tsx
432
+ function List() {
433
+ const state = store.use()
434
+
435
+ return (
436
+ <ul>
437
+ {state.items.map(item => (
438
+ <Item key={item.id} item={item} />
439
+ ))}
440
+ </ul>
441
+ )
442
+ }
443
+ ```
444
+
445
+ Only modified item re-renders.
446
+
447
+ ---
448
+
449
+ # 🧠 Advanced Internals
450
+
451
+ ### Dependency Graph
452
+ - Computed tracks exact dependencies
453
+ - Nested computed supported
454
+ - Minimal invalidation graph
455
+
456
+ ### Batched Updates
457
+ - All mutations inside same tick batched
458
+ - Transaction API supported
459
+ - Prevents render thrashing
460
+
461
+ ---
462
+
463
+ # 🔍 Comparison
464
+
465
+ | Criteria | ⚡ reazor-react | 🧰 Redux Toolkit | 🐻 Zustand | 🧠 MobX |
466
+ | ------------------------------ | ---------------- | ----------------| ---------- | ------- |
467
+ | Fine-grained rendering | ✅ | ❌ | ❌ | ✅ |
468
+ | Direct mutation | ✅ | ❌ | ✅ | ✅ |
469
+ | Deep Map / Set support | ✅ | ❌ | ❌ | ⚠️ |
470
+ | React 18 concurrent-safe | ✅ | ✅ | ✅ | ⚠️ |
471
+ | Automatic batching | ✅ | ❌ | ⚠️ | ⚠️ |
472
+ | Reducer-free | ✅ | ❌ | ✅ | ❌ |
473
+
474
+ ---
475
+
476
+ # 🖼 Reactive Flow Diagram
477
+
478
+ ```txt
479
+ ┌─────────────┐
480
+ │ Component │
481
+ │ Render │
482
+ └─────┬───────┘
483
+ │ Access paths
484
+
485
+ ┌─────────────┐
486
+ │ Proxy │
487
+ │ Tracks deps │
488
+ └─────┬───────┘
489
+ │ Mutate
490
+
491
+ ┌─────────────┐
492
+ │ Computed │
493
+ │ Sync │
494
+ └─────┬───────┘
495
+ │ Batched triggers
496
+
497
+ ┌─────────────┐
498
+ │ Listener │
499
+ │ Re-render │
500
+ └─────────────┘
501
+ ```
502
+
503
+ ---
504
+
505
+ # 🧪 Testing
506
+
507
+ You can test:
508
+
509
+ * Direct mutations
510
+ * Computed derivations
511
+ * Transaction batching
512
+ * Nested structures
513
+ * Map / Set behavior
514
+ * Effects
515
+
516
+ All without mocking React.
517
+
518
+ ## Basic Reactivity
519
+
520
+ ```ts
521
+ import { createReactive } from "reazor-react"
522
+
523
+ const store = createReactive({ count: 0 })
524
+
525
+ store.state.count++
526
+
527
+ expect(store.state.count).toBe(1)
528
+ ```
529
+
530
+ ---
531
+
532
+ ## Computed
533
+
534
+ ```ts
535
+ const store = createReactive({
536
+ price: 10,
537
+ qty: 2
538
+ })
539
+
540
+ store.computed("total", s => s.price * s.qty, { cache: true })
541
+
542
+ expect(store.state.total).toBe(20)
543
+
544
+ store.state.qty = 3
545
+
546
+ expect(store.state.total).toBe(30)
547
+ ```
548
+
549
+ ---
550
+
551
+ ## Transaction
552
+
553
+ ```ts
554
+ store.transaction(() => {
555
+ store.state.count++
556
+ store.state.count++
557
+ })
558
+
559
+ expect(store.state.count).toBe(2)
560
+ ```
561
+
562
+ ---
563
+
564
+ ## Effect
565
+
566
+ ```ts
567
+ let triggered = false
568
+
569
+ store.effect("count", () => {
570
+ triggered = true
571
+ })
572
+
573
+ store.state.count++
574
+
575
+ expect(triggered).toBe(true)
576
+ ```
577
+
578
+ ---
579
+
580
+ # 📜 License
581
+
582
+ MIT
@@ -0,0 +1,7 @@
1
+ import type { Listener } from "../reaxor/types";
2
+ export declare function createBatch(): {
3
+ add: (path: string) => void;
4
+ schedule: (notify: (key: string) => void) => void;
5
+ transaction: (fn: Listener, notify: (key: string) => void) => void;
6
+ getVersion: () => number;
7
+ };
@@ -0,0 +1,7 @@
1
+ import type { EffectOptions, ListenerManager } from "../reaxor";
2
+ type EffectFn = () => void | (() => void);
3
+ export declare function createEffectRuntime(listeners: ListenerManager): {
4
+ effect: (fn: EffectFn, options?: EffectOptions) => () => void;
5
+ batch: (fn: () => void) => void;
6
+ };
7
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { EffectFn } from "../reaxor";
2
+ export declare function createEffects(): {
3
+ register: (path: string, fn: EffectFn) => () => void;
4
+ run: (path: string, newValue: any, oldValue: any) => void;
5
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./batch";
2
+ export * from "./effectRuntime";
3
+ export * from "./effects";
4
+ export * from "./listeners";
5
+ export * from "./proxy";
6
+ export * from "./tracking";
@@ -0,0 +1,2 @@
1
+ import type { ListenerManager } from "../reaxor";
2
+ export declare function createListeners(): ListenerManager;
@@ -0,0 +1,4 @@
1
+ import type { ReactiveProxy, ProxyOptions } from "../reaxor";
2
+ export declare function createProxy<T extends object>(options: ProxyOptions<T>): {
3
+ rootProxy: ReactiveProxy<T>;
4
+ };
@@ -0,0 +1,3 @@
1
+ export declare function startTracking(): Set<string>;
2
+ export declare function stopTracking(): void;
3
+ export declare function track(path: string): void;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("react");function t(){const e=new Set;let t=!1,n=0,r=!1,o=!1;function c(r){n++;for(const t of e)r(t);e.clear(),t=!1}return{add:function(t){e.add(t),e.add("__root__")},schedule:function(e){r?o=!0:t||(t=!0,queueMicrotask(()=>c(e)))},transaction:function(e,t){r=!0,e(),r=!1,o&&(o=!1,c(t))},getVersion:()=>n}}let n=null;function r(){return n=new Set,n}function o(){n=null}function c(e){n&&n.add(e)}function u(e){const t=new Set;let n=!1,c=0;function u(){n||(n=!0,queueMicrotask(s))}function s(){for(;t.size;){const e=Array.from(t).sort(i);t.clear();for(const t of e)f(t)}n=!1}function i(e,t){return e.depth!==t.depth?e.depth-t.depth:e.priority-t.priority}function f(t){if(t.disposed)return;if(t.running)return;if(t.runCount>100)return;t.runCount++,t.running=!0,t.deps.forEach(n=>{e.remove(n,t.scheduler)}),t.cleanup&&(t.cleanup(),t.cleanup=void 0);const n=r(),c=t.fn();o(),t.deps=n,t.deps.forEach(n=>{e.add(n,t.scheduler)}),"function"==typeof c&&(t.cleanup=c),t.running=!1}return{effect:function(r,o){const s={fn:r,deps:new Set,depth:o?.depth??100,priority:o?.priority??10,running:!1,runCount:0,disposed:!1,scheduler:()=>function(e){e.disposed||(e.sync?f(e):(t.add(e),n||0!==c||u()))}(s),sync:o?.sync??!1};return f(s),()=>{s.disposed||(s.disposed=!0,s.deps.forEach(t=>{e.remove(t,s.scheduler)}),s.cleanup&&s.cleanup(),s.deps.clear())}},batch:function(e){c++;try{e()}finally{c--,0===c&&u()}}}}function s(){const e=new Map;return{register:function(t,n){let r=e.get(t);return r||(r=new Set,e.set(t,r)),r.add(n),()=>{const r=e.get(t);r&&(r.delete(n),0===r.size&&e.delete(t))}},run:function(t,n,r){const o=e.get(t);o&&[...o].forEach(e=>e(n,r,t))}}}function i(){const e=new Map;return{add:function(t,n){let r=e.get(t);r||(r=new Set,e.set(t,r)),r.add(n)},remove:function(t,n){e.get(t)?.delete(n)},notify:function(t){e.get(t)?.forEach(e=>e())}}}function f(e){return"symbol"!=typeof e&&("__proto__"!==e&&"constructor"!==e)}function a(e){const{root:t,listeners:n,batch:r,effects:o,computed:u}=e,s=new WeakMap;function i(e,t,c){r.add(e),r.schedule(n.notify),o.run(e,c,t),u.invalidateComputed(e,e=>{r.add(e)})}const a=function e(t,n,r=""){if("object"!=typeof(o=t)||null===o)return t;var o;if(s.has(t))return s.get(t);const d=new Proxy(t,{get(t,o,s){if(!f(o))return Reflect.get(t,o,s);const d=r?`${r}.${o+""}`:o+"";if(""===r&&"string"==typeof o&&u.computedMap.has(o)){const e=u.computedMap.get(o);return u.evaluateComputed(e,a)}u.trackDependency(d),c(d);const p=Reflect.get(t,o,s);if(Array.isArray(t)&&"function"==typeof p){if(["push","pop","splice","shift","unshift","sort","reverse"].includes(o))return(...e)=>{const n=t.slice(),o=p.apply(t,e);return i(r,n,t),o}}return e(p,n,d)},set(e,t,n,o){if(!f(t))return Reflect.set(e,t,n,o);const c=r?`${r}.${t+""}`:t+"",u=e[t];if(Object.is(u,n))return!0;const s=Reflect.set(e,t,n,o);return i(c,u,n),s}});return s.set(t,d),d}(t);return{rootProxy:a}}function d(t,n,r){const o="__root__";function c(e){return n.add(o,e),()=>n.remove(o,e)}function u(n){const r=e.useRef();return e.useSyncExternalStore(c,()=>{const e=n(t);return Object.is(r.current,e)?r.current:(r.current=e,e)},()=>n(t))}return{useReactive:function(){return e.useSyncExternalStore(c,r,r),t},useSelector:u,useComputed:function(e){return u(e)}}}function p(){const e=new Map,t=new Map;let n=null;function r(e,t){n=e;const r=e.getter(t);return n=null,r}return{computedMap:e,register:function(t,n,r){if(e.has(t))throw Error(`Computed key "${t}" already exists`);const o={key:t,getter:n,cache:r?.cache??!1,value:void 0,dirty:!0,deps:new Set};e.set(t,o)},trackDependency:function(e){if(!n)return;n.deps.add(e);let r=t.get(e);r||(r=new Set,t.set(e,r)),r.add(n)},invalidateComputed:function(e,n){const r=t.get(e);if(r)for(const e of r)e.cache&&(e.dirty=!0),n(e.key)},evaluateComputed:function(e,n){if(!e.cache)return r(e,n);if(!e.dirty)return e.value;e.deps.forEach(n=>{t.get(n)?.delete(e)}),e.deps.clear();const o=r(e,n);return e.value=o,e.dirty=!1,e.value}}}exports.createBatch=t,exports.createComputed=p,exports.createEffectRuntime=u,exports.createEffects=s,exports.createListeners=i,exports.createProxy=a,exports.createReactHooks=d,exports.createReactive=function(e){const n=i(),r=t(),o=s(),c=p(),{rootProxy:f}=a({root:e,listeners:n,batch:r,effects:o,computed:c}),{useReactive:l,useSelector:y}=d(f,n,r.getVersion),h=u(n);return{state:f,use:l,useSelector:y,effect:o.register,autoEffect:h.effect,computed:c.register,transaction:e=>r.transaction(e,n.notify),getVersion:r.getVersion}},exports.startTracking=r,exports.stopTracking=o,exports.track=c;
@@ -0,0 +1,3 @@
1
+ export * from "./core";
2
+ export * from "./react";
3
+ export * from "./reaxor";
@@ -0,0 +1 @@
1
+ import{useRef as e,useSyncExternalStore as t}from"react";function n(){const e=new Set;let t=!1,n=0,r=!1,o=!1;function c(r){n++;for(const t of e)r(t);e.clear(),t=!1}return{add:function(t){e.add(t),e.add("__root__")},schedule:function(e){r?o=!0:t||(t=!0,queueMicrotask(()=>c(e)))},transaction:function(e,t){r=!0,e(),r=!1,o&&(o=!1,c(t))},getVersion:()=>n}}let r=null;function o(){return r=new Set,r}function c(){r=null}function u(e){r&&r.add(e)}function i(e){const t=new Set;let n=!1,r=0;function u(){n||(n=!0,queueMicrotask(i))}function i(){for(;t.size;){const e=Array.from(t).sort(s);t.clear();for(const t of e)f(t)}n=!1}function s(e,t){return e.depth!==t.depth?e.depth-t.depth:e.priority-t.priority}function f(t){if(t.disposed)return;if(t.running)return;if(t.runCount>100)return;t.runCount++,t.running=!0,t.deps.forEach(n=>{e.remove(n,t.scheduler)}),t.cleanup&&(t.cleanup(),t.cleanup=void 0);const n=o(),r=t.fn();c(),t.deps=n,t.deps.forEach(n=>{e.add(n,t.scheduler)}),"function"==typeof r&&(t.cleanup=r),t.running=!1}return{effect:function(o,c){const i={fn:o,deps:new Set,depth:c?.depth??100,priority:c?.priority??10,running:!1,runCount:0,disposed:!1,scheduler:()=>function(e){e.disposed||(e.sync?f(e):(t.add(e),n||0!==r||u()))}(i),sync:c?.sync??!1};return f(i),()=>{i.disposed||(i.disposed=!0,i.deps.forEach(t=>{e.remove(t,i.scheduler)}),i.cleanup&&i.cleanup(),i.deps.clear())}},batch:function(e){r++;try{e()}finally{r--,0===r&&u()}}}}function s(){const e=new Map;return{register:function(t,n){let r=e.get(t);return r||(r=new Set,e.set(t,r)),r.add(n),()=>{const r=e.get(t);r&&(r.delete(n),0===r.size&&e.delete(t))}},run:function(t,n,r){const o=e.get(t);o&&[...o].forEach(e=>e(n,r,t))}}}function f(){const e=new Map;return{add:function(t,n){let r=e.get(t);r||(r=new Set,e.set(t,r)),r.add(n)},remove:function(t,n){e.get(t)?.delete(n)},notify:function(t){e.get(t)?.forEach(e=>e())}}}function d(e){return"symbol"!=typeof e&&("__proto__"!==e&&"constructor"!==e)}function a(e){const{root:t,listeners:n,batch:r,effects:o,computed:c}=e,i=new WeakMap;function s(e,t,u){r.add(e),r.schedule(n.notify),o.run(e,u,t),c.invalidateComputed(e,e=>{r.add(e)})}const f=function e(t,n,r=""){if("object"!=typeof(o=t)||null===o)return t;var o;if(i.has(t))return i.get(t);const a=new Proxy(t,{get(t,o,i){if(!d(o))return Reflect.get(t,o,i);const a=r?`${r}.${o+""}`:o+"";if(""===r&&"string"==typeof o&&c.computedMap.has(o)){const e=c.computedMap.get(o);return c.evaluateComputed(e,f)}c.trackDependency(a),u(a);const p=Reflect.get(t,o,i);if(Array.isArray(t)&&"function"==typeof p){if(["push","pop","splice","shift","unshift","sort","reverse"].includes(o))return(...e)=>{const n=t.slice(),o=p.apply(t,e);return s(r,n,t),o}}return e(p,n,a)},set(e,t,n,o){if(!d(t))return Reflect.set(e,t,n,o);const c=r?`${r}.${t+""}`:t+"",u=e[t];if(Object.is(u,n))return!0;const i=Reflect.set(e,t,n,o);return s(c,u,n),i}});return i.set(t,a),a}(t);return{rootProxy:f}}function p(n,r,o){const c="__root__";function u(e){return r.add(c,e),()=>r.remove(c,e)}function i(r){const o=e();return t(u,()=>{const e=r(n);return Object.is(o.current,e)?o.current:(o.current=e,e)},()=>r(n))}return{useReactive:function(){return t(u,o,o),n},useSelector:i,useComputed:function(e){return i(e)}}}function l(){const e=new Map,t=new Map;let n=null;function r(e,t){n=e;const r=e.getter(t);return n=null,r}return{computedMap:e,register:function(t,n,r){if(e.has(t))throw Error(`Computed key "${t}" already exists`);const o={key:t,getter:n,cache:r?.cache??!1,value:void 0,dirty:!0,deps:new Set};e.set(t,o)},trackDependency:function(e){if(!n)return;n.deps.add(e);let r=t.get(e);r||(r=new Set,t.set(e,r)),r.add(n)},invalidateComputed:function(e,n){const r=t.get(e);if(r)for(const e of r)e.cache&&(e.dirty=!0),n(e.key)},evaluateComputed:function(e,n){if(!e.cache)return r(e,n);if(!e.dirty)return e.value;e.deps.forEach(n=>{t.get(n)?.delete(e)}),e.deps.clear();const o=r(e,n);return e.value=o,e.dirty=!1,e.value}}}function y(e){const t=f(),r=n(),o=s(),c=l(),{rootProxy:u}=a({root:e,listeners:t,batch:r,effects:o,computed:c}),{useReactive:d,useSelector:y}=p(u,t,r.getVersion),h=i(t);return{state:u,use:d,useSelector:y,effect:o.register,autoEffect:h.effect,computed:c.register,transaction:e=>r.transaction(e,t.notify),getVersion:r.getVersion}}export{n as createBatch,l as createComputed,i as createEffectRuntime,s as createEffects,f as createListeners,a as createProxy,p as createReactHooks,y as createReactive,o as startTracking,c as stopTracking,u as track};
@@ -0,0 +1,6 @@
1
+ import type { ListenerManager } from "../reaxor/types";
2
+ export declare function createReactHooks<T>(rootProxy: T, listeners: ListenerManager, getVersion: () => number): {
3
+ useReactive: () => T;
4
+ useSelector: <R>(selector: (state: T) => R) => R;
5
+ useComputed: <R>(compute: (state: T) => R) => R;
6
+ };
@@ -0,0 +1 @@
1
+ export * from "./hooks";
@@ -0,0 +1,8 @@
1
+ import type { ComputedNode, ComputedOptions, ReactiveProxy } from "./types";
2
+ export declare function createComputed<T extends object>(): {
3
+ computedMap: Map<string, ComputedNode>;
4
+ register: (key: string, getter: (state: ReactiveProxy<T>) => any, options?: ComputedOptions) => void;
5
+ trackDependency: (path: string) => void;
6
+ invalidateComputed: (path: string, trigger: (path: string) => void) => void;
7
+ evaluateComputed: (node: ComputedNode, state: ReactiveProxy<T>) => any;
8
+ };
@@ -0,0 +1,2 @@
1
+ import type { Store } from "./types";
2
+ export declare function createReactive<T extends object>(state: T): Store<T>;
@@ -0,0 +1,3 @@
1
+ export * from "./computed";
2
+ export * from "./createReactive";
3
+ export * from "./types";
@@ -0,0 +1,73 @@
1
+ export type Listener = () => void;
2
+ export type EffectFn<T = any> = (newValue: T, oldValue: T, path: string) => void;
3
+ export type EffectOptions = {
4
+ priority?: number;
5
+ depth?: number;
6
+ sync?: boolean;
7
+ };
8
+ export type ReactiveProxy<T> = T extends Array<infer U> ? ReactiveProxyArray<U> : T extends Map<infer K, infer V> ? ReactiveProxyMap<K, V> : T extends Set<infer U> ? ReactiveProxySet<U> : T extends object ? {
9
+ [K in keyof T]: ReactiveProxy<T[K]>;
10
+ } : T;
11
+ export interface ReactiveProxyArray<T> extends Array<ReactiveProxy<T>> {
12
+ }
13
+ export interface ReactiveProxyMap<K, V> extends Map<K, ReactiveProxy<V>> {
14
+ }
15
+ export interface ReactiveProxySet<T> extends Set<ReactiveProxy<T>> {
16
+ }
17
+ export type ComputedOptions = {
18
+ cache?: boolean;
19
+ async?: boolean;
20
+ };
21
+ export type ComputedNode<T = any> = {
22
+ key: string;
23
+ getter: (state: any) => T;
24
+ cache: boolean;
25
+ value: T;
26
+ dirty: boolean;
27
+ deps: Set<string>;
28
+ };
29
+ export interface ListenerManager {
30
+ add(path: string, listener: Listener): void;
31
+ remove(path: string, listener: Listener): void;
32
+ notify(path: string): void;
33
+ }
34
+ export interface BatchManager {
35
+ add(path: string): void;
36
+ schedule(notify: (path: string) => void): void;
37
+ transaction(fn: Listener, notify: (path: string) => void): void;
38
+ getVersion(): number;
39
+ }
40
+ export interface EffectManager {
41
+ register(path: string, fn: EffectFn): void;
42
+ run(path: string, newValue: any, oldValue: any): void;
43
+ }
44
+ export interface ComputedManager<T extends object> {
45
+ computedMap: Map<string, ComputedNode>;
46
+ register(key: string, getter: (state: ReactiveProxy<T>) => any, options?: ComputedOptions): void;
47
+ trackDependency(path: string): void;
48
+ invalidateComputed(path: string, trigger: (path: string) => void): void;
49
+ evaluateComputed(node: ComputedNode, state: ReactiveProxy<T>): any;
50
+ }
51
+ export interface ProxyOptions<T extends object> {
52
+ root: T;
53
+ listeners: ListenerManager;
54
+ batch: BatchManager;
55
+ effects: EffectManager;
56
+ computed: ComputedManager<T>;
57
+ }
58
+ export type Selector<T, R> = (state: T) => R;
59
+ export interface Store<T extends object> {
60
+ state: ReactiveProxy<T>;
61
+ use(): ReactiveProxy<T>;
62
+ useSelector<R>(selector: (state: ReactiveProxy<T>) => R): R;
63
+ computed<K extends string, V>(key: K, getter: (state: ReactiveProxy<T>) => V, options?: {
64
+ cache?: boolean;
65
+ }): void;
66
+ effect<K extends keyof T & string>(key: K, callback: (newVal: T[K], oldVal: T[K]) => void): Listener;
67
+ autoEffect(fn: Listener, options?: {
68
+ priority?: number;
69
+ depth?: number;
70
+ }): Listener;
71
+ transaction(fn: Listener): void;
72
+ getVersion(): number;
73
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "reazor-react",
3
+ "version": "0.0.1",
4
+ "description": "Fine-grained Solid-style reactivity for React 18+",
5
+ "license": "MIT",
6
+ "author": "Delpi.Kye",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+
10
+ "main": "build/index.cjs.js",
11
+ "module": "build/index.esm.js",
12
+ "types": "build/index.d.ts",
13
+
14
+ "exports": {
15
+ ".": {
16
+ "types": "./build/index.d.ts",
17
+ "import": "./build/index.esm.js",
18
+ "require": "./build/index.cjs.js"
19
+ }
20
+ },
21
+
22
+ "files": [
23
+ "build"
24
+ ],
25
+
26
+ "scripts": {
27
+ "clean": "rimraf build",
28
+ "build": "rollup -c",
29
+ "cb": "npm run clean && npm run build",
30
+ "prepublishOnly": "npm run cb"
31
+ },
32
+
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/delpikye-v/reazor-react.git"
36
+ },
37
+
38
+ "homepage": "https://github.com/delpikye-v/reazor-react#readme",
39
+
40
+ "bugs": {
41
+ "url": "https://github.com/delpikye-v/reazor-react/issues"
42
+ },
43
+
44
+ "funding": {
45
+ "type": "github",
46
+ "url": "https://github.com/sponsors/delpikye-v"
47
+ },
48
+
49
+ "keywords": [
50
+ "react",
51
+ "react-18",
52
+ "react-state",
53
+ "react-solid",
54
+ "react-store",
55
+ "fine-grained",
56
+ "proxy-state",
57
+ "reactive",
58
+ "reactivity",
59
+ "map-support",
60
+ "set-support",
61
+ "concurrent-safe",
62
+ "external-store",
63
+ "computed-state",
64
+ "derived-state",
65
+ "state-management",
66
+ "solid-style"
67
+ ],
68
+
69
+ "peerDependencies": {
70
+ "react": ">=18.0.0"
71
+ },
72
+
73
+ "devDependencies": {
74
+ "@rollup/plugin-commonjs": "^25.0.0",
75
+ "@rollup/plugin-node-resolve": "^15.2.3",
76
+ "@rollup/plugin-terser": "^0.4.4",
77
+ "@types/react": "^18.2.43",
78
+ "react": "^18.2.0",
79
+ "rimraf": "^5.0.5",
80
+ "rollup": "^4.9.6",
81
+ "rollup-plugin-peer-deps-external": "^2.2.4",
82
+ "rollup-plugin-typescript2": "^0.36.0",
83
+ "tslib": "^2.6.2",
84
+ "typescript": "^5.3.3"
85
+ }
86
+ }