vue-router-citadel 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.2] - 2026-03-17
9
+
10
+ ### Fixed
11
+
12
+ - Timeout timer leak: `clearTimeout` is now always called in a `finally` block via the new
13
+ `raceWithTimeout` helper, preventing leaked timers when a handler resolves before the timeout
14
+ fires
15
+ - `shouldRunOnHook` parameter type narrowed from `string` to `NavigationHook`, removing an
16
+ unnecessary type cast
17
+
18
+ ## [0.2.1] - 2026-03-09
19
+
20
+ ### Fixed
21
+
22
+ - Resolved 38 ESLint `@typescript-eslint/no-non-null-assertion` warnings in
23
+ `src/navigationOutposts.ts` and `__tests__/devtools-inspector.test.ts`
24
+
8
25
  ## [0.2.0] - 2026-03-01
9
26
 
10
27
  ### Added
@@ -127,7 +144,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
127
144
  - `__tests__/navigationCitadel.test.ts` — citadel creation, hooks, destroy
128
145
  - `__tests__/navigationRegistry.test.ts` — registry CRUD, priority sorting
129
146
  - `__tests__/navigationOutposts.test.ts` — patrol logic, verdicts, redirects
130
- - `__tests__/timeout.test.ts` — timeout handling, onTimeout callback
147
+ - `__tests__/timeout.test.ts` — timeout handling, onTimeout callback, timer cleanup verification
131
148
  - `__tests__/integration.test.ts` — end-to-end navigation scenarios
132
149
  - `__tests__/lazy.test.ts` — lazy loading, caching, retry, timeout behavior
