@lensmcp/cluster 1.18.4 → 1.18.6

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.
Files changed (64) hide show
  1. package/basic-ssl.js +1 -241
  2. package/build-scope-patterns.js +1 -40
  3. package/create-webpack-dev.js +1 -186
  4. package/create-webpack-prod.js +1 -169
  5. package/executors/build/build.impl.js +1 -98
  6. package/executors/gateway/gateway-errors.js +1 -43
  7. package/executors/gateway/gateway.impl.js +1 -53
  8. package/executors/gateway/gateway.lib.js +1 -29
  9. package/executors/gateway/health-check.js +1 -66
  10. package/executors/gateway/jwks-verify.js +1 -121
  11. package/executors/gateway/main.prod-gateway.js +2 -573
  12. package/executors/gateway/main.rollout.js +11 -117
  13. package/executors/gateway/manifest.js +1 -374
  14. package/executors/gateway/metrics.js +1 -56
  15. package/executors/gateway/otel-tracing.js +1 -74
  16. package/executors/gateway/prod-gateway.lib.js +1 -22
  17. package/executors/gateway/prod-runtime/access-log.js +1 -24
  18. package/executors/gateway/prod-runtime/app.js +1 -123
  19. package/executors/gateway/prod-runtime/auth.js +1 -51
  20. package/executors/gateway/prod-runtime/cors.js +1 -40
  21. package/executors/gateway/prod-runtime/edge.js +1 -65
  22. package/executors/gateway/prod-runtime/handler.js +1 -226
  23. package/executors/gateway/prod-runtime/hooks.js +1 -42
  24. package/executors/gateway/prod-runtime/observability.js +1 -125
  25. package/executors/gateway/prod-runtime/rollout.js +1 -103
  26. package/executors/gateway/prod-runtime/routing.js +1 -40
  27. package/executors/gateway/prod-runtime/server.js +1 -79
  28. package/executors/gateway/prod-runtime/trust.js +1 -32
  29. package/executors/gateway/prod-runtime/types.js +1 -2
  30. package/executors/gateway/prod-runtime/upgrade.js +4 -116
  31. package/executors/gateway/prod-runtime/upstream.js +1 -21
  32. package/executors/gateway/providers-prod.js +1 -232
  33. package/executors/gateway/rate-limit.js +2 -75
  34. package/executors/gateway/registry-source.js +1 -131
  35. package/executors/gateway/rollout-ops.js +2 -167
  36. package/executors/gateway/runtime/auth.js +1 -64
  37. package/executors/gateway/runtime/chooser.js +12 -45
  38. package/executors/gateway/runtime/control.js +1 -128
  39. package/executors/gateway/runtime/dev-auth.js +1 -108
  40. package/executors/gateway/runtime/discovery.js +1 -123
  41. package/executors/gateway/runtime/edge.js +1 -47
  42. package/executors/gateway/runtime/handler.js +1 -183
  43. package/executors/gateway/runtime/hooks.js +1 -55
  44. package/executors/gateway/runtime/lens-children.js +1 -651
  45. package/executors/gateway/runtime/lifecycle.js +3 -842
  46. package/executors/gateway/runtime/observability.js +2 -148
  47. package/executors/gateway/runtime/pod-env.js +2 -89
  48. package/executors/gateway/runtime/proxy.js +1 -457
  49. package/executors/gateway/runtime/route-registry.js +1 -72
  50. package/executors/gateway/runtime/scope.js +1 -117
  51. package/executors/gateway/runtime/server.js +3 -487
  52. package/executors/gateway/runtime/service-keys.js +1 -49
  53. package/executors/gateway/runtime/types.js +1 -151
  54. package/executors/gateway/runtime/upgrade.js +1 -71
  55. package/executors/gateway/runtime/workspace-registry.js +1 -99
  56. package/executors/gateway/ssrf-guard.js +1 -190
  57. package/executors/serve/serve.impl.js +1 -280
  58. package/executors/trust/trust.impl.js +4 -162
  59. package/gateway.js +1 -35
  60. package/index.js +1 -16
  61. package/main.devserver.js +10 -1117
  62. package/package.json +4 -3
  63. package/tsgo-check-plugin.js +4 -364
  64. package/typecheck-bus.js +4 -256
