@lensmcp/core 1.25.42 → 1.25.43

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.
@@ -17,8 +17,14 @@ import { type LensmcpThresholds } from './thresholds.js';
17
17
  * server reloaded (`cause: 'hmr'`) is left out, and a hard load is timed from the app's entry (`appReadyMs`), not from
18
18
  * the dev server serving it.
19
19
  */
20
+ /**
21
+ * A service's record of the statement runs its flood guard kept off the bus (node-instrumentation's `sql-drops.ts`,
22
+ * the same name there): per statement, how many, how many slow (at or over the record's `slowMs`), how many failed, the
23
+ * slowest. A window counts them as runs it knows of but did not time (issues/closed/foodguard-141).
24
+ */
25
+ export declare const SQL_DROPS_KIND = "db-dropped";
20
26
  /** The history kinds the window reads — what a caller asks `EventStore.scan` for. */
21
- export declare const PERFORMANCE_EVENT_KINDS: readonly ["server-request", "db-query", "loop", "page-visit"];
27
+ export declare const PERFORMANCE_EVENT_KINDS: readonly ["server-request", "db-query", "db-dropped", "loop", "page-visit"];
22
28
  /** The limits a window is judged by: the workspace's lens thresholds, for the kinds measured here. */
23
29
  export type PerformanceLimits = Pick<LensmcpThresholds, 'slowRouteMs' | 'slowQueryMs' | 'nPlusOne' | 'dbInLoop' | 'slowPageMs'>;
24
30
  /**
@@ -64,6 +70,8 @@ export interface SqlStat extends PerformanceTiming {
64
70
  /** The statement's text, its first 300 characters. */
65
71
  text?: string;
66
72
  failed: number;
73
+ /** Runs known only from the service's drop record — counted in `count`, `slow`, `failed` and `maxMs`, never timed. */
74
+ dropped?: number;
67
75
  }
68
76
  export interface PageStat extends PerformanceTiming {
69
77
  /** `<app> <pattern or path>`. */
@@ -99,6 +107,15 @@ export interface PerformanceReport {
99
107
  verdict: 'pass' | 'fail' | 'unmeasured';
100
108
  breaches: PerformanceBreach[];
101
109
  waived: PerformanceBreach[];
110
+ /**
111
+ * Statement runs the services' flood guards kept off the bus, from their drop records: counted, never timed. A pass
112
+ * over a window with dropped runs says so — its percentiles are of the runs that reached the bus.
113
+ */
114
+ dropped?: {
115
+ runs: number;
116
+ slow: number;
117
+ failed: number;
118
+ };
102
119
  /** Slowest p95 first. */
103
120
  routes: RouteStat[];
104
121
  sql: SqlStat[];
@@ -114,6 +131,7 @@ export declare class PerformanceWindow {
114
131
  private to;
115
132
  private readonly routes;
116
133
  private readonly sql;
134
+ private readonly drops;
117
135
  private readonly pages;
118
136
  /** Per open request: its statements' runs, until its `server-request` closes it. */
119
137
  private readonly open;
@@ -125,7 +143,16 @@ export declare class PerformanceWindow {
125
143
  constructor(limits: PerformanceLimits, waivers?: readonly PerformanceWaiver[]);
126
144
  add(event: BaseEvent): void;
127
145
  private request;
146
+ /** The statement `key`'s entry, made from what `raw` says of it; undefined past the cap. */
147
+ private sqlEntry;
128
148
  private statement;
149
+ /**
150
+ * A service's drop record: each statement's dropped runs are counted — runs, failures, the slowest — but not timed. Its
151
+ * slow runs are slow by the window's limit when that is at or under the record's `slowMs`; the slowest run says at
152
+ * least one was when it is at or over the limit. A floor either way: a run between a lower limit and `slowMs` is not
153
+ * told apart.
154
+ */
155
+ private dropped;
129
156
  /** A request ended (or the window did): its repeated statements become N+1 records. */
130
157
  private close;
131
158
  private loop;
@@ -1,2 +1,2 @@
1
- "use strict";var O=Object.defineProperty;var f=(s,t)=>O(s,"name",{value:t,configurable:!0});var I=Object.defineProperty,c=f((s,t)=>I(s,"name",{value:t,configurable:!0}),"p");import{resolveThresholds as R}from"./thresholds.js";export const PERFORMANCE_EVENT_KINDS=["server-request","db-query","loop","page-visit"];export function performanceLimits(...s){let t={};for(const r of s)r&&(t={...t,...k(r)});const e=R(t);return{slowRouteMs:e.slowRouteMs,slowQueryMs:e.slowQueryMs,nPlusOne:e.nPlusOne,dbInLoop:e.dbInLoop,slowPageMs:e.slowPageMs}}f(performanceLimits,"performanceLimits"),c(performanceLimits,"performanceLimits");function k(s){const t={};for(const[e,r]of Object.entries(s))typeof r=="number"&&Number.isFinite(r)&&r>=0&&(t[e]=r);return t}f(k,"P"),c(k,"resolveThresholdsOnly");const $=c(s=>s&&typeof s=="object"?s:{},"obj"),m=c(s=>typeof s=="string"&&s?s:void 0,"str"),w=c(s=>typeof s=="number"&&Number.isFinite(s)?s:void 0,"num"),L=2e3,y=5e3,P=2e4,x=c(()=>({count:0,times:[],slow:0,max:0,evidence:[]}),"newTiming");function q(s,t,e,r){if(s.max=Math.max(s.max,t),t>=e&&(s.slow+=1,s.evidence.length<5&&s.evidence.push(r)),s.times.length<L)s.times.push(t);else{let n=0;for(let a=1;a<s.times.length;a++)s.times[a]<s.times[n]&&(n=a);t>s.times[n]&&(s.times[n]=t)}}f(q,"q"),c(q,"time");function j(s,t){return s.length?s[Math.min(s.length-1,Math.floor(s.length*t))]:0}f(j,"k"),c(j,"percentile");function M(s){const t=[...s.times].sort((e,r)=>e-r);return{count:s.count,timed:s.times.length,p50Ms:Math.round(j(t,.5)),p95Ms:Math.round(j(t,.95)),maxMs:Math.round(s.max),slow:s.slow}}f(M,"v"),c(M,"timing");function v(s,t,e){return s.includes("*")?new RegExp(`^${s.split("*").map(r=>r.replace(/[.+?^${}()|[\]\\]/g,"\\$&")).join(".*")}$`).test(t):e?t===s:t.includes(s)}f(v,"w"),c(v,"matches");export class PerformanceWindow{static{f(this,"PerformanceWindow")}static{c(this,"PerformanceWindow")}constructor(t,e=[]){this.events=0,this.from=1/0,this.to=0,this.routes=new Map,this.sql=new Map,this.pages=new Map,this.open=new Map,this.routeOf=new Map,this.repeated=new Map,this.loops=[],this.limits=t,this.waivers=e.filter(r=>typeof r?.why=="string"&&(r.route||r.sql||r.page))}add(t){const e=$(t.raw),r=e.kind;if(r!=="server-request"&&r!=="db-query"&&r!=="loop"&&r!=="page-visit")return;this.events+=1,this.from=Math.min(this.from,t.timestamp),this.to=Math.max(this.to,t.timestamp);const n=$(t.context),a=m(n.projectName)??m(e.project),l=m(n.requestId);r==="server-request"?this.request(t,e,a,l):r==="db-query"?this.statement(t,e,a,l):r==="loop"?this.loop(t,e,l):this.page(t,e)}request(t,e,r,n){const a=$(e.request),l=m(a.method)??"?",p=m(a.route)??"?",u=`${l} ${p}`;let h=this.routes.get(u);if(!h){if(this.routes.size>=y)return;h={...x(),method:l,route:p,...r?{project:r}:{},errors:0,streaming:0},this.routes.set(u,h)}h.count+=1;const g=w(a.status);g!==void 0&&g>=500&&(h.errors+=1);const d=w(a.durationMs);a.streaming===!0?h.streaming+=1:d!==void 0&&q(h,d,this.limits.slowRouteMs,t.id),n&&(this.routeOf.set(n,u),this.routeOf.size>P&&this.routeOf.delete(this.routeOf.keys().next().value),this.close(n))}statement(t,e,r,n){const a=m(e.statement)??m(e.query),l=m(e.sqlId)??a?.slice(0,300);if(!l)return;const p=1+Math.max(0,w(e.floodSuppressed)??0);let u=this.sql.get(l);if(!u&&this.sql.size<y){u={...x(),failed:0};const d=m(e.sqlName),o=m(e.op),i=m(e.table);d&&(u.name=d),r&&(u.project=r),o&&(u.op=o),i&&(u.table=i),this.sql.set(l,u)}if(u){u.count+=p,!u.text&&a&&(u.text=a.slice(0,300)),e.error!==void 0&&e.error!==null&&(u.failed+=1);const d=w(e.durationMs);d!==void 0&&q(u,d,this.limits.slowQueryMs,t.id)}if(!n)return;let h=this.open.get(n);h||(this.open.size>=P&&this.close(this.open.keys().next().value),h=new Map,this.open.set(n,h));const g=h.get(l);g?g.n+=p:h.set(l,{n:p,first:t.id})}close(t){const e=this.open.get(t);if(!e)return;this.open.delete(t);const r=this.routeOf.get(t)??"a request with no route seen";for(const[n,{n:a,first:l}]of e){if(a<this.limits.nPlusOne)continue;const p=`${r}\0${n}`,u=this.repeated.get(p)??{route:r,sql:n,worst:0,requests:0,evidence:[]};u.worst=Math.max(u.worst,a),u.requests+=1,u.evidence.length<5&&u.evidence.push(l),this.repeated.set(p,u)}}loop(t,e,r){const n=$(e.loop),a=w(n.dbCallsInsideLoop)??0;if(a<=this.limits.dbInLoop)return;const l=(r&&this.routeOf.get(r))??t.title??"a loop";this.loops.length>=200||this.loops.push({id:`db-in-loop:${l}`,kind:"db-in-loop",subject:l,measured:`${a} DB calls inside one loop (${w(n.iterations)??"?"} iterations)`,limit:`more than ${this.limits.dbInLoop} (dbInLoop)`,evidence:[t.id]})}page(t,e){if(e.phase!=="ready"||e.cause==="hmr")return;const r=m(e.app)??"?",n=m(e.pattern)??m(e.path)??"?",a=`${r} ${n}`;let l=this.pages.get(a);if(!l){if(this.pages.size>=y)return;l={...x(),app:r,path:n},this.pages.set(a,l)}l.count+=1;const p=e.nav==="hard"?w(e.appReadyMs)??w(e.readyMs):w(e.readyMs);p!==void 0&&q(l,p,this.limits.slowPageMs,t.id)}waiverOf(t){for(const e of this.waivers)if(e.route&&t.route&&v(e.route,t.route,!0)||e.sql&&t.sql&&(v(e.sql,t.sql,!1)||t.sqlText!==void 0&&v(e.sql,t.sqlText,!1))||e.page&&t.page&&v(e.page,t.page,!0))return e.why}report(t){for(const o of[...this.open.keys()])this.close(o);const e=this.limits,r=[],n=c((o,i="runs")=>`p95 ${o.p95Ms} ms, max ${o.maxMs} ms \u2014 ${o.slow} of ${o.timed} ${i} at or above`,"slowWords"),a=[...this.routes.entries()].map(([o,i])=>({key:o,method:i.method,route:i.route,...i.project?{project:i.project}:{},errors:i.errors,streaming:i.streaming,...M(i),evidence:i.evidence}));for(const o of a)o.slow>1&&r.push({breach:{id:`slow-route:${o.key}`,kind:"slow-route",subject:o.key,measured:n(o),limit:`${e.slowRouteMs} ms (slowRouteMs)`,evidence:o.evidence},about:{route:o.key}});const l=[...this.sql.entries()].map(([o,i])=>({key:o,...i.name?{name:i.name}:{},...i.project?{project:i.project}:{},...i.op?{op:i.op}:{},...i.table?{table:i.table}:{},...i.text?{text:i.text}:{},failed:i.failed,...M(i),evidence:i.evidence}));for(const o of l)o.slow>1&&r.push({breach:{id:`slow-query:${o.key}`,kind:"slow-query",subject:o.name??o.key,measured:n(o),limit:`${e.slowQueryMs} ms (slowQueryMs)`,evidence:o.evidence},about:{sql:o.key,...o.text?{sqlText:o.text}:{}}});for(const o of this.repeated.values()){const i=this.sql.get(o.sql);r.push({breach:{id:`n+1-query:${o.route}:${o.sql}`,kind:"n+1-query",subject:`${o.route} runs ${i?.name??o.sql}`,measured:`up to ${o.worst}\xD7 in one request (${o.requests} request${o.requests===1?"":"s"})`,limit:`fewer than ${e.nPlusOne} (nPlusOne)`,evidence:o.evidence},about:{route:o.route,sql:o.sql,...i?.text?{sqlText:i.text}:{}}})}for(const o of this.loops)r.push({breach:o,about:{route:o.subject}});const p=[...this.pages.entries()].map(([o,i])=>({key:o,app:i.app,path:i.path,...M(i),evidence:i.evidence}));for(const o of p)o.slow>1&&r.push({breach:{id:`slow-page:${o.key}`,kind:"slow-page",subject:o.key,measured:n(o,"visits"),limit:`${e.slowPageMs} ms (slowPageMs)`,evidence:o.evidence},about:{page:o.path}});const u=[],h=[];for(const{breach:o,about:i}of r){const b=this.waiverOf(i);b?h.push({...o,waived:b}):u.push(o)}const g=c(o=>{const{evidence:i,...b}=o;return b},"strip"),d=this.routes.size+this.sql.size+this.pages.size>0;return{window:t??{from:Number.isFinite(this.from)?this.from:0,to:this.to},events:this.events,limits:{...e},verdict:u.length?"fail":d?"pass":"unmeasured",breaches:u,waived:h,routes:a.map(g).sort((o,i)=>i.p95Ms-o.p95Ms),sql:l.map(g).sort((o,i)=>i.p95Ms-o.p95Ms),pages:p.map(g).sort((o,i)=>i.p95Ms-o.p95Ms)}}}export function describePerformance(s,t=12){const e=s.limits,r=[s.verdict==="unmeasured"?"Performance: unmeasured \u2014 no request, statement or page ran in the window.":`Performance: ${s.verdict==="pass"?"PASS":`FAIL \u2014 ${s.breaches.length} breach${s.breaches.length===1?"":"es"}`} over ${s.routes.length} route${s.routes.length===1?"":"s"}, ${s.sql.length} statement${s.sql.length===1?"":"s"}, ${s.pages.length} page${s.pages.length===1?"":"s"} (limits: route ${e.slowRouteMs} ms, query ${e.slowQueryMs} ms, N+1 ${e.nPlusOne}, DB in a loop ${e.dbInLoop}, page ${e.slowPageMs} ms).`];for(const n of s.breaches.slice(0,t))r.push(`- ${n.kind} \xB7 ${n.subject}: ${n.measured}; limit ${n.limit}`);return s.breaches.length>t&&r.push(`- \u2026 ${s.breaches.length-t} more`),s.waived.length&&r.push(`Waived by .lensmcp/team/performance.json: ${s.waived.map(n=>`${n.kind} \xB7 ${n.subject} (${n.waived})`).slice(0,t).join("; ")}`),r.join(`
2
- `)}f(describePerformance,"describePerformance"),c(describePerformance,"describePerformance");
1
+ "use strict";var L=Object.defineProperty;var v=(o,t)=>L(o,"name",{value:t,configurable:!0});var N=Object.defineProperty,f=v((o,t)=>N(o,"name",{value:t,configurable:!0}),"f");import{resolveThresholds as E}from"./thresholds.js";export const SQL_DROPS_KIND="db-dropped",PERFORMANCE_EVENT_KINDS=["server-request","db-query",SQL_DROPS_KIND,"loop","page-visit"];export function performanceLimits(...o){let t={};for(const r of o)r&&(t={...t,...I(r)});const e=E(t);return{slowRouteMs:e.slowRouteMs,slowQueryMs:e.slowQueryMs,nPlusOne:e.nPlusOne,dbInLoop:e.dbInLoop,slowPageMs:e.slowPageMs}}v(performanceLimits,"performanceLimits"),f(performanceLimits,"performanceLimits");function I(o){const t={};for(const[e,r]of Object.entries(o))typeof r=="number"&&Number.isFinite(r)&&r>=0&&(t[e]=r);return t}v(I,"S"),f(I,"resolveThresholdsOnly");const $=f(o=>o&&typeof o=="object"?o:{},"obj"),m=f(o=>typeof o=="string"&&o?o:void 0,"str"),c=f(o=>typeof o=="number"&&Number.isFinite(o)?o:void 0,"num"),Q=2e3,j=5e3,R=2e4,k=f(()=>({count:0,times:[],slow:0,max:0,evidence:[]}),"newTiming");function q(o,t,e,r){if(o.max=Math.max(o.max,t),t>=e&&(o.slow+=1,o.evidence.length<5&&o.evidence.push(r)),o.times.length<Q)o.times.push(t);else{let n=0;for(let a=1;a<o.times.length;a++)o.times[a]<o.times[n]&&(n=a);t>o.times[n]&&(o.times[n]=t)}}v(q,"v"),f(q,"time");function P(o,t){return o.length?o[Math.min(o.length-1,Math.floor(o.length*t))]:0}v(P,"P"),f(P,"percentile");function x(o){const t=[...o.times].sort((e,r)=>e-r);return{count:o.count,timed:o.times.length,p50Ms:Math.round(P(t,.5)),p95Ms:Math.round(P(t,.95)),maxMs:Math.round(o.max),slow:o.slow}}v(x,"k"),f(x,"timing");function b(o,t,e){return o.includes("*")?new RegExp(`^${o.split("*").map(r=>r.replace(/[.+?^${}()|[\]\\]/g,"\\$&")).join(".*")}$`).test(t):e?t===o:t.includes(o)}v(b,"y"),f(b,"matches");export class PerformanceWindow{static{v(this,"PerformanceWindow")}static{f(this,"PerformanceWindow")}constructor(t,e=[]){this.events=0,this.from=1/0,this.to=0,this.routes=new Map,this.sql=new Map,this.drops={runs:0,slow:0,failed:0},this.pages=new Map,this.open=new Map,this.routeOf=new Map,this.repeated=new Map,this.loops=[],this.limits=t,this.waivers=e.filter(r=>typeof r?.why=="string"&&(r.route||r.sql||r.page))}add(t){const e=$(t.raw),r=e.kind;if(r!=="server-request"&&r!=="db-query"&&r!==SQL_DROPS_KIND&&r!=="loop"&&r!=="page-visit")return;this.events+=1,this.from=Math.min(this.from,t.timestamp),this.to=Math.max(this.to,t.timestamp);const n=$(t.context),a=m(n.projectName)??m(e.project),l=m(n.requestId);r==="server-request"?this.request(t,e,a,l):r==="db-query"?this.statement(t,e,a,l):r===SQL_DROPS_KIND?this.dropped(t,e,a):r==="loop"?this.loop(t,e,l):this.page(t,e)}request(t,e,r,n){const a=$(e.request),l=m(a.method)??"?",p=m(a.route)??"?",u=`${l} ${p}`;let h=this.routes.get(u);if(!h){if(this.routes.size>=j)return;h={...k(),method:l,route:p,...r?{project:r}:{},errors:0,streaming:0},this.routes.set(u,h)}h.count+=1;const d=c(a.status);d!==void 0&&d>=500&&(h.errors+=1);const g=c(a.durationMs);a.streaming===!0?h.streaming+=1:g!==void 0&&q(h,g,this.limits.slowRouteMs,t.id),n&&(this.routeOf.set(n,u),this.routeOf.size>R&&this.routeOf.delete(this.routeOf.keys().next().value),this.close(n))}sqlEntry(t,e,r){let n=this.sql.get(t);if(!n&&this.sql.size<j){n={...k(),failed:0,dropped:0};const a=m(e.sqlName),l=m(e.op),p=m(e.table);a&&(n.name=a),r&&(n.project=r),l&&(n.op=l),p&&(n.table=p),this.sql.set(t,n)}return n}statement(t,e,r,n){const a=m(e.statement)??m(e.query),l=m(e.sqlId)??a?.slice(0,300);if(!l)return;const p=1+Math.max(0,c(e.floodSuppressed)??0),u=this.sqlEntry(l,e,r);if(u){u.count+=p,!u.text&&a&&(u.text=a.slice(0,300)),e.error!==void 0&&e.error!==null&&(u.failed+=1);const g=c(e.durationMs);g!==void 0&&q(u,g,this.limits.slowQueryMs,t.id)}if(!n)return;let h=this.open.get(n);h||(this.open.size>=R&&this.close(this.open.keys().next().value),h=new Map,this.open.set(n,h));const d=h.get(l);d?d.n+=p:h.set(l,{n:p,first:t.id})}dropped(t,e,r){const n=c(e.slowMs)??-1/0,a=f((h,d)=>Math.max(this.limits.slowQueryMs<=n?Math.max(0,c(h.slow)??0):0,d>=this.limits.slowQueryMs?1:0),"slowOf"),l=Array.isArray(e.shapes)?e.shapes:[];for(const h of l){const d=$(h),g=m(d.sqlId),s=Math.max(0,c(d.runs)??0);if(!g||!s)continue;const i=Math.max(0,c(d.maxMs)??0),M=Math.max(0,c(d.failed)??0),y=a(d,i);this.drops.runs+=s,this.drops.slow+=y,this.drops.failed+=M;const w=this.sqlEntry(g,d,r);if(w){if(w.count+=s,w.dropped+=s,w.failed+=M,w.max=Math.max(w.max,i),!w.text){const O=m(d.query);O&&(w.text=O.slice(0,300))}y&&(w.slow+=y,w.evidence.length<5&&w.evidence.push(t.id))}}const p=$(e.overflow),u=Math.max(0,c(p.runs)??0);if(u){const h=Math.max(0,c(p.maxMs)??0);this.drops.runs+=u,this.drops.slow+=a(p,h),this.drops.failed+=Math.max(0,c(p.failed)??0)}}close(t){const e=this.open.get(t);if(!e)return;this.open.delete(t);const r=this.routeOf.get(t)??"a request with no route seen";for(const[n,{n:a,first:l}]of e){if(a<this.limits.nPlusOne)continue;const p=`${r}\0${n}`,u=this.repeated.get(p)??{route:r,sql:n,worst:0,requests:0,evidence:[]};u.worst=Math.max(u.worst,a),u.requests+=1,u.evidence.length<5&&u.evidence.push(l),this.repeated.set(p,u)}}loop(t,e,r){const n=$(e.loop),a=c(n.dbCallsInsideLoop)??0;if(a<=this.limits.dbInLoop)return;const l=(r&&this.routeOf.get(r))??t.title??"a loop";this.loops.length>=200||this.loops.push({id:`db-in-loop:${l}`,kind:"db-in-loop",subject:l,measured:`${a} DB calls inside one loop (${c(n.iterations)??"?"} iterations)`,limit:`more than ${this.limits.dbInLoop} (dbInLoop)`,evidence:[t.id]})}page(t,e){if(e.phase!=="ready"||e.cause==="hmr")return;const r=m(e.app)??"?",n=m(e.pattern)??m(e.path)??"?",a=`${r} ${n}`;let l=this.pages.get(a);if(!l){if(this.pages.size>=j)return;l={...k(),app:r,path:n},this.pages.set(a,l)}l.count+=1;const p=e.nav==="hard"?c(e.appReadyMs)??c(e.readyMs):c(e.readyMs);p!==void 0&&q(l,p,this.limits.slowPageMs,t.id)}waiverOf(t){for(const e of this.waivers)if(e.route&&t.route&&b(e.route,t.route,!0)||e.sql&&t.sql&&(b(e.sql,t.sql,!1)||t.sqlText!==void 0&&b(e.sql,t.sqlText,!1))||e.page&&t.page&&b(e.page,t.page,!0))return e.why}report(t){for(const s of[...this.open.keys()])this.close(s);const e=this.limits,r=[],n=f((s,i="runs")=>`p95 ${s.p95Ms} ms, max ${s.maxMs} ms \u2014 ${s.slow} of ${s.timed} ${i} at or above`,"slowWords"),a=[...this.routes.entries()].map(([s,i])=>({key:s,method:i.method,route:i.route,...i.project?{project:i.project}:{},errors:i.errors,streaming:i.streaming,...x(i),evidence:i.evidence}));for(const s of a)s.slow>1&&r.push({breach:{id:`slow-route:${s.key}`,kind:"slow-route",subject:s.key,measured:n(s),limit:`${e.slowRouteMs} ms (slowRouteMs)`,evidence:s.evidence},about:{route:s.key}});const l=[...this.sql.entries()].map(([s,i])=>({key:s,...i.name?{name:i.name}:{},...i.project?{project:i.project}:{},...i.op?{op:i.op}:{},...i.table?{table:i.table}:{},...i.text?{text:i.text}:{},failed:i.failed,...i.dropped?{dropped:i.dropped}:{},...x(i),evidence:i.evidence}));for(const s of l){const i=s.dropped?`p95 ${s.p95Ms} ms, max ${s.maxMs} ms \u2014 ${s.slow} of ${s.count} runs at or above (${s.dropped} counted from the service's drop record, not timed)`:n(s);s.slow>1&&r.push({breach:{id:`slow-query:${s.key}`,kind:"slow-query",subject:s.name??s.key,measured:i,limit:`${e.slowQueryMs} ms (slowQueryMs)`,evidence:s.evidence},about:{sql:s.key,...s.text?{sqlText:s.text}:{}}})}for(const s of this.repeated.values()){const i=this.sql.get(s.sql);r.push({breach:{id:`n+1-query:${s.route}:${s.sql}`,kind:"n+1-query",subject:`${s.route} runs ${i?.name??s.sql}`,measured:`up to ${s.worst}\xD7 in one request (${s.requests} request${s.requests===1?"":"s"})`,limit:`fewer than ${e.nPlusOne} (nPlusOne)`,evidence:s.evidence},about:{route:s.route,sql:s.sql,...i?.text?{sqlText:i.text}:{}}})}for(const s of this.loops)r.push({breach:s,about:{route:s.subject}});const p=[...this.pages.entries()].map(([s,i])=>({key:s,app:i.app,path:i.path,...x(i),evidence:i.evidence}));for(const s of p)s.slow>1&&r.push({breach:{id:`slow-page:${s.key}`,kind:"slow-page",subject:s.key,measured:n(s,"visits"),limit:`${e.slowPageMs} ms (slowPageMs)`,evidence:s.evidence},about:{page:s.path}});const u=[],h=[];for(const{breach:s,about:i}of r){const M=this.waiverOf(i);M?h.push({...s,waived:M}):u.push(s)}const d=f(s=>{const{evidence:i,...M}=s;return M},"strip"),g=this.routes.size+this.sql.size+this.pages.size+this.drops.runs>0;return{window:t??{from:Number.isFinite(this.from)?this.from:0,to:this.to},events:this.events,limits:{...e},verdict:u.length?"fail":g?"pass":"unmeasured",breaches:u,waived:h,...this.drops.runs?{dropped:{...this.drops}}:{},routes:a.map(d).sort((s,i)=>i.p95Ms-s.p95Ms),sql:l.map(d).sort((s,i)=>i.p95Ms-s.p95Ms),pages:p.map(d).sort((s,i)=>i.p95Ms-s.p95Ms)}}}export function describePerformance(o,t=12){const e=o.limits,r=[o.verdict==="unmeasured"?"Performance: unmeasured \u2014 no request, statement or page ran in the window.":`Performance: ${o.verdict==="pass"?"PASS":`FAIL \u2014 ${o.breaches.length} breach${o.breaches.length===1?"":"es"}`} over ${o.routes.length} route${o.routes.length===1?"":"s"}, ${o.sql.length} statement${o.sql.length===1?"":"s"}, ${o.pages.length} page${o.pages.length===1?"":"s"} (limits: route ${e.slowRouteMs} ms, query ${e.slowQueryMs} ms, N+1 ${e.nPlusOne}, DB in a loop ${e.dbInLoop}, page ${e.slowPageMs} ms).`],n=o.dropped;n&&r.push(`${n.runs} statement run${n.runs===1?"":"s"} reached the lens only as counts (the services' flood guards): ${n.slow} slow, ${n.failed} failed \u2014 counted above, not in the percentiles.`);for(const a of o.breaches.slice(0,t))r.push(`- ${a.kind} \xB7 ${a.subject}: ${a.measured}; limit ${a.limit}`);return o.breaches.length>t&&r.push(`- \u2026 ${o.breaches.length-t} more`),o.waived.length&&r.push(`Waived by .lensmcp/team/performance.json: ${o.waived.map(a=>`${a.kind} \xB7 ${a.subject} (${a.waived})`).slice(0,t).join("; ")}`),r.join(`
2
+ `)}v(describePerformance,"describePerformance"),f(describePerformance,"describePerformance");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/core",
3
- "version": "1.25.42",
3
+ "version": "1.25.43",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -84,7 +84,7 @@
84
84
  }
85
85
  },
86
86
  "dependencies": {
87
- "@lensmcp/protocol-types": "1.25.42",
87
+ "@lensmcp/protocol-types": "1.25.43",
88
88
  "tslib": "^2.3.0"
89
89
  },
90
90
  "license": "Apache-2.0",