vela-cairn
v0.2.0
Published
Producer-side fetch instrumentation for Cairn — fingerprints outbound HTTP calls and reports schema drift.
Maintainers
Readme
vela-cairn
Producer-side fetch instrumentation for Cairn.
vela-cairn wraps your runtime's fetch and fingerprints every outbound HTTP call, posting the request/response schema (field names, types, structure — never raw values) to Cairn's ingest endpoint. Cairn diffs each new fingerprint against the recorded baseline and surfaces contract drift the moment an upstream changes shape.
Install
pnpm add vela-cairn
# or
npm install vela-cairnQuick start
Next.js (App Router)
Register the wrapper from instrumentation.ts at the project root:
// instrumentation.ts
import { register as registerCairn } from "vela-cairn";
export function register() {
if (process.env.NEXT_RUNTIME === "edge") return;
registerCairn({
apiKey: process.env.CAIRN_API_KEY!,
repoSlug: "your-org/your-repo",
});
}That's it. Every server-side fetch() from your route handlers is now intercepted, fingerprinted, and reported. No per-route wrapping is required — vela-cairn automatically extends the serverless function lifetime via Next.js's after() so the ingest POST completes even after your handler has returned.
Optional: explicit withCairnCapture
You can still wrap individual route handlers if you want:
// app/api/charge/route.ts
import { withCairnCapture } from "vela-cairn/next";
export const POST = withCairnCapture(async (request: Request) => {
const upstream = await fetch("https://payments.example.com/charges", {
method: "POST",
body: JSON.stringify({ amount: 4999 }),
});
return Response.json(await upstream.json());
});Wrapping is only needed for two things now:
- Debug response headers (
X-Cairn-Debug-Header-Seen,X-Cairn-Debug-Wrapper-Active) — useful when you're integrating and want to see in DevTools whether capture fired. - Non-Next.js frameworks (Express, Hono, Fastify) — there's no
next/headersfallback, so you wrap withrunWithCaptureContext({ captureForTest: true }, handler)fromvela-cairnto set the ALS marker manually.
For plain App Router routes on Vercel, register() alone is sufficient.
Non-Next.js backends (Express / Hono / Fastify)
A raw React/Vite SPA has no server — install vela-cairn on the API backend the SPA calls. There's no next/headers auto-detection there, so tag requests in middleware using isRequestTaggedForCapture (reads both the X-Cairn-Capture header and the CORS-safe __cairn_capture cookie) and wrap the handler with runWithCaptureContext:
import {
register,
runWithCaptureContext,
isRequestTaggedForCapture,
} from "vela-cairn";
register({
apiKey: process.env.CAIRN_API_KEY!,
repoSlug: "your-org/your-repo",
});
// Express
app.use((req, res, next) => {
const captureForTest = isRequestTaggedForCapture(
req.headers["x-cairn-capture"],
req.headers.cookie,
);
runWithCaptureContext({ captureForTest }, () => next());
});Hono is the same shape — read c.req.header("x-cairn-capture") / c.req.header("cookie") and wrap await next(). The cookie path means the test runner can tag cross-origin calls without tripping CORS.
Generic Node.js
import { register } from "vela-cairn";
register({
apiKey: process.env.CAIRN_API_KEY!,
repoSlug: "your-org/your-repo",
captureMode: "production",
});Capture modes
By default vela-cairn runs in test-only mode — it only fingerprints fetches that are part of a Cairn-tagged test run (the runner adds an X-Cairn-Capture: 1 header to every browser request). Real production traffic is left alone.
Switch to production mode if you want every outbound fetch fingerprinted, regardless of where the inbound request came from:
register({
apiKey: process.env.CAIRN_API_KEY!,
repoSlug: "your-org/your-repo",
captureMode: "production",
});Configuration
| Option | Type | Default | Description |
| ---------------- | ----------------------------- | ------------- | -------------------------------------------------------------------------------------- |
| apiKey | string | required | Bearer token issued from Cairn's Instrument tab. |
| repoSlug | string | required | Repo identifier (owner/name) the captures should be attributed to. |
| rootPath | string | "" | Monorepo app root (e.g. apps/web). Captures are scoped per (repoSlug, rootPath). |
| ingestUrl | string | Cairn hosted | Override for self-hosted Cairn or local stub testing. |
| captureMode | "test-only" \| "production" | "test-only" | Whether to fingerprint everything or only test-tagged calls. |
| redactHeaders | string[] | [] | Extra headers to redact in addition to the defaults (authorization, cookie, etc.). |
| redactBodyKeys | string[] | [] | Extra body keys to redact in addition to the defaults (password, token, etc.). |
| sampleRate | number | 1.0 | Fraction (0–1) of fetches to consider for fingerprinting. |
| debug | boolean | false | Verbose logging to stdout. |
What gets sent
Only the shape of requests and responses — never values. Every captured payload runs through:
- A redaction pass that replaces sensitive header values and body keys with
"[REDACTED]". - Schema extraction that keeps field names, types, and required-flags but discards primitive values.
- A noise filter that drops platform-injected headers (
x-vercel-id,x-cf-ray, etc.) so they don't read as drift.
The wire format is small, structural, and safe to send across the public internet.
License
Proprietary — see LICENSE. Internal use by Vela Engineering and authorized affiliates only.
