logic-runtime-react-z 3.1.0 → 3.1.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/README.md CHANGED
@@ -10,15 +10,16 @@ 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
14
 
14
15
  ---
15
16
 
16
17
  ## ✨ Why logic-runtime-react-z?
17
18
 
18
- - No React hooks in views
19
+ - No business logic in React components
19
20
  - Intent is the *only* entry point
20
21
  - Predictable async flows
21
- - Computed graph with caching
22
+ - Reactive computed graph with caching
22
23
  - Headless & backend-friendly
23
24
  - Deterministic testing & devtools replay
24
25
 
@@ -37,7 +38,7 @@ UI / HTTP / Queue / Cron
37
38
 
38
39
  mutate state
39
40
 
40
- computed / subscribers
41
+ computed (derived state) / subscribers
41
42
  ```
42
43
 
43
44
  Think **events → behavior → state → derived state**.
@@ -75,23 +76,46 @@ const counterLogic = createLogic({
75
76
  },
76
77
  })
77
78
 
78
- const runtime = counterLogic.create()
79
+ async function main() {
80
+ const runtime = counterLogic.create()
81
+
82
+ await runtime.emit("inc")
83
+ await runtime.emit("add", 5)
79
84
 
80
- await runtime.emit("inc")
81
- await runtime.emit("add", 5)
85
+ console.log(runtime.state.count)
86
+ }
82
87
 
83
- console.log(runtime.state.count) // 6
88
+ main()
84
89
  ```
85
90
 
86
- ✔ No UI
87
- ✔ Fully testable
91
+ ✔ No UI
92
+ ✔ Fully testable
88
93
  ✔ Deterministic
89
94
 
95
+ > 💡 This is the core usage.
96
+ > createLogic() + runtime.emit() already gives you state, computed and effects.
97
+ > React integration is just a convenience layer on top of this runtime.
98
+
99
+ ---
100
+
101
+ ## 🧠 Computed State
102
+
103
+ ```ts
104
+ computed: {
105
+ double: ({ state }) => state.count * 2,
106
+ triple: ({ state }) => state.count * 3,
107
+ }
108
+ ```
109
+
110
+ - `state` inside `computed` is **reactive**.
111
+ - Reading `state.count` automatically tracks dependencies.
112
+ - Computed values are cached and only re-evaluated when tracked dependencies change.
113
+
90
114
  ---
91
115
 
92
- ## ⚛️ React Integration (No Hooks)
116
+ ## ⚛️ React Integration (No Hooks Required)
93
117
 
94
- ### Define Logic
118
+ ### Define Logic (Framework-Agnostic)
95
119
 
96
120
  ```ts
97
121
  // counter.logic.ts
@@ -136,10 +160,11 @@ export const counterLogic = createLogic({
136
160
  })
137
161
  })
138
162
 
163
+ // effects = side-effects only (no state mutation)
139
164
  bus.effect(
140
165
  "inc-async",
141
166
  effect(async ({ payload }) => {
142
- console.log("effect run, payload =", payload)
167
+ console.log("effect run:", payload)
143
168
  }).takeLatest()
144
169
  )
145
170
  },
@@ -158,26 +183,27 @@ export const counterLogic = createLogic({
158
183
  },
159
184
  },
160
185
  })
186
+
161
187
  ```
162
188
 
163
189
  ---
164
190
 
165
- ### Pure React View (No Types Needed)
191
+ ### Pure React View (Dumb View)
166
192
 
167
193
  ```tsx
168
194
  import React from "react"
169
195
  import { withLogic } from "logic-runtime-react-z"
170
196
  import { counterLogic } from "./counter.logic"
171
197
 
172
- function CounterView(props: any) {
198
+ function CounterView(props) {
173
199
  const { state, actions, emit } = props
174
200
 
175
201
  return (
176
202
  <div style={{ padding: 12 }}>
177
- <div>Count: {state.triple}</div>
203
+ <div>Triple Count: {state.triple}</div>
178
204
 
179
- <button onClick={actions.inc}>+1 (action)</button>
180
- <button onClick={() => actions.add(10)}>+10 (action)</button>
205
+ <button onClick={actions.inc}>+1</button>
206
+ <button onClick={() => actions.add(10)}>+10</button>
181
207
 
182
208
  <button
183
209
  disabled={state.loading}
@@ -189,74 +215,93 @@ function CounterView(props: any) {
189
215
  <hr />
190
216
 
191
217
  <button onClick={() => emit("inc")}>
192
- +1 (emit directly)
218
+ emit("inc")
193
219
  </button>
194
220
  </div>
195
221
  )
196
222
  }
197
223
 
198
- export const CounterPage =
199
- withLogic(counterLogic, CounterView)
224
+ export const CounterPage = withLogic(counterLogic, CounterView)
225
+
200
226
  ```
