vue-router-citadel 0.1.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 ADDED
@@ -0,0 +1,139 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
6
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-02-17
9
+
10
+ ### Added
11
+
12
+ #### Core
13
+
14
+ - `createNavigationCitadel(router, options?)` — main factory function
15
+ - Global and route-scoped navigation outposts
16
+ - Support for `beforeEach`, `beforeResolve`, `afterEach` hooks
17
+ - Verdict system: `ALLOW`, `BLOCK`, redirect
18
+ - TypeScript support with full type definitions
19
+ - Peer dependencies: `vue@^3.0.0`, `vue-router@^4.0.0 || ^5.0.0`
20
+
21
+ #### API Methods
22
+
23
+ - `citadel.deployOutpost(options)` — deploy one or multiple outposts (scope defaults to `'global'`)
24
+ - `citadel.abandonOutpost(scope, name)` — abandon outposts by scope and name
25
+ - `citadel.getOutpostNames(scope)` — get deployed outpost names
26
+ - `citadel.assignOutpostToRoute(routeName, outpostNames)` — dynamically assign outposts to routes
27
+ - `citadel.revokeOutpostFromRoute(routeName, outpostNames)` — dynamically remove outposts from
28
+ routes
29
+ - `citadel.destroy()` — remove all hooks and clear registry
30
+
31
+ #### Features
32
+
33
+ - Priority-based processing order for global and route outposts
34
+ - Route outposts inheritance from parent routes
35
+ - Route outposts deduplication with warning log
36
+ - Route validation for redirect returns
37
+ - Default error handler (`console.error` + `BLOCK`)
38
+ - Timeout support (`defaultTimeout`, `timeout`, `onTimeout`)
39
+ - Lazy outposts (`lazy: true`) — load handler modules on-demand for code splitting
40
+ - Type-safe outpost names via declaration merging (`GlobalOutpostRegistry`, `RouteOutpostRegistry`)
41
+ - Optional `scope` in outpost config (defaults to `'global'`)
42
+
43
+ #### Developer Experience
44
+
45
+ - `log` option — enable/disable non-critical logging (default: `__DEV__`)
46
+ - `logger` option — custom logger with `CitadelLogger` interface (default: `createDefaultLogger()`)
47
+ - Critical events (errors, timeouts, missing routes) always logged regardless of `log` setting
48
+ - `createDefaultLogger()` — factory for default console logger with emoji prefixes
49
+ - `debug` option — logging + debugger breakpoints (default: `false`)
50
+ - Colored console output: 🔵 info, 🟡 warn, 🔴 error, 🟣 debug
51
+ - Named debug breakpoints: `navigation-start`, `before-outpost`, `patrol-stopped`, `timeout`,
52
+ `error-caught`, `devtools-init`, `devtools-inspector`
53
+ - Optimized processing — outposts sorted at deploy, direct calls from registry
54
+
55
+ #### NPM Scripts
56
+
57
+ - `lint` / `lint:fix` — ESLint check / auto-fix
58
+ - `check:lint` — ESLint check (alias)
59
+ - `check:types` — TypeScript type checking (`tsc --noEmit`)
60
+ - `check:format` — format check alias
61
+ - `check:size` — bundle size check ([size-limit](https://github.com/ai/size-limit), ≤4 KB)
62
+ - `check:all` — full validation chain (format + lint + types + tests + build + size)
63
+ - `release:check` — pre-release verification (check:all + pack --dry-run)
64
+ - `release:publish` — publish to npm with full checks
65
+ - `release:publish:beta` — publish beta version
66
+
67
+ #### Vue DevTools Integration
68
+
69
+ - `devtools` option — enable/disable Vue DevTools integration (default: `__DEV__`)
70
+ - Custom inspector with outpost tree (Global/Route groups)
71
+ - Tags showing priority and hooks count
72
+ - State panel with outpost details (name, scope, priority, hooks, timeout)
73
+ - Auto-refresh on deploy/abandon
74
+ - Vue Plugin API integration via `app.use(citadel)`
75
+ - Tree-shakeable via dynamic import — devtools code eliminated when `devtools: false`
76
+ - **Settings panel** — runtime Log level selector (`Off | Log | Log + Debug`)
77
+ - Settings persist in `localStorage` with priority: `localStorage → citadel options → defaults`
78
+
79
+ #### Debug Handler
80
+
81
+ - `debugHandler` option — custom debug handler for reliable breakpoints (default:
82
+ `createDefaultDebugHandler()`)
83
+ - Exports: `DebugHandler`, `DebugPoint`, `DebugPoints`, `createDefaultDebugHandler`
84
+ - Solves bundler issue where `debugger` statements are stripped from dependencies
85
+
86
+ #### Types
87
+
88
+ - `NavigationOutpost` — outpost configuration interface (scope optional, defaults to `'global'`)
89
+ - `NavigationOutpostHandler` — handler function type
90
+ - `LazyOutpostLoader` — lazy loader function type for code splitting
91
+ - `NavigationOutpostContext` — handler context with verdicts, to, from, router, hook
92
+ - `NavigationCitadelOptions` — citadel creation options
93
+ - `NavigationCitadelAPI` — public API interface
94
+
95
+ #### Testing
96
+
97
+ - Vitest + happy-dom test setup
98
+ - 140 tests across 9 test files
99
+ - `__tests__/navigationCitadel.test.ts` — citadel creation, hooks, destroy
100
+ - `__tests__/navigationRegistry.test.ts` — registry CRUD, priority sorting
101
+ - `__tests__/navigationOutposts.test.ts` — patrol logic, verdicts, redirects
102
+ - `__tests__/timeout.test.ts` — timeout handling, onTimeout callback
103
+ - `__tests__/integration.test.ts` — end-to-end navigation scenarios
104
+ - `__tests__/lazy.test.ts` — lazy loading, caching, retry, timeout behavior
105
+ - `__tests__/devtools-settings.test.ts` — DevTools settings, localStorage persistence
106
+ - `__tests__/debugHandler.test.ts` — debugHandler invocation, custom handlers
107
+ - VitePress testing guide (`docs/contributing/testing`) and test case reference
108
+ (`docs/contributing/test-cases`)
109
+
110
+ #### CI/CD
111
+
112
+ - GitHub Actions CI workflow (`ci.yml`) — `check:all` on push/PR to main/release
113
+ - GitHub Actions Release workflow (`release.yml`) — `check:all` + npm publish with provenance on
114
+ `v*` tags
115
+
116
+ #### Documentation
117
+
118
+ - `README.md` — concise project overview with quick start example, links to full docs
119
+ - `CONTRIBUTING.md` — concise contributor guide with link to full VitePress docs
120
+ - VitePress documentation site (`docs/`) — guides, API reference, examples, advanced patterns
121
+ - Error Handling — dedicated guide page with error flow diagram, `onError`, `onTimeout`
122
+ - Mermaid diagram legend with emoji markers (🟢🟡🔴🔵🟣) across all diagram pages
123
+ - API types documentation aligned with source code (lazy generics, async return types)
124
+ - Contributing guide — code style, naming conventions, architecture guidelines with examples
125
+ - Source docs consolidated into VitePress (`internals.md`, `testing.md` deleted, content
126
+ distributed)
127
+ - `examples/` directory removed — examples live in `docs/examples/` only
128
+
129
+ #### Infrastructure
130
+
131
+ - [commitlint](https://commitlint.js.org/) — commit message validation via husky `commit-msg` hook
132
+ - [Conventional Commits](https://www.conventionalcommits.org/) specification enforced
133
+ - Prettier `proseWrap: "preserve"` override for `docs/**/*.md` to preserve VitePress containers
134
+ - [ESLint](https://eslint.org/) 9 with flat config (`eslint.config.ts`) and `defineConfig`
135
+ - `typescript-eslint` with type-aware linting (`projectService: true`)
136
+ - `eslint-config-prettier` for conflict-free coexistence with Prettier
137
+ - 3 custom local rules: `switch-case-braces`, `jsdoc-comment-style`, `prefer-arrow-without-this`
138
+ - npm scripts: `lint`, `lint:fix`, `check:lint`; integrated into `check:all` and `lint-staged`
139
+ - [size-limit](https://github.com/ai/size-limit) — bundle size control (≤4 KB, minified + brotli)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License (MIT)
2
+
3
+ Copyright (c) 2026 - Present Dmytro Symonov "Kassaila"
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # 🏰 Vue Router Citadel
2
+
3
+ > _Place guards at the gates. Outposts along the way._
4
+
5
+ [![npm version](https://img.shields.io/npm/v/vue-router-citadel.svg)](https://www.npmjs.com/package/vue-router-citadel)
6
+ [![size](https://img.shields.io/badge/size-%E2%89%A44%20KB-brightgreen)](https://github.com/ai/size-limit)
7
+ [![license](https://img.shields.io/npm/l/vue-router-citadel.svg)](https://github.com/Kassaila/vue-router-citadel/blob/main/LICENSE)
8
+ [![docs](https://img.shields.io/badge/docs-VitePress-blue)](https://kassaila.github.io/vue-router-citadel/)
9
+
10
+ **Structured navigation defense for Vue Router 4 & 5.**
11
+
12
+ Citadel is a middleware-driven navigation control system for Vue Router that lets you build
13
+ **layered, predictable, and scalable route protection**.
14
+
15
+ Where Vue Router gives you guards at the entrance, Citadel introduces **navigation outposts** ---
16
+ internal checkpoints that control access, preload data, enforce permissions, and orchestrate complex
17
+ navigation flows.
18
+
19
+ Think of it as turning your router into a fortress.
20
+
21
+ 🏰 Citadel → ✋ Outposts (🛡 Guards) → 📍 Final point
22
+
23
+ ## ✨ Features
24
+
25
+ - 📜 **Verdict system** — return-based API — ALLOW, BLOCK, or redirect. No next() callback chains.
26
+ Clean, predictable control flow.
27
+ - ⭕ **Outpost scopes** — global for every navigation, route-scoped for specific pages
28
+ - 📋 **Priority-based execution** — deterministic outpost ordering with numeric priorities
29
+ - 🪝 **All navigation hooks** — beforeEach, beforeResolve, afterEach support per outpost
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.
33
+ - 🔒 **Type-safe** — full TypeScript support with declaration merging for outpost names. IDE
34
+ autocomplete and compile-time validation.
35
+ - 🦥 **Lazy outposts** — dynamic imports with automatic caching for code splitting
36
+ - 🛠️ **Vue DevTools** — custom inspector for real-time outpost monitoring and debug modes
37
+ - 🔍 **Logging & debug** — configurable logger, custom implementations, and debug breakpoints
38
+
39
+ ## 📦 Installation
40
+
41
+ ```bash
42
+ npm install vue-router-citadel
43
+ ```
44
+
45
+ ## 🚀 Quick Start
46
+
47
+ ```typescript
48
+ import { createRouter, createWebHistory } from 'vue-router';
49
+ import { createNavigationCitadel } from 'vue-router-citadel';
50
+ import { createApp } from 'vue';
51
+ import App from './App.vue';
52
+
53
+ const routes = [
54
+ { path: '/', name: 'home', component: () => import('./pages/Home.vue') },
55
+ { path: '/login', name: 'login', component: () => import('./pages/Login.vue') },
56
+ {
57
+ path: '/dashboard',
58
+ name: 'dashboard',
59
+ component: () => import('./pages/Dashboard.vue'),
60
+ meta: { requiresAuth: true },
61
+ },
62
+ ];
63
+
64
+ const router = createRouter({
65
+ history: createWebHistory(),
66
+ routes,
67
+ });
68
+
69
+ const citadel = createNavigationCitadel(router, {
70
+ outposts: [
71
+ {
72
+ name: 'auth',
73
+ handler: ({ verdicts, to }) => {
74
+ const isAuthenticated = Boolean(localStorage.getItem('token'));
75
+
76
+ if (to.meta.requiresAuth && !isAuthenticated) {
77
+ return { name: 'login' };
78
+ }
79
+
80
+ return verdicts.ALLOW;
81
+ },
82
+ },
83
+ ],
84
+ });
85
+
86
+ const app = createApp(App);
87
+
88
+ app.use(router);
89
+ app.use(citadel);
90
+ app.mount('#app');
91
+ ```
92
+
93
+ ## 📖 Documentation
94
+
95
+ **[View full documentation](https://kassaila.github.io/vue-router-citadel/)** — guides, API
96
+ reference, examples, and advanced patterns.
97
+
98
+ ## 🤝 Contributing
99
+
100
+ Contributions are welcome! See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for guidelines.
101
+
102
+ ## 📄 License
103
+
104
+ MIT
@@ -0,0 +1 @@
1
+ var n={BEFORE_EACH:"beforeEach",BEFORE_RESOLVE:"beforeResolve",AFTER_EACH:"afterEach"},r={ALLOW:"allow",BLOCK:"block"},s={GLOBAL:"global",ROUTE:"route"},p={NAVIGATION_START:"navigation-start",BEFORE_OUTPOST:"before-outpost",PATROL_STOPPED:"patrol-stopped",ERROR_CAUGHT:"error-caught",TIMEOUT:"timeout",DEVTOOLS_INIT:"devtools-init",DEVTOOLS_INSPECTOR:"devtools-inspector"};var g=typeof import.meta?.env<"u"?!!import.meta.env.DEV:globalThis.process?.env?.NODE_ENV!=="production",t="[\u{1F3F0} NavigationCitadel]",O=100;var d=()=>({info:(...o)=>console.info(`\u{1F535} ${t}`,...o),warn:(...o)=>console.log(`\u{1F7E1} ${t}`,...o),error:(...o)=>console.error(`\u{1F534} ${t}`,...o),debug:(...o)=>console.log(`\u{1F7E3} ${t} [DEBUG]`,...o)}),c=()=>()=>{debugger},m=(o,e,a,i)=>{e&&(a.debug(o),i?.(o));};export{n as a,r as b,s as c,p as d,g as e,t as f,O as g,d as h,c as i,m as j};
@@ -0,0 +1,2 @@
1
+ import {c,j as j$1,d as d$1,g as g$1,a,e}from'./chunk-BPEUPHLM.js';import {setupDevToolsPlugin}from'@vue/devtools-api';var L="navigation.citadel",O="Navigation Citadel",R="castle",b="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",u=L+".inspector",N="citadel-root",C="citadel-"+c.GLOBAL,D="citadel-"+c.ROUTE,p=16777215,x=4372867,y=3900150,A=9133302,P=16096779,B=15485081,d="vue-router-citadel:settings:",_="logLevel";var U=e=>e.priority??g$1,V=e=>e.hooks??[a.BEFORE_EACH],z=e=>({label:`priority: ${e}`,textColor:p,backgroundColor:x}),Y=e=>({label:e.length===1?e[0]:`${e.length} hooks`,textColor:p,backgroundColor:y}),K=()=>({label:"lazy",textColor:p,backgroundColor:B}),h=(e,o,n)=>{let t=[z(U(o)),Y(V(o))];return o.lazy&&t.push(K()),{id:`outpost-${n}-${e}`,label:e,tags:t}},X=e=>{let o=[],n=[];for(let t of e.globalSorted){let s=e.global.get(t);s&&o.push(h(t,s,c.GLOBAL));}for(let t of e.routeSorted){let s=e.route.get(t);s&&n.push(h(t,s,c.ROUTE));}return [{id:N,label:"Outposts",children:[{id:C,label:`Global (${o.length})`,tags:[{label:c.GLOBAL,textColor:p,backgroundColor:A}],children:o},{id:D,label:`Route (${n.length})`,tags:[{label:c.ROUTE,textColor:p,backgroundColor:P}],children:n}]}]},Z=(e,o)=>{let n=e.match(`^outpost-(${c.GLOBAL}|${c.ROUTE})-(.+)$`);if(!n)return null;let[,t,s]=n,i=(t===c.GLOBAL?o.global:o.route).get(s);return i?{"Outpost Details":[{key:"name",value:s},{key:"scope",value:t},{key:"priority",value:U(i)},{key:"hooks",value:V(i)},{key:"timeout",value:i.timeout??"none (uses default)"},{key:"lazy",value:i.lazy}]}:null},k=(e,o,n,t=false,s)=>{e.addInspector({id:u,label:O,icon:R}),e.on.getInspectorTree(a=>{a.inspectorId===u&&(a.rootNodes=X(o));}),e.on.getInspectorState(a=>{if(a.inspectorId!==u)return;let i=Z(a.nodeId,o);i&&(a.state=i);}),j$1(d$1.DEVTOOLS_INSPECTOR,t,n,s);},F=e=>{e.sendInspectorTree(u),e.sendInspectorState(u);};var r={OFF:"off",LOG:"log",DEBUG:"debug"};var M=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let e=localStorage.getItem(d+_);return e===null?null:e===r.OFF||e===r.LOG||e===r.DEBUG?e:null}catch{return null}},j=e=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(d+_,e);}catch{}},q=(e,o,n)=>o?r.DEBUG:e??n?r.LOG:r.OFF,T=e=>{switch(e){case r.OFF:return {log:false,debug:false};case r.LOG:return {log:true,debug:false};case r.DEBUG:return {log:true,debug:true}}},J=e=>e.debug?r.DEBUG:e.log?r.LOG:r.OFF,w=(e,o,n)=>{let t=M();if(t!==null)return T(t);let s=q(e,o,n);return T(s)},H=(e,o)=>{let n=T(o);e.log=n.log,e.debug=n.debug,j(o);},$=e=>({logLevel:{label:"Log level",type:"choice",defaultValue:J(e),options:[{label:"Off",value:r.OFF},{label:"Log",value:r.LOG},{label:"Log + Debug",value:r.DEBUG}],component:"button-group"}});var g=null,de=(e$1,o,n,t,s,a,i)=>{let f=w(s,a,e);t.log=f.log,t.debug=f.debug,setupDevToolsPlugin({id:L,label:O,logo:b,packageName:"vue-router-citadel",homepage:"https://github.com/Kassaila/vue-router-citadel",enableEarlyProxy:true,app:e$1,settings:$(t)},c=>{g=c,c.on.setPluginSettings(S=>{S.key==="logLevel"&&H(t,S.newValue);}),k(c,o,n,t.debug,i);});},_e=()=>{g&&F(g);},Te=()=>{g=null;};
2
+ export{Te as clearDevtoolsApi,_e as notifyDevtoolsRefresh,de as setupDevtools};
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var devtoolsApi=require('@vue/devtools-api');var Vt=Object.defineProperty;var D=(t,o)=>()=>(t&&(o=t(t=0)),o);var Ut=(t,o)=>{for(var n in o)Vt(t,n,{get:o[n],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",BEFORE_OUTPOST:"before-outpost",PATROL_STOPPED:"patrol-stopped",ERROR_CAUGHT:"error-caught",TIMEOUT:"timeout",DEVTOOLS_INIT:"devtools-init",DEVTOOLS_INSPECTOR:"devtools-inspector"};});var k,I,V,P=D(()=>{k=typeof undefined<"u"?!!undefined.DEV:globalThis.process?.env?.NODE_ENV!=="production",I="[\u{1F3F0} NavigationCitadel]",V=100;});exports.createDefaultLogger=void 0;exports.createDefaultDebugHandler=void 0;var y,w=D(()=>{P();exports.createDefaultLogger=()=>({info:(...t)=>console.info(`\u{1F535} ${I}`,...t),warn:(...t)=>console.log(`\u{1F7E1} ${I}`,...t),error:(...t)=>console.error(`\u{1F534} ${I}`,...t),debug:(...t)=>console.log(`\u{1F7E3} ${I} [DEBUG]`,...t)}),exports.createDefaultDebugHandler=()=>()=>{debugger},y=(t,o,n,e)=>{o&&(n.debug(t),e?.(t));};});var Z,F,dt,Ot,h,ft,vt,mt,H,Nt,Lt,bt,Rt,yt,q,Q,z=D(()=>{G();Z="navigation.citadel",F="Navigation Citadel",dt="castle",Ot="https://kassaila.github.io/vue-router-citadel/logo_devtools.svg",h=Z+".inspector",ft="citadel-root",vt="citadel-"+exports.NavigationOutpostScopes.GLOBAL,mt="citadel-"+exports.NavigationOutpostScopes.ROUTE,H=16777215,Nt=4372867,Lt=3900150,bt=9133302,Rt=16096779,yt=15485081,q="vue-router-citadel:settings:",Q="logLevel";});var St,Tt,Kt,Yt,Wt,Et,Xt,jt,_t,Ct,Dt=D(()=>{G();P();w();z();St=t=>t.priority??V,Tt=t=>t.hooks??[exports.NavigationHooks.BEFORE_EACH],Kt=t=>({label:`priority: ${t}`,textColor:H,backgroundColor:Nt}),Yt=t=>({label:t.length===1?t[0]:`${t.length} hooks`,textColor:H,backgroundColor:Lt}),Wt=()=>({label:"lazy",textColor:H,backgroundColor:yt}),Et=(t,o,n)=>{let e=[Kt(St(o)),Yt(Tt(o))];return o.lazy&&e.push(Wt()),{id:`outpost-${n}-${t}`,label:t,tags:e}},Xt=t=>{let o=[],n=[];for(let e of t.globalSorted){let r=t.global.get(e);r&&o.push(Et(e,r,exports.NavigationOutpostScopes.GLOBAL));}for(let e of t.routeSorted){let r=t.route.get(e);r&&n.push(Et(e,r,exports.NavigationOutpostScopes.ROUTE));}return [{id:ft,label:"Outposts",children:[{id:vt,label:`Global (${o.length})`,tags:[{label:exports.NavigationOutpostScopes.GLOBAL,textColor:H,backgroundColor:bt}],children:o},{id:mt,label:`Route (${n.length})`,tags:[{label:exports.NavigationOutpostScopes.ROUTE,textColor:H,backgroundColor:Rt}],children:n}]}]},jt=(t,o)=>{let n=t.match(`^outpost-(${exports.NavigationOutpostScopes.GLOBAL}|${exports.NavigationOutpostScopes.ROUTE})-(.+)$`);if(!n)return null;let[,e,r]=n,a=(e===exports.NavigationOutpostScopes.GLOBAL?o.global:o.route).get(r);return a?{"Outpost Details":[{key:"name",value:r},{key:"scope",value:e},{key:"priority",value:St(a)},{key:"hooks",value:Tt(a)},{key:"timeout",value:a.timeout??"none (uses default)"},{key:"lazy",value:a.lazy}]}:null},_t=(t,o,n,e=false,r)=>{t.addInspector({id:h,label:F,icon:dt}),t.on.getInspectorTree(l=>{l.inspectorId===h&&(l.rootNodes=Xt(o));}),t.on.getInspectorState(l=>{if(l.inspectorId!==h)return;let a=jt(l.nodeId,o);a&&(l.state=a);}),y(exports.DebugPoints.DEVTOOLS_INSPECTOR,e,n,r);},Ct=t=>{t.sendInspectorTree(h),t.sendInspectorState(h);};});var f,xt=D(()=>{f={OFF:"off",LOG:"log",DEBUG:"debug"};});var Jt,Zt,qt,tt,Qt,It,At,Gt,Pt=D(()=>{xt();z();Jt=()=>{if(typeof window>"u"||!window.localStorage)return null;try{let t=localStorage.getItem(q+Q);return t===null?null:t===f.OFF||t===f.LOG||t===f.DEBUG?t:null}catch{return null}},Zt=t=>{if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(q+Q,t);}catch{}},qt=(t,o,n)=>o?f.DEBUG:t??n?f.LOG:f.OFF,tt=t=>{switch(t){case f.OFF:return {log:false,debug:false};case f.LOG:return {log:true,debug:false};case f.DEBUG:return {log:true,debug:true}}},Qt=t=>t.debug?f.DEBUG:t.log?f.LOG:f.OFF,It=(t,o,n)=>{let e=Jt();if(e!==null)return tt(e);let r=qt(t,o,n);return tt(r)},At=(t,o)=>{let n=tt(o);t.log=n.log,t.debug=n.debug,Zt(o);},Gt=t=>({logLevel:{label:"Log level",type:"choice",defaultValue:Qt(t),options:[{label:"Off",value:f.OFF},{label:"Log",value:f.LOG},{label:"Log + Debug",value:f.DEBUG}],component:"button-group"}});});var ht={};Ut(ht,{clearDevtoolsApi:()=>no,notifyDevtoolsRefresh:()=>eo,setupDevtools:()=>oo});var M,oo,eo,no,Ht=D(()=>{P();z();Dt();Pt();M=null,oo=(t,o,n,e,r,l,a)=>{let v=It(r,l,k);e.log=v.log,e.debug=v.debug,devtoolsApi.setupDevToolsPlugin({id:Z,label:F,logo:Ot,packageName:"vue-router-citadel",homepage:"https://github.com/Kassaila/vue-router-citadel",enableEarlyProxy:true,app:t,settings:Gt(e)},p=>{M=p,p.on.setPluginSettings(g=>{g.key==="logLevel"&&At(e,g.newValue);}),_t(p,o,n,e.debug,a);});},eo=()=>{M&&Ct(M);},no=()=>{M=null;};});G();w();G();P();w();var it=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),rt=(t,o,n)=>{let e=t[o],r=`${o}Sorted`;t[r]=Array.from(e.keys()).sort((l,a)=>{let v=e.get(l)?.priority??n,p=e.get(a)?.priority??n;return v-p});},st=(t,o,n,e,r)=>{t[o].has(n.name)&&r.warn(`${o} outpost "${n.name}" already exists, replacing...`),t[o].set(n.name,n),rt(t,o,e);},ut=(t,o,n,e)=>{let r=t[o].delete(n);return r&&rt(t,o,e),r},lt=(t,o)=>Array.from(t[o].keys());G();P();w();var Ft=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},j=(t,o)=>{if(t instanceof Error)throw t;if(Object.values(exports.NavigationOutpostVerdicts).includes(t))return t;let n=`${I} Invalid outpost outcome: ${JSON.stringify(t)}.`;if(Ft(t)){if(o.resolve(t).matched.length===0)throw new Error(n+` Route not found: ${JSON.stringify(t)}`);return t}throw new Error(n+" Expected: verdicts.ALLOW, verdicts.BLOCK, or RouteLocationRaw (string path or object with name/path).")},U=(t,o)=>(t.hooks??[exports.NavigationHooks.BEFORE_EACH]).includes(o),gt=Symbol("timeout"),zt=t=>new Promise((o,n)=>{setTimeout(()=>{let e=new Error(`Timeout after ${t}ms`);e[gt]=true,n(e);},t);}),Mt=t=>t instanceof Error&&gt in t,pt=async(t,o,n,e,r)=>{let{onError:l,defaultTimeout:a,onTimeout:v}=n,{router:p}=o,g=t.timeout??a;y(exports.DebugPoints.BEFORE_OUTPOST,r.debug,e,n.debugHandler);try{let m=await t.getHandler(),d=g?await Promise.race([m(o),zt(g)]):await m(o);return j(d,p)}catch(m){if(Mt(m)){if(e.warn(`Outpost "${t.name}" timed out after ${g}ms`),y(exports.DebugPoints.TIMEOUT,r.debug,e,n.debugHandler),v){let d=await v(t.name,o);return j(d,p)}return exports.NavigationOutpostVerdicts.BLOCK}if(l&&m instanceof Error){let d=await l(m,o);return j(d,p)}return e.error(`Outpost "${t.name}" threw error:`,m),y(exports.DebugPoints.ERROR_CAUGHT,r.debug,e,n.debugHandler),exports.NavigationOutpostVerdicts.BLOCK}},J=async(t,o,n,e,r)=>{let{hook:l,to:a,from:v}=o,p=r.log||r.debug,g=a.matched.flatMap(c=>c.meta?.outposts??[]),m=new Set(g);g.length!==m.size&&e.warn(`Duplicate outposts detected on route "${String(a.name??a.path)}"`);let d=0,x=t.globalSorted.filter(c=>U(t.global.get(c),l)).length,$=t.routeSorted.filter(c=>m.has(c)&&U(t.route.get(c),l)).length,E=x+$;if(E===0)return exports.NavigationOutpostVerdicts.ALLOW;p&&e.info(`${l}: ${v.path} -> ${a.path} (${E} outposts)`),y(exports.DebugPoints.NAVIGATION_START,r.debug,e,n.debugHandler);for(let c of t.globalSorted){let T=t.global.get(c);if(!T||!U(T,l))continue;d++,p&&e.info(`Processing outpost ${d}/${E}: "${c}" [${l}]`);let S=await pt(T,o,n,e,r);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return p&&e.warn(`Patrol stopped by outpost "${c}":`,S),y(exports.DebugPoints.PATROL_STOPPED,r.debug,e,n.debugHandler),S}for(let c of t.routeSorted){if(!m.has(c))continue;let T=t.route.get(c);if(!T){e.warn(`Route outpost "${c}" not found in registry`);continue}if(!U(T,l))continue;d++,p&&e.info(`Processing outpost ${d}/${E}: "${c}" [${l}]`);let S=await pt(T,o,n,e,r);if(S!==exports.NavigationOutpostVerdicts.ALLOW)return p&&e.warn(`Patrol stopped by outpost "${c}":`,S),y(exports.DebugPoints.PATROL_STOPPED,r.debug,e,n.debugHandler),S}return exports.NavigationOutpostVerdicts.ALLOW},ct=t=>{switch(t){case exports.NavigationOutpostVerdicts.ALLOW:return true;case exports.NavigationOutpostVerdicts.BLOCK:return false;default:return t}};var ot=null,kt=false,K=async()=>{if(kt)return null;if(!ot)try{ot=await Promise.resolve().then(()=>(Ht(),ht));}catch{return kt=true,null}return ot},ao=(t,o={})=>{let{log:n,debug:e,devtools:r=k,defaultPriority:l=V}=o,a=o.logger??exports.createDefaultLogger(),v=o.debugHandler??exports.createDefaultDebugHandler(),p=r&&typeof window<"u",g=it(),m={...o,debugHandler:v},d={log:n??k,debug:e??false},x=[],$=(i,s,u)=>({verdicts:exports.NavigationOutpostVerdicts,to:i,from:s,router:t,hook:u}),E=()=>d.log||d.debug,c=i=>async(s,u)=>{let O=$(s,u,i),N=await J(g,O,m,a,d);return ct(N)};x.push(t.beforeEach(c(exports.NavigationHooks.BEFORE_EACH))),x.push(t.beforeResolve(c(exports.NavigationHooks.BEFORE_RESOLVE)));let T=t.afterEach(async(i,s)=>{let u=$(i,s,exports.NavigationHooks.AFTER_EACH);try{await J(g,u,m,a,d);}catch(O){a.error("Error in afterEach outpost:",O),y(exports.DebugPoints.ERROR_CAUGHT,d.debug,a,v);}});x.push(T);let S=i=>{let{scope:s="global",name:u,handler:O,priority:N,hooks:wt,timeout:$t,lazy:Y=false}=i,A=null,B=null,Bt=async()=>A||(Y?(B||(B=O().then(_=>{if(!_.default||typeof _.default!="function")throw new Error(`Lazy outpost "${u}" must export default handler`);return A=_.default,A}).catch(_=>{throw B=null,_ instanceof Error?_:new Error(String(_))})),B):(A=O,A));E()&&a.info(`Deploying ${s} outpost: ${u}${Y?" (lazy)":""}`),st(g,s,{name:u,getHandler:Bt,lazy:Y,priority:N,hooks:wt,timeout:$t},l,a),p&&K().then(_=>_?.notifyDevtoolsRefresh());},et=(i,s)=>{E()&&a.info(`Abandoning ${i} outpost: ${s}`);let u=ut(g,i,s,l);return p&&u&&K().then(O=>O?.notifyDevtoolsRefresh()),u},nt=i=>t.getRoutes().find(s=>s.name===i),at={install(i){p&&K().then(s=>{s&&(s.setupDevtools(i,g,a,d,n,e,v),y(exports.DebugPoints.DEVTOOLS_INIT,d.debug,a,v),E()&&a.info("DevTools initialized via app.use(citadel)"));});},deployOutpost(i){if(Array.isArray(i))for(let s of i)S(s);else S(i);},abandonOutpost(i,s){if(Array.isArray(s)){let u=true;for(let O of s)et(i,O)||(u=false);return u}else return et(i,s)},getOutpostNames(i){return lt(g,i)},assignOutpostToRoute(i,s){let u=nt(i);if(!u)return a.warn(`Route "${i}" not found`),false;let O=Array.isArray(s)?s:[s];u.meta.outposts||(u.meta.outposts=[]);for(let N of O)u.meta.outposts.includes(N)||u.meta.outposts.push(N);return E()&&a.info(`Assigned outposts [${O.join(", ")}] to route "${i}"`),true},revokeOutpostFromRoute(i,s){let u=nt(i);if(!u)return a.warn(`Route "${i}" not found`),false;let O=Array.isArray(s)?s:[s];if(!u.meta.outposts){for(let N of O)a.warn(`Outpost "${N}" not found in route "${i}"`);return true}for(let N of O)u.meta.outposts.includes(N)||a.warn(`Outpost "${N}" not found in route "${i}"`);return u.meta.outposts=u.meta.outposts.filter(N=>!O.includes(N)),E()&&a.info(`Revoked outposts [${O.join(", ")}] from route "${i}"`),true},destroy(){E()&&a.info("Destroying citadel");for(let i of x)i();x.length=0,g.global.clear(),g.route.clear(),g.globalSorted.length=0,g.routeSorted.length=0,p&&K().then(i=>i?.clearDevtoolsApi());}};return o.outposts&&at.deployOutpost(o.outposts),at};
2
+ exports.createNavigationCitadel=ao;
@@ -0,0 +1,372 @@
1
+ import { App } from 'vue';
2
+ import { RouteLocationNormalized, Router, RouteLocationRaw } from 'vue-router';
3
+
4
+ /**
5
+ * Navigation hooks supported by the citadel
6
+ */
7
+ declare const NavigationHooks: {
8
+ readonly BEFORE_EACH: "beforeEach";
9
+ readonly BEFORE_RESOLVE: "beforeResolve";
10
+ readonly AFTER_EACH: "afterEach";
11
+ };
12
+ type NavigationHook = (typeof NavigationHooks)[keyof typeof NavigationHooks];
13
+ /**
14
+ * Navigation outpost verdict constants
15
+ */
16
+ declare const NavigationOutpostVerdicts: {
17
+ readonly ALLOW: "allow";
18
+ readonly BLOCK: "block";
19
+ };
20
+ type NavigationOutpostVerdict = (typeof NavigationOutpostVerdicts)[keyof typeof NavigationOutpostVerdicts];
21
+ /**
22
+ * Navigation outpost scope constants
23
+ */
24
+ declare const NavigationOutpostScopes: {
25
+ readonly GLOBAL: "global";
26
+ readonly ROUTE: "route";
27
+ };
28
+ type NavigationOutpostScope = (typeof NavigationOutpostScopes)[keyof typeof NavigationOutpostScopes];
29
+ /**
30
+ * Outpost Registries (user-extensible via declaration merging)
31
+ */
32
+ /**
33
+ * Global outpost registry — extend this interface to enable type-safe global outpost names.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * declare module 'vue-router-citadel' {
38
+ * interface GlobalOutpostRegistry {
39
+ * 'auth': true;
40
+ * 'maintenance': true;
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ interface GlobalOutpostRegistry {
46
+ }
47
+ /**
48
+ * Route outpost registry — extend this interface to enable type-safe route outpost names.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * declare module 'vue-router-citadel' {
53
+ * interface RouteOutpostRegistry {
54
+ * 'admin-only': true;
55
+ * 'premium': true;
56
+ * }
57
+ * }
58
+ * ```
59
+ */
60
+ interface RouteOutpostRegistry {
61
+ }
62
+ /**
63
+ * Global outpost name type — inferred from GlobalOutpostRegistry or falls back to string
64
+ */
65
+ type GlobalOutpostName = keyof GlobalOutpostRegistry extends never ? string : keyof GlobalOutpostRegistry;
66
+ /**
67
+ * Route outpost name type — inferred from RouteOutpostRegistry or falls back to string
68
+ */
69
+ type RouteOutpostName = keyof RouteOutpostRegistry extends never ? string : keyof RouteOutpostRegistry;
70
+ /**
71
+ * Combined outpost name type (global | route)
72
+ */
73
+ type OutpostName = GlobalOutpostName | RouteOutpostName;
74
+ /**
75
+ * Helper type to get outpost name type by scope
76
+ */
77
+ type OutpostNameByScope<S extends NavigationOutpostScope> = S extends 'global' ? GlobalOutpostName : S extends 'route' ? RouteOutpostName : never;
78
+ /**
79
+ * Debug point names for debugger breakpoints
80
+ */
81
+ declare const DebugPoints: {
82
+ readonly NAVIGATION_START: "navigation-start";
83
+ readonly BEFORE_OUTPOST: "before-outpost";
84
+ readonly PATROL_STOPPED: "patrol-stopped";
85
+ readonly ERROR_CAUGHT: "error-caught";
86
+ readonly TIMEOUT: "timeout";
87
+ readonly DEVTOOLS_INIT: "devtools-init";
88
+ readonly DEVTOOLS_INSPECTOR: "devtools-inspector";
89
+ };
90
+ type DebugPoint = (typeof DebugPoints)[keyof typeof DebugPoints];
91
+ /**
92
+ * Debug handler function signature.
93
+ * Called at debug points when debug mode is enabled.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Custom debug handler with debugger statement
98
+ * const debugHandler: DebugHandler = (name) => {
99
+ * console.trace(`Debug point: ${name}`);
100
+ * debugger; // Will work because it's in your code, not library code
101
+ * };
102
+ * ```
103
+ */
104
+ type DebugHandler = (name: DebugPoint) => void;
105
+ /**
106
+ * Logger interface for citadel.
107
+ * Implement this interface to provide custom logging behavior.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // Use with pino for SSR
112
+ * import pino from 'pino';
113
+ * const pinoLogger = pino();
114
+ *
115
+ * const logger: CitadelLogger = {
116
+ * info: (...args) => pinoLogger.info(args),
117
+ * warn: (...args) => pinoLogger.warn(args),
118
+ * error: (...args) => pinoLogger.error(args),
119
+ * debug: (...args) => pinoLogger.debug(args),
120
+ * };
121
+ * ```
122
+ */
123
+ interface CitadelLogger {
124
+ info: (...args: unknown[]) => void;
125
+ warn: (...args: unknown[]) => void;
126
+ error: (...args: unknown[]) => void;
127
+ debug: (...args: unknown[]) => void;
128
+ }
129
+ /**
130
+ * Context passed to navigation outpost functions
131
+ */
132
+ interface NavigationOutpostContext {
133
+ /**
134
+ * Verdict constants for outpost return
135
+ */
136
+ verdicts: typeof NavigationOutpostVerdicts;
137
+ /**
138
+ * Target route
139
+ */
140
+ to: RouteLocationNormalized;
141
+ /**
142
+ * Current route
143
+ */
144
+ from: RouteLocationNormalized;
145
+ /**
146
+ * Router instance
147
+ */
148
+ router: Router;
149
+ /**
150
+ * Current hook being processed
151
+ */
152
+ hook: NavigationHook;
153
+ }
154
+ /**
155
+ * Outcome returned from navigation outpost
156
+ * - NavigationOutpostVerdicts.ALLOW: continue to next outpost
157
+ * - NavigationOutpostVerdicts.BLOCK: cancel navigation
158
+ * - RouteLocationRaw: redirect to specified route
159
+ * - Error: throw error (will be caught by onError handler)
160
+ */
161
+ type NavigationOutpostOutcome = NavigationOutpostVerdict | RouteLocationRaw | Error;
162
+ /**
163
+ * Navigation outpost handler function signature
164
+ */
165
+ type NavigationOutpostHandler = (ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
+ /**
167
+ * Lazy outpost loader — returns a module with default export
168
+ */
169
+ type LazyOutpostLoader = () => Promise<{
170
+ default: NavigationOutpostHandler;
171
+ }>;
172
+ /**
173
+ * Navigation outpost configuration.
174
+ * Generic parameter S constrains the name field based on scope.
175
+ * Generic parameter L constrains handler type based on lazy flag.
176
+ */
177
+ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> {
178
+ /**
179
+ * Outpost scope. Default: 'global'
180
+ */
181
+ scope?: S;
182
+ /**
183
+ * Unique outpost name (type-safe when registries are extended)
184
+ */
185
+ name: OutpostNameByScope<S>;
186
+ /**
187
+ * Outpost handler function.
188
+ * When lazy: true, must be a function returning Promise<{ default: NavigationOutpostHandler }>.
189
+ * When lazy: false (default), must be a NavigationOutpostHandler.
190
+ */
191
+ 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
+ /**
206
+ * Mark handler as lazy-loaded. Default: false.
207
+ * When true, handler must return Promise<{ default: NavigationOutpostHandler }>.
208
+ */
209
+ lazy?: L;
210
+ }
211
+ /**
212
+ * Options for creating navigation citadel
213
+ */
214
+ interface NavigationCitadelOptions {
215
+ /**
216
+ * Initial outposts to deploy on citadel creation
217
+ */
218
+ outposts?: NavigationOutpost<NavigationOutpostScope, boolean>[];
219
+ /**
220
+ * Enable logging for non-critical events. Default: __DEV__
221
+ * Critical events (errors, timeouts) are always logged regardless of this setting.
222
+ */
223
+ log?: boolean;
224
+ /**
225
+ * Custom logger implementation. Default: createDefaultLogger() (console with emoji prefixes)
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * createNavigationCitadel(router, {
230
+ * logger: myCustomLogger,
231
+ * });
232
+ * ```
233
+ */
234
+ logger?: CitadelLogger;
235
+ /**
236
+ * Enable debug mode (logging + debugger breakpoints at key points). Default: false
237
+ */
238
+ debug?: boolean;
239
+ /**
240
+ * Custom debug handler called at debug points when debug mode is enabled.
241
+ * Use this to add your own debugger statement (Vite won't strip it from your code).
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * createNavigationCitadel(router, {
246
+ * debug: true,
247
+ * debugHandler: (name) => {
248
+ * console.trace(`Debug: ${name}`);
249
+ * debugger; // Works because it's in your code
250
+ * },
251
+ * });
252
+ * ```
253
+ */
254
+ debugHandler?: DebugHandler;
255
+ /**
256
+ * Enable Vue DevTools integration. Default: __DEV__
257
+ * When enabled, registers a custom inspector showing deployed outposts.
258
+ */
259
+ devtools?: boolean;
260
+ /**
261
+ * Global error handler
262
+ */
263
+ onError?: (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
264
+ /**
265
+ * Default priority for outposts. Default: 100
266
+ */
267
+ defaultPriority?: number;
268
+ /**
269
+ * Default timeout for outposts in milliseconds. Default: undefined (no timeout)
270
+ */
271
+ defaultTimeout?: number;
272
+ /**
273
+ * Handler called when outpost times out
274
+ */
275
+ onTimeout?: (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
276
+ }
277
+ /**
278
+ * Public API returned by createNavigationCitadel
279
+ */
280
+ interface NavigationCitadelAPI {
281
+ /**
282
+ * Install method for Vue Plugin API
283
+ * @internal
284
+ */
285
+ install: (app: App) => void;
286
+ /**
287
+ * Deploy one or multiple outposts
288
+ */
289
+ deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | NavigationOutpost<S, L>[]) => void;
290
+ /**
291
+ * Remove one or multiple global outposts by name(s)
292
+ */
293
+ abandonOutpost(scope: 'global', name: GlobalOutpostName | GlobalOutpostName[]): boolean;
294
+ /**
295
+ * Remove one or multiple route outposts by name(s)
296
+ */
297
+ abandonOutpost(scope: 'route', name: RouteOutpostName | RouteOutpostName[]): boolean;
298
+ /**
299
+ * Get all deployed global outpost names
300
+ */
301
+ getOutpostNames(scope: 'global'): GlobalOutpostName[];
302
+ /**
303
+ * Get all deployed route outpost names
304
+ */
305
+ getOutpostNames(scope: 'route'): RouteOutpostName[];
306
+ /**
307
+ * Assign route outpost(s) to an existing route by route name
308
+ */
309
+ assignOutpostToRoute: (routeName: string, outpostNames: RouteOutpostName | RouteOutpostName[]) => boolean;
310
+ /**
311
+ * Revoke route outpost(s) from an existing route by route name
312
+ */
313
+ revokeOutpostFromRoute: (routeName: string, outpostNames: RouteOutpostName | RouteOutpostName[]) => boolean;
314
+ /**
315
+ * Destroy the citadel and remove navigation hooks
316
+ */
317
+ destroy: () => void;
318
+ }
319
+ /**
320
+ * Extended route meta with navigation outpost support
321
+ */
322
+ declare module 'vue-router' {
323
+ interface RouteMeta {
324
+ /**
325
+ * Route outposts to process for this route (type-safe when RouteOutpostRegistry is extended)
326
+ */
327
+ outposts?: RouteOutpostName[];
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Creates default console logger with prefix and emoji indicators.
333
+ *
334
+ * Log levels:
335
+ * - 🔵 info: Navigation flow, outpost deployment
336
+ * - 🟡 warn: Blocked navigation, missing routes, duplicates
337
+ * - 🔴 error: Outpost errors, timeouts
338
+ * - 🟣 debug: Debug breakpoints
339
+ */
340
+ declare const createDefaultLogger: () => CitadelLogger;
341
+ /**
342
+ * Default debug handler - triggers debugger statement.
343
+ * Note: Bundlers in consuming projects (Vite/esbuild) may strip this.
344
+ * For reliable breakpoints, provide your own debugHandler in options.
345
+ */
346
+ declare const createDefaultDebugHandler: () => DebugHandler;
347
+
348
+ /**
349
+ * Creates a navigation citadel for Vue Router
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * const citadel = createNavigationCitadel(router, {
354
+ * outposts: [
355
+ * {
356
+ * name: 'auth', // scope defaults to 'global'
357
+ * priority: 10,
358
+ * handler: async ({ verdicts, to }) => {
359
+ * if (!isAuthenticated && to.meta.requiresAuth) {
360
+ * return { name: 'login' };
361
+ * }
362
+ * return verdicts.ALLOW;
363
+ * },
364
+ * },
365
+ * ],
366
+ * onError: (error, ctx) => ({ name: 'error' }),
367
+ * });
368
+ * ```
369
+ */
370
+ declare const createNavigationCitadel: (router: Router, options?: NavigationCitadelOptions) => NavigationCitadelAPI;
371
+
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 };
@@ -0,0 +1,372 @@
1
+ import { App } from 'vue';
2
+ import { RouteLocationNormalized, Router, RouteLocationRaw } from 'vue-router';
3
+
4
+ /**
5
+ * Navigation hooks supported by the citadel
6
+ */
7
+ declare const NavigationHooks: {
8
+ readonly BEFORE_EACH: "beforeEach";
9
+ readonly BEFORE_RESOLVE: "beforeResolve";
10
+ readonly AFTER_EACH: "afterEach";
11
+ };
12
+ type NavigationHook = (typeof NavigationHooks)[keyof typeof NavigationHooks];
13
+ /**
14
+ * Navigation outpost verdict constants
15
+ */
16
+ declare const NavigationOutpostVerdicts: {
17
+ readonly ALLOW: "allow";
18
+ readonly BLOCK: "block";
19
+ };
20
+ type NavigationOutpostVerdict = (typeof NavigationOutpostVerdicts)[keyof typeof NavigationOutpostVerdicts];
21
+ /**
22
+ * Navigation outpost scope constants
23
+ */
24
+ declare const NavigationOutpostScopes: {
25
+ readonly GLOBAL: "global";
26
+ readonly ROUTE: "route";
27
+ };
28
+ type NavigationOutpostScope = (typeof NavigationOutpostScopes)[keyof typeof NavigationOutpostScopes];
29
+ /**
30
+ * Outpost Registries (user-extensible via declaration merging)
31
+ */
32
+ /**
33
+ * Global outpost registry — extend this interface to enable type-safe global outpost names.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * declare module 'vue-router-citadel' {
38
+ * interface GlobalOutpostRegistry {
39
+ * 'auth': true;
40
+ * 'maintenance': true;
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ interface GlobalOutpostRegistry {
46
+ }
47
+ /**
48
+ * Route outpost registry — extend this interface to enable type-safe route outpost names.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * declare module 'vue-router-citadel' {
53
+ * interface RouteOutpostRegistry {
54
+ * 'admin-only': true;
55
+ * 'premium': true;
56
+ * }
57
+ * }
58
+ * ```
59
+ */
60
+ interface RouteOutpostRegistry {
61
+ }
62
+ /**
63
+ * Global outpost name type — inferred from GlobalOutpostRegistry or falls back to string
64
+ */
65
+ type GlobalOutpostName = keyof GlobalOutpostRegistry extends never ? string : keyof GlobalOutpostRegistry;
66
+ /**
67
+ * Route outpost name type — inferred from RouteOutpostRegistry or falls back to string
68
+ */
69
+ type RouteOutpostName = keyof RouteOutpostRegistry extends never ? string : keyof RouteOutpostRegistry;
70
+ /**
71
+ * Combined outpost name type (global | route)
72
+ */
73
+ type OutpostName = GlobalOutpostName | RouteOutpostName;
74
+ /**
75
+ * Helper type to get outpost name type by scope
76
+ */
77
+ type OutpostNameByScope<S extends NavigationOutpostScope> = S extends 'global' ? GlobalOutpostName : S extends 'route' ? RouteOutpostName : never;
78
+ /**
79
+ * Debug point names for debugger breakpoints
80
+ */
81
+ declare const DebugPoints: {
82
+ readonly NAVIGATION_START: "navigation-start";
83
+ readonly BEFORE_OUTPOST: "before-outpost";
84
+ readonly PATROL_STOPPED: "patrol-stopped";
85
+ readonly ERROR_CAUGHT: "error-caught";
86
+ readonly TIMEOUT: "timeout";
87
+ readonly DEVTOOLS_INIT: "devtools-init";
88
+ readonly DEVTOOLS_INSPECTOR: "devtools-inspector";
89
+ };
90
+ type DebugPoint = (typeof DebugPoints)[keyof typeof DebugPoints];
91
+ /**
92
+ * Debug handler function signature.
93
+ * Called at debug points when debug mode is enabled.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Custom debug handler with debugger statement
98
+ * const debugHandler: DebugHandler = (name) => {
99
+ * console.trace(`Debug point: ${name}`);
100
+ * debugger; // Will work because it's in your code, not library code
101
+ * };
102
+ * ```
103
+ */
104
+ type DebugHandler = (name: DebugPoint) => void;
105
+ /**
106
+ * Logger interface for citadel.
107
+ * Implement this interface to provide custom logging behavior.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // Use with pino for SSR
112
+ * import pino from 'pino';
113
+ * const pinoLogger = pino();
114
+ *
115
+ * const logger: CitadelLogger = {
116
+ * info: (...args) => pinoLogger.info(args),
117
+ * warn: (...args) => pinoLogger.warn(args),
118
+ * error: (...args) => pinoLogger.error(args),
119
+ * debug: (...args) => pinoLogger.debug(args),
120
+ * };
121
+ * ```
122
+ */
123
+ interface CitadelLogger {
124
+ info: (...args: unknown[]) => void;
125
+ warn: (...args: unknown[]) => void;
126
+ error: (...args: unknown[]) => void;
127
+ debug: (...args: unknown[]) => void;
128
+ }
129
+ /**
130
+ * Context passed to navigation outpost functions
131
+ */
132
+ interface NavigationOutpostContext {
133
+ /**
134
+ * Verdict constants for outpost return
135
+ */
136
+ verdicts: typeof NavigationOutpostVerdicts;
137
+ /**
138
+ * Target route
139
+ */
140
+ to: RouteLocationNormalized;
141
+ /**
142
+ * Current route
143
+ */
144
+ from: RouteLocationNormalized;
145
+ /**
146
+ * Router instance
147
+ */
148
+ router: Router;
149
+ /**
150
+ * Current hook being processed
151
+ */
152
+ hook: NavigationHook;
153
+ }
154
+ /**
155
+ * Outcome returned from navigation outpost
156
+ * - NavigationOutpostVerdicts.ALLOW: continue to next outpost
157
+ * - NavigationOutpostVerdicts.BLOCK: cancel navigation
158
+ * - RouteLocationRaw: redirect to specified route
159
+ * - Error: throw error (will be caught by onError handler)
160
+ */
161
+ type NavigationOutpostOutcome = NavigationOutpostVerdict | RouteLocationRaw | Error;
162
+ /**
163
+ * Navigation outpost handler function signature
164
+ */
165
+ type NavigationOutpostHandler = (ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
166
+ /**
167
+ * Lazy outpost loader — returns a module with default export
168
+ */
169
+ type LazyOutpostLoader = () => Promise<{
170
+ default: NavigationOutpostHandler;
171
+ }>;
172
+ /**
173
+ * Navigation outpost configuration.
174
+ * Generic parameter S constrains the name field based on scope.
175
+ * Generic parameter L constrains handler type based on lazy flag.
176
+ */
177
+ interface NavigationOutpost<S extends NavigationOutpostScope = 'global', L extends boolean = false> {
178
+ /**
179
+ * Outpost scope. Default: 'global'
180
+ */
181
+ scope?: S;
182
+ /**
183
+ * Unique outpost name (type-safe when registries are extended)
184
+ */
185
+ name: OutpostNameByScope<S>;
186
+ /**
187
+ * Outpost handler function.
188
+ * When lazy: true, must be a function returning Promise<{ default: NavigationOutpostHandler }>.
189
+ * When lazy: false (default), must be a NavigationOutpostHandler.
190
+ */
191
+ 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
+ /**
206
+ * Mark handler as lazy-loaded. Default: false.
207
+ * When true, handler must return Promise<{ default: NavigationOutpostHandler }>.
208
+ */
209
+ lazy?: L;
210
+ }
211
+ /**
212
+ * Options for creating navigation citadel
213
+ */
214
+ interface NavigationCitadelOptions {
215
+ /**
216
+ * Initial outposts to deploy on citadel creation
217
+ */
218
+ outposts?: NavigationOutpost<NavigationOutpostScope, boolean>[];
219
+ /**
220
+ * Enable logging for non-critical events. Default: __DEV__
221
+ * Critical events (errors, timeouts) are always logged regardless of this setting.
222
+ */
223
+ log?: boolean;
224
+ /**
225
+ * Custom logger implementation. Default: createDefaultLogger() (console with emoji prefixes)
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * createNavigationCitadel(router, {
230
+ * logger: myCustomLogger,
231
+ * });
232
+ * ```
233
+ */
234
+ logger?: CitadelLogger;
235
+ /**
236
+ * Enable debug mode (logging + debugger breakpoints at key points). Default: false
237
+ */
238
+ debug?: boolean;
239
+ /**
240
+ * Custom debug handler called at debug points when debug mode is enabled.
241
+ * Use this to add your own debugger statement (Vite won't strip it from your code).
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * createNavigationCitadel(router, {
246
+ * debug: true,
247
+ * debugHandler: (name) => {
248
+ * console.trace(`Debug: ${name}`);
249
+ * debugger; // Works because it's in your code
250
+ * },
251
+ * });
252
+ * ```
253
+ */
254
+ debugHandler?: DebugHandler;
255
+ /**
256
+ * Enable Vue DevTools integration. Default: __DEV__
257
+ * When enabled, registers a custom inspector showing deployed outposts.
258
+ */
259
+ devtools?: boolean;
260
+ /**
261
+ * Global error handler
262
+ */
263
+ onError?: (error: Error, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
264
+ /**
265
+ * Default priority for outposts. Default: 100
266
+ */
267
+ defaultPriority?: number;
268
+ /**
269
+ * Default timeout for outposts in milliseconds. Default: undefined (no timeout)
270
+ */
271
+ defaultTimeout?: number;
272
+ /**
273
+ * Handler called when outpost times out
274
+ */
275
+ onTimeout?: (outpostName: string, ctx: NavigationOutpostContext) => NavigationOutpostOutcome | Promise<NavigationOutpostOutcome>;
276
+ }
277
+ /**
278
+ * Public API returned by createNavigationCitadel
279
+ */
280
+ interface NavigationCitadelAPI {
281
+ /**
282
+ * Install method for Vue Plugin API
283
+ * @internal
284
+ */
285
+ install: (app: App) => void;
286
+ /**
287
+ * Deploy one or multiple outposts
288
+ */
289
+ deployOutpost: <S extends NavigationOutpostScope = 'global', L extends boolean = false>(options: NavigationOutpost<S, L> | NavigationOutpost<S, L>[]) => void;
290
+ /**
291
+ * Remove one or multiple global outposts by name(s)
292
+ */
293
+ abandonOutpost(scope: 'global', name: GlobalOutpostName | GlobalOutpostName[]): boolean;
294
+ /**
295
+ * Remove one or multiple route outposts by name(s)
296
+ */
297
+ abandonOutpost(scope: 'route', name: RouteOutpostName | RouteOutpostName[]): boolean;
298
+ /**
299
+ * Get all deployed global outpost names
300
+ */
301
+ getOutpostNames(scope: 'global'): GlobalOutpostName[];
302
+ /**
303
+ * Get all deployed route outpost names
304
+ */
305
+ getOutpostNames(scope: 'route'): RouteOutpostName[];
306
+ /**
307
+ * Assign route outpost(s) to an existing route by route name
308
+ */
309
+ assignOutpostToRoute: (routeName: string, outpostNames: RouteOutpostName | RouteOutpostName[]) => boolean;
310
+ /**
311
+ * Revoke route outpost(s) from an existing route by route name
312
+ */
313
+ revokeOutpostFromRoute: (routeName: string, outpostNames: RouteOutpostName | RouteOutpostName[]) => boolean;
314
+ /**
315
+ * Destroy the citadel and remove navigation hooks
316
+ */
317
+ destroy: () => void;
318
+ }
319
+ /**
320
+ * Extended route meta with navigation outpost support
321
+ */
322
+ declare module 'vue-router' {
323
+ interface RouteMeta {
324
+ /**
325
+ * Route outposts to process for this route (type-safe when RouteOutpostRegistry is extended)
326
+ */
327
+ outposts?: RouteOutpostName[];
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Creates default console logger with prefix and emoji indicators.
333
+ *
334
+ * Log levels:
335
+ * - 🔵 info: Navigation flow, outpost deployment
336
+ * - 🟡 warn: Blocked navigation, missing routes, duplicates
337
+ * - 🔴 error: Outpost errors, timeouts
338
+ * - 🟣 debug: Debug breakpoints
339
+ */
340
+ declare const createDefaultLogger: () => CitadelLogger;
341
+ /**
342
+ * Default debug handler - triggers debugger statement.
343
+ * Note: Bundlers in consuming projects (Vite/esbuild) may strip this.
344
+ * For reliable breakpoints, provide your own debugHandler in options.
345
+ */
346
+ declare const createDefaultDebugHandler: () => DebugHandler;
347
+
348
+ /**
349
+ * Creates a navigation citadel for Vue Router
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * const citadel = createNavigationCitadel(router, {
354
+ * outposts: [
355
+ * {
356
+ * name: 'auth', // scope defaults to 'global'
357
+ * priority: 10,
358
+ * handler: async ({ verdicts, to }) => {
359
+ * if (!isAuthenticated && to.meta.requiresAuth) {
360
+ * return { name: 'login' };
361
+ * }
362
+ * return verdicts.ALLOW;
363
+ * },
364
+ * },
365
+ * ],
366
+ * onError: (error, ctx) => ({ name: 'error' }),
367
+ * });
368
+ * ```
369
+ */
370
+ declare const createNavigationCitadel: (router: Router, options?: NavigationCitadelOptions) => NavigationCitadelAPI;
371
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import {g,e,h,i,a,j as j$1,d,b,f}from'./chunk-BPEUPHLM.js';export{d as DebugPoints,a as NavigationHooks,c as NavigationOutpostScopes,b as NavigationOutpostVerdicts,i as createDefaultDebugHandler,h as createDefaultLogger}from'./chunk-BPEUPHLM.js';var U=()=>({global:new Map,route:new Map,globalSorted:[],routeSorted:[]}),j=(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});},K=(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),j(t,o,n);},W=(t,o,a,n)=>{let u=t[o].delete(a);return u&&j(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).")},H=(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.BEFORE_OUTPOST,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.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_CAUGHT,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,w=t.globalSorted.filter(l=>H(t.global.get(l),f)).length,$=t.routeSorted.filter(l=>O.has(l)&&H(t.route.get(l),f)).length,R=w+$;if(R===0)return b.ALLOW;c&&n.info(`${f}: ${N.path} -> ${s.path} (${R} 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||!H(L,f))continue;d$1++,c&&n.info(`Processing outpost ${d$1}/${R}: "${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.PATROL_STOPPED,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(!H(L,f))continue;d$1++,c&&n.info(`Processing outpost ${d$1}/${R}: "${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.PATROL_STOPPED,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,P=async()=>{if(Q)return null;if(!z)try{z=await import('./devtools-EZI2AQWB.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},w=[],$=(e,i,r)=>({verdicts:b,to:e,from:i,router:t,hook:r}),R=()=>d$1.log||d$1.debug,l=e=>async(i,r)=>{let p=$(i,r,e),v=await I(g$1,p,O,s,d$1);return q(v)};w.push(t.beforeEach(l(a.BEFORE_EACH))),w.push(t.beforeResolve(l(a.BEFORE_RESOLVE)));let L=t.afterEach(async(e,i)=>{let r=$(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_CAUGHT,d$1.debug,s,N);}});w.push(L);let b$1=e=>{let{scope:i="global",name:r,handler:p,priority:v,hooks:Z,timeout:tt,lazy:T=false}=e,C=null,S=null,ot=async()=>C||(T?(S||(S=p().then(E=>{if(!E.default||typeof E.default!="function")throw new Error(`Lazy outpost "${r}" must export default handler`);return C=E.default,C}).catch(E=>{throw S=null,E instanceof Error?E:new Error(String(E))})),S):(C=p,C));R()&&s.info(`Deploying ${i} outpost: ${r}${T?" (lazy)":""}`),K(g$1,i,{name:r,getHandler:ot,lazy:T,priority:v,hooks:Z,timeout:tt},f,s),c&&P().then(E=>E?.notifyDevtoolsRefresh());},B=(e,i)=>{R()&&s.info(`Abandoning ${e} outpost: ${i}`);let r=W(g$1,e,i,f);return c&&r&&P().then(p=>p?.notifyDevtoolsRefresh()),r},G=e=>t.getRoutes().find(i=>i.name===e),M={install(e){c&&P().then(i=>{i&&(i.setupDevtools(e,g$1,s,d$1,a$1,n,N),j$1(d.DEVTOOLS_INIT,d$1.debug,s,N),R()&&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=G(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 R()&&s.info(`Assigned outposts [${p.join(", ")}] to route "${e}"`),true},revokeOutpostFromRoute(e,i){let r=G(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)),R()&&s.info(`Revoked outposts [${p.join(", ")}] from route "${e}"`),true},destroy(){R()&&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,c&&P().then(e=>e?.clearDevtoolsApi());}};return o.outposts&&M.deployOutpost(o.outposts),M};export{rt as createNavigationCitadel};
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "vue-router-citadel",
3
+ "version": "0.1.0",
4
+ "description": "Structured navigation defense for Vue Router",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "build:dev": "NODE_ENV=development tsup",
29
+ "test": "vitest",
30
+ "test:run": "vitest run",
31
+ "test:coverage": "vitest run --coverage",
32
+ "lint": "eslint",
33
+ "lint:fix": "eslint --fix",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "check:lint": "eslint",
37
+ "check:types": "tsc --noEmit",
38
+ "check:format": "npm run format:check",
39
+ "check:size": "size-limit",
40
+ "check:all": "npm run check:format && npm run check:lint && npm run check:types && npm run test:run && npm run build && npm run check:size",
41
+ "release:check": "npm run check:all && npm pack --dry-run",
42
+ "release:publish": "npm run release:check && npm publish",
43
+ "release:publish:beta": "npm run release:check && npm publish --tag beta",
44
+ "prepare": "husky",
45
+ "prepublishOnly": "npm run check:types && npm run test:run",
46
+ "docs:dev": "vitepress dev docs",
47
+ "docs:build": "vitepress build docs",
48
+ "docs:preview": "vitepress preview docs"
49
+ },
50
+ "lint-staged": {
51
+ "*.{ts,js}": "eslint --fix",
52
+ "*.{ts,js,json,md}": "prettier --write"
53
+ },
54
+ "keywords": [
55
+ "vue",
56
+ "vue-router",
57
+ "router",
58
+ "guard",
59
+ "middleware",
60
+ "navigation",
61
+ "typescript"
62
+ ],
63
+ "author": "Kassaila",
64
+ "license": "MIT",
65
+ "engines": {
66
+ "node": ">=22.0.0"
67
+ },
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/Kassaila/vue-router-citadel.git"
71
+ },
72
+ "bugs": {
73
+ "url": "https://github.com/Kassaila/vue-router-citadel/issues"
74
+ },
75
+ "homepage": "https://github.com/Kassaila/vue-router-citadel#readme",
76
+ "peerDependencies": {
77
+ "vue": "^3.0.0",
78
+ "vue-router": "^4.0.0 || ^5.0.0"
79
+ },
80
+ "dependencies": {
81
+ "@vue/devtools-api": "^8.0.5"
82
+ },
83
+ "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",
91
+ "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",
100
+ "vitepress": "^1.6.4",
101
+ "vitepress-plugin-mermaid": "^2.0.17",
102
+ "vitest": "^4.0.18",
103
+ "vue": "^3.5.27",
104
+ "vue-router": "^5.0.2"
105
+ }
106
+ }