vue-router-citadel 0.2.1 → 0.3.0

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
@@ -7,6 +7,40 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-05-25
11
+
12
+ ### Added
13
+
14
+ - Per-outpost `onError` and `onTimeout` handlers on `NavigationOutpost`. When set, they replace the
15
+ citadel-level handler for that outpost; otherwise the citadel-level handler (or default `BLOCK`)
16
+ applies. Enables per-outpost error policies (e.g. report `auth` failures to Sentry but silently
17
+ allow on `preload` errors)
18
+ - `OutpostBehaviorOptions` interface exported — shared optional fields (`priority`, `hooks`,
19
+ `timeout`, `onError`, `onTimeout`) used by both `NavigationOutpost` and
20
+ `RegisteredNavigationOutpost`
21
+ - `NavigationOutpostErrorHandler` and `NavigationOutpostTimeoutHandler` types exported — handler
22
+ signatures for citadel-level and per-outpost error/timeout callbacks
23
+
24
+ ### Changed
25
+
26
+ - `onError` is now invoked for non-`Error` throws. Previously the handler was skipped when an
27
+ outpost threw a non-`Error` value (string, object, etc.) and navigation fell through to the
28
+ default `BLOCK`. Such values are now wrapped via `new Error(String(value))` before being passed to
29
+ `onError`
30
+ - Throws from inside `onError` / `onTimeout` are now caught. Previously they propagated out of the
31
+ guard. They are now logged and the outpost resolves to `BLOCK`. The same applies if the verdict
32
+ returned by these handlers fails `normalizeOutcome` validation
33
+
34
+ ## [0.2.2] - 2026-03-17
35
+
36
+ ### Fixed
37
+
38
+ - Timeout timer leak: `clearTimeout` is now always called in a `finally` block via the new
39
+ `raceWithTimeout` helper, preventing leaked timers when a handler resolves before the timeout
40
+ fires
41
+ - `shouldRunOnHook` parameter type narrowed from `string` to `NavigationHook`, removing an
42
+ unnecessary type cast
43
+
10
44
  ## [0.2.1] - 2026-03-09
11
45
 
12
46
  ### Fixed
@@ -95,7 +129,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
95
129
  - `check:lint` — ESLint check (alias)
96
130
  - `check:types` — TypeScript type checking (`tsc --noEmit`)
97
131
  - `check:format` — format check alias
