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

@usebetterdev/audit-next

v0.8.1

Published

Next.js App Router adapter for [`@usebetterdev/audit-core`](../core). Extracts actor identity from incoming requests and propagates it via `AsyncLocalStorage` so that every audit log captured during a request automatically includes the correct actor.

Readme

@usebetterdev/audit-next

Next.js App Router adapter for @usebetterdev/audit-core. Extracts actor identity from incoming requests and propagates it via AsyncLocalStorage so that every audit log captured during a request automatically includes the correct actor.

Installation

pnpm add @usebetterdev/audit-next @usebetterdev/audit-core

Requires next >= 14.

How context propagation works in Next.js

Next.js App Router has three distinct execution contexts, each requiring a different approach:

| Context | Has Request? | ALS propagates? | Use | |---|---|---|---| | Edge Middleware (middleware.ts) | Yes | No — separate isolate | createAuditMiddleware | | Route Handler (route.ts) | Yes | Yes | withAuditRoute | | Server Action | No | Yes | withAudit |

Edge Middleware runs in a separate V8 isolate from route handlers. AsyncLocalStorage set in middleware does not carry over. Instead, createAuditMiddleware extracts the actor from the request and forwards it as a request header (x-better-audit-actor-id by default). withAuditRoute then reads that header and sets up ALS for the handler's execution.

Quick start

Option A — Middleware + route handlers (recommended for APIs)

// middleware.ts
import { createAuditMiddleware } from "@usebetterdev/audit-next";

export default createAuditMiddleware();
export const config = { matcher: "/api/:path*" };
// app/api/orders/route.ts
import { withAuditRoute, fromHeader, AUDIT_ACTOR_HEADER, getAuditContext } from "@usebetterdev/audit-next";
import { audit } from "@/lib/audit";

async function handler(request: NextRequest) {
  const ctx = getAuditContext(); // { actorId: "user-123" }
  await audit.captureLog({ tableName: "orders", operation: "INSERT", recordId: "ord-1", after: {} });
  return Response.json({ ok: true });
}

export const POST = withAuditRoute(handler, {
  extractor: { actor: fromHeader(AUDIT_ACTOR_HEADER) },
});

Use AUDIT_ACTOR_HEADER to keep the header name in sync between middleware and route handlers without hardcoding strings.

Option B — Route handler only (no middleware)

If you don't use Next.js middleware, withAuditRoute can extract directly from the request:

// app/api/orders/route.ts
import { withAuditRoute } from "@usebetterdev/audit-next";

export const POST = withAuditRoute(handler);
// Reads `sub` from Authorization: Bearer <jwt> by default

Option C — Server actions

// app/actions.ts
"use server";
import { withAudit, getAuditContext } from "@usebetterdev/audit-next";
import { audit } from "@/lib/audit";

export const createOrder = withAudit(async (formData: FormData) => {
  const ctx = getAuditContext(); // { actorId: "user-123" }
  await audit.captureLog({ tableName: "orders", operation: "INSERT", recordId: "ord-1", after: {} });
});

Server actions don't receive a Request object. withAudit reads all request headers via next/headers and constructs a synthetic request to run the extractor against.

Actor extraction

All three wrappers use a ContextExtractor to resolve the actor. The default extracts the sub claim from Authorization: Bearer <jwt>. The token is decoded without signature verification — that is the auth layer's responsibility.

Custom JWT claim

import { fromBearerToken } from "@usebetterdev/audit-next";

withAuditRoute(handler, {
  extractor: { actor: fromBearerToken("user_id") },
});

Header-based extraction

Common when running behind an API gateway that forwards identity as a plain header:

import { fromHeader } from "@usebetterdev/audit-next";

withAuditRoute(handler, {
  extractor: { actor: fromHeader("x-user-id") },
});

Cookie-based extraction

import { fromCookie } from "@usebetterdev/audit-next";

withAudit(action, {
  extractor: { actor: fromCookie("session_id") },
});

Custom extractor

Write any async function that receives a Request and returns a string or undefined:

withAuditRoute(handler, {
  extractor: {
    actor: async (request) => {
      const key = request.headers.get("x-api-key");
      if (!key) return undefined;
      const owner = await resolveApiKeyOwner(key);
      return owner?.id;
    },
  },
});

Error handling

All wrappers fail open — if extraction fails, the request or action proceeds without audit context. No request is ever blocked by the audit layer.

Use onError to observe failures:

createAuditMiddleware({
  onError: (error) => console.error("Audit extraction failed:", error),
});

Security note

createAuditMiddleware always overwrites the actor header on the forwarded request — including when extraction fails (sets it to ""). This prevents clients from spoofing the actor identity by sending the header directly. Do not trust x-better-audit-actor-id on incoming requests unless it was set by your middleware.

API

createAuditMiddleware(options?)

Creates a Next.js edge middleware function. Extracts actor from the request and forwards it as a request header to downstream route handlers.

betterAuditNext(options?)

Convenience alias for createAuditMiddleware. Use as the default export in middleware.ts.

withAuditRoute(handler, options?)

Wraps an App Router route handler. Extracts actor from the incoming NextRequest and runs the handler inside an ALS scope.

withAudit(action, options?)

Wraps a server action. Reads headers via next/headers, extracts actor, and runs the action inside an ALS scope.

Options (all wrappers):

| Option | Type | Description | |---|---|---| | extractor | ContextExtractor | Actor extractor config. Defaults to JWT sub claim. | | onError | (error: unknown) => void | Called when extraction throws. Defaults to no-op. |

Additional option for createAuditMiddleware:

| Option | Type | Description | |---|---|---| | actorHeader | string | Header name for forwarding the actor id. Defaults to AUDIT_ACTOR_HEADER. |

AUDIT_ACTOR_HEADER

The default header name ("x-better-audit-actor-id") used to forward the actor id from middleware to route handlers. Import this constant in both files to avoid hardcoding the string.

getAuditContext()

Returns the current AuditContext from ALS, or undefined when called outside a request scope. Re-exported from @usebetterdev/audit-core for convenience.