logic-runtime-react-z 1.0.1 → 2.0.0

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
@@ -1,218 +1,324 @@
1
- ## ⚙️ logic-runtime-react-z
1
+
2
+ # ⚙️ logic-runtime-react-z
2
3
 
3
4
  [![NPM](https://img.shields.io/npm/v/logic-runtime-react-z.svg)](https://www.npmjs.com/package/logic-runtime-react-z)
4
5
  ![Downloads](https://img.shields.io/npm/dt/logic-runtime-react-z.svg)
5
6
 
6
- <a href="https://codesandbox.io/p/sandbox/x3jf32" target="_blank">LIVE EXAMPLE</a>
7
-
8
- ---
9
-
10
- **logic-runtime-react-z** is an intent-first external runtime,
11
- designed to run business logic **outside React**.
7
+ **logic-runtime-react-z** is an **intent-first business logic runtime**
8
+ designed to run **outside React**, with React acting purely as a view layer.
12
9
 
13
- React 18+ is required **only for the React bindings**.
14
- It provides a headless runtime layer that lives outside React, responsible for:
15
-
16
- > Think of it as:
17
- > **“Business logic runtime outside React — React is just the view.”**
10
+ > **Business logic lives outside React.
11
+ > React only renders state and emits intent.**
18
12
 
19
13
  ---
20
14
 
21
15
  ## ✨ Why / When to Use
22
- - You want zero hooks in UI components
23
- - Business logic should not live in React
24
- - UI should only emit intent, not orchestrate behavior
16
+
17
+ Use this library when:
18
+
19
+ - You want **zero hooks inside UI components**
20
+ - Business logic must not depend on React lifecycle
21
+ - UI should **emit intent**, not orchestrate behavior
25
22
  - Async flows are complex (login → fetch → redirect)
26
23
  - Side effects must be predictable & testable
27
- - You want headless tests without rendering
28
- - You prefer architecture-driven design over component-driven logic
24
+ - You want **headless testing** without rendering
25
+ - You prefer **architecture-driven** design
29
26
 
30
27
  ---
31
28
 
32
29
  ## 🧠 Mental Model
33
30
 
34
- ```txt
35
- UI (Pure View)
36
- └─ emits intent (no logic)
37
-
38
- External Runtime (logic-runtime-react-z)
39
- ├─ state store
40
- ├─ intent handlers
41
- ├─ effect pipeline
42
- ├─ computed graph
43
- ├─ selectors
44
- └─ devtools timeline
45
- ```
31
+ ![Architecture Diagram](https://github.com/delpikye-v/logic-runtime-react/raw/main/architect.png)
46
32
 
47
- - React is just an adapter.
48
- - The runtime owns behavior.
33
+ - React is only an adapter
34
+ - Runtime owns all behavior
49
35
 
50
36
  ---
51
37
 
52
38
  ## 📦 Installation
53
- ```ts
39
+
40
+ ```bash
54
41
  npm install logic-runtime-react-z
55
42
  ```
43
+
56
44
  ---
57
45
 
58
- ## Basic
46
+ ## Examples
47
+ #### 1. Headless (No React)
48
+
59
49
  ```ts
60
- const logic = createLogic({
50
+ import { createLogic } from "logic-runtime-react-z"
51
+
52
+ const counterLogic = createLogic({
61
53
  state: { count: 0 },
54
+
62
55
  intents: bus => {
63
56
  bus.on("inc", ({ setState }) => {
64
- setState(s => { s.count++ })
57
+ setState(s => {
58
+ s.count++
59
+ })
65
60
  })
66
- }
61
+
62
+ bus.on("add", ({ payload, setState }) => {
63
+ setState(s => {
64
+ s.count += payload
65
+ })
66
+ })
67
+ },
67
68
  })
68
69
 
69
- const runtime = logic.create()
70
- runtime.emit("inc")
70
+ const runtime = counterLogic.create()
71
+
72
+ await runtime.emit("inc")
73
+ await runtime.emit("add", 5)
74
+
75
+ console.log(runtime.state.count) // 6
76
+
71
77
  ```
72
78
 
73
79
  ---
74
80
 
75
- ## ⚛️ React Usage (No Hooks in View)
81
+ #### ⚛️ 2. With React (No Hooks in View)
76
82
 
77
83
  ```ts
78
- import { createLogic, withLogic, createSelector } from "logic-runtime-react-z"
84
+ import {
85
+ createLogic,
86
+ withLogic,
87
+ } from "logic-runtime-react-z"
79
88
 
80
89
  interface State {
81
- count: number;
82
-
83
- // computed
90
+ count: number
84
91
  double: number
85
92
  }
86
93
 
87
94
  const counterLogic = createLogic({
88
95
  name: "counter",
96
+
89
97
  state: { count: 1 },
90
98
 
91
99
  computed: {
92
- double({ state }): number {
100
+ double({ state }) {
93
101
  return state.count * 2
94
- }
102
+ },
95
103
  },
96
104
 
97
105
  intents: bus => {
98
- bus.on("inc", ({ state, setState }) => {
99
- // if (!selectIsAdult(state)) {
100
- // throw new Error("Not allowed")
101
- // }
102
-
106
+ bus.on("inc", ({ setState }) => {
103
107
  setState(s => {
104
108
  s.count++
105
109
  })
106
110
  })
111
+
112
+ bus.on("inc-payload", ({ payload, setState }) => {
113
+ setState(s => {
114
+ s.count += payload
115
+ })
116
+ })
107
117
  },
108
118
  })
109
119
 
110
- // Pure View (No Hooks)
120
+ // Pure View (no hooks)
111
121
  function CounterView({
112
122
  state,
113
123
  emit,
114
124
  }: {
115
125
  state: State
116
- emit: (intent: string) => void
126
+ emit: (intent: string, payload?: any) => void
117
127
  }) {
118
128
  return (
119
129
  <div>
120
130
  <div>Count: {state.count}</div>
121
- // <button
122
- // onClick={async () => {
123
- // try {
124
- // await emit("inc")
125
- // console.log("done")
126
- // } catch (e) {
127
- // console.error(e)
128
- // }
129
- // }}
130
- // >
131
- // +
132
- // </button>
133
131
  <div>Double: {state.double}</div>
132
+
134
133
  <button onClick={() => emit("inc")}>+</button>
134
+ <button onClick={() => emit("inc-payload", 5)}>+5</button>
135
135
  </div>
136
136
  )
137
137
  }
138
138
 
139
- // Bind Logic to React
139
+ // Bind logic to React
140
140
  export const Counter = withLogic(counterLogic, CounterView)
141
+
141
142
  ```
143
+ ---
142
144
 
143
- ## 🧪 No React
144
- ```ts
145
+ #### 🧪 3. Testing (No React)
145
146
 
146
- // import { createLogic } from "logic-runtime-react-z"
147
- const counterLogic = createLogic({
148
- state: {
149
- count: 0
150
- },
147
+ ```ts
148
+ // no render, no React
149
+ const logic = createLogic({
150
+ state: { value: 0 },
151
151
 
152
152
  computed: {
153
- double({ state }) {
154
- return state.count * 2
155
- }
153
+ squared({ state }) {
154
+ return state.value * state.value
155
+ },
156
156
  },
157
157
 
158
158
  intents: bus => {
159
- bus.on("inc", ({ state, setState, emit }) => {
160
- // state is READONLY snapshot (read-only)
161
- // ❌ state.count++
162
- setState(s => {
163
- s.count += 1
164
- })
165
-
166
- // emit("inc") ❌ no nested
167
- })
168
-
169
- // Intent handlers can be sync or async
170
- bus.on("add", ({ payload, setState }) => {
171
- setState(s => {
172
- s.count += payload
173
- })
174
- })
175
-
176
- bus.on("reset", ({ setState }) => {
159
+ bus.on("set", ({ payload, setState }) => {
177
160
  setState(s => {
178
- s.count = 0
161
+ s.value = payload
179
162
  })
180
163
  })
181
164
  },
182
165
  })
183
166
 
184
- // initial
185
- // const runtime = counterLogic.create()
186
- const runtime = counterLogic.create("counter:main")
167
+ const runtime = logic.create()
187
168
 
188
- runtime.emit("inc")
189
- // await runtime.emit("inc") // if async
190
- runtime.emit("add", 5)
169
+ await runtime.emit("set", 4)
170
+
171
+ expect(runtime.state.squared).toBe(16)
191
172
 
192
- // console.log
193
- console.log(runtime.state.count) // 7
194
- console.log(runtime.state.double) // 14
195
173
  ```
196
174
 
197
175
  ---
198
176
 
199
- ## 🧩 Plugins – Runtime extensions (cross-cutting concerns)
177
+ ## Backend Runtime
178
+
179
+ ```perl
180
+ HTTP / Queue / Cron
181
+
182
+ emit(intent)
183
+
184
+ middleware / effects
185
+
186
+ intent handlers
187
+
188
+ mutate state
189
+
190
+ emit next intent
191
+ ```
200
192
 
201
- #### Plugins extend the runtime behavior **without touching business logic**.
202
193
 
194
+ #### 🚀 1. Backend Runtime
203
195
  ```ts
204
- export const authPlugin: LogicPlugin = {
205
- name: "auth",
196
+ 1️⃣ // create backend
197
+ import { createBackendRuntime } from "logic-runtime-react-z"
206
198
 
207
- setup(runtime) {
208
- runtime.onIntent("delete", ctx => {
209
- if (!ctx.state.user?.isAdmin) {
210
- throw new Error("Forbidden")
211
- }
212
- })
199
+ type BackendState = {
200
+ user: null | {
201
+ id: string
202
+ name: string
213
203
  }
204
+ token: string | null
205
+ loading: boolean
214
206
  }
215
207
 
208
+ const runtime = createBackendRuntime<BackendState>({
209
+ user: null,
210
+ token: null,
211
+ loading: false,
212
+ })
213
+
214
+ // middleware
215
+ runtime.use(async (ctx, next) => {
216
+ console.log("➡️ intent:", ctx.intent, ctx.payload)
217
+ console.log(" state before:", ctx.state)
218
+
219
+ const start = Date.now()
220
+ await next()
221
+
222
+ console.log("⬅️ intent:", ctx.intent)
223
+ console.log(" state after:", ctx.state)
224
+ console.log(" took:", Date.now() - start, "ms")
225
+ })
226
+
227
+ 2️⃣ // register handler
228
+ runtime.onIntent("login", async ({
229
+ payload,
230
+ setState,
231
+ emit,
232
+ }) => {
233
+ setState(s => {
234
+ s.loading = true
235
+ })
236
+
237
+ // fake API
238
+ const result = await fakeLoginApi(payload)
239
+
240
+ setState(s => {
241
+ s.user = result.user
242
+ s.token = result.token
243
+ s.loading = false
244
+ })
245
+
246
+ // chain intent
247
+ await emit("login:success", result.user)
248
+ })
249
+
250
+ runtime.onIntent("login:success", ({ payload }) => {
251
+ console.log("✅ login success:", payload.name)
252
+ })
253
+
254
+ // 3️⃣ Add side-effects (effects)
255
+ runtime.effect("login", next => async ctx => {
256
+ console.log("→ login start")
257
+ await next(ctx)
258
+ console.log("← login end")
259
+ })
260
+
261
+ // 4️⃣ Emit intent (entry point)
262
+ await runtime.emit("login", {
263
+ username: "admin",
264
+ password: "123456",
265
+ })
266
+
267
+ console.log(runtime.state().user)
268
+ /*
269
+ {
270
+ id: "u1",
271
+ name: "Admin"
272
+ }
273
+ */
274
+
275
+ // 5️⃣ Subscribe state changes (optional)
276
+ runtime.subscribe(() => {
277
+ console.log("🔄 state changed:", runtime.state())
278
+ })
279
+
280
+ // 6️⃣ Fake API (for demo)
281
+ async function fakeLoginApi(payload: {
282
+ username: string
283
+ password: string
284
+ }) {
285
+ await new Promise(r => setTimeout(r, 500))
286
+
287
+ return {
288
+ user: {
289
+ id: "u1",
290
+ name: payload.username,
291
+ },
292
+ token: "jwt-token",
293
+ }
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ #### 🧪 2. Testing Example
300
+ ```ts
301
+ await runtime.emit("login", {
302
+ username: "admin",
303
+ password: "123456",
304
+ })
305
+
306
+ expect(runtime.state().user?.name).toBe("admin")
307
+ expect(runtime.state().loading).toBe(false)
308
+
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Utility
314
+
315
+ #### 🧩 Plugins (Cross-cutting Concerns)
316
+
317
+ Plugins extend runtime behavior **without touching business logic**.
318
+
319
+ ```ts
320
+ import type { LogicPlugin } from "logic-runtime-react-z"
321
+
216
322
  export const persistPlugin: LogicPlugin = {
217
323
  name: "persist",
218
324
 
@@ -223,127 +329,281 @@ export const persistPlugin: LogicPlugin = {
223
329
  JSON.stringify(runtime.state)
224
330
  )
225
331
  })
226
- }
332
+ },
227
333
  }
334
+
228
335
  ```
229
336
 
230
337
  ---
231
338
 
232
- ##Async Effects + Selectors (Advanced Usage)
339
+ #### ⚡ Effects (Async / Side-effects)
233
340
 
234
- #### createSelector
235
- Selectors are pure functions that derive data from state.
236
- They are read-only, memoized, and safe to reuse anywhere (runtime, effects, intents).
341
+ Effects are **middleware-style intent interceptors**, not React effects.
342
+
343
+ ##### Built-in effects
344
+ - `takeLatest`
345
+ - `debounce`
346
+ - `retry`
347
+
348
+ ```ts
349
+ import { effect } from "logic-runtime-react-z"
350
+
351
+ const logEffect = effect(next => async ctx => {
352
+ console.log("→", ctx.intent)
353
+ await next(ctx)
354
+ console.log("←", ctx.intent)
355
+ })
356
+
357
+ intents: bus => {
358
+ bus.effect("save", logEffect)
359
+
360
+ bus.on("save", async ({ state }) => {
361
+ await api.save(state.form)
362
+ })
363
+ }
364
+
365
+ ```
366
+ ##### Built-in helpers
367
+ - takeLatest()
368
+ - debounce(ms)
369
+ - retry(times)
370
+
371
+ ```ts
372
+ bus.effect(
373
+ "search",
374
+ logEffect.debounce(300).takeLatest()
375
+ )
376
+ ```
377
+
378
+ ---
379
+
380
+ #### 🧮 Selectors
381
+
382
+ Selectors are pure, memoized, reusable functions.
237
383
 
238
384
  ```ts
239
385
  import { createSelector } from "logic-runtime-react-z"
240
386
 
241
387
  const selectIsAdult = createSelector(
242
- (state: { age: number }) => state.age,
243
- age => age >= 18
388
+ (state: { age: number }) => state.age >= 18
244
389
  )
390
+
245
391
  ```
246
392
 
247
- #### effect Async / side-effect layer
248
- Effects here are middleware-style intent interceptors, not React effects. They are used for:
249
- - logging
250
- - permission checks
251
- - retries / debounce
252
- - cancellation
253
- - async orchestration
393
+ #### 🧭 Devtools & Timeline
394
+
395
+ - Records every intent
396
+ - Replayable
397
+ - Deterministic async flow
254
398
 
255
399
  ```ts
256
- import { effect, debounce, retry, takeLatest } from "logic-runtime-react-z"
400
+ runtime.devtools?.timeline.replay(runtime.emit)
401
+ ```
402
+
403
+ ###### You can:
404
+
405
+ - time-travel intents
406
+ - replay async flows deterministically
407
+ - debug without UI
408
+
409
+ ---
410
+
411
+ ## 🔍 Comparison
412
+
413
+ | Feature | logic-runtime-react-z | Redux | Zustand |
414
+ | ------------------- | --------------------- | ----- | ------- |
415
+ | No-hook UI | ✅ | ❌ | ❌ |
416
+ | Intent-first | ✅ | ❌ | ❌ |
417
+ | Async orchestration | ✅ | ⚠️ | ⚠️ |
418
+ | Computed graph | ✅ | ❌ | ❌ |
419
+ | Headless testing | ✅ | ⚠️ | ⚠️ |
420
+ | Devtools replay | ✅ | ⚠️ | ❌ |
421
+
422
+ ---
257
423
 
258
- const withLogging = effect(next => {
259
- return async ctx => {
260
- console.log("saving...")
261
- await next(ctx)
424
+ ## 🚫 Anti-patterns (What NOT to do)
425
+
426
+ This library enforces a **clear separation between intent, behavior, and view**.
427
+ If you find yourself doing the following, you are probably fighting the architecture.
428
+
429
+ ---
430
+
431
+ #### ❌ 1. Putting business logic inside React components
432
+
433
+ ```tsx
434
+ // ❌ Don't do this
435
+ function Login() {
436
+ const [loading, setLoading] = useState(false)
437
+
438
+ async function handleLogin() {
439
+ setLoading(true)
440
+ const user = await api.login()
441
+ setLoading(false)
442
+ navigate("/home")
262
443
  }
444
+ }
445
+ ```
446
+ Why this is wrong
447
+ - Logic tied to React lifecycle
448
+ - Hard to test without rendering
449
+ - Side-effects scattered in UI
450
+
451
+ ✅ Correct
452
+
453
+ ```ts
454
+ runtime.emit("login")
455
+ ```
456
+
457
+ ```ts
458
+ bus.on("login", async ({ setState, emit }) => {
459
+ setState(s => { s.loading = true })
460
+ const user = await api.login()
461
+ setState(s => { s.loading = false })
462
+ emit("login:success", user)
263
463
  })
264
464
  ```
265
465
 
266
- #### Combining Effects + Selectors in Intents
466
+ #### 2. Calling handlers directly instead of emitting intent
267
467
  ```ts
268
- intents: bus => {
269
- // attach effect to intent
270
- bus.effect("save", withLogging)
468
+ // Don't call handlers manually
469
+ loginHandler(payload)
470
+ ```
471
+ Why this is wrong
271
472
 
272
- // intent handler (business rule)
273
- bus.on("save", ({ state }) => {
274
- if (!selectIsAdult(state)) {
275
- throw new Error("Not allowed")
276
- }
473
+ - Skips middleware & effects
474
+ - Breaks devtools timeline
475
+ - Makes behavior non-deterministic
277
476
 
278
- // perform save logic...
279
- })
477
+ Correct
280
478
 
281
- bus.on("save-user", async ({ state, setState }) => {
282
- await api.save(state.form)
283
- setState(s => {
284
- s.saved = true
285
- })
286
- })
479
+ ```ts
480
+ runtime.emit("login", payload)
481
+ ```
482
+ Intent is the only entry point. Always.
287
483
 
288
- // bus.effect("save", debounce(300))
289
- // bus.effect("save", retry(2))
290
- // bus.effect("save", takeLatest())
484
+ #### ❌ 3. Using effects to mutate state directly
485
+ ```ts
486
+ // Effect mutating state
487
+ bus.effect("save", next => async ctx => {
488
+ ctx.setState(s => { s.saving = true })
489
+ await next(ctx)
490
+ })
491
+ ```
291
492
 
292
- }
493
+ Why this is wrong
494
+
495
+ - Effects are orchestration, not business logic
496
+ - Hard to reason about ordering
497
+ - Blurs responsibility
498
+
499
+ ✅ Correct
500
+
501
+ ```ts
502
+ bus.on("save", ({ setState }) => {
503
+ setState(s => { s.saving = true })
504
+ })
505
+ ```
506
+ Effects should only:
507
+ - debounce
508
+ - retry
509
+ - cancel
510
+ - log
511
+ - trace
512
+
513
+ #### ❌ 4. Treating intent like Redux actions
514
+ ```ts
515
+ // ❌ Generic, meaningless intent
516
+ emit("SET_STATE", { loading: true })
293
517
  ```
294
518
 
295
- ---
519
+ Why this is wrong
296
520
 
297
- ## 🧭 Devtools & Timeline
298
- - Every intent is recorded
299
- - Replayable
300
- - Deterministic async flows
521
+ - Intent should describe user or system intention
522
+ - Not raw state mutation
523
+
524
+ Correct
301
525
 
302
526
  ```ts
303
- // devtool process.env.NODE_ENV !== "production"
304
- runtime.devtools?.timeline.replay(runtime.emit)
527
+ emit("login:start")
528
+ emit("login:success", user)
529
+ emit("login:failed", error)
305
530
  ```
531
+ Intents are verbs, not patches.
306
532
 
307
- ---
533
+ #### ❌ 5. Reading or mutating state outside the runtime
534
+ ```ts
535
+ // ❌ External mutation
536
+ runtime.state.user.name = "admin"
537
+ ```
538
+ Why this is wrong
539
+ - Breaks computed cache
540
+ - Bypasses subscriptions
541
+ - Devtools become unreliable
308
542
 
309
- ## Props
543
+ Correct
310
544
 
311
- | API field | Type | Description |
312
- | ---------- | ---------------------- | ---------------------------------------------------------------------------------- |
313
- | `name` | `string?` | Optional logic name. Used for debugging, devtools, and default runtime scope. |
314
- | `state` | `S` | **Base mutable state** (source of truth). Can only be changed via `setState`. |
315
- | `computed` | `ComputedDef<S, C>?` | **Derived read-only state**, automatically recomputed when base state changes. |
316
- | `intents` | `(bus) => void` | Defines **business actions** (intent handlers). Intents describe behavior, not UI. |
317
- | `plugins` | `LogicPlugin<S, C>[]?` | Runtime extensions (devtools, logging, persistence, analytics, etc.). |
545
+ ```ts
546
+ emit("update:user:name", "admin")
547
+ ```
318
548
 
319
- ---
549
+ #### ❌ 6. Using React hooks to replace runtime behavior
550
+ ```ts
551
+ // ❌ useEffect as orchestration
552
+ useEffect(() => {
553
+ if (state.loggedIn) {
554
+ fetchProfile()
555
+ }
556
+ }, [state.loggedIn])
557
+ ```
558
+ Why this is wrong
320
559
 
321
- ## 🔍 Comparison
560
+ - Behavior split across layers
561
+ - Impossible to replay or test headlessly
562
+
563
+ ✅ Correct
322
564
 
323
- | Feature | logic-runtime-react-z | Redux | Zustand |
324
- | --------------- | ----------------------- | ----- | -------- |
325
- | No-hook UI | ✅ | ❌ | ❌ |
326
- | Intent-first | ✅ | ❌ | ❌ |
327
- | Async built-in | ✅ | ⚠️ | ⚠️ |
328
- | Computed graph | ✅ | ❌ | ❌ |
329
- | Headless test | ✅ | ⚠️ | ⚠️ |
330
- | Devtools replay | ✅ | ⚠️ | ❌ |
565
+ ```ts
566
+ bus.on("login:success", async ({ emit }) => {
567
+ await emit("profile:fetch")
568
+ })
569
+ ```
570
+ #### 7. One logic runtime doing everything
571
+ ```ts
572
+ // God runtime
573
+ createLogic({
574
+ state: {
575
+ user: {},
576
+ cart: {},
577
+ products: {},
578
+ settings: {},
579
+ ui: {},
580
+ }
581
+ })
582
+ ```
583
+ Why this is wrong
584
+ - No ownership boundaries
585
+ - Hard to compose
586
+ - Does not scale
331
587
 
332
- > This comparison focuses on **architecture and mental model**, not ecosystem size.
588
+ Correct
333
589
 
334
- ---
590
+ ```ts
591
+ composeLogic(
592
+ userLogic,
593
+ cartLogic,
594
+ productLogic
595
+ )
596
+ ```
335
597
 
336
- ## 🚫 What this library is NOT
598
+ ---
337
599
 
338
- - Not a React state manager
339
- - ❌ Not a replacement for Redux Toolkit
340
- - ❌ Not a UI framework
341
- - ❌ Not tied to React (runtime is headless)
600
+ ## 🧠 Philosophy
342
601
 
343
- It is a **behavior runtime**.
602
+ - If it can’t be tested without React, it doesn’t belong in React.
603
+ - If it bypasses emit, it doesn’t belong in the system.
344
604
 
345
605
  ---
346
606
 
347
607
  ## 📜 License
348
608
 
349
- MIT
609
+ MIT / Delpi
@@ -0,0 +1,9 @@
1
+ export declare function createBackendRuntime<S extends object>(initial: S): {
2
+ state(): S;
3
+ setState(fn: (s: S) => void): void;
4
+ emit(intent: string, payload?: any): Promise<void>;
5
+ onIntent: (intent: string, handler: import("./types").IntentHandler<S, S, any>) => void;
6
+ effect: (intent: string, fx: import("./types").Effect<S, S, any>) => void;
7
+ use: (mw: import("./types").IntentMiddleware<S, S, any>) => void;
8
+ reset(): void;
9
+ };
@@ -1,5 +1,5 @@
1
- type LogicInstance = {
2
- state(): any;
1
+ type LogicInstance<S = any> = {
2
+ readonly state: S;
3
3
  emit?: (intent: string, payload?: any) => any | Promise<any>;
4
4
  subscribe(fn: () => void): () => void;
5
5
  };
@@ -5,8 +5,8 @@ export type Devtools = {
5
5
  };
6
6
  type RuntimeLike = {
7
7
  scope: string;
8
+ get state(): any;
8
9
  emit(intent: string, payload?: any): Promise<void>;
9
- state(): any;
10
10
  };
11
11
  export declare function attachDevtools(target: RuntimeLike): Devtools;
12
12
  export {};
@@ -1,4 +1,4 @@
1
- import type { IntentHandler, Effect } from "./types";
1
+ import type { IntentHandler, Effect, IntentMiddleware } from "./types";
2
2
  export declare function createIntentBus<W extends object, R extends object = W, P = any>(scope: string): {
3
3
  on: (intent: string, handler: IntentHandler<W, R, P>) => void;
4
4
  effect: (intent: string, fx: Effect<W, R, P>) => void;
@@ -7,4 +7,5 @@ export declare function createIntentBus<W extends object, R extends object = W,
7
7
  setState: (fn: (s: W) => void) => void;
8
8
  emit: (intent: string, payload?: any) => Promise<void>;
9
9
  }) => Promise<void>;
10
+ use: (mw: IntentMiddleware<W, R, P>) => void;
10
11
  };
@@ -0,0 +1,2 @@
1
+ export type IntentMiddleware<Ctx = any> = (ctx: Ctx, next: () => Promise<void>) => Promise<void>;
2
+ export declare function compose(middlewares: IntentMiddleware[]): (ctx: any, next: () => Promise<void>) => Promise<void>;
@@ -10,12 +10,15 @@ export type EffectCtx<W = any, R = W, P = any> = {
10
10
  };
11
11
  export type IntentHandler<W = any, R = W, P = any> = (ctx: EffectCtx<W, R, P>) => void | Promise<void>;
12
12
  export type Effect<W = any, R = W, P = any> = (next: IntentHandler<W, R, P>) => IntentHandler<W, R, P>;
13
+ export type IntentMiddleware<W = any, R = W, P = any> = (ctx: EffectCtx<W, R, P>, next: () => Promise<void>) => Promise<void>;
13
14
  export type LogicRuntime<S extends object, C extends object = {}> = {
14
15
  scope: string;
15
- state(): Readonly<S & C>;
16
+ get state(): Readonly<S & C>;
16
17
  setState(mutator: (s: S) => void): void;
17
18
  reset(): void;
19
+ batch(fn: () => void): void;
18
20
  emit(intent: string, payload?: any): Promise<void>;
21
+ use(mw: IntentMiddleware<S, S & C>): void;
19
22
  subscribe(fn: Listener): () => void;
20
23
  onIntent(intent: string, handler: IntentHandler<S, S & C>): void;
21
24
  devtools?: {
@@ -1 +1 @@
1
- "use strict";var t=require("react/jsx-runtime");function e(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}),e.default=t,Object.freeze(e)}var n,r,o=e(require("react"));function c(t){const e=new Map,n=new Map,r=new Map;return{on:function(t,n){const r=e.get(t)||[];r.push(n),e.set(t,r)},effect:function(t,e){const r=n.get(t)||[];r.push(e),n.set(t,r)},emit:async function(o,c,s){const a=(e=>`${t}:${e}`)(o),i=r.get(a);null==i||i.abort();const u=new AbortController;r.set(a,u);const l=e.get(o)||[],f=n.get(o)||[];for(const e of l){let n=e;for(let t=f.length-1;t>=0;t--)n=f[t](n);await n({state:s.getState(),payload:c,signal:u.signal,scope:t,emit:s.emit,setState:s.setState})}}}}function s(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:r=0,to:o=1/0,scope:c}=null!=n?n:{},s=e.filter(t=>"emit"===t.type&&t.id>=r&&t.id<=o&&(!c||t.scope===c));for(const e of s){const n=t(e.intent,e.payload);n instanceof Promise&&await n}},clear:function(){e=[],t=0}}}();return{timeline:e,wrap:function(){const n=t.emit.bind(t);t.emit=async(r,o)=>{e.record({type:"emit:start",intent:r,payload:o,scope:t.scope,state:t.state(),timestamp:Date.now()});try{await n(r,o)}finally{e.record({type:"emit:end",intent:r,payload:o,scope:t.scope,state:t.state(),timestamp:Date.now()})}}}}}let a=0;const i="production"!==(null===(r=null===(n=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===n?void 0:n.env)||void 0===r?void 0:r.NODE_ENV);const u=[],l=[];function f(){let t=null;return e=>async n=>(null==t||t.abort(),t=new AbortController,e({...n,signal:t.signal}))}function p(t){let e;return n=>r=>{clearTimeout(e),e=setTimeout(()=>n(r),t)}}function d(t=3){return e=>async n=>{let r;for(let o=0;o<t;o++)try{return await e(n)}catch(t){r=t}throw r}}exports.composeLogic=function(...t){return{create(e){const n=t.map(t=>{var n;if("logic"in t){const r=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:t.namespace,inst:r}}return{namespace:null,inst:t.create(e)}});return{get state(){const t={};for(const{namespace:e,inst:r}of n){const n=r.state();e?t[e]=n:Object.assign(t,n)}return t},async emit(t,e){const r=n.map(({inst:n})=>{var r;return null===(r=n.emit)||void 0===r?void 0:r.call(n,t,e)});return Promise.all(r.filter(Boolean))},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>{e.forEach(t=>t())}}}}}},exports.createLogic=function(t){var e;const n=null!==(e=t.name)&&void 0!==e?e:"logic";return{create(e){var r,o;const f=null!=e?e:`${n}:${++a}`,p=structuredClone(t.state),d=function(t){let e=t;const n=new Set;return{getState:function(){return e},setState:function(t){const r=e,o=structuredClone(r);t(o),Object.is(r,o)||(e=o,n.forEach(t=>t()))},subscribe:function(t){return n.add(t),()=>n.delete(t)}}}(structuredClone(t.state)),m=function(){const t=new Map,e=new Map,n=new Map;return{compute:function(n,r,o){if(t.has(n))return t.get(n);const c=new Set,s=r({state:new Proxy(o,{get:(t,e,n)=>("string"==typeof e&&e in t&&c.add(e),Reflect.get(t,e,n))})});return t.set(n,s),e.set(n,c),s},invalidate:function(r){e.forEach((e,o)=>{var c;e.has(r)&&(t.delete(o),null===(c=n.get(o))||void 0===c||c.forEach(t=>t()))})},subscribe:function(t,e){var r;const o=null!==(r=n.get(t))&&void 0!==r?r:new Set;return o.add(e),n.set(t,o),()=>o.delete(e)},reset:function(){t.clear(),e.clear(),n.forEach(t=>{t.forEach(t=>t())})}}}(),g=c(f);null===(r=t.intents)||void 0===r||r.call(t,g);let b=null,v=null;function y(){const e=d.getState();if(e===b&&v)return v;const n={};var r;return t.computed&&(r=t.computed,Object.keys(r)).forEach(r=>{n[r]=m.compute(r,t.computed[r],e)}),b=e,v=Object.assign({},e,n),v}function h(t){const e=d.getState();d.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&m.invalidate(t)})}const w={scope:f,state:()=>y(),setState:h,reset(){!function(){var t;d.setState(()=>structuredClone(p)),null===(t=m.reset)||void 0===t||t.call(m),b=null,v=null}()},async emit(t,e){const n=y();u.forEach(r=>r({intent:t,payload:e,state:n,scope:f}));try{await g.emit(t,e,{getState:y,setState:h,emit:w.emit})}finally{l.forEach(n=>n({intent:t,payload:e,state:y(),scope:f}))}},subscribe:d.subscribe,onIntent(t,e){g.on(t,e)},__internal:{onEmitStart(t){u.push(t)},onEmitEnd(t){l.push(t)}}};let S;return null===(o=t.plugins)||void 0===o||o.forEach(t=>t.setup(w)),i&&(S=s(w),S.wrap(w)),{...w,devtools:S}}}},exports.createSelector=function(t,e=Object.is){let n,r=null;return o=>{if(null!==r){const c=t(o);return e(n,c)?n:(n=c,r=o,c)}return r=o,n=t(o),n}},exports.createSignal=function(t){let e=t;const n=new Set;return{get:()=>e,set(t){Object.is(e,t)||(e=t,n.forEach(t=>t()))},subscribe:t=>(n.add(t),()=>n.delete(t))}},exports.debounce=p,exports.effect=function(t){const e=[t],n=t=>{return(n=e,t=>n.reduceRight((t,e)=>e(t),t))(t);var n};return n.takeLatest=()=>(e.push(f()),n),n.debounce=t=>(e.push(p(t)),n),n.retry=(t=3)=>(e.push(d(t)),n),n},exports.retry=d,exports.takeLatest=f,exports.withLogic=function(e,n){var r,c;const s=r=>{const c=o.useRef(null);c.current||(c.current=e.create());const s=c.current,a=o.useSyncExternalStore(s.subscribe,s.state,s.state),i=o.useCallback((t,e)=>s.emit(t,e),[s]);return t.jsx(n,{...r,state:a,emit:i})};return s.displayName=`withLogic(${null!==(c=null!==(r=n.displayName)&&void 0!==r?r:n.name)&&void 0!==c?c:"View"})`,s};
1
+ "use strict";var t=require("react/jsx-runtime");function e(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}),e.default=t,Object.freeze(e)}var n,r,o=e(require("react"));function a(t){const e=new Map,n=new Map,r=[],o=new Map;return{on:function(t,n){const r=e.get(t)||[];r.push(n),e.set(t,r)},effect:function(t,e){const r=n.get(t)||[];r.push(e),n.set(t,r)},emit:async function(a,c,s){const i=(e=>`${t}:${e}`)(a),u=o.get(i);null==u||u.abort();const l=new AbortController;o.set(i,l);const f=(d=r,async(t,e)=>{let n=-1;await async function r(o){if(o<=n)throw new Error("next() called multiple times");n=o;const a=d[o];if(!a)return e();await a(t,()=>r(o+1))}(0)});var d;const p=Array.from(e.entries()).filter(([t])=>function(t,e){return t.endsWith("/*")?e.startsWith(t.slice(0,-2)):t===e}(t,a)).flatMap(([,t])=>t),m=n.get(a)||[];for(const e of p){let n=e;for(let t=m.length-1;t>=0;t--)n=m[t](n);const r={state:s.getState(),payload:c,signal:l.signal,scope:t,emit:s.emit,setState:s.setState};await f(r,async()=>{await n(r)})}},use:function(t){r.push(t)}}}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:r=0,to:o=1/0,scope:a}=null!=n?n:{},c=e.filter(t=>"emit"===t.type&&t.id>=r&&t.id<=o&&(!a||t.scope===a));for(const e of c){const n=t(e.intent,e.payload);n instanceof Promise&&await n}},clear:function(){e=[],t=0}}}();return{timeline:e,wrap:function(){const n=t.emit.bind(t);t.emit=async(r,o)=>{e.record({type:"emit:start",intent:r,payload:o,scope:t.scope,state:t.state,timestamp:Date.now()});try{await n(r,o)}finally{e.record({type:"emit:end",intent:r,payload:o,scope:t.scope,state:t.state,timestamp:Date.now()})}}}}}let s=0;const i="production"!==(null===(r=null===(n=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===n?void 0:n.env)||void 0===r?void 0:r.NODE_ENV);function u(){let t=null;return e=>async n=>{null==t||t.abort(),t=new AbortController;const r=t.signal;if(!r.aborted)try{return await e({...n,signal:r})}catch(t){if(r.aborted)return;throw t}}}function l(t){let e;return n=>r=>new Promise((o,a)=>{clearTimeout(e),e=setTimeout(()=>{Promise.resolve(n(r)).then(o,a)},t)})}function f(t=3){return e=>async n=>{var r;let o;for(let a=0;a<t;a++)try{return await e(n)}catch(t){if(null===(r=n.signal)||void 0===r?void 0:r.aborted)return;o=t}throw o}}exports.attachDevtools=c,exports.composeLogic=function(...t){return{create(e){const n=t.map(t=>{var n;if("logic"in t){const r=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:t.namespace,inst:r}}return{namespace:null,inst:t.create(e)}});return{get state(){const t={};for(const{namespace:e,inst:r}of n){const n=r.state;e?t[e]=n:Object.assign(t,n)}return t},async emit(t,e){const r=n.map(({inst:n})=>{var r;return null===(r=n.emit)||void 0===r?void 0:r.call(n,t,e)});return Promise.all(r.filter(Boolean).map(t=>t.catch(t=>console.error(t))))},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>{e.forEach(t=>t())}}}}}},exports.createBackendRuntime=function(t){let e=structuredClone(t);const n=a("backend"),r={state:()=>e,setState(t){t(e)},async emit(t,o){await n.emit(t,o,{getState:()=>e,setState:t=>t(e),emit:r.emit})},onIntent:n.on,effect:n.effect,use:n.use,reset(){e=structuredClone(t)}};return r},exports.createLogic=function(t){var e;const n=null!==(e=t.name)&&void 0!==e?e:"logic";return{create(e){var r,o;const u=null!=e?e:`${n}:${++s}`,l=structuredClone(t.state),f=function(t){let e=t;const n=new Set;return{getState:function(){return e},setState:function(t){const r=e,o=structuredClone(r);t(o),Object.is(r,o)||(e=o,n.forEach(t=>t()))},subscribe:function(t){return n.add(t),()=>n.delete(t)}}}(structuredClone(t.state)),d=function(){const t=new Map,e=new Map,n=new Map;return{compute:function(n,r,o){if(t.has(n))return t.get(n);const a=new Set,c=r({state:new Proxy(o,{get:(t,e,n)=>("string"==typeof e&&e in t&&a.add(e),Reflect.get(t,e,n))})});return t.set(n,c),e.set(n,a),c},invalidate:function(r){e.forEach((e,o)=>{var a;e.has(r)&&(t.delete(o),null===(a=n.get(o))||void 0===a||a.forEach(t=>t()))})},subscribe:function(t,e){var r;const o=null!==(r=n.get(t))&&void 0!==r?r:new Set;return o.add(e),n.set(t,o),()=>o.delete(e)},reset:function(){t.clear(),e.clear(),n.forEach(t=>{t.forEach(t=>t())})}}}(),p=a(u);null===(r=t.intents)||void 0===r||r.call(t,p);const m=[],b=[];let h=null,g=null,y=!1,w=new Set;function v(){const e=f.getState();if(e===h&&g)return g;const n={};var r;return t.computed&&(r=t.computed,Object.keys(r)).forEach(r=>{n[r]=d.compute(r,t.computed[r],e)}),h=e,g=Object.assign({},e,n),g}function E(t){const e=f.getState();f.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&(y?w.add(t):d.invalidate(t))})}const S={scope:u,get state(){return v()},setState:E,reset:function(){var t;f.setState(()=>structuredClone(l)),null===(t=d.reset)||void 0===t||t.call(d),h=null,g=null},batch:function(t){y=!0;try{t()}finally{y=!1,w.forEach(t=>d.invalidate(t)),w.clear()}},async emit(t,e){const n=v();m.forEach(r=>r({intent:t,payload:e,state:n,scope:u}));try{await p.emit(t,e,{getState:v,setState:E,emit:S.emit})}finally{b.forEach(n=>n({intent:t,payload:e,state:v(),scope:u}))}},subscribe:f.subscribe,onIntent(t,e){p.on(t,e)},use:p.use,__internal:{onEmitStart(t){m.push(t)},onEmitEnd(t){b.push(t)}}};let x;return null===(o=t.plugins)||void 0===o||o.forEach(t=>t.setup(S)),i&&(x=c(S),x.wrap(S)),{...S,devtools:x}}}},exports.createSelector=function(t,e=Object.is){let n,r=null;return o=>{if(null!==r){const a=t(o);return e(n,a)?n:(n=a,r=o,a)}return r=o,n=t(o),n}},exports.createSignal=function(t){let e=t;const n=new Set;return{get:()=>e,set(t){Object.is(e,t)||(e=t,n.forEach(t=>t()))},subscribe:t=>(n.add(t),()=>n.delete(t))}},exports.debounce=l,exports.effect=function(t){const e=[t];let n=!1;const r=t=>{return(n=!0,r=e,t=>r.reduceRight((t,e)=>e(t),t))(t);var r};return r.takeLatest=()=>{if(n)throw new Error("Effect already built");return e.push(u()),r},r.debounce=t=>{if(n)throw new Error("Effect already built");return e.push(l(t)),r},r.retry=(t=3)=>{if(n)throw new Error("Effect already built");return e.push(f(t)),r},r},exports.retry=f,exports.takeLatest=u,exports.withLogic=function(e,n){var r,a;const c=r=>{const a=o.useRef(null);a.current||(a.current=e.create());const c=a.current,s=o.useSyncExternalStore(c.subscribe,()=>c.state,()=>c.state),i=o.useCallback((t,e)=>c.emit(t,e),[c]);return t.jsx(n,{...r,state:s,emit:i})};return c.displayName=`withLogic(${null!==(a=null!==(r=n.displayName)&&void 0!==r?r:n.name)&&void 0!==a?a:"View"})`,c};
package/build/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export { createSignal } from "./core/signal";
2
2
  export { createLogic } from "./core/logic";
