Skip to content
bucker

@bucker/node

Server-side capture: crash handlers, on-disk source context, and per-request scopes via AsyncLocalStorage.

Distribution note. The package is build-ready, not yet published. It carries a publishConfig (pointing at dist/, split per subpath) alongside a source-pointing top-level main/types/exports — the split matters because several other workspace packages import the SDKs by source with no build step, while a published consumer cannot compile ./src and needs the compiled dist/. pnpm build / prepack run scripts/build-package.mjs, which refuses to finish if the built output does not match what publishConfig.exports promises. Inside the monorepo it resolves through workspace:*. What remains is an actual npm publish, a human decision covered in sdks/PUBLISHING.md — not code readiness.

Init

Put this in its own module and import it first in your entry file, before any other import. Crash handlers installed after the rest of the app has evaluated do not cover the rest of the app evaluating.

// bucker.server.ts
import { init } from '@bucker/node'

export const bucker = init({
  dsn: process.env.BUCKER_DSN ?? '',
  environment: process.env.NODE_ENV ?? 'development',
})
// index.ts
import './bucker.server.js' // must stay first
import express from 'express'

create-bucker writes exactly this pair and injects the import for you.

Options

All of ClientOptions (see sdk-browser.md) plus:

Option Default Meaning
linesOfContext SDK default Lines of on-disk source context per in-app frame. 0 disables the integration
maxBufferBytes SDK default Transport queue ceiling
fetchImpl global fetch Testing seam

serverName defaults to process.env.BUCKER_SERVER_NAME.

Default integrations

globalHandlersIntegration() and, unless linesOfContext: 0, contextLinesIntegration().

Crash handlers. uncaughtException is the one hook where an SDK can genuinely make things worse: install a listener and do nothing else, and you have converted "crash loudly" into "limp along in an undefined state". So the default is capture → flush with a bounded timeout (flushTimeoutMs, default 2000) → let the process die exactly as it would have. exitOnUncaught defaults to true for the same reason. unhandledRejection is captured too.

Source context. contextLinesIntegration reads ±N lines around each in-app frame straight off disk at capture time, so a server stack arrives already readable without any source-map step. SourceReader and addSourceContext are exported if you need them directly.

Express

import { errorHandler, requestHandler } from '@bucker/node'
import { bucker } from './bucker.server.js'

app.use(requestHandler(bucker))   // before your routes
// … routes …
app.use(errorHandler(bucker))     // after them

requestHandler forks a scope per request, attaches request context (method, URL, headers, query string) and a transaction name, then runs the rest of the chain inside it. errorHandler captures with mechanism: { type: 'middleware', handled: false } and passes the error onward untouched — it never swallows.

Client IPs are attached only when includeIp is set on the handler options.

Fastify

import { fastifyHooks } from '@bucker/node'
import { bucker } from './bucker.server.js'

const hooks = fastifyHooks(bucker)
app.addHook('onRequest', hooks.onRequest)
app.addHook('onError', hooks.onError)

Fastify runs the remaining lifecycle inside onRequest's done() continuation, so the AsyncLocalStorage context propagates the same way it does through Connect's next().

Next.js

Next.js runs on both sides, so it needs both SDKs. Use instrumentation.ts beside your routes directory:

export async function register() {
  if (process.env.NEXT_RUNTIME !== 'nodejs') return
  const { init } = await import('@bucker/node')
  init({ dsn: process.env.BUCKER_DSN ?? '', environment: process.env.NODE_ENV })
}

export async function onRequestError(error: unknown) {
  const { captureException } = await import('@bucker/node')
  captureException(error, { mechanism: { type: 'nextjs.onRequestError', handled: false } })
}

The dynamic import matters: an Edge runtime must not pull in the Node SDK at all. Browser-side init goes in instrumentation-client.ts (Next ≥ 15.3) or, on older versions, one import from the root layout. On Next < 15 you also need experimental: { instrumentationHook: true }.

Capturing by hand

Same surface as the browser SDK: captureException, captureMessage, addBreadcrumb, setUser, setTag, setExtra, setContext, flush, close, getCurrentScope. getRequestScope() additionally returns the scope for the request currently in flight.

Not built yet

Opt-in local-variable capture is deliberately absent. The seam exists — StackFrame.vars in the shared schema plus the event-processor hook that contextLinesIntegration already uses — but shipping variable capture before the entropy- and format-aware masking (JWT / AWS key / Luhn / email) and the redaction manifest is how an SDK exfiltrates a customer's secrets. It is staged after masking, not before.

Also absent: OTel trace correlation beyond passing through traceId/spanId, cron monitoring, and profiling.