logic-runtime-react-z 3.1.1 → 3.1.2
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 +76 -17
- package/build/core/effect.d.ts +4 -0
- package/build/index.cjs.js +1 -1
- package/build/index.esm.js +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A headless, deterministic, intent-driven runtime for frontend & backend logic.
|
|
|
10
10
|
React components stay pure. Business logic is fully testable, replayable, and framework-agnostic.
|
|
11
11
|
|
|
12
12
|
> **Intent is the only entry point.**
|
|
13
|
-
> **React is optional. createLogic is the product. Everything else is an adapter.**
|
|
13
|
+
> **React is optional. `createLogic` is the product. Everything else is an adapter.**
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -274,7 +274,7 @@ async function run() {
|
|
|
274
274
|
run()
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
✔ Same runtime, same behavior, no React involved.
|
|
277
|
+
✔ Same runtime, same behavior, no React involved.
|
|
278
278
|
✔ No React
|
|
279
279
|
✔ Replayable
|
|
280
280
|
|
|
@@ -367,7 +367,7 @@ import { useLogicSelector } from "logic-runtime-react-z"
|
|
|
367
367
|
|
|
368
368
|
function CountLabel() {
|
|
369
369
|
const count = useLogicSelector(
|
|
370
|
-
counterLogic
|
|
370
|
+
counterLogic,
|
|
371
371
|
state => state.count
|
|
372
372
|
)
|
|
373
373
|
|
|
@@ -432,20 +432,45 @@ expect(runtime.computed.squared).toBe(16)
|
|
|
432
432
|
|
|
433
433
|
---
|
|
434
434
|
|
|
435
|
-
## 🔍 Comparison
|
|
435
|
+
## 🔍 Comparison
|
|
436
|
+
|
|
437
|
+
| Criteria | logic-runtime-react-z | Redux | Zustand | Recoil | MobX |
|
|
438
|
+
| ----------------------------------------- | :---------------------: | :------------------: | :-------------------: | :----------------------------: | :-------------------------: |
|
|
439
|
+
| **Intent-first model** | ✅ | ❌ | ❌ | ⚠️ | ⚠️ |
|
|
440
|
+
| **State-first model** | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
441
|
+
| **First-class effect orchestration** | ✅ (built-in) | ⚠️ (via middleware) | ⚠️ (via middleware) | ⚠️ (selectors + async helpers) | ⚠️ (actions + reactions) |
|
|
442
|
+
| **Fine-grained derived state (computed)** | ✅ (reactive & cached) | ❌ | ⚠️ (simple selectors) | ✅ (dependency graph) | ⚠️ (observable derivations) |
|
|
443
|
+
| **Predictable execution semantics** | 🔁 intent queue/rules | 👍 sync + middleware | 👍 sync | 👍 sync | 👍 sync |
|
|
444
|
+
| **Async control strategies built-in** | ✅ takeLatest / debounce | ❌ | ❌ | ❌ | ❌ |
|
|
445
|
+
| **Logic outside React** | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
|
|
446
|
+
| **Framework-agnostic** | ✅ | ⚫ | ⚫ | ⚫ | ⚫ |
|
|
447
|
+
| **Backend-ready usage** | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
|
|
448
|
+
| **Type-inferred actions** | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
|
|
449
|
+
| **Minimal re-render strategies** | ✓ selectors/hooks | ✓ selectors | ✓ selectors | ✓ atoms/selectors | ✗ global observables |
|
|
450
|
+
| **Devtools ecosystem** | ⚠️ (nascent) | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
<b>⚠️ via selectors, not a true dependency graph. </b>
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## ❓ Why not Redux + RTK?
|
|
459
|
+
|
|
460
|
+
Redux focuses on state transitions.
|
|
436
461
|
|
|
437
|
-
|
|
438
|
-
|--------------------------|:---------------------:|:-----:|:-------:|
|
|
439
|
-
| Intent-first model | ✅ | ❌ | ❌ |
|
|
440
|
-
| State-first model | ❌ | ✅ | ✅ |
|
|
441
|
-
| First-class effects | ✅ | ❌ | ❌ |
|
|
442
|
-
| Computed graph | ✅ | ❌ | ⚠️ |
|
|
443
|
-
| Deterministic execution | ✅ | ❌ | ❌ |
|
|
444
|
-
| Logic outside React | ✅ | ❌ | ❌ |
|
|
445
|
-
| Backend-safe | ✅ | ❌ | ❌ |
|
|
462
|
+
logic-runtime-react-z models system behavior.
|
|
446
463
|
|
|
464
|
+
In Redux:
|
|
465
|
+
- async flow is external (thunk/saga)
|
|
466
|
+
- effects are not first-class
|
|
467
|
+
- execution model depends on middleware setup
|
|
468
|
+
|
|
469
|
+
In logic-runtime-react-z:
|
|
470
|
+
- async orchestration is built-in
|
|
471
|
+
- intent is the only entry point
|
|
472
|
+
- execution order is guaranteed
|
|
447
473
|
|
|
448
|
-
##### ⚠️ via selectors, not a true dependency graph
|
|
449
474
|
|
|
450
475
|
---
|
|
451
476
|
|
|
@@ -456,15 +481,49 @@ expect(runtime.computed.squared).toBe(16)
|
|
|
456
481
|
|
|
457
482
|
---
|
|
458
483
|
|
|
459
|
-
## 🧬
|
|
484
|
+
## 🧬 Deterministic Execution Model
|
|
460
485
|
|
|
461
486
|
- Intents are processed sequentially
|
|
462
487
|
- State mutations are isolated
|
|
463
|
-
- Async
|
|
464
|
-
-
|
|
488
|
+
- Async effects follow declared strategies (takeLatest, debounce, etc.)
|
|
489
|
+
- Execution order is predictable
|
|
490
|
+
|
|
491
|
+
Given the same intent sequence, the resulting state is reproducible.
|
|
465
492
|
|
|
466
493
|
---
|
|
467
494
|
|
|
495
|
+
## 📐 Architecture Diagram (High-level)
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
┌─────────────┐
|
|
499
|
+
│ React UI │
|
|
500
|
+
└──────┬──────┘
|
|
501
|
+
│ adapter
|
|
502
|
+
┌──────▼──────┐
|
|
503
|
+
│ Runtime │
|
|
504
|
+
│ (create) │
|
|
505
|
+
├─────────────┤
|
|
506
|
+
│ Intent Bus │
|
|
507
|
+
│ Effects │
|
|
508
|
+
│ Handlers │
|
|
509
|
+
│ State │
|
|
510
|
+
│ Computed │
|
|
511
|
+
└─────────────┘
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
<b>Runtime is the product. React is an adapter.</b>
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## ⚠️ When Not to Use
|
|
519
|
+
|
|
520
|
+
- Simple local component state
|
|
521
|
+
- Small apps without async complexity
|
|
522
|
+
- Teams unfamiliar with event-driven models
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
|
|
468
527
|
## License
|
|
469
528
|
|
|
470
529
|
MIT
|
package/build/core/effect.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type EffectStrategy = "default" | "takeLatest" | "debounce";
|
|
|
2
2
|
export type EffectHandler<S = any> = (context: S) => void | Promise<void>;
|
|
3
3
|
export type EffectDef<S = any> = {
|
|
4
4
|
_kind: "effect";
|
|
5
|
+
id: symbol;
|
|
5
6
|
handler: EffectHandler<S>;
|
|
6
7
|
strategy: EffectStrategy;
|
|
7
8
|
wait: number;
|
|
@@ -11,6 +12,7 @@ export declare function effect<S = any>(fn: EffectHandler<S>): {
|
|
|
11
12
|
takeLatest(): any;
|
|
12
13
|
debounce(ms: number): any;
|
|
13
14
|
_kind: "effect";
|
|
15
|
+
id: symbol;
|
|
14
16
|
handler: EffectHandler<S>;
|
|
15
17
|
strategy: EffectStrategy;
|
|
16
18
|
wait: number;
|
|
@@ -19,11 +21,13 @@ export declare function effect<S = any>(fn: EffectHandler<S>): {
|
|
|
19
21
|
takeLatest(): any;
|
|
20
22
|
debounce(ms: number): any;
|
|
21
23
|
_kind: "effect";
|
|
24
|
+
id: symbol;
|
|
22
25
|
handler: EffectHandler<S>;
|
|
23
26
|
strategy: EffectStrategy;
|
|
24
27
|
wait: number;
|
|
25
28
|
};
|
|
26
29
|
_kind: "effect";
|
|
30
|
+
id: symbol;
|
|
27
31
|
handler: EffectHandler<S>;
|
|
28
32
|
strategy: EffectStrategy;
|
|
29
33
|
wait: number;
|
package/build/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("chrono-state-z"),e=require("intentx-core-z"),s=require("react/jsx-runtime"),n=require("react");function o(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(s){if("default"!==s){var n=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,n.get?n:{enumerable:!0,get:function(){return t[s]}})}}),e.default=t,Object.freeze(e)}var r=o(n);class i{constructor(){this.handlers={},this.effects={},this.middlewares=[]}use(t){this.middlewares.push(t)}effect(t,e){var s,n;(null!==(s=(n=this.effects)[t])&&void 0!==s?s:n[t]=[]).push(e)}on(t,e){var s,n;const o=this.handlers[t];o&&o.length>0?console.warn(`[IntentBus] Duplicate intent handler "${t}" detected. Only the first handler will be used.`):(null!==(s=(n=this.handlers)[t])&&void 0!==s?s:n[t]=[]).push(e)}async emit(t,e){var s,n;const o=null!==(s=this.handlers[t])&&void 0!==s?s:[],r=null!==(n=this.effects[t])&&void 0!==n?n:[],i=(c=this.middlewares,a=async t=>{t.effects=r;for(const e of o)await e(t)},c.reduceRight((t,e)=>e(t),a));var c,a;await i(e)}}class c{constructor(t,s=e.createScope("logic")){this.computedAtoms={},this.subs=new Set,this.bus=new i,this.dirty=!0,this.isComputing=!1,this.computedKeys=new Set,this.markDirty=()=>{this.dirty=!0,this.subs.forEach(t=>t())},this.getSnapshot=()=>(this.dirty&&(this.snapshotCache=this.buildSnapshot(),this.dirty=!1),this.snapshotCache),this.subscribe=t=>(this.subs.add(t),()=>this.subs.delete(t)),this.onIntent=(t,e)=>{this.bus.on(t,e)},this.emit=async(t,e)=>{const s=new AbortController;await this.bus.emit(t,{payload:e,scope:this.scope,signal:s.signal,state:this.getSnapshot,setState:t=>this.setStateInternal(t),emit:this.emit})},this.scope=s,this.stateAtoms=this.createStateAtoms(t);for(const t in this.stateAtoms)this.stateAtoms[t].subscribe(this.markDirty);this.bus.use(function(){const t=new Map,e=new Map;return s=>async n=>{var o;const r=n.effects;if(!(null==r?void 0:r.length))return s(n);for(const s of r){const r=s.
|
|
1
|
+
"use strict";var t=require("chrono-state-z"),e=require("intentx-core-z"),s=require("react/jsx-runtime"),n=require("react");function o(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(s){if("default"!==s){var n=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,n.get?n:{enumerable:!0,get:function(){return t[s]}})}}),e.default=t,Object.freeze(e)}var r=o(n);class i{constructor(){this.handlers={},this.effects={},this.middlewares=[]}use(t){this.middlewares.push(t)}effect(t,e){var s,n;(null!==(s=(n=this.effects)[t])&&void 0!==s?s:n[t]=[]).push(e)}on(t,e){var s,n;const o=this.handlers[t];o&&o.length>0?console.warn(`[IntentBus] Duplicate intent handler "${t}" detected. Only the first handler will be used.`):(null!==(s=(n=this.handlers)[t])&&void 0!==s?s:n[t]=[]).push(e)}async emit(t,e){var s,n;const o=null!==(s=this.handlers[t])&&void 0!==s?s:[],r=null!==(n=this.effects[t])&&void 0!==n?n:[],i=(c=this.middlewares,a=async t=>{t.effects=r;for(const e of o)await e(t)},c.reduceRight((t,e)=>e(t),a));var c,a;await i(e)}}class c{constructor(t,s=e.createScope("logic")){this.computedAtoms={},this.subs=new Set,this.bus=new i,this.dirty=!0,this.isComputing=!1,this.computedKeys=new Set,this.markDirty=()=>{this.dirty=!0,this.subs.forEach(t=>t())},this.getSnapshot=()=>(this.dirty&&(this.snapshotCache=this.buildSnapshot(),this.dirty=!1),this.snapshotCache),this.subscribe=t=>(this.subs.add(t),()=>this.subs.delete(t)),this.onIntent=(t,e)=>{this.bus.on(t,e)},this.emit=async(t,e)=>{const s=new AbortController;await this.bus.emit(t,{payload:e,scope:this.scope,signal:s.signal,state:this.getSnapshot,setState:t=>this.setStateInternal(t),emit:this.emit})},this.scope=s,this.stateAtoms=this.createStateAtoms(t);for(const t in this.stateAtoms)this.stateAtoms[t].subscribe(this.markDirty);this.bus.use(function(){const t=new Map,e=new Map;return s=>async n=>{var o;const r=n.effects;if(!(null==r?void 0:r.length))return s(n);for(const s of r){const r=s.id;if("takeLatest"===s.strategy){null===(o=t.get(r))||void 0===o||o.abort();const e=new AbortController;t.set(r,e),await s.handler({...n,signal:e.signal})}else"debounce"===s.strategy?(clearTimeout(e.get(r)),e.set(r,setTimeout(()=>s.handler(n),s.wait))):await s.handler(n)}await s(n)}}())}createStateAtoms(e){const s={};for(const n in e)s[n]=t.atom(e[n]);return s}buildSnapshot(){const t={};for(const e in this.stateAtoms)t[e]=this.stateAtoms[e]();for(const e in this.computedAtoms)t[e]=this.computedAtoms[e]();return t}createReactiveState(){return new Proxy({},{get:(t,e)=>{const s=this.stateAtoms[e];return s?s():void 0}})}get state(){return this.getSnapshot()}get computed(){const t={};return this.computedKeys.forEach(e=>{t[e]=this.computedAtoms[e]()}),t}getComputedKey(t){return this.computedAtoms[t]()}getComputed(t){const e={};return this.computedKeys.forEach(s=>{e[s]=t[s]}),e}setStateInternal(t){this.isComputing&&console.warn("[logic-runtime] setState() called inside computed()");const e={};for(const t in this.stateAtoms)e[t]=this.stateAtoms[t]();t(e);for(const t in this.stateAtoms)e[t]!==this.stateAtoms[t]()&&this.stateAtoms[t].set(e[t])}useEffect(t,e){this.bus.effect(t,e)}attachComputed(e,s){const n=t.atom(void 0),o=this.createReactiveState();this.computedAtoms[e]=n,this.computedKeys.add(e),t.effect(()=>{this.isComputing=!0,n.set(s({state:o})),this.isComputing=!1}),n.subscribe(this.markDirty)}}function a(t){const e=function(){let t=0,e=[];return{get records(){return e.slice()},record:function(s){e.push({...s,id:++t,state:structuredClone(s.state)})},replay:async function(t,s){const{from:n=0,to:o=1/0,scope:r}=null!=s?s:{},i=e.filter(t=>"emit"===t.type&&t.id>=n&&t.id<=o&&(!r||t.scope===r));for(const e of i){const s=t(e.intent,e.payload);s instanceof Promise&&await s}},clear:function(){e=[],t=0}}}();function s(){return"string"==typeof t.scope?t.scope:t.scope.name}return{timeline:e,wrap:function(){const n=t.emit.bind(t);t.emit=async(o,r)=>{e.record({type:"emit:start",intent:o,payload:r,scope:s(),state:t.getSnapshot(),timestamp:Date.now()});try{const t=n(o,r);t instanceof Promise&&await t}finally{e.record({type:"emit:end",intent:o,payload:r,scope:s(),state:t.getSnapshot(),timestamp:Date.now()})}}}}}function u(t){var s;const o=n.useRef(null),r=t&&"function"==typeof t.subscribe&&"function"==typeof t.getSnapshot?t:null!==(s=o.current)&&void 0!==s?s:o.current=t.create(e.createScope("react"));return n.useSyncExternalStore(r.subscribe,r.getSnapshot,r.getSnapshot)}Object.defineProperty(exports,"createSelector",{enumerable:!0,get:function(){return t.createSelector}}),exports.LogicRuntime=c,exports.attachDevtools=a,exports.composeLogic=function(t){const s=e.createScope("logic"),n={};for(const e in t)n[e]=t[e].create(s);const o={};for(const t in n)o[t]=n[t].actions;return{scope:s,runtimes:n,emit:async(t,e)=>{await Promise.all(Object.values(n).map(s=>s.emit(t,e)))},actions:o,getState:()=>{const t={};for(const e in n)t[e]=n[e].state;return t}}},exports.createBackendRuntime=function(t){var s,n;let o=structuredClone(t);const r=()=>o,i=t=>{o={...o,...t}},c=e.createScope("backend");async function u(t,e){await l.emit(t,e,c)}const l=e.createIntentBus(t=>{const e=new AbortController;return{scope:c,payload:t,signal:e.signal,get state(){return r()},setState(){throw new Error("setState is not allowed in backend runtime")},emit:u}}),h={state:r,reset:()=>{o=structuredClone(t)},emit:u,registerIntents:function(t){for(const e in t){const s=t[e];l.on(e,async(t,e)=>{var n;const o={get state(){return r()},signal:null!==(n=t.signal)&&void 0!==n?n:(new AbortController).signal,set:i,emit:u};await s(o)},c)}},onIntent:l.on,effect:l.effect};if("production"!==(null===(n=null===(s=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===s?void 0:s.env)||void 0===n?void 0:n.NODE_ENV)){const t=a(h);t.wrap(h),h.devtools=t}return h},exports.createLogic=function(t){return{name:t.name,create(e){var s;const n=new c(structuredClone(t.state),e);if(t.computed)for(const e in t.computed)n.attachComputed(e,t.computed[e]);null===(s=t.intents)||void 0===s||s.call(t,{on:n.onIntent,effect:n.useEffect.bind(n)});const o={};if(t.actions)for(const e in t.actions)o[e]=t.actions[e]({emit:n.emit,getState:n.getSnapshot});return n.actions=o,n}}},exports.effect=function(t){const e={_kind:"effect",id:Symbol("effect"),handler:t,strategy:"default",wait:0};return{...e,takeLatest(){return e.strategy="takeLatest",this},debounce(t){return e.strategy="debounce",e.wait=t,this}}},exports.useActions=function(t){return(t instanceof c?t:u(t).__runtime).actions},exports.useComputed=function(e,s){const o=e instanceof c?e:u(e).__runtime;if(!s)return n.useSyncExternalStore(o.subscribe,()=>o.getComputed(o.getSnapshot()),()=>o.getComputed(o.getSnapshot()));const r=n.useMemo(()=>t.createSelector(s),[s]);return n.useSyncExternalStore(o.subscribe,()=>r(o.getComputed(o.getSnapshot())),()=>r(o.getComputed(o.getSnapshot())))},exports.useLogic=function(t){const e=n.useSyncExternalStore(t.subscribe,t.getSnapshot,t.getSnapshot);return{state:e,computed:t.getComputed(e),actions:t.actions}},exports.useLogicSelector=function(e,s){const o=n.useMemo(()=>t.createSelector(s),[s]);return n.useSyncExternalStore(e.subscribe,()=>o(e.getSnapshot()),()=>o(e.getSnapshot()))},exports.useRuntime=u,exports.withLogic=function(t,n,o){var i,c;const a=i=>{const c=r.useRef(null);c.current||(c.current=t.create("string"==typeof o?e.createScope(o):o));const a=c.current,u=r.useSyncExternalStore(a.subscribe,a.getSnapshot,a.getSnapshot),l=r.useCallback((t,e)=>a.emit(t,e),[a]),h=r.useMemo(()=>({state:u,actions:a.actions,emit:l}),[u,l,a]);return s.jsx(n,{...i,...h})};return a.displayName=`withLogic(${null!==(c=null!==(i=n.displayName)&&void 0!==i?i:n.name)&&void 0!==c?c:"View"})`,a};
|
package/build/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{atom as t,effect as e,createSelector as s}from"chrono-state-z";export{createSelector}from"chrono-state-z";import{createScope as n,createIntentBus as o}from"intentx-core-z";import{jsx as i}from"react/jsx-runtime";import*as r from"react";import{useRef as a,useSyncExternalStore as c,useMemo as u}from"react";class l{constructor(){this.handlers={},this.effects={},this.middlewares=[]}use(t){this.middlewares.push(t)}effect(t,e){var s,n;(null!==(s=(n=this.effects)[t])&&void 0!==s?s:n[t]=[]).push(e)}on(t,e){var s,n;const o=this.handlers[t];o&&o.length>0?console.warn(`[IntentBus] Duplicate intent handler "${t}" detected. Only the first handler will be used.`):(null!==(s=(n=this.handlers)[t])&&void 0!==s?s:n[t]=[]).push(e)}async emit(t,e){var s,n;const o=null!==(s=this.handlers[t])&&void 0!==s?s:[],i=null!==(n=this.effects[t])&&void 0!==n?n:[],r=(a=this.middlewares,c=async t=>{t.effects=i;for(const e of o)await e(t)},a.reduceRight((t,e)=>e(t),c));var a,c;await r(e)}}class h{constructor(t,e=n("logic")){this.computedAtoms={},this.subs=new Set,this.bus=new l,this.dirty=!0,this.isComputing=!1,this.computedKeys=new Set,this.markDirty=()=>{this.dirty=!0,this.subs.forEach(t=>t())},this.getSnapshot=()=>(this.dirty&&(this.snapshotCache=this.buildSnapshot(),this.dirty=!1),this.snapshotCache),this.subscribe=t=>(this.subs.add(t),()=>this.subs.delete(t)),this.onIntent=(t,e)=>{this.bus.on(t,e)},this.emit=async(t,e)=>{const s=new AbortController;await this.bus.emit(t,{payload:e,scope:this.scope,signal:s.signal,state:this.getSnapshot,setState:t=>this.setStateInternal(t),emit:this.emit})},this.scope=e,this.stateAtoms=this.createStateAtoms(t);for(const t in this.stateAtoms)this.stateAtoms[t].subscribe(this.markDirty);this.bus.use(function(){const t=new Map,e=new Map;return s=>async n=>{var o;const i=n.effects;if(!(null==i?void 0:i.length))return s(n);for(const s of i){const i=s.
|
|
1
|
+
import{atom as t,effect as e,createSelector as s}from"chrono-state-z";export{createSelector}from"chrono-state-z";import{createScope as n,createIntentBus as o}from"intentx-core-z";import{jsx as i}from"react/jsx-runtime";import*as r from"react";import{useRef as a,useSyncExternalStore as c,useMemo as u}from"react";class l{constructor(){this.handlers={},this.effects={},this.middlewares=[]}use(t){this.middlewares.push(t)}effect(t,e){var s,n;(null!==(s=(n=this.effects)[t])&&void 0!==s?s:n[t]=[]).push(e)}on(t,e){var s,n;const o=this.handlers[t];o&&o.length>0?console.warn(`[IntentBus] Duplicate intent handler "${t}" detected. Only the first handler will be used.`):(null!==(s=(n=this.handlers)[t])&&void 0!==s?s:n[t]=[]).push(e)}async emit(t,e){var s,n;const o=null!==(s=this.handlers[t])&&void 0!==s?s:[],i=null!==(n=this.effects[t])&&void 0!==n?n:[],r=(a=this.middlewares,c=async t=>{t.effects=i;for(const e of o)await e(t)},a.reduceRight((t,e)=>e(t),c));var a,c;await r(e)}}class h{constructor(t,e=n("logic")){this.computedAtoms={},this.subs=new Set,this.bus=new l,this.dirty=!0,this.isComputing=!1,this.computedKeys=new Set,this.markDirty=()=>{this.dirty=!0,this.subs.forEach(t=>t())},this.getSnapshot=()=>(this.dirty&&(this.snapshotCache=this.buildSnapshot(),this.dirty=!1),this.snapshotCache),this.subscribe=t=>(this.subs.add(t),()=>this.subs.delete(t)),this.onIntent=(t,e)=>{this.bus.on(t,e)},this.emit=async(t,e)=>{const s=new AbortController;await this.bus.emit(t,{payload:e,scope:this.scope,signal:s.signal,state:this.getSnapshot,setState:t=>this.setStateInternal(t),emit:this.emit})},this.scope=e,this.stateAtoms=this.createStateAtoms(t);for(const t in this.stateAtoms)this.stateAtoms[t].subscribe(this.markDirty);this.bus.use(function(){const t=new Map,e=new Map;return s=>async n=>{var o;const i=n.effects;if(!(null==i?void 0:i.length))return s(n);for(const s of i){const i=s.id;if("takeLatest"===s.strategy){null===(o=t.get(i))||void 0===o||o.abort();const e=new AbortController;t.set(i,e),await s.handler({...n,signal:e.signal})}else"debounce"===s.strategy?(clearTimeout(e.get(i)),e.set(i,setTimeout(()=>s.handler(n),s.wait))):await s.handler(n)}await s(n)}}())}createStateAtoms(e){const s={};for(const n in e)s[n]=t(e[n]);return s}buildSnapshot(){const t={};for(const e in this.stateAtoms)t[e]=this.stateAtoms[e]();for(const e in this.computedAtoms)t[e]=this.computedAtoms[e]();return t}createReactiveState(){return new Proxy({},{get:(t,e)=>{const s=this.stateAtoms[e];return s?s():void 0}})}get state(){return this.getSnapshot()}get computed(){const t={};return this.computedKeys.forEach(e=>{t[e]=this.computedAtoms[e]()}),t}getComputedKey(t){return this.computedAtoms[t]()}getComputed(t){const e={};return this.computedKeys.forEach(s=>{e[s]=t[s]}),e}setStateInternal(t){this.isComputing&&console.warn("[logic-runtime] setState() called inside computed()");const e={};for(const t in this.stateAtoms)e[t]=this.stateAtoms[t]();t(e);for(const t in this.stateAtoms)e[t]!==this.stateAtoms[t]()&&this.stateAtoms[t].set(e[t])}useEffect(t,e){this.bus.effect(t,e)}attachComputed(s,n){const o=t(void 0),i=this.createReactiveState();this.computedAtoms[s]=o,this.computedKeys.add(s),e(()=>{this.isComputing=!0,o.set(n({state:i})),this.isComputing=!1}),o.subscribe(this.markDirty)}}function m(t){const e={_kind:"effect",id:Symbol("effect"),handler:t,strategy:"default",wait:0};return{...e,takeLatest(){return e.strategy="takeLatest",this},debounce(t){return e.strategy="debounce",e.wait=t,this}}}function d(t){return{name:t.name,create(e){var s;const n=new h(structuredClone(t.state),e);if(t.computed)for(const e in t.computed)n.attachComputed(e,t.computed[e]);null===(s=t.intents)||void 0===s||s.call(t,{on:n.onIntent,effect:n.useEffect.bind(n)});const o={};if(t.actions)for(const e in t.actions)o[e]=t.actions[e]({emit:n.emit,getState:n.getSnapshot});return n.actions=o,n}}}function p(t){const e=n("logic"),s={};for(const n in t)s[n]=t[n].create(e);const o={};for(const t in s)o[t]=s[t].actions;return{scope:e,runtimes:s,emit:async(t,e)=>{await Promise.all(Object.values(s).map(s=>s.emit(t,e)))},actions:o,getState:()=>{const t={};for(const e in s)t[e]=s[e].state;return t}}}function f(t){const e=function(){let t=0,e=[];return{get records(){return e.slice()},record:function(s){e.push({...s,id:++t,state:structuredClone(s.state)})},replay:async function(t,s){const{from:n=0,to:o=1/0,scope:i}=null!=s?s:{},r=e.filter(t=>"emit"===t.type&&t.id>=n&&t.id<=o&&(!i||t.scope===i));for(const e of r){const s=t(e.intent,e.payload);s instanceof Promise&&await s}},clear:function(){e=[],t=0}}}();function s(){return"string"==typeof t.scope?t.scope:t.scope.name}return{timeline:e,wrap:function(){const n=t.emit.bind(t);t.emit=async(o,i)=>{e.record({type:"emit:start",intent:o,payload:i,scope:s(),state:t.getSnapshot(),timestamp:Date.now()});try{const t=n(o,i);t instanceof Promise&&await t}finally{e.record({type:"emit:end",intent:o,payload:i,scope:s(),state:t.getSnapshot(),timestamp:Date.now()})}}}}}function g(t){var e,s;let i=structuredClone(t);const r=()=>i,a=t=>{i={...i,...t}},c=n("backend");async function u(t,e){await l.emit(t,e,c)}const l=o(t=>{const e=new AbortController;return{scope:c,payload:t,signal:e.signal,get state(){return r()},setState(){throw new Error("setState is not allowed in backend runtime")},emit:u}});const h={state:r,reset:()=>{i=structuredClone(t)},emit:u,registerIntents:function(t){for(const e in t){const s=t[e];l.on(e,async(t,e)=>{var n;const o={get state(){return r()},signal:null!==(n=t.signal)&&void 0!==n?n:(new AbortController).signal,set:a,emit:u};await s(o)},c)}},onIntent:l.on,effect:l.effect};if("production"!==(null===(s=null===(e=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===e?void 0:e.env)||void 0===s?void 0:s.NODE_ENV)){const t=f(h);t.wrap(h),h.devtools=t}return h}function b(t,e,s){var o,a;const c=o=>{const a=r.useRef(null);a.current||(a.current=t.create("string"==typeof s?n(s):s));const c=a.current,u=r.useSyncExternalStore(c.subscribe,c.getSnapshot,c.getSnapshot),l=r.useCallback((t,e)=>c.emit(t,e),[c]),h=r.useMemo(()=>({state:u,actions:c.actions,emit:l}),[u,l,c]);return i(e,{...o,...h})};return c.displayName=`withLogic(${null!==(a=null!==(o=e.displayName)&&void 0!==o?o:e.name)&&void 0!==a?a:"View"})`,c}function y(t){var e;const s=a(null),o=t&&"function"==typeof t.subscribe&&"function"==typeof t.getSnapshot?t:null!==(e=s.current)&&void 0!==e?e:s.current=t.create(n("react"));return c(o.subscribe,o.getSnapshot,o.getSnapshot)}function w(t){return(t instanceof h?t:y(t).__runtime).actions}function S(t,e){const n=t instanceof h?t:y(t).__runtime;if(!e)return c(n.subscribe,()=>n.getComputed(n.getSnapshot()),()=>n.getComputed(n.getSnapshot()));const o=u(()=>s(e),[e]);return c(n.subscribe,()=>o(n.getComputed(n.getSnapshot())),()=>o(n.getComputed(n.getSnapshot())))}function v(t){const e=c(t.subscribe,t.getSnapshot,t.getSnapshot);return{state:e,computed:t.getComputed(e),actions:t.actions}}function C(t,e){const n=u(()=>s(e),[e]);return c(t.subscribe,()=>n(t.getSnapshot()),()=>n(t.getSnapshot()))}export{h as LogicRuntime,f as attachDevtools,p as composeLogic,g as createBackendRuntime,d as createLogic,m as effect,w as useActions,S as useComputed,v as useLogic,C as useLogicSelector,y as useRuntime,b as withLogic};
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logic-runtime-react-z",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "Intent-first business logic runtime.
|
|
3
|
+
"version": "3.1.2",
|
|
4
|
+
"description": "Intent-first business logic runtime. Deterministic, headless, and framework-agnostic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Delpi.Kye",
|
|
7
7
|
"sideEffects": false,
|
|
8
|
+
|
|
8
9
|
"main": "build/index.cjs.js",
|
|
9
10
|
"module": "build/index.esm.js",
|
|
10
11
|
"types": "build/index.d.ts",
|
|
@@ -28,12 +29,14 @@
|
|
|
28
29
|
"files": [
|
|
29
30
|
"build"
|
|
30
31
|
],
|
|
32
|
+
|
|
31
33
|
"scripts": {
|
|
32
34
|
"clean": "rimraf build",
|
|
33
35
|
"build": "rollup -c",
|
|
34
36
|
"cb": "npm run clean && npm run build",
|
|
35
37
|
"prepublishOnly": "npm run cb"
|
|
36
38
|
},
|
|
39
|
+
|
|
37
40
|
"repository": {
|
|
38
41
|
"type": "git",
|
|
39
42
|
"url": "https://github.com/delpikye-v/logic-runtime-react.git"
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
"bugs": {
|
|
43
46
|
"url": "https://github.com/delpikye-v/logic-runtime-react/issues"
|
|
44
47
|
},
|
|
48
|
+
|
|
45
49
|
"keywords": [
|
|
46
50
|
"intent-first",
|
|
47
51
|
"intent-runtime",
|
|
@@ -60,6 +64,7 @@
|
|
|
60
64
|
"takeLatest",
|
|
61
65
|
"state-management"
|
|
62
66
|
],
|
|
67
|
+
|
|
63
68
|
"peerDependencies": {
|
|
64
69
|
"react": ">=18.0.0",
|
|
65
70
|
"react-dom": ">=18.0.0"
|