@lensmcp/nest-instrumentation 1.25.32 → 1.25.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A request context's START signal: how a request the gateway routed with no flow is given one when its work turns out
3
+ * to matter.
4
+ *
5
+ * A request that arrives with no flow starts `api:<request id>` (`@lensmcp/core` headless-flow.ts) — unless the gateway
6
+ * routed it (`x-internal-token`). The gateway starts no flow for a call between the cluster's own services that carries
7
+ * none: that is a service's background chatter (a timer polling a directory every few seconds), and a flow for each
8
+ * poll would bury the flows a person opens. On FoodGuard (2026-10-27) those were ~5,600 routed requests in three hours,
9
+ * beside 189 flows.
10
+ *
11
+ * But chatter sometimes does the work a reader asks about. Engine's memory consolidation is a `setInterval`: it asks
12
+ * knowledge to remember a summary, and knowledge embeds it — an AI call, paid for, filed under a request id and no
13
+ * flow. Before east-west hops carried the caller's flow (tap-egress `hopHeaders`), every processor and knowledge AI
14
+ * call landed that way: 1,392 of them, none in a flow.
15
+ *
16
+ * So such a request carries a START signal. The context is opened here (`TraceInterceptor`), but the work that decides
17
+ * it matters is seen by `@lensmcp/node-instrumentation`'s taps (an AI provider's call), and that package depends on
18
+ * this one. The seam is the context object itself, the way `context-end.ts` crosses: the interceptor attaches a starter
19
+ * under a REGISTERED symbol, and a tap holding the live context calls it. The starter gives the request the flow the
20
+ * pod would have — `api:<request id>` — and says what started it (its route, stamped at the request's start), once.
21
+ * What the request does from then on carries the flow: its AI call, its later queries, spans and hops, its
22
+ * `server-request`. What it did before does not.
23
+ */
24
+ export declare const FLOW_START: unique symbol;
25
+ /** Starts the context's flow, once; answers the flow it is in. */
26
+ export type FlowStart = () => string | undefined;
27
+ /** Give `ctx` a start signal. Not enumerable: a spread or a JSON of the context never carries it. */
28
+ export declare function attachFlowStart(ctx: object, start: FlowStart): void;
@@ -0,0 +1 @@
1
+ "use strict";var a=Object.defineProperty;var r=(t,e)=>a(t,"name",{value:e,configurable:!0});var o=Object.defineProperty,c=r((t,e)=>o(t,"name",{value:e,configurable:!0}),"e");export const FLOW_START=Symbol.for("lensmcp.flowStart");export function attachFlowStart(t,e){Object.defineProperty(t,FLOW_START,{value:e})}r(attachFlowStart,"attachFlowStart"),c(attachFlowStart,"attachFlowStart");
@@ -7,6 +7,8 @@ export interface TraceWrapOptions {
7
7
  db?: boolean;
8
8
  /** Capture this method's arguments and/or what it returned on its span (capture.ts). Off unless set here or named in `LENSMCP_CAPTURE`. */
9
9
  capture?: CaptureWhat;
10
+ /** Named by `@TraceMethod`: its span is emitted however short, in a flow or not (`trace.flowlessMinMs`). */
11
+ explicit?: boolean;
10
12
  }
11
13
  /** `@LensmcpIgnore()` — skip a class (all methods) or a single method. */
12
14
  export declare function LensmcpIgnore(): ClassDecorator & MethodDecorator;
package/lib/instrument.js CHANGED
@@ -1 +1 @@
1
- "use strict";var Q=Object.defineProperty;var m=(t,r)=>Q(t,"name",{value:r,configurable:!0});var V=Object.defineProperty,l=m((t,r)=>V(t,"name",{value:r,configurable:!0}),"l");import{fingerprint as b,ulid as w}from"@lensmcp/core";import{redactText as $}from"@lensmcp/core/redact";import{captureFor as z,capturedValue as O}from"./capture.js";import{currentLensmcpContext as H,currentSpanId as X,LENSMCP_SPAN_STORAGE as J}from"./context.js";import{activeOptions as K}from"./state.js";import{performance as R}from"node:perf_hooks";export const LENSMCP_TRACED=Symbol.for("@lensmcp/nest-instrumentation/traced"),LENSMCP_IGNORE=Symbol.for("@lensmcp/nest-instrumentation/ignore");const h=300;function T(t){if(t instanceof Error){const d=t.code,c=$(t.message??"");return{name:t.name||t.constructor?.name||"Error",...c?{message:c.length>h?`${c.slice(0,h)}\u2026`:c}:{},...typeof d=="string"&&d.length<=64?{code:d}:{}}}const r=typeof t=="string"?$(t):void 0;return{name:t==null?"nothing":typeof t=="object"?"non-Error object":`non-Error ${typeof t}`,...r?{message:r.slice(0,h)}:{}}}m(T,"z"),l(T,"thrownOf");export function LensmcpIgnore(){return((t,r)=>{if(r===void 0)t[LENSMCP_IGNORE]=!0;else{const d=t[r];typeof d=="function"&&(d[LENSMCP_IGNORE]=!0)}})}m(LensmcpIgnore,"LensmcpIgnore"),l(LensmcpIgnore,"LensmcpIgnore");export function isTraced(t){return typeof t=="function"&&!!t[LENSMCP_TRACED]}m(isTraced,"isTraced"),l(isTraced,"isTraced");export function isIgnored(t){return!!t&&(typeof t=="function"||typeof t=="object")&&!!t[LENSMCP_IGNORE]}m(isIgnored,"isIgnored"),l(isIgnored,"isIgnored");export function traceWrap(t,r,d={}){if(isTraced(t))return t;const c=d.db??!1,x=l(function(...I){let n;try{n=K()}catch{return t.apply(this,I)}if(!n.trace.services)return t.apply(this,I);const u=Date.now();let D=u,j=0;const e=H(),g=w(),E=X(),A=e?.dbCallCount??0,P=e?.redisCallCount??0,_=e?.externalCallCount??0,B=e?.loopDbCalls??0,G=e?.loopRedisCalls??0,W=e?.loopExternalCalls??0,f=d.capture??z(r),v=f==="args"||f==="both"?O(I):void 0,q=l((o,s,C)=>{const a=Date.now()-u,i={id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:o==="error"?"error":"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"method-call",identity:r}),title:`${r} (${a}ms)${o==="error"?" \u2717":""}`,message:s instanceof Error?s.message:void 0,raw:{kind:"span",...n.projectName?{project:n.projectName}:{},span:{name:r,spanId:g,...E?{parentSpanId:E}:{},flowId:e?.flowId,requestId:e?.requestId,startTime:u,endTime:Date.now(),durationMs:a,syncStartedAt:D,syncDurationMs:j,status:o,outcome:o==="error"?"threw":"returned",...o==="error"?{error:T(s)}:{},...v!==void 0?{args:v}:{},...o==="ok"&&(f==="result"||f==="both")?{result:O(C)}:{}}}};n.emit(i)},"emitSpan"),M=l(()=>{e&&(e.dbCallCount=(e.dbCallCount??0)+1);const o=Date.now();n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"db",category:"db",severity:"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"db-query",identity:r}),title:`query ${r}`,raw:{kind:"db-query",...n.projectName?{project:n.projectName}:{},query:{signature:r},startedAt:u,durationMs:o-u,span:{parentSpanId:g}}})},"emitDbQuery"),N=l(()=>{if(c||!e)return;const o=(e.dbCallCount??0)-A,s=(e.redisCallCount??0)-P,C=(e.externalCallCount??0)-_,a=Math.max(0,o-((e.loopDbCalls??0)-B)),i=Math.max(0,s-((e.loopRedisCalls??0)-G)),p=Math.max(0,C-((e.loopExternalCalls??0)-W));if(a<3&&i<3&&p<3)return;const F=Date.now()-u,y=[];a>=3&&y.push(`${a} DB calls`),i>=3&&y.push(`${i} redis calls`),p>=3&&y.push(`${p} external calls`),a>=3&&(e.loopDbCalls=(e.loopDbCalls??0)+a),i>=3&&(e.loopRedisCalls=(e.loopRedisCalls??0)+i),p>=3&&(e.loopExternalCalls=(e.loopExternalCalls??0)+p);const L=o-a+(s-i)+(C-p);n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:"warning",context:{sessionId:n.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:b({kind:"loop",identity:r}),title:`loop in ${r} (${y.join(", ")})`,raw:{kind:"loop",...n.projectName?{project:n.projectName}:{},loop:{iterations:Math.max(a,i,p),durationMs:F,startedAt:u,dbCallsInsideLoop:a,redisCallsInsideLoop:i,externalCallsInsideLoop:p,awaitedOperationsInsideLoop:a+i+p,...L>0?{reportedBeneath:L}:{}}}})},"emitLoopIfNeeded"),S=l(o=>{c&&M(),q("ok",void 0,o),N()},"onOk"),k=l(o=>{c&&M(),q("error",o),N()},"onErr");try{const o=J.run(g,()=>{D=Date.now();const s=R.now();try{return t.apply(this,I)}finally{j=Math.round((R.now()-s)*100)/100}});return o instanceof Promise?o.then(s=>(S(s),s),s=>{throw k(s),s}):(S(o),o)}catch(o){throw k(o),o}},"wrapped");return x[LENSMCP_TRACED]=!0,x}m(traceWrap,"traceWrap"),l(traceWrap,"traceWrap");
1
+ "use strict";var Q=Object.defineProperty;var f=(t,r)=>Q(t,"name",{value:r,configurable:!0});var V=Object.defineProperty,l=f((t,r)=>V(t,"name",{value:r,configurable:!0}),"l");import{fingerprint as b,ulid as w}from"@lensmcp/core";import{redactText as $}from"@lensmcp/core/redact";import{captureFor as z,capturedValue as O}from"./capture.js";import{currentLensmcpContext as H,currentSpanId as X,LENSMCP_SPAN_STORAGE as J}from"./context.js";import{activeOptions as K}from"./state.js";import{performance as R}from"node:perf_hooks";export const LENSMCP_TRACED=Symbol.for("@lensmcp/nest-instrumentation/traced"),LENSMCP_IGNORE=Symbol.for("@lensmcp/nest-instrumentation/ignore");const h=300;function T(t){if(t instanceof Error){const d=t.code,p=$(t.message??"");return{name:t.name||t.constructor?.name||"Error",...p?{message:p.length>h?`${p.slice(0,h)}\u2026`:p}:{},...typeof d=="string"&&d.length<=64?{code:d}:{}}}const r=typeof t=="string"?$(t):void 0;return{name:t==null?"nothing":typeof t=="object"?"non-Error object":`non-Error ${typeof t}`,...r?{message:r.slice(0,h)}:{}}}f(T,"z"),l(T,"thrownOf");export function LensmcpIgnore(){return((t,r)=>{if(r===void 0)t[LENSMCP_IGNORE]=!0;else{const d=t[r];typeof d=="function"&&(d[LENSMCP_IGNORE]=!0)}})}f(LensmcpIgnore,"LensmcpIgnore"),l(LensmcpIgnore,"LensmcpIgnore");export function isTraced(t){return typeof t=="function"&&!!t[LENSMCP_TRACED]}f(isTraced,"isTraced"),l(isTraced,"isTraced");export function isIgnored(t){return!!t&&(typeof t=="function"||typeof t=="object")&&!!t[LENSMCP_IGNORE]}f(isIgnored,"isIgnored"),l(isIgnored,"isIgnored");export function traceWrap(t,r,d={}){if(isTraced(t))return t;const p=d.db??!1,x=l(function(...I){let n;try{n=K()}catch{return t.apply(this,I)}if(!n.trace.services)return t.apply(this,I);const u=Date.now();let D=u,j=0;const e=H(),g=w(),E=X(),A=e?.dbCallCount??0,P=e?.redisCallCount??0,_=e?.externalCallCount??0,B=e?.loopDbCalls??0,G=e?.loopRedisCalls??0,W=e?.loopExternalCalls??0,m=d.capture??z(r),v=m==="args"||m==="both"?O(I):void 0,M=l((o,s,C)=>{const a=Date.now()-u;if(o==="ok"&&!d.explicit&&m===void 0&&!e?.flowId&&!e?.requestId&&!e?.runId&&a<n.trace.flowlessMinMs)return;const i={id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:o==="error"?"error":"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"method-call",identity:r}),title:`${r} (${a}ms)${o==="error"?" \u2717":""}`,message:s instanceof Error?s.message:void 0,raw:{kind:"span",...n.projectName?{project:n.projectName}:{},span:{name:r,spanId:g,...E?{parentSpanId:E}:{},flowId:e?.flowId,requestId:e?.requestId,startTime:u,endTime:Date.now(),durationMs:a,syncStartedAt:D,syncDurationMs:j,status:o,outcome:o==="error"?"threw":"returned",...o==="error"?{error:T(s)}:{},...v!==void 0?{args:v}:{},...o==="ok"&&(m==="result"||m==="both")?{result:O(C)}:{}}}};n.emit(i)},"emitSpan"),q=l(()=>{e&&(e.dbCallCount=(e.dbCallCount??0)+1);const o=Date.now();n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"db",category:"db",severity:"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"db-query",identity:r}),title:`query ${r}`,raw:{kind:"db-query",...n.projectName?{project:n.projectName}:{},query:{signature:r},startedAt:u,durationMs:o-u,span:{parentSpanId:g}}})},"emitDbQuery"),k=l(()=>{if(p||!e)return;const o=(e.dbCallCount??0)-A,s=(e.redisCallCount??0)-P,C=(e.externalCallCount??0)-_,a=Math.max(0,o-((e.loopDbCalls??0)-B)),i=Math.max(0,s-((e.loopRedisCalls??0)-G)),c=Math.max(0,C-((e.loopExternalCalls??0)-W));if(a<3&&i<3&&c<3)return;const F=Date.now()-u,y=[];a>=3&&y.push(`${a} DB calls`),i>=3&&y.push(`${i} redis calls`),c>=3&&y.push(`${c} external calls`),a>=3&&(e.loopDbCalls=(e.loopDbCalls??0)+a),i>=3&&(e.loopRedisCalls=(e.loopRedisCalls??0)+i),c>=3&&(e.loopExternalCalls=(e.loopExternalCalls??0)+c);const L=o-a+(s-i)+(C-c);n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:"warning",context:{sessionId:n.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:b({kind:"loop",identity:r}),title:`loop in ${r} (${y.join(", ")})`,raw:{kind:"loop",...n.projectName?{project:n.projectName}:{},loop:{iterations:Math.max(a,i,c),durationMs:F,startedAt:u,dbCallsInsideLoop:a,redisCallsInsideLoop:i,externalCallsInsideLoop:c,awaitedOperationsInsideLoop:a+i+c,...L>0?{reportedBeneath:L}:{}}}})},"emitLoopIfNeeded"),N=l(o=>{p&&q(),M("ok",void 0,o),k()},"onOk"),S=l(o=>{p&&q(),M("error",o),k()},"onErr");try{const o=J.run(g,()=>{D=Date.now();const s=R.now();try{return t.apply(this,I)}finally{j=Math.round((R.now()-s)*100)/100}});return o instanceof Promise?o.then(s=>(N(s),s),s=>{throw S(s),s}):(N(o),o)}catch(o){throw S(o),o}},"wrapped");return x[LENSMCP_TRACED]=!0,x}f(traceWrap,"traceWrap"),l(traceWrap,"traceWrap");
package/lib/state.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { LensmcpModuleOptions, ResolvedOptions } from './types.js';
2
2
  export declare function setActiveOptions(opts: LensmcpModuleOptions): ResolvedOptions;