98
- - `check:size` — bundle size check ([size-limit](https://github.com/ai/size-limit), ≤4 KB)
132
+ - `check:size` — bundle size check ([size-limit](https://github.com/ai/size-limit))
99
133
  - `check:all` — full validation chain (format + lint + types + tests + build + size)
100
134
  - `release:check` — pre-release verification (check:all + pack --dry-run)
101
135
  - `release:publish` — publish to npm with full checks
@@ -136,7 +170,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
136
170
  - `__tests__/navigationCitadel.test.ts` — citadel creation, hooks, destroy
137
171
  - `__tests__/navigationRegistry.test.ts` — registry CRUD, priority sorting
138
172
  - `__tests__/navigationOutposts.test.ts` — patrol logic, verdicts, redirects
139
- - `__tests__/timeout.test.ts` — timeout handling, onTimeout callback
173
+ - `__tests__/timeout.test.ts` — timeout handling, onTimeout callback, timer cleanup verification
140
174
  - `__tests__/integration.test.ts` — end-to-end navigation scenarios
141
175
  - `__tests__/lazy.test.ts` — lazy loading, caching, retry, timeout behavior
142
176
  - `__tests__/devtools-settings.test.ts` — DevTools settings, localStorage persistence
@@ -173,4 +207,4 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
173
207
  - `eslint-config-prettier` for conflict-free coexistence with Prettier
174
208
  - 3 custom local rules: `switch-case-braces`, `jsdoc-comment-style`, `prefer-arrow-without-this`
175
209
  - npm scripts: `lint`, `lint:fix`, `check:lint`; integrated into `check:all` and `lint-staged`
176
- - [size-limit](https://github.com/ai/size-limit) — bundle size control (≤4 KB, minified + brotli)
210
+ - [size-limit](https://github.com/ai/size-limit) — bundle size control
package/README.md CHANGED
@@ -28,8 +28,8 @@ Think of it as turning your router into a fortress.
28
28
  - 📋 **Priority-based execution** — deterministic outpost ordering with numeric priorities
29
29
  - 🪝 **All navigation hooks** — beforeEach, beforeResolve, afterEach support per outpost
30
30
  - 🔄 **Dynamic management** — deploy, abandon, and reassign outposts at runtime
31
- - ⏱️ **Timeout control & error handling** — global and per-outpost timeout configuration. Custom
32
- timeout and error handlers with redirect or block verdicts.
31
+ - ⏱️ **Timeout control & error handling** — global and per-outpost timeout and error handlers.
32
+ Override citadel-level handlers per outpost with redirect or block verdicts.
33
33
  - 🔒 **Type-safe** — full TypeScript support with declaration merging for outpost names. IDE
34
34
  autocomplete and compile-time validation.
35
35
  - 🦥 **Lazy outposts** — dynamic imports with automatic caching for code splitting
@@ -1 +1 @@
1
- var n={BEFORE_EACH:"beforeEach",BEFORE_RESOLVE:"beforeResolve",AFTER_EACH:"afterEach"},s={ALLOW:"allow",BLOCK:"block"},r={GLOBAL:"global",ROUTE:"route"},p={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 g=typeof import.meta?.env<"u"?!!import.meta.env.DEV:globalThis.process?.env?.NODE_ENV!=="production",o="[\u{1F3F0} NavigationCitadel]",O=100;var d=()=>({info:(...t)=>console.info(`\u{1F535} ${o}`,...t),warn:(...t)=>console.log(`\u{1F7E1} ${o}`,...t),error:(...t)=>console.error(`\u{1F534} ${o}`,...t),debug:(...t)=>console.log(`\u{1F7E3} ${o} [DEBUG]`,...t)}),c=()=>()=>{debugger},m=(t,e,a,i)=>{e&&(a.debug(t),i?.(t));};export{n as a,s as b,r as c,p as d,g as e,o as f,O as g,d as h,c as i,m as j};
1
+ var r={BEFORE_EACH:"beforeEach",BEFORE_RESOLVE:"beforeResolve",AFTER_EACH:"afterEach"},n={ALLOW:"allow",BLOCK:"block"},s={GLOBAL:"global",ROUTE:"route"},p={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 g=typeof import.meta?.env<"u"?!!import.meta.env.DEV:globalThis.process?.env?.NODE_ENV!=="production",o="[\u{1F3F0} NavigationCitadel]",O=100;var d=()=>({info:(...t)=>console.info(`\u{1F535} ${o}`,...t),warn:(...t)=>console.log(`\u{1F7E1} ${o}`,...t),error:(...t)=>console.error(`\u{1F534} ${o}`,...t),debug:(...t)=>console.log(`\u{1F7E3} ${o} [DEBUG]`,...t)}),v=()=>()=>{debugger},c=(t,e,a,i)=>{e&&(a.debug(t),i?.(t));};export{r as a,n as b,s as c,p as d,g as e,o as f,O as g,d as h,v as i,c as j};
@@ -1,2 +1,2 @@
1
- import {c as c$1,j as j$1,d as d$1,g as g$1,a,e}from'./chunk-5IKR7JBX.js';import {setupDevToolsPlugin}from'@vue/devtools-api';var m="navigation.citadel",g="Navigation Citadel",A="castle",x="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",c=m+".inspector",y="citadel-root",U="citadel-"+c$1.GLOBAL,P="citadel-"+c$1.ROUTE,B="citadel-route-assignments",k="citadel-current-route",V=16777215,N=4372867,$=3900150,d=9133302,L=16096779,F=15485081,w=1357990,S="vue-router-citadel:settings:",v="logLevel";var p=(t,e)=>({label:t,textColor:V,backgroundColor:e}),q=t=>t===c$1.GLOBAL?d:L,J=(t,e,o,s,n)=>(o.lazy&&n.push(p("lazy",F)),{id:`${t}-${s}-${e}`,label:e,tags:n}),_=(t,e,o,s,n)=>{let a=[];for(let r of t){let i=e.get(r);i&&a.push(J(s,r,i,o,n(i)));}return a},z=t=>{let e=t.hooks??[a.BEFORE_EACH],o=t.priority??g$1;return [p(`priority: ${o}`,N),p(e.length===1?e[0]:`${e.length} hooks`,$)]},H=t=>e=>{let o=e.priority??g$1;return [p(t,q(t)),p(`priority: ${o}`,N)]},M=t=>t.name?String(t.name):t.path,Y=(t,e)=>{if(!t.name&&!t.path)return t.meta.outposts??[];try{return (t.name?e.resolve({name:t.name}):e.resolve(t.path)).matched.flatMap(s=>s.meta?.outposts??[])}catch{return t.meta.outposts??[]}},Q=t=>{let e=[];for(let o of t.getRoutes()){let s=Y(o,t);if(s.length===0)continue;let n=o.meta.outposts??[],a=s.filter(i=>!n.includes(i)).length,r=[p(`${s.length} outpost${s.length===1?"":"s"}`,L)];a>0&&r.push(p(`${a} inherited`,d)),e.push({id:`route-assignment-${M(o)}`,label:M(o),tags:r});}return {id:B,label:`Route Assignments (${e.length})`,children:e}},W=(t,e)=>{let o=e.currentRoute.value,s=_(t.globalSorted,t.global,c$1.GLOBAL,"current-route-outpost",H(c$1.GLOBAL)),n=new Set(o.matched.flatMap(i=>i.meta?.outposts??[])),a=_(t.routeSorted.filter(i=>n.has(i)),t.route,c$1.ROUTE,"current-route-outpost",H(c$1.ROUTE)),r=[...s,...a];return {id:k,label:`Current Route: ${o.path} (${r.length})`,tags:[p("active",w)],children:r}},tt=(t,e)=>{let o=_(t.globalSorted,t.global,c$1.GLOBAL,"outpost",z),s=_(t.routeSorted,t.route,c$1.ROUTE,"outpost",z),n=[{id:y,label:"Outposts",children:[{id:U,label:`Global (${o.length})`,tags:[p(c$1.GLOBAL,d)],children:o},{id:P,label:`Route (${s.length})`,tags:[p(c$1.ROUTE,L)],children:s}]}];return e&&(n.push(Q(e)),n.push(W(t,e))),n},et=(t,e,o)=>({"Outpost Details":[{key:"name",value:t},{key:"scope",value:e},{key:"priority",value:o.priority??g$1},{key:"hooks",value:o.hooks??[a.BEFORE_EACH]},{key:"timeout",value:o.timeout??"none (uses default)"},{key:"lazy",value:o.lazy}]}),ot=/^route-assignment-(.+)$/,nt=new RegExp(`^(?:outpost|current-route-outpost)-(${c$1.GLOBAL}|${c$1.ROUTE})-(.+)$`),st=(t,e)=>{let o=Y(t,e),s=t.meta.outposts??[],n=o.filter(a=>!s.includes(a));return {"Route Details":[{key:"name",value:t.name?String(t.name):"unnamed"},{key:"path",value:t.path},{key:"outposts (own)",value:s},...n.length>0?[{key:"outposts (inherited)",value:n}]:[],{key:"outposts (resolved)",value:o}]}},rt=(t,e,o)=>{let s=t.match(nt);if(s){let[,n,a]=s,i=(n===c$1.GLOBAL?e.global:e.route).get(a);return i?et(a,n,i):null}if(o){let n=t.match(ot);if(n){let a=n[1],r=o.getRoutes().find(i=>i.name?String(i.name)===a:i.path===a);return r?st(r,o):null}}return null},X=(t,e,o,s,n=false,a)=>{t.addInspector({id:c,label:g,icon:A}),t.on.getInspectorTree(r=>{r.inspectorId===c&&(r.rootNodes=tt(e,o));}),t.on.getInspectorState(r=>{if(r.inspectorId!==c)return;let i=rt(r.nodeId,e,o);i&&(r.state=i);}),o.afterEach(()=>{f(t);}),j$1(d$1.DEVTOOLS_INSPECT,n,s,a);},f=t=>{t.sendInspectorTree(c),t.sendInspectorState(c);};var u={OFF:"off",LOG:"log",DEBUG:"debug"};var it=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(S+v);return t===null?null:t===u.OFF||t===u.LOG||t===u.DEBUG?t:null}catch{return null}},at=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(S+v,t);}catch{}},lt=(t,e,o)=>e?u.DEBUG:t??o?u.LOG:u.OFF,I=t=>{switch(t){case u.OFF:return {log:false,debug:false};case u.LOG:return {log:true,debug:false};case u.DEBUG:return {log:true,debug:true}}},ut=t=>t.debug?u.DEBUG:t.log?u.LOG:u.OFF,K=(t,e,o)=>{let s=it();if(s!==null)return I(s);let n=lt(t,e,o);return I(n)},Z=(t,e)=>{let o=I(e);t.log=o.log,t.debug=o.debug,at(e);},j=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:ut(t),options:[{label:"Off",value:u.OFF},{label:"Log",value:u.LOG},{label:"Log + Debug",value:u.DEBUG}],component:"button-group"}});var R=null,Ct=(t,e$1,o,s,n,a,r,i)=>{let G=K(a,r,e);n.log=G.log,n.debug=G.debug,setupDevToolsPlugin({id:m,label:g,logo:x,packageName:"vue-router-citadel",homepage:"https://kassaila.github.io/vue-router-citadel",enableEarlyProxy:true,app:t,settings:j(n)},T=>{R=T,T.on.setPluginSettings(C=>{C.key==="logLevel"&&Z(n,C.newValue);}),X(T,e$1,o,s,n.debug,i);});},Dt=()=>{R&&f(R);},bt=()=>{R=null;};
1
+ import {c as c$1,j as j$1,d as d$1,g as g$1,a,e}from'./chunk-W4ASYKYO.js';import {setupDevToolsPlugin}from'@vue/devtools-api';var m="navigation.citadel",g="Navigation Citadel",A="castle",x="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",c=m+".inspector",y="citadel-root",U="citadel-"+c$1.GLOBAL,P="citadel-"+c$1.ROUTE,B="citadel-route-assignments",k="citadel-current-route",V=16777215,N=4372867,$=3900150,d=9133302,L=16096779,F=15485081,w=1357990,S="vue-router-citadel:settings:",v="logLevel";var p=(t,e)=>({label:t,textColor:V,backgroundColor:e}),q=t=>t===c$1.GLOBAL?d:L,J=(t,e,o,s,n)=>(o.lazy&&n.push(p("lazy",F)),{id:`${t}-${s}-${e}`,label:e,tags:n}),_=(t,e,o,s,n)=>{let a=[];for(let r of t){let i=e.get(r);i&&a.push(J(s,r,i,o,n(i)));}return a},z=t=>{let e=t.hooks??[a.BEFORE_EACH],o=t.priority??g$1;return [p(`priority: ${o}`,N),p(e.length===1?e[0]:`${e.length} hooks`,$)]},H=t=>e=>{let o=e.priority??g$1;return [p(t,q(t)),p(`priority: ${o}`,N)]},M=t=>t.name?String(t.name):t.path,Y=(t,e)=>{if(!t.name&&!t.path)return t.meta.outposts??[];try{return (t.name?e.resolve({name:t.name}):e.resolve(t.path)).matched.flatMap(s=>s.meta?.outposts??[])}catch{return t.meta.outposts??[]}},Q=t=>{let e=[];for(let o of t.getRoutes()){let s=Y(o,t);if(s.length===0)continue;let n=o.meta.outposts??[],a=s.filter(i=>!n.includes(i)).length,r=[p(`${s.length} outpost${s.length===1?"":"s"}`,L)];a>0&&r.push(p(`${a} inherited`,d)),e.push({id:`route-assignment-${M(o)}`,label:M(o),tags:r});}return {id:B,label:`Route Assignments (${e.length})`,children:e}},W=(t,e)=>{let o=e.currentRoute.value,s=_(t.globalSorted,t.global,c$1.GLOBAL,"current-route-outpost",H(c$1.GLOBAL)),n=new Set(o.matched.flatMap(i=>i.meta?.outposts??[])),a=_(t.routeSorted.filter(i=>n.has(i)),t.route,c$1.ROUTE,"current-route-outpost",H(c$1.ROUTE)),r=[...s,...a];return {id:k,label:`Current Route: ${o.path} (${r.length})`,tags:[p("active",w)],children:r}},tt=(t,e)=>{let o=_(t.globalSorted,t.global,c$1.GLOBAL,"outpost",z),s=_(t.routeSorted,t.route,c$1.ROUTE,"outpost",z),n=[{id:y,label:"Outposts",children:[{id:U,label:`Global (${o.length})`,tags:[p(c$1.GLOBAL,d)],children:o},{id:P,label:`Route (${s.length})`,tags:[p(c$1.ROUTE,L)],children:s}]}];return e&&(n.push(Q(e)),n.push(W(t,e))),n},et=(t,e,o)=>({"Outpost Details":[{key:"name",value:t},{key:"scope",value:e},{key:"priority",value:o.priority??g$1},{key:"hooks",value:o.hooks??[a.BEFORE_EACH]},{key:"timeout",value:o.timeout??"none (uses default)"},{key:"lazy",value:o.lazy}]}),ot=/^route-assignment-(.+)$/,nt=new RegExp(`^(?:outpost|current-route-outpost)-(${c$1.GLOBAL}|${c$1.ROUTE})-(.+)$`),st=(t,e)=>{let o=Y(t,e),s=t.meta.outposts??[],n=o.filter(a=>!s.includes(a));return {"Route Details":[{key:"name",value:t.name?String(t.name):"unnamed"},{key:"path",value:t.path},{key:"outposts (own)",value:s},...n.length>0?[{key:"outposts (inherited)",value:n}]:[],{key:"outposts (resolved)",value:o}]}},rt=(t,e,o)=>{let s=t.match(nt);if(s){let[,n,a]=s,i=(n===c$1.GLOBAL?e.global:e.route).get(a);return i?et(a,n,i):null}if(o){let n=t.match(ot);if(n){let a=n[1],r=o.getRoutes().find(i=>i.name?String(i.name)===a:i.path===a);return r?st(r,o):null}}return null},X=(t,e,o,s,n=false,a)=>{t.addInspector({id:c,label:g,icon:A}),t.on.getInspectorTree(r=>{r.inspectorId===c&&(r.rootNodes=tt(e,o));}),t.on.getInspectorState(r=>{if(r.inspectorId!==c)return;let i=rt(r.nodeId,e,o);i&&(r.state=i);}),o.afterEach(()=>{f(t);}),j$1(d$1.DEVTOOLS_INSPECT,n,s,a);},f=t=>{t.sendInspectorTree(c),t.sendInspectorState(c);};var u={OFF:"off",LOG:"log",DEBUG:"debug"};var it=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(S+v);return t===null?null:t===u.OFF||t===u.LOG||t===u.DEBUG?t:null}catch{return null}},at=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(S+v,t);}catch{}},lt=(t,e,o)=>e?u.DEBUG:t??o?u.LOG:u.OFF,I=t=>{switch(t){case u.LOG:return {log:true,debug:false};case u.DEBUG:return {log:true,debug:true};case u.OFF:default:return {log:false,debug:false}}},ut=t=>t.debug?u.DEBUG:t.log?u.LOG:u.OFF,K=(t,e,o)=>{let s=it();if(s!==null)return I(s);let n=lt(t,e,o);return I(n)},Z=(t,e)=>{let o=I(e);t.log=o.log,t.debug=o.debug,at(e);},j=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:ut(t),options:[{label:"Off",value:u.OFF},{label:"Log",value:u.LOG},{label:"Log + Debug",value:u.DEBUG}],component:"button-group"}});var R=null,Ct=(t,e$1,o,s,n,a,r,i)=>{let G=K(a,r,e);n.log=G.log,n.debug=G.debug,setupDevToolsPlugin({id:m,label:g,logo:x,packageName:"vue-router-citadel",homepage:"https://kassaila.github.io/vue-router-citadel",enableEarlyProxy:true,app:t,settings:j(n)},T=>{R=T,T.on.setPluginSettings(C=>{C.key==="logLevel"&&Z(n,C.newValue);}),X(T,e$1,o,s,n.debug,i);});},Dt=()=>{R&&f(R);},bt=()=>{R=null;};
2
2
  export{bt as clearDevtoolsApi,Dt as notifyDevtoolsRefresh,Ct as setupDevtools};
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 T,$=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},T=(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 C,Zt,qt,Y,ht,It,xt,At,Qt,to,oo,eo,no,ao,ro,io,Gt,at,Pt=h(()=>{G();k();$();K();C=(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(C("lazy",Ct)),{id:`${t}-${n}-${o}`,label:o,tags:a}),Y=(t,o,e,n,a)=>{let s=[];for(let r of t){let u=o.get(r);u&&s.push(qt(n,r,u,e,a(u)));}return s},ht=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,Zt(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??[]}},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,r=[C(`${n.length} outpost${n.length===1?"":"s"}`,M)];s>0&&r.push(C(`${s} inherited`,z)),o.push({id:`route-assignment-${xt(e)}`,label:xt(e),tags:r});}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)),r=[...n,...s];return {id:yt,label:`Current Route: ${e.path} (${r.length})`,tags:[C("active",Dt)],children:r}},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:[C(exports.NavigationOutpostScopes.GLOBAL,z)],children:e},{id:Et,label:`Route (${n.length})`,tags:[C(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})-(.+)$`),ro=(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}]}},io=(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],r=e.getRoutes().find(u=>u.name?String(u.name)===s:u.path===s);return r?ro(r,e):null}}return null},Gt=(t,o,e,n,a=false,s)=>{t.addInspector({id:w,label:F,icon:Nt}),t.on.getInspectorTree(r=>{r.inspectorId===w&&(r.rootNodes=oo(o,e));}),t.on.getInspectorState(r=>{if(r.inspectorId!==w)return;let u=io(r.nodeId,o,e);u&&(r.state=u);}),e.afterEach(()=>{at(t);}),T(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,rt,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,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}}},lo=t=>t.debug?N.DEBUG:t.log?N.LOG:N.OFF,wt=(t,o,e)=>{let n=so();if(n!==null)return rt(n);let a=po(t,o,e);return rt(a)},Ht=(t,o)=>{let e=rt(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,r,u)=>{let c=wt(s,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(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,r)=>{let u=n.get(s)?.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),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:r,onTimeout:u}=e,{router:c}=o,g=t.timeout??r;T(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`),T(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),T(exports.DebugPoints.ERROR_CATCH,a.debug,n,e.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}},Q=async(t,o,e,n,a)=>{let{hook:s,to:r,from:u}=o,c=a.log||a.debug,g=r.matched.flatMap(d=>d.meta?.outposts??[]),v=new Set(g);g.length!==v.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,s)}).length,B=t.routeSorted.filter(d=>{let R=t.route.get(d);return v.has(d)&&R&&V(R,s)}).length,y=I+B;if(y===0)return exports.NavigationOutpostVerdicts.ALLOW;c&&n.info(`${s}: ${u.path} -> ${r.path} (${y} outposts)`),T(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,s))continue;O++,c&&n.info(`Processing outpost ${O}/${y}: "${d}" [${s}]`);let S=await ft(R,o,e,n,a);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,S),T(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),S}for(let d of t.routeSorted){if(!v.has(d))continue;let R=t.route.get(d);if(!R){n.warn(`Route outpost "${d}" not found in registry`);continue}if(!V(R,s))continue;O++,c&&n.info(`Processing outpost ${O}/${y}: "${d}" [${s}]`);let S=await ft(R,o,e,n,a);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${d}":`,S),T(exports.DebugPoints.OUTPOST_BLOCK,a.debug,n,e.debugHandler),S}return exports.NavigationOutpostVerdicts.ALLOW},mt=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:s=P}=o,r=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=(i,p,l)=>({verdicts:exports.NavigationOutpostVerdicts,to:i,from:p,router:t,hook:l}),y=()=>O.log||O.debug,d=i=>async(p,l)=>{let f=B(p,l,i),L=await Q(g,f,v,r,O);return mt(L)};I.push(t.beforeEach(d(exports.NavigationHooks.BEFORE_EACH))),I.push(t.beforeResolve(d(exports.NavigationHooks.BEFORE_RESOLVE)));let R=t.afterEach(async(i,p)=>{let l=B(i,p,exports.NavigationHooks.AFTER_EACH);try{await Q(g,l,v,r,O);}catch(f){r.error("Error in afterEach outpost:",f),T(exports.DebugPoints.ERROR_CATCH,O.debug,r,u);}});I.push(R);let S=i=>{let{scope:p="global",name:l,handler:f,priority:L,hooks:zt,timeout:Mt,lazy:j=false}=i,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));y()&&r.info(`Deploying ${p} outpost: ${l}${j?" (lazy)":""}`),ct(g,p,{name:l,getHandler:Kt,lazy:j,priority:L,hooks:zt,timeout:Mt},s,r),c&&W().then(D=>D?.notifyDevtoolsRefresh());},st=(i,p)=>{y()&&r.info(`Abandoning ${i} outpost: ${p}`);let l=dt(g,i,p,s);return c&&l&&W().then(f=>f?.notifyDevtoolsRefresh()),l},ut=i=>t.getRoutes().find(p=>p.name===i),pt={install(i){c&&W().then(p=>{p&&(p.setupDevtools(i,g,t,r,O,e,n,u),T(exports.DebugPoints.DEVTOOLS_INIT,O.debug,r,u),y()&&r.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(i){if(Array.isArray(i))for(let p of i)S(p);else S(i);},abandonOutpost(i,p){if(Array.isArray(p)){let l=true;for(let f of p)st(i,f)||(l=false);return l}else return st(i,p)},getOutpostNames(i){return Ot(g,i)},assignOutpostToRoute(i,p){let l=ut(i);if(!l)return r.warn(`Route "${i}" not found`),false;let f=Array.isArray(p)?p:[p];l.meta.outposts||(l.meta.outposts=[]);for(let L of f)l.meta.outposts.includes(L)||l.meta.outposts.push(L);return y()&&r.info(`Assigned outposts [${f.join(", ")}] to route "${i}"`),true},revokeOutpostFromRoute(i,p){let l=ut(i);if(!l)return r.warn(`Route "${i}" not found`),false;let f=Array.isArray(p)?p:[p];if(!l.meta.outposts){for(let L of f)r.warn(`Outpost "${L}" not found in route "${i}"`);return true}for(let L of f)l.meta.outposts.includes(L)||r.warn(`Outpost "${L}" not found in route "${i}"`);return l.meta.outposts=l.meta.outposts.filter(L=>!f.includes(L)),y()&&r.info(`Revoked outposts [${f.join(", ")}] from route "${i}"`),true},destroy(){y()&&r.info("Destroying citadel");for(let i of I)i();I.length=0,g.global.clear(),g.route.clear(),g.globalSorted.length=0,g.routeSorted.length=0,c&&W().then(i=>i?.clearDevtoolsApi());}};return o.outposts&&pt.deployOutpost(o.outposts),pt};
2
- exports.createNavigationCitadel=vo;
1
+ 'use strict';var devtoolsApi=require('@vue/devtools-api');var Wt=Object.defineProperty;var D=(t,o)=>()=>(t&&(o=t(t=0)),o);var jt=(t,o)=>{for(var e in o)Wt(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 k,x,P,H=D(()=>{k=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 T,$=D(()=>{H();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},T=(t,o,e,n)=>{o&&(e.debug(t),n?.(t));};});var Q,F,Nt,Rt,w,Lt,bt,Tt,Et,yt,St,tt,_t,z,M,Ct,ht,ot,et,K=D(()=>{G();Q="navigation.citadel",F="Navigation Citadel",Nt="castle",Rt="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",w=Q+".inspector",Lt="citadel-root",bt="citadel-"+exports.NavigationOutpostScopes.GLOBAL,Tt="citadel-"+exports.NavigationOutpostScopes.ROUTE,Et="citadel-route-assignments",yt="citadel-current-route",St=16777215,tt=4372867,_t=3900150,z=9133302,M=16096779,Ct=15485081,ht=1357990,ot="vue-router-citadel:settings:",et="logLevel";});var C,oo,eo,Y,Dt,It,xt,At,no,ro,ao,io,so,uo,po,lo,Gt,nt,Pt=D(()=>{G();H();$();K();C=(t,o)=>({label:t,textColor:St,backgroundColor:o}),oo=t=>t===exports.NavigationOutpostScopes.GLOBAL?z:M,eo=(t,o,e,n,r)=>(e.lazy&&r.push(C("lazy",Ct)),{id:`${t}-${n}-${o}`,label:o,tags:r}),Y=(t,o,e,n,r)=>{let i=[];for(let a of t){let u=o.get(a);u&&i.push(eo(n,a,u,e,r(u)));}return i},Dt=t=>{let o=t.hooks??[exports.NavigationHooks.BEFORE_EACH],e=t.priority??P;return [C(`priority: ${e}`,tt),C(o.length===1?o[0]:`${o.length} hooks`,_t)]},It=t=>o=>{let e=o.priority??P;return [C(t,oo(t)),C(`priority: ${e}`,tt)]},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??[]}},no=t=>{let o=[];for(let e of t.getRoutes()){let n=At(e,t);if(n.length===0)continue;let r=e.meta.outposts??[],i=n.filter(u=>!r.includes(u)).length,a=[C(`${n.length} outpost${n.length===1?"":"s"}`,M)];i>0&&a.push(C(`${i} inherited`,z)),o.push({id:`route-assignment-${xt(e)}`,label:xt(e),tags:a});}return {id:Et,label:`Route Assignments (${o.length})`,children:o}},ro=(t,o)=>{let e=o.currentRoute.value,n=Y(t.globalSorted,t.global,exports.NavigationOutpostScopes.GLOBAL,"current-route-outpost",It(exports.NavigationOutpostScopes.GLOBAL)),r=new Set(e.matched.flatMap(u=>u.meta?.outposts??[])),i=Y(t.routeSorted.filter(u=>r.has(u)),t.route,exports.NavigationOutpostScopes.ROUTE,"current-route-outpost",It(exports.NavigationOutpostScopes.ROUTE)),a=[...n,...i];return {id:yt,label:`Current Route: ${e.path} (${a.length})`,tags:[C("active",ht)],children:a}},ao=(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),r=[{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&&(r.push(no(o)),r.push(ro(t,o))),r},io=(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}]}),so=/^route-assignment-(.+)$/,uo=new RegExp(`^(?:outpost|current-route-outpost)-(${exports.NavigationOutpostScopes.GLOBAL}|${exports.NavigationOutpostScopes.ROUTE})-(.+)$`),po=(t,o)=>{let e=At(t,o),n=t.meta.outposts??[],r=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},...r.length>0?[{key:"outposts (inherited)",value:r}]:[],{key:"outposts (resolved)",value:e}]}},lo=(t,o,e)=>{let n=t.match(uo);if(n){let[,r,i]=n,u=(r===exports.NavigationOutpostScopes.GLOBAL?o.global:o.route).get(i);return u?io(i,r,u):null}if(e){let r=t.match(so);if(r){let i=r[1],a=e.getRoutes().find(u=>u.name?String(u.name)===i:u.path===i);return a?po(a,e):null}}return null},Gt=(t,o,e,n,r=false,i)=>{t.addInspector({id:w,label:F,icon:Nt}),t.on.getInspectorTree(a=>{a.inspectorId===w&&(a.rootNodes=ao(o,e));}),t.on.getInspectorState(a=>{if(a.inspectorId!==w)return;let u=lo(a.nodeId,o,e);u&&(a.state=u);}),e.afterEach(()=>{nt(t);}),T(exports.DebugPoints.DEVTOOLS_INSPECT,r,n,i);},nt=t=>{t.sendInspectorTree(w),t.sendInspectorState(w);};});var m,Ht=D(()=>{m={OFF:"off",LOG:"log",DEBUG:"debug"};});var go,co,Oo,rt,vo,wt,kt,$t,Bt=D(()=>{Ht();K();go=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(ot+et);return t===null?null:t===m.OFF||t===m.LOG||t===m.DEBUG?t:null}catch{return null}},co=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(ot+et,t);}catch{}},Oo=(t,o,e)=>o?m.DEBUG:t??e?m.LOG:m.OFF,rt=t=>{switch(t){case m.LOG:return {log:true,debug:false};case m.DEBUG:return {log:true,debug:true};case m.OFF:default:return {log:false,debug:false}}},vo=t=>t.debug?m.DEBUG:t.log?m.LOG:m.OFF,wt=(t,o,e)=>{let n=go();if(n!==null)return rt(n);let r=Oo(t,o,e);return rt(r)},kt=(t,o)=>{let e=rt(o);t.log=e.log,t.debug=e.debug,co(o);},$t=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:vo(t),options:[{label:"Off",value:m.OFF},{label:"Log",value:m.LOG},{label:"Log + Debug",value:m.DEBUG}],component:"button-group"}});});var Ut={};jt(Ut,{clearDevtoolsApi:()=>Ro,notifyDevtoolsRefresh:()=>No,setupDevtools:()=>fo});var X,fo,No,Ro,Vt=D(()=>{H();K();Pt();Bt();X=null,fo=(t,o,e,n,r,i,a,u)=>{let g=wt(i,a,k);r.log=g.log,r.debug=g.debug,devtoolsApi.setupDevToolsPlugin({id:Q,label:F,logo:Rt,packageName:"vue-router-citadel",homepage:"https://kassaila.github.io/vue-router-citadel",enableEarlyProxy:true,app:t,settings:$t(r)},c=>{X=c,c.on.setPluginSettings(N=>{N.key==="logLevel"&&kt(r,N.newValue);}),Gt(c,o,e,n,r.debug,u);});},No=()=>{X&&nt(X);},Ro=()=>{X=null;};});G();$();G();H();$();var pt=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),lt=(t,o,e)=>{let n=t[o],r=`${o}Sorted`;t[r]=Array.from(n.keys()).sort((i,a)=>{let u=n.get(i)?.priority??e,g=n.get(a)?.priority??e;return u-g});},gt=(t,o,e,n,r)=>{t[o].has(e.name)&&r.warn(`${o} outpost "${e.name}" already exists, replacing...`),t[o].set(e.name,e),lt(t,o,n);},ct=(t,o,e,n)=>{let r=t[o].delete(e);return r&&lt(t,o,n),r},dt=(t,o)=>Array.from(t[o].keys());G();H();$();var Jt=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},Ot=(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(Jt(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),mt=Symbol("timeout"),Zt=t=>{let o;return {promise:new Promise((n,r)=>{o=setTimeout(()=>{let i=new Error(`Timeout after ${t}ms`);i[mt]=true,r(i);},t);}),cancel:()=>clearTimeout(o)}},qt=async(t,o)=>{let{promise:e,cancel:n}=Zt(o);try{return await Promise.race([t,e])}finally{n();}},Qt=t=>t instanceof Error&&mt in t,to=t=>t instanceof Error?t:new Error(String(t)),vt=async(t,o,e,n,r)=>{let{router:i}=o,a=t.timeout??e.defaultTimeout;T(exports.DebugPoints.OUTPOST_ENTER,r.debug,n,e.debugHandler);let u=async g=>{try{return Ot(await g(),i)}catch(c){return n.error(`Recovery handler for "${t.name}" threw error:`,c),T(exports.DebugPoints.ERROR_CATCH,r.debug,n,e.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}};try{let g=await t.getHandler(),c=a?await qt(g(o),a):await g(o);return Ot(c,i)}catch(g){if(Qt(g)){n.warn(`Outpost "${t.name}" timed out after ${a}ms`),T(exports.DebugPoints.OUTPOST_TIMEOUT,r.debug,n,e.debugHandler);let N=t.onTimeout??e.onTimeout;return N?u(()=>N(t.name,o)):exports.NavigationOutpostVerdicts.BLOCK}let c=t.onError??e.onError;if(c){let N=to(g);return u(()=>c(N,o))}return n.error(`Outpost "${t.name}" threw error:`,g),T(exports.DebugPoints.ERROR_CATCH,r.debug,n,e.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}},q=async(t,o,e,n,r)=>{let{hook:i,to:a,from:u}=o,g=r.log||r.debug,c=a.matched.flatMap(d=>d.meta?.outposts??[]),N=new Set(c);c.length!==N.size&&n.warn(`Duplicate outposts detected on route "${String(a.name??a.path)}"`);let E=0,I=t.globalSorted.filter(d=>{let f=t.global.get(d);return f&&V(f,i)}).length,B=t.routeSorted.filter(d=>{let f=t.route.get(d);return N.has(d)&&f&&V(f,i)}).length,y=I+B;if(y===0)return exports.NavigationOutpostVerdicts.ALLOW;g&&n.info(`${i}: ${u.path} -> ${a.path} (${y} outposts)`),T(exports.DebugPoints.NAVIGATION_START,r.debug,n,e.debugHandler);for(let d of t.globalSorted){let f=t.global.get(d);if(!f||!V(f,i))continue;E++,g&&n.info(`Processing outpost ${E}/${y}: "${d}" [${i}]`);let S=await vt(f,o,e,n,r);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return g&&n.warn(`Patrol stopped by outpost "${d}":`,S),T(exports.DebugPoints.OUTPOST_BLOCK,r.debug,n,e.debugHandler),S}for(let d of t.routeSorted){if(!N.has(d))continue;let f=t.route.get(d);if(!f){n.warn(`Route outpost "${d}" not found in registry`);continue}if(!V(f,i))continue;E++,g&&n.info(`Processing outpost ${E}/${y}: "${d}" [${i}]`);let S=await vt(f,o,e,n,r);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return g&&n.warn(`Patrol stopped by outpost "${d}":`,S),T(exports.DebugPoints.OUTPOST_BLOCK,r.debug,n,e.debugHandler),S}return exports.NavigationOutpostVerdicts.ALLOW},ft=t=>{switch(t){case exports.NavigationOutpostVerdicts.ALLOW:return true;case exports.NavigationOutpostVerdicts.BLOCK:return false;default:return t}};var at=null,Ft=false,W=async()=>{if(Ft)return null;if(!at)try{at=await Promise.resolve().then(()=>(Vt(),Ut));}catch{return Ft=true,null}return at},Lo=(t,o={})=>{let{log:e,debug:n,devtools:r=k,defaultPriority:i=P}=o,a=o.logger??exports.createDefaultLogger(),u=o.debugHandler??exports.createDefaultDebugHandler(),g=r&&typeof window<"u",c=pt(),N={...o,debugHandler:u},E={log:e??k,debug:n??false},I=[],B=(s,p,l)=>({verdicts:exports.NavigationOutpostVerdicts,to:s,from:p,router:t,hook:l}),y=()=>E.log||E.debug,d=s=>async(p,l)=>{let O=B(p,l,s),b=await q(c,O,N,a,E);return ft(b)};I.push(t.beforeEach(d(exports.NavigationHooks.BEFORE_EACH))),I.push(t.beforeResolve(d(exports.NavigationHooks.BEFORE_RESOLVE)));let f=t.afterEach(async(s,p)=>{let l=B(s,p,exports.NavigationHooks.AFTER_EACH);try{await q(c,l,N,a,E);}catch(O){a.error("Error in afterEach outpost:",O),T(exports.DebugPoints.ERROR_CATCH,E.debug,a,u);}});I.push(f);let S=s=>{let{scope:p="global",name:l,handler:O,priority:b,hooks:zt,timeout:Mt,lazy:j=false,onError:Kt,onTimeout:Yt}=s,A=null,U=null,Xt=async()=>A||(j?(U||(U=O().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=O,A));y()&&a.info(`Deploying ${p} outpost: ${l}${j?" (lazy)":""}`),gt(c,p,{name:l,getHandler:Xt,lazy:j,priority:b,hooks:zt,timeout:Mt,onError:Kt,onTimeout:Yt},i,a),g&&W().then(h=>h?.notifyDevtoolsRefresh());},it=(s,p)=>{y()&&a.info(`Abandoning ${s} outpost: ${p}`);let l=ct(c,s,p,i);return g&&l&&W().then(O=>O?.notifyDevtoolsRefresh()),l},st=s=>t.getRoutes().find(p=>p.name===s),ut={install(s){g&&W().then(p=>{p&&(p.setupDevtools(s,c,t,a,E,e,n,u),T(exports.DebugPoints.DEVTOOLS_INIT,E.debug,a,u),y()&&a.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 O of p)it(s,O)||(l=false);return l}else return it(s,p)},getOutpostNames(s){return dt(c,s)},assignOutpostToRoute(s,p){let l=st(s);if(!l)return a.warn(`Route "${s}" not found`),false;let O=Array.isArray(p)?p:[p];l.meta.outposts||(l.meta.outposts=[]);for(let b of O)l.meta.outposts.includes(b)||l.meta.outposts.push(b);return y()&&a.info(`Assigned outposts [${O.join(", ")}] to route "${s}"`),true},revokeOutpostFromRoute(s,p){let l=st(s);if(!l)return a.warn(`Route "${s}" not found`),false;let O=Array.isArray(p)?p:[p];if(!l.meta.outposts){for(let b of O)a.warn(`Outpost "${b}" not found in route "${s}"`);return true}for(let b of O)l.meta.outposts.includes(b)||a.warn(`Outpost "${b}" not found in route "${s}"`);return l.meta.outposts=l.meta.outposts.filter(b=>!O.includes(b)),y()&&a.info(`Revoked outposts [${O.join(", ")}] from route "${s}"`),true},destroy(){y()&&a.info("Destroying citadel");for(let s of I)s();I.length=0,c.global.clear(),c.route.clear(),c.globalSorted.length=0,c.routeSorted.length=0,g&&W().then(s=>s?.clearDevtoolsApi());}};return o.outposts&&ut.deployOutpost(o.outposts),ut};
2
+ exports.createNavigationCitadel=Lo;
package/dist/index.d.cts CHANGED
@@ -163,18 +163,57 @@ type NavigationOutpostOutcome = NavigationOutpostVerdict | RouteLocationRaw | Er
163
163
  * Navigation outpost handler function signature
164
164
  */
165
165
  type NavigationOutpostHandler = (ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
+ /**
167
+ * Error handler signature — called when an outpost handler throws.
168
+ * Used for citadel-level `onError` and per-outpost `onError`.
169
+ */
170
+ type NavigationOutpostErrorHandler = (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
171
+ /**
172
+ * Timeout handler signature — called when an outpost exceeds its timeout.
173
+ * Used for citadel-level `onTimeout` and per-outpost `onTimeout`.
174
+ */
175
+ type NavigationOutpostTimeoutHandler = (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
176
  /**
167
177
  * Lazy outpost loader — returns a module with default export
168
178
  */
169
179
  type LazyOutpostLoader = () => Promise<{
170
180
  default: NavigationOutpostHandler;
171
181
  }>;
182
+ /**
183
+ * Shared optional behavior for outposts. Used by both `NavigationOutpost` (deployment input)
184
+ * and `RegisteredNavigationOutpost` (runtime form).
185
+ */
186
+ interface OutpostBehaviorOptions {
187
+ /**
188
+ * Priority for outposts (lower = processed first). Default: 100
189
+ */
190
+ priority?: number;
191
+ /**
192
+ * Hooks this outpost should run on. Default: ['beforeEach']
193
+ */
194
+ hooks?: NavigationHook[];
195
+ /**
196
+ * Timeout for this outpost in milliseconds. Overrides defaultTimeout.
197
+ * Note: For lazy outposts, timeout applies only to handler execution, not module loading.
198
+ */
199
+ timeout?: number;
200
+ /**
201
+ * Per-outpost error handler. Replaces the citadel-level `onError` for this outpost.
202
+ * If absent, falls back to the citadel-level `onError`, then to the default behavior (BLOCK).
203
+ */
204
+ onError?: NavigationOutpostErrorHandler;
205
+ /**
206
+ * Per-outpost timeout handler. Replaces the citadel-level `onTimeout` for this outpost.
207
+ * If absent, falls back to the citadel-level `onTimeout`, then to the default behavior (BLOCK).
208
+ */
209
+ onTimeout?: NavigationOutpostTimeoutHandler;
210
+ }
172
211
  /**
173
212
  * Navigation outpost configuration.
174
213
  * Generic parameter S constrains the name field based on scope.
175
214
  * Generic parameter L constrains handler type based on lazy flag.
176
215
  */
177
- interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> {
216
+ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> extends OutpostBehaviorOptions {
178
217
  /**
179
218
  * Outpost scope. Default: 'global'
180
219
  */
@@ -189,19 +228,6 @@ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L exten
189
228
  * When lazy: false (default), must be a NavigationOutpostHandler.
190
229
  */
191
230
  handler: L extends true ? LazyOutpostLoader : NavigationOutpostHandler;
192
- /**
193
- * Priority for outposts (lower = processed first). Default: 100
194
- */
195
- priority?: number;
196
- /**
197
- * Hooks this outpost should run on. Default: ['beforeEach']
198
- */
199
- hooks?: NavigationHook[];
200
- /**
201
- * Timeout for this outpost in milliseconds. Overrides defaultTimeout.
202
- * Note: For lazy outposts, timeout applies only to handler execution, not module loading.
203
- */
204
- timeout?: number;
205
231
  /**
206
232
  * Mark handler as lazy-loaded. Default: false.
207
233
  * When true, handler must return Promise<{ default: NavigationOutpostHandler }>.
@@ -215,7 +241,7 @@ interface NavigationCitadelOptions {
215
241
  /**
216
242
  * Initial outposts to deploy on citadel creation
217
243
  */
218
- outposts?: NavigationOutpost<NavigationOutpostScope, boolean>[];
244
+ outposts?: Array<NavigationOutpost<NavigationOutpostScope, boolean>>;
219
245
  /**
220
246
  * Enable logging for non-critical events. Default: __DEV__
221
247
  * Critical events (errors, timeouts) are always logged regardless of this setting.
@@ -260,7 +286,7 @@ interface NavigationCitadelOptions {
260
286
  /**
261
287
  * Global error handler
262
288
  */
263
- onError?: (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
289
+ onError?: NavigationOutpostErrorHandler;
264
290
  /**
265
291
  * Default priority for outposts. Default: 100
266
292
  */
@@ -272,7 +298,7 @@ interface NavigationCitadelOptions {
272
298
  /**
273
299
  * Handler called when outpost times out
274
300
  */
275
- onTimeout?: (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
301
+ onTimeout?: NavigationOutpostTimeoutHandler;
276
302
  }
277
303
  /**
278
304
  * Public API returned by createNavigationCitadel
@@ -286,7 +312,7 @@ interface NavigationCitadelAPI {
286
312
  /**
287
313
  * Deploy one or multiple outposts
288
314
  */
289
- deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | NavigationOutpost<S, L>[]) => void;
315
+ deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | Array<NavigationOutpost<S, L>>) => void;
290
316
  /**
291
317
  * Remove one or multiple global outposts by name(s)
292
318
  */
@@ -369,4 +395,4 @@ declare const createDefaultDebugHandler: () => DebugHandler;
369
395
  */
370
396
  declare const createNavigationCitadel: (router: Router, options?: NavigationCitadelOptions) => NavigationCitadelAPI;
371
397
 
372
- export { type CitadelLogger, type DebugHandler, type DebugPoint, DebugPoints, type GlobalOutpostName, type GlobalOutpostRegistry, type LazyOutpostLoader, type NavigationCitadelAPI, type NavigationCitadelOptions, type NavigationHook, NavigationHooks, type NavigationOutpost, type NavigationOutpostContext, type NavigationOutpostHandler, type NavigationOutpostScope, NavigationOutpostScopes, NavigationOutpostVerdicts, type OutpostName, type RouteOutpostName, type RouteOutpostRegistry, createDefaultDebugHandler, createDefaultLogger, createNavigationCitadel };
398
+ export { type CitadelLogger, type DebugHandler, type DebugPoint, DebugPoints, type GlobalOutpostName, type GlobalOutpostRegistry, type LazyOutpostLoader, type NavigationCitadelAPI, type NavigationCitadelOptions, type NavigationHook, NavigationHooks, type NavigationOutpost, type NavigationOutpostContext, type NavigationOutpostErrorHandler, type NavigationOutpostHandler, type NavigationOutpostScope, NavigationOutpostScopes, type NavigationOutpostTimeoutHandler, NavigationOutpostVerdicts, type OutpostBehaviorOptions, type OutpostName, type RouteOutpostName, type RouteOutpostRegistry, createDefaultDebugHandler, createDefaultLogger, createNavigationCitadel };
package/dist/index.d.ts CHANGED
@@ -163,18 +163,57 @@ type NavigationOutpostOutcome = NavigationOutpostVerdict | RouteLocationRaw | Er
163
163
  * Navigation outpost handler function signature
164
164
  */
165
165
  type NavigationOutpostHandler = (ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
+ /**
167
+ * Error handler signature — called when an outpost handler throws.
168
+ * Used for citadel-level `onError` and per-outpost `onError`.
169
+ */
170
+ type NavigationOutpostErrorHandler = (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
171
+ /**
172
+ * Timeout handler signature — called when an outpost exceeds its timeout.
173
+ * Used for citadel-level `onTimeout` and per-outpost `onTimeout`.
174
+ */
175
+ type NavigationOutpostTimeoutHandler = (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
176
  /**
167
177
  * Lazy outpost loader — returns a module with default export
168
178
  */
169
179
  type LazyOutpostLoader = () => Promise<{
170
180
  default: NavigationOutpostHandler;
171
181
  }>;
182
+ /**
183
+ * Shared optional behavior for outposts. Used by both `NavigationOutpost` (deployment input)
184
+ * and `RegisteredNavigationOutpost` (runtime form).
185
+ */
186
+ interface OutpostBehaviorOptions {
187
+ /**
188
+ * Priority for outposts (lower = processed first). Default: 100
189
+ */
190
+ priority?: number;
191
+ /**
192
+ * Hooks this outpost should run on. Default: ['beforeEach']
193
+ */
194
+ hooks?: NavigationHook[];
195
+ /**
196
+ * Timeout for this outpost in milliseconds. Overrides defaultTimeout.
197
+ * Note: For lazy outposts, timeout applies only to handler execution, not module loading.
198
+ */
199
+ timeout?: number;
200
+ /**
201
+ * Per-outpost error handler. Replaces the citadel-level `onError` for this outpost.
202
+ * If absent, falls back to the citadel-level `onError`, then to the default behavior (BLOCK).
203
+ */
204
+ onError?: NavigationOutpostErrorHandler;
205
+ /**
206
+ * Per-outpost timeout handler. Replaces the citadel-level `onTimeout` for this outpost.
207
+ * If absent, falls back to the citadel-level `onTimeout`, then to the default behavior (BLOCK).
208
+ */
209
+ onTimeout?: NavigationOutpostTimeoutHandler;
210
+ }
172
211
  /**
173
212
  * Navigation outpost configuration.
174
213
  * Generic parameter S constrains the name field based on scope.
175
214
  * Generic parameter L constrains handler type based on lazy flag.
176
215
  */
177
- interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> {
216
+ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> extends OutpostBehaviorOptions {
178
217
  /**
179
218
  * Outpost scope. Default: 'global'
180
219
  */
@@ -189,19 +228,6 @@ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L exten
189
228
  * When lazy: false (default), must be a NavigationOutpostHandler.
190
229
  */
191
230
  handler: L extends true ? LazyOutpostLoader : NavigationOutpostHandler;
192
- /**
193
- * Priority for outposts (lower = processed first). Default: 100
194
- */
195
- priority?: number;
196
- /**
197
- * Hooks this outpost should run on. Default: ['beforeEach']
198
- */
199
- hooks?: NavigationHook[];
200
- /**
201
- * Timeout for this outpost in milliseconds. Overrides defaultTimeout.
202
- * Note: For lazy outposts, timeout applies only to handler execution, not module loading.
203
- */
204
- timeout?: number;
205
231
  /**
206
232
  * Mark handler as lazy-loaded. Default: false.
207
233
  * When true, handler must return Promise<{ default: NavigationOutpostHandler }>.
@@ -215,7 +241,7 @@ interface NavigationCitadelOptions {
215
241
  /**
216
242
  * Initial outposts to deploy on citadel creation
217
243
  */
218
- outposts?: NavigationOutpost<NavigationOutpostScope, boolean>[];
244
+ outposts?: Array<NavigationOutpost<NavigationOutpostScope, boolean>>;
219
245
  /**
220
246
  * Enable logging for non-critical events. Default: __DEV__
221
247
  * Critical events (errors, timeouts) are always logged regardless of this setting.
@@ -260,7 +286,7 @@ interface NavigationCitadelOptions {
260
286
  /**
261
287
  * Global error handler
262
288
  */
263
- onError?: (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
289
+ onError?: NavigationOutpostErrorHandler;
264
290
  /**
265
291
  * Default priority for outposts. Default: 100
266
292
  */
@@ -272,7 +298,7 @@ interface NavigationCitadelOptions {
272
298
  /**
273
299
  * Handler called when outpost times out
274
300
  */
275
- onTimeout?: (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
301
+ onTimeout?: NavigationOutpostTimeoutHandler;
276
302
  }
277
303
  /**
278
304
  * Public API returned by createNavigationCitadel
@@ -286,7 +312,7 @@ interface NavigationCitadelAPI {
286
312
  /**
287
313
  * Deploy one or multiple outposts
288
314
  */
289
- deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | NavigationOutpost<S, L>[]) => void;
315
+ deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | Array<NavigationOutpost<S, L>>) => void;
290
316
  /**
291
317
  * Remove one or multiple global outposts by name(s)
292
318
  */
@@ -369,4 +395,4 @@ declare const createDefaultDebugHandler: () => DebugHandler;
369
395
  */
370
396
  declare const createNavigationCitadel: (router: Router, options?: NavigationCitadelOptions) => NavigationCitadelAPI;
371
397
 
372
- export { type CitadelLogger, type DebugHandler, type DebugPoint, DebugPoints, type GlobalOutpostName, type GlobalOutpostRegistry, type LazyOutpostLoader, type NavigationCitadelAPI, type NavigationCitadelOptions, type NavigationHook, NavigationHooks, type NavigationOutpost, type NavigationOutpostContext, type NavigationOutpostHandler, type NavigationOutpostScope, NavigationOutpostScopes, NavigationOutpostVerdicts, type OutpostName, type RouteOutpostName, type RouteOutpostRegistry, createDefaultDebugHandler, createDefaultLogger, createNavigationCitadel };
398
+ export { type CitadelLogger, type DebugHandler, type DebugPoint, DebugPoints, type GlobalOutpostName, type GlobalOutpostRegistry, type LazyOutpostLoader, type NavigationCitadelAPI, type NavigationCitadelOptions, type NavigationHook, NavigationHooks, type NavigationOutpost, type NavigationOutpostContext, type NavigationOutpostErrorHandler, type NavigationOutpostHandler, type NavigationOutpostScope, NavigationOutpostScopes, type NavigationOutpostTimeoutHandler, NavigationOutpostVerdicts, type OutpostBehaviorOptions, type OutpostName, type RouteOutpostName, type RouteOutpostRegistry, createDefaultDebugHandler, createDefaultLogger, createNavigationCitadel };
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 y=n.get(f)?.priority??a,c=n.get(s)?.priority??a;return y-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:y}=a,{router:c}=o,g=t.timeout??s;j$1(d.OUTPOST_ENTER,u.debug,n,a.debugHandler);try{let v=await t.getHandler(),p=g?await Promise.race([v(o),it(g)]):await v(o);return x(p,c)}catch(v){if(at(v)){if(n.warn(`Outpost "${t.name}" timed out after ${g}ms`),j$1(d.OUTPOST_TIMEOUT,u.debug,n,a.debugHandler),y){let p=await y(t.name,o);return x(p,c)}return b.BLOCK}if(f&&v instanceof Error){let p=await f(v,o);return x(p,c)}return n.error(`Outpost "${t.name}" threw error:`,v),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:y}=o,c=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 p=0,E=t.globalSorted.filter(l=>{let O=t.global.get(l);return O&&T(O,f)}).length,A=t.routeSorted.filter(l=>{let O=t.route.get(l);return v.has(l)&&O&&T(O,f)}).length,R=E+A;if(R===0)return b.ALLOW;c&&n.info(`${f}: ${y.path} -> ${s.path} (${R} outposts)`),j$1(d.NAVIGATION_START,u.debug,n,a.debugHandler);for(let l of t.globalSorted){let O=t.global.get(l);if(!O||!T(O,f))continue;p++,c&&n.info(`Processing outpost ${p}/${R}: "${l}" [${f}]`);let h=await J(O,o,a,n,u);if(h!==b.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${l}":`,h),j$1(d.OUTPOST_BLOCK,u.debug,n,a.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(!T(O,f))continue;p++,c&&n.info(`Processing outpost ${p}/${R}: "${l}" [${f}]`);let h=await J(O,o,a,n,u);if(h!==b.ALLOW)return c&&n.warn(`Patrol stopped by outpost "${l}":`,h),j$1(d.OUTPOST_BLOCK,u.debug,n,a.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},rt=(t,o={})=>{let{log:a$1,debug:n,devtools:u=e,defaultPriority:f=g}=o,s=o.logger??h(),y=o.debugHandler??i(),c=u&&typeof window<"u",g$1=U(),v={...o,debugHandler:y},p={log:a$1??e,debug:n??false},E=[],A=(e,i,r)=>({verdicts:b,to:e,from:i,router:t,hook:r}),R=()=>p.log||p.debug,l=e=>async(i,r)=>{let d=A(i,r,e),m=await I(g$1,d,v,s,p);return q(m)};E.push(t.beforeEach(l(a.BEFORE_EACH))),E.push(t.beforeResolve(l(a.BEFORE_RESOLVE)));let O=t.afterEach(async(e,i)=>{let r=A(e,i,a.AFTER_EACH);try{await I(g$1,r,v,s,p);}catch(d$1){s.error("Error in afterEach outpost:",d$1),j$1(d.ERROR_CATCH,p.debug,s,y);}});E.push(O);let h$1=e=>{let{scope:i="global",name:r,handler:d,priority:m,hooks:Z,timeout:tt,lazy:P=false}=e,$=null,S=null,ot=async()=>$||(P?(S||(S=d().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):($=d,$));R()&&s.info(`Deploying ${i} outpost: ${r}${P?" (lazy)":""}`),j(g$1,i,{name:r,getHandler:ot,lazy:P,priority:m,hooks:Z,timeout:tt},f,s),c&&H().then(C=>C?.notifyDevtoolsRefresh());},B=(e,i)=>{R()&&s.info(`Abandoning ${e} outpost: ${i}`);let r=W(g$1,e,i,f);return c&&r&&H().then(d=>d?.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,p,a$1,n,y),j$1(d.DEVTOOLS_INIT,p.debug,s,y),R()&&s.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(e){if(Array.isArray(e))for(let i of e)h$1(i);else h$1(e);},abandonOutpost(e,i){if(Array.isArray(i)){let r=true;for(let d of i)B(e,d)||(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 d=Array.isArray(i)?i:[i];r.meta.outposts||(r.meta.outposts=[]);for(let m of d)r.meta.outposts.includes(m)||r.meta.outposts.push(m);return R()&&s.info(`Assigned outposts [${d.join(", ")}] to route "${e}"`),true},revokeOutpostFromRoute(e,i){let r=M(e);if(!r)return s.warn(`Route "${e}" not found`),false;let d=Array.isArray(i)?i:[i];if(!r.meta.outposts){for(let m of d)s.warn(`Outpost "${m}" not found in route "${e}"`);return true}for(let m of d)r.meta.outposts.includes(m)||s.warn(`Outpost "${m}" not found in route "${e}"`);return r.meta.outposts=r.meta.outposts.filter(m=>!d.includes(m)),R()&&s.info(`Revoked outposts [${d.join(", ")}] from route "${e}"`),true},destroy(){R()&&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-W4ASYKYO.js';export{d as DebugPoints,a as NavigationHooks,c as NavigationOutpostScopes,b as NavigationOutpostVerdicts,i as createDefaultDebugHandler,h as createDefaultLogger}from'./chunk-W4ASYKYO.js';var F=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),K=(t,o,r)=>{let n=t[o],u=`${o}Sorted`;t[u]=Array.from(n.keys()).sort((c,a)=>{let y=n.get(c)?.priority??r,l=n.get(a)?.priority??r;return y-l});},U=(t,o,r,n,u)=>{t[o].has(r.name)&&u.warn(`${o} outpost "${r.name}" already exists, replacing...`),t[o].set(r.name,r),K(t,o,n);},W=(t,o,r,n)=>{let u=t[o].delete(r);return u&&K(t,o,n),u},j=(t,o)=>Array.from(t[o].keys());var it=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},Y=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(b).includes(t))return t;let r=`${f} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(it(t)){if(o.resolve(t).matched.length===0)throw new Error(r+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(r+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},H=(t,o)=>(t.hooks??[a.BEFORE_EACH]).includes(o),X=Symbol("timeout"),at=t=>{let o;return {promise:new Promise((n,u)=>{o=setTimeout(()=>{let c=new Error(`Timeout after ${t}ms`);c[X]=true,u(c);},t);}),cancel:()=>clearTimeout(o)}},st=async(t,o)=>{let{promise:r,cancel:n}=at(o);try{return await Promise.race([t,r])}finally{n();}},ut=t=>t instanceof Error&&X in t,lt=t=>t instanceof Error?t:new Error(String(t)),J=async(t,o,r,n,u)=>{let{router:c}=o,a=t.timeout??r.defaultTimeout;j$1(d.OUTPOST_ENTER,u.debug,n,r.debugHandler);let y=async l=>{try{return Y(await l(),c)}catch(p){return n.error(`Recovery handler for "${t.name}" threw error:`,p),j$1(d.ERROR_CATCH,u.debug,n,r.debugHandler),b.BLOCK}};try{let l=await t.getHandler(),p=a?await st(l(o),a):await l(o);return Y(p,c)}catch(l){if(ut(l)){n.warn(`Outpost "${t.name}" timed out after ${a}ms`),j$1(d.OUTPOST_TIMEOUT,u.debug,n,r.debugHandler);let R=t.onTimeout??r.onTimeout;return R?y(()=>R(t.name,o)):b.BLOCK}let p=t.onError??r.onError;if(p){let R=lt(l);return y(()=>p(R,o))}return n.error(`Outpost "${t.name}" threw error:`,l),j$1(d.ERROR_CATCH,u.debug,n,r.debugHandler),b.BLOCK}},x=async(t,o,r,n,u)=>{let{hook:c,to:a,from:y}=o,l=u.log||u.debug,p=a.matched.flatMap(d=>d.meta?.outposts??[]),R=new Set(p);p.length!==R.size&&n.warn(`Duplicate outposts detected on route "${String(a.name??a.path)}"`);let m=0,C=t.globalSorted.filter(d=>{let f=t.global.get(d);return f&&H(f,c)}).length,$=t.routeSorted.filter(d=>{let f=t.route.get(d);return R.has(d)&&f&&H(f,c)}).length,b$1=C+$;if(b$1===0)return b.ALLOW;l&&n.info(`${c}: ${y.path} -> ${a.path} (${b$1} outposts)`),j$1(d.NAVIGATION_START,u.debug,n,r.debugHandler);for(let d$1 of t.globalSorted){let f=t.global.get(d$1);if(!f||!H(f,c))continue;m++,l&&n.info(`Processing outpost ${m}/${b$1}: "${d$1}" [${c}]`);let E=await J(f,o,r,n,u);if(E!==b.ALLOW)return l&&n.warn(`Patrol stopped by outpost "${d$1}":`,E),j$1(d.OUTPOST_BLOCK,u.debug,n,r.debugHandler),E}for(let d$1 of t.routeSorted){if(!R.has(d$1))continue;let f=t.route.get(d$1);if(!f){n.warn(`Route outpost "${d$1}" not found in registry`);continue}if(!H(f,c))continue;m++,l&&n.info(`Processing outpost ${m}/${b$1}: "${d$1}" [${c}]`);let E=await J(f,o,r,n,u);if(E!==b.ALLOW)return l&&n.warn(`Patrol stopped by outpost "${d$1}":`,E),j$1(d.OUTPOST_BLOCK,u.debug,n,r.debugHandler),E}return b.ALLOW},q=t=>{switch(t){case b.ALLOW:return true;case b.BLOCK:return false;default:return t}};var I=null,Q=false,P=async()=>{if(Q)return null;if(!I)try{I=await import('./devtools-OVHNQHN4.js');}catch{return Q=true,null}return I},pt=(t,o={})=>{let{log:r,debug:n,devtools:u=e,defaultPriority:c=g}=o,a$1=o.logger??h(),y=o.debugHandler??i(),l=u&&typeof window<"u",p=F(),R={...o,debugHandler:y},m={log:r??e,debug:n??false},C=[],$=(e,i,s)=>({verdicts:b,to:e,from:i,router:t,hook:s}),b$1=()=>m.log||m.debug,d$1=e=>async(i,s)=>{let g=$(i,s,e),v=await x(p,g,R,a$1,m);return q(v)};C.push(t.beforeEach(d$1(a.BEFORE_EACH))),C.push(t.beforeResolve(d$1(a.BEFORE_RESOLVE)));let f=t.afterEach(async(e,i)=>{let s=$(e,i,a.AFTER_EACH);try{await x(p,s,R,a$1,m);}catch(g){a$1.error("Error in afterEach outpost:",g),j$1(d.ERROR_CATCH,m.debug,a$1,y);}});C.push(f);let E=e=>{let{scope:i="global",name:s,handler:g,priority:v,hooks:Z,timeout:tt,lazy:S=false,onError:ot,onTimeout:et}=e,w=null,A=null,nt=async()=>w||(S?(A||(A=g().then(T=>{if(!T.default||typeof T.default!="function")throw new Error(`Lazy outpost "${s}" must export default handler`);return w=T.default,w}).catch(T=>{throw A=null,T instanceof Error?T:new Error(String(T))})),A):(w=g,w));b$1()&&a$1.info(`Deploying ${i} outpost: ${s}${S?" (lazy)":""}`),U(p,i,{name:s,getHandler:nt,lazy:S,priority:v,hooks:Z,timeout:tt,onError:ot,onTimeout:et},c,a$1),l&&P().then(T=>T?.notifyDevtoolsRefresh());},z=(e,i)=>{b$1()&&a$1.info(`Abandoning ${e} outpost: ${i}`);let s=W(p,e,i,c);return l&&s&&P().then(g=>g?.notifyDevtoolsRefresh()),s},B=e=>t.getRoutes().find(i=>i.name===e),M={install(e){l&&P().then(i=>{i&&(i.setupDevtools(e,p,t,a$1,m,r,n,y),j$1(d.DEVTOOLS_INIT,m.debug,a$1,y),b$1()&&a$1.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(e){if(Array.isArray(e))for(let i of e)E(i);else E(e);},abandonOutpost(e,i){if(Array.isArray(i)){let s=true;for(let g of i)z(e,g)||(s=false);return s}else return z(e,i)},getOutpostNames(e){return j(p,e)},assignOutpostToRoute(e,i){let s=B(e);if(!s)return a$1.warn(`Route "${e}" not found`),false;let g=Array.isArray(i)?i:[i];s.meta.outposts||(s.meta.outposts=[]);for(let v of g)s.meta.outposts.includes(v)||s.meta.outposts.push(v);return b$1()&&a$1.info(`Assigned outposts [${g.join(", ")}] to route "${e}"`),true},revokeOutpostFromRoute(e,i){let s=B(e);if(!s)return a$1.warn(`Route "${e}" not found`),false;let g=Array.isArray(i)?i:[i];if(!s.meta.outposts){for(let v of g)a$1.warn(`Outpost "${v}" not found in route "${e}"`);return true}for(let v of g)s.meta.outposts.includes(v)||a$1.warn(`Outpost "${v}" not found in route "${e}"`);return s.meta.outposts=s.meta.outposts.filter(v=>!g.includes(v)),b$1()&&a$1.info(`Revoked outposts [${g.join(", ")}] from route "${e}"`),true},destroy(){b$1()&&a$1.info("Destroying citadel");for(let e of C)e();C.length=0,p.global.clear(),p.route.clear(),p.globalSorted.length=0,p.routeSorted.length=0,l&&P().then(e=>e?.clearDevtoolsApi());}};return o.outposts&&M.deployOutpost(o.outposts),M};export{pt as createNavigationCitadel};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-router-citadel",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Structured navigation defense for Vue Router",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -62,9 +62,6 @@
62
62
  ],
63
63
  "author": "Kassaila",
64
64
  "license": "MIT",
65
- "engines": {
66
- "node": ">=22.0.0"
67
- },
68
65
  "repository": {
69
66
  "type": "git",
70
67
  "url": "git+https://github.com/Kassaila/vue-router-citadel.git"
@@ -81,25 +78,28 @@
81
78
  "@vue/devtools-api": "^8.0.5"
82
79
  },
83
80
  "devDependencies": {
84
- "@commitlint/cli": "^20.4.1",
85
- "@commitlint/config-conventional": "^20.4.1",
86
- "@eslint/js": "^9.39.2",
87
- "@size-limit/preset-small-lib": "^12.0.0",
88
- "@vitest/coverage-v8": "^4.0.18",
89
- "@vue/test-utils": "^2.4.6",
90
- "eslint": "^9.39.2",
81
+ "@commitlint/cli": "^20.5.0",
82
+ "@commitlint/config-conventional": "^20.5.0",
83
+ "@eslint/js": "^10.0.1",
84
+ "@size-limit/preset-small-lib": "^12.0.1",
85
+ "@stylistic/eslint-plugin": "^5.10.0",
86
+ "@vitest/coverage-v8": "^4.1.2",
87
+ "@vue/devtools-kit": "^8.1.1",
88
+ "eslint": "^10.1.0",
91
89
  "eslint-config-prettier": "^10.1.8",
92
- "happy-dom": "^20.4.0",
93
- "husky": "^9.0.0",
94
- "lint-staged": "^16.0.0",
95
- "prettier": "^3.0.0",
96
- "size-limit": "^12.0.0",
97
- "tsup": "^8.0.0",
98
- "typescript": "^5.0.0",
99
- "typescript-eslint": "^8.55.0",
90
+ "eslint-plugin-import-x": "^4.16.2",
91
+ "eslint-plugin-kassaila": "^0.2.0",
92
+ "happy-dom": "^20.8.8",
93
+ "husky": "^9.1.7",
94
+ "lint-staged": "^16.4.0",
95
+ "prettier": "^3.8.1",
96
+ "size-limit": "^12.0.1",
97
+ "tsup": "^8.5.1",
98
+ "typescript": "^5.9.3",
99
+ "typescript-eslint": "^8.57.2",
100
100
  "vitepress": "^1.6.4",
101
- "vitepress-plugin-mermaid": "^2.0.17",
102
- "vitest": "^4.0.18",
101
+ "vitepress-mermaid-viewer": "^0.4.0",
102
+ "vitest": "^4.1.2",
103
103
  "vue": "^3.5.27",
104
104
  "vue-router": "^5.0.2"
105
105
  }