logic-runtime-react-z 1.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,349 +1,399 @@
1
- ## ⚙️ logic-runtime-react-z
1
+ # ⚙️ logic-runtime-react-z
2
2
 
3
- [![NPM](https://img.shields.io/npm/v/logic-runtime-react-z.svg)](https://www.npmjs.com/package/logic-runtime-react-z)
4
- ![Downloads](https://img.shields.io/npm/dt/logic-runtime-react-z.svg)
3
+ [![NPM](https://img.shields.io/npm/v/logic-runtime-react-z.svg)](https://www.npmjs.com/package/logic-runtime-react-z) ![Downloads](https://img.shields.io/npm/dt/logic-runtime-react-z.svg)
5
4
 
6
5
  <a href="https://codesandbox.io/p/sandbox/x3jf32" target="_blank">LIVE EXAMPLE</a>
7
6
 
8
- ---
9
-
10
- **logic-runtime-react-z** is an intent-first external runtime,
11
- designed to run business logic **outside React**.
7
+ **Intent-first business logic runtime**
8
+ React is a view. Logic lives elsewhere.
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:
10
+ ---
15
11
 
16
- > Think of it as:
17
- > **“Business logic runtime outside React — React is just the view.”**
12
+ ## Core Idea
18
13
 
19
- ---
14
+ > **Business logic lives outside React. React only renders state and emits intent.**
20
15
 
21
- ## 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
25
- - Async flows are complex (login → fetch → redirect)
26
- - Side effects must be predictable & testable
27
- - You want headless tests without rendering
28
- - You prefer architecture-driven design over component-driven logic
16
+ * No React hooks in views
17
+ * Intent is the only entry point
18
+ * Predictable async flows
19
+ * Headless & backend-friendly
20
+ * Fully testable without rendering
29
21
 
30
22
  ---
31
23
 
32
24
  ## 🧠 Mental Model
33
25
 
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
26
  ```
46
-
47
- - React is just an adapter.
48
- - The runtime owns behavior.
27
+ UI / HTTP / Queue / Cron
28
+
29
+ emit(intent)
30
+
31
+ middleware / effects
32
+
33
+ intent handlers
34
+
35
+ mutate state
36
+
37
+ computed / subscribers
38
+ ```
49
39
 
50
40
  ---
51
41
 
52
42
  ## 📦 Installation
53
- ```ts
43
+
44
+ ```bash
54
45
  npm install logic-runtime-react-z
55
46
  ```
47
+
56
48
  ---
57
49
 
58
- ## Basic
50
+ ## 🚀 Quick Start (Headless)
51
+
59
52
  ```ts
60
- const logic = createLogic({
53
+ import { createLogic } from "logic-runtime-react-z"
54
+
55
+ const counterLogic = createLogic({
61
56
  state: { count: 0 },
62
57
  intents: bus => {
63
- bus.on("inc", ({ setState }) => {
64
- setState(s => { s.count++ })
65
- })
66
- }
58
+ bus.on("inc", ({ setState }) => setState(s => { s.count++ }))
59
+ bus.on("add", ({ payload, setState }) => setState(s => { s.count += payload }))
60
+ },
67
61
  })
68
62
 
69
- const runtime = logic.create()
70
- runtime.emit("inc")
63
+ const runtime = counterLogic.create()
64
+ await runtime.emit("inc")
65
+ await runtime.emit("add", 5)
66
+ console.log(runtime.state.count) // 6
67
+
71
68
  ```
72
69
 
73
70
  ---
74
71
 
75
- ## ⚛️ React Usage (No Hooks in View)
72
+ ## ⚛️ React Integration (No Hooks in View)
76
73
 
77
74
  ```ts
78
- import { createLogic, withLogic, createSelector } from "logic-runtime-react-z"
75
+ import { createLogic, effect, withLogic } from "logic-runtime-react-z"
79
76
 
80
77
  interface State {
81
78
  count: number;
82
-
83
- // computed
84
- double: number
79
+ loading: boolean;
80
+ double: number;
85
81
  }
86
82
 
83
+ // Async effect for takeLatest behavior
84
+ const asyncEffect = effect(async ({ payload, setState }) => {
85
+ console.log("Effect fired for payload:", payload);
86
+ }).takeLatest()
87
+
87
88
  const counterLogic = createLogic({
88
89
  name: "counter",
89
- state: { count: 1 },
90
-
91
- computed: {
92
- double({ state }): number {
93
- return state.count * 2
94
- }
95
- },
96
-
90
+ state: { count: 1, loading: false },
91
+ computed: { double: ({ state }) => state.count * 2 },
97
92
  intents: bus => {
98
- bus.on("inc", ({ state, setState }) => {
99
- // if (!selectIsAdult(state)) {
100
- // throw new Error("Not allowed")
101
- // }
102
-
103
- setState(s => {
104
- s.count++
105
- })
93
+ bus.on("inc", ({ setState }) => setState(s => { s.count++ }))
94
+ bus.on("inc-async", async ({ payload, setState }) => {
95
+ setState(s => { s.loading = true })
96
+ await new Promise(r => setTimeout(r, 5000))
97
+ setState(s => { s.count += payload; s.loading = false })
106
98
  })
99
+ bus.effect("inc-async", asyncEffect)
107
100
  },
108
101
  })
109
102
 
110
- // Pure View (No Hooks)
111
- function CounterView({
112
- state,
113
- emit,
114
- }: {
115
- state: State
116
- emit: (intent: string) => void
117
- }) {
103
+ // React view (pure, no hooks)
104
+ function CounterView({ state, emit }: { state: State; emit: (intent: string, payload?: any) => void | Promise<void> }) {
118
105
  return (
119
106
  <div>
120
107
  <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>
108
+ <button disabled={state.loading} onClick={() => emit("inc")}>Plus</button>
109
+ <button disabled={state.loading} onClick={() => emit("inc-async", 100)}>Async +100</button>
133
110
  <div>Double: {state.double}</div>
134
- <button onClick={() => emit("inc")}>+</button>
135
111
  </div>
136
112
  )
137
113
  }
138
114
 
139
- // Bind Logic to React
140
115
  export const Counter = withLogic(counterLogic, CounterView)
116
+
141
117
  ```
142
118
 
143
- ## 🧪 No React
144
- ```ts
119
+ ---
145
120
 
146
- // import { createLogic } from "logic-runtime-react-z"
147
- const counterLogic = createLogic({
148
- state: {
149
- count: 0
150
- },
121
+ ## 🧪 Middleware Example (Backend)
151
122
 
152
- computed: {
153
- double({ state }) {
154
- return state.count * 2
155
- }
156
- },
123
+ ```ts
124
+ import { createBackendRuntime } from "logic-runtime-react-z"
157
125
 
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
- })
126
+ // Create runtime with initial state
127
+ const runtime = createBackendRuntime({
128
+ user: null,
129
+ loading: false,
130
+ })
168
131
 
169
- // Intent handlers can be sync or async
170
- bus.on("add", ({ payload, setState }) => {
171
- setState(s => {
172
- s.count += payload
173
- })
174
- })
132
+ // Optional: attach devtools in dev mode
133
+ const devtools = runtime.devtools
134
+
135
+ // Register some intents
136
+ runtime.onIntent("login", async ({ payload, setState }) => {
137
+ setState(s => { s.loading = true })
138
+ // simulate async login
139
+ const user = await fakeLoginApi(payload)
140
+ setState(s => {
141
+ s.user = user
142
+ s.loading = false
143
+ })
144
+ })
175
145
 
176
- bus.on("reset", ({ setState }) => {
177
- setState(s => {
178
- s.count = 0
179
- })
180
- })
181
- },
146
+ runtime.onIntent("logout", ({ setState }) => {
147
+ setState(s => { s.user = null })
182
148
  })
183
149
 
184
- // initial
185
- // const runtime = counterLogic.create()
186
- const runtime = counterLogic.create("counter:main")
150
+ // Emit some intents
151
+ await runtime.emit("login", { username: "alice", password: "123" })
152
+ await runtime.emit("logout")
153
+
154
+ // ----------------- Using devtools -----------------
187
155
 
188
- runtime.emit("inc")
189
- // await runtime.emit("inc") // if async
190
- runtime.emit("add", 5)
156
+ // 1️⃣ Access timeline records
157
+ console.log("Timeline records:", devtools.timeline.records)
158
+
159
+ // 2️⃣ Replay intents
160
+ await devtools.timeline.replay(runtime.emit, { scope: "backend" })
161
+
162
+ // 3️⃣ Clear timeline
163
+ devtools.timeline.clear()
164
+ console.log("Timeline cleared:", devtools.timeline.records)
191
165
 
192
- // console.log
193
- console.log(runtime.state.count) // 7
194
- console.log(runtime.state.double) // 14
195
166
  ```
196
167
 
197
168
  ---
198
169
 
199
- ## 🧩 Plugins Runtime extensions (cross-cutting concerns)
200
-
201
- #### Plugins extend the runtime behavior **without touching business logic**.
170
+ ## 🧪 Unit Test Example (Headless)
202
171
 
203
172
  ```ts
204
- export const authPlugin: LogicPlugin = {
205
- name: "auth",
206
-
207
- setup(runtime) {
208
- runtime.onIntent("delete", ctx => {
209
- if (!ctx.state.user?.isAdmin) {
210
- throw new Error("Forbidden")
211
- }
212
- })
173
+ const logic = createLogic({
174
+ state: { value: 0 },
175
+ computed: { squared: ({ state }) => state.value * state.value },
176
+ intents: bus => {
177
+ bus.on("set", ({ payload, setState }) => setState(s => { s.value = payload }))
213
178
  }
214
- }
179
+ })
215
180
 
216
- export const persistPlugin: LogicPlugin = {
217
- name: "persist",
181
+ const runtime = logic.create()
182
+ await runtime.emit("set", 4)
183
+ expect(runtime.state.squared).toBe(16)
218
184
 
219
- setup(runtime) {
220
- runtime.subscribe(() => {
221
- localStorage.setItem(
222
- runtime.scope,
223
- JSON.stringify(runtime.state)
224
- )
225
- })
226
- }
227
- }
228
185
  ```
229
186
 
230
187
  ---
231
188
 
232
- ## Async Effects + Selectors (Advanced Usage)
189
+ ## 🔍 Comparison
233
190
 
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).
191
+ | Feature | logic-runtime-react-z | Redux | Zustand | Recoil/Jotai |
192
+ | --------------------------- | ------------------------ | -------------------- | -------- | -------------- |
193
+ | Intent-first | ✅ | ❌ | ❌ | ❌ |
194
+ | Headless / backend-friendly | ✅ | ⚠️ | ⚠️ | ❌ |
195
+ | Async orchestration | ✅ (takeLatest, debounce) | ⚠️ (middleware add ) | ⚠️ | ⚠️ |
196
+ | Computed graph | ✅ | ❌ | ❌ | ✅ (atom deps) |
197
+ | Devtools replay async | ✅ | ⚠️ | ❌ | ⚠️ |
198
+ | UI-agnostic | ✅ | ⚠️ | ⚠️ | ❌ |
199
+ | Deterministic testability | ✅ | ⚠️ | ⚠️ | ⚠️ |
237
200
 
238
- ```ts
239
- import { createSelector } from "logic-runtime-react-z"
240
201
 
241
- const selectIsAdult = createSelector(
242
- (state: { age: number }) => state.age,
243
- age => age >= 18
244
- )
202
+ ---
203
+
204
+ ## ⚖️ Comparison with Vue2
205
+
206
+ While logic-runtime-react-z uses a **reactive + computed pattern** similar to Vue2, the behavior is quite different:
207
+
208
+ | Feature | Vue2 | logic-runtime-react-z |
209
+ |---------------------------|----------------------- |---------------------------------------------------- |
210
+ | Reactive base state | ✅ proxy | ✅ store + computed tracking. |
211
+ | Computed | ✅ | ✅ dependency tracking + invalidation. |
212
+ | Intent-driven flow | ❌ | ✅ all actions go through `emit(intent)`. |
213
+ | Async orchestration | ❌ | ✅ effects + middleware (takeLatest, debounce, etc.) |
214
+ | Headless / backend-ready | ❌ | ✅ can run without React/UI |
215
+ | Deterministic testing | ❌ | ✅ full headless tests possible |
216
+ | Devtools replay | ❌ | ✅ timeline tracking & replay |
217
+
218
+ > **Takeaway:** It feels familiar if you know Vue2 reactivity, but under the hood it's **intent-first, headless, and fully testable**, unlike Vue2.
219
+
220
+ ---
221
+
222
+ ## 🚫 Anti-patterns (What NOT to do)
223
+
224
+ This library enforces a **clear separation between intent, behavior, and view**.
225
+ If you find yourself doing the following, you are probably fighting the architecture.
226
+
227
+
228
+ #### ❌ 1. Putting business logic inside React components
229
+
230
+ ```tsx
231
+ // ❌ Don't do this
232
+ function Login() {
233
+ const [loading, setLoading] = useState(false)
234
+
235
+ async function handleLogin() {
236
+ setLoading(true)
237
+ const user = await api.login()
238
+ setLoading(false)
239
+ navigate("/home")
240
+ }
241
+ }
245
242
  ```
243
+ Why this is wrong
244
+ - Logic tied to React lifecycle
245
+ - Hard to test without rendering
246
+ - Side-effects scattered in UI
246
247
 
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
248
+ Correct
254
249
 
255
250
  ```ts
256
- import { effect, debounce, retry, takeLatest } from "logic-runtime-react-z"
251
+ runtime.emit("login")
252
+ ```
257
253
 
258
- const withLogging = effect(next => {
259
- return async ctx => {
260
- console.log("saving...")
261
- await next(ctx)
262
- }
254
+ ```ts
255
+ bus.on("login", async ({ setState, emit }) => {
256
+ setState(s => { s.loading = true })
257
+ const user = await api.login()
258
+ setState(s => { s.loading = false })
259
+ emit("login:success", user)
263
260
  })
264
261
  ```
265
262
 
266
- #### Combining Effects + Selectors in Intents
263
+ #### 2. Calling handlers directly instead of emitting intent
267
264
  ```ts
268
- intents: bus => {
269
- // attach effect to intent
270
- bus.effect("save", withLogging)
265
+ // Don't call handlers manually
266
+ loginHandler(payload)
267
+ ```
268
+ Why this is wrong
271
269
 
272
- // intent handler (business rule)
273
- bus.on("save", ({ state }) => {
274
- if (!selectIsAdult(state)) {
275
- throw new Error("Not allowed")
276
- }
270
+ - Skips middleware & effects
271
+ - Breaks devtools timeline
272
+ - Makes behavior non-deterministic
277
273
 
278
- // perform save logic...
279
- })
274
+ Correct
280
275
 
281
- bus.on("save-user", async ({ state, setState }) => {
282
- await api.save(state.form)
283
- setState(s => {
284
- s.saved = true
285
- })
286
- })
287
-
288
- // bus.effect("save", debounce(300))
289
- // bus.effect("save", retry(2))
290
- // bus.effect("save", takeLatest())
276
+ ```ts
277
+ runtime.emit("login", payload)
278
+ ```
279
+ Intent is the only entry point. Always.
291
280
 
292
- }
281
+ #### ❌ 3. Using effects to mutate state directly
282
+ ```ts
283
+ // ❌ Effect mutating state
284
+ bus.effect("save", next => async ctx => {
285
+ ctx.setState(s => { s.saving = true })
286
+ await next(ctx)
287
+ })
293
288
  ```
294
289
 
295
- ---
290
+ Why this is wrong
291
+
292
+ - Effects are orchestration, not business logic
293
+ - Hard to reason about ordering
294
+ - Blurs responsibility
296
295
 
297
- ## 🧭 Devtools & Timeline
298
- - Every intent is recorded
299
- - Replayable
300
- - Deterministic async flows
296
+ Correct
301
297
 
302
298
  ```ts
303
- // devtool process.env.NODE_ENV !== "production"
304
- runtime.devtools?.timeline.replay(runtime.emit)
299
+ bus.on("save", ({ setState }) => {
300
+ setState(s => { s.saving = true })
301
+ })
302
+ ```
303
+ Effects should only:
304
+ - debounce
305
+ - retry
306
+ - cancel
307
+ - log
308
+ - trace
309
+
310
+ #### ❌ 4. Treating intent like Redux actions
311
+ ```ts
312
+ // ❌ Generic, meaningless intent
313
+ emit("SET_STATE", { loading: true })
305
314
  ```
306
315
 
307
- ---
316
+ Why this is wrong
308
317
 
309
- ## Props
318
+ - Intent should describe user or system intention
319
+ - Not raw state mutation
310
320
 
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.). |
321
+ Correct
318
322
 
319
- ---
323
+ ```ts
324
+ emit("login:start")
325
+ emit("login:success", user)
326
+ emit("login:failed", error)
327
+ ```
328
+ Intents are verbs, not patches.
320
329
 
321
- ## 🔍 Comparison
330
+ #### 5. Reading or mutating state outside the runtime
331
+ ```ts
332
+ // ❌ External mutation
333
+ runtime.state.user.name = "admin"
334
+ ```
335
+ Why this is wrong
336
+ - Breaks computed cache
337
+ - Bypasses subscriptions
338
+ - Devtools become unreliable
322
339
 
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 | ✅ | ⚠️ | ❌ |
340
+ Correct
331
341
 
332
- > This comparison focuses on **architecture and mental model**, not ecosystem size.
342
+ ```ts
343
+ emit("update:user:name", "admin")
344
+ ```
333
345
 
334
- ---
346
+ #### ❌ 6. Using React hooks to replace runtime behavior
347
+ ```ts
348
+ // ❌ useEffect as orchestration
349
+ useEffect(() => {
350
+ if (state.loggedIn) {
351
+ fetchProfile()
352
+ }
353
+ }, [state.loggedIn])
354
+ ```
355
+ Why this is wrong
356
+
357
+ - Behavior split across layers
358
+ - Impossible to replay or test headlessly
335
359
 
336
- ## 🚫 What this library is NOT
360
+ Correct
361
+
362
+ ```ts
363
+ bus.on("login:success", async ({ emit }) => {
364
+ await emit("profile:fetch")
365
+ })
366
+ ```
367
+ #### ❌ 7. One logic runtime doing everything
368
+ ```ts
369
+ // ❌ God runtime
370
+ createLogic({
371
+ state: {
372
+ user: {},
373
+ cart: {},
374
+ products: {},
375
+ settings: {},
376
+ ui: {},
377
+ }
378
+ })
379
+ ```
380
+ Why this is wrong
381
+ - No ownership boundaries
382
+ - Hard to compose
383
+ - Does not scale
337
384
 
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)
385
+ Correct
342
386
 
343
- It is a **behavior runtime**.
387
+ ```ts
388
+ composeLogic(
389
+ userLogic,
390
+ cartLogic,
391
+ productLogic
392
+ )
393
+ ```
344
394
 
345
395
  ---
346
396
 
347
397
  ## 📜 License
348
398
 
349
- MIT
399
+ MIT / Delpi
@@ -1,11 +1,6 @@
1
- import type { Effect } from "./types";
1
+ import type { Effect, EffectCtx } from "./types";
2
2
  export declare function composeEffects<W, R>(effects: Effect<W, R>[]): Effect<W, R>;
3
3
  export declare function takeLatest<W, R>(): Effect<W, R>;
4
4
  export declare function debounce<W, R>(ms: number): Effect<W, R>;
5
5
  export declare function retry<W, R>(count?: number): Effect<W, R>;
6
- export type EffectBuilder<W, R> = Effect<W, R> & {
7
- takeLatest(): EffectBuilder<W, R>;
8
- debounce(ms: number): EffectBuilder<W, R>;
9
- retry(count?: number): EffectBuilder<W, R>;
10
- };
11
- export declare function effect<W extends object = any, R extends object = W>(fx: Effect<W, R>): EffectBuilder<W, R>;
6
+ export declare function effect<W extends object, R extends object = W, P = any>(body: (ctx: EffectCtx<W, R, P>) => void | Promise<void>): any;
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export * from "./intent";
3
+ export * from "./effect";
@@ -1,10 +1,11 @@
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;
5
5
  emit: (intent: string, payload: P, ctx: {
6
- getState: () => R;
7
- setState: (fn: (s: W) => void) => void;
8
- emit: (intent: string, payload?: any) => Promise<void>;
6
+ getState(): R;
7
+ setState(fn: (s: W) => void): void;
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>;
@@ -1,4 +1,4 @@
1
- import type { Timeline } from "./timeline";
1
+ import type { Timeline } from "../devtools/timeline";
2
2
  export type Listener = () => void;
3
3
  export type EffectCtx<W = any, R = W, P = any> = {
4
4
  state: Readonly<R>;
@@ -10,12 +10,16 @@ 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
16
  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
+ effect(intent: string, fx: Effect<S, S & C>): void;
22
+ use(mw: IntentMiddleware<S, S & C>): void;
19
23
  subscribe(fn: Listener): () => void;
20
24
  onIntent(intent: string, handler: IntentHandler<S, S & C>): void;
21
25
  devtools?: {
@@ -0,0 +1,2 @@
1
+ export { attachDevtools } from "./devtools";
2
+ export * from "./timeline";
@@ -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 s(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)}}}function a(){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 s=new Set,a=r({state:new Proxy(o,{get:(t,e,n)=>("string"==typeof e&&e in t&&s.add(e),Reflect.get(t,e,n))})});return t.set(n,a),e.set(n,s),a},invalidate:function(r){e.forEach((e,o)=>{var s;e.has(r)&&(t.delete(o),null===(s=n.get(o))||void 0===s||s.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()))}}}function c(t){const e=new Map,n=new Map,r=[],o=new Map;return{on:function(t,n){var r;const o=null!==(r=e.get(t))&&void 0!==r?r:[];o.push(n),e.set(t,o)},effect:function(t,e){var r;const o=null!==(r=n.get(t))&&void 0!==r?r:[];o.push(e),n.set(t,o)},emit:async function(s,a,c){var i,l;const u=`${t}:${s}`;null===(i=o.get(u))||void 0===i||i.abort();const f=new AbortController;o.set(u,f);const d=Array.from(e.entries()).filter(([t])=>function(t,e){return t.endsWith("/*")?e.startsWith(t.slice(0,-2)):t===e}(t,s)).flatMap(([,t])=>t),p=null!==(l=n.get(s))&&void 0!==l?l:[];for(const e of d){let n=e;for(let t=p.length-1;t>=0;t--)n=p[t](n);const o={state:c.getState(),payload:a,signal:f.signal,scope:t,emit:c.emit,setState:c.setState};let s=-1;const i=async t=>{if(t<=s)throw new Error("next() called multiple times");s=t;const e=r[t];if(!e)return n(o);await e(o,()=>i(t+1))};await i(0)}},use:function(t){r.push(t)}}}function i(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:s}=null!=n?n:{},a=e.filter(t=>"emit"===t.type&&t.id>=r&&t.id<=o&&(!s||t.scope===s));for(const e of a){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 l=0;const u="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 f(t){return e=>t.reduceRight((t,e)=>e(t),e)}function d(){let t=null;return e=>async n=>{if(null==t||t.abort(),t=new AbortController,!t.signal.aborted)return e({...n,signal:t.signal})}}function p(t){let e;return n=>r=>new Promise(o=>{clearTimeout(e),e=setTimeout(()=>o(n(r)),t)})}function m(t=3){return e=>async n=>{let r;for(let o=0;o<t;o++)try{return await e(n)}catch(t){if(n.signal.aborted)return;r=t}throw r}}exports.attachDevtools=i,exports.composeEffects=f,exports.composeLogic=function(...t){return{create(e){const n=t.map(t=>{var n,r;if("logic"in t){const o=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:null!==(r=t.namespace)&&void 0!==r?r:null,inst:o}}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(n=>{var r,o;return null===(o=(r=n.inst).emit)||void 0===o?void 0:o.call(r,t,e)}).filter(Boolean);await Promise.all(r)},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>e.forEach(t=>t())}}}}},exports.composeMiddleware=function(t){return function(e,n){let r=-1;return function o(s){if(s<=r)return Promise.reject(new Error("next() called multiple times"));r=s;const a=t[s]||n;return a?Promise.resolve(a(e,()=>o(s+1))):Promise.resolve()}(0)}},exports.createBackendRuntime=function(t){var e,n;let r=structuredClone(t);const o=a(),s=c("backend"),l=[],u=[];let f=!1,d=new Set;function p(t){const e=structuredClone(r);if(t(r),f)for(const t in r)e[t]!==r[t]&&d.add(t);else for(const t in r)e[t]!==r[t]&&o.invalidate(t)}function m(t){return{...t,set(t){p(e=>Object.assign(e,t))},emit:v.emit}}const v={state:()=>r,setState:p,batch:function(t){f=!0;try{t()}finally{f=!1,d.forEach(t=>o.invalidate(t)),d.clear()}},async emit(t,e){l.forEach(n=>n({intent:t,payload:e,state:structuredClone(r)}));try{await s.emit(t,e,{getState:()=>r,setState:p,emit:v.emit})}finally{u.forEach(n=>n({intent:t,payload:e,state:structuredClone(r)}))}},onIntent:s.on,effect:s.effect,use:s.use,registerIntents:function(t){for(const e in t){const n=t[e];s.on(e,async t=>{await n(m({state:r,signal:t.signal}))})}},__internal:{onEmitStart(t){l.push(t)},onEmitEnd(t){u.push(t)}},reset:function(){r=structuredClone(t),o.reset()}};if("production"!==(null===(n=null===(e=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===e?void 0:e.env)||void 0===n?void 0:n.NODE_ENV)){const t=i(v);t.wrap(v),v.devtools=t}return v},exports.createComputed=a,exports.createIntentBus=c,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}:${++l}`,d=structuredClone(t.state),p=s(structuredClone(t.state)),m=a(),v=c(f);null===(r=t.intents)||void 0===r||r.call(t,v);let b=null,g=null,h=!1,y=new Set;const w=[],E=[];function S(){const e=p.getState();if(e===b&&g)return g;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,g=Object.assign({},e,n),g}function x(t){const e=p.getState();p.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&(h?y.add(t):m.invalidate(t))})}const j={scope:f,state:()=>S(),setState:x,reset(){!function(){var t;p.setState(()=>structuredClone(d)),null===(t=m.reset)||void 0===t||t.call(m),b=null,g=null}()},use:v.use,effect:v.effect,batch:function(t){h=!0;try{t()}finally{h=!1,y.forEach(t=>m.invalidate(t)),y.clear()}},async emit(t,e){const n=S();w.forEach(r=>r({intent:t,payload:e,state:n,scope:f}));try{await v.emit(t,e,{getState:S,setState:x,emit:j.emit})}finally{E.forEach(n=>n({intent:t,payload:e,state:S(),scope:f}))}},subscribe:p.subscribe,onIntent(t,e){v.on(t,e)},__internal:{onEmitStart(t){w.push(t)},onEmitEnd(t){E.push(t)}}};let O;return null===(o=t.plugins)||void 0===o||o.forEach(t=>t.setup(j)),u&&(O=i(j),O.wrap(j)),{...j,devtools:O}}}},exports.createSelector=function(t,e=Object.is){let n,r=null;return o=>{if(null!==r){const s=t(o);return e(n,s)?n:(n=s,r=o,s)}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.createStore=s,exports.debounce=p,exports.effect=function(t){const e=[e=>async n=>{if(!n.signal.aborted)return await t(n),e(n)}];let n=!1;const r=t=>(n=!0,f(e))(t);return r.takeLatest=()=>{if(n)throw new Error("Effect already built");return e.push(d()),r},r.debounce=t=>{if(n)throw new Error("Effect already built");return e.push(p(t)),r},r.retry=(t=3)=>{if(n)throw new Error("Effect already built");return e.push(m(t)),r},r},exports.retry=m,exports.takeLatest=d,exports.withLogic=function(e,n,r){var s,a;const c=s=>{const a=o.useRef(null);a.current||(a.current=e.create(r));const c=a.current,i=o.useSyncExternalStore(c.subscribe,c.state,c.state),l=o.useCallback((t,e)=>c.emit(t,e),[c]);return t.jsx(n,{...s,state:i,emit:l})};return c.displayName=`withLogic(${null!==(a=null!==(s=n.displayName)&&void 0!==s?s:n.name)&&void 0!==a?a:"View"})`,c};
package/build/index.d.ts CHANGED
@@ -1,9 +1,16 @@
1
- export { createSignal } from "./core/signal";
2
- export { createLogic } from "./core/logic";
3
- export { composeLogic } from "./core/compose";
4
- export { effect, takeLatest, debounce, retry } from "./core/effect";
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";
1
+ export { createLogic } from "./runtime/logic";
2
+ export { composeLogic } from "./runtime/compose";
3
+ export { createBackendRuntime } from "./runtime/backend";
4
+ export type { SimpleBackendCtx } from "./runtime/backend";
5
+ export { createSignal } from "./state/signal";
6
+ export { createStore } from "./state/store";
7
+ export { createComputed } from "./state/computed";
8
+ export { createSelector } from "./state/selector";
9
+ export type { Subscriber } from "./state/signal";
10
+ export { effect, takeLatest, debounce, retry, composeEffects } from "./core/effect";
11
+ export { createIntentBus } from "./core/intent";
12
+ export { compose as composeMiddleware } from "./core/middleware";
13
+ export type { LogicRuntime, IntentHandler, Effect, EffectCtx, IntentMiddleware, Listener, } from "./core/types";
14
+ export { attachDevtools } from "./devtools/devtools";
15
+ export type { Timeline, IntentRecord, } from "./devtools/timeline";
9
16
  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{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)}}}function r(){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 s=new Set,a=r({state:new Proxy(o,{get:(t,e,n)=>("string"==typeof e&&e in t&&s.add(e),Reflect.get(t,e,n))})});return t.set(n,a),e.set(n,s),a},invalidate:function(r){e.forEach((e,o)=>{var s;e.has(r)&&(t.delete(o),null===(s=n.get(o))||void 0===s||s.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()))}}}function o(t,e=Object.is){let n,r=null;return o=>{if(null!==r){const s=t(o);return e(n,s)?n:(n=s,r=o,s)}return r=o,n=t(o),n}}function s(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 a(t){const e=new Map,n=new Map,r=[],o=new Map;return{on:function(t,n){var r;const o=null!==(r=e.get(t))&&void 0!==r?r:[];o.push(n),e.set(t,o)},effect:function(t,e){var r;const o=null!==(r=n.get(t))&&void 0!==r?r:[];o.push(e),n.set(t,o)},emit:async function(s,a,i){var c,l;const u=`${t}:${s}`;null===(c=o.get(u))||void 0===c||c.abort();const f=new AbortController;o.set(u,f);const d=Array.from(e.entries()).filter(([t])=>function(t,e){return t.endsWith("/*")?e.startsWith(t.slice(0,-2)):t===e}(t,s)).flatMap(([,t])=>t),p=null!==(l=n.get(s))&&void 0!==l?l:[];for(const e of d){let n=e;for(let t=p.length-1;t>=0;t--)n=p[t](n);const o={state:i.getState(),payload:a,signal:f.signal,scope:t,emit:i.emit,setState:i.setState};let s=-1;const c=async t=>{if(t<=s)throw new Error("next() called multiple times");s=t;const e=r[t];if(!e)return n(o);await e(o,()=>c(t+1))};await c(0)}},use:function(t){r.push(t)}}}function i(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:s}=null!=n?n:{},a=e.filter(t=>"emit"===t.type&&t.id>=r&&t.id<=o&&(!s||t.scope===s));for(const e of a){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 c,l;let u=0;const f="production"!==(null===(l=null===(c=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===c?void 0:c.env)||void 0===l?void 0:l.NODE_ENV);function d(t){var e;const o=null!==(e=t.name)&&void 0!==e?e:"logic";return{create(e){var s,c;const l=null!=e?e:`${o}:${++u}`,d=structuredClone(t.state),p=n(structuredClone(t.state)),m=r(),v=a(l);null===(s=t.intents)||void 0===s||s.call(t,v);let h=null,b=null,g=!1,y=new Set;const w=[],E=[];function S(){const e=p.getState();if(e===h&&b)return b;const n={};var r;return t.computed&&(r=t.computed,Object.keys(r)).forEach(r=>{n[r]=m.compute(r,t.computed[r],e)}),h=e,b=Object.assign({},e,n),b}function C(t){const e=p.getState();p.setState(n=>{t(n);for(const t in n)e[t]!==n[t]&&(g?y.add(t):m.invalidate(t))})}const j={scope:l,state:()=>S(),setState:C,reset(){!function(){var t;p.setState(()=>structuredClone(d)),null===(t=m.reset)||void 0===t||t.call(m),h=null,b=null}()},use:v.use,effect:v.effect,batch:function(t){g=!0;try{t()}finally{g=!1,y.forEach(t=>m.invalidate(t)),y.clear()}},async emit(t,e){const n=S();w.forEach(r=>r({intent:t,payload:e,state:n,scope:l}));try{await v.emit(t,e,{getState:S,setState:C,emit:j.emit})}finally{E.forEach(n=>n({intent:t,payload:e,state:S(),scope:l}))}},subscribe:p.subscribe,onIntent(t,e){v.on(t,e)},__internal:{onEmitStart(t){w.push(t)},onEmitEnd(t){E.push(t)}}};let O;return null===(c=t.plugins)||void 0===c||c.forEach(t=>t.setup(j)),f&&(O=i(j),O.wrap(j)),{...j,devtools:O}}}}function p(...t){return{create(e){const n=t.map(t=>{var n,r;if("logic"in t){const o=t.logic.create(null!==(n=t.namespace)&&void 0!==n?n:e);return{namespace:null!==(r=t.namespace)&&void 0!==r?r:null,inst:o}}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(n=>{var r,o;return null===(o=(r=n.inst).emit)||void 0===o?void 0:o.call(r,t,e)}).filter(Boolean);await Promise.all(r)},subscribe(t){const e=n.map(e=>e.inst.subscribe(t));return()=>e.forEach(t=>t())}}}}}function m(t){return e=>t.reduceRight((t,e)=>e(t),e)}function v(){let t=null;return e=>async n=>{if(null==t||t.abort(),t=new AbortController,!t.signal.aborted)return e({...n,signal:t.signal})}}function h(t){let e;return n=>r=>new Promise(o=>{clearTimeout(e),e=setTimeout(()=>o(n(r)),t)})}function b(t=3){return e=>async n=>{let r;for(let o=0;o<t;o++)try{return await e(n)}catch(t){if(n.signal.aborted)return;r=t}throw r}}function g(t){const e=[e=>async n=>{if(!n.signal.aborted)return await t(n),e(n)}];let n=!1;const r=t=>(n=!0,m(e))(t);return r.takeLatest=()=>{if(n)throw new Error("Effect already built");return e.push(v()),r},r.debounce=t=>{if(n)throw new Error("Effect already built");return e.push(h(t)),r},r.retry=(t=3)=>{if(n)throw new Error("Effect already built");return e.push(b(t)),r},r}function y(t){var e,n;let o=structuredClone(t);const s=r(),c=a("backend"),l=[],u=[];let f=!1,d=new Set;function p(t){const e=structuredClone(o);if(t(o),f)for(const t in o)e[t]!==o[t]&&d.add(t);else for(const t in o)e[t]!==o[t]&&s.invalidate(t)}function m(t){return{...t,set(t){p(e=>Object.assign(e,t))},emit:v.emit}}const v={state:()=>o,setState:p,batch:function(t){f=!0;try{t()}finally{f=!1,d.forEach(t=>s.invalidate(t)),d.clear()}},async emit(t,e){l.forEach(n=>n({intent:t,payload:e,state:structuredClone(o)}));try{await c.emit(t,e,{getState:()=>o,setState:p,emit:v.emit})}finally{u.forEach(n=>n({intent:t,payload:e,state:structuredClone(o)}))}},onIntent:c.on,effect:c.effect,use:c.use,registerIntents:function(t){for(const e in t){const n=t[e];c.on(e,async t=>{await n(m({state:o,signal:t.signal}))})}},__internal:{onEmitStart(t){l.push(t)},onEmitEnd(t){u.push(t)}},reset:function(){o=structuredClone(t),s.reset()}};if("production"!==(null===(n=null===(e=null===globalThis||void 0===globalThis?void 0:globalThis.process)||void 0===e?void 0:e.env)||void 0===n?void 0:n.NODE_ENV)){const t=i(v);t.wrap(v),v.devtools=t}return v}function w(t){return function(e,n){let r=-1;return function o(s){if(s<=r)return Promise.reject(new Error("next() called multiple times"));r=s;const a=t[s]||n;return a?Promise.resolve(a(e,()=>o(s+1))):Promise.resolve()}(0)}}function E(n,r,o){var s,a;const i=s=>{const a=e.useRef(null);a.current||(a.current=n.create(o));const i=a.current,c=e.useSyncExternalStore(i.subscribe,i.state,i.state),l=e.useCallback((t,e)=>i.emit(t,e),[i]);return t(r,{...s,state:c,emit:l})};return i.displayName=`withLogic(${null!==(a=null!==(s=r.displayName)&&void 0!==s?s:r.name)&&void 0!==a?a:"View"})`,i}export{i as attachDevtools,m as composeEffects,p as composeLogic,w as composeMiddleware,y as createBackendRuntime,r as createComputed,a as createIntentBus,d as createLogic,o as createSelector,s as createSignal,n as createStore,h as debounce,g as effect,b as retry,v as takeLatest,E as withLogic};
@@ -1,4 +1,4 @@
1
- import type { LogicRuntime } from "./types";
1
+ import type { LogicRuntime } from "../core";
2
2
  export type LogicPlugin<S extends object, C extends object = {}> = {
3
3
  name: string;
4
4
  setup(runtime: LogicRuntime<S, C>): void;
@@ -10,5 +10,5 @@ type LogicFactory<S> = {
10
10
  export declare function withLogic<S, P extends object>(logic: LogicFactory<S>, View: React.ComponentType<P & {
11
11
  state: Readonly<S>;
12
12
  emit: (intent: string, payload?: any) => Promise<void>;
13
- }>): React.FC<Omit<P, "state" | "emit">>;
13
+ }>, scope?: string): React.FC<Omit<P, "state" | "emit">>;
14
14
  export {};
@@ -0,0 +1,29 @@
1
+ export type SimpleBackendCtx<S> = {
2
+ state: Readonly<S>;
3
+ set: (patch: Partial<S>) => void;
4
+ emit: (intent: string, payload?: any) => Promise<void>;
5
+ signal: AbortSignal;
6
+ };
7
+ export declare function createBackendRuntime<S extends object>(initial: S): {
8
+ state(): S;
9
+ setState: (mutator: (s: S) => void) => void;
10
+ batch: (fn: () => void) => void;
11
+ emit(intent: string, payload?: any): Promise<void>;
12
+ onIntent: (intent: string, handler: import("../core").IntentHandler<S, S, any>) => void;
13
+ effect: (intent: string, fx: import("../core").Effect<S, S, any>) => void;
14
+ use: (mw: import("../core").IntentMiddleware<S, S, any>) => void;
15
+ registerIntents: (intentsObj: Record<string, (ctx: SimpleBackendCtx<S>) => void | Promise<void>>) => void;
16
+ __internal: {
17
+ onEmitStart(fn: (ctx: {
18
+ intent: string;
19
+ payload: any;
20
+ state: S;
21
+ }) => void): void;
22
+ onEmitEnd(fn: (ctx: {
23
+ intent: string;
24
+ payload: any;
25
+ state: S;
26
+ }) => void): void;
27
+ };
28
+ reset: () => void;
29
+ };
@@ -1,6 +1,6 @@
1
1
  type LogicInstance = {
2
2
  state(): any;
3
- emit?: (intent: string, payload?: any) => any | Promise<any>;
3
+ emit?: (intent: string, payload?: any) => Promise<any> | any;
4
4
  subscribe(fn: () => void): () => void;
5
5
  };
6
6
  type ComposedLogic = {
@@ -14,7 +14,7 @@ type ComposedLogic = {
14
14
  export declare function composeLogic(...entries: ComposedLogic[]): {
15
15
  create(scope?: string): {
16
16
  readonly state: any;
17
- emit(intent: string, payload?: any): Promise<any[]>;
17
+ emit(intent: string, payload?: any): Promise<void>;
18
18
  subscribe(fn: () => void): () => void;
19
19
  };
20
20
  };
@@ -0,0 +1,3 @@
1
+ export { createLogic } from "./logic";
2
+ export { createBackendRuntime } from "./backend";
3
+ export { composeLogic } from "./compose";
@@ -1,5 +1,5 @@
1
- import type { LogicRuntime, IntentHandler, Effect } from "./types";
2
- import type { LogicPlugin } from "./plugin";
1
+ import type { LogicRuntime, IntentHandler, Effect } from "../core/types";
2
+ import { LogicPlugin } from "../plugins";
3
3
  type ComputedDef<S, C> = {
4
4
  [K in keyof C]: (ctx: {
5
5
  state: S;
@@ -0,0 +1,4 @@
1
+ export { createStore } from "./store";
2
+ export { createComputed } from "./computed";
3
+ export { createSelector } from "./selector";
4
+ export { createSignal } from "./signal";
@@ -1,4 +1,4 @@
1
- import { Listener } from "./types";
1
+ import { Listener } from "../core/types";
2
2
  export declare function createStore<S extends object>(initial: S): {
3
3
  getState: () => S;
4
4
  setState: (mutator: (s: S) => void) => void;
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.1",
4
4
  "description": "Intent-first runtime for React. No hooks. Deterministic state. Orchestrated effects.",
5
5
  "license": "MIT",
6
6
  "author": "Delpi.Kye",
@@ -5,8 +5,8 @@ export type Devtools = {
5
5
  };
6
6
  type RuntimeLike = {
7
7
  scope: string;
8
- emit(intent: string, payload?: any): Promise<void>;
9
8
  state(): any;
9
+ emit(intent: string, payload?: any): Promise<void>;
10
10
  };
11
11
  export declare function attachDevtools(target: RuntimeLike): Devtools;
12
12
  export {};
File without changes
File without changes
File without changes
File without changes