@@ -1,183 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createHandler = createHandler;
4
- const manifest_1 = require("../manifest");
5
- const gateway_errors_1 = require("../gateway-errors");
6
- const discovery_1 = require("./discovery");
7
- const edge_1 = require("./edge");
8
- const observability_1 = require("./observability");
9
- const chooser_1 = require("./chooser");
10
- function createHandler(deps) {
11
- const { rt, obs, auth, proxy, hooks } = deps;
12
- const match = (req) => (0, manifest_1.matchRoute)(rt.routes, (req.headers.host || '').split(':')[0], req.url ?? '/');
13
- // Host-root infra: when a host only has PATH-mounted services (pure
14
- // microservices under api.<domain>/{prefix}), the gateway itself owns the
15
- // root — CORS, health aggregation + service discovery, like a real edge.
16
- const handleNoRoute = (req, res) => {
17
- // gateway-owned endpoints are cross-origin by design (the app calls
18
- // api.<domain>/health from app.<domain>) — answer CORS here; routed
19
- // paths inherit CORS from the services themselves.
20
- (0, edge_1.cors)(req, res);
21
- if (req.method === 'OPTIONS') {
22
- res.writeHead(204, {
23
- 'access-control-allow-methods': edge_1.PREFLIGHT_METHODS_NOROUTE,
24
- 'access-control-allow-headers': String(req.headers['access-control-request-headers'] ?? '*'),
25
- });
26
- res.end();
27
- return;
28
- }
29
- const host = (req.headers.host || '').split(':')[0];
30
- const mounted = rt.routes.filter((r) => r.host && (0, manifest_1.hostMatches)(r.host, host) && r.prefix);
31
- const url = (req.url ?? '/').split('?')[0];
32
- // The lens dashboard HOST ROOT → the multi-workspace chooser. The dashboard itself mounts at `/<key>`,
33
- // so the bare `/` is unrouted and lands here; serve the landing page listing every known workspace.
34
- if ((url === '/' || url === '/index.html' || url === '') &&
35
- rt.routes.some((r) => r.host && (0, manifest_1.hostMatches)(r.host, host) && r.project === observability_1.LENS_DASHBOARD_PROJECT)) {
36
- (0, chooser_1.serveChooser)(res, rt.wsKey);
37
- return;
38
- }
39
- if (mounted.length > 0 && (url === '/health' || url === '/healthz')) {
40
- const services = mounted.map((r) => ({
41
- project: r.project,
42
- prefix: r.prefix,
43
- pods: r.pool ? ((0, discovery_1.pickSock)(r.pool, 0), r.pool.socks.length) : undefined,
44
- state: r.svc?.state ?? (r.target ? 'up' : 'unknown'),
45
- }));
46
- const ok = services.every((s) => s.state !== 'down' || true);
47
- res.writeHead(ok ? 200 : 503, { 'content-type': 'application/json' });
48
- res.end(JSON.stringify({ status: 'ok', host, services }, null, 2));
49
- return;
50
- }
51
- if (mounted.length > 0 && url === '/.well-known/services') {
52
- res.writeHead(200, { 'content-type': 'application/json' });
53
- res.end(JSON.stringify({ host, services: mounted.map((r) => ({ prefix: r.prefix, project: r.project, internal: !!r.internal })) }, null, 2));
54
- return;
55
- }
56
- (0, edge_1.sendError)(res, req, 404, 'not_found', mounted.length > 0
57
- ? `No service mounted at ${url}. Mounted on ${host}: ${[...new Set(mounted.map((r) => r.prefix))].join(', ')} (+ /health, /.well-known/services)`
58
- : 'No gateway route for this hostname.');
59
- };
60
- const runPipeline = async (req, res) => {
61
- // 0. HTTP/2 carries the host in the `:authority` pseudo-header, NOT `host` — the
62
- // http2 compat layer exposes it as `req.authority` and omits `host` from `req.headers`.
63
- // Every downstream step (route match, trace, CORS) reads `req.headers.host`, so
64
- // normalize it here before anything else. No-op for HTTP/1.1 (host is already set).
65
- if (!req.headers.host) {
66
- const authority = req.authority ?? req.headers[':authority'];
67
- if (authority)
68
- req.headers.host = authority;
69
- }
70
- // 1. STRIP client-supplied trust headers before anything else: the gateway is
71
- // the ONLY party allowed to assert `x-internal-token` (came through me) and
72
- // `x-api-key-id` (validated caller identity). Without this, a keyless/TCP route
73
- // would leak a spoofed token and any public route would forward a spoofed caller
74
- // id to downstream audit. The gateway re-stamps them below where earned.
75
- for (const h of edge_1.GATEWAY_OWNED_HEADERS)
76
- delete req.headers[h];
77
- // 2. Mint a trace id for EVERY request (not just lens-traced ones): forwarded to the
78
- // pod (adopted as its traceId) and returned on the response — any request is traceable.
79
- (0, gateway_errors_1.ensureRequestId)(req);
80
- const trace = obs.beginTrace(req);
81
- if (trace) {
82
- obs.traceStep(trace, 'received', { host: trace.host, method: trace.method, url: trace.url });
83
- req.__lensmcpTrace = trace;
84
- }
85
- const ctx = hooks.httpContext(req, res, trace);
86
- try {
87
- // 3. onRequest (sees already-stripped headers; may short-circuit)
88
- if (hooks.has('onRequest')) {
89
- await hooks.run('onRequest', ctx);
90
- if (ctx.responded())
91
- return;
92
- }
93
- // 4. route match
94
- const route = match(req);
95
- if (!route) {
96
- handleNoRoute(req, res);
97
- return;
98
- }
99
- ctx.route = route;
100
- obs.traceStep(trace, 'route-match', {
101
- project: route.project,
102
- ...(route.prefix ? { prefix: route.prefix } : {}),
103
- ...(route.host ? { routeHost: route.host } : {}),
104
- ...(route.internal ? { internal: true } : {}),
105
- });
106
- // 7. CORS preflight on a ROUTED path: answer it HERE, BEFORE edge auth — an OPTIONS preflight
107
- // carries no token by spec, so the JWT (or x-api-key) enforcement below would 401 it and the
108
- // browser's CORS check fails. The matching real request still goes through auth normally.
109
- if (req.method === 'OPTIONS') {
110
- (0, edge_1.cors)(req, res);
111
- res.writeHead(204, {
112
- 'access-control-allow-methods': edge_1.PREFLIGHT_METHODS_ROUTED,
113
- 'access-control-allow-headers': String(req.headers['access-control-request-headers'] ?? '*'),
114
- });
115
- res.end();
116
- return;
117
- }
118
- // 8. CORS on EVERY routed response — including the 401/403/5xx the gateway generates ITSELF
119
- // below. Without this a gateway-generated error short-circuits before the upstream (which
120
- // carries CORS on success), so the browser sees the error WITHOUT an Access-Control-Allow-Origin
121
- // and reports a CORS failure that MASKS the real status. On success the upstream's CORS header
122
- // overwrites this one (http-proxy setHeader), so there's no duplicate.
123
- (0, edge_1.cors)(req, res);
124
- // 9. beforeAuth
125
- if (hooks.has('beforeAuth')) {
126
- await hooks.run('beforeAuth', ctx);
127
- if (ctx.responded())
128
- return;
129
- }
130
- // 10/11. auth + authz. internal.* validates the caller's api-key; public verifies the end-user
131
- // JWT + enforces per-route rules. Custom middleware (afterAuth) runs AFTER core auth, public ONLY.
132
- if (route.internal) {
133
- const v = auth.internal(req, route, trace);
134
- if (!v.ok) {
135
- obs.finishTrace(trace, route.project, v.status);
136
- (0, edge_1.sendError)(res, req, v.status, v.reason, v.detail);
137
- return;
138
- }
139
- ctx.caller = v.caller;
140
- }
141
- else {
142
- const v = auth.public(req, route, trace);
143
- if (!v.ok) {
144
- obs.finishTrace(trace, route.project, v.status);
145
- (0, edge_1.sendError)(res, req, v.status, v.reason, v.detail);
146
- return;
147
- }
148
- ctx.claims = v.claims;
149
- if (hooks.has('afterAuth')) {
150
- await hooks.run('afterAuth', ctx);
151
- if (ctx.responded())
152
- return;
153
- }
154
- }
155
- // 12. inject the target service's key so pods trust the gateway (pool routes, both branches)
156
- if (route.pool && rt.serviceKeys[route.project]) {
157
- req.headers['x-internal-token'] = rt.serviceKeys[route.project];
158
- }
159
- // 13. beforeForward (last chance to mutate headers/url)
160
- if (hooks.has('beforeForward')) {
161
- await hooks.run('beforeForward', ctx);
162
- if (ctx.responded())
163
- return;
164
- }
165
- // 15. onResponse — fire user handlers once the response closes (the built-in
166
- // recordEdge/finishTrace live in proxy.forward's own close handler).
167
- if (hooks.has('onResponse')) {
168
- res.on('close', () => { ctx.status = res.statusCode ?? 0; void hooks.run('onResponse', ctx); });
169
- }
170
- // 14. forward
171
- void proxy.forward(route, req, res);
172
- }
173
- catch (e) {
174
- // A hook (or the pipeline) threw — narrate via onError, then 500 if nobody responded.
175
- ctx.error = e;
176
- if (hooks.has('onError'))
177
- await hooks.run('onError', ctx);
178
- if (!ctx.responded())
179
- (0, edge_1.sendError)(res, req, 500, 'internal', e.message);
180
- }
181
- };
182
- return (req, res) => { void runPipeline(req, res); };
183
- }
1
+ "use strict";var E=Object.defineProperty;var d=(l,i)=>E(l,"name",{value:i,configurable:!0});var v=Object.defineProperty,h=d((l,i)=>v(l,"name",{value:i,configurable:!0}),"h");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),gateway_errors_1=require("../gateway-errors"),discovery_1=require("./discovery"),edge_1=require("./edge"),observability_1=require("./observability"),chooser_1=require("./chooser");function createHandler(l){const{rt:i,obs:u,auth:p,proxy:f,hooks:a}=l,w=h(e=>(0,manifest_1.matchRoute)(i.routes,(e.headers.host||"").split(":")[0],e.url??"/"),"match"),m=h((e,o)=>{if((0,edge_1.cors)(e,o),e.method==="OPTIONS"){o.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),o.end();return}const n=(e.headers.host||"").split(":")[0],s=i.routes.filter(r=>r.host&&(0,manifest_1.hostMatches)(r.host,n)&&r.prefix),t=(e.url??"/").split("?")[0];if((t==="/"||t==="/index.html"||t==="")&&i.routes.some(r=>r.host&&(0,manifest_1.hostMatches)(r.host,n)&&r.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(o,i.wsKey);return}if(s.length>0&&(t==="/health"||t==="/healthz")){const r=s.map(c=>({project:c.project,prefix:c.prefix,pods:c.pool?((0,discovery_1.pickSock)(c.pool,0),c.pool.socks.length):void 0,state:c.svc?.state??(c.target?"up":"unknown")})),_=r.every(c=>c.state!=="down"||!0);o.writeHead(_?200:503,{"content-type":"application/json"}),o.end(JSON.stringify({status:"ok",host:n,services:r},null,2));return}if(s.length>0&&t==="/.well-known/services"){o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({host:n,services:s.map(r=>({prefix:r.prefix,project:r.project,internal:!!r.internal}))},null,2));return}(0,edge_1.sendError)(o,e,404,"not_found",s.length>0?`No service mounted at ${t}. Mounted on ${n}: ${[...new Set(s.map(r=>r.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),y=h(async(e,o)=>{if(!e.headers.host){const t=e.authority??e.headers[":authority"];t&&(e.headers.host=t)}for(const t of edge_1.GATEWAY_OWNED_HEADERS)delete e.headers[t];(0,gateway_errors_1.ensureRequestId)(e);const n=u.beginTrace(e);n&&(u.traceStep(n,"received",{host:n.host,method:n.method,url:n.url}),e.__lensmcpTrace=n);const s=a.httpContext(e,o,n);try{if(a.has("onRequest")&&(await a.run("onRequest",s),s.responded()))return;const t=w(e);if(!t){m(e,o);return}if(s.route=t,u.traceStep(n,"route-match",{project:t.project,...t.prefix?{prefix:t.prefix}:{},...t.host?{routeHost:t.host}:{},...t.internal?{internal:!0}:{}}),e.method==="OPTIONS"){(0,edge_1.cors)(e,o),o.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),o.end();return}if((0,edge_1.cors)(e,o),a.has("beforeAuth")&&(await a.run("beforeAuth",s),s.responded()))return;if(t.internal){const r=p.internal(e,t,n);if(!r.ok){u.finishTrace(n,t.project,r.status),(0,edge_1.sendError)(o,e,r.status,r.reason,r.detail);return}s.caller=r.caller}else{const r=p.public(e,t,n);if(!r.ok){u.finishTrace(n,t.project,r.status),(0,edge_1.sendError)(o,e,r.status,r.reason,r.detail);return}if(s.claims=r.claims,a.has("afterAuth")&&(await a.run("afterAuth",s),s.responded()))return}if(t.pool&&i.serviceKeys[t.project]&&(e.headers["x-internal-token"]=i.serviceKeys[t.project]),a.has("beforeForward")&&(await a.run("beforeForward",s),s.responded()))return;a.has("onResponse")&&o.on("close",()=>{s.status=o.statusCode??0,a.run("onResponse",s)}),f.forward(t,e,o)}catch(t){s.error=t,a.has("onError")&&await a.run("onError",s),s.responded()||(0,edge_1.sendError)(o,e,500,"internal",t.message)}},"runPipeline");return(e,o)=>{y(e,o)}}d(createHandler,"createHandler"),h(createHandler,"createHandler");
@@ -1,55 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createHooks = createHooks;
4
- const edge_1 = require("./edge");
5
- // Terminal phases observe a request whose response is ALREADY complete, so
6
- // `responded()` is true by definition — they must run every handler regardless.
7
- // The request-flow phases short-circuit the moment a handler responds.
8
- const TERMINAL_PHASES = new Set(['onResponse', 'onError']);
9
- function createHooks(hooks) {
10
- const run = async (phase, ctx) => {
11
- const list = hooks[phase];
12
- if (!list || list.length === 0)
13
- return;
14
- const terminal = TERMINAL_PHASES.has(phase);
15
- for (const h of list) {
16
- if (!terminal && ctx.responded())
17
- return; // a request-flow hook already responded → stop
18
- await h(ctx);
19
- }
20
- };
21
- const has = (phase) => (hooks[phase]?.length ?? 0) > 0;
22
- const httpContext = (req, res, trace) => ({
23
- req,
24
- res,
25
- socket: undefined,
26
- trace,
27
- route: undefined,
28
- claims: undefined,
29
- caller: undefined,
30
- pod: undefined,
31
- error: undefined,
32
- status: undefined,
33
- sendError: (status, reason, detail) => (0, edge_1.sendError)(res, req, status, reason, detail),
34
- responded: () => res.writableEnded || res.headersSent,
35
- });
36
- const upgradeContext = (req, socket, trace) => ({
37
- req,
38
- res: undefined,
39
- socket,
40
- trace,
41
- route: undefined,
42
- claims: undefined,
43
- caller: undefined,
44
- pod: undefined,
45
- error: undefined,
46
- status: undefined,
47
- // No HTTP response on an upgrade — short-circuiting means destroying the socket.
48
- sendError: () => { try {
49
- socket.destroy();
50
- }
51
- catch { /* already gone */ } },
52
- responded: () => socket.destroyed,
53
- });
54
- return { run, has, httpContext, upgradeContext };
55
- }
1
+ "use strict";var u=Object.defineProperty;var i=(t,e)=>u(t,"name",{value:e,configurable:!0});var a=Object.defineProperty,n=i((t,e)=>a(t,"name",{value:e,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHooks=createHooks;const edge_1=require("./edge"),TERMINAL_PHASES=new Set(["onResponse","onError"]);function createHooks(t){return{run:n(async(e,r)=>{const o=t[e];if(!o||o.length===0)return;const d=TERMINAL_PHASES.has(e);for(const s of o){if(!d&&r.responded())return;await s(r)}},"run"),has:n(e=>(t[e]?.length??0)>0,"has"),httpContext:n((e,r,o)=>({req:e,res:r,socket:void 0,trace:o,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n((d,s,c)=>(0,edge_1.sendError)(r,e,d,s,c),"sendError"),responded:n(()=>r.writableEnded||r.headersSent,"responded")}),"httpContext"),upgradeContext:n((e,r,o)=>({req:e,res:void 0,socket:r,trace:o,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n(()=>{try{r.destroy()}catch{}},"sendError"),responded:n(()=>r.destroyed,"responded")}),"upgradeContext")}}i(createHooks,"createHooks"),n(createHooks,"createHooks");