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

strus-middleware

v3.0.0

Published

API response monitoring middleware for Strus

Readme

strus-middleware

API response monitoring middleware for Strus. Intercepts HTTP responses, extracts structural signals from response bodies, and sends them to Strus in batches.

Response bodies are processed in your server's process. Only field-level signals (null rates, enum distributions, array sizes) are sent to Strus. PHI/PII fields are automatically excluded.

Works everywhere: Cloudflare Workers, Vercel, AWS Lambda, Deno Deploy, Fly.io, Railway, Render, Docker, plain Node.js, Bun, and anything else that runs JavaScript.

Install

npm install strus-middleware

Quick Start

Pick the adapter for your framework. The client is safe to construct at module level on any platform; it only starts its internal timer on the first request.

Hono

Works on Cloudflare Workers, Cloudflare Pages, Vercel Edge, Deno Deploy, Bun, Node.js, and any other runtime Hono supports. On Workers and Pages, the adapter automatically uses executionCtx.waitUntil to ensure telemetry flushes complete before the isolate dies.

import { Hono } from "hono";
import { StrusClient } from "strus-middleware";
import { strusHono } from "strus-middleware/hono";

const strus = new StrusClient({ apiKey: "sk_..." });
const app = new Hono();

app.use("*", strusHono(strus));

app.get("/api/patients", (c) => {
  return c.json({ patients: [] });
});

export default app;

Next.js

Works on Vercel (Edge and Serverless), self-hosted Next.js, and Next.js on Cloudflare (via next-on-pages or OpenNext). The adapter wraps your route handlers and automatically picks up waitUntil from the Next.js request context on Vercel, or from the event argument when available.

// app/api/patients/route.ts
import { StrusClient } from "strus-middleware";
import { strusNextjs } from "strus-middleware/nextjs";

const strus = new StrusClient({ apiKey: "sk_..." });
const { wrapHandler } = strusNextjs(strus);

export const GET = wrapHandler(async (req) => {
  const patients = await getPatients();
  return Response.json({ patients });
});

export const POST = wrapHandler(async (req) => {
  const body = await req.json();
  const patient = await createPatient(body);
  return Response.json(patient, { status: 201 });
});

Wrap each route handler you want to monitor. The original response is returned unchanged to the caller.

Express

Works on Node.js, Bun, AWS Lambda (via serverless-express or similar), Google Cloud Run, Docker, EC2, Fly.io, Railway, Render, and anywhere Express runs.

import express from "express";
import { StrusClient } from "strus-middleware";
import { strusExpress } from "strus-middleware/express";

const strus = new StrusClient({ apiKey: "sk_..." });
const app = express();

app.use(strusExpress(strus));

app.get("/api/patients", (req, res) => {
  res.json({ patients: [] });
});

app.listen(3000);

Fastify

Works everywhere Fastify runs. Register it as a standard Fastify plugin.

import Fastify from "fastify";
import { StrusClient } from "strus-middleware";
import { strusFastify } from "strus-middleware/fastify";

const strus = new StrusClient({ apiKey: "sk_..." });
const fastify = Fastify();

fastify.register(strusFastify(strus));

fastify.get("/api/patients", async () => {
  return { patients: [] };
});

fastify.listen({ port: 3000 });

Direct Usage

If your framework is not listed above, use StrusClient directly. Call observe after each response and flushAsync to send events. Pass the returned Promise to whatever lifecycle hook your platform provides (waitUntil, event.waitUntil, Lambda callback, etc.).

import { StrusClient } from "strus-middleware";

const strus = new StrusClient({ apiKey: "sk_..." });

// Inside your request handler:
strus.observe({
  method: "GET",
  path: "/api/patients",
  statusCode: 200,
  responseBody: data,
});

// Flush and wait for delivery:
await strus.flushAsync();

// Or on a platform with waitUntil:
ctx.waitUntil(strus.flushAsync());

Platform Guide

The middleware handles platform differences automatically. Here is what happens on each platform so you know what to expect.

