@lensmcp/node-instrumentation 1.21.4 → 1.22.0
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.
- package/lib/flood-guard.d.ts +5 -25
- package/lib/flood-guard.js +1 -1
- package/lib/flow-context.d.ts +52 -1
- package/lib/flow-context.js +1 -1
- package/lib/tap-bullmq.js +1 -1
- package/package.json +4 -3
package/lib/flood-guard.d.ts
CHANGED
|
@@ -1,27 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* of lines/sec into events.jsonl: the 2026-07-28 incident sustained ~680 ev/s,
|
|
7
|
-
* grew the bus file to 254 MB in two hours, and OOM-crash-looped every consumer
|
|
8
|
-
* that ingests it. The guard keeps the SIGNAL (the first burst, then a trickle
|
|
9
|
-
* with a `floodSuppressed` count) while dropping the redundant repetition.
|
|
2
|
+
* Moved to `@lensmcp/protocol-types` so the Nest sink, the gateway, the bridge and
|
|
3
|
+
* the browser runtime all bucket against the SAME implementation — four divergent
|
|
4
|
+
* copies of the write contract is how a writer ended up with no guard at all.
|
|
5
|
+
* Re-exported here so existing import sites are unchanged.
|
|
10
6
|
*/
|
|
11
|
-
export
|
|
12
|
-
ok: boolean;
|
|
13
|
-
/** Events dropped for this fingerprint since the last admitted one (0 normally). */
|
|
14
|
-
suppressed: number;
|
|
15
|
-
}
|
|
16
|
-
export interface FloodGuard {
|
|
17
|
-
admit(fingerprint: string): FloodAdmit;
|
|
18
|
-
}
|
|
19
|
-
export interface FloodGuardOptions {
|
|
20
|
-
/** Instantaneous burst allowance per fingerprint (bucket capacity). Default 30. */
|
|
21
|
-
burst?: number;
|
|
22
|
-
/** Sustained events/sec per fingerprint (bucket refill). Default 5. */
|
|
23
|
-
perSec?: number;
|
|
24
|
-
/** Max distinct fingerprints tracked before stale buckets are evicted. Default 2048. */
|
|
25
|
-
maxKeys?: number;
|
|
26
|
-
}
|
|
27
|
-
export declare function createEventFloodGuard(opts?: FloodGuardOptions): FloodGuard;
|
|
7
|
+
export { createEventFloodGuard, type FloodAdmit, type FloodGuard, type FloodGuardOptions, } from '@lensmcp/protocol-types';
|
package/lib/flood-guard.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var n=Object.defineProperty;var a=(r,t)=>n(r,"name",{value:t,configurable:!0});var o=Object.defineProperty,e=a((r,t)=>o(r,"name",{value:t,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEventFloodGuard=void 0;var protocol_types_1=require("@lensmcp/protocol-types");Object.defineProperty(exports,"createEventFloodGuard",{enumerable:!0,get:e(function(){return protocol_types_1.createEventFloodGuard},"get")});
|
package/lib/flow-context.d.ts
CHANGED
|
@@ -28,7 +28,58 @@ interface LiveCtx {
|
|
|
28
28
|
redisCallCount?: number;
|
|
29
29
|
externalCallCount?: number;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
type CtxGetter = () => LiveCtx | undefined;
|
|
32
|
+
type CtxRunner = <T>(ctx: {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
requestId: string;
|
|
35
|
+
flowId?: string;
|
|
36
|
+
}, fn: () => T) => T;
|
|
37
|
+
/**
|
|
38
|
+
* Why the failure has to be told apart from the package being absent (issues/codex-security/46).
|
|
39
|
+
*
|
|
40
|
+
* `require(esm)` only works from Node 20.19 (and 22.12); before that it throws `ERR_REQUIRE_ESM`.
|
|
41
|
+
* The catch here said *"nest instrumentation absent"*, which on Node 18 was simply untrue — the
|
|
42
|
+
* package was installed and working, and every database, Redis, egress and BullMQ event silently
|
|
43
|
+
* lost its request/flow correlation while the application ran normally. Nothing logged, nothing
|
|
44
|
+
* threw, and the lens was just quietly less useful. The declared engine floor said `>=18`, so the
|
|
45
|
+
* runtime that broke it was one we claimed to support.
|
|
46
|
+
*
|
|
47
|
+
* The floor is now `>=20.19` (see this package's `engines`), and this reports rather than swallows,
|
|
48
|
+
* because an install that predates the floor must not look like an install that lacks the package.
|
|
49
|
+
* The same shape as issues/full-review/13 — a catch hiding the case it was not written for.
|
|
50
|
+
*/
|
|
51
|
+
export type FlowContextStatus = {
|
|
52
|
+
state: 'ready';
|
|
53
|
+
} | {
|
|
54
|
+
state: 'absent';
|
|
55
|
+
} | {
|
|
56
|
+
state: 'runtime-too-old';
|
|
57
|
+
detail: string;
|
|
58
|
+
} | {
|
|
59
|
+
state: 'failed';
|
|
60
|
+
detail: string;
|
|
61
|
+
} | {
|
|
62
|
+
state: 'not-primed';
|
|
63
|
+
};
|
|
64
|
+
/** What happened the last time `primeFlowContext` ran. */
|
|
65
|
+
export declare function flowContextStatus(): FlowContextStatus;
|
|
66
|
+
/** Test seam — forget both the resolved getters and the once-only warning. */
|
|
67
|
+
export declare function resetFlowContextForTests(): void;
|
|
68
|
+
/** `require(esm)` before Node 20.19/22.12. Node reports it on the error's `code`. */
|
|
69
|
+
export declare function isRequireEsmError(err: unknown): boolean;
|
|
70
|
+
/** The package genuinely is not installed — a legitimate, silent, supported configuration. */
|
|
71
|
+
export declare function isModuleNotFoundError(err: unknown): boolean;
|
|
72
|
+
interface NestContextModule {
|
|
73
|
+
currentLensmcpContext: CtxGetter;
|
|
74
|
+
runInLensmcpContext: CtxRunner;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `load` exists so the CLASSIFICATION above can be tested against each failure it distinguishes.
|
|
78
|
+
* Nothing in production passes it: intercepting the real `require` from a test would mean patching
|
|
79
|
+
* `Module.prototype.require`, which the bundler's own injected require does not go through — a test
|
|
80
|
+
* that reaches nothing, which is the shape this round exists to stop writing.
|
|
81
|
+
*/
|
|
82
|
+
export declare function primeFlowContext(load?: () => NestContextModule): void;
|
|
32
83
|
/**
|
|
33
84
|
* Run `fn` inside a fresh flow context (queue workers, timers — async
|
|
34
85
|
* entry points that aren't HTTP requests). Inner taps then inherit the
|
package/lib/flow-context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var o=(e,t)=>u(e,"name",{value:t,configurable:!0});var s=Object.defineProperty,n=o((e,t)=>s(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.flowContextStatus=flowContextStatus,exports.resetFlowContextForTests=resetFlowContextForTests,exports.isRequireEsmError=isRequireEsmError,exports.isModuleNotFoundError=isModuleNotFoundError,exports.primeFlowContext=primeFlowContext,exports.runInFlowContext=runInFlowContext,exports.liveFlowContext=liveFlowContext,exports.flowContext=flowContext;let getCtx=null,runWith=null,status={state:"not-primed"},warned=!1;function flowContextStatus(){return status}o(flowContextStatus,"flowContextStatus"),n(flowContextStatus,"flowContextStatus");function resetFlowContextForTests(){getCtx=null,runWith=null,status={state:"not-primed"},warned=!1}o(resetFlowContextForTests,"resetFlowContextForTests"),n(resetFlowContextForTests,"resetFlowContextForTests");function isRequireEsmError(e){const t=e?.code;return t==="ERR_REQUIRE_ESM"||t==="ERR_REQUIRE_ASYNC_MODULE"}o(isRequireEsmError,"isRequireEsmError"),n(isRequireEsmError,"isRequireEsmError");function isModuleNotFoundError(e){const t=e;return t?.code!=="MODULE_NOT_FOUND"&&t?.code!=="ERR_MODULE_NOT_FOUND"?!1:String(t?.message??"").includes("@lensmcp/nest-instrumentation")}o(isModuleNotFoundError,"isModuleNotFoundError"),n(isModuleNotFoundError,"isModuleNotFoundError");function requireNestInstrumentation(){return require("@lensmcp/nest-instrumentation")}o(requireNestInstrumentation,"requireNestInstrumentation"),n(requireNestInstrumentation,"requireNestInstrumentation");function primeFlowContext(e=requireNestInstrumentation){try{const t=e();getCtx=t.currentLensmcpContext,runWith=t.runInLensmcpContext,status={state:"ready"}}catch(t){if(getCtx=null,runWith=null,isModuleNotFoundError(t)){status={state:"absent"};return}const r=t instanceof Error?t.message:String(t);if(status=isRequireEsmError(t)?{state:"runtime-too-old",detail:r}:{state:"failed",detail:r},!warned){warned=!0;const i=status.state==="runtime-too-old"?`this Node (${process.version}) cannot require an ESM package; @lensmcp/node-instrumentation needs >=20.19`:r;console.warn(`[lensmcp] flow correlation is OFF \u2014 could not load @lensmcp/nest-instrumentation: ${i}`)}}}o(primeFlowContext,"primeFlowContext"),n(primeFlowContext,"primeFlowContext");function runInFlowContext(e,t){if(!runWith)return t();try{return runWith({sessionId:"pending",requestId:e.requestId,...e.flowId?{flowId:e.flowId}:{}},t)}catch{return t()}}o(runInFlowContext,"runInFlowContext"),n(runInFlowContext,"runInFlowContext");function liveFlowContext(){try{return getCtx?.()}catch{return}}o(liveFlowContext,"liveFlowContext"),n(liveFlowContext,"liveFlowContext");function flowContext(){const e=liveFlowContext();if(e)return{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}}}o(flowContext,"flowContext"),n(flowContext,"flowContext");
|
package/lib/tap-bullmq.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var b=Object.defineProperty;var
|
|
1
|
+
"use strict";var b=Object.defineProperty;var f=(n,e)=>b(n,"name",{value:e,configurable:!0});var w=Object.defineProperty,l=f((n,e)=>w(n,"name",{value:e,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.bullmqTap=bullmqTap;const active_work_1=require("./active-work"),workerGeneration=new WeakMap,emit_1=require("./emit"),flow_context_1=require("./flow-context");function jobFlow(n){const e=n?.opts?.__lensmcpFlow;return e&&typeof e=="object"?e:void 0}f(jobFlow,"jobFlow"),l(jobFlow,"jobFlow");const jobLegId=l((n,e)=>`job:${n}#${e?.id??"?"}`,"jobLegId"),publishers=new Map;function bullmqTap(n){const e=n;if(typeof e?.Queue!="function"||typeof e?.Worker!="function")return!1;try{tapQueueAdd(e)}catch{}try{tapWorkerEmit(e)}catch{}try{tapWorkerProcess(e)}catch{}try{tapWorkerConstruct(e)}catch{}return!0}f(bullmqTap,"bullmqTap"),l(bullmqTap,"bullmqTap");function tapWorkerConstruct(n){const e=n.Worker;if(typeof e!="function"||e.__lensmcpGen)return;const u=new Proxy(e,{construct(r,t,s){const a=Reflect.construct(r,t,s);try{workerGeneration.set(a,(0,active_work_1.activeWorkGeneration)())}catch{}return a}});Object.defineProperty(e,"__lensmcpGen",{value:!0,enumerable:!1}),n.Worker=u}f(tapWorkerConstruct,"tapWorkerConstruct"),l(tapWorkerConstruct,"tapWorkerConstruct");function tapWorkerProcess(n){const e=n.Worker?.prototype,u=e?.callProcessJob;if(!e||typeof u!="function"||u.__lensmcp)return;const r=l(async function(t,...s){const a=workerGeneration.get(this),i=(0,active_work_1.beginActiveWork)(a),c=jobFlow(t);if(!c?.flowId)return u.call(this,t,...s).finally(i);const o=String(this?.name??t?.queueName??"queue"),m=jobLegId(o,t),d=Date.now(),p={flowId:c.flowId,requestId:m};return(0,flow_context_1.runInFlowContext)(p,async()=>{try{const q=await u.call(this,t,...s);return(0,emit_1.lensEmit)("queue","info",`process ${t?.name}#${t?.id} (${Date.now()-d}ms)`,{queue:o,kind:"queue-process",job:t?.name,jobId:t?.id,startedAt:d,durationMs:Date.now()-d,status:"ok"},`queue:${o}`,p),q}catch(q){throw(0,emit_1.lensEmit)("queue","error",`process ${t?.name}#${t?.id} failed (${Date.now()-d}ms)`,{queue:o,kind:"queue-process",job:t?.name,jobId:t?.id,startedAt:d,durationMs:Date.now()-d,status:"error",error:String(q?.message??q)},`queue:${o}`,p),q}finally{i()}})},"wrapped");r.__lensmcp=!0,e.callProcessJob=r}f(tapWorkerProcess,"tapWorkerProcess"),l(tapWorkerProcess,"tapWorkerProcess");function tapQueueAdd(n){const e=n.Queue?.prototype,u=e?.add;if(!e||typeof u!="function"||u.__lensmcp)return;const r=l(async function(t,...s){const a=Date.now(),i=(0,flow_context_1.flowContext)();if(i?.flowId){const o=s[1]??{};s[1]={...o,__lensmcpFlow:{flowId:i.flowId,...i.requestId?{originRequestId:i.requestId}:{}}}}const c=await u.call(this,t,...s);try{const o=String(this?.name??"queue"),m=s[0];let d;try{const p=JSON.stringify(m);d=p&&p!=="undefined"?p.length>300?p.slice(0,299)+"\u2026":p:void 0}catch{}(0,emit_1.lensEmit)("queue","info",`enqueued ${t}#${c?.id} \u2192 ${o}`,{queue:o,kind:"queue-enqueued",job:t,jobId:c?.id,...d?{data:d}:{},startedAt:a,durationMs:Date.now()-a},`queue:${o}`,i),ensureStatePublisher(o,this)}catch{}return c},"wrapped");r.__lensmcp=!0,e.add=r}f(tapQueueAdd,"tapQueueAdd"),l(tapQueueAdd,"tapQueueAdd");function tapWorkerEmit(n){const e=n.Worker?.prototype,u=e?.emit;if(!e||typeof u!="function"||u.__lensmcp)return;const r=l(function(t,...s){try{observeWorkerEvent(this,String(t),s)}catch{}return u.call(this,t,...s)},"wrapped");r.__lensmcp=!0,e.emit=r}f(tapWorkerEmit,"tapWorkerEmit"),l(tapWorkerEmit,"tapWorkerEmit");function observeWorkerEvent(n,e,u){if(e!=="active"&&e!=="completed"&&e!=="failed")return;const r=String(n.name??"queue"),t=u[0],s=jobFlow(t),a=s?.flowId?{flowId:s.flowId,requestId:jobLegId(r,t)}:void 0;if(e==="active")(0,emit_1.lensEmit)("queue","info",`processing ${t?.name}#${t?.id}`,{queue:r,kind:"queue-active",jobId:t?.id},`queue:${r}`,a);else if(e==="completed"){let i;try{const c=t?.returnvalue??u[1],o=JSON.stringify(c);i=o&&o!=="undefined"?o.length>160?o.slice(0,159)+"\u2026":o:void 0}catch{}(0,emit_1.lensEmit)("queue","info",`completed ${t?.name}#${t?.id}`,{queue:r,kind:"queue-completed",jobId:t?.id,...i?{returned:i}:{}},`queue:${r}`,a)}else{const i=u[1];(0,emit_1.lensEmit)("queue","error",`job ${t?.id} failed: ${i?.message}`,{queue:r,kind:"queue-failed",jobId:t?.id,error:String(i?.message??i)},`queue:${r}`,a)}if(!publishers.has(r))try{const i=n.opts,c=require("bullmq").Queue;ensureStatePublisher(r,new c(r,{connection:i?.connection}))}catch{}}f(observeWorkerEvent,"observeWorkerEvent"),l(observeWorkerEvent,"observeWorkerEvent");function ensureStatePublisher(n,e){if(publishers.has(n))return;let u="";const r=setInterval(()=>{(async()=>{try{const t=await e.getJobCounts(),s=[];for(const c of["active","waiting","failed","completed","delayed"])for(const o of await e.getJobs([c],0,7)){const m=o?.data?.to;s.push({id:String(o.id),name:o.name,state:c,timestamp:o.timestamp??0,...typeof m=="string"?{to:m}:{}})}s.sort((c,o)=>Number(o.timestamp)-Number(c.timestamp));const a=s.slice(0,8),i=JSON.stringify([t,a.map(c=>`${c.id}${c.state}`)]);if(i===u)return;u=i,(0,emit_1.lensEmit)("queue","info",`queue state: ${t.active??0} active \xB7 ${t.waiting??0} waiting \xB7 ${t.failed??0} failed`,{queue:n,kind:"queue-state",counts:t,recent:a},`queue:${n}`)}catch{}})()},2e3);r.unref?.(),publishers.set(n,r)}f(ensureStatePublisher,"ensureStatePublisher"),l(ensureStatePublisher,"ensureStatePublisher");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/node-instrumentation",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"types": "./index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@lensmcp/nest-instrumentation": "1.
|
|
18
|
+
"@lensmcp/nest-instrumentation": "1.22.0",
|
|
19
|
+
"@lensmcp/protocol-types": "1.22.0",
|
|
19
20
|
"tslib": "^2.3.0"
|
|
20
21
|
},
|
|
21
22
|
"optionalDependencies": {
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
"url": "https://github.com/kiwiapps-ltd/lensmcp/issues"
|
|
29
30
|
},
|
|
30
31
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
32
|
+
"node": ">=20.19"
|
|
32
33
|
},
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|