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

@ransjnr/strata

v0.2.0

Published

Declarative, type-safe, parallel request pipeline — a better alternative to middleware

Readme

Strata

Declarative, type-safe, parallel request pipelines — a better alternative to middleware.

npm install strata zod

Middleware says "do these things in this order." Strata says "here's what I need and what I provide — you figure out the order."


Table of Contents


Why not middleware?

Traditional middleware chains are simple to start with and painful to scale. Here are the specific problems Strata was built to solve.

Problem 1 — Everything runs in a single line

Middleware is a queue. Every concern waits for the one before it, even when they have nothing to do with each other.

Request → Logger (30ms) → Auth (50ms) → RateLimit (20ms) → Handler
                                                             Total: 100ms

Logging and authentication are completely independent. They could run at the same time. But middleware has no way to express that.

With Strata, independent concerns run in parallel automatically:

Wave 1:  Logger + Auth run together (50ms)
Wave 2:  RateLimit runs after Auth (20ms)
Wave 3:  Handler runs (context is ready)
                                         Total: 70ms

No special configuration. Just declare your dependencies honestly and Strata finds the fastest valid execution order.

Problem 2 — Passing data between layers requires mutation hacks

Auth middleware figures out who the user is. Your route handler needs that user. In Express and Next.js the common answer is to mutate the request object:

// Express middleware
app.use((req, res, next) => {
  req.user = decodeToken(req.headers.authorization) // mutate request
  next()
})

// Route handler
app.get('/me', (req, res) => {
  console.log(req.user?.name) // hope it's there. TypeScript has no idea.
})

This pattern has no contract. TypeScript doesn't know req.user exists. You get any types, undefined surprises, and no way for the type system to verify correctness.

Strata gives every concern a typed provides declaration. Your handler receives a fully inferred context object — TypeScript knows exactly what's in it because the types flow from the strata definitions.

Problem 3 — One file for everything (Next.js)

Next.js gives you a single middleware.ts at the project root. All routing concerns live there: auth guards, redirects, locale detection, A/B testing, bot protection. This file grows without bound and can't be co-located with the routes it protects.

Strata lets you define concerns anywhere and compose them directly in each route file.

Problem 4 — Order is invisible

If you register rate limiting before authentication, anonymous requests eat into your rate limit budget. The correct order lives only in your head — nothing in the code enforces or explains it.

With requires, dependencies are explicit. Strata enforces the correct order structurally and detects cycles at startup.


Core idea

Each concern in Strata is called a stratum. A stratum is a small, self-contained unit that:

  1. Provides — declares the typed data it will produce (a Zod schema)
  2. Requires — lists other strata it depends on
  3. Resolves — an async function that produces its output
stratum = provides + requires + resolve

When you build a route, you hand it a list of strata. Strata reads the dependency graph, executes strata in the fastest valid order, and passes a fully typed context to your handler.


Installation

npm install strata zod
# or
pnpm add strata zod
# or
yarn add strata zod

Strata requires Zod v3 as a peer dependency for schema declarations. TypeScript 5.0+ is recommended for best type inference.


Quick start

import { stratum, route, StratumError } from 'strata'
import { z } from 'zod'

// 1. Define a concern
const authStratum = stratum({
  name: 'auth',
  provides: z.object({
    user: z.object({ id: z.string(), role: z.enum(['admin', 'user']) }),
  }),
  requires: [],
  resolve: async ({ req }) => {
    const token = req.headers.get('authorization')?.replace('Bearer ', '')
    if (!token) throw new StratumError(401, 'Missing token')
    const user = await verifyToken(token) // your auth logic
    if (!user) throw new StratumError(401, 'Invalid token')
    return { user }
  },
})

// 2. Use it in a route
export const GET = route({
  strata: [authStratum],
  handler: async ({ req, context }) => {
    // context.auth.user is fully typed — no casting needed
    return Response.json({ hello: context.auth.user.id })
  },
})

API reference

stratum()

Defines a single pipeline concern.

function stratum<Name, Provides, Deps>(config: {
  name: Name            // unique string identifier
  provides: Provides    // Zod schema describing what this stratum outputs
  requires: Deps        // array of other StratumDef this stratum depends on
  resolve(args: {
    req: Request
    context: DepsContext<Deps>  // typed output of all required strata
  }): Promise<z.infer<Provides>>
}): StratumDef<Name, Provides, Deps>

Parameters

| Field | Type | Description | |---|---|---| | name | string | Unique name. Used as the key in the route context object. | | provides | z.ZodObject | Zod schema. The shape returned by resolve. | | requires | StratumDef[] | Other strata this one depends on. Their output is in context. | | resolve | async fn | Performs the work. Receives req and typed context from deps. |

Throwing errors

Throw StratumError inside resolve to short-circuit the entire pipeline and return an HTTP error response immediately. No other strata or the handler will run.

if (!token) throw new StratumError(401, 'Unauthorized')

Any unhandled Error (not a StratumError) will become a 500 Internal Server Error response.

Example — a stratum with a dependency

const rateLimitStratum = stratum({
  name: 'rateLimit',
  provides: z.object({ remaining: z.number() }),
  requires: [authStratum],          // depends on auth
  resolve: async ({ context }) => {
    // context.auth is fully typed here because authStratum is in requires
    const remaining = await checkLimit(context.auth.user.id)
    if (remaining <= 0) throw new StratumError(429, 'Rate limit exceeded')
    return { remaining }
  },
})

route()

Composes strata into a standard route handler function.

function route<Strata>(def: {
  strata: Strata                              // array of StratumDef
  handler(args: {
    req: Request
    context: RouteContext<Strata>             // fully typed union of all strata outputs
  }): Promise<Response>
}): (req: Request) => Promise<Response>

Returns a (req: Request) => Promise<Response> function — the standard signature used by Next.js App Router, Cloudflare Workers, Hono, and any runtime using the web Fetch API.

// app/api/posts/route.ts
export const GET = route({
  strata: [loggingStratum, authStratum, rateLimitStratum],
  handler: async ({ req, context }) => {
    // All three strata outputs are available and typed:
    context.logging.requestId    // string
    context.auth.user.role       // 'admin' | 'user'
    context.rateLimit.remaining  // number

    return Response.json({ ok: true })
  },
})

Error handling

  • StratumError thrown from any stratum → short-circuits to that HTTP response
  • Any other Error → becomes 500 { "error": "..." }
  • Both are caught transparently — you don't write try/catch in handlers

route.traced()

Same as route() but attaches an X-Strata-Trace response header with execution timing. Use this during development to see which strata ran in parallel and how long each wave took.

// dev-only: swap route() for route.traced()
export const GET = route.traced({
  strata: [loggingStratum, authStratum, rateLimitStratum],
  handler: async ({ context }) => Response.json({ ok: true }),
})

Response header

X-Strata-Trace: {
  "totalMs": 63,
  "waves": [
    { "parallel": ["logging", "auth"], "ms": 48 },
    { "parallel": ["rateLimit"],       "ms": 15 }
  ]
}

This is the foundation for the upcoming visual devtools panel.


formation()

Bundles a set of strata into a reusable, named group.

function formation<Strata extends readonly AnyStratumDef[]>(
  strata: Strata
): Strata

A formation is just a typed tuple of strata. Use it to define common combinations once and share them across routes.

// strata/formations.ts
export const apiFormation = formation([
  loggingStratum,
  authStratum,
  rateLimitStratum,
])

// Any route can use it
export const GET = route({
  strata: apiFormation,
  handler: async ({ context }) => { ... },
})

// Add extra strata on top for specific routes
export const DELETE = route({
  strata: [...apiFormation, requireRole('admin')],
  handler: async ({ context }) => { ... },
})

StratumError

Thrown inside resolve to short-circuit the pipeline and produce a specific HTTP error response.

class StratumError extends Error {
  constructor(
    status: number,    // HTTP status code
    message: string,   // error message (also used as response body if no body given)
    body?: unknown     // optional custom JSON response body
  )
}

Examples

// Simple — response body becomes { "error": "Unauthorized" }
throw new StratumError(401, 'Unauthorized')

// Custom body
throw new StratumError(429, 'Rate limit exceeded', {
  error: 'rate_limit_exceeded',
  resetAt: 1710000000000,
})

// Forbidden
throw new StratumError(403, 'Requires admin role')

isStratumError(err) — type guard utility:

import { isStratumError } from 'strata'

if (isStratumError(err)) {
  console.log(err.status) // number
}

How the executor works

When a request arrives, Strata runs your strata using a parallel wave executor. Understanding this is the key to getting the most out of Strata.

Step 1 — Collect all strata (including transitive deps)

The executor walks your strata and their requires arrays recursively, deduplicating by name. You never have to manually specify transitive dependencies.

// If rateLimitStratum requires authStratum, and you only list rateLimitStratum,
// Strata will still run authStratum first — it found it through the graph.
route({ strata: [rateLimitStratum], handler: ... })
//                   ↑
//      authStratum is discovered and run automatically

Step 2 — Check for cycles

Before execution starts, Strata runs a depth-first cycle detection pass. If your graph has a cycle (A requires B, B requires A), it throws immediately with a descriptive error:

Strata: circular dependency detected at "auth". Cycle path: auth → session → auth

Step 3 — Execute in waves

The executor repeatedly finds ready strata — those whose every dependency is already resolved — and runs them all with Promise.all. This is true I/O parallelism.

Iteration 1: find strata with no unresolved deps → [logging, auth]
             run both with Promise.all
             mark both resolved

Iteration 2: find strata whose deps are now resolved → [rateLimit]
             run it
             mark resolved

Iteration 3: all resolved → pass context to handler

Step 4 — Build and pass typed context

Every stratum's output is merged into a context object keyed by name. TypeScript infers this type from your strata declarations — no casting, no any.

// What TypeScript sees at compile time:
context: {
  logging:   { requestId: string, startedAt: number }
  auth:      { user: { id: string, email: string, role: 'admin' | 'user' | 'guest' } }
  rateLimit: { remaining: number, resetAt: number }
}

Recipes

These are production-ready strata you can copy, adapt, and use directly.

Logging stratum

// strata/logging.ts
import { stratum } from 'strata'
import { z } from 'zod'

export const loggingStratum = stratum({
  name: 'logging',
  provides: z.object({
    requestId: z.string(),
    startedAt: z.number(),
  }),
  requires: [],
  resolve: async ({ req }) => {
    const requestId = crypto.randomUUID()
    const startedAt = Date.now()
    const { pathname } = new URL(req.url)
    console.log(`→ [${requestId}] ${req.method} ${pathname}`)
    return { requestId, startedAt }
  },
})

Authentication stratum

// strata/auth.ts
import { stratum, StratumError } from 'strata'
import { z } from 'zod'
import { verifyJwt } from '@/lib/jwt'

export const authStratum = stratum({
  name: 'auth',
  provides: z.object({
    user: z.object({
      id: z.string(),
      email: z.string().email(),
      role: z.enum(['admin', 'user', 'guest']),
    }),
  }),
  requires: [],
  resolve: async ({ req }) => {
    const header = req.headers.get('authorization')
    const token = header?.startsWith('Bearer ') ? header.slice(7) : null

    if (!token) throw new StratumError(401, 'Missing authorization header')

    const payload = await verifyJwt(token)
    if (!payload) throw new StratumError(401, 'Invalid or expired token')

    return { user: payload }
  },
})

Rate limiting stratum

// strata/rate-limit.ts
import { stratum, StratumError } from 'strata'
import { z } from 'zod'
import { authStratum } from './auth'
import { redis } from '@/lib/redis'

export const rateLimitStratum = stratum({
  name: 'rateLimit',
  provides: z.object({
    remaining: z.number(),
    resetAt: z.number(),
  }),
  requires: [authStratum],
  resolve: async ({ context }) => {
    const key = `rate:${context.auth.user.id}`
    const window = 60          // 60-second window
    const limit = 100          // 100 requests per window

    const count = await redis.incr(key)
    if (count === 1) await redis.expire(key, window)
    const ttl = await redis.ttl(key)

    const remaining = Math.max(0, limit - count)
    const resetAt = Date.now() + ttl * 1000

    if (remaining === 0) {
      throw new StratumError(429, 'Too many requests', {
        error: 'rate_limit_exceeded',
        remaining: 0,
        resetAt,
      })
    }

    return { remaining, resetAt }
  },
})

Role-based permissions

A factory function that creates permission strata for any required role:

// strata/permission.ts
import { stratum, StratumError } from 'strata'
import { z } from 'zod'
import { authStratum } from './auth'

const roleRank = { admin: 2, user: 1, guest: 0 } as const

export function requireRole(role: 'admin' | 'user') {
  return stratum({
    name: 'permission',
    provides: z.object({ granted: z.literal(true) }),
    requires: [authStratum],
    resolve: async ({ context }) => {
      const userRank = roleRank[context.auth.user.role] ?? 0
      if (userRank < roleRank[role]) {
        throw new StratumError(403, `This action requires the '${role}' role`)
      }
      return { granted: true as const }
    },
  })
}

// Usage
export const DELETE = route({
  strata: [...apiFormation, requireRole('admin')],
  handler: async ({ context }) => { ... },
})

Request body validation

// strata/body.ts
import { stratum, StratumError } from 'strata'
import { z } from 'zod'

export function validateBody<T extends z.ZodTypeAny>(schema: T) {
  return stratum({
    name: 'body',
    provides: z.object({ data: schema }),
    requires: [],
    resolve: async ({ req }) => {
      let raw: unknown
      try {
        raw = await req.json()
      } catch {
        throw new StratumError(400, 'Request body must be valid JSON')
      }
      const result = schema.safeParse(raw)
      if (!result.success) {
        throw new StratumError(422, 'Validation failed', {
          error: 'validation_error',
          issues: result.error.issues,
        })
      }
      return { data: result.data as z.infer<T> }
    },
  })
}

// Usage
const createPostBody = validateBody(z.object({
  title: z.string().min(1),
  content: z.string().min(1),
}))

export const POST = route({
  strata: [...apiFormation, createPostBody],
  handler: async ({ context }) => {
    // context.body.data is typed as { title: string, content: string }
    const post = await db.post.create({ data: context.body.data })
    return Response.json({ post }, { status: 201 })
  },
})

Reusable formations

Formations let you define your common strata combinations once. Think of them as middleware groups with a name.

// strata/formations.ts
import { formation } from 'strata'
import { loggingStratum } from './logging'
import { authStratum } from './auth'
import { rateLimitStratum } from './rate-limit'

// Public routes — just logging
export const publicFormation = formation([loggingStratum])

// Authenticated routes — logging + auth + rate limiting
export const apiFormation = formation([loggingStratum, authStratum, rateLimitStratum])

// Admin routes — same as api, plus permission check
export const adminFormation = formation([
  ...apiFormation,
  requireRole('admin'),
])
// app/api/posts/route.ts
import { apiFormation, adminFormation } from '@/strata/formations'

export const GET = route({
  strata: apiFormation,
  handler: async ({ context }) => { ... },
})

export const DELETE = route({
  strata: adminFormation,
  handler: async ({ context }) => { ... },
})

Using with Next.js App Router

Strata is designed for the App Router. The route() return value matches Next.js's expected (req: Request) => Promise<Response> signature exactly.

your-next-app/
├── app/
│   └── api/
│       ├── posts/
│       │   └── route.ts     ← uses apiFormation
│       └── admin/
│           └── route.ts     ← uses adminFormation
└── strata/
    ├── logging.ts
    ├── auth.ts
    ├── rate-limit.ts
    ├── permission.ts
    └── formations.ts
// app/api/posts/route.ts
import { route } from 'strata'
import { apiFormation } from '@/strata/formations'
import { db } from '@/lib/db'

