Skip to content

delpikye-v/intentx-solid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”·βš‘ intentx-solid

NPM Downloads

LIVE EXAMPLE


intentx-solid enforces a strict architectural boundary between deterministic business logic and reactive UI in Solid.

It enforces a strict separation between:

  • Business Logic (deterministic runtime)
  • UI Rendering (fine-grained reactivity)

It is a bridge between deterministic logic and Solid’s reactive UI.


✨ Why intentx-solid?

Use it when your UI starts to feel like business logic.

βœ… Complex async workflows
βœ… Intent-based architecture
βœ… Microfrontend communication
βœ… Testable business logic
βœ… Cross-framework runtime reuse

Avoid it when:

❌ This library introduces an architectural boundary.
❌ If you don’t need architectural boundaries, don’t use it.


🧠 Mental Model

UI / HTTP / Queue / Cron
        ↓
  intentx-runtime
        ↓
Fine-grained reactivity updates UI

Core principle:

Intent is the only mutation entry point. The runtime owns behavior. UI only triggers intent.


πŸ“¦ Installation

npm install intentx-solid

🧩 Core Logic (Framework-Agnostic)

Even though this is the React package, the runtime is fully usable without React.

import { createLogic } from "intentx-solid"

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

  state: {
    count: 0
  },

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

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

  actions: {
    inc({ emit }) {
      return () => emit("inc");
    },
  },
})

πŸš€ Usage

import { useLogic } from "intentx-solid"
import { counterLogic } from "./counter.logic"

export default function Counter() {
  const counter = useLogic(counterLogic)
  // const { actions, emit, store } = useLogic(counterLogic)

  return (
    <>
      <button onClick={counter.actions.inc}>
        {counter.state.count}
      </button>

      <button onClick={() => emit("inc")}>
        {counter.state.count}
      </button>

      <p>Double: {counter.store.double}</p>
    </>
  )
}

No wrapper components. No providers required (unless you want shared context).


πŸ“¦ What useLogic Returns

const counter = useLogic(counterLogic)
  {
    store, //
    state, // alias state
    actions, // actions -> emit
    emit // directly
  }

πŸ”₯ Important

  • store is readonly

  • Mutations must go through actions

  • Solid reactivity remains fine-grained


πŸ“‘ Multiple Logic Communication (Bus)

Each logic instance is isolated by default.

To enable communication between runtimes, you can share an Intent Bus.


1️⃣ Scoped Shared Bus (Recommended)

import { useLogic } from "intentx-solid"

// βœ… Same scope β†’ shared bus
useLogic(logic, {
  scope: "dashboard",
  sharedBus: true
})

// ❌ Different scope β†’ different bus
useLogic(logic, {
  scope: "settings",
  sharedBus: true
})

How it works

When sharedBus: true is enabled:

  • A singleton bus is created per scope

  • Same scope β†’ same bus instance

  • Different scope β†’ different bus

  • No global leakage

If no scope is provided:

useLogic(logic, {
  sharedBus: true
})

β†’ uses a default global scope bus.


2️⃣ Custom Bus (Advanced / Cross-Scope)

import { createIntentBus } from "intentx-react"

const bus = createIntentBus()

useLogic(logicA, { bus })
useLogic(logicB, { bus })

Behavior

  • Full cross-scope communication
  • Manual orchestration control
  • Suitable for:
    • Microfrontend
    • App-wide coordination
    • Complex workflow systems

πŸ” Behavior Comparison

Mode Isolation Scope-aware Cross-scope Recommended
Default (no options) βœ… Full ❌ ❌ Small/local logic
sharedBus: true ❌ Per scope βœ… ❌ Modular apps
Custom bus ❌ Manual ❌ βœ… Advanced architecture

🎯 Recommendation

βœ… Use sharedBus for modular communication.
βœ… Use custom bus for orchestration layer.
🚫 Avoid global single bus without scope in large apps.


🧩 Context API

Provide logic via Solid context:

import { setLogicContext, useLogicContext } from "intentx-solid"
import { counterLogic } from "./counter.logic"

Provider:

const { Provider } = setLogicContext(
  "counter",
  counterLogic
)

export default function App() {
  return (
    <Provider>
      <Child />
    </Provider>
  )
}

Consume:

const counter = useLogicContext("counter")

🌍 SSR

  • Runtime created during SSR
  • Deterministic snapshot
  • Hydration-safe
  • No client-only hacks
  • Server snapshot serializable
  • Client rehydrates from deterministic state

Works with:

  • SolidStart
  • Node SSR
  • Edge runtimes

πŸ§ͺ Testability Upgrade

Without rendering anything:

const runtime = counterLogic.create()

runtime.actions.inc()
expect(runtime.state.count).toBe(1)

That is the real split.


πŸ” Comparison

Criteria Solid primitives intentx-solid
Local UI Excellent Overkill
Async orchestration Manual Structured
Cross-environment reuse No Yes
Deterministic runtime No Yes
  • UI consumes state.
  • Logic lives outside components.

πŸ”₯ What This Library Actually Does

It separates business logic from UI completely.

Without this split:

  • Components start holding async workflows
  • Event chains become implicit
  • State transitions become coupled to rendering
  • Testing requires rendering components

With intentx-solid:

  • Logic lives outside the component tree
  • UI becomes a pure consumer
  • Async workflows are deterministic
  • Runtime can be reused in Node, SSR, microfrontends

🧩 Architectural Boundary

Solid owns:

  • Signals
  • Reactivity
  • DOM updates
  • Rendering lifecycle

intentx owns:

  • State machine
  • Intent routing
  • Async orchestration
  • Computed graph
  • Cross-runtime communication

This is not about replacing createSignal.

It is about preventing this:

// ❌ Business logic leaking into UI
createEffect(async () => {
  if (userId()) {
    const data = await fetchUser(userId())
    setUser(data)
  }
})

And moving it here:

// βœ… Logic outside UI
intents: (bus) => {
  bus.on("loadUser", async ({ setState }) => {
    const data = await fetchUser(id)
    setState(d => {
      d.user = data
    })
  });
}

Now UI only emits intent.


🎯 Philosophy

Rendering is reactive.

Business logic should be deterministic.

intentx-solid ensures they never mix.


❌ What This Is Not

  • Not a signal replacement
  • Not a store wrapper
  • Not a UI state helper
  • Not a reducer abstraction

πŸ“œ License

MIT

About

Intent-driven logic adapter for SolidJS. Connects intentx-runtime with Solid's fine-grained reactivity.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors