import { type OnModuleDestroy, type OnModuleInit } from '@nestjs/common';
import type { ResolvedCoreConfig } from '../config/options.js';
import { type TelescopeModuleOptions } from './telescope.options.js';
import { TelescopeService } from './telescope.service.js';
/**
 * Which process-level event produced a captured crash. Kept as the literal Node
 * event names so the recorded `content.context.source` reads the same as the
 * thing you'd grep for in Node's docs.
 */
export type ProcessCrashKind = 'unhandledRejection' | 'uncaughtException';
/** Resolved exit behaviour — see {@link TelescopeCrashCapture} for the contract. */
type CrashExitMode = 'exit' | 'passthrough';
/**
 * Records `process.on('unhandledRejection')` and `process.on('uncaughtException')`
 * as telescope `exception` entries. **Opt-in** via
 * `exceptions.processCrashes.enabled` — off by default.
 *
 * WHY this exists: before it, the ONLY server-side exception source was
 * {@link TelescopeExceptionInterceptor}, which lives on the Nest pipeline. A
 * promise rejected with nobody awaiting it, or a throw from a timer / stream
 * callback / event emitter, never touches that pipeline — so it produced no
 * entry, no exception family, and no `new-exception` alert. Those are precisely
 * the failures that take the process down: the incident with the least
 * observability was the one that ended the process.
 *
 * WHY opt-in and not on by default: attaching a process-level listener CHANGES
 * THE HOST'S CRASH SEMANTICS. Node's default for an `uncaughtException` is to
 * print the stack and exit(1); the moment ANY listener is registered that
 * default is suppressed and the process keeps running. The same is true for
 * `unhandledRejection` under Node's default `--unhandled-rejections=throw`. A
 * library that attached these behind the host's back would silently convert
 * "crashed, restarted clean by the orchestrator" into "limping along with
 * half-initialised state" — a strictly worse failure mode than the one it was
 * trying to observe. So the host has to ask for it, in writing.
 *
 * ## The exit contract
 *
 * Telescope never decides on its own whether your process dies. After the entry
 * is recorded and the bounded flush has settled:
 *
 * - `onCrash: 'exit'` — reproduce Node's default: write the stack to stderr and
 *   `process.exit(1)` (`exitCode` is configurable). Use this when Telescope is
 *   the only process-level listener, i.e. when the process WOULD have died.
 * - `onCrash: 'passthrough'` — record only, then return. The host's own handler
 *   (or an APM agent's) decides what happens next. Use this ONLY when something
 *   else already owns the crash, otherwise you have converted a crash into a
 *   zombie.
 * - `onCrash: 'auto'` (the default) — decide at registration time by counting
 *   PRE-EXISTING listeners for the two events. Zero listeners means nothing else
 *   was deciding and Node would have crashed, so Telescope reproduces that
 *   (`'exit'`). One or more means the host was already deciding, so Telescope
 *   defers (`'passthrough'`) rather than yanking the exit out from under an
 *   existing handler. The decision is logged at boot, once.
 *
 * `'auto'` samples the listener count at `onModuleInit`. A host that registers
 * its own handler AFTER Nest bootstrap must therefore pass `onCrash` explicitly
 * — auto will already have picked `'exit'` and will race the late handler to
 * the exit. The boot log line tells you which mode is live.
 *
 * To keep Node's ORIGINAL crash behaviour exactly: leave `onCrash` at `'auto'`
 * (or set `'exit'`) and register no competing handler, or — the belt-and-braces
 * version — register your own handler that exits, and let Telescope run in
 * `'passthrough'`.
 *
 * ## Recording is best-effort and bounded
 *
 * The process may be milliseconds from death, so the flush is raced against
 * `flushTimeoutMs` (default 2s) on an unref'd timer: a wedged storage provider
 * delays the exit by at most that budget instead of hanging a dying process
 * forever. Every step is wrapped so that a failure INSIDE the recording path can
 * never mask or replace the host's original error — the worst case is a missing
 * entry, never a swallowed crash.
 */
