![logo](https://raw.githubusercontent.com/delpikye-v/intentx-runtime/main/intentx-runtime.png)

[![NPM](https://img.shields.io/npm/v/intentx-runtime.svg)](https://www.npmjs.com/package/intentx-runtime) ![Downloads](https://img.shields.io/npm/dt/intentx-runtime.svg)

<a href="https://codesandbox.io/p/devbox/rq72pf" target="_blank">LIVE EXAMPLE</a>

**A framework-agnostic domain logic runtime**

> Build isolated modules. Orchestrate through intents. Scale without chaos.

---

## ✨ Why intentx-runtime?

- 🧠 Modular business logic
- 📦 Independent state per logic
- 🔄 Event-driven communication
- 🧩 Shared intent bus
- ⚡ Computed with shallow caching
- 🛠 Works in Node, React, Vue, or anywhere

This is **not a UI state library**.  
This is a **logic runtime engine**.

---

## 🧠 Mental Model

```
UI / HTTP / Queue / Cron
        ↓
     emit(intent)
        ↓
   effects / middleware
        ↓
   intent handlers
        ↓
     mutate state
        ↓
computed (derived state) / subscribers
```

---

## 📦 Installation

```bash
npm install intentx-runtime
```

---

## ✨ Two Ways to Define Logic

intentx-runtime supports **2 styles**:

1.  Object configuration (classic)
2.  Builder chain API (fluent architecture style)

Both produce the same runtime behavior.

---

### 🧩 1️⃣ Object Style (Classic)

``` ts
import { createLogic, effect } from "intentx-runtime"

export const counterLogic = createLogic({
  name: "counter",

  state: {
    count: 1,
    loading: false,
  },

  computed: {
    double: ({ state }) => state.count * 2,
    triple: ({ state }) => state.count * 3,
  },

  intents: bus => {
    bus.on("inc", ({ setState }) => {
      setState(s => { s.count++ })
    })

    bus.on<number>("add", ({ payload, setState }) => {
      setState(s => { s.count += payload })
    })

    bus.on<number>("inc-async", async ({ payload, setState }) => {
      setState(s => { s.loading = true })

      await new Promise(r => setTimeout(r, 1000))

      setState(s => {
        s.loading = false
      })
    })

    bus.effect(
      "inc-async",
      effect(async ({ payload }) => {
        console.log("effect run:", payload)
      }).takeLatest().blocking()
    )
  },

  actions: {
    inc: ({ emit }) => () => emit("inc"),
    add: ({ emit }) => (n: number) => emit("add", n),
    incAsync: ({ emit }) => (n: number) => emit("inc-async", n),
  },
})
```

---

### 🏗 2️⃣ Builder Style (Fluent API)

Builder API allows more architectural clarity and composition.

``` ts
import { createLogic, effect } from "intentx-runtime"

export const counterLogic =
  createLogic({
    name: "counter",
    state: {
      count: 1,
      loading: false,
    },
  })

    .computed({
      double: ({ state }) => state.count * 2,
      triple: ({ state }) => state.count * 3,
    })

    .intent("inc", ({ setState }) => {
      setState(s => { s.count++ })
    })

    .intent<number>("add", ({ payload, setState }) => {
      setState(s => { s.count += payload })
    })

    .intent("inc-async", async ({ payload, setState }) => {
      setState(s => { s.loading = true })

      await new Promise(r => setTimeout(r, 1000))

      setState(s => {
        s.count += payload
        s.loading = false
      })
    })

    .effect(
      "inc-async",
      effect(async ({ payload }: { payload: number }) => {
        console.log("effect run:", payload)
      }).takeLatest().blocking()
    )

    .action({
      inc: ({ emit }) => () => emit("inc"),
      add: ({ emit }) => (n: number) => emit("add", n),
      incAsync: ({ emit }) => (n: number) => emit("inc-async", n),
    })
```

---

### ▶ Run

``` ts
const runtime = counterLogic.create()

console.log("Initial:", runtime.state)

runtime.actions.incAsync(4).then(() => {
  console.log("After +4:", runtime.state)
})
```

---

### 🧠 When to Use Which?

| Style   | Use When                                                      |
|---------|---------------------------------------------------------------|
| Object  | Simple modules, compact definition                            |
| Builder | Large domain modules, composition, plugin-style architecture  |

Both styles:

-   Isolate domain state
-   Use shared intent bus
-   Support async intent handlers
-   Support effect orchestration
-   Provide cached computed
-   Work in Node, React, Vue, or anywhere

---

## 🧩 Multi-Logic (Shared Bus)

```ts
import { createLogic } from "intentx-runtime"

export const cartLogic = createLogic({
  name: "cart",

  state: {
    items: [] as string[],
  },

  computed: {
    total: ({ state }) => state.items.length,
  },

  intents: bus => {
    bus.on<string>("add-item", ({ payload, setState }) => {
      setState(s => {
        s.items.push(payload)
      })
    })

    bus.on("clear-cart", ({ setState }) => {
      setState(s => {
        s.items = []
      })
    })
  },

  actions: {
    addItem({ emit }) {
      return (item: string) => emit("add-item", item)
    },

    clear({ emit }) {
      return () => emit("clear-cart")
    },
  },
})
```

```ts
import { createLogic } from "intentx-runtime";

type AuthState = {
  user: { name: string } | null
}

export const userLogic = createLogic({
  state: {
    // generic
    user: null as null | AuthState,
  },

  actions: {
    login: ({ emit }) => async (name: string) => {
      await emit("user/login", { name })
    },
  },

  intents: ({ on }) => {
    // generic
    on<{ name: string }>("user/login", ({ payload, setState }) => {
      setState(draft => {
        draft.user = { user: { name: payload.name } }
      })
    })
  },
})
```

---

### 🚀 Create App

```ts
import { createApp } from "intentx-runtime"
import { userLogic } from "./userLogic"
import { cartLogic } from "./cartLogic"

const app = createApp({
  logics: {
    user: userLogic,
    cart: cartLogic,
  },
})
```

---

### ▶ Run

```ts
const run = async () => {
  await app.logics.user.actions.login("An")

  await app.logics.cart.actions.addItem("iPhone")
  await app.logics.cart.actions.addItem("Macbook")

  console.log(app.logics.cart.state.total)
  // 👉 2
}

run()
```

---

## ⚡ Computed Engine

Computed values:

- Recalculate only when state changes (shallow)
- Cached between reads
- Lazy evaluation

Example:

```ts
computed: {
  total: ({ state }) => state.items.length
}
```

It only recalculates when `items` reference changes.


---

## 🧩 Logic Isolation

Each logic:

- Has its own state
- Has its own computed
- Has its own actions
- Communicates only via intent bus

No direct cross-logic mutation.

---

## 🏗 Architecture

```
createApp()
   │
   ├── shared IntentBus
   │
   ├── userLogic runtime
   │       ├ state
   │       ├ computed
   │       └ actions
   │
   └── cartLogic runtime
           ├ state
           ├ computed
           └ actions
```

---

## 🛠 Works Anywhere

<b>Because this runtime:</b>

- Has no framework dependency
- No React hooks
- No Vue reactivity
- Pure TypeScript

<b>You can:</b>

- Use in Node backend
- Wrap into React hook
- Wrap into Vue composable
- Run in service workers

---

## 🔍 Comparison

This is not about “better” — it's about architectural intent.

| Criteria                | 🚀 intentx-runtime       | 🧰 Redux Toolkit  | 🐻 Zustand      | ⚡ MobX           | 🎛️ XState        |
| ----------------------- | ----------------------- | ----------------  | --------------- | ---------------- | ---------------- |
| **Event-driven Core**   | ✅ Native                | ⚠️ Action-based   | ❌              | ❌               | ✅ FSM            |
| **Modular Isolation**   | ✅ True domain isolation | ⚠️ Slice-based    | ⚠️ Store-based  | ❌ Shared graph  | ⚠️ Machine-based  |
| **Shared Event Bus**    | ✅ Built-in              | ❌                | ❌              | ❌               | ⚠️ Actor model    |
| **Built-in Computed**   | ✅ First-class           | ❌                | ❌              | ✅               | ❌                |
| **Framework Agnostic**  | ✅ 100%                  | ⚠️ React-first    | ⚠️ React-first  | ⚠️ Mostly React  | ✅ 100%           |
| **Backend Friendly**    | ✅ Designed for it       | ❌                | ❌              | ❌               | ⚠️ Possible       |
| **Domain-first Design** | ✅ Core philosophy       | ❌                | ❌              | ❌               | ⚠️ Machine-first  |

---

<b>Legend</b>

- `Event-driven Core`: Has a first-class event system for orchestration.

- `Modular Isolation`: Encourages splitting logic into isolated modules.

- `Shared Event Bus`: Supports communication between modules via central bus.

- `Built-in Computed`: Provides derived state with caching.

- `Framework Agnostic`: Not tightly coupled to React or any UI framework.

- `Backend Friendly`: Can run naturally in Node or server environments.

- `Domain-first Design`: Designed primarily for business logic, not UI state.

---

### 🧠 Positioning Summary

- **Redux Toolkit** → predictable UI state for React apps  
- **Zustand** → tiny and ergonomic React store  
- **MobX** → automatic reactivity system  
- **XState** → explicit state machine modeling  
- **intentx-runtime** → modular event-driven domain logic runtime  

---

### 🎯 What Makes intentx-runtime Different?

- Redux for domain
- XState without machine rigidity
- Event bus with isolated state containers
- Backend-style architecture usable on frontend

---

## 🔥 Philosophy

**intentx-runtime** is built for:

- Complex domain logic
- Backend-like state flow
- Deterministic event system
- Scalable modular architecture

It is not meant to replace Redux or Zustand.  
It is meant to replace messy business logic.

---

## 📜 License

MIT