@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
66 lines • 3.88 kB
TypeScript
import type { ExceptionContent } from '../entry/content.js';
import { type RecordInput } from '../entry/entry.js';
import type { ExceptionsOptions } from './telescope.options.js';
/**
* Where a throw happened, for the doors that know something the stack doesn't.
*
* The Nest interceptor passes nothing (the request entry in the same batch
* already says which route it was). A queue or schedule watcher DOES have
* context worth keeping — which queue, which job id, which cron task — because
* off the request path there is no sibling `request` entry to read it from.
*/
export interface ExceptionCaptureDetails {
/** Merged into the entry's `content.context` (queue + job name, task name, …). */
context?: Record<string, unknown>;
/** Extra tags appended to the exception entry. */
tags?: string[];
}
/**
* Decides whether a thrown error is expected 4xx control flow that should NOT
* become an exception entry. True only for a NestJS `HttpException` whose
* `getStatus()` is a 4xx (>= 400 and < 500), and only while the
* `captureHttp4xx` escape hatch is off (the default).
*
* WHY the default-skip: expected 4xx control flow is NOT an incident. A
* `ForbiddenException` (403), `NotFoundException` (404) or a validation 400 is
* the framework doing its job — permission denied, resource missing, bad input.
* Recording those as exception entries means every permission denial in
* production opens a NEW exception family (the family hash keys on
* name+message+top-frame, so each call site is its own family), which fires the
* `new-exception` Slack alert and, in AI auto-mode, spends model tokens on a
* "diagnosis" of intended behaviour. We hit exactly this: Telescope's own
* client-errors `authorize` gate threw a 403, it was captured as a brand-new
* family, paged Slack, and burned an AI diagnosis.
*
* This applies to EVERY door, not just HTTP. An `HttpException` thrown inside a
* job body is the same expected control flow (hosts routinely reuse
* `NotFoundException` in services that both a controller and a worker call), so
* a queue retry storm must not be able to page on-call through the back door
* that the front door was hardened against.
*
* Detected via `instanceof HttpException` from `@nestjs/common` (a peer dep),
* which also covers all the built-in subclasses (`ForbiddenException`,
* `NotFoundException`, `BadRequestException`, the validation-pipe 400, …).
*/
export declare function isExpectedHttpControlFlow(error: unknown, options: ExceptionsOptions | undefined): boolean;
/**
* Build the `exception` entry for a thrown value. A non-`Error` throw (a string,
* a rejected object) is normalised into an `Error` so `class`/`message` are
* always populated and the family hash is always computable.
*/
export declare function toExceptionRecordInput(error: unknown, details?: ExceptionCaptureDetails): RecordInput<ExceptionContent>;
/**
* Apply the 4xx policy and, when the error survives it, hand the exception
* entry to `record`. Returns whether an entry was recorded (the watchers ignore
* it; it exists so tests and future doors can assert the decision).
*
* This function NEVER throws. Every caller is on the host's own failure path —
* an rxjs `catchError`, a `catch` block that is about to re-throw a job's error
* — and a throw from here would REPLACE the host's original error with a
* Telescope error, turning an observability bug into a data-loss bug. Building
* the entry (`String(error)` on a hostile `toString`, a getter on `err.stack`)
* runs on the host's thread, so the guard has to be here and not only inside
* the Recorder.
*/
export declare function captureException(record: (input: RecordInput) => void, error: unknown, options: ExceptionsOptions | undefined, details?: ExceptionCaptureDetails): boolean;
//# sourceMappingURL=exception-capture.d.ts.map