| Platform | Adapter | Flush mechanism | Notes | |----------|---------|-----------------|-------| | Cloudflare Workers | Hono | executionCtx.waitUntil | Automatic. Safe to construct client at module top level. | | Cloudflare Pages | Hono | executionCtx.waitUntil | Same as Workers. | | Vercel Edge | Hono or Next.js | Global @next/request-context waitUntil | Automatic via Next.js adapter. Hono adapter flushes per-request. | | Vercel Serverless | Next.js or Express | Global @next/request-context waitUntil | Next.js adapter picks up waitUntil automatically. | | AWS Lambda | Express or Direct | await flushAsync() before handler returns | Express adapter flushes per-request. Call shutdown() on SIGTERM for clean exit. | | Lambda@Edge | Direct | await flushAsync() | Constrained execution window. Flush before returning. | | Deno Deploy | Hono | Per-request flush | Timer fallback for long-lived Deno processes. | | Google Cloud Run | Express or Fastify | Per-request flush + timer | Container stays alive between requests; timer handles the rest. | | Azure Functions | Express or Direct | await shutdown() before function completes | Ensure buffer is drained. | | Netlify Edge Functions | Hono | Per-request flush | Deno-based edge runtime. | | Netlify Functions | Express | Per-request flush | AWS Lambda under the hood. | | Fly.io | Any | Per-request flush + timer | Long-running containers. Timer starts on first request. | | Railway | Any | Per-request flush + timer | Long-running containers. | | Render | Any | Per-request flush + timer | Long-running containers. | | Docker / EC2 / ECS | Any | Per-request flush + timer | Long-running. Call shutdown() on SIGTERM. | | Node.js | Any | Per-request flush + timer | Timer is unrefed so it does not keep the process alive. | | Bun | Any | Per-request flush + timer | Same as Node.js. | | Fastly Compute | Hono or Direct | Per-request flush | Single-request lifecycle. | | Next.js on Cloudflare | Next.js | waitUntil via event arg | Works with next-on-pages and OpenNext. |

Configuration

const strus = new StrusClient({
  apiKey: "sk_...",                    // required
  endpoint: "https://...",             // defaults to Strus production
  batchSize: 50,                       // events per batch (default: 50)
  flushIntervalMs: 5000,               // flush interval in ms (default: 5000)
  enabled: true,                       // toggle on/off (default: true)
  extraction: {
    excludePaths: ["internal.debug"],   // additional paths to skip
    maxDepth: 10,                       // max nesting depth to walk
  },
  onError: (err) => console.error(err),
});

Extraction Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxDepth | number | 10 | Maximum depth to walk nested objects | | maxArraySample | number | 5 | Number of array elements to sample | | maxEnumCardinality | number | 50 | Max unique values before a string field stops being treated as an enum | | excludePaths | string[] | [] | Additional field paths to skip (on top of the built-in PHI/PII list) | | excludePatterns | RegExp[] | [] | Additional regex patterns for fields to skip |

PHI/PII fields like firstName, lastName, ssn, email, dateOfBirth, diagnosis, medicalRecord, and similar patterns are excluded automatically. You never need to configure these.

How It Works

The middleware intercepts responses and extracts structural metadata from the response body. Extraction happens synchronously in your process. Only signals (null rates, enum distributions, array cardinalities, new values) are collected. The actual field values for sensitive data are never captured.

Each adapter calls flushAsync() after every request to send buffered events to Strus. On platforms with waitUntil (Cloudflare Workers, Vercel), the adapter pipes the flush promise through it so the runtime stays alive until delivery completes. On long-running servers, a background timer (unrefed, so it will not keep your process alive) provides an additional safety net.

The timer starts lazily on the first observe call, not in the constructor. This means constructing a StrusClient at module top level is safe on every platform, including Cloudflare Workers where global-scope timers are not allowed.

Graceful Shutdown

On long-running servers, call shutdown() before your process exits to flush any remaining buffered events:

process.on("SIGTERM", async () => {
  await strus.shutdown();
  process.exit(0);
});

On serverless platforms this is not necessary since the adapters flush per-request.

License

MIT