3
+ /** The floor a call outside any flow must reach to emit its span: the environment's, else the option's, else 10 ms. */
4
+ export declare function flowlessMinMs(option: number | undefined, env?: NodeJS.ProcessEnv): number;
3
5
  export declare function activeOptions(): ResolvedOptions;
4
6
  export declare function currentGeneration(): number;
package/lib/state.js CHANGED
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});var d=Object.defineProperty,o=r((e,t)=>d(e,"name",{value:t,configurable:!0}),"r");import{ulid as m}from"@lensmcp/core";import{defaultEventSink as p}from"./event-sink.js";import{performance as f}from"node:perf_hooks";import{threadId as l}from"node:worker_threads";let s,a=0;export function setActiveOptions(e){const t=e.emit??p(),c=`node:${process.pid}:${l}:${f.timeOrigin}`,n={projectName:e.projectName,sessionId:e.sessionId??process.env.LENSMCP_SESSION_ID??m(),emit:o(i=>t({...i,raw:{...i.raw,loopId:c}}),"emit"),trace:{requests:e.trace?.requests??!0,guards:e.trace?.guards??!0,controllers:e.trace?.controllers??!0,services:e.trace?.services??!0,db:e.trace?.db??!1,redis:e.trace?.redis??!1,queues:e.trace?.queues??!1,autoInstrumentMethods:e.trace?.autoInstrumentMethods??!1,suppressNoiseMethods:e.trace?.suppressNoiseMethods??!0},memory:{mode:e.memory?.mode??"off",scope:e.memory?.scope??"tagged"}};return s=n,a+=1,n}r(setActiveOptions,"setActiveOptions"),o(setActiveOptions,"setActiveOptions");export function activeOptions(){if(!s)throw new Error("@lensmcp/nest-instrumentation: LensmcpModule.forRoot(...) has not been registered. Did you forget to add it to your AppModule?");return s}r(activeOptions,"activeOptions"),o(activeOptions,"activeOptions");export function currentGeneration(){return a}r(currentGeneration,"currentGeneration"),o(currentGeneration,"currentGeneration");
1
+ "use strict";var u=Object.defineProperty;var s=(e,r)=>u(e,"name",{value:r,configurable:!0});var m=Object.defineProperty,n=s((e,r)=>m(e,"name",{value:r,configurable:!0}),"t");import{ulid as d}from"@lensmcp/core";import{defaultEventSink as p}from"./event-sink.js";import{performance as f}from"node:perf_hooks";import{threadId as l}from"node:worker_threads";let i,a=0;export function setActiveOptions(e){const r=e.emit??p(),o=`node:${process.pid}:${l}:${f.timeOrigin}`,t={projectName:e.projectName,sessionId:e.sessionId??process.env.LENSMCP_SESSION_ID??d(),emit:n(c=>r({...c,raw:{...c.raw,loopId:o}}),"emit"),trace:{requests:e.trace?.requests??!0,guards:e.trace?.guards??!0,controllers:e.trace?.controllers??!0,services:e.trace?.services??!0,db:e.trace?.db??!1,redis:e.trace?.redis??!1,queues:e.trace?.queues??!1,autoInstrumentMethods:e.trace?.autoInstrumentMethods??!1,suppressNoiseMethods:e.trace?.suppressNoiseMethods??!0,flowlessMinMs:flowlessMinMs(e.trace?.flowlessMinMs)},memory:{mode:e.memory?.mode??"off",scope:e.memory?.scope??"tagged"}};return i=t,a+=1,t}s(setActiveOptions,"setActiveOptions"),n(setActiveOptions,"setActiveOptions");export function flowlessMinMs(e,r=process.env){const o=r.LENSMCP_TRACE_FLOWLESS_MIN_MS,t=o!==void 0&&o.trim()!==""?Number(o):e;return t!==void 0&&Number.isFinite(t)&&t>=0?t:10}s(flowlessMinMs,"flowlessMinMs"),n(flowlessMinMs,"flowlessMinMs");export function activeOptions(){if(!i)throw new Error("@lensmcp/nest-instrumentation: LensmcpModule.forRoot(...) has not been registered. Did you forget to add it to your AppModule?");return i}s(activeOptions,"activeOptions"),n(activeOptions,"activeOptions");export function currentGeneration(){return a}s(currentGeneration,"currentGeneration"),n(currentGeneration,"currentGeneration");
@@ -1,3 +1,3 @@
1
- "use strict";var V=Object.defineProperty;var u=(t,e)=>V(t,"name",{value:e,configurable:!0});var ee=Object.defineProperty,a=u((t,e)=>ee(t,"name",{value:e,configurable:!0}),"d");import{__decorate as te}from"tslib";import{Injectable as se}from"@nestjs/common";import{Observable as G}from"rxjs";import{catchError as re,tap as oe}from"rxjs/operators";import{fingerprint as $,headlessFlowId as ne,headlessFlowsEnabled as ae,isProbePath as ie,LENS_REQUEST_ID_HEADER as de,requestFacts as ue,responseFacts as ce,shapeOf as z,ulid as h}from"@lensmcp/core";import{activeMemoryOptions as fe,onFlowStart as le,onFlowSettled as pe}from"@lensmcp/memory-tracker";import{LENSMCP_CONTEXT_STORAGE as me}from"./context.js";import{attachContextEnd as Ie}from"./context-end.js";import{activeOptions as q}from"./state.js";const ge=Symbol.for("lensmcp.exchangeId");let j=class{static{u(this,"S")}static{a(this,"TraceInterceptor")}intercept(t,e){const o=q();if(!o.trace.requests)return e.handle();const d=t.switchToHttp(),s=d.getRequest(),r=d.getResponse(),n=y(s.headers?.[de])??y(s.headers?.["x-request-id"])??h(),c=y(s.headers?.["x-lensmcp-flow-id"]),f=y(s.headers?.["x-internal-token"])!==void 0,I=c??(!f&&ae()&&!ie(s.url)?ne(n):void 0),i={sessionId:o.sessionId,requestId:n,parentRequestId:y(s.headers?.["x-lensmcp-parent-request-id"]),flowId:I,originNodeId:y(s.headers?.["x-lensmcp-origin-node-id"]),traceparent:y(s.headers?.traceparent),runId:h(),dbCallCount:0},m=s[ge],l=(s.method??"GET").toUpperCase(),p=s.route?.path??s.url??"<unknown>",O=t.getClass?.()?.name,D=t.getHandler?.()?.name,v=Date.now();I&&!c&&J(i,l,p,v);const M=o.memory.mode!=="off";M&&le(n);const _=a(()=>{if(!M)return;const g=fe()?.settleMs??1e3;setTimeout(()=>pe(n,i.flowId),g).unref?.()},"settleAndCheck"),W=Ie(i);let E;typeof r?.once=="function"&&ye()&&r.once("finish",()=>Q(o.projectName,i,l,p,s,r,E));let A=!1,T=typeof r?.once!="function";const H=a(()=>{A&&T&&W()},"endIfOver");if(!T){const g=a(()=>{T=!0,H()},"responseOut");r.once?.("finish",g),r.once?.("close",g)}return new G(g=>{let P;return me.run(i,()=>{P=e.handle().pipe(oe({next:a(w=>{E??={value:w},g.next(w)},"next"),complete:a(()=>{x(o.projectName,i,l,p,r.statusCode??200,v,void 0,m,k(r)),N(i,O,D,v,"ok"),_(),g.complete()},"complete")}),re(w=>{const F=w?.getResponse?.();F!==void 0&&(E??={value:F});const B=R(w);if(B!==void 0||typeof r.once!="function")x(o.projectName,i,l,p,B??S(r),v,w,m,k(r));else{let L=!1;const U=a(Y=>{if(L)return;L=!0;const Z=Y&&r.statusCode&&r.statusCode>=400?r.statusCode:S(r);x(o.projectName,i,l,p,Z,v,w,m,k(r))},"settle");r.once("finish",()=>U(!0)),r.once("close",()=>U(!1))}return N(i,O,D,v,"error"),_(),g.error(w),new G})).subscribe()}),()=>{P?.unsubscribe(),A=!0,H()}})}};j=te([se()],j);export{j as TraceInterceptor};const b=new Map,we=6e4,ye=a(()=>process.env.LENSMCP_HTTP_SHAPES!=="0","shapesOn");function Q(t,e,o,d,s,r,n){try{const c=r.statusCode??200,f=`${o} ${d} ${Math.floor(c/100)}`,I=Date.now();if(I-(b.get(f)??0)<we)return;b.set(f,I),b.size>2e3&&b.delete(b.keys().next().value);const i=s.headers??{},m=s.body!==void 0&&s.body!==null&&typeof s.body=="object"&&!Buffer.isBuffer(s.body)?z(s.body):void 0,l=n&&n.value!==void 0&&typeof n.value=="object"&&n.value!==null&&!Buffer.isBuffer(n.value)&&typeof n.value.pipe!="function"?z(n.value):void 0,p={id:h(),sessionId:e.sessionId,timestamp:I,source:"nestjs",category:"backend",severity:"info",context:{sessionId:e.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:$({kind:"http-exchange",identity:f}),title:`${o} ${d} \u2192 ${c} (its shape)`,raw:{kind:"http-exchange",method:o,route:d,status:c,request:{...ue(i,s.query,s.params),...m?{body:m}:{}},response:{...ce(r.getHeaders?.()??{},typeof i.origin=="string"?i.origin:void 0),...l?{body:l}:{}}}};C(p,t),q().emit(p)}catch{}}u(Q,"Ie"),a(Q,"recordExchange");function R(t){const e=t;if(typeof e?.getStatus=="function")return e.getStatus();if(typeof e?.status=="number"&&e.status>=100)return e.status;if(typeof e?.statusCode=="number"&&e.statusCode>=100)return e.statusCode}u(R,"U"),a(R,"ownStatus");const he=new Set(["TypeError","ReferenceError","SyntaxError","RangeError","EvalError","URIError"]);export function requestOutcome(t,e){return e instanceof Error&&he.has(e.name)&&R(e)===void 0?"fault":t<400?"ok":t<500?"refused":t===500?"fault":"declared"}u(requestOutcome,"requestOutcome"),a(requestOutcome,"requestOutcome");function X(t){return t==="ok"?"info":t==="refused"?"warning":"error"}u(X,"ye"),a(X,"severityOf");function S(t){return t.statusCode&&t.statusCode>=400?t.statusCode:500}u(S,"X"),a(S,"inferStatus");function y(t){if(typeof t=="string")return t;if(Array.isArray(t)&&typeof t[0]=="string")return t[0]}u(y,"w"),a(y,"stringHeader");function J(t,e,o,d){const s=q(),r=`request:${e} ${o}`,n={id:h(),sessionId:t.sessionId,timestamp:d,source:"nestjs",category:"backend",severity:"info",context:{sessionId:t.sessionId,flowId:t.flowId,requestId:t.requestId,originType:"request",originNodeId:r},fingerprint:$({kind:"flow-origin",identity:`${e}:${o}`}),title:`${e} ${o}`,raw:{kind:"flow-origin",originNodeId:r,method:e,route:o}};C(n,s.projectName),s.emit(n)}u(J,"ge"),a(J,"emitRequestOrigin");function N(t,e,o,d,s){if(!e||!o)return;const r=q(),n=Date.now()-d,c=`${e}.${o}`,f={id:h(),sessionId:t.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:s==="error"?"error":"info",context:{sessionId:t.sessionId,flowId:t.flowId,requestId:t.requestId,...t.runId?{runId:t.runId}:{}},fingerprint:$({kind:"method-call",identity:c}),title:`${c} (${n}ms)${s==="error"?" \u2717":""}`,raw:{kind:"span",span:{name:c,role:"controller",flowId:t.flowId,requestId:t.requestId,startTime:d,endTime:Date.now(),durationMs:n,status:s}}};C(f,q().projectName),r.emit(f)}u(N,"z"),a(N,"emitControllerSpan");function K(t){if(!(t instanceof Error))return{message:String(t)};const e=t.stack?t.stack.split(`
1
+ "use strict";var re=Object.defineProperty;var u=(t,e)=>re(t,"name",{value:e,configurable:!0});var se=Object.defineProperty,i=u((t,e)=>se(t,"name",{value:e,configurable:!0}),"a");import{__decorate as oe}from"tslib";import{Injectable as ne}from"@nestjs/common";import{Observable as z}from"rxjs";import{catchError as ae,tap as ie}from"rxjs/operators";import{fingerprint as $,headlessFlowId as Q,headlessFlowsEnabled as X,isProbePath as J,LENS_REQUEST_ID_HEADER as de,requestFacts as ue,responseFacts as ce,shapeOf as K,ulid as v}from"@lensmcp/core";import{activeMemoryOptions as fe,onFlowStart as le,onFlowSettled as pe}from"@lensmcp/memory-tracker";import{LENSMCP_CONTEXT_STORAGE as me}from"./context.js";import{attachContextEnd as Ie}from"./context-end.js";import{attachFlowStart as ge}from"./flow-start.js";import{activeOptions as q}from"./state.js";const we=Symbol.for("lensmcp.exchangeId");let T=class{static{u(this,"S")}static{i(this,"TraceInterceptor")}intercept(t,e){const o=q();if(!o.trace.requests)return e.handle();const d=t.switchToHttp(),r=d.getRequest(),s=d.getResponse(),n=h(r.headers?.[de])??h(r.headers?.["x-request-id"])??v(),c=h(r.headers?.["x-lensmcp-flow-id"]),f=h(r.headers?.["x-internal-token"])!==void 0,m=c??(!f&&X()&&!J(r.url)?Q(n):void 0),a={sessionId:o.sessionId,requestId:n,parentRequestId:h(r.headers?.["x-lensmcp-parent-request-id"]),flowId:m,originNodeId:h(r.headers?.["x-lensmcp-origin-node-id"]),traceparent:h(r.headers?.traceparent),runId:v(),dbCallCount:0},I=r[we],l=(r.method??"GET").toUpperCase(),p=r.route?.path??r.url??"<unknown>",D=t.getClass?.()?.name,M=t.getHandler?.()?.name,y=Date.now();m&&!c&&N(a,l,p,y),!m&&f&&X()&&!J(r.url)&&ge(a,()=>(a.flowId||(a.flowId=Q(n),N(a,l,p,y)),a.flowId));const _=o.memory.mode!=="off";_&&le(n);const A=i(()=>{if(!_)return;const g=fe()?.settleMs??1e3;setTimeout(()=>pe(n,a.flowId),g).unref?.()},"settleAndCheck"),W=Ie(a);let E;typeof s?.once=="function"&&ye()&&s.once("finish",()=>V(o.projectName,a,l,p,r,s,E));let H=!1,S=typeof s?.once!="function";const P=i(()=>{H&&S&&W()},"endIfOver");if(!S){const g=i(()=>{S=!0,P()},"responseOut");s.once?.("finish",g),s.once?.("close",g)}return new z(g=>{let F;return me.run(a,()=>{F=e.handle().pipe(ie({next:i(w=>{E??={value:w},g.next(w)},"next"),complete:i(()=>{x(o.projectName,a,l,p,s.statusCode??200,y,void 0,I,k(s)),O(a,D,M,y,"ok"),A(),g.complete()},"complete")}),ae(w=>{const B=w?.getResponse?.();B!==void 0&&(E??={value:B});const L=j(w);if(L!==void 0||typeof s.once!="function")x(o.projectName,a,l,p,L??R(s),y,w,I,k(s));else{let U=!1;const G=i(ee=>{if(U)return;U=!0;const te=ee&&s.statusCode&&s.statusCode>=400?s.statusCode:R(s);x(o.projectName,a,l,p,te,y,w,I,k(s))},"settle");s.once("finish",()=>G(!0)),s.once("close",()=>G(!1))}return O(a,D,M,y,"error"),A(),g.error(w),new z})).subscribe()}),()=>{F?.unsubscribe(),H=!0,P()}})}};T=oe([ne()],T);export{T as TraceInterceptor};const b=new Map,he=6e4,ye=i(()=>process.env.LENSMCP_HTTP_SHAPES!=="0","shapesOn");function V(t,e,o,d,r,s,n){try{const c=s.statusCode??200,f=`${o} ${d} ${Math.floor(c/100)}`,m=Date.now();if(m-(b.get(f)??0)<he)return;b.set(f,m),b.size>2e3&&b.delete(b.keys().next().value);const a=r.headers??{},I=r.body!==void 0&&r.body!==null&&typeof r.body=="object"&&!Buffer.isBuffer(r.body)?K(r.body):void 0,l=n&&n.value!==void 0&&typeof n.value=="object"&&n.value!==null&&!Buffer.isBuffer(n.value)&&typeof n.value.pipe!="function"?K(n.value):void 0,p={id:v(),sessionId:e.sessionId,timestamp:m,source:"nestjs",category:"backend",severity:"info",context:{sessionId:e.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:$({kind:"http-exchange",identity:f}),title:`${o} ${d} \u2192 ${c} (its shape)`,raw:{kind:"http-exchange",method:o,route:d,status:c,request:{...ue(a,r.query,r.params),...I?{body:I}:{}},response:{...ce(s.getHeaders?.()??{},typeof a.origin=="string"?a.origin:void 0),...l?{body:l}:{}}}};C(p,t),q().emit(p)}catch{}}u(V,"ye"),i(V,"recordExchange");function j(t){const e=t;if(typeof e?.getStatus=="function")return e.getStatus();if(typeof e?.status=="number"&&e.status>=100)return e.status;if(typeof e?.statusCode=="number"&&e.statusCode>=100)return e.statusCode}u(j,"Q"),i(j,"ownStatus");const ve=new Set(["TypeError","ReferenceError","SyntaxError","RangeError","EvalError","URIError"]);export function requestOutcome(t,e){return e instanceof Error&&ve.has(e.name)&&j(e)===void 0?"fault":t<400?"ok":t<500?"refused":t===500?"fault":"declared"}u(requestOutcome,"requestOutcome"),i(requestOutcome,"requestOutcome");function Y(t){return t==="ok"?"info":t==="refused"?"warning":"error"}u(Y,"we"),i(Y,"severityOf");function R(t){return t.statusCode&&t.statusCode>=400?t.statusCode:500}u(R,"V"),i(R,"inferStatus");function h(t){if(typeof t=="string")return t;if(Array.isArray(t)&&typeof t[0]=="string")return t[0]}u(h,"h"),i(h,"stringHeader");function N(t,e,o,d){const r=q(),s=`request:${e} ${o}`,n={id:v(),sessionId:t.sessionId,timestamp:d,source:"nestjs",category:"backend",severity:"info",context:{sessionId:t.sessionId,flowId:t.flowId,requestId:t.requestId,originType:"request",originNodeId:s},fingerprint:$({kind:"flow-origin",identity:`${e}:${o}`}),title:`${e} ${o}`,raw:{kind:"flow-origin",originNodeId:s,method:e,route:o}};C(n,r.projectName),r.emit(n)}u(N,"Y"),i(N,"emitRequestOrigin");function O(t,e,o,d,r){if(!e||!o)return;const s=q(),n=Date.now()-d,c=`${e}.${o}`,f={id:v(),sessionId:t.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:r==="error"?"error":"info",context:{sessionId:t.sessionId,flowId:t.flowId,requestId:t.requestId,...t.runId?{runId:t.runId}:{}},fingerprint:$({kind:"method-call",identity:c}),title:`${c} (${n}ms)${r==="error"?" \u2717":""}`,raw:{kind:"span",span:{name:c,role:"controller",flowId:t.flowId,requestId:t.requestId,startTime:d,endTime:Date.now(),durationMs:n,status:r}}};C(f,q().projectName),s.emit(f)}u(O,"J"),i(O,"emitControllerSpan");function Z(t){if(!(t instanceof Error))return{message:String(t)};const e=t.stack?t.stack.split(`
2
2
  `).slice(0,30).join(`
3
- `):void 0;return{...t.name?{name:t.name}:{},message:t.message,...e?{stack:e}:{}}}u(K,"we"),a(K,"errorDetail");function C(t,e){if(!e)return;const o=t.raw;o&&o.project===void 0&&(o.project=e)}u(C,"b"),a(C,"stampProject");function k(t){try{const e=t.getHeaders?.()["content-type"];return typeof e=="string"?e.split(";")[0]?.trim().toLowerCase():void 0}catch{return}}u(k,"D"),a(k,"responseContentType");function x(t,e,o,d,s,r,n,c,f){const I=q(),i=Date.now()-r,m=requestOutcome(s,n),l={id:h(),sessionId:e.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:X(m),context:{sessionId:e.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},originNodeId:e.originNodeId,traceparent:e.traceparent},fingerprint:$({kind:"server-request",identity:`${o}:${d}:${Math.floor(s/100)}xx`}),title:`${o} ${d} \u2192 ${s} (${i}ms)`,message:n instanceof Error?n.message:void 0,raw:{kind:"server-request",request:{method:o,route:d,status:s,outcome:m,durationMs:i,startedAt:r,...f?{contentType:f,streaming:f==="text/event-stream"}:{},endedAt:Date.now(),flowId:e.flowId,requestId:e.requestId,...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...typeof c=="string"?{exchangeId:c}:{},...e.runId?{runId:e.runId}:{},...e.dbCallCount||e.redisCallCount?{calls:{...e.dbCallCount?{db:e.dbCallCount}:{},...e.redisCallCount?{cache:e.redisCallCount}:{}}}:{},...n?{error:K(n)}:{}}}},p={...l,id:h(),raw:{kind:"span",span:{name:`nest.request ${o} ${d}`,traceId:e.traceparent,flowId:e.flowId,requestId:e.requestId,startTime:r,endTime:Date.now(),durationMs:i,status:m==="ok"?"ok":"error"}}};C(l,t),C(p,t),I.emit(l),I.emit(p)}u(x,"O"),a(x,"emitRequest");
3
+ `):void 0;return{...t.name?{name:t.name}:{},message:t.message,...e?{stack:e}:{}}}u(Z,"he"),i(Z,"errorDetail");function C(t,e){if(!e)return;const o=t.raw;o&&o.project===void 0&&(o.project=e)}u(C,"b"),i(C,"stampProject");function k(t){try{const e=t.getHeaders?.()["content-type"];return typeof e=="string"?e.split(";")[0]?.trim().toLowerCase():void 0}catch{return}}u(k,"D"),i(k,"responseContentType");function x(t,e,o,d,r,s,n,c,f){const m=q(),a=Date.now()-s,I=requestOutcome(r,n),l={id:v(),sessionId:e.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:Y(I),context:{sessionId:e.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},originNodeId:e.originNodeId,traceparent:e.traceparent},fingerprint:$({kind:"server-request",identity:`${o}:${d}:${Math.floor(r/100)}xx`}),title:`${o} ${d} \u2192 ${r} (${a}ms)`,message:n instanceof Error?n.message:void 0,raw:{kind:"server-request",request:{method:o,route:d,status:r,outcome:I,durationMs:a,startedAt:s,...f?{contentType:f,streaming:f==="text/event-stream"}:{},endedAt:Date.now(),flowId:e.flowId,requestId:e.requestId,...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...typeof c=="string"?{exchangeId:c}:{},...e.runId?{runId:e.runId}:{},...e.dbCallCount||e.redisCallCount?{calls:{...e.dbCallCount?{db:e.dbCallCount}:{},...e.redisCallCount?{cache:e.redisCallCount}:{}}}:{},...n?{error:Z(n)}:{}}}},p={...l,id:v(),raw:{kind:"span",span:{name:`nest.request ${o} ${d}`,traceId:e.traceparent,flowId:e.flowId,requestId:e.requestId,startTime:s,endTime:Date.now(),durationMs:a,status:I==="ok"?"ok":"error"}}};C(l,t),C(p,t),m.emit(l),m.emit(p)}u(x,"O"),i(x,"emitRequest");
@@ -1 +1 @@
1
- "use strict";var i=Object.defineProperty;var n=(r,e)=>i(r,"name",{value:e,configurable:!0});var s=Object.defineProperty,p=n((r,e)=>s(r,"name",{value:e,configurable:!0}),"u");import{traceWrap as m}from"./instrument.js";export function TraceMethod(r){const e=typeof r=="string"?{name:r}:r??{};return function(c,u,t){const a=t.value;if(typeof a!="function")return t;const o=c.constructor?.name??"Anonymous",f=e.name??`${o}.${String(u)}`;return t.value=m(a,f,{db:e.db,...e.capture?{capture:e.capture}:{}}),t}}n(TraceMethod,"TraceMethod"),p(TraceMethod,"TraceMethod");
1
+ "use strict";var p=Object.defineProperty;var n=(e,t)=>p(e,"name",{value:t,configurable:!0});var f=Object.defineProperty,s=n((e,t)=>f(e,"name",{value:t,configurable:!0}),"a");import{traceWrap as m}from"./instrument.js";export function TraceMethod(e){const t=typeof e=="string"?{name:e}:e??{};return function(c,u,r){const a=r.value;if(typeof a!="function")return r;const o=c.constructor?.name??"Anonymous",i=t.name??`${o}.${String(u)}`;return r.value=m(a,i,{db:t.db,explicit:!0,...t.capture?{capture:t.capture}:{}}),r}}n(TraceMethod,"TraceMethod"),s(TraceMethod,"TraceMethod");
package/lib/types.d.ts CHANGED
@@ -42,6 +42,14 @@ export interface LensmcpModuleOptions {
42
42
  * Default: true. Set false to trace absolutely everything.
43
43
  */
44
44
  suppressNoiseMethods?: boolean;
45
+ /**
46
+ * A traced call outside any request, flow or job run that returned in under this many ms emits no span (default
47
+ * 10; `LENSMCP_TRACE_FLOWLESS_MIN_MS` overrides it, `0` keeps them all). No reader asks for one: on FoodGuard
48
+ * (2026-10-29) a queue's poll loop and 0 ms getters made 69% of all spans, none in a flow, and at the event
49
+ * history's size cap they pushed everything else out after 2.5 days. A call that threw, a `@TraceMethod`, a
50
+ * captured method, and every call in a flow still emit theirs; the loop (N+1) check runs either way.
51
+ */
52
+ flowlessMinMs?: number;
45
53
  };
46
54
  /** Memory tracking (Phase 7). */
47
55
  memory?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/nest-instrumentation",
3
- "version": "1.25.32",
3
+ "version": "1.25.34",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -14,9 +14,9 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@lensmcp/core": "1.25.32",
18
- "@lensmcp/memory-tracker": "1.25.32",
19
- "@lensmcp/protocol-types": "1.25.32",
17
+ "@lensmcp/core": "1.25.34",
18
+ "@lensmcp/memory-tracker": "1.25.34",
19
+ "@lensmcp/protocol-types": "1.25.34",
20
20
  "tslib": "^2.3.0"
21
21
  },
22
22
  "peerDependencies": {