@lensmcp/cluster 1.21.2 → 1.21.3

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.
@@ -187,7 +187,25 @@ export declare function matchRoute<R extends {
187
187
  prefix?: string;
188
188
  apex?: string;
189
189
  }>(routes: readonly R[], host: string, url: string): R | undefined;
190
- /** Specificity sort: host+path beats host-only beats catch-all. Mutates+returns. */
190
+ /**
191
+ * Specificity sort: host+path beats host-only beats catch-all, and among equals the LONGER path
192
+ * wins. Mutates+returns.
193
+ *
194
+ * The length tie-break is what makes `buildRouteTable`'s promise ("sorted by specificity so
195
+ * `matchRoute` picks the most specific") true for two path mounts on ONE host. The rank alone
196
+ * scores the PRESENCE of host/prefix, never its length, so `/api` and `/api/tasks` tie at 3 and
197
+ * `Array.sort` — stable — leaves them in manifest-arrival order. `matchRoute` then takes the FIRST
198
+ * `startsWith` hit, so whichever arrived first swallows the other's whole surface: `/api` catches
199
+ * `/api/tasks/v1`, or `/api/tasks` sorts first and `/api` never matches anything under it.
200
+ *
201
+ * Measured before this tie-break existed (foodguard, 2026-08-31): a second service declaring `/api`
202
+ * beside the incumbent took the ENTIRE mount, and a `@Public` health route on the incumbent began
203
+ * answering 401 because the edge was applying the OTHER service's auth policy to its path. Two
204
+ * services could not share a host prefix at all, in dev or prod — both run this function.
205
+ *
206
+ * Longest-prefix-match is the standard routing semantic and the one the caller already documents;
207
+ * the rank ordering above is unchanged, so only the previously-arbitrary tie moves.
208
+ */
191
209
  export declare function sortBySpecificity<R extends {
192
210
  host?: string;
193
211
  prefix?: string;
@@ -1 +1 @@
1
- "use strict";var h=Object.defineProperty;var u=(t,e)=>h(t,"name",{value:e,configurable:!0});var f=Object.defineProperty,a=u((t,e)=>f(t,"name",{value:e,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.smoothWeightedSelector=smoothWeightedSelector,exports.hostMatches=hostMatches,exports.matchRoute=matchRoute,exports.sortBySpecificity=sortBySpecificity,exports.authForPath=authForPath,exports.authPathMatches=authPathMatches,exports.authRuleFor=authRuleFor,exports.authRuleForPath=authRuleForPath,exports.buildRouteTable=buildRouteTable,exports.manifestFromClusterDecl=manifestFromClusterDecl,exports.matchCidr=matchCidr,exports.evalRule=evalRule,exports.pickTargetedVersion=pickTargetedVersion,exports.fnv1a=fnv1a,exports.bucketByWeight=bucketByWeight;function smoothWeightedSelector(t){const e=t.map(()=>0),r=t.reduce((o,n)=>o+Math.max(0,n),0);return()=>{if(r<=0||t.length===0)return 0;let o=0;for(let n=0;n<t.length;n++)e[n]+=Math.max(0,t[n]),e[n]>e[o]&&(o=n);return e[o]-=r,o}}u(smoothWeightedSelector,"smoothWeightedSelector"),a(smoothWeightedSelector,"smoothWeightedSelector");function hostMatches(t,e){return t.startsWith("*.")?e.endsWith(t.slice(1))||e===t.slice(2):e===t}u(hostMatches,"hostMatches"),a(hostMatches,"hostMatches");function matchRoute(t,e,r){const o=t.some(s=>s.host&&hostMatches(s.host,e));let n;if(o)n=t.filter(s=>s.host&&hostMatches(s.host,e)),n.some(s=>s.host===e)&&(n=n.filter(s=>s.host===e));else{const s=t.filter(i=>!i.host);if(s.length<=1)n=s;else{const i=s.filter(l=>l.apex&&(e===l.apex||e.endsWith("."+l.apex))).sort((l,c)=>c.apex.length-l.apex.length);n=i.length>0?i:s.filter(l=>!l.apex)}}return n.find(s=>!s.prefix||r.startsWith(s.prefix))}u(matchRoute,"matchRoute"),a(matchRoute,"matchRoute");function sortBySpecificity(t){return t.sort((e,r)=>(r.host?2:0)+(r.prefix?1:0)-((e.host?2:0)+(e.prefix?1:0))),t}u(sortBySpecificity,"sortBySpecificity"),a(sortBySpecificity,"sortBySpecificity");function authForPath(t,e,r){if(r)return"internal";const o=t.auth?.overrides??[];for(const n of o)if(e.startsWith(n.path))return n.mode;return t.auth?.default??"jwt"}u(authForPath,"authForPath"),a(authForPath,"authForPath");function authPathMatches(t,e){const r=t.split("/").filter(Boolean),o=(e.split("?")[0]??"").split("/").filter(Boolean);if(o.length<r.length)return!1;for(let n=0;n<r.length;n++){const s=r[n];if(!(s==="*"||s.startsWith(":"))&&s!==o[n])return!1}return!0}u(authPathMatches,"authPathMatches"),a(authPathMatches,"authPathMatches");function authRuleFor(t,e,r){const o=r.toUpperCase();for(const n of t?.overrides??[]){if(!authPathMatches(n.path,e)||n.methods&&!n.methods.some(i=>i.toUpperCase()===o))continue;const s=n.rule??(n.permission?{attr:"perms",op:"hastoken",value:n.permission}:void 0);return{mode:n.mode??t?.default??"jwt",...s?{rule:s}:{}}}return{mode:t?.default??"jwt"}}u(authRuleFor,"authRuleFor"),a(authRuleFor,"authRuleFor");function authRuleForPath(t,e,r,o){return o?{mode:"internal"}:authRuleFor(t.auth,e,r)}u(authRuleForPath,"authRuleForPath"),a(authRuleForPath,"authRuleForPath");function buildRouteTable(t){const e=[];for(const r of t){const o=r.auth?.default??"jwt";for(const n of r.mounts){const s={service:r.service,...n.path?{prefix:n.path}:{},...r.prependPrefix?{prependPrefix:r.prependPrefix}:{},...r.upstream?{upstream:r.upstream}:{}};e.push({...s,host:n.host,auth:o}),r.internal&&r.internalHost!==!1&&e.push({...s,host:typeof r.internalHost=="string"?r.internalHost:`internal.${n.host}`,internal:!0,auth:"internal"})}r.default&&e.push({service:r.service,...r.prependPrefix?{prependPrefix:r.prependPrefix}:{},...r.upstream?{upstream:r.upstream}:{},default:!0,auth:o})}return sortBySpecificity(e)}u(buildRouteTable,"buildRouteTable"),a(buildRouteTable,"buildRouteTable");function manifestFromClusterDecl(t,e){const r=e.upstream??(e.port?`http://localhost:${e.port}`:void 0);if(!(!e.service&&!r))return{service:e.service??t,mounts:e.host?[{host:e.host,...e.path?{path:e.path}:{}}]:[],internal:!!e.service,...e.internalHost!==void 0?{internalHost:e.internalHost}:{},...e.default?{default:!0}:{},...e.prependPrefix?{prependPrefix:e.prependPrefix}:{},...e.auth?{auth:e.auth}:{},...r?{upstream:r}:{}}}u(manifestFromClusterDecl,"manifestFromClusterDecl"),a(manifestFromClusterDecl,"manifestFromClusterDecl");const isGroup=a(t=>"all"in t||"any"in t||"not"in t,"isGroup"),reCache=new Map,compileRe=a(t=>{if(t.length>512)return null;if(reCache.has(t))return reCache.get(t);let e;try{e=new RegExp(t)}catch{e=null}return reCache.set(t,e),e},"compileRe");function ipToInt(t){const e=(t.startsWith("::ffff:")?t.slice(7):t).split(".");if(e.length!==4)return null;let r=0;for(const o of e){const n=Number(o);if(!Number.isInteger(n)||n<0||n>255||o.length>1&&o[0]==="0")return null;r=r*256+n}return r>>>0}u(ipToInt,"ipToInt"),a(ipToInt,"ipToInt");function matchCidr(t,e){const r=e.indexOf("/");if(r<0)return!1;const o=Number(e.slice(r+1));if(!Number.isInteger(o)||o<0||o>32)return!1;const n=ipToInt(t),s=ipToInt(e.slice(0,r));if(n===null||s===null)return!1;if(o===0)return!0;const i=(o===32?4294967295:~((1<<32-o)-1))>>>0;return(n&i)>>>0===(s&i)>>>0}u(matchCidr,"matchCidr"),a(matchCidr,"matchCidr");const str=a(t=>t==null?"":String(t),"str"),eq=a((t,e)=>str(t)===str(e),"eq");function evalCondition(t,e){const r=e[t.attr],o=r!=null;switch(t.op){case"exists":return o;case"ne":return!o||!eq(r,t.value)}if(!o)return!1;switch(t.op){case"eq":return eq(r,t.value);case"in":return Array.isArray(t.value)&&t.value.some(n=>Array.isArray(r)?r.some(s=>eq(s,n)):eq(r,n));case"nin":return!(Array.isArray(t.value)&&t.value.some(n=>Array.isArray(r)?r.some(s=>eq(s,n)):eq(r,n)));case"prefix":return str(r).startsWith(str(t.value));case"suffix":return str(r).endsWith(str(t.value));case"contains":return Array.isArray(r)?r.some(n=>eq(n,t.value)):str(r).includes(str(t.value));case"hastoken":{const n=str(t.value);return Array.isArray(r)?r.some(s=>eq(s,t.value)):str(r).split(/[\s,]+/).filter(Boolean).includes(n)}case"regex":{const n=compileRe(str(t.value));return n?n.test(str(r)):!1}case"gt":return Number(r)>Number(t.value);case"lt":return Number(r)<Number(t.value);case"cidr":return matchCidr(str(r),str(t.value));default:return!1}}u(evalCondition,"evalCondition"),a(evalCondition,"evalCondition");function evalRule(t,e){if(!isGroup(t))return evalCondition(t,e);const r=Array.isArray(t.all)&&t.all.length?t.all:void 0,o=Array.isArray(t.any)&&t.any.length?t.any:void 0,n=t.not&&Object.keys(t.not).length?t.not:void 0;return n&&evalRule(n,e)||r&&!r.every(s=>evalRule(s,e))||o&&!o.some(s=>evalRule(s,e))?!1:!!(r||o||n)}u(evalRule,"evalRule"),a(evalRule,"evalRule");function pickTargetedVersion(t,e){for(const r of t)if(evalRule(r.when,e))return{version:r.version,...r.name?{rule:r.name}:{}}}u(pickTargetedVersion,"pickTargetedVersion"),a(pickTargetedVersion,"pickTargetedVersion");function fnv1a(t){let e=2166136261;for(let r=0;r<t.length;r++)e^=t.charCodeAt(r),e=Math.imul(e,16777619);return e>>>0}u(fnv1a,"fnv1a"),a(fnv1a,"fnv1a");function bucketByWeight(t,e){const r=e.reduce((s,i)=>s+Math.max(0,i),0);if(r<=0||e.length===0)return 0;const o=fnv1a(t)%1e4;let n=0;for(let s=0;s<e.length;s++)if(n+=Math.max(0,e[s])/r*1e4,o<n)return s;return e.length-1}u(bucketByWeight,"bucketByWeight"),a(bucketByWeight,"bucketByWeight");
1
+ "use strict";var h=Object.defineProperty;var a=(t,r)=>h(t,"name",{value:r,configurable:!0});var f=Object.defineProperty,u=a((t,r)=>f(t,"name",{value:r,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.smoothWeightedSelector=smoothWeightedSelector,exports.hostMatches=hostMatches,exports.matchRoute=matchRoute,exports.sortBySpecificity=sortBySpecificity,exports.authForPath=authForPath,exports.authPathMatches=authPathMatches,exports.authRuleFor=authRuleFor,exports.authRuleForPath=authRuleForPath,exports.buildRouteTable=buildRouteTable,exports.manifestFromClusterDecl=manifestFromClusterDecl,exports.matchCidr=matchCidr,exports.evalRule=evalRule,exports.pickTargetedVersion=pickTargetedVersion,exports.fnv1a=fnv1a,exports.bucketByWeight=bucketByWeight;function smoothWeightedSelector(t){const r=t.map(()=>0),e=t.reduce((o,n)=>o+Math.max(0,n),0);return()=>{if(e<=0||t.length===0)return 0;let o=0;for(let n=0;n<t.length;n++)r[n]+=Math.max(0,t[n]),r[n]>r[o]&&(o=n);return r[o]-=e,o}}a(smoothWeightedSelector,"smoothWeightedSelector"),u(smoothWeightedSelector,"smoothWeightedSelector");function hostMatches(t,r){return t.startsWith("*.")?r.endsWith(t.slice(1))||r===t.slice(2):r===t}a(hostMatches,"hostMatches"),u(hostMatches,"hostMatches");function matchRoute(t,r,e){const o=t.some(s=>s.host&&hostMatches(s.host,r));let n;if(o)n=t.filter(s=>s.host&&hostMatches(s.host,r)),n.some(s=>s.host===r)&&(n=n.filter(s=>s.host===r));else{const s=t.filter(i=>!i.host);if(s.length<=1)n=s;else{const i=s.filter(l=>l.apex&&(r===l.apex||r.endsWith("."+l.apex))).sort((l,c)=>c.apex.length-l.apex.length);n=i.length>0?i:s.filter(l=>!l.apex)}}return n.find(s=>!s.prefix||e.startsWith(s.prefix))}a(matchRoute,"matchRoute"),u(matchRoute,"matchRoute");function sortBySpecificity(t){const r=u(e=>(e.host?2:0)+(e.prefix?1:0),"rank");return t.sort((e,o)=>r(o)-r(e)||(o.prefix?.length??0)-(e.prefix?.length??0)),t}a(sortBySpecificity,"sortBySpecificity"),u(sortBySpecificity,"sortBySpecificity");function authForPath(t,r,e){if(e)return"internal";const o=t.auth?.overrides??[];for(const n of o)if(r.startsWith(n.path))return n.mode;return t.auth?.default??"jwt"}a(authForPath,"authForPath"),u(authForPath,"authForPath");function authPathMatches(t,r){const e=t.split("/").filter(Boolean),o=(r.split("?")[0]??"").split("/").filter(Boolean);if(o.length<e.length)return!1;for(let n=0;n<e.length;n++){const s=e[n];if(!(s==="*"||s.startsWith(":"))&&s!==o[n])return!1}return!0}a(authPathMatches,"authPathMatches"),u(authPathMatches,"authPathMatches");function authRuleFor(t,r,e){const o=e.toUpperCase();for(const n of t?.overrides??[]){if(!authPathMatches(n.path,r)||n.methods&&!n.methods.some(i=>i.toUpperCase()===o))continue;const s=n.rule??(n.permission?{attr:"perms",op:"hastoken",value:n.permission}:void 0);return{mode:n.mode??t?.default??"jwt",...s?{rule:s}:{}}}return{mode:t?.default??"jwt"}}a(authRuleFor,"authRuleFor"),u(authRuleFor,"authRuleFor");function authRuleForPath(t,r,e,o){return o?{mode:"internal"}:authRuleFor(t.auth,r,e)}a(authRuleForPath,"authRuleForPath"),u(authRuleForPath,"authRuleForPath");function buildRouteTable(t){const r=[];for(const e of t){const o=e.auth?.default??"jwt";for(const n of e.mounts){const s={service:e.service,...n.path?{prefix:n.path}:{},...e.prependPrefix?{prependPrefix:e.prependPrefix}:{},...e.upstream?{upstream:e.upstream}:{}};r.push({...s,host:n.host,auth:o}),e.internal&&e.internalHost!==!1&&r.push({...s,host:typeof e.internalHost=="string"?e.internalHost:`internal.${n.host}`,internal:!0,auth:"internal"})}e.default&&r.push({service:e.service,...e.prependPrefix?{prependPrefix:e.prependPrefix}:{},...e.upstream?{upstream:e.upstream}:{},default:!0,auth:o})}return sortBySpecificity(r)}a(buildRouteTable,"buildRouteTable"),u(buildRouteTable,"buildRouteTable");function manifestFromClusterDecl(t,r){const e=r.upstream??(r.port?`http://localhost:${r.port}`:void 0);if(!(!r.service&&!e))return{service:r.service??t,mounts:r.host?[{host:r.host,...r.path?{path:r.path}:{}}]:[],internal:!!r.service,...r.internalHost!==void 0?{internalHost:r.internalHost}:{},...r.default?{default:!0}:{},...r.prependPrefix?{prependPrefix:r.prependPrefix}:{},...r.auth?{auth:r.auth}:{},...e?{upstream:e}:{}}}a(manifestFromClusterDecl,"manifestFromClusterDecl"),u(manifestFromClusterDecl,"manifestFromClusterDecl");const isGroup=u(t=>"all"in t||"any"in t||"not"in t,"isGroup"),reCache=new Map,compileRe=u(t=>{if(t.length>512)return null;if(reCache.has(t))return reCache.get(t);let r;try{r=new RegExp(t)}catch{r=null}return reCache.set(t,r),r},"compileRe");function ipToInt(t){const r=(t.startsWith("::ffff:")?t.slice(7):t).split(".");if(r.length!==4)return null;let e=0;for(const o of r){const n=Number(o);if(!Number.isInteger(n)||n<0||n>255||o.length>1&&o[0]==="0")return null;e=e*256+n}return e>>>0}a(ipToInt,"ipToInt"),u(ipToInt,"ipToInt");function matchCidr(t,r){const e=r.indexOf("/");if(e<0)return!1;const o=Number(r.slice(e+1));if(!Number.isInteger(o)||o<0||o>32)return!1;const n=ipToInt(t),s=ipToInt(r.slice(0,e));if(n===null||s===null)return!1;if(o===0)return!0;const i=(o===32?4294967295:~((1<<32-o)-1))>>>0;return(n&i)>>>0===(s&i)>>>0}a(matchCidr,"matchCidr"),u(matchCidr,"matchCidr");const str=u(t=>t==null?"":String(t),"str"),eq=u((t,r)=>str(t)===str(r),"eq");function evalCondition(t,r){const e=r[t.attr],o=e!=null;switch(t.op){case"exists":return o;case"ne":return!o||!eq(e,t.value)}if(!o)return!1;switch(t.op){case"eq":return eq(e,t.value);case"in":return Array.isArray(t.value)&&t.value.some(n=>Array.isArray(e)?e.some(s=>eq(s,n)):eq(e,n));case"nin":return!(Array.isArray(t.value)&&t.value.some(n=>Array.isArray(e)?e.some(s=>eq(s,n)):eq(e,n)));case"prefix":return str(e).startsWith(str(t.value));case"suffix":return str(e).endsWith(str(t.value));case"contains":return Array.isArray(e)?e.some(n=>eq(n,t.value)):str(e).includes(str(t.value));case"hastoken":{const n=str(t.value);return Array.isArray(e)?e.some(s=>eq(s,t.value)):str(e).split(/[\s,]+/).filter(Boolean).includes(n)}case"regex":{const n=compileRe(str(t.value));return n?n.test(str(e)):!1}case"gt":return Number(e)>Number(t.value);case"lt":return Number(e)<Number(t.value);case"cidr":return matchCidr(str(e),str(t.value));default:return!1}}a(evalCondition,"evalCondition"),u(evalCondition,"evalCondition");function evalRule(t,r){if(!isGroup(t))return evalCondition(t,r);const e=Array.isArray(t.all)&&t.all.length?t.all:void 0,o=Array.isArray(t.any)&&t.any.length?t.any:void 0,n=t.not&&Object.keys(t.not).length?t.not:void 0;return n&&evalRule(n,r)||e&&!e.every(s=>evalRule(s,r))||o&&!o.some(s=>evalRule(s,r))?!1:!!(e||o||n)}a(evalRule,"evalRule"),u(evalRule,"evalRule");function pickTargetedVersion(t,r){for(const e of t)if(evalRule(e.when,r))return{version:e.version,...e.name?{rule:e.name}:{}}}a(pickTargetedVersion,"pickTargetedVersion"),u(pickTargetedVersion,"pickTargetedVersion");function fnv1a(t){let r=2166136261;for(let e=0;e<t.length;e++)r^=t.charCodeAt(e),r=Math.imul(r,16777619);return r>>>0}a(fnv1a,"fnv1a"),u(fnv1a,"fnv1a");function bucketByWeight(t,r){const e=r.reduce((s,i)=>s+Math.max(0,i),0);if(e<=0||r.length===0)return 0;const o=fnv1a(t)%1e4;let n=0;for(let s=0;s<r.length;s++)if(n+=Math.max(0,r[s])/e*1e4,o<n)return s;return r.length-1}a(bucketByWeight,"bucketByWeight"),u(bucketByWeight,"bucketByWeight");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.21.2",
3
+ "version": "1.21.3",
4
4
  "description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -57,9 +57,9 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lensmcp/core": "1.21.2",
61
- "@lensmcp/node-instrumentation": "1.21.2",
62
- "@lensmcp/nx-plugin": "1.21.2",
60
+ "@lensmcp/core": "1.21.3",
61
+ "@lensmcp/node-instrumentation": "1.21.3",
62
+ "@lensmcp/nx-plugin": "1.21.3",
63
63
  "fastify": "^5.0.0",
64
64
  "@fastify/reply-from": "^12.0.0",
65
65
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",