@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
123 lines • 6.58 kB
TypeScript
export declare const DEFAULT_REDACT_KEYS: readonly string[];
export interface RedactOptions {
/** Extra keys to mask (merged with DEFAULT_REDACT_KEYS, case-insensitive). */
keys?: string[];
/**
* Exact dot-paths to mask regardless of key name, e.g. `'body.ssn'`.
* Unlike `keys` (which match that key at any depth), a path must match the
* full traversal location from the root of the value passed to `redact()`.
*/
paths?: string[];
/** Replacement string. Defaults to '[REDACTED]'. */
mask?: string;
/**
* Maximum object/array nesting depth to clone. Beyond it, the subtree is
* replaced with `'[Truncated: depth]'`. ON BY DEFAULT (8). This bounds a deep
* recursive graph (e.g. an ORM entity referencing its EntityManager → identity
* map → entities) regardless of how the host shaped it.
*/
maxDepth?: number;
/**
* Maximum cloned string length. Longer strings are clipped to the first N
* chars plus a `'…[truncated]'` suffix. ON BY DEFAULT (8_192). Defeats a single
* mega-string field (a serialized blob, a base64 payload) ballooning an entry.
*/
maxStringLength?: number;
/**
* Maximum number of array items to clone. Longer arrays keep the first N items
* and append a final `'[Truncated: N of M items]'` marker element. ON BY
* DEFAULT (200). Bounds high-cardinality collections (a full result set, a
* relation collection) without losing the head of the list.
*/
maxArrayLength?: number;
/**
* Per-call walked-node budget: every object, array, and leaf visited counts
* against it. Once exhausted, remaining subtrees become `'[Truncated: size]'`.
* ON BY DEFAULT (5_000). This is the overall bytes-ish cap that bounds a
* mega-graph regardless of its SHAPE — wide, deep, or both — so a single fat
* entry can never retain an unbounded clone.
*/
maxNodes?: number;
/**
* Approximate serialized-byte budget per cloned content: every string charges
* its length, every visited node a small fixed overhead. Once exhausted,
* remaining subtrees become `'[Truncated: size]'`. ON BY DEFAULT (16_384).
* This is the DETERMINISTIC cap on `bytes_per_entry` — the third factor of the
* OOM working-set formula (`prune.after × ingest rate × bytes_per_entry`) —
* and the bound that bites on incident-class payloads made of MANY SMALL
* strings (an ORM user graph) that slip under the node/string/array limits.
*/
maxContentBytes?: number;
/**
* Per-entry-type overrides of the numeric bounds above, keyed by the entry's
* `type` (e.g. `'exception'`, `'client_exception'`). A listed type's bounds are
* merged OVER the top-level bounds for that entry only; unlisted types use the
* top-level bounds unchanged. The masking spec (`keys`/`paths`/`mask`) is never
* per-type — it stays uniform (and is compiled once).
*
* Motivation: the content-byte budget is really an OOM guard on HIGH-VOLUME
* entries (request/query/cache clone big live graphs). Rare, high-value entries
* — exceptions and client exceptions, whose stacks/componentStacks are
* legitimately many KB — can be given a bigger budget WITHOUT loosening the
* guard on the noisy ones.
*/
perType?: Record<string, RedactBounds>;
}
/**
* The numeric memory bounds of {@link RedactOptions} — the subset overridable
* PER ENTRY TYPE via {@link RedactOptions.perType}. The masking spec
* (`keys`/`paths`/`mask`) is deliberately excluded: masking is a security
* invariant that stays uniform across every entry.
*/
export type RedactBounds = Pick<RedactOptions, 'maxDepth' | 'maxStringLength' | 'maxArrayLength' | 'maxNodes' | 'maxContentBytes'>;
/** Result of a bounded redaction: the detached clone plus whether anything was clipped. */
export interface RedactBoundedResult {
/** The detached, masked, bounded clone. */
value: unknown;
/** True when any bound (depth/string/array/node) clipped some content. */
truncated: boolean;
}
/**
* The masking decision derived from {@link RedactOptions}, compiled ONCE so the
* per-entry hot path never rebuilds these Sets. `keySet` holds the lowercased
* union of {@link DEFAULT_REDACT_KEYS} and `options.keys`; `paths` holds the
* exact dot-paths. Build it via {@link compileRedactSpec} at boot (the Recorder
* does this in its constructor) and feed it to {@link redactBoundedWith}.
*/
export interface CompiledRedactSpec {
/** Lowercased union of default + configured keys, matched at any depth. */
keySet: ReadonlySet<string>;
/** Exact dot-paths to mask regardless of key name. */
paths: ReadonlySet<string>;
}
/**
* Precompiles the immutable key/path Sets from {@link RedactOptions}. Call once
* (config is immutable after boot) and reuse the result across every entry —
* this is the optimization that keeps the hottest redaction path allocation-free.
*/
export declare function compileRedactSpec(options: RedactOptions): CompiledRedactSpec;
/**
* Bounded, never-throwing, SYNCHRONOUS deep clone of `value` with sensitive
* leaves masked. Same key/path masking semantics as {@link redact}, plus the
* hard memory bounds in {@link RedactOptions} (all defaulted on). Returns the
* clone AND whether truncation happened so the Recorder can surface a counter.
*
* Synchronicity is load-bearing: this is the detach that releases the host's
* live object graph at `record()` time — never defer it (see spec §A.1).
*/
export declare function redactBounded(value: unknown, options: RedactOptions): RedactBoundedResult;
/**
* Bounded redaction using an ALREADY-COMPILED {@link CompiledRedactSpec}, so the
* per-entry hot path never rebuilds the key/path Sets. Identical behaviour to
* {@link redactBounded}; only the `keys`/`paths` of `options` are ignored in
* favour of the prebuilt `spec` (the remaining bound options are still read).
*/
export declare function redactBoundedWith(value: unknown, options: RedactOptions, spec: CompiledRedactSpec): RedactBoundedResult;
/**
* Returns a deep clone of `value` with sensitive leaves replaced by the mask.
* Never mutates the input. Memory-bounded by default (see {@link RedactOptions});
* delegates to {@link redactBounded} and discards the truncation flag, so every
* existing caller keeps the original `(value) => clone` signature.
*/
export declare function redact(value: unknown, options: RedactOptions): unknown;
//# sourceMappingURL=redact.d.ts.map