export const GET = route({
  strata: apiFormation,
  handler: async ({ req, context }) => {
    const { searchParams } = new URL(req.url)
    const page = Number(searchParams.get('page') ?? '1')

    const posts = await db.post.findMany({
      where: { authorId: context.auth.user.id },
      skip: (page - 1) * 10,
      take: 10,
    })

    return Response.json({
      posts,
      requestId: context.logging.requestId,
    })
  },
})

No middleware.ts required. Auth, logging, and rate limiting live next to the routes they protect.


Using with Cloudflare Workers / Hono

Strata uses only web-standard Request and Response — it works in any runtime.

// Cloudflare Worker
import { route } from 'strata'
import { apiFormation } from './strata/formations'

const GET = route({
  strata: apiFormation,
  handler: async ({ context }) => Response.json({ user: context.auth.user }),
})

export default {
  fetch: (req: Request) => GET(req),
}
// Hono
import { Hono } from 'hono'
import { route } from 'strata'
import { apiFormation } from './strata/formations'

const app = new Hono()
const getPosts = route({ strata: apiFormation, handler: ... })

app.get('/posts', (c) => getPosts(c.req.raw))

TypeScript types

Strata exports all its types for advanced use cases.

import type {
  StratumDef,        // the shape of a stratum definition
  AnyStratumDef,     // StratumDef with erased generics
  DepsContext,       // maps a deps tuple to a typed context object
  RouteContext,      // same as DepsContext but for route strata
  ResolveArgs,       // args passed to stratum.resolve()
  HandlerArgs,       // args passed to route.handler()
  ExecutionTrace,    // output of executeStrataWithTrace
  ExecutionWave,     // a single parallel wave in a trace
} from 'strata'

DepsContext<Deps>

Given a tuple of strata, produces the merged typed context:

type MyContext = DepsContext<[typeof authStratum, typeof loggingStratum]>
// → { auth: { user: { id: string, ... } }, logging: { requestId: string, ... } }

HandlerArgs<Strata>

The full argument type for a route handler:

async function myHandler({ req, context }: HandlerArgs<typeof apiFormation>) {
  context.auth.user.id   // string ✓
}

Development and debugging

route.traced() — execution timing

During development, swap route() for route.traced(). Every response will include an X-Strata-Trace header:

{
  "totalMs": 63,
  "waves": [
    { "parallel": ["logging", "auth"], "ms": 48 },
    { "parallel": ["rateLimit"],       "ms": 15 }
  ]
}

This tells you exactly which strata ran in parallel, which had to wait, and how long each wave took. If you see a stratum running alone that could be parallel, check its requires — it may have an unnecessary dependency.

Cycle detection

If you accidentally create a circular dependency, Strata throws at request time (before any I/O):

Error: Strata: circular dependency detected at "session".
       Cycle path: auth → session → auth

Naming collisions

Each stratum name must be unique within a route. If two strata have the same name, the second one silently overwrites the first in context. Name strata clearly and consistently — treat the name as a key.


Roadmap

  • [ ] npm publish — available as strata on the npm registry
  • [ ] Visual devtools panel — browser extension / overlay that renders the execution graph with live timing from X-Strata-Trace
  • [ ] Express adapterexpressRoute() wrapper for Express 4/5 apps
  • [ ] Next.js scaffoldcreate-strata-app or npx strata init to bootstrap a Next.js project with formation folders and example strata
  • [ ] Strata cache — per-request memoization so the same stratum used in multiple routes isn't re-executed for the same request
  • [ ] onError hook — per-stratum error handler for logging or fallback behavior
  • [ ] Stream support — strata that produce streaming responses

Contributing

Contributions are welcome.

git clone https://github.com/ransjnr/strata
cd strata
npm install
npm test          # run tests with vitest
npm run typecheck # verify TypeScript
npm run build     # build CJS + ESM + .d.ts

The core logic lives in four files:

| File | What it does | |---|---| | src/stratum.ts | stratum() factory | | src/executor.ts | dependency collection, cycle detection, parallel wave executor | | src/route.ts | route(), route.traced(), formation() | | src/error.ts | StratumError class |


License

MIT — see LICENSE


Built with the belief that declaring what you need is always better than prescribing the order things run.