npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stackra/pipeline

v2.0.0

Published

Laravel-style middleware pipeline with fluent API, DI integration, and Hub presets.

Readme

@stackra/pipeline

Laravel-style middleware pipeline with a fluent API, DI-container integration, and named preset registration via PipelineHub. Framework-agnostic, zero server dependency, works in every JavaScript runtime.

Install

pnpm add @stackra/pipeline @stackra/container @stackra/contracts reflect-metadata

Peer requirements: @stackra/container, @stackra/contracts, reflect-metadata.

Quick start

import "reflect-metadata";
import { ApplicationFactory, Module } from "@stackra/container";
import { PipelineModule, Pipeline } from "@stackra/pipeline";

@Module({
  imports: [PipelineModule.forRoot()],
})
class AppModule {}

const app = await ApplicationFactory.create(AppModule);

const result = new Pipeline<number>(app)
  .send(1)
  .through([(n, next) => next(n + 1), (n, next) => next(n * 10)])
  .thenReturn();

console.log(result); // 20

Public API

Pipeline<TPassable, TReturn>

The fluent pipeline class. Composes a chain of pipes that transform a passable value.

new Pipeline(container)
  .send(input) // set the passable
  .through(pipes) // replace all pipes
  .pipe(extra) // append additional pipes
  .via("process") // override handler method name (default: 'handle')
  .finally((v) => log(v)) // callback after execution
  .then((v) => destination) // execute with destination
  // — or —
  .thenReturn(); // execute and return the passable as-is

PipelineHub

Named pipeline preset registry — reusable pipeline configurations invoked by name.

import { PipelineHub } from "@stackra/pipeline";

@Injectable()
class OrderService {
  constructor(private hub: PipelineHub) {}

  onModuleInit() {
    this.hub.defaults((pipeline, passable) =>
      pipeline.send(passable).through([Sanitize]).thenReturn(),
    );

    this.hub.pipeline("order-validation", (pipeline, order) =>
      pipeline
        .send(order)
        .through([ValidateStock, ValidatePayment, ValidateAddress])
        .thenReturn(),
    );
  }

  validate(order: Order) {
    return this.hub.pipe(order, "order-validation");
  }
}

Hub runtime API:

hub.defaults(cb);          // set the default pipeline definition
hub.pipeline(name, cb);    // register a named pipeline
hub.pipe(passable, name?); // execute a named or default pipeline
hub.has(name);             // boolean — does this name exist?

PipelineModule

DI module exposing Pipeline, PipelineHub, and PIPELINE_FACTORY. Import once at the root:

@Module({
  imports: [PipelineModule.forRoot()],
})
class AppModule {}

PIPELINE_FACTORY

DI token for a factory that produces fresh Pipeline instances backed by the container.

import { Inject, Injectable } from "@stackra/container";
import { PIPELINE_FACTORY, PipelineFactory } from "@stackra/pipeline";

@Injectable()
class RequestHandler {
  constructor(
    @Inject(PIPELINE_FACTORY) private makePipeline: PipelineFactory,
  ) {}

  handle(request: Request) {
    return this.makePipeline<Request, Response>()
      .send(request)
      .through([Auth, Logging, Handler])
      .thenReturn();
  }
}

PipelineError

Thrown when a pipe cannot be resolved or executed. Carries a code for programmatic dispatch:

| Code | Meaning | | ------------------------ | ------------------------------------------------------------ | | INVALID_PIPE_TYPE | Pipe was not a function, string, object, or tuple | | INVALID_PIPE_ENTRY | Tuple's first element was of an unsupported type | | NO_CONTAINER | String pipe used but no container provided to Pipeline | | PIPE_RESOLUTION_FAILED | Container threw while resolving a string pipe | | INVALID_RESOLVED_PIPE | Container returned a non-object for a string pipe | | METHOD_NOT_FOUND | Pipe object is missing the configured handler method | | PIPE_EXECUTION_FAILED | A pipe threw during execution (cause carries the original) | | INVALID_PIPELINE_NAME | Empty name passed to hub.pipeline(name, cb) | | PIPELINE_NOT_FOUND | hub.pipe(v, name) called with an unregistered name | | NO_DEFAULT_PIPELINE | hub.pipe(v) called with no name and no default registered |

Pipe forms

A pipe can be any of four shapes:

Closure

Simplest form — a function taking (passable, next):

const AddOne = (n: number, next: (n: number) => number) => next(n + 1);

new Pipeline<number>().send(1).through([AddOne]).thenReturn(); // 2

String (DI-resolved)

Resolved from the container by name/token. The resolved instance must have a handler method (default handle):

@Injectable()
class LoggingPipe {
  handle(request: Request, next: (r: Request) => Response) {
    console.log(request.url);
    return next(request);
  }
}

// register `LoggingPipe` under the string 'logging' via a module provider
// ...
new Pipeline<Request, Response>(container)
  .send(request)
  .through(["logging"])
  .then((r) => processRequest(r));

Object

Instance with a handler method — bypasses container resolution:

new Pipeline<Request, Response>()
  .send(request)
  .through([new LoggingPipe()])
  .then((r) => processRequest(r));

