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

@aurascope-analytics/forwarder-app-hook

v0.2.0

Published

AuraScope Analytics app-hook forwarder — observes requests from inside a Node.js application and ships them off the request path

Readme

AuraScope app-hook forwarder

The app-hook forwarder is the replayable server-plane sender for request-lifecycle hooks in tenant applications. This package is a Node.js reference implementation for Next.js middleware and SvelteKit server hooks. Its core has no framework dependency; the framework boundary is a small structural request type.

The request path is fail open. observe() captures evidence into memory and returns synchronously. It performs no file-system access, network access, or asynchronous wait. The flush loop forms durable immutable batches and ships them away from the response path.

Frozen wire contract

Each batch is sent to POST {AURASCOPE_INGEST_URL}/v1/logs/app-hook as newline-delimited JSON (NDJSON), with at most 500 non-empty lines. It carries Authorization: Bearer <forwarder secret>, Idempotency-Key: <persisted batch key>, and Content-Type: application/x-ndjson.

Each line has exactly these fields:

| Field | Type | App-hook meaning | | --- | --- | --- | | v | integer | Always 1 | | ts | string | Coordinated Universal Time observation timestamp in RFC 3339 form | | site | string | Tenant site public key (pk_…) | | iid | string | Random process instance identifier, captured once | | n | integer | Per-process monotonic sequence, starting at 1 | | method | string or null | Observed request method | | path | string or null | Full request target; query string is untouched | | status | integer or null | Always null because middleware is pre-response | | ip_evidence | string or null | Complete raw configured header value, not a selected hop | | ua | string or null | Raw user-agent, shipped byte-pristine (never mutated) |

There is no event_id or forwarder line field. Ingest derives event_id; the route segment supplies the app-hook dialect. Ingest also resolves visitor Internet Protocol (IP) evidence and applies the server-plane query allowlist. src/eventId.ts exists only to reproduce ingest's committed golden vectors.

Configuration

loadConfigFromEnv() reads:

  • AURASCOPE_INGEST_URL — required ingest base URL.
  • AURASCOPE_SITE_KEY — required pk_… public site key.
  • AURASCOPE_FORWARDER_SK — required sk_… forwarder secret. It has no default and is never persisted or logged.
  • AURASCOPE_QUEUE_DIR — durable queue root. Defaults to .aurascope/app-hook-queue under the process working directory.
  • AURASCOPE_BUFFER_MAX_EVENTS — optional explicit event-capacity override.
  • AURASCOPE_WORST_CASE_RATE_PER_SEC — sizing input; defaults to 10.
  • AURASCOPE_TARGET_OUTAGE_SEC — sizing input; defaults to 86,400 seconds.
  • AURASCOPE_IP_EVIDENCE_HEADER — raw evidence header; defaults to x-forwarded-for.
  • AURASCOPE_PROBE_MARKER — optional synthetic-sender self-declaration (ADR 0020). Sent as the x-aurascope-probe-marker batch envelope header on every shipped batch — a versioned addition to the frozen v1 contract; event lines (including ua) are never mutated by it.

Every refusal names the variable it is about. The transposed pair — a pk_ in AURASCOPE_FORWARDER_SK and an sk_ in AURASCOPE_SITE_KEY — is its own case and names both variables and both prefixes in one message, because reporting only the first costs the reader a second failed boot to discover the second.

Unless explicitly overridden, buffer capacity is computed as:

bufferMaxEvents = worstCaseRatePerSec × targetOutageSec

Size both inputs from observed tenant peak rate and the outage window the tenant disk must retain. The default is a starting point, not a universal production measurement.

Next.js installation

The durable queue requires the Node.js runtime and writable persistent local storage. Do not deploy this implementation to an Edge runtime or ephemeral read-only file system.

This package publishes to the public npm registry as @aurascope-analytics/forwarder-app-hook (ADR 0032), so a tenant consumes it as an ordinary dependency:

npm install @aurascope-analytics/forwarder-app-hook

The published tarball is dist/ only — compiled JavaScript plus type declarations, built by prepack on the way into every npm pack/npm publish. npm run build (tsc) produces the same output locally and npm run typecheck is the same compiler without emit; neither is a tenant step. The wordpress/ tree beside this package ships through a different channel entirely and is excluded from the tarball by the files whitelist.

The tenant-facing walkthrough is docs/public/install/forwarder-app-hook.md, and scripts/check-app-hook-install.sh executes the tenant's real sequence on every change to this forwarder — pack, install the tarball into a throwaway project, and import it by bare specifier on plain Node.

Configure the environment at runtime, then adapt the framework response:

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { createAuraScopeMiddleware } from "@aurascope-analytics/forwarder-app-hook";

export const config = { runtime: "nodejs" };

const auraScope = createAuraScopeMiddleware();

