interface BugCatchOptions { /** DSN from the BugCatch dashboard. Format: http://host/ingest/{projectId}?key={sdkKey} */ dsn: string; /** Application release version, e.g. "1.2.3". Attached to every event. */ release?: string; /** Deployment environment, e.g. "production" | "staging". Attached to every event. */ environment?: string; /** Print debug info to the console. Default: false. */ debug?: boolean; /** Max breadcrumbs to keep in memory. Default: 100. */ maxBreadcrumbs?: number; /** * Automatically attach global error handlers and capture breadcrumbs. * Default: true. Set to false to manage captures manually. */ autoCaptureErrors?: boolean; /** * Automatically capture breadcrumbs from clicks, navigation, and console. * Default: true. */ autoCaptureBreadcrumbs?: boolean; /** * Automatically intercept fetch() calls and report API timing metrics * to BugCatch. Default: false. */ autoTrackRequests?: boolean; /** * URL patterns to exclude from request tracking. The BugCatch ingest URL * is always excluded automatically. */ trackIgnoreUrls?: Array; /** * A list of URL patterns (strings or RegExps). Any error originating from * a script URL that matches will be ignored. */ ignoreUrls?: Array; /** * A list of error message patterns (strings or RegExps). Matching errors * will be dropped before being sent. */ ignoreErrors?: Array; /** Called before an event is sent. Return false to drop the event. */ beforeSend?: (event: EventPayload) => EventPayload | false; /** * Automatically capture console.log/info/warn/error/debug/trace calls as * structured logs (in addition to the existing warn/error breadcrumbs). * Default: false. */ autoCaptureLogs?: boolean; /** How often to flush the log batch, in ms. Default: 2000. */ logsFlushInterval?: number; /** Flush immediately once this many logs are queued. Default: 20 (server caps a batch at 100). */ logsBatchSize?: number; /** How often to flush finished transactions, in ms. Default: 2000. */ tracesFlushInterval?: number; /** Flush immediately once this many finished transactions are queued. Default: 10 (server caps a batch at 20). */ tracesBatchSize?: number; /** * Automatically track one release-health session per SDK lifetime - starts * on `init()`, escalates to "errored"/"crashed" as `captureException()` is * called, ends on `destroy()`/tab close/process exit. Powers the * crash-free sessions/users chart. Default: true. */ autoSessionTracking?: boolean; /** How often to flush queued session updates, in ms. Default: 5000. */ sessionsFlushInterval?: number; /** * Automatically capture Core Web Vitals (LCP, INP, CLS, FCP, TTFB) via the * `web-vitals` library and report them for RUM analytics. Browser-only - * a no-op in Node. Default: true. */ autoCaptureWebVitals?: boolean; /** How often to flush queued Web Vitals readings, in ms. Default: 5000. */ webVitalsFlushInterval?: number; /** * Record the session's DOM via `rrweb` for session replay playback in the * dashboard. Browser-only, privacy-sensitive - unlike Web Vitals/sessions, * this is **off by default**; a project must opt in deliberately. When on, * every `captureException()` call while a recording is active is * automatically tagged with the recording's id so the dashboard can jump * straight from an error to "what led to this." */ sessionReplay?: boolean; /** How often to flush queued replay chunks, in ms. Default: 10000. */ replayFlushInterval?: number; /** * Abort a replay chunk upload if it hasn't finished after this many ms. * Default: 15000. Prevents one slow/stuck request (the largest payloads * this SDK sends) from holding a browser connection slot open forever and * cascading into every other request to the same API stalling behind it. */ replayRequestTimeout?: number; /** * CSS selector for elements whose *rendered text* should be masked in * session replay recordings, e.g. `"[data-sensitive], .salary-cell"`. * `maskAllInputs` (always on) only covers form inputs - a table of * salaries, an SSN displayed as plain text, etc. render as regular DOM * text and are recorded as-is unless matched by this selector. Passed * straight through to rrweb's own `maskTextSelector` option, so its * selector syntax and matching rules apply here too. */ replayMaskTextSelector?: string; } interface UserContext { id?: string; email?: string; username?: string; /** Prioritized over the IP the ingest API detects from the request itself - see IngestService/EventProcessor. */ ip_address?: string; } interface BreadcrumbEntry { timestamp: string; type?: string; category?: string; message?: string; data?: Record; } interface StackFrame { filename?: string; lineno?: number; colno?: number; function?: string; context_line?: string; in_app?: boolean; } interface ExceptionValue { type?: string; value?: string; stacktrace?: { frames: StackFrame[]; }; } interface EventPayload { event_id: string; timestamp: string; level?: string; message?: string; platform: string; release?: string; environment?: string; user?: UserContext; request?: { url?: string; method?: string; headers?: Record; }; exception?: { values: ExceptionValue[]; }; breadcrumbs?: BreadcrumbEntry[]; tags?: Record; extra?: Record; } type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; interface LogPayload { level: LogLevel; message: string; timestamp?: string; /** Links this line to a trace/request. See `BugCatch.setTraceId`. */ trace_id?: string; attributes?: Record; environment?: string; release?: string; } interface SpanPayload { span_id: string; parent_span_id?: string; op: string; description?: string; start_timestamp: string; duration_ms: number; status?: string; tags?: Record; } interface TransactionPayload { trace_id: string; name: string; op: string; start_timestamp: string; duration_ms: number; status?: string; status_code?: number; environment?: string; release?: string; user_id?: string; tags?: Record; spans?: SpanPayload[]; } type SessionStatus = 'ok' | 'errored' | 'crashed'; interface SessionPayload { session_id: string; started_at: string; ended_at?: string; status?: SessionStatus; /** Handled errors observed so far this session. */ errors?: number; duration_ms?: number; environment?: string; release?: string; user_id?: string; } type WebVitalMetric = 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB'; interface WebVitalPayload { metric: WebVitalMetric; /** Milliseconds, except CLS which is a unitless layout-shift score. */ value: number; route: string; timestamp: string; environment?: string; release?: string; user_id?: string; } interface ReplayChunkPayload { replay_id: string; sequence: number; events: unknown[]; started_at?: string; ended_at?: string; url?: string; environment?: string; release?: string; user_id?: string; } interface ParsedDsn { /** Full URL including path and query, e.g. http://host/ingest/projectId?key=xyz */ ingestUrl: string; projectId: string; sdkKey: string; } /** One timed unit of work inside a transaction (a DB query, an outbound call). */ declare class BugCatchSpan { private readonly transaction; readonly parentSpanId: string | undefined; readonly op: string; description?: string | undefined; tags?: Record | undefined; readonly spanId: string; readonly startedAt: number; durationMs: number | undefined; status: string; constructor(transaction: BugCatchTransaction, parentSpanId: string | undefined, op: string, description?: string | undefined, tags?: Record | undefined); /** Start a span nested inside this one. */ startChild(op: string, description?: string, tags?: Record): BugCatchSpan; setStatus(status: string): this; finish(status?: string): void; /** @internal */ toPayload(): SpanPayload; } /** * The root of one traced operation - an HTTP request, a background job. * Created via `BugCatch.startTransaction()`, not directly. */ declare class BugCatchTransaction { readonly name: string; readonly op: string; private readonly onFinish; readonly traceId: string; readonly startedAt: number; durationMs: number | undefined; status: string; statusCode: number | undefined; tags: Record | undefined; private readonly spans; constructor(name: string, op: string, onFinish: (txn: BugCatchTransaction) => void); /** Start a span as a direct child of the transaction. */ startChild(op: string, description?: string, tags?: Record): BugCatchSpan; setStatus(status: string): this; /** Convenience: maps >=400 (client errors included, not just 5xx server errors) to 'error', everything else to 'ok'. */ setHttpStatus(statusCode: number): this; setTag(key: string, value: string): this; finish(status?: string): void; /** @internal */ startChildSpan(parentSpanId: string | undefined, op: string, description?: string, tags?: Record): BugCatchSpan; /** @internal */ toPayload(userId?: string, environment?: string, release?: string): TransactionPayload; } declare class BugCatchClient { private readonly opts; private readonly dsn; private readonly crumbs; private user; private tags; private currentTraceId; private currentTransaction; private readonly logQueue; private logFlushTimer; private readonly transactionQueue; private transactionFlushTimer; private readonly sessionQueue; private sessionFlushTimer; private sessionId; private sessionStartedAt; private sessionStatus; private sessionErrorCount; private readonly webVitalsQueue; private webVitalsFlushTimer; private readonly replayQueue; private replayFlushTimer; private replayId; private replaySequence; private replayStartedAt; /** True while an `isReplayAllowed()` check for a pending start is in flight. */ private replayStarting; /** Set if `stopReplay()` runs while a start's allowlist check is still in flight, so the check's callback aborts instead of starting a recording that was already told to stop. */ private replayStopRequested; /** Guards against concurrent replay POSTs - a busy page can refill 200 events * faster than one large chunk round-trips, and unbounded parallel sends * can exhaust the browser's per-origin connection limit and stall every * other request the page makes. */ private replaySendInFlight; private replayFlushPending; private stopRecording; private readonly cleanups; constructor(options: BugCatchOptions); captureException(error: unknown, extra?: Record): string; captureMessage(message: string, level?: string, extra?: Record): string; /** * Queue a structured log line. Batched and flushed automatically - does * not send a request per call. `traceId` overrides the trace set via * `setTraceId()` for this one line. */ captureLog(level: LogLevel, message: string, attributes?: Record, traceId?: string): void; /** * Set the trace id attached to every `captureLog()` call that doesn't pass * its own. Global mutable state, same as `setUser` - fine for a browser * session, but a Node server handling concurrent requests should pass * `traceId` explicitly to `captureLog()` instead of relying on this. */ setTraceId(traceId: string): void; clearTraceId(): void; /** Send any queued log lines immediately instead of waiting for the next flush. */ flushLogsNow(): Promise; /** Send any queued session updates immediately instead of waiting for the next flush. */ flushSessionsNow(): Promise; /** Send any queued Web Vitals readings immediately instead of waiting for the next flush. */ flushWebVitalsNow(): Promise; /** * Start recording session replay if it isn't already running. Only needed * when `sessionReplay: true` wasn't set at `init()` time - e.g. starting a * recording only after a user opts in, or only for a sampled subset of * sessions decided by the calling app. No-op outside the browser. */ startReplayNow(): void; /** Stop the active recording (if any) and flush whatever's queued. */ stopReplayNow(): void; /** Send any queued replay chunks immediately instead of waiting for the next flush. */ flushReplayNow(): Promise; /** The active replay recording's id, if session replay is currently running. */ getReplayId(): string | undefined; /** * Start a new traced transaction. Queued for delivery when `.finish()` is * called on it - starting one and never finishing it leaks nothing (no * request is made), it's just never reported. * * @example * const txn = BugCatch.startTransaction('GET /api/orders/:id', 'http.server'); * const span = txn.startChild('db.query', 'SELECT * FROM orders WHERE id = ?'); * // ... * span.finish(); * txn.setHttpStatus(200); * txn.finish(); */ startTransaction(name: string, op?: string): BugCatchTransaction; /** * Set the transaction `startTransaction()`'s auto-instrumentation (the * fetch/XHR interceptors under `autoTrackRequests`) attaches child spans * to. Global mutable state, same limitation as `setTraceId` - a Node * server handling concurrent requests should keep its own reference to * the transaction (e.g. on `req`) and call `.startChild()` on it directly * instead of relying on this. */ setCurrentTransaction(transaction: BugCatchTransaction | undefined): void; getCurrentTransaction(): BugCatchTransaction | undefined; /** * Queue a finished transaction for delivery. Called automatically by * `transaction.finish()` - only call this directly if you built the * transaction payload yourself instead of using `startTransaction()`. */ captureTransaction(transaction: BugCatchTransaction): void; /** Send any queued transactions immediately instead of waiting for the next flush. */ flushTransactionsNow(): Promise; /** * Express/Connect-style middleware: starts an `http.server` transaction * per request, finishes it when the response ends. Attaches the * transaction to `req.bugcatchTransaction` for concurrency-safe access, * and also sets it as the "current" transaction (see `setCurrentTransaction`) * as a convenience for single-request-at-a-time contexts. * * By default, requests that end in a 404 are dropped rather than * captured - a "route not found" is rarely a real business transaction * (bots/scanners probing random paths are a common source), and letting * them through skews throughput/error-rate stats and clutters the * transaction list with noise nobody asked to track. Pass * `ignoreStatusCodes: []` to capture everything, or a different list to * customize it. * * `ignoreRoutes` is the explicit version of the same idea - name specific * paths (health checks, readiness probes, ...) that were never a "real" * request to begin with, so they're skipped before a transaction is even * started (no wasted work, and any db.query/etc spans during the request * just have no parent transaction to attach to, same as when tracing * isn't active at all). Matched against the same normalized route * (`/users/:id`, not the raw URL) used for the transaction name - a * string matches by substring, a RegExp by `.test()`, same as * `ignoreUrls`/`ignoreErrors`. * * @example * app.use(BugCatch.tracingMiddleware()); * app.use(BugCatch.tracingMiddleware({ ignoreStatusCodes: [404, 401] })); * app.use(BugCatch.tracingMiddleware({ ignoreRoutes: ['/health', /^\/internal\//] })); */ tracingMiddleware(options?: { ignoreStatusCodes?: number[]; ignoreRoutes?: Array; }): (req: { method: string; originalUrl?: string; url?: string; bugcatchTransaction?: BugCatchTransaction; }, res: { statusCode: number; on: (event: "finish", cb: () => void) => void; }, next: () => void) => void; /** * Patch TypeORM's `QueryRunner.query()` (the single choke point every * repository/query-builder call eventually goes through) so each query * becomes a `db.query` child span on `currentTransaction` - same global * mutable state `tracingMiddleware()` sets, same caveat as `setTraceId` * for concurrent servers. * * TypeORM's `QueryRunner` export is a TypeScript **interface**, not a * class - there is no `.prototype` to patch on it directly. The concrete * class is driver-specific (`MysqlQueryRunner`, `PostgresQueryRunner`, * ...) and isn't exported. So this takes the `DataSource` instead, spins * up one query runner to discover its (shared, per-driver) prototype via * `Object.getPrototypeOf`, patches `.query` there - which affects every * runner of that driver, past or future, since they all share the same * class - and releases the probe runner. Duck-typed against * `{ createQueryRunner }` so this package never needs `typeorm` as a * dependency. * * @example * BugCatch.instrumentTypeOrm(dataSource); // the TypeORM DataSource */ instrumentTypeOrm(dataSource: { createQueryRunner: () => { query(...args: unknown[]): Promise; release?: () => unknown; }; }): () => void; /** * Patch ioredis's `sendCommand()` (every command - `get`, `set`, `hset`, a * pipeline's individual calls - funnels through it) so each Redis command * becomes a `cache.command` child span on `currentTransaction`. Same * global-mutable-state caveat as `instrumentTypeOrm`/`tracingMiddleware`. * Duck-typed against `{ prototype: { sendCommand } }` so this package * never needs `ioredis` as a dependency. * * @example * import Redis from 'ioredis'; * BugCatch.instrumentIoredis(Redis); */ instrumentIoredis(RedisClass: { prototype: { sendCommand(...args: unknown[]): unknown; }; }): () => void; setUser(user: UserContext): void; clearUser(): void; setTag(key: string, value: string): void; addBreadcrumb(crumb: BreadcrumbEntry): void; /** Tear down all global listeners. Call when unmounting in SPAs. */ destroy(): void; /** * Manually report an API call timing. Use this when `autoTrackRequests` * is off or when you want to track server-side (Node.js) requests explicitly. */ trackRequest(method: string, route: string, durationMs: number, statusCode: number): void; private buildExceptionPayload; private buildMessagePayload; private buildBase; private installErrorHandlers; private shouldIgnore; private installFetchInterceptor; private installXhrInterceptor; private sendMetric; /** * Patches console.trace/debug/log/info/warn/error to also queue a * structured log. Composes with `installConsoleBreadcrumbs` (warn/error) - * each wrap captures whatever the console method currently is at install * time and calls it at the end, so both run. */ private installConsoleLogCapture; /** Drains the queue, one request per 100 lines (the server's batch cap). */ private flushLogs; private sendLogBatch; /** Drains the queue, one request per 20 transactions (the server's batch cap). */ private flushTransactions; private sendTransactionBatch; private startSession; /** * Called from `captureException()`. Escalates the session's status - never * ends it, since a handled error doesn't mean the app stopped running. * `fatal` (from `installErrorHandlers()`'s uncaught/unhandled paths) marks * the session "crashed" and flushes immediately rather than waiting for * the timer, since the process may be about to exit. */ private recordSessionError; private endSession; private queueSessionUpdate; /** Drains the queue, one request per 100 updates (the server's batch cap). */ private flushSessions; private sendSessionBatch; /** * Dynamically imported so Node consumers (same bundle, no browser APIs) * never pay for parsing a browser-only library they can't use - this * method itself is only ever called when `typeof window !== 'undefined'`. */ private installWebVitalsCapture; private queueWebVital; /** Drains the queue, one request per 100 readings (the server's batch cap). */ private flushWebVitals; private sendWebVitalBatch; /** * Checks the project's session-replay privacy allowlist for a user (see * `ProjectEntity.replayUserAllowlist` on the API). Fails *closed* - a * network error or non-200 response means "don't record", since the whole * point is that a disallowed user's DOM never gets captured, and a * transient check failure shouldn't be the thing that decides otherwise. * Exposed publicly so a caller can gate its own `startReplayNow()` call * too, not just rely on the automatic check inside `startReplay()`. */ isReplayAllowed(userId?: string): Promise; /** * Dynamically imported, same reasoning as `installWebVitalsCapture()` - * `rrweb` assumes browser globals and this package ships one bundle for * both browser and Node, so a static import would force every Node * consumer to pay for parsing a library they can never use. * * Gated behind `isReplayAllowed()` - recording only actually starts once * the allowlist check resolves, so a disallowed user's DOM is never * captured to begin with (not just dropped after ingest). */ private startReplay; private stopReplay; private queueReplayEvent; /** Drains the queue into one chunk, tagged with whatever's pending (e.g. an `ended_at` from `stopReplay()`). */ private flushReplay; /** Queues an out-of-band chunk (e.g. the closing `ended_at` marker) through the same flush path. */ private queueReplayChunk; private sendReplayChunk; private send; private log; } /** * Collects Node.js process metrics (memory, CPU, event loop lag) and reports * them periodically to the BugCatch server-metrics ingest endpoint. * * Framework-agnostic - wrap it in any lifecycle hooks (NestJS, Express, etc.) * * @example NestJS * ```ts * \@Injectable() * export class BugCatchMetricsService implements OnModuleInit, OnModuleDestroy { * private readonly reporter = new BugCatchServerReporter({ * dsn: process.env.BUGCATCH_DSN!, * }); * onModuleInit() { this.reporter.start(); } * onModuleDestroy() { this.reporter.stop(); } * } * ``` */ interface ServerReporterOptions { /** DSN copied from the BugCatch dashboard (same as used for error capture). */ dsn: string; /** * How often to send a snapshot in milliseconds. * @default 30_000 */ reportInterval?: number; /** * Identifier for the reporting server/instance, shown in the dashboard and * alert emails so you can tell which process a snapshot came from. * @default os.hostname() */ instanceId?: string; /** Print debug logs. @default false */ debug?: boolean; } declare class BugCatchServerReporter { private timer; private readonly serverMetricsUrl; private readonly interval; private readonly instanceId; private readonly debug; private prevCpuUsage; private prevCpuTime; constructor(opts: ServerReporterOptions); start(): void; stop(): void; private report; private log; } declare const BugCatch: { /** * Initialize the SDK. Call this once, as early as possible in your app. * * @example * BugCatch.init({ * dsn: 'http://localhost:3000/ingest/project-id?key=sdk-key', * release: '1.0.0', * environment: 'production', * }); */ init(options: BugCatchOptions): BugCatchClient; /** * Capture an Error object or any value as an error event. * Returns the generated event_id. */ captureException(error: unknown, extra?: Record): string; /** * Capture a plain text message as an event. * Returns the generated event_id. */ captureMessage(message: string, level?: string, extra?: Record): string; /** * Set the current user context. Attached to all subsequent events. */ setUser(user: UserContext): void; /** * Clear the current user context (e.g. on logout). */ clearUser(): void; /** * Set a tag that will be attached to all subsequent events. */ setTag(key: string, value: string): void; /** * Manually add a breadcrumb. */ addBreadcrumb(crumb: BreadcrumbEntry): void; /** * Queue a structured log line. Batched and flushed automatically - * does not send a request per call. * * @example * BugCatch.captureLog('info', 'Checkout completed', { orderId: '4821' }); */ captureLog(level: LogLevel, message: string, attributes?: Record, traceId?: string): void; /** * Set the trace id attached to every subsequent `captureLog()` call that * doesn't pass its own. Global mutable state - a Node server handling * concurrent requests should pass `traceId` explicitly to `captureLog()` * instead. */ setTraceId(traceId: string): void; /** Clear the trace id set via `setTraceId()`. */ clearTraceId(): void; /** Send any queued log lines immediately instead of waiting for the next flush. */ flushLogsNow(): Promise; /** * Send any queued session updates immediately instead of waiting for the * next flush. Useful before a short-lived script exits. */ flushSessionsNow(): Promise; /** * Send any queued Web Vitals readings immediately instead of waiting for * the next flush. Web Vitals capture is automatic in the browser * (`autoCaptureWebVitals: true` by default) - you rarely need this. */ flushWebVitalsNow(): Promise; /** * Start recording session replay if it isn't already running. Only needed * when `sessionReplay: true` wasn't set at `init()` - e.g. starting a * recording after a user opts in. No-op outside the browser. * * @example * BugCatch.startReplayNow(); */ startReplayNow(): void; /** Stop the active session replay recording (if any) and flush what's queued. */ stopReplayNow(): void; /** Send any queued session replay chunks immediately instead of waiting for the next flush. */ flushReplayNow(): Promise; /** The active replay recording's id, if session replay is currently running. */ getReplayId(): string | undefined; /** * Whether the given user (or the currently `setUser()`-set one) may be * session-replay recorded under this project's privacy allowlist. Fails * closed - a network error counts as "not allowed". `startReplayNow()` * and the `sessionReplay: true` init option already check this before * recording starts; call it directly if your app wants to decide first * (e.g. to show/hide a "recording" indicator) rather than find out only * after attempting to start. * * @example * if (await BugCatch.isReplayAllowed()) BugCatch.startReplayNow(); */ isReplayAllowed(userId?: string): Promise; /** * Start a new traced transaction. Queue for delivery by calling * `.finish()` on it (and on any spans you started). * * @example * const txn = BugCatch.startTransaction('GET /api/orders/:id', 'http.server'); * const span = txn.startChild('db.query', 'SELECT * FROM orders WHERE id = ?'); * span.finish(); * txn.setHttpStatus(200); * txn.finish(); */ startTransaction(name: string, op?: string): BugCatchTransaction; /** * Set the transaction that fetch/XHR auto-instrumentation attaches child * spans to. Global mutable state - a Node server handling concurrent * requests should use `req.bugcatchTransaction` (set by * `tracingMiddleware()`) instead of relying on this. */ setCurrentTransaction(transaction: BugCatchTransaction | undefined): void; /** The transaction set via `setCurrentTransaction`, if any. */ getCurrentTransaction(): BugCatchTransaction | undefined; /** Send any queued transactions immediately instead of waiting for the next flush. */ flushTransactionsNow(): Promise; /** * Express/Connect-style middleware: starts an `http.server` transaction * per request, finishes it when the response ends. * Drops 404s by default, and any route you name via `ignoreRoutes` (e.g. * health checks) - see `BugCatchClient.tracingMiddleware()`. * * @example * app.use(BugCatch.tracingMiddleware()); * app.use(BugCatch.tracingMiddleware({ ignoreRoutes: ['/health'] })); */ tracingMiddleware(options?: { ignoreStatusCodes?: number[]; ignoreRoutes?: Array; }): (req: { method: string; originalUrl?: string; url?: string; bugcatchTransaction?: BugCatchTransaction; }, res: { statusCode: number; on: (event: "finish", cb: () => void) => void; }, next: () => void) => void; /** * Auto-instrument TypeORM: every query run through the given `DataSource` * becomes a `db.query` child span on the current transaction. * * @example * BugCatch.instrumentTypeOrm(dataSource); // the TypeORM DataSource */ instrumentTypeOrm(dataSource: Parameters[0]): () => void; /** * Auto-instrument ioredis: every command sent through the given `Redis` * class becomes a `cache.command` child span on the current transaction. * * @example * import Redis from 'ioredis'; * BugCatch.instrumentIoredis(Redis); */ instrumentIoredis(RedisClass: Parameters[0]): () => void; /** * Manually report an API call timing to BugCatch. * Use when `autoTrackRequests` is off or for server-side tracking. * * @example * const start = Date.now(); * await fetch('/api/orders'); * BugCatch.trackRequest('GET', '/api/orders', Date.now() - start, 200); */ trackRequest(method: string, route: string, durationMs: number, statusCode: number): void; /** * Tear down all listeners. Useful in SPA cleanup or hot-reload scenarios. */ destroy(): void; }; export { type BreadcrumbEntry, BugCatch, BugCatchClient, type BugCatchOptions, BugCatchServerReporter, BugCatchSpan, BugCatchTransaction, type EventPayload, type LogLevel, type LogPayload, type ParsedDsn, type ReplayChunkPayload, type ServerReporterOptions, type SessionPayload, type SessionStatus, type SpanPayload, type StackFrame, type TransactionPayload, type UserContext, type WebVitalMetric, type WebVitalPayload, BugCatch as default };