201
227
 
202
- ✔ Props inferred automatically
203
- ✔ No generics
204
- ✔ No interfaces
205
- ✔ View stays dumb
228
+ ✔ Props are inferred when using withLogic, no manual generics required.
206
229
 
207
230
  ---
208
231
 
209
- ## 🧪 Backend Runtime Example
232
+ ## 🧪 Backend Usage (Same Runtime)
210
233
 
211
234
  ```ts
212
- import { createBackendRuntime } from "logic-runtime-react-z"
235
+ import { createLogic } from "logic-runtime-react-z"
213
236
 
214
- const runtime = createBackendRuntime({
215
- user: null,
216
- loading: false,
217
- })
237
+ const authLogic = createLogic({
238
+ state: {
239
+ user: null,
240
+ loading: false,
241
+ },
218
242
 
219
- runtime.registerIntents({
220
- async login({ set }) {
221
- set({ loading: true })
222
- await new Promise(r => setTimeout(r, 500))
223
- set({
224
- user: { name: "Alice" },
225
- loading: false,
243
+ intents: bus => {
244
+ bus.on("login", async ({ setState }) => {
245
+ setState(s => {
246
+ s.loading = true
247
+ })
248
+
249
+ await new Promise(r => setTimeout(r, 500))
250
+
251
+ setState(s => {
252
+ s.user = { name: "Alice" }
253
+ s.loading = false
254
+ })
226
255
  })
227
- },
228
256
 
229
- logout({ set }) {
230
- set({ user: null })
257
+ bus.on("logout", ({ setState }) => {
258
+ setState(s => {
259
+ s.user = null
260
+ })
261
+ })
231
262
  },
232
263
  })
233
264
 
234
- await runtime.emit("login")
235
- await runtime.emit("logout")
265
+ async function run() {
266
+ const runtime = authLogic.create()
236
267
 
237
- // 👇 backend devtools
238
- const devtools = runtime.devtools
239
- console.log(devtools.timeline.records)
268
+ await runtime.emit("login")
269
+ await runtime.emit("logout")
240
270
 
241
- // relay
242
- // await devtools.timeline.replay(runtime.emit, {
243
- // scope: "backend"
244
- })
271
+ console.log(runtime.getSnapshot())
272
+ }
273
+
274
+ run()
245
275
  ```
246
276
 
247
- ✔ Same intent model
277
+ ✔ Same runtime, same behavior, no React involved.
248
278
  ✔ No React
249
279
  ✔ Replayable
250
- ✔ Devtools is backend-first.
251
280
 
252
281
  ---
253
282
 
254
- ## 🪝 Hooks API (Optional)
283
+ ## 🪝 Hooks Examples (Optional, Thin Adapters)
284
+
285
+ Hooks are optional convenience layers on top of the same logic runtime.
286
+ They do not own state, they only subscribe to it.
255
287
 
288
+ #### useRuntime – full snapshot
289
+ ```ts
290
+ import { useRuntime } from "logic-runtime-react-z"
291
+
292
+ function Debug() {
293
+ const snapshot = useRuntime(counterLogic)
294
+ return <pre>{JSON.stringify(snapshot, null, 2)}</pre>
295
+ }
296
+ ```
297
+
298
+ ✔ Subscribes to full snapshot
299
+ ✔ Includes state + computed
300
+ ✔ Read-only
301
+
302
+ #### useActions – actions only (no re-render)
256
303
  ```ts
257
- // useActions
258
304
  import { useActions } from "logic-runtime-react-z"
259
- import { counterLogic } from "./counter.logic"
260
305
 
261
306
  function Buttons() {
262
307
  const actions = useActions(counterLogic)
@@ -268,36 +313,72 @@ function Buttons() {
268
313
  </>
269
314
  )
270
315
  }
316
+ ```
317
+
318
+
319
+ ✔ No re-render on state change
320
+ ✔ Fully inferred action types
321
+ ✔ Ideal for buttons / handlers
322
+
323
+ #### useComputed – Subscribe to computed values
324
+ ```ts
325
+ import { useComputed } from "logic-runtime-react-z"
326
+
327
+ function Stats() {
328
+ const { double, triple } = useComputed(counterLogic)
329
+
330
+ return (
331
+ <>
332
+ <div>Double: {double}</div>
333
+ <div>Triple: {triple}</div>
334
+ </>
335
+ )
336
+ }
271
337
 
272
- // useLogicSelector
273
- import { useLogicSelector } from "logic-runtime-react-z"
274
- import { counterLogic } from "./counter.logic"
338
+ function DoubleOnly() {
339
+ const double = useComputed(counterLogic, c => c.double)
340
+ return <div>{double}</div>
341
+ }
342
+ ```
275
343
 
