@route-forge/core 3.0.0 → 3.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.
@@ -1,2 +1,2 @@
1
- var RouteForge=(function(exports){'use strict';var Y="route-forge:";function Be(r){return r==="memory"||typeof globalThis>"u"?null:r==="sessionStorage"?globalThis.sessionStorage??null:r==="localStorage"?globalThis.localStorage??null:null}var N=class{constructor(e){this.memory=new Map;this.onStorageEvent=e=>{e.key&&e.key.startsWith(Y)&&this.memory.delete(e.key.slice(Y.length));};this.storage=e.storage,this.fallbackTtl=e.ttl,this.backend=Be(e.storage),this.backend&&typeof globalThis.addEventListener=="function"&&globalThis.addEventListener("storage",this.onStorageEvent);}key(e){return `${Y}${e}`}get(e){if(this.storage==="memory"||!this.backend)return this.getFromMemory(e);let t=this.getFromMemory(e);if(t)return t;let n=this.backend.getItem(this.key(e));if(n)try{let o=JSON.parse(n);if(this.isExpired(o)){this.del(e);return}return this.memory.set(e,o),o}catch{return}}getFromMemory(e){let t=this.memory.get(e);if(t){if(this.isExpired(t)){this.memory.delete(e);return}return t}}set(e,t){let n,o;t===null?(n=0,o=false):t===void 0?(n=this.fallbackTtl,o=true):t===0?(n=0,o=true):(n=Math.min(t,this.fallbackTtl),o=true);let s={level:e.level,routes:e.routes,ttl:n,cachedAt:Date.now()};if(this.memory.set(e.level,s),!(!o||this.storage==="memory"||!this.backend))try{this.backend.setItem(this.key(e.level),JSON.stringify(s));}catch{}}del(e){if(this.memory.delete(e),this.backend)try{this.backend.removeItem(this.key(e));}catch{}}clear(){if(this.memory.clear(),this.backend)try{let e=[];for(let t=0;t<this.backend.length;t++){let n=this.backend.key(t);n&&n.startsWith(Y)&&e.push(n);}for(let t of e)this.backend.removeItem(t);}catch{}}isExpired(e){return e.ttl===null||e.ttl===0?false:Date.now()-e.cachedAt>e.ttl*1e3}};var C=class extends Error{constructor(e,t){super(e),this.name=this.constructor.name,this.code=t.code,t.route!==void 0&&(this.route=t.route),t.level!==void 0&&(this.level=t.level),t.context!==void 0&&(this.context=t.context),t.cause!==void 0&&(this.cause=t.cause);}};function xe(r){if(!r||r.length===0)return "";let e=5,t=r.slice(0,e).join(", "),n=r.length>e?` (+${r.length-e} more)`:"";return ` Available: ${t}${n}`}var E=class extends C{constructor(e,t,n){super(`Route "${e}" not found${t?` in level "${t}"`:""}.${xe(n)}`,{code:"RF_FE_001",route:e,level:t,context:n&&n.length>0?{candidates:n}:void 0});}},B=class extends C{constructor(e,t){super(`Level "${e}" not declared in options.levels.${xe(t)}`,{code:"RF_FE_002",level:e,context:t&&t.length>0?{candidates:t}:void 0});}},H=class extends C{constructor(e,t,n){super(`Missing path parameter(s) ${t.join(", ")} for route "${e}"${n?` (${n})`:""}`,{code:"RF_FE_003",route:e,context:{missingParams:t,...n!==void 0?{uri:n}:{}}});}},W=class extends C{constructor(e,t,n){super(`Path parameter "${t}" must be a primitive value (string, number, boolean), got ${typeof n}`,{code:"RF_FE_003",route:e,context:{param:t,value:n}});}},j=class extends C{constructor(e){super(`Adapter "${e}" not available; install axios or use 'builtin'`,{code:"RF_FE_005",context:{adapter:e}});}},G=class extends C{constructor(e){super("Request interceptor must return a RequestConfig object",{code:"RF_FE_006",route:e});}},F=class extends C{constructor(e,t,n,o){super(e,{code:"RF_FE_007",route:t,level:n,cause:o});}},T=class extends C{constructor(e,t){super(e,{code:"RF_FE_008",route:t.route,level:t.level,context:{status:t.status,url:t.url,method:t.method},cause:t.cause}),t.response!==void 0&&(this.response=t.response);}},$=class extends C{constructor(e,t,n){super(`Request aborted${e?` for route "${e}"`:""}`,{code:"RF_FE_009",route:e,level:t,cause:n});}},z=class extends C{constructor(){super("Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first, or await ready() before calling route()/hasRoute().",{code:"RF_FE_010"});}};var _=class{constructor(){this.handlers=[];this.nextId=0;}use(e,t){let n=this.nextId++;return this.handlers.push({id:n,onFulfilled:e,onRejected:t}),n}eject(e){let t=this.handlers.findIndex(n=>n.id===e);t>=0&&this.handlers.splice(t,1);}clear(){this.handlers=[];}forEach(e){for(let t of this.handlers)e(t);}get size(){return this.handlers.length}};async function Z(r,e){let t=[];r.forEach(o=>t.push(o)),t.reverse();let n=Promise.resolve(e);for(let o of t){let s=o.onFulfilled,i=o.onRejected;n=n.then(async c=>{if(!s)return c;let f=await s(c);if(f===null||typeof f!="object")throw new G;return f},c=>i?i(c):Promise.reject(c));}return n}async function ee(r,e){let t=[];r.forEach(o=>t.push(o));let n=e;for(let o of t){let s=o.onFulfilled,i=o.onRejected;n=n.then(c=>s?s(c):c,c=>i?i(c):Promise.reject(c));}return n}function te(){return new _}function ue(r){if(r==null)return {};let e,t;if(typeof r=="function")e=r;else if(Array.isArray(r))e=r[0],t=r[1];else if(typeof r=="object"){let o=r;e=o.resolve,t=o.reject;}else throw new TypeError(`Interceptor declaration must be a function, a [resolve, reject] tuple, or a { resolve, reject } object; received ${typeof r}.`);let n={};if(e!=null){if(typeof e!="function")throw new TypeError(`Interceptor "resolve" (onFulfilled) must be a function; received ${typeof e}.`);n.onFulfilled=e;}if(t!=null){if(typeof t!="function")throw new TypeError(`Interceptor "reject" (onRejected) must be a function; received ${typeof t}.`);n.onRejected=t;}return n}function ce(r,e){if(e?.aborted)return true;let t=r;return t?t.name==="AbortError"||t.code==="ERR_CANCELED":false}function He(...r){let e=r.filter(n=>!!n);if(e.length===0)return;if(e.length===1)return e[0];if(typeof AbortSignal<"u"&&typeof AbortSignal.any=="function")return AbortSignal.any(e);let t=new AbortController;for(let n of e){if(n.aborted)return t.abort(n.reason),t.signal;n.addEventListener("abort",()=>t.abort(n.reason),{once:true});}return t.signal}function We(r){return r===null?false:typeof FormData<"u"&&r instanceof FormData||typeof URLSearchParams<"u"&&r instanceof URLSearchParams||typeof Blob<"u"&&r instanceof Blob||typeof ArrayBuffer<"u"&&(r instanceof ArrayBuffer||ArrayBuffer.isView(r))||typeof ReadableStream<"u"&&r instanceof ReadableStream}async function Ee(r){let e=He(r.signal,r.timeout&&r.timeout>0?AbortSignal.timeout(r.timeout):void 0),t=r.url;if(r.paramsSerializer&&r.params){let a=r.paramsSerializer(r.params);a&&(t=t.includes("?")?`${t}&${a}`:`${t}?${a}`);}let n=new Headers(r.headers),o={method:r.method,headers:n};e&&(o.signal=e),r.body!==void 0&&!["GET","HEAD"].includes(r.method.toUpperCase())&&(typeof r.body=="string"||We(r.body)?o.body=r.body:(o.body=JSON.stringify(r.body),n.has("Content-Type")||n.set("Content-Type","application/json")));let s;try{s=await fetch(t,o);}catch(a){throw ce(a)?a:new F(a instanceof Error?a.message:String(a),r.route,r.level,a)}let i=await s.text(),c=i;if((s.headers.get("content-type")??"").includes("application/json"))try{c=JSON.parse(i);}catch{}let d={route:r.route,level:r.level,method:r.method,url:t,status:s.status,headers:s.headers,data:c,config:r};if(s.status>=200&&s.status<300)return d;throw new T(`HTTP ${s.status} for route "${r.route}" (${r.method} ${t})`,{route:r.route,level:r.level,status:s.status,url:t,method:r.method,response:d})}function le(r){let e=r?.request??te(),t=r?.response??te(),n=Ee;async function o(a){let p=await Z(e,a),g=Promise.resolve(p).then(n);return ee(t,g)}return {request:o,requestRaw:n,interceptors:{request:e,response:t},runsInterceptors:true,get:(a,p)=>o({...p,url:a,method:"GET"}),post:(a,p)=>o({...p,url:a,method:"POST"}),put:(a,p)=>o({...p,url:a,method:"PUT"}),patch:(a,p)=>o({...p,url:a,method:"PATCH"}),delete:(a,p)=>o({...p,url:a,method:"DELETE"})}}function Fe(r){if(!r)return new Headers;let e=typeof r.toJSON=="function"?r.toJSON():r;try{return new Headers(e)}catch{return new Headers}}async function de(){let r,e="axios";try{let n=await import(e);r=n.default??n;}catch{typeof globalThis<"u"&&globalThis.axios&&(r=globalThis.axios);}if(!r||typeof r.request!="function")return null;async function t(n){let o={...n.headers};try{let s=await r.request({url:n.url,method:n.method,headers:o,data:n.body,timeout:n.timeout,signal:n.signal});return {route:n.route,level:n.level,method:n.method,url:n.url,status:s.status,headers:Fe(s.headers),data:s.data,config:n}}catch(s){if(s&&s.response){let i=s.response;return {route:n.route,level:n.level,method:n.method,url:n.url,status:i.status,headers:Fe(i.headers),data:i.data,config:n}}throw s}}return {request:t,interceptors:void 0}}async function pe(r){let{adapter:e}=r;if(e&&typeof e=="object")return {request:n=>e.request(n),interceptors:e.interceptors};if(e==="builtin")return le(r.forgeInterceptors);if(e==="axios"){let n=await de();if(!n)throw new j("axios");return n}let t=await de().catch(()=>null);return t||le(r.forgeInterceptors)}var J=class{constructor(){this.count=0;this.subscribers=new Set;}start(){this.count++,this.notify();}stop(){this.count=Math.max(0,this.count-1),this.notify();}isLoading(){return this.count>0}getCount(){return this.count}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e);}}notify(){let e={loading:this.count>0,count:this.count};for(let t of this.subscribers)try{t(e);}catch{}}};function Te(r,e){let t=e.baseURL.endsWith("/")?e.baseURL.slice(0,-1):e.baseURL,n=e.endpoint.startsWith("/")?e.endpoint:`/${e.endpoint}`;return `${t}${n}/${encodeURIComponent(r)}`}function re(r,e,t){let n=r.parameter_defaults??{},o=[],s={};for(let d of r.parameters){let a=e[d];a==null&&d in n&&(a=n[d]),a==null?r.uri.includes(`{${d}?}`)||o.push(d):s[d]=a;}if(o.length>0)throw new H(r.name,o,r.uri);let i=r.uri.replace(/\{([^{}]+)\}/g,(d,a)=>{let p=a.endsWith("?"),g=p?a.slice(0,-1):a;if(s[g]!==void 0){let h=s[g];if(typeof h=="object")throw new W(r.name,g,h);return encodeURIComponent(String(h))}return p?"":d});if(i=i.replace(/\/+/g,"/").replace(/\/$/,""),/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(t.urlPrefix)){let d=t.urlPrefix.endsWith("/")?t.urlPrefix.slice(0,-1):t.urlPrefix;return i.startsWith("/")?`${d}${i}`:`${d}/${i}`}let c=t.baseURL.endsWith("/")?t.baseURL.slice(0,-1):t.baseURL,f=t.urlPrefix;return i.startsWith("/")?`${c}${f}${i}`:`${c}${f}/${i}`}function qe(r){return (r.methods.find(t=>t.toUpperCase()!=="HEAD")??r.methods[0]??"GET").toUpperCase()}function De(r,e){if(!e)return r;let t=new URLSearchParams;for(let[o,s]of Object.entries(e))s!=null&&t.append(o,String(s));let n=t.toString();return n?r.includes("?")?`${r}&${n}`:`${r}?${n}`:r}function Ie(r){let{params:e,query:t,body:n,headers:o,timeout:s,...i}=r,c=e?{...e}:{};for(let[p,g]of Object.entries(i))p in c||(c[p]=g);let f,d,a;return t!==void 0&&(typeof t=="object"&&t!==null?f=t:"query"in c||(c.query=t)),n!==void 0&&(typeof n!="string"&&typeof n!="number"?d=n:"body"in c||(c.body=n)),o!==void 0&&(typeof o=="object"&&o!==null?a=o:"headers"in c||(c.headers=o)),{pathParams:c,query:f,body:d,headers:a,timeout:s}}var fe="/_forge/routes";async function Ae(r,e,t){let{explicitLevels:n,explicitEndpoint:o,warnings:s}=r,i=o??fe,c=e.endsWith("/")?e.slice(0,-1):e,f=i.startsWith("/")?i:`/${i}`,d=`${c}${f}`;try{return await t("__forge__.summary",d)}catch(a){if(n&&n.length>0)return s&&console.warn(`[route-forge] summary endpoint unreachable: ${a.message}; using explicit levels`),null;throw new F(`Failed to fetch route summary from "${d}": ${a?.message??String(a)}; check the backend manifest endpoint or options.endpoint`,void 0,void 0,a)}}function Ge(r){return typeof r=="number"&&r<0?null:r}function ge(r,e,t){let{explicitLevels:n,explicitEager:o,explicitEndpoint:s,warnings:i}=t,c=r.schemeVersion??1;c>1&&i&&console.warn(`[route-forge] backend schemeVersion=${c} > client supported 1; some features may be unavailable`),r.config.endpoint_prefix&&r.config.endpoint_prefix!==s&&(s&&i&&console.warn(`[route-forge] backend endpoint_prefix "${r.config.endpoint_prefix}" overrides frontend endpoint "${s}"`),e.endpoint=r.config.endpoint_prefix),r.config.url_prefix&&(e.urlPrefix=r.config.url_prefix.endsWith("/")?r.config.url_prefix.slice(0,-1):r.config.url_prefix),e.cacheTtl=Ge(r.config.cache_ttl);let f=Object.keys(r.levels),d={};for(let p of f){let g=r.levels[p]?.route;g&&typeof g.uri=="string"&&(d[p]={uri:g.uri,methods:g.methods??["GET","HEAD"]});}if(e.levelRoutes=d,n&&n.length>0){let p=n.filter(h=>f.includes(h)),g=n.filter(h=>!f.includes(h));g.length>0&&i&&console.warn(`[route-forge] levels not in backend summary and dropped: ${g.join(", ")}`),e.levels=p;}else e.levels=f.slice();let a=f.filter(p=>r.levels[p]?.load==="eager");if(!o)e.eager=a;else {let p=new Set([...a,...o]);e.eager=[...p];}}var ze="__ROUTE_FORGE__",V;function Se(){if(V!==void 0)return V;if(typeof window>"u")return V=null,null;let r;try{r=window[ze];}catch{r=void 0;}return V=r!==null&&typeof r=="object"?r:null,V}var ne=class{constructor(e){this.inflight=new Map;this.invalidationGens=new Map;this.cache=e.cache,this.state=e.state,this.baseURL=e.baseURL,this.fetchMeta=e.fetchMeta,this.autoDiscoveryPromise=e.autoDiscoveryPromise,this.getAutoDiscoveryError=e.getAutoDiscoveryError;}assertLevelDeclared(e){if(!this.state.levels.includes(e))throw new B(e,this.state.levels)}async fetchLevel(e){let t=this.state.levelRoutes[e]?.uri,n=t?this.joinBaseAndPath(this.baseURL,t):Te(e,{baseURL:this.baseURL,endpoint:this.state.endpoint});return await this.fetchMeta(`route-forge.${e}`,n,e)}joinBaseAndPath(e,t){let n=e.endsWith("/")?e.slice(0,-1):e,o=t.startsWith("/")?t:`/${t}`;return `${n}${o}`}async loadOne(e){let t=this.getAutoDiscoveryError();if(t)throw t;if(this.assertLevelDeclared(e),!(this.state.cacheTtl===null)&&this.cache.get(e))return;let o=this.inflight.get(e);if(o)return o;let s=this.invalidationGens.get(e)??0,i=(async()=>{try{let c=await this.fetchLevel(e);(this.invalidationGens.get(e)??0)===s&&this.cache.set(c,this.state.cacheTtl);}finally{this.inflight.delete(e);}})();return this.inflight.set(e,i),i}async load(e){await this.autoDiscoveryPromise;let t=Array.isArray(e)?e:[e];await Promise.all(t.map(n=>this.loadOne(n)));}invalidate(e){if(e===void 0){this.cache.clear(),this.inflight.clear();for(let t of this.state.levels)this.invalidationGens.set(t,(this.invalidationGens.get(t)??0)+1);}else if(Array.isArray(e))for(let t of e)this.cache.del(t),this.inflight.delete(t),this.invalidationGens.set(t,(this.invalidationGens.get(t)??0)+1);else this.cache.del(e),this.inflight.delete(e),this.invalidationGens.set(e,(this.invalidationGens.get(e)??0)+1);}isLoaded(e){return e?this.cache.get(e)!==void 0:this.state.levels.length>0&&this.state.levels.every(t=>this.cache.get(t)!==void 0)}findRouteMeta(e,t){let o=this.cache.get(e)?.routes[t];if(o)return {...o,level:e}}getRoutes(e){if(e!==void 0){this.assertLevelDeclared(e);let o=this.cache.get(e)?.routes??{},s={};for(let[i,c]of Object.entries(o))s[i]=JSON.parse(JSON.stringify(c));return s}let t={};for(let n of this.state.levels){let o=this.cache.get(n);if(o){let s={};for(let[i,c]of Object.entries(o.routes))s[i]=JSON.parse(JSON.stringify(c));t[n]=s;}}return t}};function Le(r){let{ensureAdapter:e,requestInterceptors:t,responseInterceptors:n,load:o,findRouteMeta:s,getRouteNames:i,baseURL:c,state:f,timeout:d,loadingTracker:a,autoDiscoveryPromise:p}=r;async function g(h,l,m){let{pathParams:w,query:y,body:k,headers:v,timeout:P}=Ie(l);if(m?.aborted)throw new $(h.name,h.level,m.reason);let q=qe(h),x=De(re(h,w,{baseURL:c,urlPrefix:f.urlPrefix}),y),D={route:h.name,level:h.level??"",method:q,url:x,headers:{Accept:"application/json",...v??{}},body:k,params:w,timeout:P??d,signal:m,meta:h},I=await e(),A=I.runsInterceptors?D:await Z(t,D);if(A.signal?.aborted)throw new $(h.name,h.level,A.signal.reason);a.start();try{let Q=I.request(A).then(R=>{if(R.status<200||R.status>=300)throw new T(`HTTP ${R.status} for route "${R.route}" (${R.method} ${R.url})`,{route:R.route,level:R.level,status:R.status,url:R.url,method:R.method,response:R});return R},R=>{throw R instanceof C?R:ce(R,m)?new $(h.name,h.level,R):new F(R instanceof Error?R.message:String(R),h.name,h.level,R)});return I.runsInterceptors?await Q:await ee(n,Q)}finally{a.stop();}}return function(l,m,w={}){let y,k=false,v,q=(async()=>{y=new AbortController,k&&y.abort(v),await p,await o(l);let x=s(l,m);if(!x)throw new E(m,l,i(l));return g(x,w,y.signal)})();return q.abort=()=>{y?y.abort():k=true;},q}}function Me(r,e){let t=r;for(;t.endsWith(e);)t=t.slice(0,-e.length);return t}async function he(r,e,t,n,o="."){let s=Me(t,o);if(!n)return s;let i=`${s}${o}${n}`;if(!n.startsWith(`${s}${o}`)||(await r.load(e),r.hasRoute(e,i)))return i;if(r.hasRoute(e,n))return n;throw new E(i,e,r.getRouteNames?.(e))}function oe(r,e,t,n,o="."){let s=Me(t,o);if(!n)return s;let i=`${s}${o}${n}`;if(!n.startsWith(`${s}${o}`)||r.hasRoute(e,i))return i;if(r.hasRoute(e,n))return n;throw new E(i,e,r.getRouteNames?.(e))}function je(r,e){for(let t of Object.keys(e)){let n=e[t];Object.defineProperty(r,t,{value:n!==null&&typeof n=="object"?Object.freeze(n):n,writable:false,enumerable:false,configurable:false});}return r}function $e(r){let{load:e,api:t,route:n,hasRoute:o,getRoutes:s,invalidate:i,isLoaded:c,loadingTracker:f}=r,d={load:e,hasRoute:o,getRouteNames:l=>Object.keys(s(l))};function a(l,m){let w=e(l);w.catch(()=>{});let y=m?(v,P)=>he(d,l,m,v).then(q=>t(l,q,P)):(v,P)=>t(l,v,P),k=y;return je(k,{level:l,...m!==void 0?{prefix:m}:{},api:y,route:m?(v,P)=>n(l,oe(d,l,m,v),P):(v,P)=>n(l,v,P),url:m?(v,P)=>n(l,oe(d,l,m,v),P):(v,P)=>n(l,v,P),hasRoute:v=>o(l,v),getRoutes:()=>s(l),load:()=>e(l),invalidate:()=>i(l),isLoaded:()=>c(l),isLoading:()=>f.isLoading(),onLoadingChange:v=>f.subscribe(v)}),Object.defineProperty(k,"levelLoaded",{value:w,writable:false,enumerable:true,configurable:true}),k}function p(l,m,w,y){let k=m.then(()=>l);return w?k.then(w,y).then(()=>l):k}function g(l,m,w){Object.defineProperty(l,"onLevelLoaded",{value:(y,k)=>p(l,m,y,k),writable:false,enumerable:false,configurable:false}),Object.defineProperty(l,"useRoutePrefix",{value:y=>h(w,y),writable:false,enumerable:false,configurable:false});}function h(l,m){let w=a(l,m),y=w.levelLoaded;return g(w,y,l),w}return h}var Je=3e4,Ve=3600;function Qe(r={}){let e=Se()??r.summary??null,{adapter:t="auto",timeout:n=Je,baseURL:o="",interceptors:s,cache:i={}}=r,c=r.warnings??true,f=new J,d=r.levels,a=r.eager,p=r.endpoint,g={levels:d??[],eager:a??[],endpoint:p??e?.config?.endpoint_prefix??fe,urlPrefix:"",cacheTtl:void 0,levelRoutes:{}},h={explicitLevels:d,explicitEager:a,explicitEndpoint:p,warnings:c},l=false,m,w,y=new Promise((u,b)=>{m=u,w=b;});y.catch(()=>{});let k=false;y.then(()=>{k=true;},()=>{});let v=i.ttl??Ve,P=i.storage??"memory",q=new N({storage:P,ttl:v}),x=new _,D=new _,I=ue(s?.request);(I.onFulfilled||I.onRejected)&&x.use(I.onFulfilled,I.onRejected);let A=ue(s?.response);(A.onFulfilled||A.onRejected)&&D.use(A.onFulfilled,A.onRejected);let Q=pe({adapter:t,forgeInterceptors:{request:x,response:D}}),se=false,R=null;async function me(){return se||(R=await Q.catch(u=>{if(u instanceof j)throw u;return c&&console.warn(`[route-forge] adapter initialization failed (${u?.message??String(u)}); falling back to builtin`),pe({adapter:"builtin",forgeInterceptors:{request:x,response:D}})}),se=true),R}function Re(){if(!l&&!d?.length)throw new z}async function ye(u,b,L=""){let U=await me(),Ne={route:u,level:L,method:"GET",url:b,headers:{Accept:"application/json"},params:{},timeout:n,meta:{name:u,uri:b,methods:["GET"],parameters:[],level:L}},M=await(U.requestRaw??U.request)(Ne);if(!M||M.status<200||M.status>=300)throw new T(`Failed to fetch "${u}" (${b}): HTTP ${M?.status}`,{level:L,status:M?.status,url:b,method:"GET",response:M??void 0});return M.data}let O;e?(ge(e,g,h),l=true,O=Promise.resolve()):O=Ae(h,o,ye).then(u=>{u!==null&&ge(u,g,h);});let ve=null;O.catch(u=>{ve=u;});let S=new ne({cache:q,state:g,baseURL:o,fetchMeta:ye,autoDiscoveryPromise:O,getAutoDiscoveryError:()=>ve}),K=u=>S.load(u),ie=(u,b)=>S.findRouteMeta(u,b),be=u=>S.invalidate(u),we=u=>S.isLoaded(u);function ke(u){return u===void 0?S.getRoutes():S.getRoutes(u)}function _e(){return [...g.levels]}function ae(u,b,L){Re();let U=ie(u,b);if(!U)throw new E(b,u,Object.keys(S.getRoutes(u)));return re(U,L??{},{baseURL:o,urlPrefix:g.urlPrefix})}let Pe=Le({ensureAdapter:me,requestInterceptors:x,responseInterceptors:D,load:K,findRouteMeta:ie,getRouteNames:u=>Object.keys(S.getRoutes(u)),baseURL:o,state:g,timeout:n,loadingTracker:f,autoDiscoveryPromise:O});function Ce(u,b){return Re(),ie(u,b)!==void 0}O.then(()=>{if(l=true,g.eager.length>0)return Promise.allSettled(g.eager.map(u=>K(u))).then(u=>{u.forEach((b,L)=>{b.status==="rejected"&&console.error(`[route-forge] eager load failed for level "${g.eager[L]}":`,b.reason);});})}).then(()=>{m(X);}).catch(u=>{w(u);});function Oe(u,b){return u?y.then(u,b).then(()=>X):y}let Ue=$e({load:K,api:Pe,route:ae,hasRoute:Ce,getRoutes:ke,invalidate:be,isLoaded:we,loadingTracker:f}),X={api:Pe,load:K,route:ae,url:ae,invalidate:be,isLoaded:we,hasRoute:Ce,getRoutes:ke,getLevels:_e,warnings:c,isLoading:()=>f.isLoading(),isReady:()=>k,onLoadingChange:u=>f.subscribe(u),interceptors:{request:x,response:D},ready:Oe,use(u,b){return u===void 0?X:Ue(u,b)}};return X}
2
- exports.AdapterNotFoundError=j;exports.DiscoveryNotReadyError=z;exports.ForgeError=C;exports.HTTPError=T;exports.InterceptorManagerImpl=_;exports.InvalidInterceptorReturnError=G;exports.InvalidPathParamError=W;exports.LoadingTracker=J;exports.MissingRouteParamError=H;exports.NetworkError=F;exports.RequestAbortedError=$;exports.RouteCache=N;exports.UnknownLevelError=B;exports.UnknownRouteError=E;exports.createInterceptorManager=te;exports.createRouteForge=Qe;exports.resolveRouteName=he;exports.resolveRouteNameSync=oe;return exports;})({});
1
+ var RouteForge=(function(exports){'use strict';var se="route-forge:";function Ge(r){return r==="memory"||typeof globalThis>"u"?null:r==="sessionStorage"?globalThis.sessionStorage??null:r==="localStorage"?globalThis.localStorage??null:null}var G=class{constructor(e){this.memory=new Map;this.onStorageEvent=e=>{e.key&&e.key.startsWith(se)&&this.memory.delete(e.key.slice(se.length));};this.storage=e.storage,this.fallbackTtl=e.ttl,this.backend=Ge(e.storage),this.backend&&typeof globalThis.addEventListener=="function"&&globalThis.addEventListener("storage",this.onStorageEvent);}key(e){return `${se}${e}`}get(e){if(this.storage==="memory"||!this.backend)return this.getFromMemory(e);let t=this.getFromMemory(e);if(t)return t;let n=this.backend.getItem(this.key(e));if(n)try{let o=JSON.parse(n);if(this.isExpired(o)){this.del(e);return}return this.memory.set(e,o),o}catch{return}}getFromMemory(e){let t=this.memory.get(e);if(t){if(this.isExpired(t)){this.memory.delete(e);return}return t}}set(e,t){let n,o;t===null?(n=0,o=false):t===void 0?(n=this.fallbackTtl,o=true):t===0?(n=0,o=true):(n=Math.min(t,this.fallbackTtl),o=true);let s={level:e.level,routes:e.routes,ttl:n,cachedAt:Date.now()};if(this.memory.set(e.level,s),!(!o||this.storage==="memory"||!this.backend))try{this.backend.setItem(this.key(e.level),JSON.stringify(s));}catch{}}del(e){if(this.memory.delete(e),this.backend)try{this.backend.removeItem(this.key(e));}catch{}}clear(){if(this.memory.clear(),this.backend)try{let e=[];for(let t=0;t<this.backend.length;t++){let n=this.backend.key(t);n&&n.startsWith(se)&&e.push(n);}for(let t of e)this.backend.removeItem(t);}catch{}}isExpired(e){return e.ttl===null||e.ttl===0?false:Date.now()-e.cachedAt>e.ttl*1e3}};var L=class{constructor(){this.handlers=[];this.nextId=0;}use(e,t){let n=this.nextId++;return this.handlers.push({id:n,onFulfilled:e,onRejected:t}),n}eject(e){let t=this.handlers.findIndex(n=>n.id===e);t>=0&&this.handlers.splice(t,1);}clear(){this.handlers=[];}forEach(e){for(let t of this.handlers)e(t);}get size(){return this.handlers.length}};function z(){return new L}var P=class extends Error{constructor(e,t){super(e),this.name=this.constructor.name,this.code=t.code,t.route!==void 0&&(this.route=t.route),t.level!==void 0&&(this.level=t.level),t.context!==void 0&&(this.context=t.context),t.cause!==void 0&&(this.cause=t.cause);}};function Ae(r){if(!r||r.length===0)return "";let e=5,t=r.slice(0,e).join(", "),n=r.length>e?` (+${r.length-e} more)`:"";return ` Available: ${t}${n}`}var T=class extends P{constructor(e,t,n){super(`Route "${e}" not found${t?` in level "${t}"`:""}.${Ae(n)}`,{code:"RF_FE_001",route:e,level:t,context:n&&n.length>0?{candidates:n}:void 0});}},W=class extends P{constructor(e,t){super(`Level "${e}" not declared in options.levels.${Ae(t)}`,{code:"RF_FE_002",level:e,context:t&&t.length>0?{candidates:t}:void 0});}},V=class extends P{constructor(e,t,n){super(`Missing path parameter(s) ${t.join(", ")} for route "${e}"${n?` (${n})`:""}`,{code:"RF_FE_003",route:e,context:{missingParams:t,...n!==void 0?{uri:n}:{}}});}},J=class extends P{constructor(e,t,n){super(`Path parameter "${t}" must be a primitive value (string, number, boolean), got ${typeof n}`,{code:"RF_FE_003",route:e,context:{param:t,value:n}});}},$=class extends P{constructor(e){super(`Adapter "${e}" not available; install axios or use 'builtin'`,{code:"RF_FE_005",context:{adapter:e}});}},K=class extends P{constructor(e){super("Request interceptor must return a RequestConfig object",{code:"RF_FE_006",route:e});}},q=class extends P{constructor(e,t,n,o){super(e,{code:"RF_FE_007",route:t,level:n,cause:o});}},D=class extends P{constructor(e,t){super(e,{code:"RF_FE_008",route:t.route,level:t.level,context:{status:t.status,url:t.url,method:t.method},cause:t.cause}),t.response!==void 0&&(this.response=t.response);}},_=class extends P{constructor(e,t,n){super(`Request aborted${e?` for route "${e}"`:""}`,{code:"RF_FE_009",route:e,level:t,cause:n});}},Q=class extends P{constructor(){super("Route data not available. Auto-discovery has not completed. Use forge.ready() or forge.use(level) first, or await ready() before calling route()/hasRoute().",{code:"RF_FE_010"});}};async function Y(r,e){let t=[];r.forEach(o=>t.push(o)),t.reverse();let n=Promise.resolve(e);for(let o of t){let s=o.onFulfilled,i=o.onRejected;n=n.then(async u=>{if(!s)return u;let l=await s(u);if(l===null||typeof l!="object")throw new K;return l},u=>i?i(u):Promise.reject(u));}return n}async function X(r,e){let t=[];r.forEach(o=>t.push(o));let n=e;for(let o of t){let s=o.onFulfilled,i=o.onRejected;n=n.then(u=>s?s(u):u,u=>i?i(u):Promise.reject(u));}return n}function ie(r){if(r==null)return {};let e,t;if(typeof r=="function")e=r;else if(Array.isArray(r))e=r[0],t=r[1];else if(typeof r=="object"){let o=r;e=o.resolve,t=o.reject;}else throw new TypeError(`Interceptor declaration must be a function, a [resolve, reject] tuple, or a { resolve, reject } object; received ${typeof r}.`);let n={};if(e!=null){if(typeof e!="function")throw new TypeError(`Interceptor "resolve" (onFulfilled) must be a function; received ${typeof e}.`);n.onFulfilled=e;}if(t!=null){if(typeof t!="function")throw new TypeError(`Interceptor "reject" (onRejected) must be a function; received ${typeof t}.`);n.onRejected=t;}return n}function ge(r,e){if(e?.aborted)return true;let t=r;return t?t.name==="AbortError"||t.code==="ERR_CANCELED":false}function ze(...r){let e=r.filter(n=>!!n);if(e.length===0)return;if(e.length===1)return e[0];if(typeof AbortSignal<"u"&&typeof AbortSignal.any=="function")return AbortSignal.any(e);let t=new AbortController;for(let n of e){if(n.aborted)return t.abort(n.reason),t.signal;n.addEventListener("abort",()=>t.abort(n.reason),{once:true});}return t.signal}function We(r){return r===null?false:typeof FormData<"u"&&r instanceof FormData||typeof URLSearchParams<"u"&&r instanceof URLSearchParams||typeof Blob<"u"&&r instanceof Blob||typeof ArrayBuffer<"u"&&(r instanceof ArrayBuffer||ArrayBuffer.isView(r))||typeof ReadableStream<"u"&&r instanceof ReadableStream}async function Ie(r){let e=ze(r.signal,r.timeout&&r.timeout>0?AbortSignal.timeout(r.timeout):void 0),t=r.url;if(r.paramsSerializer&&r.params){let a=r.paramsSerializer(r.params);a&&(t=t.includes("?")?`${t}&${a}`:`${t}?${a}`);}let n=new Headers(r.headers),o={method:r.method,headers:n};e&&(o.signal=e),r.body!==void 0&&!["GET","HEAD"].includes(r.method.toUpperCase())&&(typeof r.body=="string"||We(r.body)?o.body=r.body:(o.body=JSON.stringify(r.body),n.has("Content-Type")||n.set("Content-Type","application/json")));let s;try{s=await fetch(t,o);}catch(a){throw ge(a)?a:new q(a instanceof Error?a.message:String(a),r.route,r.level,a)}let i=await s.text(),u=i;if((s.headers.get("content-type")??"").includes("application/json"))try{u=JSON.parse(i);}catch{}let f={route:r.route,level:r.level,method:r.method,url:t,status:s.status,headers:s.headers,data:u,config:r};if(s.status>=200&&s.status<300)return f;throw new D(`HTTP ${s.status} for route "${r.route}" (${r.method} ${t})`,{route:r.route,level:r.level,status:s.status,url:t,method:r.method,response:f})}function he(r){let e=r?.request??z(),t=r?.response??z(),n=Ie;async function o(a){let g=await Y(e,a),y=Promise.resolve(g).then(n);return X(t,y)}return {request:o,requestRaw:n,interceptors:{request:e,response:t},runsInterceptors:true,get:(a,g)=>o({...g,url:a,method:"GET"}),post:(a,g)=>o({...g,url:a,method:"POST"}),put:(a,g)=>o({...g,url:a,method:"PUT"}),patch:(a,g)=>o({...g,url:a,method:"PATCH"}),delete:(a,g)=>o({...g,url:a,method:"DELETE"})}}function qe(r){if(!r)return new Headers;let e=typeof r.toJSON=="function"?r.toJSON():r;try{return new Headers(e)}catch{return new Headers}}async function me(){let r,e="axios";try{let n=await import(e);r=n.default??n;}catch{typeof globalThis<"u"&&globalThis.axios&&(r=globalThis.axios);}if(!r||typeof r.request!="function")return null;async function t(n){let o={...n.headers};try{let s=await r.request({url:n.url,method:n.method,headers:o,data:n.body,timeout:n.timeout,signal:n.signal});return {route:n.route,level:n.level,method:n.method,url:n.url,status:s.status,headers:qe(s.headers),data:s.data,config:n}}catch(s){if(s&&s.response){let i=s.response;return {route:n.route,level:n.level,method:n.method,url:n.url,status:i.status,headers:qe(i.headers),data:i.data,config:n}}throw s}}return {request:t,interceptors:void 0}}async function Re(r){let{adapter:e}=r;if(e&&typeof e=="object")return {request:n=>e.request(n),interceptors:e.interceptors};if(e==="builtin")return he(r.forgeInterceptors);if(e==="axios"){let n=await me();if(!n)throw new $("axios");return n}let t=await me().catch(()=>null);return t||he(r.forgeInterceptors)}function De(r){let{adapter:e,requestInterceptors:t,responseInterceptors:n,warnings:o}=r,s=Re({adapter:e,forgeInterceptors:{request:t,response:n}}),i=false,u=null;return async function(){return i||(u=await s.catch(f=>{if(f instanceof $)throw f;return o&&console.warn(`[route-forge] adapter initialization failed (${f?.message??String(f)}); falling back to builtin`),Re({adapter:"builtin",forgeInterceptors:{request:t,response:n}})}),i=true),u}}function Le(){let r,e,t=new Promise((i,u)=>{r=i,e=u;}),n;t.catch(()=>{});let o=false;t.then(i=>{n=i,o=true;},()=>{});function s(i,u){return i?t.then(i,u).then(()=>n):t}return {resolve:i=>r(i),reject:i=>e(i),isReady:()=>o,ready:s}}var Z=class{constructor(){this.count=0;this.subscribers=new Set;}start(){this.count++,this.notify();}stop(){this.count=Math.max(0,this.count-1),this.notify();}isLoading(){return this.count>0}getCount(){return this.count}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e);}}notify(){let e={loading:this.count>0,count:this.count};for(let t of this.subscribers)try{t(e);}catch{}}};var ee=class{constructor(){this.subscribers=new Set;this.pending=new Set;this.scheduled=false;}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e);}}notify(e){this.subscribers.size!==0&&(this.pending.add(e),!this.scheduled&&(this.scheduled=true,queueMicrotask(()=>this.flush())));}flush(){this.scheduled=false;let e=[...this.pending];this.pending.clear();for(let t of e)for(let n of this.subscribers)try{n(t);}catch{}}};function te(r){return r.endsWith("/")?r.slice(0,-1):r}function ae(r){return r.startsWith("/")?r:`/${r}`}function O(r,e){return `${te(r)}${ae(e)}`}function ye(r,e){return `${O(e.baseURL,ae(e.endpoint))}/${encodeURIComponent(r)}`}function re(r){let{params:e,query:t,body:n,headers:o,timeout:s,signal:i,...u}=r,l=e?{...e}:{};for(let[y,p]of Object.entries(u))y in l||(l[y]=p);let f,a,g;return t!==void 0&&(typeof t=="object"&&t!==null?f=t:"query"in l||(l.query=t)),n!==void 0&&(typeof n!="string"&&typeof n!="number"?a=n:"body"in l||(l.body=n)),o!==void 0&&(typeof o=="object"&&o!==null?g=o:"headers"in l||(l.headers=o)),{pathParams:l,query:f,body:a,headers:g,timeout:s,signal:i}}function B(r,e,t){let n=r.parameter_defaults??{},o=[],s={};for(let f of r.parameters){let a=e[f];a==null&&f in n&&(a=n[f]),a==null?r.uri.includes(`{${f}?}`)||o.push(f):s[f]=a;}if(o.length>0)throw new V(r.name,o,r.uri);let i=r.uri.replace(/\{([^{}]+)\}/g,(f,a)=>{let g=a.endsWith("?"),y=g?a.slice(0,-1):a;if(s[y]!==void 0){let p=s[y];if(typeof p=="object")throw new J(r.name,y,p);return encodeURIComponent(String(p))}return g?"":f});if(i=i.replace(/\/+/g,"/").replace(/\/$/,""),/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(t.urlPrefix)){let f=te(t.urlPrefix);return i.startsWith("/")?`${f}${i}`:`${f}/${i}`}let u=te(t.baseURL),l=t.urlPrefix;return i.startsWith("/")?`${u}${l}${i}`:`${u}${l}/${i}`}function ve(r){return (r.methods.find(t=>t.toUpperCase()!=="HEAD")??r.methods[0]??"GET").toUpperCase()}function ue(r,e){if(!e)return r;let t=new URLSearchParams;for(let[o,s]of Object.entries(e))s!=null&&t.append(o,String(s));let n=t.toString();return n?r.includes("?")?`${r}&${n}`:`${r}?${n}`:r}var Ve=["params","query","body","headers","timeout","signal"];function be(r,e,t){if(e){if(!Ve.some(i=>i in e))return B(r,e,t)}else return B(r,{},t);let{pathParams:n,query:o}=re(e);return ue(B(r,n,t),o)}var we="/_forge/routes";async function Se(r,e,t){let{explicitLevels:n,explicitEndpoint:o,warnings:s}=r,u=O(e,o??we);try{return await t("__forge__.summary",u)}catch(l){if(n&&n.length>0)return s&&console.warn(`[route-forge] summary endpoint unreachable: ${l.message}; using explicit levels`),null;throw new q(`Failed to fetch route summary from "${u}": ${l?.message??String(l)}; check the backend manifest endpoint or options.endpoint`,void 0,void 0,l)}}function Je(r){return typeof r=="number"&&r<0?null:r}function Ce(r,e,t){let{explicitLevels:n,explicitEager:o,explicitEndpoint:s,warnings:i}=t,u=r.schemeVersion??1;u>1&&i&&console.warn(`[route-forge] backend schemeVersion=${u} > client supported 1; some features may be unavailable`),r.config.endpoint_prefix&&r.config.endpoint_prefix!==s&&(s&&i&&console.warn(`[route-forge] backend endpoint_prefix "${r.config.endpoint_prefix}" overrides frontend endpoint "${s}"`),e.endpoint=r.config.endpoint_prefix),r.config.url_prefix&&(e.urlPrefix=r.config.url_prefix.endsWith("/")?r.config.url_prefix.slice(0,-1):r.config.url_prefix),e.cacheTtl=Je(r.config.cache_ttl);let l=Object.keys(r.levels),f={};for(let g of l){let y=r.levels[g]?.route;y&&typeof y.uri=="string"&&(f[g]={uri:y.uri,methods:y.methods??["GET","HEAD"]});}if(e.levelRoutes=f,n&&n.length>0){let g=n.filter(p=>l.includes(p)),y=n.filter(p=>!l.includes(p));y.length>0&&i&&console.warn(`[route-forge] levels not in backend summary and dropped: ${y.join(", ")}`),e.levels=g;}else e.levels=l.slice();let a=l.filter(g=>r.levels[g]?.load==="eager");if(!o)e.eager=a;else {let g=new Set([...a,...o]);e.eager=[...g];}}var Ke="__ROUTE_FORGE__",ne;function je(){if(ne!==void 0)return ne;if(typeof window>"u")return ne=null,null;let r;try{r=window[Ke];}catch{r=void 0;}return ne=r!==null&&typeof r=="object"?r:null,ne}var ce=class{constructor(e){this.inflight=new Map;this.invalidationGens=new Map;this.cache=e.cache,this.state=e.state,this.baseURL=e.baseURL,this.fetchMeta=e.fetchMeta,this.autoDiscoveryPromise=e.autoDiscoveryPromise,this.getAutoDiscoveryError=e.getAutoDiscoveryError,this.onChange=e.onChange;}assertLevelDeclared(e){if(!this.state.levels.includes(e))throw new W(e,this.state.levels)}async fetchLevel(e){let t=this.state.levelRoutes[e]?.uri,n=t?O(this.baseURL,t):ye(e,{baseURL:this.baseURL,endpoint:this.state.endpoint});return await this.fetchMeta(`route-forge.${e}`,n,e)}async loadOne(e){let t=this.getAutoDiscoveryError();if(t)throw t;if(this.assertLevelDeclared(e),!(this.state.cacheTtl===null)&&this.cache.get(e))return;let o=this.inflight.get(e);if(o)return o;let s=this.invalidationGens.get(e)??0,i=(async()=>{try{let u=await this.fetchLevel(e);(this.invalidationGens.get(e)??0)===s&&(this.cache.set(u,this.state.cacheTtl),this.onChange?.(e));}finally{this.inflight.delete(e);}})();return this.inflight.set(e,i),i}async load(e){await this.autoDiscoveryPromise;let t=Array.isArray(e)?e:[e];await Promise.all(t.map(n=>this.loadOne(n)));}async revalidate(e){await this.autoDiscoveryPromise;let t=Array.isArray(e)?e:[e];await Promise.all(t.map(n=>this.revalidateOne(n)));}async revalidateOne(e){let t=this.getAutoDiscoveryError();if(t)throw t;this.assertLevelDeclared(e);let n=this.inflight.get(e);if(n)return n;let o=this.invalidationGens.get(e)??0,s=(async()=>{try{let i=await this.fetchLevel(e);(this.invalidationGens.get(e)??0)===o&&(this.cache.set(i,this.state.cacheTtl),this.onChange?.(e));}finally{this.inflight.delete(e);}})();return this.inflight.set(e,s),s}invalidate(e){if(e===void 0){this.cache.clear(),this.inflight.clear();for(let t of this.state.levels)this.invalidationGens.set(t,(this.invalidationGens.get(t)??0)+1);for(let t of this.state.levels)this.onChange?.(t);}else if(Array.isArray(e)){for(let t of e)this.cache.del(t),this.inflight.delete(t),this.invalidationGens.set(t,(this.invalidationGens.get(t)??0)+1);for(let t of e)this.onChange?.(t);}else this.cache.del(e),this.inflight.delete(e),this.invalidationGens.set(e,(this.invalidationGens.get(e)??0)+1),this.onChange?.(e);}isLoaded(e){return e?this.cache.get(e)!==void 0:this.state.levels.length>0&&this.state.levels.every(t=>this.cache.get(t)!==void 0)}findRouteMeta(e,t){let o=this.cache.get(e)?.routes[t];if(o)return {...o,level:e}}routeNames(e){this.assertLevelDeclared(e);let t=this.cache.get(e);return t?Object.keys(t.routes):[]}getRoutes(e){if(e!==void 0){this.assertLevelDeclared(e);let o=this.cache.get(e)?.routes??{},s={};for(let[i,u]of Object.entries(o))s[i]=structuredClone(u);return s}let t={};for(let n of this.state.levels){let o=this.cache.get(n);if(o){let s={};for(let[i,u]of Object.entries(o.routes))s[i]=structuredClone(u);t[n]=s;}}return t}};function Me(r){let{ensureAdapter:e,requestInterceptors:t,responseInterceptors:n,load:o,findRouteMeta:s,getRouteNames:i,baseURL:u,state:l,timeout:f,loadingTracker:a,autoDiscoveryPromise:g}=r;async function y(p,k,d){let{pathParams:m,query:v,body:w,headers:C,timeout:h}=re(k);if(d?.aborted)throw new _(p.name,p.level,d.reason);let b=ve(p),A=ue(B(p,m,{baseURL:u,urlPrefix:l.urlPrefix}),v),E={route:p.name,level:p.level??"",method:b,url:A,headers:{Accept:"application/json",...C??{}},body:w,params:m,timeout:h??f,signal:d,meta:p},I=await e(),U=I.runsInterceptors?E:await Y(t,E);if(U.signal?.aborted)throw new _(p.name,p.level,U.signal.reason);a.start();try{let N=I.request(U).then(R=>{if(R.status<200||R.status>=300)throw new D(`HTTP ${R.status} for route "${R.route}" (${R.method} ${R.url})`,{route:R.route,level:R.level,status:R.status,url:R.url,method:R.method,response:R});return R},R=>{throw R instanceof P?R:ge(R,d)?new _(p.name,p.level,R):new q(R instanceof Error?R.message:String(R),p.name,p.level,R)});return I.runsInterceptors?await N:await X(n,N)}finally{a.stop();}}return function(k,d,m={}){let v,w=false,C,h=m.signal,b=()=>{v?v.abort(h?.reason):(w=true,C=h?.reason);};h?.aborted?(w=true,C=h.reason):h?.addEventListener("abort",b,{once:true});let E=(async()=>{try{v=new AbortController,w&&v.abort(C),await g,await o(k);let I=s(k,d);if(!I)throw new T(d,k,i(k));return await y(I,m,v.signal)}finally{h?.removeEventListener("abort",b);}})();return E.abort=()=>{v?v.abort():w=true;},E}}function $e(r,e){let t=r;for(;t.endsWith(e);)t=t.slice(0,-e.length);return t}async function xe(r,e,t,n,o="."){let s=$e(t,o);if(!n)return s;let i=`${s}${o}${n}`;if(!n.startsWith(`${s}${o}`)||(await r.load(e),r.hasRoute(e,i)))return i;if(r.hasRoute(e,n))return n;throw new T(i,e,r.getRouteNames?.(e))}function le(r,e,t,n,o="."){let s=$e(t,o);if(!n)return s;let i=`${s}${o}${n}`;if(!n.startsWith(`${s}${o}`)||r.hasRoute(e,i))return i;if(r.hasRoute(e,n))return n;throw new T(i,e,r.getRouteNames?.(e))}function _e(r,e){for(let t of Object.keys(e)){let n=e[t];Object.defineProperty(r,t,{value:n!==null&&typeof n=="object"?Object.freeze(n):n,writable:false,enumerable:false,configurable:false});}return r}function Oe(r){let{load:e,api:t,route:n,hasRoute:o,getRoutes:s,getRouteNames:i,invalidate:u,isLoaded:l,loadingTracker:f}=r,a={load:e,hasRoute:o,getRouteNames:i};function g(d,m){let v=e(d);v.catch(()=>{});let w=m?(h,b)=>xe(a,d,m,h).then(A=>t(d,A,b)):(h,b)=>t(d,h,b),C=w;return _e(C,{level:d,...m!==void 0?{prefix:m}:{},api:w,route:m?(h,b)=>n(d,le(a,d,m,h),b):(h,b)=>n(d,h,b),url:m?(h,b)=>n(d,le(a,d,m,h),b):(h,b)=>n(d,h,b),hasRoute:h=>o(d,h),getRoutes:()=>s(d),load:()=>e(d),invalidate:()=>u(d),isLoaded:()=>l(d),isLoading:()=>f.isLoading(),onLoadingChange:h=>f.subscribe(h)}),Object.defineProperty(C,"levelLoaded",{value:v,writable:false,enumerable:true,configurable:true}),C}function y(d,m,v,w){let C=m.then(()=>d);return v?C.then(v,w).then(()=>d):C}function p(d,m,v){Object.defineProperty(d,"onLevelLoaded",{value:(w,C)=>y(d,m,w,C),writable:false,enumerable:false,configurable:false}),Object.defineProperty(d,"useRoutePrefix",{value:w=>k(v,w),writable:false,enumerable:false,configurable:false});}function k(d,m){let v=g(d,m),w=v.levelLoaded;return p(v,w,d),v}return k}var Qe=3e4,Ye=3600;function Xe(r={}){let e=je()??r.summary??null,{adapter:t="auto",timeout:n=Qe,baseURL:o="",interceptors:s,cache:i={}}=r,u=r.warnings??true,l=new Z,f=new ee,a=r.levels,g=r.eager,y=r.endpoint,p={levels:a??[],eager:g??[],endpoint:y??e?.config?.endpoint_prefix??we,urlPrefix:"",cacheTtl:void 0,levelRoutes:{}},k={explicitLevels:a,explicitEager:g,explicitEndpoint:y,warnings:u},d=false,m=Le(),v=i.ttl??Ye,w=i.storage??"memory",C=new G({storage:w,ttl:v}),h=new L,b=new L,A=ie(s?.request);(A.onFulfilled||A.onRejected)&&h.use(A.onFulfilled,A.onRejected);let E=ie(s?.response);(E.onFulfilled||E.onRejected)&&b.use(E.onFulfilled,E.onRejected);let I=De({adapter:t,requestInterceptors:h,responseInterceptors:b,warnings:u});function U(){if(!d&&!a?.length)throw new Q}async function N(c,x,j=""){let H=await I(),He={route:c,level:j,method:"GET",url:x,headers:{Accept:"application/json"},params:{},timeout:n,meta:{name:c,uri:x,methods:["GET"],parameters:[],level:j}},M=await(H.requestRaw??H.request)(He);if(!M||M.status<200||M.status>=300)throw new D(`Failed to fetch "${c}" (${x}): HTTP ${M?.status}`,{level:j,status:M?.status,url:x,method:"GET",response:M??void 0});return M.data}let S;e?(Ce(e,p,k),d=true,S=Promise.resolve()):S=Se(k,o,N).then(c=>{c!==null&&Ce(c,p,k);});let R=null;S.catch(c=>{R=c;});let F=new ce({cache:C,state:p,baseURL:o,fetchMeta:N,autoDiscoveryPromise:S,getAutoDiscoveryError:()=>R,onChange:c=>f.notify(c)}),oe=c=>F.load(c),Ue=c=>F.revalidate(c),de=(c,x)=>F.findRouteMeta(c,x),Pe=c=>F.invalidate(c),ke=c=>F.isLoaded(c),Ee=F.getRoutes.bind(F);function Be(){return [...p.levels]}function pe(c,x,j){U();let H=de(c,x);if(!H)throw new T(x,c,F.routeNames(c));return be(H,j??{},{baseURL:o,urlPrefix:p.urlPrefix})}let Fe=Me({ensureAdapter:I,requestInterceptors:h,responseInterceptors:b,load:oe,findRouteMeta:de,getRouteNames:c=>F.routeNames(c),baseURL:o,state:p,timeout:n,loadingTracker:l,autoDiscoveryPromise:S});function Te(c,x){return U(),de(c,x)!==void 0}S.then(()=>{if(d=true,p.eager.length>0)return Promise.allSettled(p.eager.map(c=>oe(c))).then(c=>{c.forEach((x,j)=>{x.status==="rejected"&&console.error(`[route-forge] eager load failed for level "${p.eager[j]}":`,x.reason);});})}).then(()=>{m.resolve(fe);}).catch(c=>{m.reject(c);});let Ne=Oe({load:oe,api:Fe,route:pe,hasRoute:Te,getRoutes:Ee,getRouteNames:c=>F.routeNames(c),invalidate:Pe,isLoaded:ke,loadingTracker:l}),fe={api:Fe,load:oe,revalidate:Ue,route:pe,url:pe,invalidate:Pe,isLoaded:ke,hasRoute:Te,getRoutes:Ee,getLevels:Be,warnings:u,isLoading:()=>l.isLoading(),isReady:()=>m.isReady(),onLoadingChange:c=>l.subscribe(c),onRoutesChange:c=>f.subscribe(c),interceptors:{request:h,response:b},ready:m.ready,use(c,x){return c===void 0?fe:Ne(c,x)}};return fe}
2
+ exports.AdapterNotFoundError=$;exports.DiscoveryNotReadyError=Q;exports.ForgeError=P;exports.HTTPError=D;exports.InterceptorManagerImpl=L;exports.InvalidInterceptorReturnError=K;exports.InvalidPathParamError=J;exports.LoadingTracker=Z;exports.MissingRouteParamError=V;exports.NetworkError=q;exports.RequestAbortedError=_;exports.RouteCache=G;exports.RouteChangeTracker=ee;exports.UnknownLevelError=W;exports.UnknownRouteError=T;exports.createInterceptorManager=z;exports.createRouteForge=Xe;exports.resolveRouteName=xe;exports.resolveRouteNameSync=le;return exports;})({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@route-forge/core",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "框架无关的命名路由客户端核心:分级懒加载、隔离缓存、并发去重、拦截器 / Framework-agnostic named-routes client core: tiered lazy loading, isolated cache, request deduplication, interceptors",
5
5
  "keywords": [
6
6
  "laravel",
@@ -1,501 +0,0 @@
1
- /**
2
- * LoadingTracker:加载中标识状态管理
3
- *
4
- * 通过引用计数器跟踪并发请求数。
5
- * - start():计数器 +1,进入加载状态
6
- * - stop():计数器 -1,归零时退出加载状态
7
- * - isLoading():查询当前是否处于加载中
8
- * - subscribe(cb):监听状态变化,返回取消订阅函数
9
- *
10
- * @see .docs/SPEC.md §4.1.8
11
- */
12
- /** 状态变更回调签名 */
13
- type LoadingChangeCallback = (event: LoadingChangeEvent) => void;
14
- /** 状态变更事件 */
15
- interface LoadingChangeEvent {
16
- /** 当前是否仍处于加载中 */
17
- loading: boolean;
18
- /** 当前并发请求数 */
19
- count: number;
20
- }
21
- /**
22
- * 加载状态跟踪器
23
- *
24
- * 单一计数器,跟踪所有 API 请求的并发数。
25
- * 不绑定任何 UI 样式,仅提供状态供框架层或业务层消费。
26
- */
27
- declare class LoadingTracker {
28
- /** 当前并发请求计数 */
29
- private count;
30
- /** 订阅者集合 */
31
- private subscribers;
32
- /**
33
- * 开始一次加载(计数器 +1)
34
- */
35
- start(): void;
36
- /**
37
- * 结束一次加载(计数器 -1)
38
- */
39
- stop(): void;
40
- /**
41
- * 查询当前是否处于加载中
42
- */
43
- isLoading(): boolean;
44
- /**
45
- * 获取当前并发计数
46
- */
47
- getCount(): number;
48
- /**
49
- * 订阅加载状态变更
50
- * @returns 取消订阅函数
51
- */
52
- subscribe(cb: LoadingChangeCallback): () => void;
53
- /** 通知所有订阅者 */
54
- private notify;
55
- }
56
-
57
- /**
58
- * @route-forge/core 核心类型定义
59
- * @see .docs/SPEC.md §4.1.3a, §4.1.1
60
- */
61
-
62
- /**
63
- * 路由元信息(后端 /_forge/routes/{level} 返回的单条路由结构)
64
- */
65
- interface RouteMeta {
66
- /** 路由名,如 'admin.users.show' */
67
- name: string;
68
- /** 路由 URI 模板,如 'admin/users/{user}' */
69
- uri: string;
70
- /** 支持的 HTTP 方法集合,如 ['GET','HEAD'] */
71
- methods: string[];
72
- /** 路径参数名列表,如 ['user'] */
73
- parameters: string[];
74
- /** 路径参数默认值(Laravel ->defaults()),key 为参数名,value 为默认值 */
75
- parameter_defaults?: Record<string, unknown>;
76
- /** 所属层级(前端填充,便于隔离缓存) */
77
- level?: string;
78
- /**
79
- * 后端 manifest 契约预留字段:路由级缓存 TTL(秒)。
80
- * 当前后端层级表响应(LevelRoutesResponse)不下发此字段,缓存 TTL 统一由摘要
81
- * `config.cache_ttl`(全局,见 cache.ts / auto-discovery)驱动;保留字段位以便将来支持路由级缓存时不改类型。
82
- */
83
- cache?: number | null;
84
- }
85
- /**
86
- * 某层级下的全部路由元信息响应(后端 GET {levels[level].route.uri} 返回)。
87
- * 结构为 `{ level, routes }`,缓存 TTL 不在层级表内下发,统一取自摘要 config.cache_ttl。
88
- */
89
- interface LevelRoutesResponse {
90
- level: string;
91
- /** 路由名 → 路由元信息;后端空层级序列化为 `{}`(对象契约,非数组) */
92
- routes: Record<string, RouteMeta>;
93
- }
94
- /**
95
- * 摘要端点响应(SPEC §3.1.6)
96
- * GET {endpoint_prefix} 返回此结构;同时是 @forgeSummary 注入 window.__ROUTE_FORGE__ 的值。
97
- *
98
- * unassigned 不是顶层字段,而是 `levels` 中一个恒存在的特殊层级,
99
- * 前端按其 route.uri 走 HTTP 懒加载获取未分配路由明细。
100
- */
101
- interface SummaryResponse {
102
- /**
103
- * manifest 协议版本号(拼写为 scheme「方案」而非 schema;DESIGN.md §6.3),必填,默认 1;
104
- * 前端据此做向前兼容(>1 时告警)。
105
- */
106
- schemeVersion: number;
107
- levels: Record<string, {
108
- description: string;
109
- load: 'lazy' | 'eager';
110
- route_count: number;
111
- /** 该层级明细端点的自描述,前端据此拼 URL 懒加载(endpoint_prefix 仅作兜底) */
112
- route: {
113
- /** 层级明细端点绝对路径,如 '/_forge/routes/admin' */
114
- uri: string;
115
- /** 恒为 ['GET','HEAD'] */
116
- methods: string[];
117
- };
118
- }>;
119
- config: {
120
- strict_mode: boolean;
121
- endpoint_prefix: string;
122
- /** 后端下发的 URL 前缀,生成业务 URL 时拼接到路由 URI 前;未配置为 null(SPEC §3.1.6) */
123
- url_prefix: string | null;
124
- /** 全局统一缓存 TTL(秒):null=不缓存、0=永久、正整数=N 秒;后端已把负值归一为 null */
125
- cache_ttl: number | null;
126
- };
127
- }
128
- /**
129
- * 请求拦截器接收/返回的配置对象(可变,返回修改后的版本)
130
- */
131
- interface RequestConfig {
132
- route: string;
133
- level: string;
134
- method: string;
135
- url: string;
136
- headers: Record<string, string>;
137
- body?: unknown;
138
- params: Record<string, unknown>;
139
- meta: RouteMeta;
140
- /** 请求超时毫秒数 */
141
- timeout?: number;
142
- /** 自定义 query 序列化函数 */
143
- paramsSerializer?: (params: Record<string, unknown>) => string;
144
- /** 请求取消信号(AbortSignal),用于取消已发出的请求 */
145
- signal?: AbortSignal;
146
- }
147
- /**
148
- * 响应拦截器首段接收的完整数据对象
149
- */
150
- interface ResponseData {
151
- route: string;
152
- level: string;
153
- method: string;
154
- url: string;
155
- status: number;
156
- headers: Headers;
157
- data: unknown;
158
- config: RequestConfig;
159
- }
160
- /**
161
- * forge.api() 返回的可取消请求对象。
162
- * 继承 Promise,附加 abort() 方法用于取消请求。
163
- * 内部自动创建 AbortController,用户无需手动管理。
164
- */
165
- interface ForgeRequest<T = unknown> extends Promise<T> {
166
- /** 取消请求。调用后请求将被中止,Promise reject 为 RequestAbortedError */
167
- abort(): void;
168
- }
169
- /**
170
- * forge.api(level, name, params) 调用参数
171
- *
172
- * 参数解析规则(智能消解):
173
- * 1. `params` — 显式指定路径参数,优先级最高
174
- * 2. 平铺的 string | number 值 — 作为路径参数(含与 query/body/headers 同名的 key)
175
- * 3. `query` (对象) — 查询参数,序列化到 URL query string
176
- * 4. `body` (非 string/number) — 请求体
177
- * 5. `headers` (对象) — 自定义请求头
178
- *
179
- * 当路径参数名与 query/body/headers 冲突时:
180
- * - 值为 string | number → 智能识别为路径参数
181
- * - 同时提供 params 显式指定 → params 优先,固定 key 按原定义处理
182
- */
183
- interface ApiCallParams {
184
- /** 路径参数:填充到 URI 模板的 {name} 占位符 */
185
- [paramName: string]: unknown;
186
- /** 显式指定路径参数(优先级最高,解决路径参数名与 query/body/headers 冲突的场景) */
187
- params?: Record<string, unknown>;
188
- /**
189
- * 查询参数(对象 → query string)或路径参数(string | number → 填充 {query} 占位符)
190
- * @see .docs/SPEC.md §4.1.3 参数智能解析
191
- */
192
- query?: Record<string, unknown> | string | number;
193
- /**
194
- * 请求体(非基元值 → body)或路径参数(string | number → 填充 {body} 占位符)
195
- */
196
- body?: unknown;
197
- /**
198
- * 自定义请求头(对象 → headers)或路径参数(string | number → 填充 {headers} 占位符)
199
- */
200
- headers?: Record<string, string> | string | number;
201
- /**
202
- * 单次请求超时覆盖(毫秒);不传时使用 createRouteForge({ timeout }) 全局值
203
- */
204
- timeout?: number;
205
- }
206
- /**
207
- * 缓存存储介质
208
- */
209
- type CacheStorage = 'memory' | 'sessionStorage' | 'localStorage';
210
- /**
211
- * Adapter 选择值
212
- */
213
- type AdapterOption = 'auto' | 'axios' | 'builtin' | Fetcher;
214
- /**
215
- * Fetcher 接口(自定义 adapter)
216
- * @see .docs/SPEC.md §4.3.3
217
- */
218
- interface Fetcher {
219
- request(config: RequestConfig): Promise<ResponseData>;
220
- interceptors?: {
221
- request?: InterceptorManager<RequestConfig, RequestConfig>;
222
- response?: InterceptorManager<ResponseData, unknown>;
223
- };
224
- }
225
- /**
226
- * 拦截器管理器接口(与 axios use/eject/clear API 一致)
227
- *
228
- * 双类型参数说明(对应 SPEC §4.1.3a):
229
- * - 请求拦截:TIn = TOut = RequestConfig(不可变换类型,仅修改字段)
230
- * - 响应拦截:TIn = ResponseData, TOut = unknown(首段接收 ResponseData,
231
- * 后续段接收上一段返回值,返回类型由用户自行约束)
232
- */
233
- interface InterceptorManager<TIn, TOut = TIn> {
234
- use(onFulfilled?: (value: TIn) => TOut | Promise<TOut>, onRejected?: (error: unknown) => unknown | Promise<unknown>): number;
235
- eject(id: number): void;
236
- clear(): void;
237
- /** 内部使用:当前已注册拦截器快照 */
238
- forEach(fn: (handler: InterceptorHandler<TIn, TOut>) => void): void;
239
- }
240
- /**
241
- * 单个拦截器内部结构
242
- */
243
- interface InterceptorHandler<TIn, TOut = TIn> {
244
- id: number;
245
- onFulfilled?: (value: TIn) => TOut | Promise<TOut>;
246
- onRejected?: (error: unknown) => unknown | Promise<unknown>;
247
- }
248
- /**
249
- * forge 顶层 API 形状
250
- */
251
- interface RouteForge {
252
- /**
253
- * 通过层级 + 路由名调用 API;level 用于确定加载哪个层级的路由元信息。
254
- * 声明 `ForgeRouteMap`(codegen 生成或 module augmentation)后,name / params / 响应类型
255
- * 按映射收敛;未声明映射时完全等价于 `(level: string, name: string, params?: ApiCallParams) => ForgeRequest<unknown>`。
256
- */
257
- api<L extends string, N extends ForgeRouteName<L>>(level: L, name: N, params?: ForgeApiParams<L, N>): ForgeRequest<ForgeApiResponse<L, N>>;
258
- /** 拉取一个或多个层级(自动并发去重) */
259
- load(level: string | string[]): Promise<void>;
260
- /**
261
- * 仅生成 URL,不发请求;level 用于定位路由所在的层级缓存。
262
- * 声明 `ForgeRouteMap` 后 name 按映射收敛(params 保持宽松——路径参数与后端默认值均可)。
263
- */
264
- route<L extends string, N extends ForgeRouteName<L>>(level: L, name: N, params?: Record<string, unknown>): string;
265
- /** route() 的语义别名,适用于链接生成等场景 */
266
- url<L extends string, N extends ForgeRouteName<L>>(level: L, name: N, params?: Record<string, unknown>): string;
267
- /**
268
- * 失效缓存:
269
- * - invalidate():失效全部层级
270
- * - invalidate('admin'):失效指定层级
271
- * - invalidate(['admin', 'manage']):批量失效指定层级
272
- */
273
- invalidate(level?: string | string[]): void;
274
- /** 检查指定层级路由是否已加载并缓存;不传参检查全部 */
275
- isLoaded(level?: string): boolean;
276
- /** 检查指定层级下某路由是否存在(需该层级缓存已加载);声明 `ForgeRouteMap` 后 name 按映射收敛 */
277
- hasRoute<L extends string, N extends ForgeRouteName<L>>(level: L, name: N): boolean;
278
- /** 查询加载中标识状态 */
279
- isLoading(): boolean;
280
- /**
281
- * ready() 是否已成功 resolve(同步查询,不触发任何加载)。
282
- * discovery + eager 完成为 true;reject 不算就绪(保持 false)。
283
- * 供框架层 ready 门闩(react Provider `gate` / vue `ForgeReady`)避免多余的 fallback 首帧。
284
- */
285
- isReady(): boolean;
286
- /** 非致命警告是否启用(来自 createRouteForge({ warnings }),默认 true);vue/react 适配层共用此开关 */
287
- readonly warnings: boolean;
288
- /** 订阅加载状态变更,返回取消订阅函数 */
289
- onLoadingChange(cb: LoadingChangeCallback): () => void;
290
- /**
291
- * 获取路由元信息快照(深拷贝,修改返回值不影响内部缓存)。
292
- * - getRoutes(level):返回指定层级下全部路由;层级未声明抛 `UnknownLevelError`,
293
- * 已声明但未加载返回 `{}`
294
- * - getRoutes():返回全部层级的路由(按 level 分组)
295
- */
296
- getRoutes(level: string): Record<string, RouteMeta>;
297
- getRoutes(): Record<string, Record<string, RouteMeta>>;
298
- /**
299
- * 当前已知的已声明层级列表(含后端恒注入的 `unassigned`)。
300
- * 内嵌 / `summary` 引导:构造后即可用;网络引导:`ready()` 前可能为空数组、之后为全量。
301
- * 只读发现 API,不触发加载、未就绪不抛错(返回 `[]`)。
302
- */
303
- getLevels(): string[];
304
- /** 拦截器入口(请求 / 响应) */
305
- interceptors: {
306
- request: InterceptorManager<RequestConfig, RequestConfig>;
307
- response: InterceptorManager<ResponseData, unknown>;
308
- };
309
- /**
310
- * auto-discovery + eager load 完成后 resolve。
311
- * 始终返回 Promise<this>,resolve 值为 forge 实例自身,支持链式调用。
312
- *
313
- * - 无参:返回 Promise,适合 async/await
314
- * - 有参:回调内部走 then/catch,仍返回 Promise
315
- */
316
- ready(): Promise<RouteForge>;
317
- ready(onFulfilled: (forge: RouteForge) => void, onRejected?: (error: unknown) => void): Promise<RouteForge>;
318
- /**
319
- * 绑定 level(+ 可选 prefix),返回 BoundForge。
320
- * 唯一入口 — Vue/React/IIFE 共享同一套 API 表面。
321
- *
322
- * - use():不绑定,返回 RouteForge 自身
323
- * - use(level):绑定 level
324
- * - use(level, prefix):绑定 level + prefix
325
- */
326
- use(): RouteForge;
327
- use<L extends string>(level: L, prefix?: string): BoundForge;
328
- }
329
- /**
330
- * createRouteForge 配置项
331
- * @see .docs/SPEC.md §5.2
332
- */
333
- interface RouteForgeOptions {
334
- /**
335
- * 摘要端点 URL(网络拉取来源)。
336
- * 摘要数据源级联(SPEC §4.1.1):Blade 注入的 `window.__ROUTE_FORGE__` > 本 `summary` 字段 > 网络拉取 `endpoint`。
337
- * 命中前两者时可省略;层级明细端点取自摘要 `levels[].route.uri`,不依赖本字段。
338
- * 三者皆无(既无注入/summary、又无 endpoint)时网络引导回退默认摘要端点 `/_forge/routes`(DEFAULT_ENDPOINT)。
339
- */
340
- endpoint?: string;
341
- /**
342
- * 直接提供摘要数据(SummaryResponse),跳过摘要 HTTP 往返——用于测试或非 Blade 的注入式引导。
343
- * 优先级低于页面内嵌的 `window.__ROUTE_FORGE__`(存在时以后端真值为准)。
344
- * @see .docs/SPEC.md §4.1.1 / §3.1.8
345
- */
346
- summary?: SummaryResponse;
347
- /**
348
- * 层级列表。未传时从摘要自动发现(SPEC §4.1.1)。
349
- * 显式传入时取与摘要响应 levels 键的交集(前端不能声明后端不存在的层级,SPEC §5.3)。
350
- */
351
- levels?: string[];
352
- eager?: string[];
353
- adapter?: AdapterOption;
354
- cache?: {
355
- ttl?: number;
356
- storage?: CacheStorage;
357
- };
358
- interceptors?: {
359
- /**
360
- * 声明式请求拦截器,只描述**一个**拦截器,支持三种写法(SPEC §4.1.1):
361
- * - 函数 `resolve` → 视为 onFulfilled
362
- * - 元组 `[resolve?, reject?]` → 成功 / 失败(允许缺位,如 `[resolve]`、`[undefined, reject]`)
363
- * - 对象 `{ resolve?, reject? }` → 具名成功 / 失败
364
- * 需要注册多个拦截器请改用运行时 `forge.interceptors.request.use()`(可多次调用)。
365
- */
366
- request?: ((c: RequestConfig) => RequestConfig | Promise<RequestConfig>) | [
367
- ((c: RequestConfig) => RequestConfig | Promise<RequestConfig>)?,
368
- ((e: unknown) => unknown | Promise<unknown>)?
369
- ] | {
370
- resolve?: (c: RequestConfig) => RequestConfig | Promise<RequestConfig>;
371
- reject?: (e: unknown) => unknown | Promise<unknown>;
372
- };
373
- /**
374
- * 声明式响应拦截器,形状同 `request`(首段 onFulfilled 接收 ResponseData)。
375
- * 同样只描述一个拦截器,多拦截器改用运行时 `forge.interceptors.response.use()`。
376
- */
377
- response?: ((r: ResponseData) => unknown | Promise<unknown>) | [((r: ResponseData) => unknown | Promise<unknown>)?, ((e: unknown) => unknown | Promise<unknown>)?] | {
378
- resolve?: (r: ResponseData) => unknown | Promise<unknown>;
379
- reject?: (e: unknown) => unknown | Promise<unknown>;
380
- };
381
- };
382
- /**
383
- * @deprecated 前端校验始终开启,此选项不再被消费。
384
- * 前端在层级未声明时始终抛 `UnknownLevelError`、路由名不存在始终抛 `UnknownRouteError`、
385
- * 必填路径参数缺失始终抛 `MissingRouteParamError` —— 校验行为与 strict 无关,静默忽略会掩盖拼写错误、难以排查。
386
- * 后端 `strict_mode` 的宽松/严格语义(未命中层级路由是否归入 unassigned/fallback)由后端在生成 manifest 时决定。
387
- * 字段保留仅为向后兼容,传入不会有任何效果。
388
- */
389
- strict?: boolean;
390
- timeout?: number;
391
- baseURL?: string;
392
- /**
393
- * 非致命警告开关(默认 true):控制 core 内部的 `console.warn`(如显式 levels 降级、
394
- * endpoint_prefix 覆盖、schemeVersion 兼容提示等)。`false` 时静音这些 warn——
395
- * `console.error` 级输出(如 eager 层级加载失败)不受影响,错误永远响亮。
396
- * 该值以只读字段 `RouteForge.warnings` 暴露,vue/react 适配层的渲染期降级警告同样遵循它。
397
- */
398
- warnings?: boolean;
399
- }
400
- /**
401
- * 二级路由类型映射(可由 codegen 生成或通过 module augmentation 增强)
402
- *
403
- * @example
404
- * declare module '@route-forge/core' {
405
- * interface ForgeRouteMap {
406
- * admin: {
407
- * 'users.show': { method: 'GET'; params: { user: string | number }; response: User };
408
- * 'users.index': { method: 'GET'; params: {}; response: User[] };
409
- * };
410
- * public: {
411
- * 'login.show': { method: 'GET'; params: {}; response: unknown };
412
- * };
413
- * }
414
- * }
415
- */
416
- interface ForgeRouteMap {
417
- }
418
- /** 从 ForgeRouteMap 推断指定层级下的路由名;未定义时回退 string */
419
- type ForgeRouteName<L extends string> = [
420
- keyof ForgeRouteMap
421
- ] extends [never] ? string : L extends keyof ForgeRouteMap ? keyof ForgeRouteMap[L] & string : string;
422
- /** 从 ForgeRouteMap 推断指定路由的 params 类型;未定义时回退 ApiCallParams */
423
- type ForgeApiParams<L extends string, N extends string> = [
424
- keyof ForgeRouteMap
425
- ] extends [never] ? ApiCallParams : L extends keyof ForgeRouteMap ? N extends keyof ForgeRouteMap[L] ? (ForgeRouteMap[L][N] extends {
426
- params: infer P;
427
- } ? P & ApiCallParams : ApiCallParams) : ApiCallParams : ApiCallParams;
428
- /** 从 ForgeRouteMap 推断指定路由的响应类型;未定义时回退 unknown */
429
- type ForgeApiResponse<L extends string, N extends string> = [
430
- keyof ForgeRouteMap
431
- ] extends [never] ? unknown : L extends keyof ForgeRouteMap ? N extends keyof ForgeRouteMap[L] ? (ForgeRouteMap[L][N] extends {
432
- response: infer R;
433
- } ? R : unknown) : unknown : unknown;
434
- /**
435
- * 已绑定 level 的 forge 对象。
436
- * 由 `forge.use(level, prefix?)` 返回,Vue/React/IIFE 共享同一 API 表面。
437
- *
438
- * @typeParam LL - levelLoaded 的类型:core 默认 Promise<void>,Vue 替换为 Ref<boolean>
439
- */
440
- interface BoundForge<LL = Promise<void>> {
441
- /** 直接调用 = api 快捷方式,自动带绑定的 level */
442
- (name: string, params?: ApiCallParams): ForgeRequest;
443
- /** 当前绑定的 level */
444
- readonly level: string;
445
- /** 绑定的路由名前缀(仅传入 prefix 时存在) */
446
- readonly prefix?: string;
447
- /** level 加载状态(core: Promise<void>,Vue: Ref<boolean>) */
448
- levelLoaded: LL;
449
- api(name: string, params?: ApiCallParams): ForgeRequest;
450
- route(name: string, params?: Record<string, unknown>): string;
451
- url(name: string, params?: Record<string, unknown>): string;
452
- hasRoute(name: string): boolean;
453
- getRoutes(): Record<string, RouteMeta>;
454
- load(): Promise<void>;
455
- invalidate(): void;
456
- isLoaded(): boolean;
457
- isLoading(): boolean;
458
- onLoadingChange(cb: LoadingChangeCallback): () => void;
459
- /** 等待绑定的 level 加载完成,resolve 值为自身(BoundForge),保证可安全调用 */
460
- onLevelLoaded(): Promise<BoundForge<LL>>;
461
- onLevelLoaded(onFulfilled: (bound: BoundForge<LL>) => void, onRejected?: (error: unknown) => void): Promise<BoundForge<LL>>;
462
- /** 在已绑定 level 基础上追加/替换 prefix,返回新的 BoundForge */
463
- useRoutePrefix(prefix: string): BoundForge<LL>;
464
- }
465
- /** call 函数签名 — 未绑定 level,需要显式传入 */
466
- interface UseForgeApiCall {
467
- (level: string, name: string, params?: ApiCallParams): Promise<{
468
- data: unknown;
469
- error: unknown;
470
- }>;
471
- }
472
- /** call 函数签名 — 已绑定 level,无需再传 */
473
- interface UseForgeApiBoundCall<L extends string> {
474
- (name: ForgeRouteName<L>, params?: ForgeApiParams<L, ForgeRouteName<L>>): Promise<{
475
- data: ForgeApiResponse<L, ForgeRouteName<L>>;
476
- error: unknown;
477
- }>;
478
- }
479
- /**
480
- * useForgeApi 返回类型 — 未绑定 level
481
- * @typeParam PendingType - 框架层 pending 状态类型(Vue: Ref<boolean>, React: boolean)
482
- * @typeParam ErrorType - 框架层 error 状态类型(Vue: Ref<unknown>, React: unknown)
483
- */
484
- interface UseForgeApiReturn<PendingType = unknown, ErrorType = unknown> {
485
- pending: PendingType;
486
- error: ErrorType;
487
- call: UseForgeApiCall;
488
- }
489
- /**
490
- * useForgeApi 返回类型 — 已绑定 level
491
- * @typeParam L - 层级名
492
- * @typeParam PendingType - 框架层 pending 状态类型
493
- * @typeParam ErrorType - 框架层 error 状态类型
494
- */
495
- interface UseForgeApiBoundReturn<L extends string, PendingType = unknown, ErrorType = unknown> {
496
- pending: PendingType;
497
- error: ErrorType;
498
- call: UseForgeApiBoundCall<L>;
499
- }
500
-
501
- export { type AdapterOption as A, type BoundForge as B, type CacheStorage as C, type Fetcher as F, type InterceptorManager as I, type LevelRoutesResponse as L, type RouteMeta as R, type SummaryResponse as S, type UseForgeApiBoundCall as U, type RouteForgeOptions as a, type RouteForge as b, type InterceptorHandler as c, type ResponseData as d, type ApiCallParams as e, type ForgeApiParams as f, type ForgeApiResponse as g, type ForgeRequest as h, type ForgeRouteMap as i, type ForgeRouteName as j, type LoadingChangeCallback as k, type LoadingChangeEvent as l, LoadingTracker as m, type RequestConfig as n, type UseForgeApiBoundReturn as o, type UseForgeApiCall as p, type UseForgeApiReturn as q };