133
150
  - `__tests__/devtools-settings.test.ts` — DevTools settings, localStorage persistence
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var devtoolsApi=require('@vue/devtools-api');var Yt=Object.defineProperty;var h=(t,o)=>()=>(t&&(o=t(t=0)),o);var Xt=(t,o)=>{for(var e in o)Yt(t,e,{get:o[e],enumerable:true});};exports.NavigationHooks=void 0;exports.NavigationOutpostVerdicts=void 0;exports.NavigationOutpostScopes=void 0;exports.DebugPoints=void 0;var G=h(()=>{exports.NavigationHooks={BEFORE_EACH:"beforeEach",BEFORE_RESOLVE:"beforeResolve",AFTER_EACH:"afterEach"},exports.NavigationOutpostVerdicts={ALLOW:"allow",BLOCK:"block"},exports.NavigationOutpostScopes={GLOBAL:"global",ROUTE:"route"},exports.DebugPoints={NAVIGATION_START:"navigation-start",OUTPOST_ENTER:"outpost-enter",OUTPOST_BLOCK:"outpost-block",OUTPOST_TIMEOUT:"outpost-timeout",ERROR_CATCH:"error-catch",DEVTOOLS_INIT:"devtools-init",DEVTOOLS_INSPECT:"devtools-inspect"};});var H,x,P,k=h(()=>{H=typeof undefined<"u"?!!undefined.DEV:globalThis.process?.env?.NODE_ENV!=="production",x="[\u{1F3F0} NavigationCitadel]",P=100;});exports.createDefaultLogger=void 0;exports.createDefaultDebugHandler=void 0;var E,$=h(()=>{k();exports.createDefaultLogger=()=>({info:(...t)=>console.info(`\u{1F535} ${x}`,...t),warn:(...t)=>console.log(`\u{1F7E1} ${x}`,...t),error:(...t)=>console.error(`\u{1F534} ${x}`,...t),debug:(...t)=>console.log(`\u{1F7E3} ${x} [DEBUG]`,...t)}),exports.createDefaultDebugHandler=()=>()=>{debugger},E=(t,o,e,n)=>{o&&(e.debug(t),n?.(t));};});var tt,F,Nt,Rt,w,Lt,bt,Et,Tt,yt,St,ot,_t,z,M,Ct,Dt,et,nt,K=h(()=>{G();tt="navigation.citadel",F="Navigation Citadel",Nt="castle",Rt="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",w=tt+".inspector",Lt="citadel-root",bt="citadel-"+exports.NavigationOutpostScopes.GLOBAL,Et="citadel-"+exports.NavigationOutpostScopes.ROUTE,Tt="citadel-route-assignments",yt="citadel-current-route",St=16777215,ot=4372867,_t=3900150,z=9133302,M=16096779,Ct=15485081,Dt=1357990,et="vue-router-citadel:settings:",nt="logLevel";});var _,Zt,qt,Y,ht,It,xt,At,Qt,to,oo,eo,no,ao,io,ro,Gt,at,Pt=h(()=>{G();k();$();K();_=(t,o)=>({label:t,textColor:St,backgroundColor:o}),Zt=t=>t===exports.NavigationOutpostScopes.GLOBAL?z:M,qt=(t,o,e,n,a)=>(e.lazy&&a.push(_("lazy",Ct)),{id:`${t}-${n}-${o}`,label:o,tags:a}),Y=(t,o,e,n,a)=>{let s=[];for(let i of t){let u=o.get(i);u&&s.push(qt(n,i,u,e,a(u)));}return s},ht=t=>{let o=t.hooks??[exports.NavigationHooks.BEFORE_EACH],e=t.priority??P;return [_(`priority: ${e}`,ot),_(o.length===1?o[0]:`${o.length} hooks`,_t)]},It=t=>o=>{let e=o.priority??P;return [_(t,Zt(t)),_(`priority: ${e}`,ot)]},xt=t=>t.name?String(t.name):t.path,At=(t,o)=>{if(!t.name&&!t.path)return t.meta.outposts??[];try{return (t.name?o.resolve({name:t.name}):o.resolve(t.path)).matched.flatMap(n=>n.meta?.outposts??[])}catch{return t.meta.outposts??[]}},Qt=t=>{let o=[];for(let e of t.getRoutes()){let n=At(e,t);if(n.length===0)continue;let a=e.meta.outposts??[],s=n.filter(u=>!a.includes(u)).length,i=[_(`${n.length} outpost${n.length===1?"":"s"}`,M)];s>0&&i.push(_(`${s} inherited`,z)),o.push({id:`route-assignment-${xt(e)}`,label:xt(e),tags:i});}return {id:Tt,label:`Route Assignments (${o.length})`,children:o}},to=(t,o)=>{let e=o.currentRoute.value,n=Y(t.globalSorted,t.global,exports.NavigationOutpostScopes.GLOBAL,"current-route-outpost",It(exports.NavigationOutpostScopes.GLOBAL)),a=new Set(e.matched.flatMap(u=>u.meta?.outposts??[])),s=Y(t.routeSorted.filter(u=>a.has(u)),t.route,exports.NavigationOutpostScopes.ROUTE,"current-route-outpost",It(exports.NavigationOutpostScopes.ROUTE)),i=[...n,...s];return {id:yt,label:`Current Route: ${e.path} (${i.length})`,tags:[_("active",Dt)],children:i}},oo=(t,o)=>{let e=Y(t.globalSorted,t.global,exports.NavigationOutpostScopes.GLOBAL,"outpost",ht),n=Y(t.routeSorted,t.route,exports.NavigationOutpostScopes.ROUTE,"outpost",ht),a=[{id:Lt,label:"Outposts",children:[{id:bt,label:`Global (${e.length})`,tags:[_(exports.NavigationOutpostScopes.GLOBAL,z)],children:e},{id:Et,label:`Route (${n.length})`,tags:[_(exports.NavigationOutpostScopes.ROUTE,M)],children:n}]}];return o&&(a.push(Qt(o)),a.push(to(t,o))),a},eo=(t,o,e)=>({"Outpost Details":[{key:"name",value:t},{key:"scope",value:o},{key:"priority",value:e.priority??P},{key:"hooks",value:e.hooks??[exports.NavigationHooks.BEFORE_EACH]},{key:"timeout",value:e.timeout??"none (uses default)"},{key:"lazy",value:e.lazy}]}),no=/^route-assignment-(.+)$/,ao=new RegExp(`^(?:outpost|current-route-outpost)-(${exports.NavigationOutpostScopes.GLOBAL}|${exports.NavigationOutpostScopes.ROUTE})-(.+)$`),io=(t,o)=>{let e=At(t,o),n=t.meta.outposts??[],a=e.filter(s=>!n.includes(s));return {"Route Details":[{key:"name",value:t.name?String(t.name):"unnamed"},{key:"path",value:t.path},{key:"outposts (own)",value:n},...a.length>0?[{key:"outposts (inherited)",value:a}]:[],{key:"outposts (resolved)",value:e}]}},ro=(t,o,e)=>{let n=t.match(ao);if(n){let[,a,s]=n,u=(a===exports.NavigationOutpostScopes.GLOBAL?o.global:o.route).get(s);return u?eo(s,a,u):null}if(e){let a=t.match(no);if(a){let s=a[1],i=e.getRoutes().find(u=>u.name?String(u.name)===s:u.path===s);return i?io(i,e):null}}return null},Gt=(t,o,e,n,a=false,s)=>{t.addInspector({id:w,label:F,icon:Nt}),t.on.getInspectorTree(i=>{i.inspectorId===w&&(i.rootNodes=oo(o,e));}),t.on.getInspectorState(i=>{if(i.inspectorId!==w)return;let u=ro(i.nodeId,o,e);u&&(i.state=u);}),e.afterEach(()=>{at(t);}),E(exports.DebugPoints.DEVTOOLS_INSPECT,a,n,s);},at=t=>{t.sendInspectorTree(w),t.sendInspectorState(w);};});var N,kt=h(()=>{N={OFF:"off",LOG:"log",DEBUG:"debug"};});var so,uo,po,it,lo,wt,Ht,$t,Bt=h(()=>{kt();K();so=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(et+nt);return t===null?null:t===N.OFF||t===N.LOG||t===N.DEBUG?t:null}catch{return null}},uo=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(et+nt,t);}catch{}},po=(t,o,e)=>o?N.DEBUG:t??e?N.LOG:N.OFF,it=t=>{switch(t){case N.OFF:return {log:false,debug:false};case N.LOG:return {log:true,debug:false};case N.DEBUG:return {log:true,debug:true}}},lo=t=>t.debug?N.DEBUG:t.log?N.LOG:N.OFF,wt=(t,o,e)=>{let n=so();if(n!==null)return it(n);let a=po(t,o,e);return it(a)},Ht=(t,o)=>{let e=it(o);t.log=e.log,t.debug=e.debug,uo(o);},$t=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:lo(t),options:[{label:"Off",value:N.OFF},{label:"Log",value:N.LOG},{label:"Log + Debug",value:N.DEBUG}],component:"button-group"}});});var Ut={};Xt(Ut,{clearDevtoolsApi:()=>fo,notifyDevtoolsRefresh:()=>Oo,setupDevtools:()=>co});var X,co,Oo,fo,Vt=h(()=>{k();K();Pt();Bt();X=null,co=(t,o,e,n,a,s,i,u)=>{let c=wt(s,i,H);a.log=c.log,a.debug=c.debug,devtoolsApi.setupDevToolsPlugin({id:tt,label:F,logo:Rt,packageName:"vue-router-citadel",homepage:"https://kassaila.github.io/vue-router-citadel",enableEarlyProxy:true,app:t,settings:$t(a)},g=>{X=g,g.on.setPluginSettings(v=>{v.key==="logLevel"&&Ht(a,v.newValue);}),Gt(g,o,e,n,a.debug,u);});},Oo=()=>{X&&at(X);},fo=()=>{X=null;};});G();$();G();k();$();var lt=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),gt=(t,o,e)=>{let n=t[o],a=`${o}Sorted`;t[a]=Array.from(n.keys()).sort((s,i)=>{let u=n.get(s)?.priority??e,c=n.get(i)?.priority??e;return u-c});},ct=(t,o,e,n,a)=>{t[o].has(e.name)&&a.warn(`${o} outpost "${e.name}" already exists, replacing...`),t[o].set(e.name,e),gt(t,o,n);},dt=(t,o,e,n)=>{let a=t[o].delete(e);return a&&gt(t,o,n),a},Ot=(t,o)=>Array.from(t[o].keys());G();k();$();var Wt=t=>{if(typeof t=="string")return true;if(typeof t=="object"&&t!==null){let o=t;return "name"in o||"path"in o}return false},q=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(exports.NavigationOutpostVerdicts).includes(t))return t;let e=`${x} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(Wt(t)){if(o.resolve(t).matched.length===0)throw new Error(e+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(e+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},V=(t,o)=>(t.hooks??[exports.NavigationHooks.BEFORE_EACH]).includes(o),vt=Symbol("timeout"),jt=t=>new Promise((o,e)=>{setTimeout(()=>{let n=new Error(`Timeout after ${t}ms`);n[vt]=true,e(n);},t);}),Jt=t=>t instanceof Error&&vt in t,ft=async(t,o,e,n,a)=>{let{onError:s,defaultTimeout:i,onTimeout:u}=e,{router:c}=o,g=t.timeout??i;E(exports.DebugPoints.OUTPOST_ENTER,a.debug,n,e.debugHandler);try{let v=await t.getHandler(),O=g?await Promise.race([v(o),jt(g)]):await v(o);return q(O,c)}catch(v){if(Jt(v)){if(n.warn(`Outpost "${t.name}" timed out after ${g}ms`),E(exports.DebugPoints.OUTPOST_TIMEOUT,a.debug,n,e.debugHandler),u){let O=await u(t.name,o);return q(O,c)}return exports.NavigationOutpostVerdicts.BLOCK}if(s&&v instanceof Error){let O=await s(v,o);return q(O,c)}return n.error(`Outpost "${t.name}" threw error:`,v),E(exports.DebugPoints.ERROR_CATCH,a.debug,n,e.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}},Q=async(t,o,e,n,a)=>{let{hook:s,to:i,from:u}=o,c=a.log||a.debug,g=i.matched.flatMap(d=>d.meta?.outposts??[]),v=new Set(g);g.length!==v.size&&n.warn(`Duplicate outposts detected on route "${String(i.name??i.path)}"`);let O=0,I=t.globalSorted.filter(d=>V(t.global.get(d),s)).length,B=t.routeSorted.filter(d=>v.has(d)&&V(t.route.get(d),s)).length,T=I+B;if(T===0)return exports.NavigationOutpostVerdicts.ALLOW;c&&n.info(`${s}: ${u.path} -> ${i.path} (${T} outposts)`),E(exports.DebugPoints.NAVIGATION_START,a.debug,n,e.debugHandler);for(let d of t.globalSorted){let C=t.global.get(d);if(!C||!V(C,s))continue;O++,c&&n.info(`Processing outpost ${O}/${T}: "${d}" [${s}]`);let y=await ft(C,o,e,n,a);if(y!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,y),E(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),y}for(let d of t.routeSorted){if(!v.has(d))continue;let C=t.route.get(d);if(!C){n.warn(`Route outpost "${d}" not found in registry`);continue}if(!V(C,s))continue;O++,c&&n.info(`Processing outpost ${O}/${T}: "${d}" [${s}]`);let y=await ft(C,o,e,n,a);if(y!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,y),E(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),y}return exports.NavigationOutpostVerdicts.ALLOW},mt=t=>{switch(t){case exports.NavigationOutpostVerdicts.ALLOW:return true;case exports.NavigationOutpostVerdicts.BLOCK:return false;default:return t}};var rt=null,Ft=false,W=async()=>{if(Ft)return null;if(!rt)try{rt=await Promise.resolve().then(()=>(Vt(),Ut));}catch{return Ft=true,null}return rt},vo=(t,o={})=>{let{log:e,debug:n,devtools:a=H,defaultPriority:s=P}=o,i=o.logger??exports.createDefaultLogger(),u=o.debugHandler??exports.createDefaultDebugHandler(),c=a&&typeof window<"u",g=lt(),v={...o,debugHandler:u},O={log:e??H,debug:n??false},I=[],B=(r,p,l)=>({verdicts:exports.NavigationOutpostVerdicts,to:r,from:p,router:t,hook:l}),T=()=>O.log||O.debug,d=r=>async(p,l)=>{let f=B(p,l,r),R=await Q(g,f,v,i,O);return mt(R)};I.push(t.beforeEach(d(exports.NavigationHooks.BEFORE_EACH))),I.push(t.beforeResolve(d(exports.NavigationHooks.BEFORE_RESOLVE)));let C=t.afterEach(async(r,p)=>{let l=B(r,p,exports.NavigationHooks.AFTER_EACH);try{await Q(g,l,v,i,O);}catch(f){i.error("Error in afterEach outpost:",f),E(exports.DebugPoints.ERROR_CATCH,O.debug,i,u);}});I.push(C);let y=r=>{let{scope:p="global",name:l,handler:f,priority:R,hooks:zt,timeout:Mt,lazy:j=false}=r,A=null,U=null,Kt=async()=>A||(j?(U||(U=f().then(D=>{if(!D.default||typeof D.default!="function")throw new Error(`Lazy outpost "${l}" must export default handler`);return A=D.default,A}).catch(D=>{throw U=null,D instanceof Error?D:new Error(String(D))})),U):(A=f,A));T()&&i.info(`Deploying ${p} outpost: ${l}${j?" (lazy)":""}`),ct(g,p,{name:l,getHandler:Kt,lazy:j,priority:R,hooks:zt,timeout:Mt},s,i),c&&W().then(D=>D?.notifyDevtoolsRefresh());},st=(r,p)=>{T()&&i.info(`Abandoning ${r} outpost: ${p}`);let l=dt(g,r,p,s);return c&&l&&W().then(f=>f?.notifyDevtoolsRefresh()),l},ut=r=>t.getRoutes().find(p=>p.name===r),pt={install(r){c&&W().then(p=>{p&&(p.setupDevtools(r,g,t,i,O,e,n,u),E(exports.DebugPoints.DEVTOOLS_INIT,O.debug,i,u),T()&&i.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(r){if(Array.isArray(r))for(let p of r)y(p);else y(r);},abandonOutpost(r,p){if(Array.isArray(p)){let l=true;for(let f of p)st(r,f)||(l=false);return l}else return st(r,p)},getOutpostNames(r){return Ot(g,r)},assignOutpostToRoute(r,p){let l=ut(r);if(!l)return i.warn(`Route "${r}" not found`),false;let f=Array.isArray(p)?p:[p];l.meta.outposts||(l.meta.outposts=[]);for(let R of f)l.meta.outposts.includes(R)||l.meta.outposts.push(R);return T()&&i.info(`Assigned outposts [${f.join(", ")}] to route "${r}"`),true},revokeOutpostFromRoute(r,p){let l=ut(r);if(!l)return i.warn(`Route "${r}" not found`),false;let f=Array.isArray(p)?p:[p];if(!l.meta.outposts){for(let R of f)i.warn(`Outpost "${R}" not found in route "${r}"`);return true}for(let R of f)l.meta.outposts.includes(R)||i.warn(`Outpost "${R}" not found in route "${r}"`);return l.meta.outposts=l.meta.outposts.filter(R=>!f.includes(R)),T()&&i.info(`Revoked outposts [${f.join(", ")}] from route "${r}"`),true},destroy(){T()&&i.info("Destroying citadel");for(let r of I)r();I.length=0,g.global.clear(),g.route.clear(),g.globalSorted.length=0,g.routeSorted.length=0,c&&W().then(r=>r?.clearDevtoolsApi());}};return o.outposts&&pt.deployOutpost(o.outposts),pt};
1
+ 'use strict';var devtoolsApi=require('@vue/devtools-api');var Yt=Object.defineProperty;var D=(t,o)=>()=>(t&&(o=t(t=0)),o);var Xt=(t,o)=>{for(var e in o)Yt(t,e,{get:o[e],enumerable:true});};exports.NavigationHooks=void 0;exports.NavigationOutpostVerdicts=void 0;exports.NavigationOutpostScopes=void 0;exports.DebugPoints=void 0;var G=D(()=>{exports.NavigationHooks={BEFORE_EACH:"beforeEach",BEFORE_RESOLVE:"beforeResolve",AFTER_EACH:"afterEach"},exports.NavigationOutpostVerdicts={ALLOW:"allow",BLOCK:"block"},exports.NavigationOutpostScopes={GLOBAL:"global",ROUTE:"route"},exports.DebugPoints={NAVIGATION_START:"navigation-start",OUTPOST_ENTER:"outpost-enter",OUTPOST_BLOCK:"outpost-block",OUTPOST_TIMEOUT:"outpost-timeout",ERROR_CATCH:"error-catch",DEVTOOLS_INIT:"devtools-init",DEVTOOLS_INSPECT:"devtools-inspect"};});var H,x,P,k=D(()=>{H=typeof undefined<"u"?!!undefined.DEV:globalThis.process?.env?.NODE_ENV!=="production",x="[\u{1F3F0} NavigationCitadel]",P=100;});exports.createDefaultLogger=void 0;exports.createDefaultDebugHandler=void 0;var y,$=D(()=>{k();exports.createDefaultLogger=()=>({info:(...t)=>console.info(`\u{1F535} ${x}`,...t),warn:(...t)=>console.log(`\u{1F7E1} ${x}`,...t),error:(...t)=>console.error(`\u{1F534} ${x}`,...t),debug:(...t)=>console.log(`\u{1F7E3} ${x} [DEBUG]`,...t)}),exports.createDefaultDebugHandler=()=>()=>{debugger},y=(t,o,e,n)=>{o&&(e.debug(t),n?.(t));};});var tt,F,Nt,Rt,w,Lt,bt,Tt,yt,Et,St,ot,_t,z,M,Ct,ht,et,nt,K=D(()=>{G();tt="navigation.citadel",F="Navigation Citadel",Nt="castle",Rt="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",w=tt+".inspector",Lt="citadel-root",bt="citadel-"+exports.NavigationOutpostScopes.GLOBAL,Tt="citadel-"+exports.NavigationOutpostScopes.ROUTE,yt="citadel-route-assignments",Et="citadel-current-route",St=16777215,ot=4372867,_t=3900150,z=9133302,M=16096779,Ct=15485081,ht=1357990,et="vue-router-citadel:settings:",nt="logLevel";});var C,qt,Qt,Y,Dt,It,xt,At,to,oo,eo,no,ao,ro,io,so,Gt,at,Pt=D(()=>{G();k();$();K();C=(t,o)=>({label:t,textColor:St,backgroundColor:o}),qt=t=>t===exports.NavigationOutpostScopes.GLOBAL?z:M,Qt=(t,o,e,n,a)=>(e.lazy&&a.push(C("lazy",Ct)),{id:`${t}-${n}-${o}`,label:o,tags:a}),Y=(t,o,e,n,a)=>{let i=[];for(let r of t){let u=o.get(r);u&&i.push(Qt(n,r,u,e,a(u)));}return i},Dt=t=>{let o=t.hooks??[exports.NavigationHooks.BEFORE_EACH],e=t.priority??P;return [C(`priority: ${e}`,ot),C(o.length===1?o[0]:`${o.length} hooks`,_t)]},It=t=>o=>{let e=o.priority??P;return [C(t,qt(t)),C(`priority: ${e}`,ot)]},xt=t=>t.name?String(t.name):t.path,At=(t,o)=>{if(!t.name&&!t.path)return t.meta.outposts??[];try{return (t.name?o.resolve({name:t.name}):o.resolve(t.path)).matched.flatMap(n=>n.meta?.outposts??[])}catch{return t.meta.outposts??[]}},to=t=>{let o=[];for(let e of t.getRoutes()){let n=At(e,t);if(n.length===0)continue;let a=e.meta.outposts??[],i=n.filter(u=>!a.includes(u)).length,r=[C(`${n.length} outpost${n.length===1?"":"s"}`,M)];i>0&&r.push(C(`${i} inherited`,z)),o.push({id:`route-assignment-${xt(e)}`,label:xt(e),tags:r});}return {id:yt,label:`Route Assignments (${o.length})`,children:o}},oo=(t,o)=>{let e=o.currentRoute.value,n=Y(t.globalSorted,t.global,exports.NavigationOutpostScopes.GLOBAL,"current-route-outpost",It(exports.NavigationOutpostScopes.GLOBAL)),a=new Set(e.matched.flatMap(u=>u.meta?.outposts??[])),i=Y(t.routeSorted.filter(u=>a.has(u)),t.route,exports.NavigationOutpostScopes.ROUTE,"current-route-outpost",It(exports.NavigationOutpostScopes.ROUTE)),r=[...n,...i];return {id:Et,label:`Current Route: ${e.path} (${r.length})`,tags:[C("active",ht)],children:r}},eo=(t,o)=>{let e=Y(t.globalSorted,t.global,exports.NavigationOutpostScopes.GLOBAL,"outpost",Dt),n=Y(t.routeSorted,t.route,exports.NavigationOutpostScopes.ROUTE,"outpost",Dt),a=[{id:Lt,label:"Outposts",children:[{id:bt,label:`Global (${e.length})`,tags:[C(exports.NavigationOutpostScopes.GLOBAL,z)],children:e},{id:Tt,label:`Route (${n.length})`,tags:[C(exports.NavigationOutpostScopes.ROUTE,M)],children:n}]}];return o&&(a.push(to(o)),a.push(oo(t,o))),a},no=(t,o,e)=>({"Outpost Details":[{key:"name",value:t},{key:"scope",value:o},{key:"priority",value:e.priority??P},{key:"hooks",value:e.hooks??[exports.NavigationHooks.BEFORE_EACH]},{key:"timeout",value:e.timeout??"none (uses default)"},{key:"lazy",value:e.lazy}]}),ao=/^route-assignment-(.+)$/,ro=new RegExp(`^(?:outpost|current-route-outpost)-(${exports.NavigationOutpostScopes.GLOBAL}|${exports.NavigationOutpostScopes.ROUTE})-(.+)$`),io=(t,o)=>{let e=At(t,o),n=t.meta.outposts??[],a=e.filter(i=>!n.includes(i));return {"Route Details":[{key:"name",value:t.name?String(t.name):"unnamed"},{key:"path",value:t.path},{key:"outposts (own)",value:n},...a.length>0?[{key:"outposts (inherited)",value:a}]:[],{key:"outposts (resolved)",value:e}]}},so=(t,o,e)=>{let n=t.match(ro);if(n){let[,a,i]=n,u=(a===exports.NavigationOutpostScopes.GLOBAL?o.global:o.route).get(i);return u?no(i,a,u):null}if(e){let a=t.match(ao);if(a){let i=a[1],r=e.getRoutes().find(u=>u.name?String(u.name)===i:u.path===i);return r?io(r,e):null}}return null},Gt=(t,o,e,n,a=false,i)=>{t.addInspector({id:w,label:F,icon:Nt}),t.on.getInspectorTree(r=>{r.inspectorId===w&&(r.rootNodes=eo(o,e));}),t.on.getInspectorState(r=>{if(r.inspectorId!==w)return;let u=so(r.nodeId,o,e);u&&(r.state=u);}),e.afterEach(()=>{at(t);}),y(exports.DebugPoints.DEVTOOLS_INSPECT,a,n,i);},at=t=>{t.sendInspectorTree(w),t.sendInspectorState(w);};});var N,kt=D(()=>{N={OFF:"off",LOG:"log",DEBUG:"debug"};});var uo,po,lo,rt,go,wt,Ht,$t,Bt=D(()=>{kt();K();uo=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(et+nt);return t===null?null:t===N.OFF||t===N.LOG||t===N.DEBUG?t:null}catch{return null}},po=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(et+nt,t);}catch{}},lo=(t,o,e)=>o?N.DEBUG:t??e?N.LOG:N.OFF,rt=t=>{switch(t){case N.OFF:return {log:false,debug:false};case N.LOG:return {log:true,debug:false};case N.DEBUG:return {log:true,debug:true}}},go=t=>t.debug?N.DEBUG:t.log?N.LOG:N.OFF,wt=(t,o,e)=>{let n=uo();if(n!==null)return rt(n);let a=lo(t,o,e);return rt(a)},Ht=(t,o)=>{let e=rt(o);t.log=e.log,t.debug=e.debug,po(o);},$t=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:go(t),options:[{label:"Off",value:N.OFF},{label:"Log",value:N.LOG},{label:"Log + Debug",value:N.DEBUG}],component:"button-group"}});});var Ut={};Xt(Ut,{clearDevtoolsApi:()=>fo,notifyDevtoolsRefresh:()=>mo,setupDevtools:()=>Oo});var X,Oo,mo,fo,Vt=D(()=>{k();K();Pt();Bt();X=null,Oo=(t,o,e,n,a,i,r,u)=>{let c=wt(i,r,H);a.log=c.log,a.debug=c.debug,devtoolsApi.setupDevToolsPlugin({id:tt,label:F,logo:Rt,packageName:"vue-router-citadel",homepage:"https://kassaila.github.io/vue-router-citadel",enableEarlyProxy:true,app:t,settings:$t(a)},g=>{X=g,g.on.setPluginSettings(f=>{f.key==="logLevel"&&Ht(a,f.newValue);}),Gt(g,o,e,n,a.debug,u);});},mo=()=>{X&&at(X);},fo=()=>{X=null;};});G();$();G();k();$();var lt=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),gt=(t,o,e)=>{let n=t[o],a=`${o}Sorted`;t[a]=Array.from(n.keys()).sort((i,r)=>{let u=n.get(i)?.priority??e,c=n.get(r)?.priority??e;return u-c});},ct=(t,o,e,n,a)=>{t[o].has(e.name)&&a.warn(`${o} outpost "${e.name}" already exists, replacing...`),t[o].set(e.name,e),gt(t,o,n);},dt=(t,o,e,n)=>{let a=t[o].delete(e);return a&&gt(t,o,n),a},Ot=(t,o)=>Array.from(t[o].keys());G();k();$();var Wt=t=>{if(typeof t=="string")return true;if(typeof t=="object"&&t!==null){let o=t;return "name"in o||"path"in o}return false},q=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(exports.NavigationOutpostVerdicts).includes(t))return t;let e=`${x} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(Wt(t)){if(o.resolve(t).matched.length===0)throw new Error(e+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(e+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},V=(t,o)=>(t.hooks??[exports.NavigationHooks.BEFORE_EACH]).includes(o),ft=Symbol("timeout"),jt=t=>{let o;return {promise:new Promise((n,a)=>{o=setTimeout(()=>{let i=new Error(`Timeout after ${t}ms`);i[ft]=true,a(i);},t);}),cancel:()=>clearTimeout(o)}},Jt=async(t,o)=>{let{promise:e,cancel:n}=jt(o);try{return await Promise.race([t,e])}finally{n();}},Zt=t=>t instanceof Error&&ft in t,mt=async(t,o,e,n,a)=>{let{onError:i,defaultTimeout:r,onTimeout:u}=e,{router:c}=o,g=t.timeout??r;y(exports.DebugPoints.OUTPOST_ENTER,a.debug,n,e.debugHandler);try{let f=await t.getHandler(),O=g?await Jt(f(o),g):await f(o);return q(O,c)}catch(f){if(Zt(f)){if(n.warn(`Outpost "${t.name}" timed out after ${g}ms`),y(exports.DebugPoints.OUTPOST_TIMEOUT,a.debug,n,e.debugHandler),u){let O=await u(t.name,o);return q(O,c)}return exports.NavigationOutpostVerdicts.BLOCK}if(i&&f instanceof Error){let O=await i(f,o);return q(O,c)}return n.error(`Outpost "${t.name}" threw error:`,f),y(exports.DebugPoints.ERROR_CATCH,a.debug,n,e.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}},Q=async(t,o,e,n,a)=>{let{hook:i,to:r,from:u}=o,c=a.log||a.debug,g=r.matched.flatMap(d=>d.meta?.outposts??[]),f=new Set(g);g.length!==f.size&&n.warn(`Duplicate outposts detected on route "${String(r.name??r.path)}"`);let O=0,I=t.globalSorted.filter(d=>{let R=t.global.get(d);return R&&V(R,i)}).length,B=t.routeSorted.filter(d=>{let R=t.route.get(d);return f.has(d)&&R&&V(R,i)}).length,E=I+B;if(E===0)return exports.NavigationOutpostVerdicts.ALLOW;c&&n.info(`${i}: ${u.path} -> ${r.path} (${E} outposts)`),y(exports.DebugPoints.NAVIGATION_START,a.debug,n,e.debugHandler);for(let d of t.globalSorted){let R=t.global.get(d);if(!R||!V(R,i))continue;O++,c&&n.info(`Processing outpost ${O}/${E}: "${d}" [${i}]`);let S=await mt(R,o,e,n,a);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,S),y(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),S}for(let d of t.routeSorted){if(!f.has(d))continue;let R=t.route.get(d);if(!R){n.warn(`Route outpost "${d}" not found in registry`);continue}if(!V(R,i))continue;O++,c&&n.info(`Processing outpost ${O}/${E}: "${d}" [${i}]`);let S=await mt(R,o,e,n,a);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,S),y(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),S}return exports.NavigationOutpostVerdicts.ALLOW},vt=t=>{switch(t){case exports.NavigationOutpostVerdicts.ALLOW:return true;case exports.NavigationOutpostVerdicts.BLOCK:return false;default:return t}};var it=null,Ft=false,W=async()=>{if(Ft)return null;if(!it)try{it=await Promise.resolve().then(()=>(Vt(),Ut));}catch{return Ft=true,null}return it},vo=(t,o={})=>{let{log:e,debug:n,devtools:a=H,defaultPriority:i=P}=o,r=o.logger??exports.createDefaultLogger(),u=o.debugHandler??exports.createDefaultDebugHandler(),c=a&&typeof window<"u",g=lt(),f={...o,debugHandler:u},O={log:e??H,debug:n??false},I=[],B=(s,p,l)=>({verdicts:exports.NavigationOutpostVerdicts,to:s,from:p,router:t,hook:l}),E=()=>O.log||O.debug,d=s=>async(p,l)=>{let m=B(p,l,s),L=await Q(g,m,f,r,O);return vt(L)};I.push(t.beforeEach(d(exports.NavigationHooks.BEFORE_EACH))),I.push(t.beforeResolve(d(exports.NavigationHooks.BEFORE_RESOLVE)));let R=t.afterEach(async(s,p)=>{let l=B(s,p,exports.NavigationHooks.AFTER_EACH);try{await Q(g,l,f,r,O);}catch(m){r.error("Error in afterEach outpost:",m),y(exports.DebugPoints.ERROR_CATCH,O.debug,r,u);}});I.push(R);let S=s=>{let{scope:p="global",name:l,handler:m,priority:L,hooks:zt,timeout:Mt,lazy:j=false}=s,A=null,U=null,Kt=async()=>A||(j?(U||(U=m().then(h=>{if(!h.default||typeof h.default!="function")throw new Error(`Lazy outpost "${l}" must export default handler`);return A=h.default,A}).catch(h=>{throw U=null,h instanceof Error?h:new Error(String(h))})),U):(A=m,A));E()&&r.info(`Deploying ${p} outpost: ${l}${j?" (lazy)":""}`),ct(g,p,{name:l,getHandler:Kt,lazy:j,priority:L,hooks:zt,timeout:Mt},i,r),c&&W().then(h=>h?.notifyDevtoolsRefresh());},st=(s,p)=>{E()&&r.info(`Abandoning ${s} outpost: ${p}`);let l=dt(g,s,p,i);return c&&l&&W().then(m=>m?.notifyDevtoolsRefresh()),l},ut=s=>t.getRoutes().find(p=>p.name===s),pt={install(s){c&&W().then(p=>{p&&(p.setupDevtools(s,g,t,r,O,e,n,u),y(exports.DebugPoints.DEVTOOLS_INIT,O.debug,r,u),E()&&r.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(s){if(Array.isArray(s))for(let p of s)S(p);else S(s);},abandonOutpost(s,p){if(Array.isArray(p)){let l=true;for(let m of p)st(s,m)||(l=false);return l}else return st(s,p)},getOutpostNames(s){return Ot(g,s)},assignOutpostToRoute(s,p){let l=ut(s);if(!l)return r.warn(`Route "${s}" not found`),false;let m=Array.isArray(p)?p:[p];l.meta.outposts||(l.meta.outposts=[]);for(let L of m)l.meta.outposts.includes(L)||l.meta.outposts.push(L);return E()&&r.info(`Assigned outposts [${m.join(", ")}] to route "${s}"`),true},revokeOutpostFromRoute(s,p){let l=ut(s);if(!l)return r.warn(`Route "${s}" not found`),false;let m=Array.isArray(p)?p:[p];if(!l.meta.outposts){for(let L of m)r.warn(`Outpost "${L}" not found in route "${s}"`);return true}for(let L of m)l.meta.outposts.includes(L)||r.warn(`Outpost "${L}" not found in route "${s}"`);return l.meta.outposts=l.meta.outposts.filter(L=>!m.includes(L)),E()&&r.info(`Revoked outposts [${m.join(", ")}] from route "${s}"`),true},destroy(){E()&&r.info("Destroying citadel");for(let s of I)s();I.length=0,g.global.clear(),g.route.clear(),g.globalSorted.length=0,g.routeSorted.length=0,c&&W().then(s=>s?.clearDevtoolsApi());}};return o.outposts&&pt.deployOutpost(o.outposts),pt};
2
2
  exports.createNavigationCitadel=vo;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import {g,e,h,i,a,j as j$1,d,b,f}from'./chunk-5IKR7JBX.js';export{d as DebugPoints,a as NavigationHooks,c as NavigationOutpostScopes,b as NavigationOutpostVerdicts,i as createDefaultDebugHandler,h as createDefaultLogger}from'./chunk-5IKR7JBX.js';var U=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),K=(t,o,a)=>{let n=t[o],u=`${o}Sorted`;t[u]=Array.from(n.keys()).sort((f,s)=>{let N=n.get(f)?.priority??a,c=n.get(s)?.priority??a;return N-c});},j=(t,o,a,n,u)=>{t[o].has(a.name)&&u.warn(`${o} outpost "${a.name}" already exists, replacing...`),t[o].set(a.name,a),K(t,o,n);},W=(t,o,a,n)=>{let u=t[o].delete(a);return u&&K(t,o,n),u},Y=(t,o)=>Array.from(t[o].keys());var nt=t=>{if(typeof t=="string")return true;if(typeof t=="object"&&t!==null){let o=t;return "name"in o||"path"in o}return false},x=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(b).includes(t))return t;let a=`${f} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(nt(t)){if(o.resolve(t).matched.length===0)throw new Error(a+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(a+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},T=(t,o)=>(t.hooks??[a.BEFORE_EACH]).includes(o),X=Symbol("timeout"),it=t=>new Promise((o,a)=>{setTimeout(()=>{let n=new Error(`Timeout after ${t}ms`);n[X]=true,a(n);},t);}),at=t=>t instanceof Error&&X in t,J=async(t,o,a,n,u)=>{let{onError:f,defaultTimeout:s,onTimeout:N}=a,{router:c}=o,g=t.timeout??s;j$1(d.OUTPOST_ENTER,u.debug,n,a.debugHandler);try{let O=await t.getHandler(),d=g?await Promise.race([O(o),it(g)]):await O(o);return x(d,c)}catch(O){if(at(O)){if(n.warn(`Outpost "${t.name}" timed out after ${g}ms`),j$1(d.OUTPOST_TIMEOUT,u.debug,n,a.debugHandler),N){let d=await N(t.name,o);return x(d,c)}return b.BLOCK}if(f&&O instanceof Error){let d=await f(O,o);return x(d,c)}return n.error(`Outpost "${t.name}" threw error:`,O),j$1(d.ERROR_CATCH,u.debug,n,a.debugHandler),b.BLOCK}},I=async(t,o,a,n,u)=>{let{hook:f,to:s,from:N}=o,c=u.log||u.debug,g=s.matched.flatMap(l=>l.meta?.outposts??[]),O=new Set(g);g.length!==O.size&&n.warn(`Duplicate outposts detected on route "${String(s.name??s.path)}"`);let d$1=0,E=t.globalSorted.filter(l=>T(t.global.get(l),f)).length,A=t.routeSorted.filter(l=>O.has(l)&&T(t.route.get(l),f)).length,y=E+A;if(y===0)return b.ALLOW;c&&n.info(`${f}: ${N.path} -> ${s.path} (${y} outposts)`),j$1(d.NAVIGATION_START,u.debug,n,a.debugHandler);for(let l of t.globalSorted){let L=t.global.get(l);if(!L||!T(L,f))continue;d$1++,c&&n.info(`Processing outpost ${d$1}/${y}: "${l}" [${f}]`);let b$1=await J(L,o,a,n,u);if(b$1!==b.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${l}":`,b$1),j$1(d.OUTPOST_BLOCK,u.debug,n,a.debugHandler),b$1}for(let l of t.routeSorted){if(!O.has(l))continue;let L=t.route.get(l);if(!L){n.warn(`Route outpost "${l}" not found in registry`);continue}if(!T(L,f))continue;d$1++,c&&n.info(`Processing outpost ${d$1}/${y}: "${l}" [${f}]`);let b$1=await J(L,o,a,n,u);if(b$1!==b.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${l}":`,b$1),j$1(d.OUTPOST_BLOCK,u.debug,n,a.debugHandler),b$1}return b.ALLOW},q=t=>{switch(t){case b.ALLOW:return true;case b.BLOCK:return false;default:return t}};var z=null,Q=false,H=async()=>{if(Q)return null;if(!z)try{z=await import('./devtools-3TRWBQAH.js');}catch{return Q=true,null}return z},rt=(t,o={})=>{let{log:a$1,debug:n,devtools:u=e,defaultPriority:f=g}=o,s=o.logger??h(),N=o.debugHandler??i(),c=u&&typeof window<"u",g$1=U(),O={...o,debugHandler:N},d$1={log:a$1??e,debug:n??false},E=[],A=(e,i,r)=>({verdicts:b,to:e,from:i,router:t,hook:r}),y=()=>d$1.log||d$1.debug,l=e=>async(i,r)=>{let p=A(i,r,e),v=await I(g$1,p,O,s,d$1);return q(v)};E.push(t.beforeEach(l(a.BEFORE_EACH))),E.push(t.beforeResolve(l(a.BEFORE_RESOLVE)));let L=t.afterEach(async(e,i)=>{let r=A(e,i,a.AFTER_EACH);try{await I(g$1,r,O,s,d$1);}catch(p){s.error("Error in afterEach outpost:",p),j$1(d.ERROR_CATCH,d$1.debug,s,N);}});E.push(L);let b$1=e=>{let{scope:i="global",name:r,handler:p,priority:v,hooks:Z,timeout:tt,lazy:P=false}=e,$=null,S=null,ot=async()=>$||(P?(S||(S=p().then(C=>{if(!C.default||typeof C.default!="function")throw new Error(`Lazy outpost "${r}" must export default handler`);return $=C.default,$}).catch(C=>{throw S=null,C instanceof Error?C:new Error(String(C))})),S):($=p,$));y()&&s.info(`Deploying ${i} outpost: ${r}${P?" (lazy)":""}`),j(g$1,i,{name:r,getHandler:ot,lazy:P,priority:v,hooks:Z,timeout:tt},f,s),c&&H().then(C=>C?.notifyDevtoolsRefresh());},B=(e,i)=>{y()&&s.info(`Abandoning ${e} outpost: ${i}`);let r=W(g$1,e,i,f);return c&&r&&H().then(p=>p?.notifyDevtoolsRefresh()),r},M=e=>t.getRoutes().find(i=>i.name===e),G={install(e){c&&H().then(i=>{i&&(i.setupDevtools(e,g$1,t,s,d$1,a$1,n,N),j$1(d.DEVTOOLS_INIT,d$1.debug,s,N),y()&&s.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(e){if(Array.isArray(e))for(let i of e)b$1(i);else b$1(e);},abandonOutpost(e,i){if(Array.isArray(i)){let r=true;for(let p of i)B(e,p)||(r=false);return r}else return B(e,i)},getOutpostNames(e){return Y(g$1,e)},assignOutpostToRoute(e,i){let r=M(e);if(!r)return s.warn(`Route "${e}" not found`),false;let p=Array.isArray(i)?i:[i];r.meta.outposts||(r.meta.outposts=[]);for(let v of p)r.meta.outposts.includes(v)||r.meta.outposts.push(v);return y()&&s.info(`Assigned outposts [${p.join(", ")}] to route "${e}"`),true},revokeOutpostFromRoute(e,i){let r=M(e);if(!r)return s.warn(`Route "${e}" not found`),false;let p=Array.isArray(i)?i:[i];if(!r.meta.outposts){for(let v of p)s.warn(`Outpost "${v}" not found in route "${e}"`);return true}for(let v of p)r.meta.outposts.includes(v)||s.warn(`Outpost "${v}" not found in route "${e}"`);return r.meta.outposts=r.meta.outposts.filter(v=>!p.includes(v)),y()&&s.info(`Revoked outposts [${p.join(", ")}] from route "${e}"`),true},destroy(){y()&&s.info("Destroying citadel");for(let e of E)e();E.length=0,g$1.global.clear(),g$1.route.clear(),g$1.globalSorted.length=0,g$1.routeSorted.length=0,c&&H().then(e=>e?.clearDevtoolsApi());}};return o.outposts&&G.deployOutpost(o.outposts),G};export{rt as createNavigationCitadel};
1
+ import {g,e,h,i,a,j as j$1,d,b,f}from'./chunk-5IKR7JBX.js';export{d as DebugPoints,a as NavigationHooks,c as NavigationOutpostScopes,b as NavigationOutpostVerdicts,i as createDefaultDebugHandler,h as createDefaultLogger}from'./chunk-5IKR7JBX.js';var U=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),K=(t,o,i)=>{let n=t[o],u=`${o}Sorted`;t[u]=Array.from(n.keys()).sort((p,s)=>{let y=n.get(p)?.priority??i,f=n.get(s)?.priority??i;return y-f});},W=(t,o,i,n,u)=>{t[o].has(i.name)&&u.warn(`${o} outpost "${i.name}" already exists, replacing...`),t[o].set(i.name,i),K(t,o,n);},j=(t,o,i,n)=>{let u=t[o].delete(i);return u&&K(t,o,n),u},Y=(t,o)=>Array.from(t[o].keys());var nt=t=>{if(typeof t=="string")return true;if(typeof t=="object"&&t!==null){let o=t;return "name"in o||"path"in o}return false},x=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(b).includes(t))return t;let i=`${f} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(nt(t)){if(o.resolve(t).matched.length===0)throw new Error(i+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(i+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},S=(t,o)=>(t.hooks??[a.BEFORE_EACH]).includes(o),X=Symbol("timeout"),it=t=>{let o;return {promise:new Promise((n,u)=>{o=setTimeout(()=>{let p=new Error(`Timeout after ${t}ms`);p[X]=true,u(p);},t);}),cancel:()=>clearTimeout(o)}},at=async(t,o)=>{let{promise:i,cancel:n}=it(o);try{return await Promise.race([t,i])}finally{n();}},rt=t=>t instanceof Error&&X in t,J=async(t,o,i,n,u)=>{let{onError:p,defaultTimeout:s,onTimeout:y}=i,{router:f}=o,g=t.timeout??s;j$1(d.OUTPOST_ENTER,u.debug,n,i.debugHandler);try{let v=await t.getHandler(),d=g?await at(v(o),g):await v(o);return x(d,f)}catch(v){if(rt(v)){if(n.warn(`Outpost "${t.name}" timed out after ${g}ms`),j$1(d.OUTPOST_TIMEOUT,u.debug,n,i.debugHandler),y){let d=await y(t.name,o);return x(d,f)}return b.BLOCK}if(p&&v instanceof Error){let d=await p(v,o);return x(d,f)}return n.error(`Outpost "${t.name}" threw error:`,v),j$1(d.ERROR_CATCH,u.debug,n,i.debugHandler),b.BLOCK}},I=async(t,o,i,n,u)=>{let{hook:p,to:s,from:y}=o,f=u.log||u.debug,g=s.matched.flatMap(l=>l.meta?.outposts??[]),v=new Set(g);g.length!==v.size&&n.warn(`Duplicate outposts detected on route "${String(s.name??s.path)}"`);let d$1=0,w=t.globalSorted.filter(l=>{let O=t.global.get(l);return O&&S(O,p)}).length,$=t.routeSorted.filter(l=>{let O=t.route.get(l);return v.has(l)&&O&&S(O,p)}).length,b$1=w+$;if(b$1===0)return b.ALLOW;f&&n.info(`${p}: ${y.path} -> ${s.path} (${b$1} outposts)`),j$1(d.NAVIGATION_START,u.debug,n,i.debugHandler);for(let l of t.globalSorted){let O=t.global.get(l);if(!O||!S(O,p))continue;d$1++,f&&n.info(`Processing outpost ${d$1}/${b$1}: "${l}" [${p}]`);let h=await J(O,o,i,n,u);if(h!==b.ALLOW)return f&&n.warn(`Patrol stopped by outpost "${l}":`,h),j$1(d.OUTPOST_BLOCK,u.debug,n,i.debugHandler),h}for(let l of t.routeSorted){if(!v.has(l))continue;let O=t.route.get(l);if(!O){n.warn(`Route outpost "${l}" not found in registry`);continue}if(!S(O,p))continue;d$1++,f&&n.info(`Processing outpost ${d$1}/${b$1}: "${l}" [${p}]`);let h=await J(O,o,i,n,u);if(h!==b.ALLOW)return f&&n.warn(`Patrol stopped by outpost "${l}":`,h),j$1(d.OUTPOST_BLOCK,u.debug,n,i.debugHandler),h}return b.ALLOW},q=t=>{switch(t){case b.ALLOW:return true;case b.BLOCK:return false;default:return t}};var z=null,Q=false,H=async()=>{if(Q)return null;if(!z)try{z=await import('./devtools-3TRWBQAH.js');}catch{return Q=true,null}return z},st=(t,o={})=>{let{log:i$1,debug:n,devtools:u=e,defaultPriority:p=g}=o,s=o.logger??h(),y=o.debugHandler??i(),f=u&&typeof window<"u",g$1=U(),v={...o,debugHandler:y},d$1={log:i$1??e,debug:n??false},w=[],$=(e,a,r)=>({verdicts:b,to:e,from:a,router:t,hook:r}),b$1=()=>d$1.log||d$1.debug,l=e=>async(a,r)=>{let c=$(a,r,e),m=await I(g$1,c,v,s,d$1);return q(m)};w.push(t.beforeEach(l(a.BEFORE_EACH))),w.push(t.beforeResolve(l(a.BEFORE_RESOLVE)));let O=t.afterEach(async(e,a$1)=>{let r=$(e,a$1,a.AFTER_EACH);try{await I(g$1,r,v,s,d$1);}catch(c){s.error("Error in afterEach outpost:",c),j$1(d.ERROR_CATCH,d$1.debug,s,y);}});w.push(O);let h$1=e=>{let{scope:a="global",name:r,handler:c,priority:m,hooks:Z,timeout:tt,lazy:P=false}=e,E=null,A=null,ot=async()=>E||(P?(A||(A=c().then(C=>{if(!C.default||typeof C.default!="function")throw new Error(`Lazy outpost "${r}" must export default handler`);return E=C.default,E}).catch(C=>{throw A=null,C instanceof Error?C:new Error(String(C))})),A):(E=c,E));b$1()&&s.info(`Deploying ${a} outpost: ${r}${P?" (lazy)":""}`),W(g$1,a,{name:r,getHandler:ot,lazy:P,priority:m,hooks:Z,timeout:tt},p,s),f&&H().then(C=>C?.notifyDevtoolsRefresh());},B=(e,a)=>{b$1()&&s.info(`Abandoning ${e} outpost: ${a}`);let r=j(g$1,e,a,p);return f&&r&&H().then(c=>c?.notifyDevtoolsRefresh()),r},M=e=>t.getRoutes().find(a=>a.name===e),G={install(e){f&&H().then(a=>{a&&(a.setupDevtools(e,g$1,t,s,d$1,i$1,n,y),j$1(d.DEVTOOLS_INIT,d$1.debug,s,y),b$1()&&s.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(e){if(Array.isArray(e))for(let a of e)h$1(a);else h$1(e);},abandonOutpost(e,a){if(Array.isArray(a)){let r=true;for(let c of a)B(e,c)||(r=false);return r}else return B(e,a)},getOutpostNames(e){return Y(g$1,e)},assignOutpostToRoute(e,a){let r=M(e);if(!r)return s.warn(`Route "${e}" not found`),false;let c=Array.isArray(a)?a:[a];r.meta.outposts||(r.meta.outposts=[]);for(let m of c)r.meta.outposts.includes(m)||r.meta.outposts.push(m);return b$1()&&s.info(`Assigned outposts [${c.join(", ")}] to route "${e}"`),true},revokeOutpostFromRoute(e,a){let r=M(e);if(!r)return s.warn(`Route "${e}" not found`),false;let c=Array.isArray(a)?a:[a];if(!r.meta.outposts){for(let m of c)s.warn(`Outpost "${m}" not found in route "${e}"`);return true}for(let m of c)r.meta.outposts.includes(m)||s.warn(`Outpost "${m}" not found in route "${e}"`);return r.meta.outposts=r.meta.outposts.filter(m=>!c.includes(m)),b$1()&&s.info(`Revoked outposts [${c.join(", ")}] from route "${e}"`),true},destroy(){b$1()&&s.info("Destroying citadel");for(let e of w)e();w.length=0,g$1.global.clear(),g$1.route.clear(),g$1.globalSorted.length=0,g$1.routeSorted.length=0,f&&H().then(e=>e?.clearDevtoolsApi());}};return o.outposts&&G.deployOutpost(o.outposts),G};export{st as createNavigationCitadel};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-router-citadel",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Structured navigation defense for Vue Router",
5
5
  "type": "module",
6
6
  "sideEffects": false,