# ⚡ react-runtime-pipe

[![NPM](https://img.shields.io/npm/v/react-runtime-pipe.svg)](https://www.npmjs.com/package/react-runtime-pipe) ![Downloads](https://img.shields.io/npm/dt/react-runtime-pipe.svg) ![Bundle Size](https://img.shields.io/bundlephobia/minzip/react-runtime-pipe)

[LIVE EXAMPLE](https://codesandbox.io/p/sandbox/ps2sp8)

🚀 A backend-inspired execution runtime for React applications.

> Build scalable frontend systems using middleware pipelines, typed actions, dependency injection, runtime context, plugins, orchestration, and event-driven execution.

---

# Installation

```bash
npm install react-runtime-pipe
```

# What is react-runtime-pipe?

`react-runtime-pipe` is a lightweight runtime for executing backend-style logic in React applications.

It brings concepts like middleware, dependency injection, and orchestration into frontend code.

It provides:

- typed action execution  
- middleware pipelines  
- dependency injection  
- runtime context  
- plugins and event system  
- retry and cancellation support  

without enforcing any specific state management approach.

---

# Why react-runtime-pipe?

Frontend apps often become hard to scale as logic spreads across components and async flows become inconsistent.

Common problems:

- business logic inside UI components  
- duplicated async logic  
- scattered dependencies  
- tightly coupled side effects  
- inconsistent orchestration  

react-runtime-pipe solves this by introducing a structured execution layer:

- predictable execution flow  
- composable middleware  
- centralized orchestration  
- backend-like architecture in frontend  
- framework-agnostic design  

---

# Features

- ⚡ Runtime-first architecture
- 🧩 Middleware pipeline
- 🔌 Plugin system
- 🧠 Typed execution context
- 🛡 Action lifecycle hooks
- ♻️ Retry orchestration
- 📦 Dependency injection container
- 📡 Event bus system
- 📝 Full TypeScript inference
- 🚦 Abort/cancellation support
- 💾 Scoped execution cache
- ⚛️ React integration
- 🧱 Framework agnostic core

---

# Mental Model

```text
UI Event
    ↓
useAction()
    ↓
createExecutionContext()
    ↓
Plugins
    ↓
Middleware Pipeline
    ↓
Action Handler
    ↓
Service Layer
    ↓
Event Bus
    ↓
UI Update
```

---

# Quick Start

## Create App

```ts
// app.ts
import { createApp } from 'react-runtime-pipe'
import { loggerPlugin } from 'react-runtime-pipe/plugin'

import { ApiService } from './services/api'
import { ApiServiceToken } from './tokens'

export const app = createApp({
  services: [
    {
      token: ApiServiceToken,
      useValue: new ApiService(),
    },
  ],
})

app.plugin(loggerPlugin)

```

---

## Define Action

```ts
import { defineAction } from 'react-runtime-pipe'
import { globalEvents } from 'react-runtime-pipe/react'
import { logger, retry } from 'react-runtime-pipe/middleware'

import { ApiServiceToken } from './tokens'

export const saveUserAction = defineAction({
  name: 'save-user',

  middleware: [logger, retry(3)],

  async handler({ input, context }) {
    context.logger.info('saving user', input)

    const api = context.container.resolve(ApiServiceToken)

    const user = await api.saveUser(input)

    // context.events.$emit('user:saved', user)
    globalEvents.$emit('user:saved', user)

    return {
      success: true,
      user,
    }
  },
})
```

---

## Service Implementation

```ts
// api.ts
import { createRequestId } from 'react-runtime-pipe'

export class ApiService {
  async saveUser(input: any) {
    await new Promise((resolve) => {
      setTimeout(resolve, 500)
    })

    return {
      id: createRequestId(),
      input,
    }
  }
}
```

---

## Tokens

```ts
// tokens.ts
import { createToken } from 'react-runtime-pipe'

import type { ApiService } from './services/api'

export const ApiServiceToken =
  createToken<ApiService>('ApiService')
```

---

## React Usage

```tsx
// Root.tsx
import React from 'react'

import {
  useAction,
  useEventBus,
  globalEvents,
} from 'react-runtime-pipe/react'

import { saveUserAction } from './saveUser'

export function Root() {
  const saveUser = useAction(saveUserAction)

  useEventBus(
    'user:saved',
    () => {
      alert('User saved successfully!')
    },
    globalEvents,
  )

  return (
    <div>
      <button
        disabled={saveUser.loading}
        onClick={() => {
          saveUser.execute({
            name: 'Tung',
          })
        }}
      >
        {saveUser.loading
          ? 'Saving...'
          : 'Save User'}
      </button>

      {saveUser.error ? (
        <pre>{String(saveUser.error)}</pre>
      ) : null}
    </div>
  )
}
```

---

## Bootstrap

```tsx
// main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'

import { RuntimeProvider } from 'react-runtime-pipe/react'

import { Root } from './Root'
import { app } from './app'

async function bootstrap() {
  await app.run()

  const rootElement =
    document.getElementById('root')

  if (!rootElement) {
    throw new Error(
      'Root element #root not found',
    )
  }

  ReactDOM.createRoot(rootElement).render(
    <React.StrictMode>
      <RuntimeProvider app={app}>
        <App />
      </RuntimeProvider>
    </React.StrictMode>,
  )
}

bootstrap()
```

---

# Dependency Injection

```ts
import { createApp, createToken } from 'react-runtime-pipe'

class ApiService {
  async saveUser() {}
}

const ApiServiceToken = createToken<ApiService>('ApiService')

const app = createApp({
  services: [
    {
      token: ApiServiceToken,
      useFactory: () =>
        new ApiService(),
    },
  ],
})
```

---

## Service Injection in Actions

```ts
import { defineAction } from 'react-runtime-pipe'

import { ApiServiceToken } from './tokens'

export const saveUserAction =
  defineAction({
    name: 'save-user',

    async handler({
      input,
      context,
    }) {
      const api =
        context.container.resolve(
          ApiServiceToken,
        )

      return api.saveUser(input)
    },
  })
```

---

## useService hook

```tsx
import { useService } from 'react-runtime-pipe/react'

import { ApiServiceToken } from './tokens'

export function UserPage() {
  const api = useService(ApiServiceToken)

  return null
}
```

---

# Runtime Context

| Property      | Description              |
|---------------|--------------------------|
| ctx.requestId | execution request id     |
| ctx.signal    | abort signal             |
| ctx.logger    | runtime logger           |
| ctx.container | dependency container     |
| ctx.events    | execution event bus      |
| ctx.cache     | scoped execution cache   |
| ctx.auth      | authenticated user state |

---

# Event Bus

## Emit Event

```ts
globalEvents.$emit(
  'user:saved',
  user,
)
```

---

## Listen To Event

```ts
useEventBus(
  'user:saved',
  (payload) => {
    console.log(payload)
  },
  globalEvents,
)
```

---

# Middleware

Middleware in `react-runtime-pipe` allows you to intercept and control the execution flow of an action.

Here is a simple example: **Abort Middleware** — used to stop execution if the request has been cancelled.

```ts
import type { Middleware } from 'react-runtime-pipe'

export const abortMiddleware: Middleware = async ({
  context,
  next,
}) => {
  // check before executing the handler
  context.throwIfAborted()

  const result = await next()

  // check after the handler finishes
  context.throwIfAborted()

  return result
}
```

This middleware ensures that if the user triggers an abort (e.g. cancels a request or unmounts a component), the action is immediately stopped both before and after execution.

---

# Plugins

```ts
import type { Plugin } from 'react-runtime-pipe'

const analyticsPlugin: Plugin = {
  name: 'analytics',

  setup({ app }) {
    console.log('plugin setup')
  },
}

app.plugin(analyticsPlugin)
```

---

# React Runtime Provider

```tsx
import { RuntimeProvider } from 'react-runtime-pipe/react'

import { app } from './app'

export function Root() {
  return (
    <RuntimeProvider app={app}>
      <App />
    </RuntimeProvider>
  )
}
```

---

# Action Lifecycle

```text
useAction.execute()
      ↓
createExecutionContext()
      ↓
composeMiddleware()
      ↓
executeHandler()
      ↓
emitEvents()
      ↓
returnResult()
```

---

# Execution Cache

```ts
const cacheKey = 'user:1'

if (ctx.cache.has(cacheKey)) {
  return ctx.cache.get(cacheKey)
}

const user = await api.getUser()

ctx.cache.set(cacheKey, user)

return user
```

---

# Abort Support

```ts
ctx.throwIfAborted()
```

```ts
ctx.signal.addEventListener(
  'abort',
  () => {
    console.log('cancelled')
  },
)
```

---

# Full Example

```ts
import { defineAction } from 'react-runtime-pipe'

import {
  logger,
  retry,
  auth,
} from 'react-runtime-pipe/middleware'
import { globalEvents } from 'react-runtime-pipe/react'

import { ApiServiceToken } from './tokens'

export const saveUserAction =
  defineAction({
    name: 'save-user',

    middleware: [
      auth,
      logger,
      retry(),
    ],

    async handler({
      input,
      context,
    }) {
      const api =
        context.container.resolve(
          ApiServiceToken,
        )

      const user =
        await api.saveUser(input)

      globalEvents.$emit(
        'user:saved',
        user,
      )

      return {
        success: true,
        user,
      }
    },
  })
```

---

# Comparison

| Criteria            | react-runtime-pipe | Redux | React Query |
|---------------------|--------------------|-------|-------------|
| Middleware Pipeline | ✅                 | ⚠️    | ❌           |
| Dependency Injection| ✅                 | ❌    | ❌           |
| Runtime Context     | ✅                 | ❌    | ❌           |
| Event Bus           | ✅                 | ❌    | ❌           |
| Orchestration Layer | ✅                 | ❌    | ⚠️           |
| Async Data Handling | ⚠️                 | ❌    | ✅           |
| Plugin System       | ✅                 | ❌    | ❌           |
| Action-based API    | ✅                 | ❌    | ❌           |
| Execution Pipeline  | ✅                 | ❌    | ❌           |
---

# Philosophy

```text
You control:
- middleware
- plugins
- orchestration
- architecture
- services

react-runtime-pipe controls:
- execution
- lifecycle
- context
- typing
- pipelines
```

---

# Design

- Explicit execution
- Composition over inheritance
- Backend-inspired architecture
- Runtime-first design
- Framework-agnostic core
- Deterministic orchestration

---

# When to Use

- Large React applications
- Complex async flows
- Plugin-based frontends
- Runtime orchestration systems
- Event-driven UI architecture
- Shared business execution layers
- Backend/frontend unified patterns

---

# License

MIT