@nreactive/core
v0.2.0
Published
Automatic server-side error capture and reporting for nreactive
Maintainers
Readme
@nreactive/core
Automatic server-side error capture for Node.js — the companion to the nreactive AI error-fix pipeline that catches runtime errors in production and opens pull requests that fix them.
- Zero runtime dependencies (native
fetch,node:diagnostics_channel). - Auto-captures
uncaughtException,unhandledRejection, process warnings, and outbound HTTP failures by default. - Per-request context propagated via
AsyncLocalStorage. - Built for Node ≥ 20.
Install
npm install @nreactive/core
# or: pnpm add @nreactive/core
# or: yarn add @nreactive/core
# or: bun add @nreactive/coreUsing a framework? Install a sibling package instead — they depend on @nreactive/core and wire request context into your router:
@nreactive/express— Express 4/5 middleware.@nreactive/fastify— Fastify 4/5 plugin.
AI-assisted setup
Paste the prompt below into Claude Code, Cursor, Copilot, or any agent with file-write access. It has enough information to finish the integration end-to-end — no manual wiring required.
You are adding the
@nreactive/coreSDK to this Node.js project.nreactiveis a production error-monitoring service that opens AI-generated pull requests to fix the errors it catches.
- Detect the package manager from the lockfile (
package-lock.json→ npm,pnpm-lock.yaml→ pnpm,yarn.lock→ yarn,bun.lockb→ bun).- Install
@nreactive/corewith that package manager.- Create a file
src/observability/nreactive.ts(or.jsif the project is not TypeScript) that importsinitfrom@nreactive/coreand calls it with{ appId: process.env.NREACTIVE_APP_ID, environment: process.env.NODE_ENV, release: process.env.APP_VERSION }. Export nothing — the module is used for its import side effect.- In the server entry file (commonly
src/index.ts,src/server.ts, or themainfield ofpackage.json), addimport "./observability/nreactive";as the very first import, before any framework imports.- Add
NREACTIVE_APP_ID=to.env.example(create it if missing) and document the variable in the README's environment-variables section. The user will obtain their App ID from https://nreactive.com/dashboard/apps.- Do not invent additional configuration — the defaults capture
uncaughtException,unhandledRejection, process warnings, and outbound HTTP failures automatically. If the project looks like it uses Express or Fastify, stop and advise the user to install@nreactive/expressor@nreactive/fastifyinstead.
Quick start
import { init, captureException } from "@nreactive/core";
init({
appId: process.env.NREACTIVE_APP_ID!,
environment: process.env.NODE_ENV, // "production" | "development" | …
release: process.env.APP_VERSION, // e.g. "1.2.3"
});
// From this point, the following are captured automatically:
// - uncaughtException → severity "critical" + process.exit(1) after a 2s flush
// - unhandledRejection → severity "critical"
// - process warnings → severity "warn" (Deprecation/Experimental filtered)
// - outbound HTTP → 4xx "error", 5xx "critical", >3s "warn", net fail "critical"
// Manual capture is always available:
captureException(new Error("payment failed"), "critical", {
tags: { feature: "checkout" },
user: { id: "u_42" },
});Get your App ID from the nreactive dashboard.
Configuration reference
init({
// ── Required ───────────────────────────────────────────────────────────
appId: "…", // issued by nreactive
// ── Identity ───────────────────────────────────────────────────────────
endpoint: "https://nreactive.com", // default: nreactive.com
environment: "production", // default: process.env.NODE_ENV ?? "production"
release: "1.2.3", // app version — surfaced in metadata.browser
serverName: os.hostname(), // default: os.hostname()
sendInDevelopment: false, // default false → no-op when env !== "production"
// ── Auto-capture toggles ───────────────────────────────────────────────
captureUncaughtException: true, // default true; accepts object form below
captureUnhandledRejection: true, // default true
captureProcessWarning: true, // default true (filters Deprecation/Experimental)
captureHttpClient: true, // default true — fetch + http/https outbound
captureHttpServer: false, // default false — prefer framework adapters
captureEventLoopLag: false, // default false — mean>=200ms "warn", p99>=500ms "critical"
captureConsole: false, // default false — wraps console.error/.warn
// Object form of captureUncaughtException — all fields optional
captureUncaughtException: {
exit: true, // process.exit after flush (default true)
exitCode: 1,
flushTimeout: 2000, // ms; cap on how long flush blocks exit
mimicNative: true, // write raw stack to stderr like Node does
},
// ── Filtering & shaping ────────────────────────────────────────────────
beforeSend: (evt) => evt | null, // sync or async; return null to drop
ignoreErrors: [/AbortError/, "ECONNRESET", (e) => e instanceof MyOwnError],
denyUrls: [/^node:/, "/node_modules/"],
// ── Scrubbing ──────────────────────────────────────────────────────────
scrubHeaders: ["authorization", "cookie", "x-api-key"],
scrubQueryParams: ["token", "password", "secret", "api_key"],
// ── Tuning ─────────────────────────────────────────────────────────────
batchInterval: 5000, // ms — server may override via /validate
maxQueueSize: 100, // events buffered; drop-oldest when full
maxErrorsPerSession: 50, // per-process cap; server may override
// ── Transport ──────────────────────────────────────────────────────────
transportContextMode: "both", // "metadata" (≤300 char string) | "both" (adds structured JSON)
debug: false, // when true, log SDK internals to stderr
});Public API
import {
init, // set up the SDK (call once at process start)
captureException, // captureException(err, severity?, { tags?, extra?, user?, url?, fingerprint? })
captureMessage, // captureMessage(msg, severity?, opts?)
addContext, // addContext({ user, tags, extra, requestId, … }) — merges into active store
withContext, // withContext(ctx, fn) — isolated context for the duration of fn()
addBreadcrumb, // addBreadcrumb({ category, message, level, data })
getContext, // read the current context
flush, // await flush(timeoutMs?) — best-effort send
close, // await close(timeoutMs?) — flush + remove handlers
ready, // await ready() — resolve when init+validation complete
getClient, // low-level access to the Client singleton
} from "@nreactive/core";withContext vs addContext
withContext(ctx, fn) creates a new AsyncLocalStorage frame that is scoped to the lifetime of fn(). Use this to wrap an inbound request, a background job, or a queue message:
withContext({ requestId: req.id, user: { id: req.user?.id } }, () => {
// any captureException inside this closure (including async work) is tagged
});addContext({...}) merges into the currently active context frame. Use this to add fields as the request progresses (e.g. after looking up the user from the session).
Capture sources
| Source | Default | Severity mapping |
|---|---|---|
| process.on('uncaughtException') | ON | critical, then process.exit(1) after 2s flush |
| process.on('unhandledRejection') | ON | critical |
| process.on('warning') | ON | warn (DeprecationWarning + ExperimentalWarning filtered) |
| Outbound HTTP (fetch + http/https) | ON | 4xx error, 5xx critical, net fail critical, >3s warn |
| SIGTERM / SIGINT / beforeExit | ON | flush + re-raise signal so user handlers fire |
| Inbound HTTP (http.Server) | OFF | Use the framework adapters (@nreactive/express, @nreactive/fastify) for richer context. |
| console.error / console.warn | OFF | error / warn |
| Event-loop lag | OFF | warn (mean ≥ 200ms), critical (p99 ≥ 500ms) |
Severity auto-classification for manual captureException():
critical:TypeError,ReferenceError,RangeError,SyntaxError, and Node errors withcodeinENOENT,ECONNREFUSED,ETIMEDOUT,EPIPE,ERR_UNHANDLED_ERROR.error: default.warn: pass"warn"explicitly (used by console + event-loop integrations).
Shutdown guarantees
- All internal timers (
setIntervalflush,setTimeoutabort guards) are.unref()d — the SDK never holds the event loop open on its own. uncaughtException: the handler runscaptureException→flush(captureUncaughtException.flushTimeout)→process.exit(captureUncaughtException.exitCode). IfmimicNative: true(default) the raw stack is written to stderr first so logs look like a normal Node crash.SIGTERM/SIGINT: the handler flushes, unregisters itself, then re-raises the signal viaprocess.kill(pid, sig)so any user signal handlers you've installed still fire. The SDK does not callprocess.exiton signals — that's your application's job.close(timeout): marks the client disabled (rejects new events), tears down all integrations, flushes once, stops the timer. Idempotent.
Context transport
Every event includes:
metadata.browser— ≤300 char string packed withnode/vX.Y host=… pid=… env=… rel=… METHOD /path req=<requestId> user=<userId>. Never fingerprinted by the backend — safe to vary per-request.metadata.os—linux 6.5.0-x86_64or equivalent.metadata.timestamp— ISO timestamp.context(≤4000 chars, optional) — structured JSON withrequest,user,tags,extra,breadcrumbs. Sent whentransportContextMode: "both"(default). Forward-compatible: old backends ignore it.
The backend-side fingerprint is computed only over message + stack + source.file + source.line, so varying request context does not create duplicate issues.
Self-loop protection
Every outbound request from the SDK includes an x-nreactive-self: 1 header. The HTTP-client integration skips any request carrying that header (and any request whose origin matches the configured endpoint) so reporting errors never triggers more errors.
Privacy & compliance
- Headers matching
scrubHeaders(default:authorization,cookie,set-cookie,x-api-key,x-auth-token,proxy-authorization) are replaced with[REDACTED]before transport. - Query parameters matching
scrubQueryParams(default:token,password,secret,api_key,apikey,access_token,refresh_token) are redacted inmetadata.browserand insource.url. - Body scrubbing is applied to breadcrumb
dataandCaptureOptions.extrapayloads (bounded depth of 8). - When
environment !== "production"the SDK sendsOrigin: http://localhostso the backend's localhost short-circuit still activates for misconfigured local/dev workloads — no events are persisted. sendInDevelopment: false(the default) disables the network path entirely whenenvironment !== "production". Flip totrueonly if you intentionally want staging/dev errors on your dashboard.
Links
- Full documentation: https://nreactive.com/docs
- Express adapter:
@nreactive/express - Fastify adapter:
@nreactive/fastify - Dashboard: https://nreactive.com/dashboard
- Pricing: https://nreactive.com/pricing
License
PROPRIETARY. See LICENSE.