3
3
  export { composeLogic } from "./core/compose";
4
- export { effect, takeLatest, debounce, retry } from "./core/effect";
4
+ export { effect, takeLatest, debounce, retry, } from "./core/effect";
5
5
  export { createSelector } from "./core/selector";
6
- export type { LogicPlugin } from "./core/plugin";
7
- export type { LogicRuntime } from "./core/types";
8
- export type { Timeline, IntentRecord } from "./core/timeline";
6
+ export { createBackendRuntime } from "./core/backend-runtime";
7
+ export type { LogicPlugin, } from "./core/plugin";
8
+ export type { LogicRuntime, IntentHandler, Effect, EffectCtx, IntentMiddleware, Listener, } from "./core/types";
9
+ export { attachDevtools } from "./core/devtools";
10
+ export type { Timeline, IntentRecord, } from "./core/timeline";
9
11
  export { withLogic } from "./react/withLogic";
@@ -1 +1 @@
1
- import{jsx as t}from"react/jsx-runtime";import*as e from"react";function n(t){let e=t;const n=new Set;return{get:()=>e,set(t){Object.is(e,t)||(e=t,n.forEach(t=>t()))},subscribe:t=>(n.add(t),()=>n.delete(t))}}function o(t){const e=new Map,n=new Map,o=new Map;return{on:function(t,n){const o=e.get(t)||[];o.push(n),e.set(t,o)},effect:function(t,e){const o=n.get(t)||[];o.push(e),n.set(t,o)},emit:async function(r,s,c){const a=(e=>`${t}:${e}`)(r),i=o.get(a);null==i||i.abort();const u=new AbortController;o.set(a,u);const l=e.get(r)||[],f=n.get(r)||[];for(const e of l){let n=e;for(let t=f.length-1;t>=0;t--)n=f[t](n);await n({state:c.getState(),payload:s,signal:u.signal,scope:t,emit:c.emit,setState:c.setState})}}}}function r(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:o=0,to:r=1/0,scope:s}=null!=n?n:{},c=e.filter(t=>"emit"===t.type&&t.id>=o&&t.id<=r&&(!s||t.scope===s));for(const e of c){const n=t(e.intent,e.payload);n instanceof Promise&&await n}},clear:function(){e=[],t=0}}}();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:t.scope,state:t.state(),timestamp:Date.now()});try{await n(o,r)}finally{e.record({type:"emit:end",intent:o,payload:r,scope:t.scope,state:t.state(),timestamp:Date.now()})}}}}}var s,c;let a=0;const i="production"!==(null===(c=null===(s=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===s?void 0:s.env)||void 0===c?void 0:c.NODE_ENV);const u=[],l=[];function f(t){var e;const n=null!==(e=t.name)&&void 0!==e?e:"logic";return{create(e){var s,c;const f=null!=e?e:`${n}:${++a}`,d=structuredClone(t.state),p=function(t){let e=t;const n=new Set;return{getState:function(){return e},setState:function(t){const o=e,r=structuredClone(o);t(r),Object.is(o,r)||(e=r,n.forEach(t=>t()))},subscribe:function(t){return n.add(t),()=>n.delete(t)}}}(structuredClone(t.state)),m=function(){const t=new Map,e=new Map,n=new Map;return{compute:function(n,o,r){if(t.has(n))return t.get(n);const s=new Set,c=o({state:new Proxy(r,{get:(t,e,n)=>("string"==typeof e&&e in t&&s.add(e),Reflect.get(t,e,n))})});return t.set(n,c),e.set(n,s),c},invalidate:function(o){e.forEach((e,r)=>{var s;e.has(o)&&(t.delete(r),null===(s=n.get(r))||void 0===s||s.forEach(t=>t()))})},subscribe:function(t,e){var o;const r=null!==(o=n.get(t))&&void 0!==o?o:new Set;return r.add(e),n.set(t,r),()=>r.delete(e)},reset:function(){t.clear(),e.clear(),n.forEach(t=>{t.forEach(t=>t())})}}}(),g=o(f);null===(s=t.intents)||void 0===s||s.call(t,g);let b=null,v=null;function h(){const e=p.getState();if(e===b&&v)return v;const n={};var o;return t.computed&&(o=t.computed,Object.keys(o)).forEach(o=>{n[o]=m.compute(o,t.computed[o],e)}),b=e,v=Object.assign({},e,n),v}function y(t){const e=p.getState();p.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&m.invalidate(t)})}const w={scope:f,state:()=>h(),setState:y,reset(){!function(){var t;p.setState(()=>structuredClone(d)),null===(t=m.reset)||void 0===t||t.call(m),b=null,v=null}()},async emit(t,e){const n=h();u.forEach(o=>o({intent:t,payload:e,state:n,scope:f}));try{await g.emit(t,e,{getState:h,setState:y,emit:w.emit})}finally{l.forEach(n=>n({intent:t,payload:e,state:h(),scope:f}))}},subscribe:p.subscribe,onIntent(t,e){g.on(t,e)},__internal:{onEmitStart(t){u.push(t)},onEmitEnd(t){l.push(t)}}};let S;return null===(c=t.plugins)||void 0===c||c.forEach(t=>t.setup(w)),i&&(S=r(w),S.wrap(w)),{...w,devtools:S}}}}function d(...t){return{create(e){const n=t.map(t=>{var n;if("logic"in t){const o=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:t.namespace,inst:o}}return{namespace:null,inst:t.create(e)}});return{get state(){const t={};for(const{namespace:e,inst:o}of n){const n=o.state();e?t[e]=n:Object.assign(t,n)}return t},async emit(t,e){const o=n.map(({inst:n})=>{var o;return null===(o=n.emit)||void 0===o?void 0:o.call(n,t,e)});return Promise.all(o.filter(Boolean))},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>{e.forEach(t=>t())}}}}}}function p(){let t=null;return e=>async n=>(null==t||t.abort(),t=new AbortController,e({...n,signal:t.signal}))}function m(t){let e;return n=>o=>{clearTimeout(e),e=setTimeout(()=>n(o),t)}}function g(t=3){return e=>async n=>{let o;for(let r=0;r<t;r++)try{return await e(n)}catch(t){o=t}throw o}}function b(t){const e=[t],n=t=>{return(n=e,t=>n.reduceRight((t,e)=>e(t),t))(t);var n};return n.takeLatest=()=>(e.push(p()),n),n.debounce=t=>(e.push(m(t)),n),n.retry=(t=3)=>(e.push(g(t)),n),n}function v(t,e=Object.is){let n,o=null;return r=>{if(null!==o){const s=t(r);return e(n,s)?n:(n=s,o=r,s)}return o=r,n=t(r),n}}function h(n,o){var r,s;const c=r=>{const s=e.useRef(null);s.current||(s.current=n.create());const c=s.current,a=e.useSyncExternalStore(c.subscribe,c.state,c.state),i=e.useCallback((t,e)=>c.emit(t,e),[c]);return t(o,{...r,state:a,emit:i})};return c.displayName=`withLogic(${null!==(s=null!==(r=o.displayName)&&void 0!==r?r:o.name)&&void 0!==s?s:"View"})`,c}export{d as composeLogic,f as createLogic,v as createSelector,n as createSignal,m as debounce,b as effect,g as retry,p as takeLatest,h as withLogic};
1
+ import{jsx as t}from"react/jsx-runtime";import*as e from"react";function n(t){let e=t;const n=new Set;return{get:()=>e,set(t){Object.is(e,t)||(e=t,n.forEach(t=>t()))},subscribe:t=>(n.add(t),()=>n.delete(t))}}function r(t){const e=new Map,n=new Map,r=[],o=new Map;return{on:function(t,n){const r=e.get(t)||[];r.push(n),e.set(t,r)},effect:function(t,e){const r=n.get(t)||[];r.push(e),n.set(t,r)},emit:async function(a,s,c){const i=(e=>`${t}:${e}`)(a),u=o.get(i);null==u||u.abort();const l=new AbortController;o.set(i,l);const f=(d=r,async(t,e)=>{let n=-1;await async function r(o){if(o<=n)throw new Error("next() called multiple times");n=o;const a=d[o];if(!a)return e();await a(t,()=>r(o+1))}(0)});var d;const p=Array.from(e.entries()).filter(([t])=>function(t,e){return t.endsWith("/*")?e.startsWith(t.slice(0,-2)):t===e}(t,a)).flatMap(([,t])=>t),m=n.get(a)||[];for(const e of p){let n=e;for(let t=m.length-1;t>=0;t--)n=m[t](n);const r={state:c.getState(),payload:s,signal:l.signal,scope:t,emit:c.emit,setState:c.setState};await f(r,async()=>{await n(r)})}},use:function(t){r.push(t)}}}function o(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:r=0,to:o=1/0,scope:a}=null!=n?n:{},s=e.filter(t=>"emit"===t.type&&t.id>=r&&t.id<=o&&(!a||t.scope===a));for(const e of s){const n=t(e.intent,e.payload);n instanceof Promise&&await n}},clear:function(){e=[],t=0}}}();return{timeline:e,wrap:function(){const n=t.emit.bind(t);t.emit=async(r,o)=>{e.record({type:"emit:start",intent:r,payload:o,scope:t.scope,state:t.state,timestamp:Date.now()});try{await n(r,o)}finally{e.record({type:"emit:end",intent:r,payload:o,scope:t.scope,state:t.state,timestamp:Date.now()})}}}}}var a,s;let c=0;const i="production"!==(null===(s=null===(a=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===a?void 0:a.env)||void 0===s?void 0:s.NODE_ENV);function u(t){var e;const n=null!==(e=t.name)&&void 0!==e?e:"logic";return{create(e){var a,s;const u=null!=e?e:`${n}:${++c}`,l=structuredClone(t.state),f=function(t){let e=t;const n=new Set;return{getState:function(){return e},setState:function(t){const r=e,o=structuredClone(r);t(o),Object.is(r,o)||(e=o,n.forEach(t=>t()))},subscribe:function(t){return n.add(t),()=>n.delete(t)}}}(structuredClone(t.state)),d=function(){const t=new Map,e=new Map,n=new Map;return{compute:function(n,r,o){if(t.has(n))return t.get(n);const a=new Set,s=r({state:new Proxy(o,{get:(t,e,n)=>("string"==typeof e&&e in t&&a.add(e),Reflect.get(t,e,n))})});return t.set(n,s),e.set(n,a),s},invalidate:function(r){e.forEach((e,o)=>{var a;e.has(r)&&(t.delete(o),null===(a=n.get(o))||void 0===a||a.forEach(t=>t()))})},subscribe:function(t,e){var r;const o=null!==(r=n.get(t))&&void 0!==r?r:new Set;return o.add(e),n.set(t,o),()=>o.delete(e)},reset:function(){t.clear(),e.clear(),n.forEach(t=>{t.forEach(t=>t())})}}}(),p=r(u);null===(a=t.intents)||void 0===a||a.call(t,p);const m=[],h=[];let w=null,b=null,y=!1,v=new Set;function g(){const e=f.getState();if(e===w&&b)return b;const n={};var r;return t.computed&&(r=t.computed,Object.keys(r)).forEach(r=>{n[r]=d.compute(r,t.computed[r],e)}),w=e,b=Object.assign({},e,n),b}function E(t){const e=f.getState();f.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&(y?v.add(t):d.invalidate(t))})}const S={scope:u,get state(){return g()},setState:E,reset:function(){var t;f.setState(()=>structuredClone(l)),null===(t=d.reset)||void 0===t||t.call(d),w=null,b=null},batch:function(t){y=!0;try{t()}finally{y=!1,v.forEach(t=>d.invalidate(t)),v.clear()}},async emit(t,e){const n=g();m.forEach(r=>r({intent:t,payload:e,state:n,scope:u}));try{await p.emit(t,e,{getState:g,setState:E,emit:S.emit})}finally{h.forEach(n=>n({intent:t,payload:e,state:g(),scope:u}))}},subscribe:f.subscribe,onIntent(t,e){p.on(t,e)},use:p.use,__internal:{onEmitStart(t){m.push(t)},onEmitEnd(t){h.push(t)}}};let C;return null===(s=t.plugins)||void 0===s||s.forEach(t=>t.setup(S)),i&&(C=o(S),C.wrap(S)),{...S,devtools:C}}}}function l(...t){return{create(e){const n=t.map(t=>{var n;if("logic"in t){const r=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:t.namespace,inst:r}}return{namespace:null,inst:t.create(e)}});return{get state(){const t={};for(const{namespace:e,inst:r}of n){const n=r.state;e?t[e]=n:Object.assign(t,n)}return t},async emit(t,e){const r=n.map(({inst:n})=>{var r;return null===(r=n.emit)||void 0===r?void 0:r.call(n,t,e)});return Promise.all(r.filter(Boolean).map(t=>t.catch(t=>console.error(t))))},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>{e.forEach(t=>t())}}}}}}function f(){let t=null;return e=>async n=>{null==t||t.abort(),t=new AbortController;const r=t.signal;if(!r.aborted)try{return await e({...n,signal:r})}catch(t){if(r.aborted)return;throw t}}}function d(t){let e;return n=>r=>new Promise((o,a)=>{clearTimeout(e),e=setTimeout(()=>{Promise.resolve(n(r)).then(o,a)},t)})}function p(t=3){return e=>async n=>{var r;let o;for(let a=0;a<t;a++)try{return await e(n)}catch(t){if(null===(r=n.signal)||void 0===r?void 0:r.aborted)return;o=t}throw o}}function m(t){const e=[t];let n=!1;const r=t=>{return(n=!0,r=e,t=>r.reduceRight((t,e)=>e(t),t))(t);var r};return r.takeLatest=()=>{if(n)throw new Error("Effect already built");return e.push(f()),r},r.debounce=t=>{if(n)throw new Error("Effect already built");return e.push(d(t)),r},r.retry=(t=3)=>{if(n)throw new Error("Effect already built");return e.push(p(t)),r},r}function h(t,e=Object.is){let n,r=null;return o=>{if(null!==r){const a=t(o);return e(n,a)?n:(n=a,r=o,a)}return r=o,n=t(o),n}}function w(t){let e=structuredClone(t);const n=r("backend"),o={state:()=>e,setState(t){t(e)},async emit(t,r){await n.emit(t,r,{getState:()=>e,setState:t=>t(e),emit:o.emit})},onIntent:n.on,effect:n.effect,use:n.use,reset(){e=structuredClone(t)}};return o}function b(n,r){var o,a;const s=o=>{const a=e.useRef(null);a.current||(a.current=n.create());const s=a.current,c=e.useSyncExternalStore(s.subscribe,()=>s.state,()=>s.state),i=e.useCallback((t,e)=>s.emit(t,e),[s]);return t(r,{...o,state:c,emit:i})};return s.displayName=`withLogic(${null!==(a=null!==(o=r.displayName)&&void 0!==o?o:r.name)&&void 0!==a?a:"View"})`,s}export{o as attachDevtools,l as composeLogic,w as createBackendRuntime,u as createLogic,h as createSelector,n as createSignal,d as debounce,m as effect,p as retry,f as takeLatest,b as withLogic};
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  type LogicInstance<S> = {
3
- state(): Readonly<S>;
3
+ readonly state: Readonly<S>;
4
4
  emit(intent: string, payload?: any): Promise<void>;
5
5
  subscribe(fn: () => void): () => void;
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logic-runtime-react-z",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Intent-first runtime for React. No hooks. Deterministic state. Orchestrated effects.",
5
5
  "license": "MIT",
6
6
  "author": "Delpi.Kye",