export function middleware(request: NextRequest) {
  auraScope(request);
  return NextResponse.next();
}

src/middleware.example.ts is the full documentation-as-code example. It is excluded from this package's TypeScript build because next is intentionally not a dependency — the tarball's dependency surface is a tenant-facing fact. It is still compiled: it is type-checked against the real next types inside the throwaway project scripts/check-app-hook-install.sh builds, where framework packages can be installed without entering the tarball. The created middleware exposes .forwarder when configuration and queue startup succeed, allowing application shutdown code to call await forwarder.stop().

A missing or invalid required setting — or a queue directory that cannot be created — makes the factory return a no-op middleware, and logs one line naming the reason before it does. Fail open, not fail silent: the refusal messages above are tenant-facing copy and this factory is the only path they can take, because both documented wirings call it with no argument. Pass { logger } as the second argument to send that line somewhere other than console.

Per-REQUEST capture and middleware errors are swallowed, with no log, so AuraScope never changes the tenant response and one malformed URL cannot flood their error output. The counters carry those instead.

SvelteKit installation

SvelteKit server hooks run during prerender as well as at request time. Guard creation with building so the forwarder does not start its queue directory or shipper during a build. A Fetch API Request already satisfies the middleware's accepted shape directly, so no adapter object is needed:

import { building } from "$app/environment";
import type { Handle } from "@sveltejs/kit";
import { createAuraScopeMiddleware } from "@aurascope-analytics/forwarder-app-hook";

const auraScope = building ? undefined : createAuraScopeMiddleware();

export const handle: Handle = async ({ event, resolve }) => {
  auraScope?.(event.request);
  return resolve(event);
};

src/hooks.server.example.ts is the documentation-as-code version of this wiring. Like the Next.js example, it is excluded from the package's TypeScript build because its framework imports are intentionally not dependencies of this package, and like it, the packaged-install gate type-checks it against the real @sveltejs/kit types.

Route matching is a measurement boundary

A framework route matcher (Next.js config.matcher, SvelteKit route-level early returns) narrows traffic one level above the forwarder: match every path, or accept that unmatched paths are unobserved and their crawler history is never recoverable. Raw-first storage only covers traffic the forwarder saw.

Retention and retries

Staging is cut into batches of no more than 500 lines. A formed batch's bytes and idempotency key are immutable and persisted together. A restart recovers pending files in ordinal order. 200 acknowledges and removes a batch, including duplicate rows; 401, 429, 503, other server failures, timeouts, and network errors retain it. The circuit breaker pauses, retains, and resumes, honoring Retry-After and otherwise using capped exponential backoff.

Each retained outcome moves exactly one counter, and the set covers the space: http401, http429, http503, networkErrors for a send that never got an answer, and httpOther for every remaining status the shipper retries — a 500 from ingest's own dependency failure, a 502 from a proxy in between, a 404 from a mistyped AURASCOPE_INGEST_URL — plus a 200 whose acknowledgement could not be parsed, which is retry(200) because an unreadable acknowledgement cannot release a durable batch. Before httpOther that whole class moved nothing at all, so a permanently wrong endpoint URL and a healthy quiet site produced identical metrics.

A 400 — or a batch this process finds locally malformed and refuses to send — retains the batch too, and since #378 also opens the breaker and increments badRequests. The refusal is permanent for those bytes, so the alternative shapes are both worse: dropping is the ack-and-move-on ADR 0018 forbids for a replayable sender, and the pre-#378 shape re-offered the same rejected batch on every flush tick forever with no counter moving. A wedged queue head is now a state (breakerState: "open") and a number rather than an inference from a flat accepted.

Nothing is dropped merely because the breaker is open. At configured capacity exhaustion only, fail-open wins: the queue drops the oldest data and increments the local dropped metric. Persisted batches are immutable, so the implementation evicts an entire oldest batch; staged events are evicted one oldest event at a time. This keeps the loss window contiguous and preserves the newest resume edge. No drop signal is added to the wire.

These behaviors implement ADR 0018, the durability acknowledgement rule in ADR 0004, and the frozen server-plane contract in ADR 0011.

Probe traffic is declared on the BATCH ENVELOPE, as the x-aurascope-probe-marker header, because the frozen app-hook dialect has no dedicated marker field and mutating ua would corrupt the evidence it carries. This is sender self-declaration, not write-time classification, and is an explicit freeze-task input under ADR 0020.

WordPress seam

The later WordPress host uses this exact ten-field contract, endpoint, immutable batch identity, durable replay, and circuit-breaker semantics. Its host-specific queue and off-response-path scheduling are outlined in wordpress/README.md.

Development

bunx tsc --noEmit
bun test

Runtime code uses only Node.js standard-library modules and the Node 22 global fetch. Development dependencies are limited to TypeScript and Node.js type declarations.