/** * Bridge to the request's flow context. The grafted nest module runs every * request inside `@lensmcp/nest-instrumentation`'s AsyncLocalStorage scope — * since taps fire INSIDE that scope (a pg query awaited by a controller, a * fetch from a service), reading the same ALS at call time yields the * flowId/requestId that stitches the tap event into the request's trace. * * The module is ESM and this lib is CJS, so the getter is resolved via * `require(esm)` — SYNCHRONOUSLY, not a dynamic `import()`. This ran fire- * and-forget async until it caused a real crash: `install()` runs this at * PRELOAD (before the host's bundle even starts), so its dynamic import was * still in flight the instant a host's own top-level * `require('@lensmcp/nest-instrumentation')` (any Nest service built with * `createLensmcpNestApp`) executed moments later — two concurrent loaders * (one sync `require`, one async `import()`) racing the SAME not-yet-settled * ESM module is exactly what Node's `require(esm)` cannot tolerate, and it * threw `ERR_INTERNAL_ASSERTION: Unexpected module status 0` on every boot. * A synchronous `require()` here puts BOTH loaders on the same serialized * path — the second one just hits Node's module cache — so the race is * structurally impossible instead of merely unlikely. */ import type { FlowCtx } from './emit'; interface LiveCtx { flowId?: string; requestId?: string; originNodeId?: string; dbCallCount?: number; redisCallCount?: number; externalCallCount?: number; } type CtxGetter = () => LiveCtx | undefined; type CtxRunner = (ctx: { sessionId: string; requestId: string; flowId?: string; }, fn: () => T) => T; /** * Why the failure has to be told apart from the package being absent (issues/codex-security/46). * * `require(esm)` only works from Node 20.19 (and 22.12); before that it throws `ERR_REQUIRE_ESM`. * The catch here said *"nest instrumentation absent"*, which on Node 18 was simply untrue — the * package was installed and working, and every database, Redis, egress and BullMQ event silently * lost its request/flow correlation while the application ran normally. Nothing logged, nothing * threw, and the lens was just quietly less useful. The declared engine floor said `>=18`, so the * runtime that broke it was one we claimed to support. * * The floor is now `>=20.19` (see this package's `engines`), and this reports rather than swallows, * because an install that predates the floor must not look like an install that lacks the package. * The same shape as issues/full-review/13 — a catch hiding the case it was not written for. */ export type FlowContextStatus = { state: 'ready'; } | { state: 'absent'; } | { state: 'runtime-too-old'; detail: string; } | { state: 'failed'; detail: string; } | { state: 'not-primed'; }; /** What happened the last time `primeFlowContext` ran. */ export declare function flowContextStatus(): FlowContextStatus; /** Test seam — forget both the resolved getters and the once-only warning. */ export declare function resetFlowContextForTests(): void; /** `require(esm)` before Node 20.19/22.12. Node reports it on the error's `code`. */ export declare function isRequireEsmError(err: unknown): boolean; /** The package genuinely is not installed — a legitimate, silent, supported configuration. */ export declare function isModuleNotFoundError(err: unknown): boolean; interface NestContextModule { currentLensmcpContext: CtxGetter; runInLensmcpContext: CtxRunner; } /** * `load` exists so the CLASSIFICATION above can be tested against each failure it distinguishes. * Nothing in production passes it: intercepting the real `require` from a test would mean patching * `Module.prototype.require`, which the bundler's own injected require does not go through — a test * that reaches nothing, which is the shape this round exists to stop writing. */ export declare function primeFlowContext(load?: () => NestContextModule): void; /** * Run `fn` inside a fresh flow context (queue workers, timers — async * entry points that aren't HTTP requests). Inner taps then inherit the * flowId exactly like they do inside a traced request. */ export declare function runInFlowContext(ctx: { flowId?: string; requestId: string; }, fn: () => T): T; /** * The LIVE (mutable) request context — taps bump its operation counters * (db/redis/external) so the span wrapper's loop detector can report * "this method issued N of these" (the N+1 / fan-out signal). */ export declare function liveFlowContext(): LiveCtx | undefined; /** Snapshot the live flow context — call AT the operation site, before awaits. */ export declare function flowContext(): FlowCtx | undefined; export {};