276
- function DoubleValue() {
277
- const double = useLogicSelector(
344
+ Only derived data
345
+ Cached & reactive
346
+ ✔ No state mutation possible
347
+
348
+ #### useComputed with selector (recommended)
349
+ ```ts
350
+ function DoubleOnly() {
351
+ const double = useComputed(
278
352
  counterLogic,
279
- s => s.double
353
+ c => c.double
280
354
  )
281
355
 
282
356
  return <div>Double: {double}</div>
283
357
  }
358
+ ```
284
359
 
285
- // useRuntime
286
- import { useRuntime } from "logic-runtime-react-z"
287
- import { counterLogic } from "./counter.logic"
360
+ Component re-renders only when double changes
361
+ No extra dependencies
362
+ Type-safe selector
288
363
 
289
- function DebugPanel() {
290
- const runtime = useRuntime(counterLogic)
364
+ #### useLogicSelector – State selector (Redux-like)
365
+ ```ts
366
+ import { useLogicSelector } from "logic-runtime-react-z"
291
367
 
292
- return (
293
- <button onClick={() => runtime.emit("inc")}>
294
- Emit directly
295
- </button>
368
+ function CountLabel() {
369
+ const count = useLogicSelector(
370
+ counterLogic.create(),
371
+ state => state.count
296
372
  )
297
- }
298
373
 
374
+ return <span>{count}</span>
375
+ }
299
376
  ```
300
377
 
378
+ ✔ Memoized selector
379
+ ✔ Fine-grained subscriptions
380
+ ✔ Familiar mental model
381
+
301
382
  ---
302
383
 
303
384
  ## 🧱 Composing Multiple Logic Modules
@@ -307,20 +388,16 @@ import { composeLogic } from "logic-runtime-react-z"
307
388
  import { userLogic } from "./user.logic"
308
389
  import { cartLogic } from "./cart.logic"
309
390
 
310
- export const appLogic = composeLogic({
391
+ const app = composeLogic({
311
392
  user: userLogic,
312
393
  cart: cartLogic,
313
394
  })
314
395
 
396
+ await app.emit("login")
315
397
 
316
- // usage
317
- const runtime = appLogic.create()
318
-
319
- await runtime.emit("user:login", credentials)
320
-
321
- const snapshot = runtime.getSnapshot()
322
- snapshot.user // user state
323
- snapshot.cart // cart state
398
+ const state = app.getState()
399
+ state.user
400
+ state.cart
324
401
 
325
402
  ```
326
403
 
@@ -345,105 +422,46 @@ const logic = createLogic({
345
422
  },
346
423
  })
347
424
 
348
- const runtime = logic.create()
425
+ const runtime = counterLogic.create()
349
426
 
350
427
  await runtime.emit("set", 4)
351
-
352
- expect(runtime.state.squared).toBe(16)
353
- ```
354
-
355
- ---
356
-
357
- ## 🚫 Anti-patterns (What NOT to do)
358
-
359
- ### ❌ Business logic in React
360
-
361
- ```tsx
362
- useEffect(() => {
363
- fetchData()
364
- }, [])
365
- ```
366
-
367
- ✅ Correct
368
-
369
- ```ts
370
- emit("data:fetch")
371
- ```
372
-
373
- ---
374
-
375
- ### ❌ Mutating state directly
376
-
377
- ```ts
378
- runtime.state.user.name = "admin"
379
- ```
380
-
381
- ✅ Correct
382
-
383
- ```ts
384
- emit("update:user:name", "admin")
385
- ```
386
-
387
- ---
388
-
389
- ### ❌ Generic Redux-style intents
390
-
391
- ```ts
392
- emit("SET_STATE", { loading: true })
393
- ```
394
-
395
- ✅ Correct
396
-
397
- ```ts
398
- emit("login:start")
399
- emit("login:success", user)
400
- emit("login:failed", error)
428
+ expect(runtime.computed.squared).toBe(16)
401
429
  ```
402
430
 
403
- ---
404
-
405
- ## 🧩 When to Use This
406
-
407
- - Complex async flows
408
- - Shared logic across UI / backend
409
- - Need deterministic tests
410
- - Want to remove logic from React
411
-
412
- ## 🚫 When NOT to Use
413
-
414
- - Simple local UI state
415
- - Throwaway components
431
+ ✔ Computed values are tested like plain data
416
432
 
417
433
  ---
418
434
 
419
- ## 🔍 Comparison with: Redux vs Zustand
435
+ ## 🔍 Comparison: Redux vs Zustand
420
436
 
421
437
  | Capability / Library | logic-runtime-react-z | Redux | Zustand |
422
438
  |--------------------------|:---------------------:|:-----:|:-------:|
423
439
  | Intent-first model | ✅ | ❌ | ❌ |
424
440
  | State-first model | ❌ | ✅ | ✅ |
425
441
  | First-class effects | ✅ | ❌ | ❌ |
426
- | Built-in async handling | ✅ | ❌ | |
427
- | Computed state graph | ✅ | ❌ | ⚠️ |
442
+ | Computed graph | ✅ | ❌ | ⚠️ |
428
443
  | Deterministic execution | ✅ | ❌ | ❌ |
429
444
  | Logic outside React | ✅ | ❌ | ❌ |
430
445
  | Backend-safe | ✅ | ❌ | ❌ |
431
- | Intent / effect tracing | ✅ | ❌ | ❌ |
432
- | Centralized state store | ❌ | ✅ | ✅ |
433
- | Easy global state | ⚠️ | ✅ | ✅ |
434
- | Minimal boilerplate | ✅ | ❌ | ✅ |
435
446
 
436
- ```bash
437
- Redux / Zustand:
438
- UI → setState → store → re-render
439
447
 
440
- logic-runtime-react-z:
441
- UI → intent → logic → effect → state
442
- ```
448
+ ##### ⚠️ via selectors, not a true dependency graph
449
+
450
+ ---
451
+
452
+ ## 🧠 One-liner Takeaway
453
+
454
+ - Redux & Zustand manage **state**
455
+ - logic-runtime-react-z orchestrates **logic**
456
+
457
+ ---
458
+
459
+ ## 🧬 Determinism Guarantee
443
460
 
444
- ### One-liner takeaway
445
- - Redux and Zustand manage **state**.
446
- - logic-runtime-react-z orchestrates **logic**.
461
+ - Intents are processed sequentially
462
+ - State mutations are isolated
463
+ - Async flows are predictable
464
+ - Same inputs → same outputs
447
465
 
448
466
  ---
449
467
 
@@ -1,26 +1,39 @@
1
- import { Atom } from "chrono-state-z/build/core/atom";
1
+ import type { Atom } from "chrono-state-z/build/core/atom";
2
2
  import { Scope } from "intentx-core-z";
3
3
  import { EffectDef } from "./effect";
4
4
  export type AtomAccessor<T> = Atom<T>;
5
- export declare class LogicRuntime<S extends object, A extends Record<string, any> = {}> {
5
+ export type ComputedDef<S> = Record<string, (context: {
6
+ state: Readonly<S>;
7
+ }) => any>;
8
+ export type InferComputed<C> = {
9
+ [K in keyof C]: C[K] extends (...args: any[]) => infer R ? R : never;
10
+ };
11
+ export declare class LogicRuntime<S extends object, C extends ComputedDef<S>, A extends Record<string, any>> {
6
12
  readonly scope: Scope;
7
- private atoms;
13
+ private stateAtoms;
14
+ private computedAtoms;
8
15
  private subs;
9
16
  private bus;
10
17
  private snapshotCache;
11
18
  private dirty;
12
19
  private isComputing;
20
+ private computedKeys;
13
21
  actions: A;
14
22
  constructor(initial: S, scope?: Scope);
15
- private createAtoms;
23
+ private createStateAtoms;
16
24
  private buildSnapshot;
17
25
  private markDirty;
18
- getSnapshot: () => Readonly<S>;
26
+ private createReactiveState;
27
+ getSnapshot: () => Readonly<S & InferComputed<C>>;
19
28
  subscribe: (fn: () => void) => () => boolean;
29
+ get state(): Readonly<S & InferComputed<C>>;
30
+ get computed(): Readonly<InferComputed<C>>;
31
+ getComputedKey<K extends keyof InferComputed<C>>(key: K): InferComputed<C>[K];
32
+ getComputed(snapshot: Readonly<S & InferComputed<C>>): Readonly<InferComputed<C>>;
20
33
  private setStateInternal;
21
34
  onIntent: <P = any>(type: string, handler: (context: {
22
35
  payload: P;
23
- state: () => Readonly<S>;
36
+ state: () => Readonly<S & InferComputed<C>>;
24
37
  scope: Scope;
25
38
  signal: AbortSignal;
26
39
  setState(fn: (draft: S) => void): void;
@@ -28,5 +41,5 @@ export declare class LogicRuntime<S extends object, A extends Record<string, any
28
41
  }) => any) => void;
29
42
  useEffect(type: string, eff: EffectDef): void;
30
43
  emit: <P = any>(type: string, payload?: P) => Promise<void>;
31
- attachComputed<K extends string, T>(key: K, compute: () => T): void;
44
+ attachComputed<K extends keyof C & string>(key: K, compute: C[K]): void;
32
45
  }
@@ -1 +1 @@
1
- "use strict";var t=require("chrono-state-z"),e=require("intentx-core-z"),n=require("react/jsx-runtime"),s=require("react");function o(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(n){if("default"!==n){var s=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,s.get?s:{enumerable:!0,get:function(){return t[n]}})}}),e.default=t,Object.freeze(e)}var r=o(s);class i{constructor(){this.handlers={},this.effects={},this.middlewares=[]}use(t){this.middlewares.push(t)}effect(t,e){var n,s;(null!==(n=(s=this.effects)[t])&&void 0!==n?n:s[t]=[]).push(e)}on(t,e){var n,s;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!==(n=(s=this.handlers)[t])&&void 0!==n?n:s[t]=[]).push(e)}async emit(t,e){var n,s;const o=null!==(n=this.handlers[t])&&void 0!==n?n:[],r=null!==(s=this.effects[t])&&void 0!==s?s:[],i=(a=this.middlewares,c=async t=>{t.effects=r;for(const e of o)await e(t)},a.reduceRight((t,e)=>e(t),c));var a,c;await i(e)}}class a{constructor(t,n=e.createScope("logic")){this.subs=new Set,this.bus=new i,this.dirty=!0,this.isComputing=!1,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 n=new AbortController;await this.bus.emit(t,{payload:e,scope:this.scope,signal:n.signal,state:this.getSnapshot,setState:t=>this.setStateInternal(t),emit:this.emit})},this.scope=n,this.atoms=this.createAtoms(t);for(const t in this.atoms)this.atoms[t].subscribe(this.markDirty);this.bus.use(function(){const t=new Map,e=new Map;return n=>async s=>{var o;const r=s.effects;if(!(null==r?void 0:r.length))return n(s);for(const n of r){const r=n.handler.toString();if("takeLatest"===n.strategy){null===(o=t.get(r))||void 0===o||o.abort();const e=new AbortController;t.set(r,e),await n.handler({...s,signal:e.signal})}else"debounce"===n.strategy?(clearTimeout(e.get(r)),e.set(r,setTimeout(()=>n.handler(s),n.wait))):await n.handler(s)}await n(s)}}())}createAtoms(e){const n={};for(const s in e)n[s]=t.atom(e[s]);return n}buildSnapshot(){const t={};for(const e in this.atoms)t[e]=this.atoms[e]();return t}setStateInternal(t){this.isComputing&&console.warn("[logic-runtime] Side-effect detected: setState() called inside computed()");const e=this.buildSnapshot();t(e);for(const t in e)e[t]!==this.atoms[t]()&&this.atoms[t].set(e[t])}useEffect(t,e){this.bus.effect(t,e)}attachComputed(e,n){const s=t.atom(void 0);t.effect(()=>{this.isComputing=!0;const t=n();s.set(t),this.isComputing=!1}),this.atoms[e]=s,s.subscribe(this.markDirty)}}function c(t){const e=function(){let t=0,e=[];return{get records(){return e.slice()},record:function(n){e.push({...n,id:++t,state:structuredClone(n.state)})},replay:async function(t,n){const{from:s=0,to:o=1/0,scope:r}=null!=n?n:{},i=e.filter(t=>"emit"===t.type&&t.id>=s&&t.id<=o&&(!r||t.scope===r));for(const e of i){const n=t(e.intent,e.payload);n instanceof Promise&&await n}},clear:function(){e=[],t=0}}}();function n(){return"string"==typeof t.scope?t.scope:t.scope.name}return{timeline:e,wrap:function(){const s=t.emit.bind(t);t.emit=async(o,r)=>{e.record({type:"emit:start",intent:o,payload:r,scope:n(),state:t.getSnapshot(),timestamp:Date.now()});try{const t=s(o,r);t instanceof Promise&&await t}finally{e.record({type:"emit:end",intent:o,payload:r,scope:n(),state:t.getSnapshot(),timestamp:Date.now()})}}}}}function u(t){var n;const o=s.useRef(null),r=null!=t&&"function"==typeof t.subscribe&&"function"==typeof t.getSnapshot?t:null!==(n=o.current)&&void 0!==n?n:o.current=t.create(e.createScope("react"));return s.useSyncExternalStore(r.subscribe,r.getSnapshot,r.getSnapshot)}Object.defineProperty(exports,"createSelector",{enumerable:!0,get:function(){return t.createSelector}}),exports.LogicRuntime=a,exports.attachDevtools=c,exports.composeLogic=function(t){return{create(e){const n={};for(const s in t){const o=s;n[o]=t[o].create(e)}return{async emit(t,e){for(const s of Object.values(n))await s.emit(t,e)},subscribe(t){const e=Object.values(n).map(e=>e.subscribe(t));return()=>e.forEach(t=>t())},getSnapshot(){const t={};for(const e in n)t[e]=n[e].getSnapshot();return t},runtimes:n}}}},exports.createBackendRuntime=function(t){var n,s;let o=structuredClone(t);const r=()=>o,i=t=>{o={...o,...t}},a=e.createScope("backend");async function u(t,e){await l.emit(t,e,a)}const l=e.createIntentBus(t=>{const e=new AbortController;return{scope:a,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 n=t[e];l.on(e,async(t,e)=>{var s;const o={get state(){return r()},signal:null!==(s=t.signal)&&void 0!==s?s:(new AbortController).signal,set:i,emit:u};await n(o)},a)}},onIntent:l.on,effect:l.effect};if("production"!==(null===(s=null===(n=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===n?void 0:n.env)||void 0===s?void 0:s.NODE_ENV)){const t=c(h);t.wrap(h),h.devtools=t}return h},exports.createLogic=function(t){return{name:t.name,create(e){var n;const s=new a(structuredClone(t.state),e);if(t.computed)for(const e in t.computed)s.attachComputed(e,()=>t.computed[e]({state:s.getSnapshot()}));null===(n=t.intents)||void 0===n||n.call(t,{on:s.onIntent,effect:s.useEffect.bind(s)});const o={};if(t.actions)for(const e in t.actions)o[e]=t.actions[e]({emit:s.emit,getState:s.getSnapshot});return s.actions=o,s}}},exports.effect=function(t){const e={_kind:"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(null!=t&&"actions"in t?t:u(t)).actions},exports.useLogicSelector=function(e,n){const o=s.useMemo(()=>t.createSelector(n),[n]);return s.useSyncExternalStore(e.subscribe,()=>o(e.getSnapshot()),()=>o(e.getSnapshot()))},exports.useRuntime=u,exports.withLogic=function(t,s,o){var i,a;const c=i=>{const a=r.useRef(null);a.current||(a.current=t.create("string"==typeof o?e.createScope(o):o));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 n.jsx(s,{...i,...h})};return c.displayName=`withLogic(${null!==(a=null!==(i=s.displayName)&&void 0!==i?i:s.name)&&void 0!==a?a:"View"})`,c};
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.handler.toString();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",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.d.ts CHANGED
@@ -8,7 +8,9 @@ export type { LogicFactory, LogicActions, } from "./logic/createLogic";
8
8
  export { composeLogic } from "./logic/composeLogic";
9
9
  export { createBackendRuntime } from "./logic/createBackendRuntime";
10
10
  export { withLogic } from "./react/withLogic";
11
- export { useRuntime } from "./react/useRuntime";
12
11
  export { useActions } from "./react/useActions";
12
+ export { useComputed } from "./react/useComputed";
13
+ export { useLogic } from "./react/useLogic";
13
14
  export { useLogicSelector } from "./react/useLogicSelector";
15
+ export { useRuntime } from "./react/useRuntime";
14
16
  export * from "./devtools";
@@ -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.subs=new Set,this.bus=new l,this.dirty=!0,this.isComputing=!1,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.atoms=this.createAtoms(t);for(const t in this.atoms)this.atoms[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.handler.toString();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)}}())}createAtoms(e){const s={};for(const n in e)s[n]=t(e[n]);return s}buildSnapshot(){const t={};for(const e in this.atoms)t[e]=this.atoms[e]();return t}setStateInternal(t){this.isComputing&&console.warn("[logic-runtime] Side-effect detected: setState() called inside computed()");const e=this.buildSnapshot();t(e);for(const t in e)e[t]!==this.atoms[t]()&&this.atoms[t].set(e[t])}useEffect(t,e){this.bus.effect(t,e)}attachComputed(s,n){const o=t(void 0);e(()=>{this.isComputing=!0;const t=n();o.set(t),this.isComputing=!1}),this.atoms[s]=o,o.subscribe(this.markDirty)}}function f(t){const e={_kind:"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]({state:n.getSnapshot()}));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 m(t){return{create(e){const s={};for(const n in t){const o=n;s[o]=t[o].create(e)}return{async emit(t,e){for(const n of Object.values(s))await n.emit(t,e)},subscribe(t){const e=Object.values(s).map(e=>e.subscribe(t));return()=>e.forEach(t=>t())},getSnapshot(){const t={};for(const e in s)t[e]=s[e].getSnapshot();return t},runtimes:s}}}}function p(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=p(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=null!=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(null!=t&&"actions"in t?t:y(t)).actions}function v(t,e){const n=u(()=>s(e),[e]);return c(t.subscribe,()=>n(t.getSnapshot()),()=>n(t.getSnapshot()))}export{h as LogicRuntime,p as attachDevtools,m as composeLogic,g as createBackendRuntime,d as createLogic,f as effect,w as useActions,v as useLogicSelector,y as useRuntime,b as withLogic};
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.handler.toString();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",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};
@@ -1,18 +1,21 @@
1
+ import { Scope } from "intentx-core-z";
2
+ import type { ComputedDef, InferComputed } from "../core/runtime";
1
3
  import { LogicRuntime } from "../core/runtime";
2
4
  import type { LogicFactory } from "../logic/createLogic";
3
- type LogicMap = Record<string, LogicFactory<any, any>>;
4
- type RuntimeMap<M extends LogicMap> = {
5
- [K in keyof M]: ReturnType<M[K]["create"]>;
5
+ type AnyLogicFactory = LogicFactory<any, ComputedDef<any>, any>;
6
+ type LogicMap = Record<string, AnyLogicFactory>;
7
+ type InferRuntime<L> = L extends LogicFactory<infer S, infer C, infer A> ? LogicRuntime<S, C, A> : never;
8
+ type InferState<M extends LogicMap> = {
9
+ [K in keyof M]: InferRuntime<M[K]> extends LogicRuntime<infer S, infer C, any> ? Readonly<S & InferComputed<C>> : never;
6
10
  };
7
- type SnapshotOf<M extends LogicMap> = {
8
- [K in keyof M]: ReturnType<M[K]["create"]> extends LogicRuntime<infer S extends object, any> ? Readonly<S> : never;
11
+ type InferActions<M extends LogicMap> = {
12
+ [K in keyof M]: InferRuntime<M[K]> extends LogicRuntime<any, any, infer A> ? A : never;
9
13
  };
10
- export declare function composeLogic<M extends LogicMap>(logics: M): {
11
- create(scope?: any): {
12
- emit(intent: string, payload?: any): Promise<void>;
13
- subscribe(fn: () => void): () => void;
14
- getSnapshot(): SnapshotOf<M>;
15
- runtimes: RuntimeMap<M>;
16
- };
14
+ export declare function composeLogic<M extends LogicMap>(map: M): {
15
+ scope: Scope;
16
+ runtimes: { [K in keyof M]: InferRuntime<M[K]>; };
17
+ emit: <P = any>(type: string, payload?: P) => Promise<void>;
18
+ actions: InferActions<M>;
19
+ getState: () => InferState<M>;
17
20
  };
18
21
  export {};
@@ -1,28 +1,23 @@
1
1
  import { Scope } from "intentx-core-z";
2
- import { LogicRuntime } from "../core/runtime";
2
+ import { LogicRuntime, ComputedDef, InferComputed } from "../core/runtime";
3
3
  import { EffectDef } from "../core/effect";
4
- type ComputedFactory<S> = (context: {
5
- state: Readonly<S>;
6
- }) => any;
7
- type ActionFactory<S extends object, Fn extends (...args: any[]) => any> = (context: {
8
- emit: LogicRuntime<S, any>["emit"];
9
- getState: () => Readonly<S>;
10
- }) => Fn;
11
4
  export type LogicActions = Record<string, (...args: any[]) => any>;
12
- export type LogicFactory<S extends object, A extends LogicActions> = {
5
+ export type LogicFactory<S extends object, C extends ComputedDef<S>, A extends LogicActions> = {
13
6
  name?: string;
14
- create(scope?: Scope): LogicRuntime<S, A>;
7
+ create(scope?: Scope): LogicRuntime<S, C, A>;
15
8
  };
16
- export declare function createLogic<S extends object, A extends LogicActions>(config: {
9
+ export declare function createLogic<S extends object, C extends ComputedDef<S> = {}, A extends LogicActions = {}>(config: {
17
10
  name?: string;
18
11
  state: S;
19
- computed?: Record<string, ComputedFactory<S>>;
12
+ computed?: C;
20
13
  intents?: (bus: {
21
- on: LogicRuntime<S>["onIntent"];
14
+ on: LogicRuntime<S, C, A>["onIntent"];
22
15
  effect: (type: string, eff: EffectDef) => void;
23
16
  }) => void;
24
17
  actions?: {
25
- [K in keyof A]: ActionFactory<S, A[K]>;
18
+ [K in keyof A]: (context: {
19
+ emit: LogicRuntime<S, C, A>["emit"];
20
+ getState: () => Readonly<S & InferComputed<C>>;
21
+ }) => A[K];
26
22
  };
27
- }): LogicFactory<S, A>;
28
- export {};
23
+ }): LogicFactory<S, C, A>;
@@ -1,6 +1,4 @@
1
- import { LogicActions, LogicFactory } from "../logic/createLogic";
2
- import { LogicRuntime } from "../core/runtime";
3
- export declare function useActions<A extends object>(runtime: LogicRuntime<any> & {
4
- actions: A;
5
- }): A;
6
- export declare function useActions<S extends object, A extends LogicActions>(logic: LogicFactory<S, A>): A;
1
+ import { LogicFactory, LogicActions } from "../logic/createLogic";
2
+ import { LogicRuntime, ComputedDef } from "../core/runtime";
3
+ export declare function useActions<A extends LogicActions>(runtime: LogicRuntime<any, any, A>): A;
4
+ export declare function useActions<S extends object, C extends ComputedDef<S>, A extends LogicActions>(logic: LogicFactory<S, C, A>): A;
@@ -0,0 +1,6 @@
1
+ import { LogicFactory } from "../logic/createLogic";
2
+ import { LogicRuntime, ComputedDef, InferComputed } from "../core/runtime";
3
+ export declare function useComputed<S extends object, C extends ComputedDef<S>>(runtime: LogicRuntime<S, C, any>): Readonly<InferComputed<C>>;
4
+ export declare function useComputed<S extends object, C extends ComputedDef<S>, R>(runtime: LogicRuntime<S, C, any>, selector: (computed: Readonly<InferComputed<C>>) => R): R;
5
+ export declare function useComputed<S extends object, C extends ComputedDef<S>, A extends Record<string, any>>(logic: LogicFactory<S, C, A>): Readonly<InferComputed<C>>;
6
+ export declare function useComputed<S extends object, C extends ComputedDef<S>, A extends Record<string, any>, R>(logic: LogicFactory<S, C, A>, selector: (computed: Readonly<InferComputed<C>>) => R): R;
@@ -0,0 +1,6 @@
1
+ import { LogicRuntime, InferComputed } from "../core/runtime";
2
+ export declare function useLogic<S extends object, C extends Record<string, any>, A extends Record<string, any>>(runtime: LogicRuntime<S, C, A>): {
3
+ state: Readonly<S & InferComputed<C>>;
4
+ computed: Readonly<InferComputed<C>>;
5
+ actions: A;
6
+ };
@@ -1,2 +1,2 @@
1
- import { LogicRuntime } from "../core/runtime";
2
- export declare function useLogicSelector<S extends object, R>(runtime: LogicRuntime<S>, selector: (state: Readonly<S>) => R): R;
1
+ import { LogicRuntime, InferComputed } from "../core/runtime";
2
+ export declare function useLogicSelector<S extends object, C extends Record<string, any>, R>(runtime: LogicRuntime<S, C, any>, selector: (state: Readonly<S & InferComputed<C>>) => R): R;
@@ -1,4 +1,5 @@
1
+ import type { ComputedDef, InferComputed } from "../core/runtime";
1
2
  import { LogicRuntime } from "../core/runtime";
2
- import { LogicFactory } from "../logic/createLogic";
3
- export declare function useRuntime<S extends object>(runtime: LogicRuntime<S>): Readonly<S>;
4
- export declare function useRuntime<S extends object, A extends Record<string, any>>(logic: LogicFactory<S, A>): Readonly<S>;
3
+ import type { LogicFactory } from "../logic/createLogic";
4
+ export declare function useRuntime<S extends object, C extends ComputedDef<S>, A extends Record<string, any>>(runtime: LogicRuntime<S, C, A>): Readonly<S & InferComputed<C>>;
5
+ export declare function useRuntime<S extends object, C extends ComputedDef<S>, A extends Record<string, any>>(logic: LogicFactory<S, C, A>): Readonly<S & InferComputed<C>>;
@@ -1,10 +1,11 @@
1
1
  import * as React from "react";
2
2
  import { Scope } from "intentx-core-z";
3
- import { LogicActions, LogicFactory } from "../logic/createLogic";
4
- type InjectedProps<S extends object, A extends LogicActions> = {
5
- state: Readonly<S>;
3
+ import type { LogicActions, LogicFactory } from "../logic/createLogic";
4
+ import type { ComputedDef, InferComputed } from "../core/runtime";
5
+ type InjectedProps<S extends object, C extends ComputedDef<S>, A extends LogicActions> = {
6
+ state: Readonly<S & InferComputed<C>>;
6
7
  actions: A;
7
8
  emit: (intent: string, payload?: any) => Promise<void>;
8
9
  };
9
- export declare function withLogic<S extends object, A extends LogicActions, P extends object>(logic: LogicFactory<S, A>, View: React.ComponentType<P & InjectedProps<S, A>>, scope?: Scope | string): React.FC<Omit<P, keyof InjectedProps<S, A>>>;
10
+ export declare function withLogic<S extends object, C extends ComputedDef<S>, A extends LogicActions, P extends object>(logic: LogicFactory<S, C, A>, View: React.ComponentType<P & InjectedProps<S, C, A>>, scope?: Scope | string): React.FC<Omit<P, keyof InjectedProps<S, C, A>>>;
10
11
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logic-runtime-react-z",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Intent-first business logic runtime. Headless and backend-friendly. Deterministic state, computed graph, and orchestrated async effects. React is just a view layer.",
5
5
  "license": "MIT",
6
6
  "author": "Delpi.Kye",