# Pipelines

Every primitive with a `handler` in Alepha is a pipeline: a handler with a list
of middleware wrapped around it. `$action`, `$job` and `$page` all extend the
same base, which is why they all take a `use` array, and `$pipeline` is that
base exposed on its own for the functions that are not any of those.

```typescript
import { $pipeline } from "alepha";
```

That shared shape is what makes a middleware one topic instead of one per
primitive: each works anywhere a `use` array does.

## `$pipeline`

Wrap a plain function and call it like a function:

```typescript
import { $pipeline } from "alepha";
import { $lock } from "alepha/lock";

class OrderService {
  processOrder = $pipeline({
    use: [$lock({ name: "process-order" })],
    handler: async (orderId: string) => {
      return { orderId, status: "paid" };
    },
  });

  async run(id: string) {
    return this.processOrder(id);
  }
}
```

Reach for it when the work is not a route, a job or a page: an internal service
method called from several places that should carry its middleware with it
rather than have each caller remember one.

### Order in `use` is not cosmetic

The first middleware in the array is the outermost. `use: [A, B]` composes as
`A(B(handler))`, so A sees the call first and the result last. A lock placed
before a cache middleware serialises the misses; placed after it, only the
handler is serialised and the cache answers concurrently. Both are reasonable.
Only one is what you meant.

## Writing a middleware

A middleware is a function that takes the next handler and returns a wrapped one. `createMiddleware` builds it with the container already resolved, and attaches the `name` and `options` that tooling reads back from `primitive.middlewares`:

```typescript
import { $inject, $pipeline, createMiddleware, type Middleware } from "alepha";
import { DateTimeProvider } from "alepha/datetime";

const $timed = (label: string): Middleware => {
  const time = $inject(DateTimeProvider);
  return createMiddleware({
    name: "$timed",
    options: { label },
    handler: ({ next }) => {
      return async (...args) => {
        const start = time.nowMillis();
        try {
          return await next(...args);
        } finally {
          console.log(`${label}: ${time.nowMillis() - start}ms`);
        }
      };
    },
  });
};

class ReportService {
  build = $pipeline({
    use: [$timed("report")],
    handler: async (month: string) => ({ month }),
  });
}
```

`createMiddleware` calls [`$context()`](https://latest.alepha.dev/docs/guides-core-alepha-instance.md#writing-your-own-primitive-with-context), so it must run where a primitive would: inside a property initializer, which is where a `use` array is written. The `handler` itself runs later, the first time the pipeline is called, once the app has started and the container is locked. Resolve the services a middleware needs in the factory with `$inject`, as above, rather than with `alepha.inject()` inside `handler`. A `meta` record of free-form capability flags can be attached as well, for consumers that react to what a middleware does without knowing which one it is.

## `$scope`

Host primitives run their handler inside an AsyncLocalStorage scope, which is
what makes `alepha.context.get()` and `.set()` work per request. A standalone
`$pipeline` has no such scope, so add one when the handler needs it:

```typescript
import { $pipeline, $scope } from "alepha";

class OrderService {
  processOrder = $pipeline({
    use: [$scope()],
    handler: async (orderId: string) => {
      return orderId;
    },
  });
}
```

`$scope()` throws when a scope already exists, on purpose: nesting one would
give you a second, empty context that silently loses everything the outer one
held. That rules it out of an `$action`, `$job` or `$page`, and out of a
pipeline called from inside a request.

## See also

- [Middlewares](https://latest.alepha.dev/docs/guides-server-middlewares.md) for the server-side middleware
  that ships with `AlephaServer`
- [Caching](https://latest.alepha.dev/docs/guides-persistence-caching.md) for `$cache`
- [Background Jobs](https://latest.alepha.dev/docs/guides-features-background-jobs.md) for work that has to
  survive the process, with its own durable retries
