@lensmcp/cluster 1.24.3 → 1.24.5

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.
@@ -1 +1 @@
1
- "use strict";var K=Object.defineProperty;var x=(t,c)=>K(t,"name",{value:c,configurable:!0});var G=Object.defineProperty,s=x((t,c)=>G(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.wedgedMessage=wedgedMessage,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const c=t.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}x(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const c=t.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}x(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,c){return c.headersSent||c.writableEnded||c.destroyed===!0||t.aborted===!0}x(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"],httpProxy=require("http-proxy");function declaredFirstByteMs(t){const c=Number(t);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}x(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(t=>`${(t/1e3).toFixed(1)}s`,"secs");function wedgedMessage(t,c,E){const U=E.failure==="gone"?"its process is gone":E.failure==="silent"?`it did not answer its liveness check for ${secs(E.silentMs)}`:"it gave no liveness answer";return`Pod ${t} of ${c} sent no response for ${secs(E.heldMs)} and ${U} \u2014 taken out of rotation, and the service is being restarted, so work it was still doing for this request may have been cut short. Retry if the call is safe to repeat.`}x(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(t){let c,E=!1,U=!1,q,M=0,P=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const b=s(()=>{P??=Date.now()},"onBodyEnd");P===void 0&&t.req.once("end",b);const N=s(()=>(P===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&(P=Date.now()),P===void 0?0:Date.now()-P),"heldMs"),k=s(j=>{c=setTimeout(C,j),c.unref?.()},"arm"),C=s(()=>{if(E||!t.pending())return;const j=N();if(t.ceilingMs<=0){if(P!==void 0&&j>=t.firstByteMs){t.onWedged(`no response within ${j}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:j,silentMs:0,probes:0,failure:"deadline"});return}k(P===void 0?t.firstByteMs:t.firstByteMs-j);return}(0,pod_probe_1.probePodAliveVerdict)(t.socketPath,void 0,t.serviceKey).then(({alive:D,failure:I})=>{if(E||!t.pending())return;const R=N();if(!D){if(M+=1,I==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(q??=Date.now(),Date.now()-q<types_1.POD_WEDGE_CONFIRM_MS)){k(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const O=q===void 0?0:Date.now()-q,L=I==="gone"?"its socket no longer accepts (the pod process is gone)":I==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${O}ms (${M} probe${M===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;t.onWedged(`no response within ${R}ms and ${L} (wedged socket) \u2014 evicted + respawning`,{heldMs:R,silentMs:O,probes:M,failure:I??"no-route"});return}if(q=void 0,R>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${R}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,D);return}!U&&P!==void 0&&(U=!0,t.onSlow(R,D)),k(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return k(t.firstByteMs),{clear:s(()=>{E=!0,c&&clearTimeout(c),t.req.removeListener("end",b)},"clear")}}x(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,c,E){const{recordEdge:U,finishTrace:q,traceStep:M,emit:P}=c,b=s((e,r,o,p)=>{const h=p.kind==="pod-evicted";P(h?"error":"warning",`${h?"pod evicted":"request given up"}: ${e.project} (${o}) \u2014 ${p.status}`,`cluster-pod-incident:${e.project}`,{kind:p.kind,project:e.project,pod:o,status:p.status,reason:p.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...p.detail??{}})},"reportPodIncident"),N=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(g=>{r=setTimeout(()=>g("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),p=t.lensSupervisor.recover(e.lens,"proxy-connection").then(g=>g?"ready":"failed").catch(()=>"failed"),h=await Promise.race([p,o]);r&&clearTimeout(r);const w=t.lensSupervisor.status(e.lens);return h==="failed"&&(!w||w.state==="ready")?"unsupported":h},"waitForManagedRecovery"),k=s((e,r,o,p,h,w)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const g=e.lens?t.lensSupervisor?.status(e.lens):void 0,v=w==="timeout"||g?.state==="starting"||g?.state==="replacing",S=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:g?.reason??"connection-failure",generation:g?.generation??0,...v?{retryAfterMs:2e3}:{}}:void 0,n=v?503:502,i=v?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${S} project=${e.project} target=${String(p)} state=${g?.state??"unmanaged"} generation=${g?.generation??0}: ${h.message}`),P("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:S,project:e.project,target:String(p),state:g?.state??"unmanaged",generation:g?.generation,error:h.message}),(0,edge_1.sendError)(o,r,n,i,v?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,v?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),C=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),j={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},D=new http.Agent(j),I=new(require("node:https")).Agent(j),R=s(e=>typeof e=="string"&&e.startsWith("https:")?I:D,"agentForTarget"),O=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:D}),L=s((e,r,o)=>{const p=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",p),e.once("close",()=>r.removeListener("close",p))},"propagateEdgeCancel");O.on("proxyReq",(e,r,o)=>{for(const p of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(p);e.setHeader("connection","keep-alive"),L(e,o,()=>o.writableEnded)}),O.on("proxyRes",(e,r,o)=>{const p=r.__lensmcpPod;p&&(e.headers["x-lensmcp-pod"]=p),delete e.headers["x-powered-by"],e.headers.server="LensMCP",o&&L(e,o,()=>o.writableEnded),o&&C.armResponse(o)});const W=s((e,r,o,p)=>{(0,edge_1.stampForwardingHeaders)(t,r),C.arm(r);const h={},w=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[a,f]of Object.entries(r.headers))f===void 0||a.startsWith(":")||HOP_BY_HOP.has(a)||w&&(a==="content-length"||a==="transfer-encoding")||(h[a]=f);let g=http,v;if(typeof e=="object")v={socketPath:e.socketPath,path:r.url,method:r.method,headers:h,agent:D};else{const a=new URL(e),f=a.protocol==="https:";g=f?require("node:https"):http,h.host=a.host,v={protocol:a.protocol,hostname:a.hostname,port:a.port||(f?443:80),path:r.url,method:r.method,headers:h,agent:R(e)}}const S=o;S.__lensmcpErrContained||(S.__lensmcpErrContained=!0,o.on("error",()=>{}));let l;const n=s(()=>{o.writableEnded||y.destroy()},"onReqGone"),i=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),d=s(()=>{r.removeListener("error",n),r.removeListener("aborted",n),r.removeListener("close",n),o.removeListener("close",i)},"detach"),y=g.request(v,a=>{if(l=a,p.onResponse(),o.headersSent||o.writableEnded||o.destroyed){a.destroy();return}const f={};for(const[T,u]of Object.entries(a.headers))u===void 0||HOP_BY_HOP.has(T)||(f[T]=u);delete f["x-powered-by"],f.server="LensMCP";const B=r.__lensmcpPod;B&&(f["x-lensmcp-pod"]=B);try{o.writeHead(a.statusCode??502,f)}catch(T){console.error("[gateway] h2 relay: bad response header, resetting stream:",T.message),a.destroy(),o.destroyed||o.destroy(T);return}a.pipe(o),C.armResponse(o);const A=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");a.once("error",A),a.once("close",A),o.on("close",i)});y.once("error",a=>p.onError(a)),y.once("close",d),r.on("error",n),r.on("aborted",n),r.on("close",n),w?y.end():r.pipe(y)},"pipeUpstream"),F=s(async(e,r,o)=>{const p=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,o.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!n&&l&&(M(p,"cold-start",{project:e.project}),await E.ensureUp(l)&&(n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),l&&n&&E.markUp(l,"observed"),l&&E.scaleUp(l),!n){const u=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${u} The watcher recovers and the next request retries automatically.`);return}const i=path.basename(n,".sock");r.__lensmcpPod=i,M(p,"pod-select",{pod:i,pods:e.pool.socks.length,strategy:"round-robin"});let d=!1;const y=s((u,$="unlink")=>{if(e.pool.socks=e.pool.socks.filter(m=>m!==n),e.pool.idx=-1,$==="unlink")try{fs.unlinkSync(n)}catch{}console.error(`[gateway] pod ${i} of ${e.project}: ${u}`)},"evictPod"),a=armFirstByteGuard({socketPath:n,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!d&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((u,$)=>{d=!0,y(u),b(e,r,i,{kind:"pod-evicted",status:502,reason:u,detail:$}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(i,e.project,$))},"onWedged"),onCeiling:s(u=>{d=!0,y(u,"keep"),b(e,r,i,{kind:"request-gave-up",status:504,reason:u}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${i} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((u,$)=>{console.log(`[gateway] pod ${i} of ${e.project}: slow first byte, pod alive (${u}ms, ${$.inflight} in-flight, ${$.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>a.clear());const f=n,B=isReplayableRequest(r);let A=0;const T=s(()=>{A+=1,W({socketPath:f},r,o,{onResponse:s(()=>{a.clear(),d=!0},"onResponse"),onError:s(u=>{if(!d){if(edgeGone(r,o)){a.clear(),d=!0;return}(0,pod_probe_1.podConnectionVerdict)(f,u).then($=>{if(!d){if($==="alive"&&A<2&&B&&!edgeGone(r,o)){console.log(`[gateway] pod ${i} of ${e.project}: ${u.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),T();return}if(a.clear(),d=!0,$==="gone"){y(u.message),b(e,r,i,{kind:"pod-evicted",status:502,reason:`${u.message} \u2014 its socket no longer accepts (the pod process is gone)`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} is gone (${u.message}) \u2014 retry.`);return}console.error(`[gateway] pod ${i} of ${e.project}: ${u.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");T();return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let w=0,g=!1;const v=isReplayableRequest(r),S=s(()=>{if(edgeGone(r,o))return;const l=e.target;W(l,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(n=>{if(isConnError(n)&&(w+=1),e.lens&&isConnError(n)&&v&&w>=2&&!g&&!edgeGone(r,o)){g=!0,N(e).then(i=>{if(!edgeGone(r,o)){if(i==="ready"){S();return}if(i==="unsupported"&&Date.now()<h){setTimeout(S,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,n,i)}});return}if(isConnError(n)&&v&&Date.now()<h&&!edgeGone(r,o)){setTimeout(S,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}k(e,r,o,l,n)},"onError")})},"tryTcp");S()},"forwardNative");return{proxy:O,forward:s(async(e,r,o)=>{const p=Date.now(),h=r.__lensmcpTrace;if(o.on("close",()=>{U(e,Date.now()-p,o.statusCode??0,r.__lensmcpCaller),q(h,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),C.arm(r),(r.httpVersionMajor??1)>=2){await F(e,r,o);return}if(e.pool){const n=e.svc;n&&(n.lastUsed=Date.now(),n.inflight+=1,o.on("close",()=>{n.inflight-=1,n.lastUsed=Date.now()}));let i=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!i&&n&&(M(h,"cold-start",{project:e.project}),await E.ensureUp(n)&&(i=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),n&&i&&E.markUp(n,"observed"),n&&E.scaleUp(n),!i){const m=n?.lastErrors?.length?` Service errors: ${n.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${m} The watcher recovers and the next request retries automatically.`);return}const d=path.basename(i,".sock");r.__lensmcpPod=d,M(h,"pod-select",{pod:d,pods:e.pool.socks.length,strategy:"round-robin"});let y=!1;const a=s((m,_="unlink")=>{if(e.pool.socks=e.pool.socks.filter(H=>H!==i),e.pool.idx=-1,_==="unlink")try{fs.unlinkSync(i)}catch{}console.error(`[gateway] pod ${d} of ${e.project}: ${m}`)},"evictPod"),f=armFirstByteGuard({socketPath:i,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!y&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((m,_)=>{y=!0,a(m),b(e,r,d,{kind:"pod-evicted",status:502,reason:m,detail:_}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(d,e.project,_))},"onWedged"),onCeiling:s(m=>{y=!0,a(m,"keep"),b(e,r,d,{kind:"request-gave-up",status:504,reason:m}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${d} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((m,_)=>{console.log(`[gateway] pod ${d} of ${e.project}: slow first byte, pod alive (${m}ms, ${_.inflight} in-flight, ${_.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>f.clear());const B=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),A=i,T=isReplayableRequest(r);let u=0;const $=s(()=>{u+=1;const m=Date.now();O.web(r,o,{target:{socketPath:A},agent:D,proxyTimeout:B},_=>{if(!y){if(edgeGone(r,o)){f.clear(),y=!0;return}(0,pod_probe_1.podConnectionVerdict)(A,_).then(H=>{if(y)return;const V=Date.now()-m>=B-250;if(H==="alive"&&u<2&&T&&!V&&!edgeGone(r,o)){console.log(`[gateway] pod ${d} of ${e.project}: ${_.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),$();return}if(f.clear(),y=!0,H==="gone"){a(_.message),b(e,r,d,{kind:"pod-evicted",status:502,reason:`${_.message} \u2014 its socket no longer accepts (the pod process is gone)`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${d} of ${e.project} is gone (${_.message}) \u2014 retry.`);return}console.error(`[gateway] pod ${d} of ${e.project}: ${_.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${d} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");$();return}const w=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let g=0,v=!1;const S=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,o))return;const n=e.target,i={target:n,autoRewrite:typeof n=="string",changeOrigin:!0,agent:R(n)};O.web(r,o,i,d=>{if(isConnError(d)&&(g+=1),e.lens&&isConnError(d)&&S&&g>=2&&!v&&!edgeGone(r,o)){v=!0,N(e).then(y=>{if(!edgeGone(r,o)){if(y==="ready"){l();return}if(y==="unsupported"&&Date.now()<w){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,d,y)}});return}if(isConnError(d)&&S&&Date.now()<w&&!edgeGone(r,o)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||k(e,r,o,n,d)})},"tryTcp");l()},"forward"),agentForTarget:R,destroyAgents:s(()=>{C.stop(),D.destroy(),I.destroy()},"destroyAgents"),closeProxy:s(()=>{try{O.close?.()}catch{}},"closeProxy")}}x(createProxy,"createProxy"),s(createProxy,"createProxy");
1
+ "use strict";var ee=Object.defineProperty;var O=(t,d)=>ee(t,"name",{value:d,configurable:!0});var Z=Object.defineProperty,s=O((t,d)=>Z(t,"name",{value:d,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.wedgedMessage=wedgedMessage,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const d=t.code??"";return d==="ECONNREFUSED"||d==="ECONNRESET"||d==="ETIMEDOUT"||d==="EPIPE"||d==="EHOSTUNREACH"||d==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}O(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const d=t.headers["content-length"];return!(d!==void 0&&String(d).trim()!=="0")}O(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,d){return d.headersSent||d.writableEnded||d.destroyed===!0||t.aborted===!0}O(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"];function returnRequestId(t,d){if(t["x-request-id"]!==void 0)return;const f=d.headers["x-request-id"],R=Array.isArray(f)?f[0]:f;typeof R=="string"&&R!==""&&(t["x-request-id"]=R)}O(returnRequestId,"returnRequestId"),s(returnRequestId,"returnRequestId");const httpProxy=require("http-proxy");function declaredFirstByteMs(t){const d=Number(t);return Number.isFinite(d)&&d>0?d:types_1.POD_RESPONSE_TIMEOUT_MS}O(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(t=>`${(t/1e3).toFixed(1)}s`,"secs");function wedgedMessage(t,d,f){const R=f.failure==="gone"?"its process is gone":f.failure==="silent"?`it did not answer its liveness check for ${secs(f.silentMs)}`:"it gave no liveness answer";return`Pod ${t} of ${d} sent no response for ${secs(f.heldMs)} and ${R} \u2014 taken out of rotation, and the service is being restarted, so work it was still doing for this request may have been cut short. Retry if the call is safe to repeat.`}O(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(t){let d,f=!1,R=!1,U,D=0,$=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const q=s(()=>{$??=Date.now()},"onBodyEnd");$===void 0&&t.req.once("end",q);const L=s(()=>($===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&($=Date.now()),$===void 0?0:Date.now()-$),"heldMs"),I=s(k=>{d=setTimeout(V,k),d.unref?.()},"arm"),V=s(()=>{if(f||!t.pending())return;const k=L();if(t.ceilingMs<=0){if($!==void 0&&k>=t.firstByteMs){t.onWedged(`no response within ${k}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:k,silentMs:0,probes:0,failure:"deadline"});return}I($===void 0?t.firstByteMs:t.firstByteMs-k);return}(0,pod_probe_1.probePodAliveVerdict)(t.socketPath,void 0,t.serviceKey).then(({alive:x,failure:C})=>{if(f||!t.pending())return;const T=L();if(!x){if(D+=1,C==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(U??=Date.now(),Date.now()-U<types_1.POD_WEDGE_CONFIRM_MS)){I(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const N=U===void 0?0:Date.now()-U,H=C==="gone"?"its socket no longer accepts (the pod process is gone)":C==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${N}ms (${D} probe${D===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;t.onWedged(`no response within ${T}ms and ${H} (wedged socket) \u2014 evicted + respawning`,{heldMs:T,silentMs:N,probes:D,failure:C??"no-route"});return}if(U=void 0,T>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${T}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,x);return}!R&&$!==void 0&&(R=!0,t.onSlow(T,x)),I(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return I(t.firstByteMs),{clear:s(()=>{f=!0,d&&clearTimeout(d),t.req.removeListener("end",q)},"clear")}}O(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,d,f){const{recordEdge:R,finishTrace:U,traceStep:D,emit:$}=d,q=s((e,r,o,c)=>{const p=c.kind==="pod-evicted";$(p?"error":"warning",`${p?"pod evicted":"request given up"}: ${e.project} (${o}) \u2014 ${c.status}`,`cluster-pod-incident:${e.project}`,{kind:c.kind,project:e.project,pod:o,status:c.status,reason:c.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...c.detail??{}})},"reportPodIncident"),L=s(async(e,r)=>{const o=e.pool;if(!o)return;o.scannedAt=0;let c=(0,discovery_1.pickSock)(o,t.scanTtlMs);return!c&&e.svc&&(D(r,"cold-start",{project:e.project}),await f.ensureUp(e.svc)&&(o.scannedAt=0,c=(0,discovery_1.pickSock)(o,t.scanTtlMs))),c},"anotherPod"),I=s((e,r,o,c,p)=>{console.log(`[gateway] pod ${o} of ${e.project} is gone \u2014 its ${r.method} was not answered, sending it to ${c}`),$("info",`request moved to another pod: ${e.project} (${o} \u2192 ${c})`,`cluster-pod-moved:${e.project}`,{kind:"pod-gone-rerouted",project:e.project,pod:o,to:c,reason:p,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0]})},"reportPodMoved"),V=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(g=>{r=setTimeout(()=>g("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),c=t.lensSupervisor.recover(e.lens,"proxy-connection").then(g=>g?"ready":"failed").catch(()=>"failed"),p=await Promise.race([c,o]);r&&clearTimeout(r);const S=t.lensSupervisor.status(e.lens);return p==="failed"&&(!S||S.state==="ready")?"unsupported":p},"waitForManagedRecovery"),k=s((e,r,o,c,p,S)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const g=e.lens?t.lensSupervisor?.status(e.lens):void 0,_=S==="timeout"||g?.state==="starting"||g?.state==="replacing",P=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:g?.reason??"connection-failure",generation:g?.generation??0,..._?{retryAfterMs:2e3}:{}}:void 0,a=_?503:502,y=_?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${P} project=${e.project} target=${String(c)} state=${g?.state??"unmanaged"} generation=${g?.generation??0}: ${p.message}`),$("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:P,project:e.project,target:String(c),state:g?.state??"unmanaged",generation:g?.generation,error:p.message}),(0,edge_1.sendError)(o,r,a,y,_?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,_?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),x=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),C={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},T=new http.Agent(C),N=new(require("node:https")).Agent(C),H=s(e=>typeof e=="string"&&e.startsWith("https:")?N:T,"agentForTarget"),B=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:T}),Y=s((e,r,o)=>{const c=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",c),e.once("close",()=>r.removeListener("close",c))},"propagateEdgeCancel");B.on("proxyReq",(e,r,o)=>{for(const c of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(c);e.setHeader("connection","keep-alive"),Y(e,o,()=>o.writableEnded)}),B.on("proxyRes",(e,r,o)=>{const c=r.__lensmcpPod;c&&(e.headers["x-lensmcp-pod"]=c),delete e.headers["x-powered-by"],e.headers.server="LensMCP",returnRequestId(e.headers,r),o&&Y(e,o,()=>o.writableEnded),o&&x.armResponse(o)});const Q=s((e,r,o,c)=>{(0,edge_1.stampForwardingHeaders)(t,r),x.arm(r);const p={},S=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[n,i]of Object.entries(r.headers))i===void 0||n.startsWith(":")||HOP_BY_HOP.has(n)||S&&(n==="content-length"||n==="transfer-encoding")||(p[n]=i);let g=http,_;if(typeof e=="object")_={socketPath:e.socketPath,path:r.url,method:r.method,headers:p,agent:T};else{const n=new URL(e),i=n.protocol==="https:";g=i?require("node:https"):http,p.host=n.host,_={protocol:n.protocol,hostname:n.hostname,port:n.port||(i?443:80),path:r.url,method:r.method,headers:p,agent:H(e)}}const P=o;P.__lensmcpErrContained||(P.__lensmcpErrContained=!0,o.on("error",()=>{}));let l;const a=s(()=>{o.writableEnded||u.destroy()},"onReqGone"),y=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),m=s(()=>{r.removeListener("error",a),r.removeListener("aborted",a),r.removeListener("close",a),o.removeListener("close",y)},"detach"),u=g.request(_,n=>{if(l=n,c.onResponse(),o.headersSent||o.writableEnded||o.destroyed){n.destroy();return}const i={};for(const[b,A]of Object.entries(n.headers))A===void 0||HOP_BY_HOP.has(b)||(i[b]=A);delete i["x-powered-by"],i.server="LensMCP",returnRequestId(i,r);const E=r.__lensmcpPod;E&&(i["x-lensmcp-pod"]=E);try{o.writeHead(n.statusCode??502,i)}catch(b){console.error("[gateway] h2 relay: bad response header, resetting stream:",b.message),n.destroy(),o.destroyed||o.destroy(b);return}n.pipe(o),x.armResponse(o);const j=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");n.once("error",j),n.once("close",j),o.on("close",y)});u.once("error",n=>c.onError(n)),u.once("close",m),r.on("error",a),r.on("aborted",a),r.on("close",a),S?u.end():r.pipe(u)},"pipeUpstream"),X=s(async(e,r,o)=>{const c=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,o.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!a&&l&&(D(c,"cold-start",{project:e.project}),await f.ensureUp(l)&&(a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),l&&a&&f.markUp(l,"observed"),l&&f.scaleUp(l),!a){const m=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${m} The watcher recovers and the next request retries automatically.`);return}const y=s((m,u)=>{const n=path.basename(m,".sock");r.__lensmcpPod=n,D(c,"pod-select",{pod:n,pods:e.pool.socks.length,strategy:"round-robin",...u?{movedFrom:u}:{}});let i=!1;const E=s((h,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(w=>w!==m),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(m)}catch{}console.error(`[gateway] pod ${n} of ${e.project}: ${h}`)},"evictPod"),j=armFirstByteGuard({socketPath:m,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!i&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((h,M)=>{i=!0,E(h),q(e,r,n,{kind:"pod-evicted",status:502,reason:h,detail:M}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(n,e.project,M))},"onWedged"),onCeiling:s(h=>{i=!0,E(h,"keep"),q(e,r,n,{kind:"request-gave-up",status:504,reason:h}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${n} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((h,M)=>{console.log(`[gateway] pod ${n} of ${e.project}: slow first byte, pod alive (${h}ms, ${M.inflight} in-flight, ${M.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>j.clear());const b=m,A=isReplayableRequest(r);let F=0;const W=s(()=>{F+=1,Q({socketPath:b},r,o,{onResponse:s(()=>{j.clear(),i=!0},"onResponse"),onError:s(h=>{if(!i){if(edgeGone(r,o)){j.clear(),i=!0;return}(0,pod_probe_1.podConnectionVerdict)(b,h).then(M=>{if(!i){if(M==="alive"&&F<2&&A&&!edgeGone(r,o)){console.log(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),W();return}if(j.clear(),i=!0,M==="gone"){E(h.message);const w=s(()=>{q(e,r,n,{kind:"pod-evicted",status:502,reason:`${h.message} \u2014 its socket no longer accepts (the pod process is gone)${u?`; the request had already been moved here from ${u}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} is gone (${h.message}) \u2014 retry.`)},"giveUp");if(u||!A){w();return}L(e,c).then(v=>{if(!edgeGone(r,o)){if(!v){w();return}I(e,r,n,path.basename(v,".sock"),h.message),y(v,n)}},w);return}console.error(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");W()},"onPod");y(a);return}const p=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let S=0,g=!1;const _=isReplayableRequest(r),P=s(()=>{if(edgeGone(r,o))return;const l=e.target;Q(l,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(a=>{if(isConnError(a)&&(S+=1),e.lens&&isConnError(a)&&_&&S>=2&&!g&&!edgeGone(r,o)){g=!0,V(e).then(y=>{if(!edgeGone(r,o)){if(y==="ready"){P();return}if(y==="unsupported"&&Date.now()<p){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,a,y)}});return}if(isConnError(a)&&_&&Date.now()<p&&!edgeGone(r,o)){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}k(e,r,o,l,a)},"onError")})},"tryTcp");P()},"forwardNative");return{proxy:B,forward:s(async(e,r,o)=>{const c=Date.now(),p=r.__lensmcpTrace;if(o.on("close",()=>{R(e,Date.now()-c,o.statusCode??0,r.__lensmcpCaller),U(p,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),x.arm(r),(r.httpVersionMajor??1)>=2){await X(e,r,o);return}if(e.pool){const a=e.svc;a&&(a.lastUsed=Date.now(),a.inflight+=1,o.on("close",()=>{a.inflight-=1,a.lastUsed=Date.now()}));let y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!y&&a&&(D(p,"cold-start",{project:e.project}),await f.ensureUp(a)&&(y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),a&&y&&f.markUp(a,"observed"),a&&f.scaleUp(a),!y){const u=a?.lastErrors?.length?` Service errors: ${a.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${u} The watcher recovers and the next request retries automatically.`);return}const m=s((u,n)=>{const i=path.basename(u,".sock");r.__lensmcpPod=i,D(p,"pod-select",{pod:i,pods:e.pool.socks.length,strategy:"round-robin",...n?{movedFrom:n}:{}});let E=!1;const j=s((w,v="unlink")=>{if(e.pool.socks=e.pool.socks.filter(G=>G!==u),e.pool.idx=-1,v==="unlink")try{fs.unlinkSync(u)}catch{}console.error(`[gateway] pod ${i} of ${e.project}: ${w}`)},"evictPod"),b=armFirstByteGuard({socketPath:u,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!E&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((w,v)=>{E=!0,j(w),q(e,r,i,{kind:"pod-evicted",status:502,reason:w,detail:v}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(i,e.project,v))},"onWedged"),onCeiling:s(w=>{E=!0,j(w,"keep"),q(e,r,i,{kind:"request-gave-up",status:504,reason:w}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${i} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((w,v)=>{console.log(`[gateway] pod ${i} of ${e.project}: slow first byte, pod alive (${w}ms, ${v.inflight} in-flight, ${v.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>b.clear());const A=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),F=u,W=isReplayableRequest(r);let h=0;const M=s(()=>{h+=1;const w=Date.now();B.web(r,o,{target:{socketPath:F},agent:T,proxyTimeout:A},v=>{if(!E){if(edgeGone(r,o)){b.clear(),E=!0;return}(0,pod_probe_1.podConnectionVerdict)(F,v).then(G=>{if(E)return;const J=Date.now()-w>=A-250;if(G==="alive"&&h<2&&W&&!J&&!edgeGone(r,o)){console.log(`[gateway] pod ${i} of ${e.project}: ${v.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),M();return}if(b.clear(),E=!0,G==="gone"){j(v.message);const K=s(()=>{q(e,r,i,{kind:"pod-evicted",status:502,reason:`${v.message} \u2014 its socket no longer accepts (the pod process is gone)${n?`; the request had already been moved here from ${n}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} is gone (${v.message}) \u2014 retry.`)},"giveUp");if(n||!W||J){K();return}L(e,p).then(z=>{if(!edgeGone(r,o)){if(!z){K();return}I(e,r,i,path.basename(z,".sock"),v.message),m(z,i)}},K);return}console.error(`[gateway] pod ${i} of ${e.project}: ${v.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");M()},"onPod");m(y);return}const S=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let g=0,_=!1;const P=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,o))return;const a=e.target,y={target:a,autoRewrite:typeof a=="string",changeOrigin:!0,agent:H(a)};B.web(r,o,y,m=>{if(isConnError(m)&&(g+=1),e.lens&&isConnError(m)&&P&&g>=2&&!_&&!edgeGone(r,o)){_=!0,V(e).then(u=>{if(!edgeGone(r,o)){if(u==="ready"){l();return}if(u==="unsupported"&&Date.now()<S){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,m,u)}});return}if(isConnError(m)&&P&&Date.now()<S&&!edgeGone(r,o)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||k(e,r,o,a,m)})},"tryTcp");l()},"forward"),agentForTarget:H,destroyAgents:s(()=>{x.stop(),T.destroy(),N.destroy()},"destroyAgents"),closeProxy:s(()=>{try{B.close?.()}catch{}},"closeProxy")}}O(createProxy,"createProxy"),s(createProxy,"createProxy");
@@ -1,3 +1,3 @@
1
- "use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:v,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:v,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:te,keyToProject:oe,keyToWorkspace:ne}=(0,service_keys_1.loadServiceKeys)(r.root,v,i),ae=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:v,serviceKeys:te,keyToProject:oe,keyToWorkspace:ne,clientIp:ae,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(v,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ie=createClientErrorLogGate(),x=h2BudgetFromEnv(),ce=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ce(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),N=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>N.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),N.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,N),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),le=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[le,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),re=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=N.startSweeper();await k.bootSingletonsAndApps();for(const e of v)e.decl.eager&&N.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",re),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ie(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
1
+ "use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:N,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:N,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:te,keyToProject:oe,keyToWorkspace:ne}=(0,service_keys_1.loadServiceKeys)(r.root,N,i),ae=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:N,serviceKeys:te,keyToProject:oe,keyToWorkspace:ne,clientIp:ae,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(N,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ie=createClientErrorLogGate(),x=h2BudgetFromEnv(),ce=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ce(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),v=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>v.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),v.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,v),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),le=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[le,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),re=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=v.startSweeper();await k.bootSingletonsAndApps();for(const e of N)e.decl.eager&&v.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",re),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ie(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
2
2
  \r
3
- `),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,C=0;u.on("stream",M=>{G.admitStream(u,M)&&M.once("close",()=>{u.destroyed?C+=1:M.rstCode===E&&(g+=1)})}),u.once("close",()=>{C!==0&&(console.warn(`[gateway] h2 session closed with ${C} stream(s) in flight after ${g} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),w.emit("warning",`h2 session lost with ${C} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:C,clientResets:g}))}),u.on("error",M=>{const pe=M.code??M.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:v,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of v)N.killSpawned(e,"gateway shutdown");await N.stopBuildHost(),await k.reapAll(),re.closeTunnels(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
3
+ `),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,C=0;u.on("stream",M=>{G.admitStream(u,M)&&M.once("close",()=>{u.destroyed?C+=1:M.rstCode===E&&(g+=1)})}),u.once("close",()=>{C!==0&&(console.warn(`[gateway] h2 session closed with ${C} stream(s) in flight after ${g} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),w.emit("warning",`h2 session lost with ${C} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:C,clientResets:g}))}),u.on("error",M=>{const pe=M.code??M.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:N,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of N)v.killSpawned(e,"gateway shutdown");v.stopRetiringTrees(),await v.stopBuildHost(),await k.reapAll(),re.closeTunnels(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
@@ -107,6 +107,12 @@ export interface SockPool {
107
107
  socks: string[];
108
108
  idx: number;
109
109
  scannedAt: number;
110
+ /**
111
+ * Sockets this gateway has STOPPED ROUTING TO — a generation it replaced at a handoff, a tree it stopped —
112
+ * each with the identity of the file it meant (discovery.ts `stopRoutingTo`). A scan never hands one out
113
+ * again; an entry goes when its file does, or when another pod has bound the path since.
114
+ */
115
+ retired?: Map<string, string>;
110
116
  }
111
117
  export interface Route {
112
118
  /** The workspace the route belongs to (its key) — whose edge middleware runs on it (workspace-middleware.ts). */
@@ -1 +1 @@
1
- "use strict";var ie=Object.defineProperty;var c=(e,r)=>ie(e,"name",{value:r,configurable:!0});var se=Object.defineProperty,o=c((e,r)=>se(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");const frozen=o(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),U=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),B=e.configEnv||"development",R=path.join(i,"config",`config.${B}.yaml`),$=3050;let d=$,l=r.projectName,S={};fs.existsSync(R)?(S=yaml.load(fs.readFileSync(R,"utf8")),d=Number(S.port)||d,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${R} \u2014 public devserver port defaults to ${$}. If another service also lacks its config, both bind ${$} \u2192 EADDRINUSE. Add config/config.${B}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const O=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),z=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let T,D;e.gateway?.middleware&&(T=path.resolve(g,e.gateway.middleware),D=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",Z=path.resolve(g,K),W=path.resolve(g,Q),ee=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),q={appName:r.projectName,appRoot:g,outputDir:U,main:Z,tsConfig:W,assets:e.assets||[],additionalEntryPoints:ee,port:d,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let u=null,k=!1,I=!1,m=!1;const V=path.join(__dirname,"../../main.devserver.js"),H=path.join(__dirname,"../../main.devserver.ts"),J=!fs.existsSync(V)&&fs.existsSync(H),x=J?H:V,oe=path.join(U,"main.js");function A(){u&&!u.killed&&(u.kill("SIGTERM"),u=null)}c(A,"H"),o(A,"cleanup");function N(){m=!0,A()}c(N,"k"),o(N,"stop");const te=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>N(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",N);function E(){if(m)return;if(!fs.existsSync(x)){console.error(`[serve] devserver script not found at ${x}`);return}const t=["--enable-source-maps"];J&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(x,{cwd:i,env:{...process.env,PORT:String(d),SERVICE_NAME:l,SERVE_PREFIX:O,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:oe,...T&&{GATEWAY_MIDDLEWARE:T},...D&&{GATEWAY_CONFIG:D},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:I?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});u=n,n.on("exit",(a,f)=>{u===n&&(u=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!u&&k&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${d} (service: ${l}${O?`, prefix: /${O}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),z?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${l}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!u||u.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),A(),E()})}c(re,"ne"),o(re,"_notifyDevServerReload");let y=null;const M=[];function h(t){if(y){const n=y;y=null,n(t)}else M.push(t)}c(h,"h"),o(h,"pushResult");function Y(){return M.length>0?Promise.resolve(M.shift()):new Promise(t=>{y=t})}c(Y,"ee"),o(Y,"waitForNext");let w=o(()=>{},"closeBuild");function F(){return!k||!u?(k=!0,E(),!0):!1}c(F,"J"),o(F,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(q),{watch:f,...P}=a,s=n(P);I=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const j=o((L,C)=>{if(L){console.error("[serve] Webpack error:",L),h({success:!1});return}const ne=C.hasErrors();if(console.info(C.toString({colors:!0,chunks:!1,assets:!0})),ne){h({success:!1});return}F(),h({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),s.run((L,C)=>{j(L,C),s.close(()=>{})});return}const G=s.watch(a.watchOptions||{},j);w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"A"),o(b,"compileOnItsOwn");function X(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:W,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),I=s.sourceMaps,f.built(s),!s.ok){a.tellWatching(),h({success:!1});return}const j=s.hash!==P;P=s.hash;const G=F();a.built(j&&!G),h({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(c(X,"re"),o(X,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:q,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?X(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await Y()}finally{N(),w(),te.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
1
+ "use strict";var ie=Object.defineProperty;var c=(e,r)=>ie(e,"name",{value:r,configurable:!0});var se=Object.defineProperty,o=c((e,r)=>se(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");const frozen=o(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),U=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),B=e.configEnv||"development",R=path.join(i,"config",`config.${B}.yaml`),$=3050;let u=$,l=r.projectName,S={};fs.existsSync(R)?(S=yaml.load(fs.readFileSync(R,"utf8")),u=Number(S.port)||u,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${R} \u2014 public devserver port defaults to ${$}. If another service also lacks its config, both bind ${$} \u2192 EADDRINUSE. Add config/config.${B}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const O=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),z=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let T,D;e.gateway?.middleware&&(T=path.resolve(g,e.gateway.middleware),D=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",Z=path.resolve(g,K),q=path.resolve(g,Q),ee=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),W={appName:r.projectName,appRoot:g,outputDir:U,main:Z,tsConfig:q,assets:e.assets||[],additionalEntryPoints:ee,port:u,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let d=null,k=!1,I=!1,m=!1;const V=path.join(__dirname,"../../main.devserver.js"),H=path.join(__dirname,"../../main.devserver.ts"),J=!fs.existsSync(V)&&fs.existsSync(H),x=J?H:V,oe=path.join(U,"main.js");function A(){d&&!d.killed&&(d.kill("SIGTERM"),d=null)}c(A,"H"),o(A,"cleanup");function N(){m=!0,A()}c(N,"k"),o(N,"stop");const te=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>N(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",N);function E(){if(m)return;if(!fs.existsSync(x)){console.error(`[serve] devserver script not found at ${x}`);return}const t=["--enable-source-maps"];J&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(x,{cwd:i,env:{...process.env,PORT:String(u),SERVICE_NAME:l,SERVE_PREFIX:O,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:oe,...T&&{GATEWAY_MIDDLEWARE:T},...D&&{GATEWAY_CONFIG:D},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:I?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});d=n,n.on("exit",(a,f)=>{d===n&&(d=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!d&&k&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${u} (service: ${l}${O?`, prefix: /${O}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),z?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${l}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!d||d.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${u}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),A(),E()})}c(re,"ne"),o(re,"_notifyDevServerReload");let y=null;const M=[];function h(t){if(y){const n=y;y=null,n(t)}else M.push(t)}c(h,"h"),o(h,"pushResult");function Y(){return M.length>0?Promise.resolve(M.shift()):new Promise(t=>{y=t})}c(Y,"ee"),o(Y,"waitForNext");let w=o(()=>{},"closeBuild");function F(){return!k||!d?(k=!0,E(),!0):!1}c(F,"J"),o(F,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(W),{watch:f,...P}=a,s=n(P);I=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const j=o((L,C)=>{if(L){console.error("[serve] Webpack error:",L),h({success:!1});return}const ne=C.hasErrors();if(console.info(C.toString({colors:!0,chunks:!1,assets:!0})),ne){h({success:!1});return}F(),h({success:!0,baseUrl:`http://localhost:${u}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),s.run((L,C)=>{j(L,C),s.close(()=>{})});return}const G=s.watch(a.watchOptions||{},j);w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"A"),o(b,"compileOnItsOwn");function X(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(u,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:q,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),I=s.sourceMaps,f.built(s),!s.ok){a.failed(),h({success:!1});return}const j=s.hash!==P;P=s.hash;const G=F();a.built(j&&!G),h({success:!0,baseUrl:`http://localhost:${u}`});return}default:return}})}if(c(X,"re"),o(X,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:W,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?X(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await Y()}finally{N(),w(),te.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * How long a marker is a request. The gateway signals within milliseconds of writing it; a marker older than this
3
+ * is a leftover (a gateway that died between the write and the signal), and a leftover must not turn a later
4
+ * `gateway stop` into a fifteen-minute drain.
5
+ */
6
+ export declare const HANDOFF_MARKER_FRESH_MS = 60000;
7
+ /** `<pool dir>/retire-g<generation>` — named by generation, so the standby beside it is never addressed. */
8
+ export declare function handoffRetireMarker(poolDir: string, generation: number): string;
9
+ /** Gateway: ask generation `generation` to retire on the SIGTERM that follows. `false` when it could not be written. */
10
+ export declare function requestHandoffRetire(poolDir: string, generation: number, now?: number): boolean;
11
+ /** Either end: the request is spent (the tree is gone) or was never one (a tree that is just starting). */
12
+ export declare function clearHandoffRetire(poolDir: string, generation: number): void;
13
+ /** Devserver: was THIS generation asked to retire, just now? */
14
+ export declare function handoffRetireRequested(poolDir: string, generation: number, now?: number): boolean;
15
+ /**
16
+ * How long the gateway lets a retired tree run before it kills the group. The tree bounds itself — a pod drains for
17
+ * `LENSMCP_SWAP_DRAIN_MS` (15 min by default: a queue job holds a lock that outlives a killed worker by an hour),
18
+ * then closes — so this is the backstop behind that bound, 90 s later. `LENSMCP_POD_HANDOFF_DRAIN_MS` overrides it;
19
+ * `0` turns the graceful retire off (the old tree gets a plain SIGTERM, as before).
20
+ */
21
+ export declare function handoffDrainBoundMs(env?: NodeJS.ProcessEnv): number;
@@ -0,0 +1 @@
1
+ "use strict";var a=Object.defineProperty;var o=(e,r)=>a(e,"name",{value:r,configurable:!0});var i=Object.defineProperty,n=o((e,r)=>i(e,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.HANDOFF_MARKER_FRESH_MS=void 0,exports.handoffRetireMarker=handoffRetireMarker,exports.requestHandoffRetire=requestHandoffRetire,exports.clearHandoffRetire=clearHandoffRetire,exports.handoffRetireRequested=handoffRetireRequested,exports.handoffDrainBoundMs=handoffDrainBoundMs;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.HANDOFF_MARKER_FRESH_MS=6e4;function handoffRetireMarker(e,r){return path.join(e,`retire-g${r}`)}o(handoffRetireMarker,"handoffRetireMarker"),n(handoffRetireMarker,"handoffRetireMarker");function requestHandoffRetire(e,r,t=Date.now()){try{return fs.writeFileSync(handoffRetireMarker(e,r),String(t),{mode:384}),!0}catch{return!1}}o(requestHandoffRetire,"requestHandoffRetire"),n(requestHandoffRetire,"requestHandoffRetire");function clearHandoffRetire(e,r){try{fs.unlinkSync(handoffRetireMarker(e,r))}catch{}}o(clearHandoffRetire,"clearHandoffRetire"),n(clearHandoffRetire,"clearHandoffRetire");function handoffRetireRequested(e,r,t=Date.now()){try{const f=Number(fs.readFileSync(handoffRetireMarker(e,r),"utf8").trim());return Number.isFinite(f)&&f>0&&t-f<exports.HANDOFF_MARKER_FRESH_MS}catch{return!1}}o(handoffRetireRequested,"handoffRetireRequested"),n(handoffRetireRequested,"handoffRetireRequested");function handoffDrainBoundMs(e=process.env){const r=Number(e.LENSMCP_POD_HANDOFF_DRAIN_MS);if(e.LENSMCP_POD_HANDOFF_DRAIN_MS!==void 0&&e.LENSMCP_POD_HANDOFF_DRAIN_MS!==""&&Number.isFinite(r)&&r>=0)return Math.floor(r);const t=Number(e.LENSMCP_SWAP_DRAIN_MS);return(e.LENSMCP_SWAP_DRAIN_MS!==void 0&&e.LENSMCP_SWAP_DRAIN_MS!==""&&Number.isFinite(t)&&t>=0?t:15*6e4)+9e4}o(handoffDrainBoundMs,"handoffDrainBoundMs"),n(handoffDrainBoundMs,"handoffDrainBoundMs");