export declare class TelescopeCrashCapture implements OnModuleInit, OnModuleDestroy {
    private readonly service;
    private readonly options;
    private readonly config;
    private readonly logger;
    /** True once this instance owns the process listeners (drives teardown). */
    private installed;
    private exitMode;
    private flushTimeoutMs;
    private exitCode;
    /**
     * Re-entrancy latch around the SYNCHRONOUS record call. A tagger, redactor or
     * host `filter` that throws — or anything else that raises a crash from
     * inside `record()` — would otherwise re-enter this handler and recurse until
     * the stack gives out, burying the original error under its own failure. The
     * latch is deliberately NOT held across the flush: two genuinely unrelated
     * crashes in the same tick must both be recorded, and holding a mutex for the
     * whole flush budget would silently drop the second one.
     */
    private recording;
    /**
     * Bound instance arrow functions, kept as fields so `onModuleDestroy` can pass
     * the SAME references to `removeListener`. A fresh `.bind()` at teardown time
     * would silently remove nothing and leak the listener into the next test file
     * — the classic way this kind of code poisons an unrelated suite.
     */
    /**
     * The `.catch()` on each is the terminator of the loop this class could
     * otherwise become: an error escaping `capture()` would reject a promise
     * nobody awaits, which IS an unhandled rejection, which re-enters this very
     * handler. Swallowing at the boundary means the worst case is a lost entry.
     */
    private readonly onUnhandledRejection;
    private readonly onUncaughtException;
    constructor(service: TelescopeService, options: TelescopeModuleOptions, config: ResolvedCoreConfig);
    onModuleInit(): void;
    onModuleDestroy(): void;
    /** Whether this instance currently owns the process listeners. @internal */
    get isInstalled(): boolean;
    /** The resolved exit behaviour for this instance. @internal */
    get resolvedExitMode(): CrashExitMode;
    /**
     * Resolve `'auto'` against the listeners already on the process. Counting
     * BOTH events together is deliberate: a host that handles only
     * `uncaughtException` still demonstrably owns crash policy, and pre-empting it
     * on the rejection side alone would produce two different exit behaviours for
     * what is, under Node's defaults, the same fatal path.
     */
    private resolveExitMode;
    /**
     * Record the crash, flush on a bounded budget, then apply the exit contract.
     *
     * Everything up to the first `await` runs SYNCHRONOUSLY inside the process
     * event handler, which is load-bearing: `AsyncLocalStorage` still holds the
     * batch that was active when the promise rejected / the callback threw, so
     * `service.record()` inherits its `batchId`, `origin` and ambient `traceId`
     * for free. Move the `record()` call after an await and every crash becomes
     * orphaned.
     */
    private capture;
    /**
     * The synchronous half of the capture: read the active batch and hand the
     * entry to the Recorder. Never throws — a failure in OUR path must never
     * replace or hide the host's error, so the worst case is a warning and a
     * missing entry, and the exit contract still runs.
     */
    private recordCrash;
    /**
     * Race the recorder flush against `flushTimeoutMs` on an UNREF'D timer.
     *
     * Two failure modes are being avoided at once. First, awaiting an unbounded
     * `flush()` in a process that is about to exit turns a crash into a hang if
     * the storage backend is wedged — the timeout caps that at a known budget and
     * we exit with the entry possibly unwritten, which is the right trade for a
     * dying process. Second, the `.catch()` on the flush promise itself is NOT
     * decoration: once the race resolves via timeout, that promise is abandoned,
     * and an abandoned rejecting promise is an unhandled rejection — which would
     * re-enter this very handler. The catch is what stops crash capture from
     * feeding itself.
     */
    private boundedFlush;
    /**
     * Apply the exit contract. In `'exit'` mode this reproduces what Node would
     * have done with no listener attached: the stack on stderr, then exit(1).
     * `process.stderr.write` rather than the Nest `Logger` so the output survives
     * a custom logger that buffers, filters by level, or ships asynchronously —
     * the last thing written before a fatal exit has to be unconditional.
     */
    private finish;
}
export {};
//# sourceMappingURL=telescope-crash-capture.service.d.ts.map