# @effectionx/middleware

> Type-safe composable middleware for generators and Effection operations

Latest version **0.1.1** (published 2026-03-22) · MIT license · 0 weekly downloads

## Install

```sh
npm install @effectionx/middleware
pnpm add @effectionx/middleware
yarn add @effectionx/middleware
bun add @effectionx/middleware
```

## Health

**Score 65/100 (B)** — status: stable.

Positive: has types; esm support; no vulnerabilities; has provenance; high maintenance score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.1 |
| Published | 2026-03-22 |
| First published | 2026-03-10 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 56 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 12 |
| Author | engineering@frontside.com |
| Maintainers | frontsidejack |
| Keywords | concurrency, interop |

## Links

- npm: https://www.npmjs.com/package/@effectionx/middleware
- Repository: https://github.com/thefrontside/effectionx
- Homepage: https://github.com/thefrontside/effectionx#readme
- Issues: https://github.com/thefrontside/effectionx/issues
- npm.io page: https://npm.io/package/@effectionx/middleware

## Recent versions

- 0.1.1 (latest) — 2026-03-22
- 0.1.0 — 2026-03-10

## README

# Middleware

Type-safe composable middleware for generators and Effection operations

---

Middleware lets you wrap any function with a chain of interceptors that can
inspect arguments, transform return values, or short-circuit execution entirely.
The `combine()` function composes an array of middleware into a single
middleware, executing left-to-right.

Because middleware is generic over its return type, generator functions compose
through `combine()` the same way plain functions do. This means you can build
middleware pipelines where each layer sets up Effection resources, establishes
context, or manages cleanup — and everything below it in the stack inherits that
context automatically.

## Quick Start

```ts
import { combine } from "@effectionx/middleware";
import type { Middleware } from "@effectionx/middleware";
import type { Operation } from "effection";
import { createContext } from "effection";

type Handler = Middleware<[Request], Operation<Response>>;

const DatabaseConnection = createContext<Connection>("database");

// Each middleware is a generator — it's still running while inner layers execute
const withDatabase: Handler = function* (args, next) {
  const conn = yield* connect(process.env.DATABASE_URL);
  yield* DatabaseConnection.set(conn);
  try {
    return yield* next(...args);
  } finally {
    yield* conn.close();
  }
};

const withTransaction: Handler = function* (args, next) {
  const conn = yield* DatabaseConnection.expect();
  const tx = yield* conn.begin();
  try {
    const response = yield* next(...args);
    yield* tx.commit();
    return response;
  } catch (error) {
    yield* tx.rollback();
    throw error;
  }
};
```

The core handler uses context set up by middleware — but never receives any of
it as parameters:

```ts
function* handleRequest(request: Request): Operation<Response> {
  const conn = yield* DatabaseConnection.expect();
  const posts = yield* conn.query("SELECT * FROM posts");
  return Response.json(posts);
}
```

Compose it all together:

```ts
const handle = combine([withDatabase, withTransaction]);

function* processRequest(request: Request): Operation<Response> {
  return yield* handle([request], handleRequest);
}
```

When `handleRequest` returns (or throws, or is cancelled), the stack unwinds in
reverse: `withTransaction` (commit or rollback) → `withDatabase` (close
connection). Structured concurrency guarantees that no resources leak, even if
the request is cancelled mid-flight.

## Plain Functions

Middleware also works with plain synchronous functions — there's no Effection
dependency. This is useful for argument validation, logging, or result
transformation:

```ts
import { combine } from "@effectionx/middleware";
import type { Middleware } from "@effectionx/middleware";

const logger: Middleware<[number, number], number> = (args, next) => {
  console.log("adding", args);
  const result = next(...args);
  console.log("result", result);
  return result;
};

const doubler: Middleware<[number, number], number> = (args, next) =>
  next(...args) * 2;

const add = combine([logger, doubler]);

add([3, 4], (a, b) => a + b);
// adding [3, 4]
// result 14
// => 14
```

## API

### `Middleware<TArgs, TReturn>`

The type of a single middleware function. It receives the arguments as a tuple
and a `next` function to delegate to the next middleware (or the core function).

```ts
import type { Middleware } from "@effectionx/middleware";

const timer: Middleware<[string], string> = (args, next) => {
  const start = performance.now();
  const result = next(...args);
  console.log(`took ${performance.now() - start}ms`);
  return result;
};
```

A middleware can:

- **Pass through**: call `next(...args)` and return its result
- **Transform arguments**: call `next()` with different arguments
- **Transform the return value**: modify what `next()` returns
- **Short-circuit**: return a value without calling `next()` at all

### `combine(middlewares)`

Compose an array of middleware into a single middleware. Middlewares execute
left-to-right: the first in the array runs outermost.

```ts
import { combine } from "@effectionx/middleware";

const composed = combine([logger, validator, retry]);
const result = composed(["hello"], coreFn);
// Execution: logger → validator → retry → coreFn
```

The returned value is itself a `Middleware`, so it can be nested inside other
`combine()` calls or passed anywhere a middleware is expected.

---
_Source: https://npm.io/package/@effectionx/middleware · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
