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

@camada/node

v0.3.1

Published

camada backend SDK for Node: request capture, inline blocking, first-party beacon serving; Express/Fastify/Nest/Koa adapters

Downloads

435

Readme

@camada/node

camada's backend SDK for Node: captures request metadata on response-finish (real status, latency, and the true wire header order no proxy position can see), enforces the tenant blocklist inline before your app runs, and serves the fingerprint beacon first-party at /_cam/b.js + /_cam/fp. Fails open by design: a camada outage or bug never 5xxes your app.

npm install @camada/node

Requires Node 20 or later.

Quickstart

Env (printed by camada onboarding / npm run seed in dev):

CAMADA_KEY=<ingest_token>.<snap_token>
CAMADA_INGEST_URL=http://localhost:8787        # dev only; defaults to production ingest

Express / Connect

import camada from '@camada/node';
app.use(camada.express());

Fastify

import { camadaFastify } from '@camada/node/fastify';
await app.register(camadaFastify);

NestJS (default Express platform; on the Fastify platform register the fastify plugin instead)

import { CamadaModule } from '@camada/node/nest';
@Module({ imports: [CamadaModule.forRoot()] })   // Nest ≤10: CamadaModule.forRoot({ routes: '*' })

Koa

import { camadaKoa } from '@camada/node/koa';
app.use(camadaKoa());

What it does per request

  1. Refreshes the blocklist snapshot off-path (30 s poll, ETag; cold start fails open). Every poll and event batch carries x-camada-sdk: @camada/node/<version>.
  2. Resolves the client IP per your tenant's trusted-proxy config — raw X-Forwarded-For is never trusted without it (CAMADA_TRUSTED_PROXY=hops:1|cidrs:…|vercel overrides locally).
  3. Runs your ordered custom rules (see below), then the allow, block and challenge lists. Blocked → 403 with x-block-reason before your app; the event still ships, with st: 403 and blk: <reason> (ip4|ip6|path|rule) so the analyst counts SDK blocks apart from your app's own 403s.
  4. Serves /_cam/b.js (the beacon, first-party — no third-party domain for ad-blockers or CSP to break) and relays /_cam/fp posts to ingest with the resolved client IP.
  5. Otherwise: sets x-rid + the _sfp session cookie (kept when the app later replaces Set-Cookie through setHeader/writeHead), and on response-finish ships one batched, redacted event (Authorization/Cookie values never leave the process; credential-looking query values are scrubbed; see @camada/core). A client that disconnects mid-response still ships its one event, with the status set so far and dur up to the disconnect.

Custom rules

Your Rules page holds one ordered list per project, and this SDK walks it before the allow, block and challenge lists. First match wins — the order is the precedence — and each rule carries one of four actions:

| action | what this SDK does | on the event | |---|---|---| | skip | passes the request | nothing | | block | 403 before your app | blk: "rule", rl: "<rule id>" | | challenge | serves the proof-of-work page (challenge: false opts out) | blk: "challenge" | | warn | passes the request and marks it for the analyst | wrn: "<rule id>" |

A skip rule also carries a record matches flag, which only the analyst reads: a recorded skip is still scored and shows on your dashboard as Allowed, an unrecorded one is dropped before scoring. Either way the request passes here, unstamped — the built-in Allow-list is a skip rule with recording on.

A rule block also names the row that decided, so the response says which rule to edit:

HTTP/1.1 403 Forbidden
x-block-reason: rule
x-block-rule: cr_4f2a9c1b7e03

A rule may also test one request header (is, contains or matches), and the header name is matched case-insensitively against what the client actually sent. Headers belong to the request plane alone: the analyst never sees them, so it treats a header rule as not matching and only a v5 SDK like this one enforces it.

The rules ride the v5 snapshot, which this SDK asks for by default. snapshotVersion: 4 pins the allow/challenge lists without the rules, 3 the block list alone; a project that has not published the container you ask for is answered with the next one down, so asking for the newest is always safe.

HTML templates add the beacon with the helper (or use @camada/react's <CamadaBeacon/>):

res.send(`<head>${camada.scriptTag(req)}</head>…`);

App-context outcomes (the signals no edge tap can see; identifiers are HMAC-hashed in-process):

camada.track(req, 'login_failed', { user: email });

The event name is free-form, but the analyst's app-context rules read a fixed vocabulary — use these names and the credential-stuffing, password-spray, account-aggregation, signup-velocity, carding and coupon rules fire on your app's own truth instead of path heuristics:

| event | when | |---|---| | login_failed / login_succeeded | a password (or passwordless) login attempt settled; pass { user } so attempts per account can be counted | | signup | an account was created | | password_reset | a reset was requested | | mfa_failed | a second factor was rejected | | payment_failed / payment_succeeded | a payment authorisation settled | | coupon_failed | a promo/voucher code was rejected |

WebSockets

Node hands an upgrade request (a WebSocket handshake) to the server's 'upgrade' listeners, never to the request handler the middleware runs in, so the middleware alone never sees one. Pass the server to attach() and each upgrade ships one event with st: 101:

const server = app.listen(3000);            // Fastify: app.server · Nest: app.getHttpServer() · Koa: app.listen()
camada.attach(server);                      // returns the server; calling it twice is harmless
new WebSocketServer({ server });            // ws, socket.io, … attach before or after, either way

It only observes. The handshake stays your WebSocket library's: camada writes nothing to the socket, adds no delay, sets no session cookie (the event carries the visitor's existing _sfp), and never blocks or challenges an upgrade. A server with no 'upgrade' listener of its own behaves exactly as before. The event is recorded when Node hands the upgrade over, so a handshake your library then rejects still ships st: 101.

Operational notes

  • CAMADA_DISABLED=1 — kill switch, checked per request.
  • CAMADA_SERVERLESS=1 — lazy snapshot refresh (no interval timer); cold invocations fail open and catch up asynchronously.
  • Enforcement scope at this position: IP, path, user-agent and request-header conditions. ASN, country and TLS-fingerprint conditions can't be evaluated in-app and fail open (a rule that needs one never matches here; run @camada/hono on Cloudflare Workers for those).
  • Memory: ~5 MB per loaded snapshot.

Develop

npm install && npm run build && npm test && npm run check

Sibling checkouts of camada-core and camada-browser must exist (file: deps).