Tuple (parameterized)

[pipe, ...params] — extra params flow to the handler after (passable, next):

@Injectable()
class RateLimitPipe {
  handle(
    req: Request,
    next: (r: Request) => Response,
    limit: number,
    window: string,
  ) {
    // ...
    return next(req);
  }
}

new Pipeline<Request, Response>(container)
  .send(request)
  .through([["rate-limit", 100, "1m"]])
  .then((r) => processRequest(r));

Container integration

Every string pipe is resolved through the @stackra/container DI system. The Pipeline constructor takes an optional IApplication:

  • With container — string pipes work, Pipeline is itself @Injectable() so it can be injected anywhere.
  • Without container — only function and object pipes are supported (string pipes throw NO_CONTAINER).

When injecting Pipeline into your services, always inject a factory (PIPELINE_FACTORY) rather than a singleton — pipelines are single-use, and reusing the same instance across requests will leak state.

via() — custom method names

By default the pipeline calls handle(passable, next) on pipe objects. Use .via(method) to invoke a different method:

class ProcessorPipe {
  process(data: Data, next: (d: Data) => Data) {
    return next(transform(data));
  }
}

new Pipeline<Data>()
  .send(data)
  .via("process")
  .through([new ProcessorPipe()])
  .thenReturn();

finally() — post-execution callback

The finally callback fires after then() / thenReturn() completes, receiving the (potentially mutated) passable:

new Pipeline<Request, Response>()
  .send(request)
  .through([Auth, Logging])
  .finally((req) => telemetry.record(req))
  .then((r) => handleRequest(r));

PipelineHub presets

Use the Hub when the same pipeline definition is called from multiple sites. Register once, invoke by name — the passable is provided at call time.

@Injectable()
class BootstrapService {
  constructor(private hub: PipelineHub) {}

  onModuleInit() {
    // Preset A — inbound HTTP
    this.hub.pipeline("http-inbound", (pipeline, req) =>
      pipeline.send(req).through([Auth, RateLimit, Logger]).thenReturn(),
    );

    // Preset B — outbound API call
    this.hub.pipeline("http-outbound", (pipeline, req) =>
      pipeline
        .send(req)
        .through([AddAuthHeader, Retry, Telemetry])
        .thenReturn(),
    );
  }
}

// elsewhere
const finalReq = hub.pipe(request, "http-inbound");

Types

type PipeClosure<TPassable, TResult> = (
  passable: TPassable,
  next: (passable: TPassable) => TResult,
) => TResult;

type PipeTuple = [PipeEntry, ...unknown[]];

type PipeEntry = string | object | PipeClosure<unknown, unknown>;

type PipeType = PipeClosure<unknown, unknown> | string | object | PipeTuple;

type PipelineDefinition = <T>(pipeline: Pipeline, passable: T) => unknown;

type PipelineFactory = <TPassable = unknown, TReturn = TPassable>() => Pipeline<
  TPassable,
  TReturn
>;

Error handling

Every pipe execution is wrapped in a try/catch. If a pipe throws:

  • Non-PipelineError — wrapped in a new PipelineError with code PIPE_EXECUTION_FAILED and the original attached as cause.
  • Already a PipelineError — re-thrown unchanged.

This preserves the original stack trace via the cause chain while giving downstream code a stable error surface.

import { PipelineError } from "@stackra/pipeline";

try {
  const result = pipeline.thenReturn();
} catch (err) {
  if (err instanceof PipelineError) {
    console.error(err.code, err.message, err.cause);
  }
}

Integration with @stackra/ssr/middleware

@stackra/pipeline is the runtime that powers HTTP and UI middleware in @stackra/ssr. Any MiddlewareDefinition can be adapted to a PipeType via toPipe(mw, container) and dropped straight into .through([...]).

See the SSR middleware docs for the full integration story.

Testing helper — @stackra/pipeline/testing

Two helpers for exercising pipes in isolation:

import { runPipe, createMockPipeline } from "@stackra/pipeline/testing";

// Run a single pipe end-to-end — great for unit-testing middleware
const result = runPipe<Request>(
  (req, next) => next({ ...req, userId: "ada" }),
  new Request("http://x/"),
  (req) => new Response(JSON.stringify(req)),
);

// Object-form pipe
class AuthPipe {
  handle(req: Request, next: (req: Request) => unknown) {
    if (!req.headers.get("authorization")) throw new Error("unauth");
    return next(req);
  }
}
const authed = runPipe(new AuthPipe(), authRequest, (r) => r);

// Assertable Pipeline — real Pipeline instance wrapped for call recording
const pipeline = createMockPipeline<Request, Response>();
pipeline
  .send(req)
  .through([AuthPipe])
  .then((r) => handle(r));
pipeline.$.assertCalled("through").once();

runPipe supports function, object, and tuple pipe forms. String pipes require a container — use createMockPipeline(container) (or a real Pipeline) for those.

Subpaths

| Import | Purpose | | --------------------------- | ------------------------------------------- | | @stackra/pipeline | PipelineModule, Pipeline, PipelineHub | | @stackra/pipeline/testing | runPipe(), createMockPipeline() |

License

MIT © Figentra L.L.C.