logic-runtime-react-z 3.1.0 → 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 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
+
90
99
  ---
91
100
 
92
- ## ⚛️ React Integration (No Hooks)
101
+ ## 🧠 Computed State
93
102
 
94
- ### Define Logic
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
+
114
+ ---
115
+
116
+ ## ⚛️ React Integration (No Hooks Required)
117
+
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
+ },
242
+
243
+ intents: bus => {
244
+ bus.on("login", async ({ setState }) => {
245
+ setState(s => {
246
+ s.loading = true
247
+ })
218
248
 
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,
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
+ ```
271
317
 
272
- // useLogicSelector
273
- import { useLogicSelector } from "logic-runtime-react-z"
274
- import { counterLogic } from "./counter.logic"
275
318
 
276
- function DoubleValue() {
277
- const double = useLogicSelector(
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
+ }
337
+
338
+ function DoubleOnly() {
339
+ const double = useComputed(counterLogic, c => c.double)
340
+ return <div>{double}</div>
341
+ }
342
+ ```
343
+
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,
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,108 +422,108 @@ 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)
428
+ expect(runtime.computed.squared).toBe(16)
353
429
  ```
354
430
 
431
+ ✔ Computed values are tested like plain data
432
+
355
433
  ---
356
434
 
357
- ## 🚫 Anti-patterns (What NOT to do)
435
+ ## 🔍 Comparison
358
436
 
359
- ### ❌ Business logic in React
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) | ✅ | ⚠️ | ⚠️ | ⚠️ |
360
451
 
361
- ```tsx
362
- useEffect(() => {
363
- fetchData()
364
- }, [])
365
- ```
366
452
 
367
- ✅ Correct
368
453
 
369
- ```ts
370
- emit("data:fetch")
371
- ```
454
+ <b>⚠️ via selectors, not a true dependency graph. </b>
372
455
 
373
456
  ---
374
457
 
375
- ### Mutating state directly
458
+ ## Why not Redux + RTK?
376
459
 
377
- ```ts
378
- runtime.state.user.name = "admin"
379
- ```
460
+ Redux focuses on state transitions.
380
461
 
381
- Correct
462
+ logic-runtime-react-z models system behavior.
382
463
 
383
- ```ts
384
- emit("update:user:name", "admin")
385
- ```
464
+ In Redux:
465
+ - async flow is external (thunk/saga)
466
+ - effects are not first-class
467
+ - execution model depends on middleware setup
386
468
 
387
- ---
469
+ In logic-runtime-react-z:
470
+ - async orchestration is built-in
471
+ - intent is the only entry point
472
+ - execution order is guaranteed
388
473
 
389
- ### ❌ Generic Redux-style intents
390
474
 
391
- ```ts
392
- emit("SET_STATE", { loading: true })
393
- ```
475
+ ---
394
476
 
395
- Correct
477
+ ## 🧠 One-liner Takeaway
396
478
 
397
- ```ts
398
- emit("login:start")
399
- emit("login:success", user)
400
- emit("login:failed", error)
401
- ```
479
+ - Redux & Zustand manage **state**
480
+ - logic-runtime-react-z orchestrates **logic**
402
481
 
403
482
  ---
404
483
 
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
484
+ ## 🧬 Deterministic Execution Model
411
485
 
412
- ## 🚫 When NOT to Use
486
+ - Intents are processed sequentially
487
+ - State mutations are isolated
488
+ - Async effects follow declared strategies (takeLatest, debounce, etc.)
489
+ - Execution order is predictable
413
490
 
414
- - Simple local UI state
415
- - Throwaway components
491
+ Given the same intent sequence, the resulting state is reproducible.
416
492
 
417
493
  ---
418
494
 
419
- ## 🔍 Comparison with: Redux vs Zustand
420
-
421
- | Capability / Library | logic-runtime-react-z | Redux | Zustand |
422
- |--------------------------|:---------------------:|:-----:|:-------:|
423
- | Intent-first model | ✅ | ❌ | ❌ |
424
- | State-first model | ❌ | ✅ | ✅ |
425
- | First-class effects | ✅ | ❌ | ❌ |
426
- | Built-in async handling | ✅ | ❌ | ❌ |
427
- | Computed state graph | ✅ | ❌ | ⚠️ |
428
- | Deterministic execution | ✅ | ❌ | ❌ |
429
- | Logic outside React | ✅ | ❌ | ❌ |
430
- | Backend-safe | ✅ | ❌ | ❌ |
431
- | Intent / effect tracing | ✅ | ❌ | ❌ |
432
- | Centralized state store | ❌ | ✅ | ✅ |
433
- | Easy global state | ⚠️ | ✅ | ✅ |
434
- | Minimal boilerplate | ✅ | ❌ | ✅ |
495
+ ## 📐 Architecture Diagram (High-level)
435
496
 
436
497
  ```bash
437
- Redux / Zustand:
438
- UI → setState → store → re-render
439
-
440
- logic-runtime-react-z:
441
- UI → intent → logic → effect → state
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
+ └─────────────┘
442
512
  ```
443
513
 
444
- ### One-liner takeaway
445
- - Redux and Zustand manage **state**.
446
- - logic-runtime-react-z orchestrates **logic**.
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
447
523
 
448
524
  ---
449
525
 
526
+
450
527
  ## License
451
528
 
452
529
  MIT
@@ -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;
@@ -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.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.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.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};
@@ -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,10 +1,11 @@
1
1
  {
2
2
  "name": "logic-runtime-react-z",
3
- "version": "3.1.0",
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.",
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"