tezx 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +225 -165
- package/dist/index.js +2503 -5
- package/dist/index.mjs +2492 -5
- package/package.json +34 -34
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,2492 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
`)+4,v=Buffer.from(h.substring(C),"binary"),E=v.buffer.slice(v.byteOffset,v.byteOffset+v.byteLength);typeof e?.maxSize<"u"&&v.byteLength>e.maxSize&&r(`File size exceeds the limit: ${v.byteLength} bytes (Max: ${e.maxSize} bytes)`);const T=new File([E],m,{type:g});o[y]?Array.isArray(o[y])?o[y].push(T):o[y]=[o[y],T]:o[y]=T}}n(o)}catch{}})});if(s==="deno"||s==="bun"){const n=await l.formData(),r={};for(const[i,o]of n.entries()){let a=o;if(a instanceof File&&typeof e=="object"){let c=a.name;if(e?.sanitized&&(c=`${Date.now()}-${c.replace(/\s+/g,"")?.replace(/[^a-zA-Z0-9.-]/g,"-")}`?.toLowerCase()),Array.isArray(e?.allowedTypes)&&!e.allowedTypes?.includes(a.type))throw new Error(`Invalid file type: "${a.type}". Allowed types: ${e.allowedTypes.join(", ")}`);if(typeof e?.maxSize<"u"&&a.size>e.maxSize)throw new Error(`File size exceeds the limit: ${a.size} bytes (Max: ${e.maxSize} bytes)`);a=new File([await a.arrayBuffer()],c,{type:a.type})}r[i]?Array.isArray(r[i])?r[i].push(a):r[i]=[r[i],a]:r[i]=a}return r}else throw new Error("Unsupported environment for multipart parsing")}d(H,"parseMultipartBody");function z(l){const t=/^(?:(\w+):\/\/)?(?:([^:@]+)?(?::([^@]+))?@)?([^:/?#]+)?(?::(\d+))?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/;let e=l.match(t);const[s,n,r,i,o,a,c,h,u]=e;let f=o;n&&(f=n+"://"+o),a&&(f=f+":"+a);let p=c;p?.endsWith("/")&&p.slice(0,-1);function m(){return h?(decodeURIComponent(h).split("&")?.map(v=>{const[E,T]=v.split("=");return{[E]:T}})).reduce(function(v,E){return{...v,...E}},{}):{}}return d(m,"query"),{pathname:p,hash:u,protocol:n,origin:f,username:r,password:i,hostname:o,href:l,port:a,query:m()}}d(z,"urlParse");class I{static{d(this,"Request")}headers=new j;url;method;urlRef={hash:void 0,protocol:void 0,origin:void 0,username:void 0,password:void 0,hostname:void 0,port:void 0,href:void 0,query:{},pathname:"/"};query;#e;params={};constructor(t,e){if(this.headers=new j(t?.headers),this.method=t?.method?.toUpperCase(),this.params=e,this.#e=t,x.getEnvironment=="node"){const s=x.detectProtocol(t),n=x.getHost(this.headers);this.url=`${s}://${n}${t.url}`}else this.url=t.url;this.urlRef=z(this.url),this.query=this.urlRef.query}async text(){return await k(this.#e)}async json(){return(this.headers.get("content-type")||"").includes("application/json")?await O(this.#e):{}}async formData(t){const e=this.headers.get("content-type")||"";if(!e)throw Error("Invalid Content-Type");if(e.includes("application/json"))return await O(this.#e);if(e.includes("application/x-www-form-urlencoded"))return L(this.#e);if(e.includes("multipart/form-data")){const s=e?.split("; ")?.[1]?.split("=")?.[1];if(!s)throw Error("Boundary not found");return await H(this.#e,s,t)}else return{}}}class B{static{d(this,"JetResponse")}static json(t,...e){let s=200,n={"Content-Type":"application/json; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(JSON.stringify(t),{status:s,headers:n})}static html(t,...e){let s=200,n={"Content-Type":"text/html; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}static text(t,...e){let s=200,n={"Content-Type":"text/plain; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}static xml(t,...e){let s=200,n={"Content-Type":"application/xml; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}static send(t,...e){let s=200,n={};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n=e[1])):typeof e[0]=="object"&&(n=e[0]),n["Content-Type"]||(typeof t=="string"?n["Content-Type"]="text/plain;":typeof t=="object"&&t!==null?(n["Content-Type"]="application/json;",t=JSON.stringify(t)):n["Content-Type"]="application/octet-stream"),new Response(t,{status:s,headers:n})}static redirect(t,e=302,s){return new Response(null,{status:e,headers:{Location:t}})}static async download(t,e){try{let s=!1;const n=x.getEnvironment;if(n==="node"){const{existsSync:i}=await import("fs");s=i(t)}else if(n==="bun")s=Bun.file(t).exists();else if(n==="deno")try{await Deno.stat(t),s=!0}catch{s=!1}if(!s)throw Error("File not found");let r;if(n==="node"){const{readFileSync:i}=await import("fs");r=await i(t)}else n==="bun"?r=await Bun.file(t).arrayBuffer().then(i=>new Uint8Array(i)):n==="deno"&&(r=await Deno.readFile(t));return new Response(r,{status:200,headers:{"Content-Disposition":`attachment; filename="${e}"`,"Content-Type":"application/octet-stream","Content-Length":r.byteLength.toString()}})}catch(s){throw Error("Internal Server Error"+s?.message)}}static async sendFile(t,...e){try{const s=x.getEnvironment,n=t;let r=!1;if(s==="node"){const{existsSync:p}=await import("fs");r=p(n)}else if(s==="bun")r=Bun.file(n).exists();else if(s==="deno")try{await Deno.stat(n),r=!0}catch{r=!1}if(!r)throw Error("File not found");let i=0;if(s==="node"){const{statSync:p}=await import("fs");i=p(n).size}else s==="bun"?i=(await Bun.file(n).arrayBuffer()).byteLength:s==="deno"&&(i=(await Deno.stat(n)).size);const o={html:"text/html",htm:"text/html",css:"text/css",js:"application/javascript",json:"application/json",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",ico:"image/x-icon",pdf:"application/pdf",txt:"text/plain",xml:"application/xml",mp4:"video/mp4",webm:"video/webm",ogg:"audio/ogg",mp3:"audio/mpeg",wav:"audio/wav",zip:"application/zip",gz:"application/gzip",tar:"application/x-tar"},a=t.split(".").pop()?.toLowerCase()||"",c=o[a]||"application/octet-stream";let h;if(s==="node"){const{createReadStream:p}=await import("fs");h=p(n)}else s==="bun"?h=Bun.file(n).stream():s==="deno"&&(h=(await Deno.open(n,{read:!0})).readable);let u={"Content-Type":c,"Content-Length":i.toString()},f="";return typeof e[0]=="string"?(f=e[0],typeof e[1]=="object"&&(u={...u,...e[1]})):typeof e[0]=="object"&&(u={...u,...e[0]}),f&&(u["Content-Disposition"]=`attachment; filename="${f}"`),new Response(h,{status:200,headers:u})}catch(s){throw Error("Internal Server Error"+s?.message)}}}class q{static{d(this,"State")}state;constructor(){this.state=new Map}set(t,e){this.state.set(t,e)}get(t){return this.state.get(t)}delete(t){return this.state.delete(t)}has(t){return this.state.has(t)}keys(){return Array.from(this.state.keys())}values(){return Array.from(this.state.values())}entries(){return Array.from(this.state.entries())}clear(){this.state.clear()}}const M={100:"Continue",101:"Switching Protocols",102:"Processing",103:"Early Hints",200:"OK",201:"Created",202:"Accepted",203:"Non-Authoritative Information",204:"No Content",205:"Reset Content",206:"Partial Content",207:"Multi-Status",208:"Already Reported",226:"IM Used",300:"Multiple Choices",301:"Moved Permanently",302:"Found",303:"See Other",304:"Not Modified",305:"Use Proxy",306:"Switch Proxy",307:"Temporary Redirect",308:"Permanent Redirect",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",409:"Conflict",410:"Gone",411:"Length Required",412:"Precondition Failed",413:"Payload Too Large",414:"URI Too Long",415:"Unsupported Media Type",416:"Range Not Satisfiable",417:"Expectation Failed",418:"I'm a Teapot",421:"Misdirected Request",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",425:"Too Early",426:"Upgrade Required",428:"Precondition Required",429:"Too Many Requests",431:"Request Header Fields Too Large",451:"Unavailable For Legal Reasons",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout",505:"HTTP Version Not Supported",506:"Variant Also Negotiates",507:"Insufficient Storage",508:"Loop Detected",510:"Not Extended",511:"Network Authentication Required"};class U{static{d(this,"Context")}#e;env={};headers=new j;res=new Response;pathname;url;method;#t=200;state=new q;#n={};#r;finalized=!1;#s;#o;#i;#a;#l=!0;#c;#d;#f;#u;#h;constructor(t){this.#e=t,this.method=t?.method?.toUpperCase(),this.pathname=this.req.urlRef.pathname,this.url=this.req.url}get cookies(){const t=this.headers.getAll("cookie");let e={};if(Array.isArray(t)&&t.length!=0){const s=t.join("; ").split(";");for(const n of s){const[r,i]=n?.trim()?.split("=");e[r]=decodeURIComponent(i)}}else if(typeof t=="string"){const s=t.split(";");for(const n of s){const[r,i]=n?.trim()?.split("=");e[r]=decodeURIComponent(i)}}return{get:d(s=>e?.[s],"get"),all:d(()=>e,"all"),delete:d((s,n)=>{const r="",i={...n,expires:new Date(0)},o=`${s}=${r};${F(i)}`;this.headers.set("Set-Cookie",o)},"delete"),set:d((s,n,r)=>{const i=`${s}=${n};${F(r||{})}`;this.headers.set("Set-Cookie",i)},"set")}}json(t,...e){let s=this.#t,n={"Content-Type":"application/json; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(JSON.stringify(t),{status:s,headers:n})}send(t,...e){let s=this.#t,n={};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n=e[1])):typeof e[0]=="object"&&(n=e[0]),n["Content-Type"]||(typeof t=="string"?n["Content-Type"]="text/plain;":typeof t=="object"&&t!==null?(n["Content-Type"]="application/json;",t=JSON.stringify(t)):n["Content-Type"]="application/octet-stream"),new Response(t,{status:s,headers:n})}html(t,...e){let s=this.#t,n={"Content-Type":"text/html; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}text(t,...e){let s=this.#t,n={"Content-Type":"text/plain; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}xml(t,...e){let s=this.#t,n={"Content-Type":"application/xml; charset=utf-8"};return typeof e[0]=="number"?(s=e[0],typeof e[1]=="object"&&(n={...n,...e[1]})):typeof e[0]=="object"&&(n={...n,...e[0]}),new Response(t,{status:s,headers:n})}status=d(t=>(this.#t=t,this),"status");redirect(t,e=302,s){return B.redirect(t,e,s)}async download(t,e){return await B.download(t,e)}async sendFile(t,...e){return await B.sendFile(t,...e)}get req(){return new I(this.#e,this.params)}set params(t){this.#n=t}get params(){return this.#n}}function F(l){const t=[];return l.maxAge&&t.push(`Max-Age=${l.maxAge}`),l.expires&&t.push(`Expires=${l.expires.toUTCString()}`),l.path&&t.push(`Path=${l.path}`),l.domain&&t.push(`Domain=${l.domain}`),l.secure&&t.push("Secure"),l.httpOnly&&t.push("HttpOnly"),l.sameSite&&t.push(`SameSite=${l.sameSite}`),t.join("; ")}d(F,"serializeOptions");let b=class{static{d(this,"GlobalConfig")}static middlewareRule="ignore";static env;static notFound=d(l=>{const{method:t,urlRef:{pathname:e}}=l.req;return new Response(`${t}: '${e}' could not find
|
|
4
|
-
`,{headers:{"Content-Type":"text/plain"},status:404})},"notFound");static onError=d((l,t)=>{throw Error(l)},"onError");static loggerFn=d(()=>({}),"loggerFn")};function W(l){async function t(s,n){console.log(n.addr);const r=await l.serve(s);return r instanceof Response?r:new Response(r.body,{status:r.status,statusText:r.statusText||M[r?.status],headers:new Headers(r.headers)})}d(t,"handleRequest");function e(s,n){const r=typeof Deno<"u";try{const i=r?Deno.serve({port:s},t):null;if(!i)throw new Error("Deno is not find");const a=`\x1B[1m\u{1F680} Deno Accelero Server running at \x1B[1;34mhttp\x1B[0m://localhost:${s}/\x1B[0m`;if(typeof n=="function")n(a);else{const c=b.loggerFn();c.success&&c.success(a)}return i}catch(i){throw new Error(i?.message)}}return d(e,"listen"),{listen:e}}d(W,"denoAdapter");function J(l){function t(e,s){const n=typeof Bun<"u"?Bun.serve:null;try{if(!n)throw new Error("Bun is not find");const r=n({port:e,async fetch(a){console.log(r.requestIP(a));const c=await l.serve(a);return c instanceof Response?c:new Response(c.body,{status:c.status,statusText:c.statusText||M[c?.status],headers:new Headers(c.headers)})}}),o=`\x1B[1m Bun \u{1F680}Accelero Server running at \x1B[1;34mhttp\x1B[0m://localhost:${e}/\x1B[0m`;if(typeof s=="function")s(o);else{const a=b.loggerFn();a.success&&a.success(o)}return r}catch(r){throw new Error(r?.message)}}return d(t,"listen"),{listen:t}}d(J,"bunAdapter");function V(l){function t(e,s){import("http").then(n=>{let r=n.createServer(async(i,o)=>{const a=await l.serve(i);console.log(i.socket.remoteAddress);const c=a?.statusText;if(!(a instanceof Response))throw new Error("Invalid response from Accelero.serve");const h=Object.fromEntries(await a.headers.entries());c&&(o.statusMessage=c),o.writeHead(a.status,h);const{Readable:u}=await import("stream");if(a.body instanceof u)a.body.pipe(o);else{const f=await a.arrayBuffer();o.end(Buffer.from(f))}});r.listen(e,()=>{const o=`\x1B[1m NodeJS \u{1F680}Accelero Server running at \x1B[1;34mhttp\x1B[0m://localhost:${e}/\x1B[0m`;if(typeof s=="function")s(o);else{const a=b.loggerFn();a.success&&a.success(o)}return r})}).catch(n=>{throw Error(n.message)})}return d(t,"listen"),{listen:t}}d(V,"nodeAdapter");class G{static{d(this,"CommonHandler")}notFound(t){return b.notFound=t,this}onError(t){return b.onError=t,this}}class R{static{d(this,"TriMiddleware")}children=new Map;middlewares=[];groupMiddlewares=new Map;isOptional=!1;pathname;constructor(t="/"){this.pathname=t}}class _ extends G{static{d(this,"MiddlewareConfigure")}triMiddlewares=new R;basePath;constructor(t="/"){super(),this.basePath=t}addMiddleware(t,e){const s=`${this.basePath}/${t}`?.split("/").filter(Boolean);let n=this.triMiddlewares;for(const r of s)if(r.startsWith("*"))n.children.has("*")||n.children.set("*",new R),n=n.children.get("*");else if(r.startsWith(":")){const i=r?.endsWith("?");if(i){n.isOptional=i;continue}n.children.has(":")||n.children.set(":",new R),n=n.children.get(":")}else n.children.has(r)||n.children.set(r,new R),n=n.children.get(r);n.middlewares.push(...e)}}class A{static{d(this,"TrieRouter")}children=new Map;handlers=new Map;pathname;paramName;isParam=!1;constructor(t="/"){this.children=new Map,this.pathname=t}}class $ extends _{static{d(this,"Router")}routers=new Map;rootNode;constructor({basePath:t="/",env:e={}}={}){super(t),this.basePath=t,b.env={...b.env,...e},this.rootNode=new A(t),this.get.bind(this),this.post.bind(this),this.put.bind(this),this.delete.bind(this),this.all.bind(this),this.#s.bind(this),this.addRouter.bind(this),this.group.bind(this)}get(t,...e){return this.#e("GET",t,...e),this}post(t,...e){return this.#e("POST",t,...e),this}put(t,...e){return this.#e("PUT",t,...e),this}patch(t,...e){return this.#e("PATCH",t,...e),this}delete(t,...e){return this.#e("DELETE",t,...e),this}options(t,...e){return this.#e("OPTIONS",t,...e),this}head(t,...e){return this.#e("HEAD",t,...e),this}all(t,...e){return this.#e("ALL",t,...e),this}addRoute(t,e,...s){return this.#e(t,e,...s),this}addRouter(t,e){return this.#s(t,e)}group(t,e){const s=new $({basePath:t,env:b.env});return e(s),this.#s("/",s),this}use(...t){let e="/",s=[],n;return typeof t[0]=="string"?(e=t[0],Array.isArray(t[1])?(s=t[1],n=t[2]):typeof t[1]=="function"?(s=[t[1]],n=t[2]):n=t[1]):typeof t[0]=="function"?t.length===1?s=[t[0]]:(s=[t[0]],n=t[1]):Array.isArray(t[0])?(s=t[0],n=t[1]):t[0]instanceof $&&(n=t[0]),this.#n(e,s),n&&n instanceof $&&this.addRouter(e,n),this}#e(t,e,...s){if(s.length===0)throw new Error("At least one handler is required.");let n=[],r;if(s.length>1?(Array.isArray(s[0])?n=s[0]:typeof s[0]=="function"&&(n=[s[0]]),r=s[s.length-1]):r=s[0],typeof r!="function")throw new Error("Route callback function is missing or invalid.");if(!n.every(i=>typeof i=="function"))throw new Error("Middleware must be a function or an array of functions.");this.#t(t,e,r,n)}#t(t,e,s,n){const r=`${this.basePath}/${e}`.replace(/\\/g,"")?.split("/").filter(Boolean);let i=r.join("/");if(/(\/\*|\?)/.test(i)){let a=this.routers.get(i);return a?a.set(t,{callback:s,middlewares:n}):(a=new Map,a.set(t,{callback:s,middlewares:n}),this.routers.set(i,a))}let o=this.rootNode;for(const a of r)a.startsWith(":")?(o.children.has(":")||o.children.set(":",new A),o=o.children.get(":"),o.isParam=!0,o.paramName||(o.paramName=a.slice(1))):(o.children.has(a)||o.children.set(a,new A),o=o.children.get(a));o.handlers.set(t,{callback:s,middlewares:n}),o.pathname=e}#n(t,e){this.addMiddleware(t,e)}#r(){return`HNDLR-${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`}#s(t,e){const s=`${this.basePath}/${t}`.replace(/\\/g,"")?.split("/").filter(Boolean);let n;if(b.middlewareRule=="ignore"&&(n=this.#r()),e.routers.size)for(const[o,a]of e.routers){let c=o;if(s.length!==0&&(c=s?.join("/")+"/"+o),this.routers.has(c)){const h=this.routers.get(c);for(const[u,f]of a)f.handlerID=n,h.set(u,f);continue}if(n)for(const[h,u]of a)u.handlerID=n;this.routers.set(c,a)}let r=this.rootNode,i=this.triMiddlewares;if(s.length==0)this.#o(r,i,e,n);else{for(const o of s)o.startsWith(":")?(r.children.has(":")||r.children.set(":",new A),r=r.children.get(":"),r.isParam=!0,r.paramName||(r.paramName=o.slice(1))):(r.children.has(o)||r.children.set(o,new A),r=r.children.get(o));for(const o of s)if(o.startsWith("*"))i.children.has("*")||i.children.set("*",new R),i=i.children.get("*");else if(o.startsWith(":")){const a=o?.endsWith("?");if(a){i.isOptional=a;continue}i.children.has(":")||i.children.set(":",new R),i=i.children.get(":")}else i.children.has(o)||i.children.set(o,new R),i=i.children.get(o);this.#o(r,i,e,n)}}#o(t,e,s,n){function r(c,h){let u=h;for(const f of c)if(u.children.has(f[0])){let p=u.children.get(f[0]);for(const[m,y]of f[1].handlers){let g={...y,handlerID:n};p.handlers.set(m,g)}f[1].children.size&&r(f[1].children,p)}else{if(n){let p=d(function(m){for(const[y,g]of m.handlers)g.handlerID=n;for(const[y,g]of m.children)p(g)},"assignHandlerIDs2");p(f[1])}u.children.set(f[0],f[1])}}d(r,"addSubRouter");function i(c,h){let u=h;for(const[f,p]of c)if(u.children.has(f)){let m=u.children.get(f);if(n){let y=[...p.middlewares];m.groupMiddlewares.set(n,y)}else m.middlewares.push(...p.middlewares);p.children.size&&i(p.children,m)}else{if(n){let m=d(function(y){let g=[...y.middlewares];y.groupMiddlewares.set(n,g);for(const[,C]of y.children)m(C)},"assignHandlerIDs2");m(p)}u.children.set(f,p)}}d(i,"addMiddleware");let o=s.rootNode;const a=s.triMiddlewares;for(const[c,h]of o.handlers){let u={...h,handlerID:n};t.handlers.set(c,u)}o.children.size>0&&r(o.children,t),n?e.groupMiddlewares.set(n,[...a.middlewares]):e.middlewares.push(...a.middlewares),a.children.size>0&&i(a.children,e)}}function Z({path:l,urlPattern:t}){let e={};l=l.replace(/^\/+|\/+$/g,""),t=t.replace(/^\/+|\/+$/g,"");const s=l?l.split("/"):[],n=t?t.split("/"):[],r=s.length,i=n.length;if(r>i&&!t.includes("*"))return{success:!1,params:{}};let o=0;for(let a=0;a<i;a++){const c=n[a];if(c?.startsWith("*")){const u=n.slice(a+1);let f=c.length==1?"*":c?.slice(1);if(u.length>0){const p=u.join("/"),m=s.slice(r-u.length).join("/"),y=s.slice(o,r-u.length).join("/");return p!==m||!y?{success:!1,params:{}}:(e[f]=y,{success:!0,params:e})}else{const p=s.slice(o).join("/");return p?(e[f]=p,{success:!0,params:e}):{success:!1,params:{}}}}if(c.startsWith(":")&&c.endsWith("?")){const u=c.slice(1,-1),f=n[a+1];if(f&&!f.startsWith(":")&&f!=="*"&&o<r&&s[o]===f){e[u]=null;continue}const m=n.slice(a+1).filter(g=>!(g.startsWith(":")&&g.endsWith("?"))).length;r-o===m?e[u]=null:o<r?(e[u]=s[o],o++):e[u]=null;continue}if(c.startsWith(":")){const u=c.slice(1);if(!/^[a-zA-Z0-9_]+$/.test(u))return{success:!1,params:{}};if(o<r)e[u]=s[o],o++;else return{success:!1,params:{}};continue}const h=s[o];if(c!==h)return{success:!1,params:{}};o++}return o<r?{success:!1,params:{}}:{success:!0,params:e}}d(Z,"useParams");class K extends ${static{d(this,"Accelero")}constructor({basePath:t="/",middlewareRule:e="follow",env:s={},logger:n=void 0}={}){super({basePath:t,env:s}),b.middlewareRule=e,n&&(b.loggerFn=n),this.serve=this.serve.bind(this)}#e(t,e){const s=this.routers;for(let n of this.routers.keys()){const{success:r,params:i}=Z({path:e,urlPattern:n}),o=s.get(n)?.get(t)||s.get(n)?.get("ALL");if(r&&o)return{handlerID:o.handlerID,callback:o.callback,middlewares:o.middlewares,params:i}}return null}#t(t,e){const s=e.split("/").filter(Boolean),n={};let r=this.rootNode;for(let i of s)if(r.children.has(i))r=r.children.get(i);else if(r.children.has(":"))r=r.children.get(":"),r.paramName&&(n[r.paramName]=i);else return null;if(r?.handlers?.size&&r?.pathname){const i=r.handlers.get(t)||r.handlers.get("ALL");return i?{handlerID:i?.handlerID,middlewares:i.middlewares,callback:i.callback,params:n}:null}return null}findRoute(t,e){return this.#t(t,e)||this.#e(t,e)}#n(t,e){return async s=>{let n=0;const r=d(async()=>n<t.length?t[n++](s,r):await e(s),"next");return await r()}}#r(t,e){const s=t.split("/").filter(Boolean);let n=[],r=this.triMiddlewares;for(let i of s){if(r.children.has(i))r=r.children.get(i);else if(r.children.has("*"))r=r.children.get("*");else if(r.children.has(":"))r=r.children.get(":");else break;e?n.push(...r.groupMiddlewares.get(e)||[]):n.push(...r.middlewares)}return n}async#s(t){let e=new U(t);const s=e.req.urlRef,{pathname:n}=s;let r=this.#r(n);e.env=b.env;const i=b.loggerFn();i.request&&i.request(e.method,e.pathname);try{return await this.#n([...this.triMiddlewares.middlewares,...r],async o=>{const a=this.findRoute(o.req.method,n);if(a?.callback){o.params=a.params;const c=a.callback,h=a.handlerID;let u=[...a.middlewares];h&&u.push(...this.#r(n,h));const f=await this.#n(u,c)(o);f?.headers&&o.headers.add(f.headers);const p=f?.statusText||M[f?.status]||"",m=f.status||200;let y=o.headers.toObject();return i.response&&i.response(o.method,o.pathname,m),f instanceof Response?new Response(f.body,{status:m,statusText:p,headers:y}):new Response(f.body,{status:m,statusText:p,headers:y})}else return i.response&&i.response(o.method,o.pathname,404),b.notFound(o)})(e)}catch(o){let a=o;return o instanceof Error&&(a=o.message),i.error&&i.error(`${M[500]}: ${a} `),b.onError(a,e)}}async serve(t){return this.#s(t)}}function Q(l,t){try{let e=!1,s=x.getEnvironment;if(s==="node"||s==="bun"){const{existsSync:i}=N("fs");e=i(l)}else if(s==="deno")try{Deno.statSync(l),e=!0}catch{e=!1}if(!e)return;let n="";if(s==="node"||s==="bun"){const{readFileSync:i}=N("fs");n=i(l,"utf8")}else s==="deno"&&(n=new TextDecoder("utf-8").decode(Deno.readFileSync(l)));const r=n.split(`
|
|
5
|
-
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);let GlobalConfig = class {
|
|
2
|
+
static notFound = (ctx2) => {
|
|
3
|
+
const {
|
|
4
|
+
method,
|
|
5
|
+
urlRef: { pathname }
|
|
6
|
+
} = ctx2.req;
|
|
7
|
+
return ctx2.text(`${method}: '${pathname}' could not find
|
|
8
|
+
`, 404);
|
|
9
|
+
};
|
|
10
|
+
static onError = (err, ctx2) => {
|
|
11
|
+
return ctx2.text(err, 500);
|
|
12
|
+
};
|
|
13
|
+
static allowDuplicateMw = false;
|
|
14
|
+
static overwriteMethod = true;
|
|
15
|
+
static enableLogger = false;
|
|
16
|
+
static loggerFn = () => {
|
|
17
|
+
return {};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function denoAdapter(TezX2) {
|
|
22
|
+
async function handleRequest(req, x) {
|
|
23
|
+
const response = await TezX2.serve(req);
|
|
24
|
+
if (response instanceof Response) {
|
|
25
|
+
return response;
|
|
26
|
+
} else {
|
|
27
|
+
return new Response(response.body, {
|
|
28
|
+
status: response.status,
|
|
29
|
+
statusText: response.statusText || "",
|
|
30
|
+
headers: new Headers(response.headers)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function listen(port, callback) {
|
|
35
|
+
const isDeno = typeof Deno !== "undefined";
|
|
36
|
+
try {
|
|
37
|
+
const server = isDeno ? Deno.serve({ port }, handleRequest) : null;
|
|
38
|
+
if (!server) {
|
|
39
|
+
throw new Error("Deno is not find");
|
|
40
|
+
}
|
|
41
|
+
const protocol = "\x1B[1;34mhttp\x1B[0m";
|
|
42
|
+
const message = `\x1B[1m\u{1F680} Deno TezX Server running at ${protocol}://localhost:${port}/\x1B[0m`;
|
|
43
|
+
if (typeof callback === "function") {
|
|
44
|
+
callback(message);
|
|
45
|
+
} else {
|
|
46
|
+
const logger = GlobalConfig.loggerFn();
|
|
47
|
+
if (logger.success) {
|
|
48
|
+
logger.success(message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return server;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(err?.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
listen
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function bunAdapter(TezX2) {
|
|
61
|
+
function listen(port, callback) {
|
|
62
|
+
const serve = typeof Bun !== "undefined" ? Bun.serve : null;
|
|
63
|
+
try {
|
|
64
|
+
if (!serve) {
|
|
65
|
+
throw new Error("Bun is not find");
|
|
66
|
+
}
|
|
67
|
+
const server = serve({
|
|
68
|
+
port,
|
|
69
|
+
async fetch(req) {
|
|
70
|
+
const response = await TezX2.serve(req);
|
|
71
|
+
if (response instanceof Response) {
|
|
72
|
+
return response;
|
|
73
|
+
} else {
|
|
74
|
+
return new Response(response.body, {
|
|
75
|
+
status: response.status,
|
|
76
|
+
statusText: response.statusText || "",
|
|
77
|
+
headers: new Headers(response.headers)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const protocol = "\x1B[1;34mhttp\x1B[0m";
|
|
83
|
+
const message = `\x1B[1m Bun TezX Server running at ${protocol}://localhost:${port}/\x1B[0m`;
|
|
84
|
+
if (typeof callback == "function") {
|
|
85
|
+
callback(message);
|
|
86
|
+
} else {
|
|
87
|
+
const logger = GlobalConfig.loggerFn();
|
|
88
|
+
if (logger.success) {
|
|
89
|
+
logger.success(message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return server;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(err?.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
listen
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function nodeAdapter(TezX2) {
|
|
102
|
+
function listen(port, callback) {
|
|
103
|
+
import('http').then((r) => {
|
|
104
|
+
let server = r.createServer(async (req, res) => {
|
|
105
|
+
const response = await TezX2.serve(req);
|
|
106
|
+
const statusText = response?.statusText;
|
|
107
|
+
if (!(response instanceof Response)) {
|
|
108
|
+
throw new Error("Invalid response from TezX.serve");
|
|
109
|
+
}
|
|
110
|
+
const headers = Object.fromEntries(await response.headers.entries());
|
|
111
|
+
if (statusText) {
|
|
112
|
+
res.statusMessage = statusText;
|
|
113
|
+
}
|
|
114
|
+
res.writeHead(response.status, headers);
|
|
115
|
+
const { Readable } = await import('stream');
|
|
116
|
+
if (response.body instanceof Readable) {
|
|
117
|
+
response.body.pipe(res);
|
|
118
|
+
} else {
|
|
119
|
+
const body = await response.arrayBuffer();
|
|
120
|
+
res.end(Buffer.from(body));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
server.listen(port, () => {
|
|
124
|
+
const protocol = "\x1B[1;34mhttp\x1B[0m";
|
|
125
|
+
const message = `\x1B[1m NodeJS TezX Server running at ${protocol}://localhost:${port}/\x1B[0m`;
|
|
126
|
+
if (typeof callback == "function") {
|
|
127
|
+
callback(message);
|
|
128
|
+
} else {
|
|
129
|
+
const logger = GlobalConfig.loggerFn();
|
|
130
|
+
if (logger.success) {
|
|
131
|
+
logger.success(message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return server;
|
|
135
|
+
});
|
|
136
|
+
}).catch((r) => {
|
|
137
|
+
throw Error(r.message);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
listen
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class EnvironmentDetector {
|
|
146
|
+
static get getEnvironment() {
|
|
147
|
+
if (typeof Bun !== "undefined") return "bun";
|
|
148
|
+
if (typeof Deno !== "undefined") return "deno";
|
|
149
|
+
if (typeof process !== "undefined" && process.versions?.node) return "node";
|
|
150
|
+
return "unknown";
|
|
151
|
+
}
|
|
152
|
+
static detectProtocol(req) {
|
|
153
|
+
try {
|
|
154
|
+
if (this.getEnvironment === "node") {
|
|
155
|
+
return req?.socket?.encrypted ? "https" : "http";
|
|
156
|
+
}
|
|
157
|
+
return "unknown";
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw new Error("Failed to detect protocol.");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
static getHost(headers) {
|
|
163
|
+
try {
|
|
164
|
+
return headers?.get("host") || "unknown";
|
|
165
|
+
} catch (error) {
|
|
166
|
+
throw new Error("Failed to get host.");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const mimeTypes = {
|
|
172
|
+
// Text/Web Formats
|
|
173
|
+
html: "text/html",
|
|
174
|
+
htm: "text/html",
|
|
175
|
+
css: "text/css",
|
|
176
|
+
js: "text/javascript",
|
|
177
|
+
mjs: "text/javascript",
|
|
178
|
+
json: "application/json",
|
|
179
|
+
xml: "application/xml",
|
|
180
|
+
txt: "text/plain",
|
|
181
|
+
md: "text/markdown",
|
|
182
|
+
csv: "text/csv",
|
|
183
|
+
tsv: "text/tab-separated-values",
|
|
184
|
+
rtf: "application/rtf",
|
|
185
|
+
markdown: "text/markdown",
|
|
186
|
+
// Image Formats
|
|
187
|
+
png: "image/png",
|
|
188
|
+
jpg: "image/jpeg",
|
|
189
|
+
jpeg: "image/jpeg",
|
|
190
|
+
gif: "image/gif",
|
|
191
|
+
svg: "image/svg+xml",
|
|
192
|
+
webp: "image/webp",
|
|
193
|
+
ico: "image/x-icon",
|
|
194
|
+
bmp: "image/bmp",
|
|
195
|
+
tiff: "image/tiff",
|
|
196
|
+
psd: "image/vnd.adobe.photoshop",
|
|
197
|
+
// Video Formats
|
|
198
|
+
mp4: "video/mp4",
|
|
199
|
+
webm: "video/webm",
|
|
200
|
+
ogg: "video/ogg",
|
|
201
|
+
mov: "video/quicktime",
|
|
202
|
+
avi: "video/x-msvideo",
|
|
203
|
+
wmv: "video/x-ms-wmv",
|
|
204
|
+
flv: "video/x-flv",
|
|
205
|
+
"3gp": "video/3gpp",
|
|
206
|
+
// Audio Formats
|
|
207
|
+
mp3: "audio/mpeg",
|
|
208
|
+
wav: "audio/wav",
|
|
209
|
+
aac: "audio/aac",
|
|
210
|
+
flac: "audio/flac",
|
|
211
|
+
m4a: "audio/mp4",
|
|
212
|
+
mid: "audio/midi",
|
|
213
|
+
midi: "audio/midi",
|
|
214
|
+
// Font Formats
|
|
215
|
+
woff: "font/woff",
|
|
216
|
+
woff2: "font/woff2",
|
|
217
|
+
ttf: "font/ttf",
|
|
218
|
+
otf: "font/otf",
|
|
219
|
+
eot: "application/vnd.ms-fontobject",
|
|
220
|
+
// Application/Document Formats
|
|
221
|
+
pdf: "application/pdf",
|
|
222
|
+
odp: "application/vnd.oasis.opendocument.presentation",
|
|
223
|
+
zip: "application/zip",
|
|
224
|
+
gz: "application/gzip",
|
|
225
|
+
tar: "application/x-tar",
|
|
226
|
+
rar: "application/x-rar-compressed",
|
|
227
|
+
_7z: "application/x-7z-compressed",
|
|
228
|
+
bz2: "application/x-bzip2",
|
|
229
|
+
"7z": "application/x-7z-compressed",
|
|
230
|
+
doc: "application/msword",
|
|
231
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
232
|
+
xls: "application/vnd.ms-excel",
|
|
233
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
234
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
235
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
236
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
237
|
+
ods: "application/vnd.oasis.opendocument.spreadsheet",
|
|
238
|
+
// Programming/Data Formats
|
|
239
|
+
wasm: "application/wasm",
|
|
240
|
+
map: "application/json",
|
|
241
|
+
// Source maps
|
|
242
|
+
yaml: "application/yaml",
|
|
243
|
+
yml: "application/yaml",
|
|
244
|
+
proto: "text/plain",
|
|
245
|
+
graphql: "application/graphql",
|
|
246
|
+
// Security/Config Formats
|
|
247
|
+
pem: "application/x-pem-file",
|
|
248
|
+
cer: "application/pkix-cert",
|
|
249
|
+
crt: "application/x-x509-ca-cert",
|
|
250
|
+
key: "application/x-pem-file",
|
|
251
|
+
pfx: "application/x-pkcs12",
|
|
252
|
+
// 3D/Model Formats
|
|
253
|
+
glb: "model/gltf-binary",
|
|
254
|
+
gltf: "model/gltf+json",
|
|
255
|
+
obj: "model/obj",
|
|
256
|
+
stl: "model/stl",
|
|
257
|
+
// Virtual/Mixed Reality
|
|
258
|
+
usdz: "model/vnd.usdz+zip",
|
|
259
|
+
// System Formats
|
|
260
|
+
exe: "application/x-msdownload",
|
|
261
|
+
dmg: "application/x-apple-diskimage",
|
|
262
|
+
deb: "application/x-debian-package",
|
|
263
|
+
rpm: "application/x-redhat-package-manager",
|
|
264
|
+
apk: "application/vnd.android.package-archive",
|
|
265
|
+
// Special Web Formats
|
|
266
|
+
webmanifest: "application/manifest+json",
|
|
267
|
+
ics: "text/calendar",
|
|
268
|
+
vcf: "text/vcard",
|
|
269
|
+
warc: "application/warc",
|
|
270
|
+
atom: "application/atom+xml",
|
|
271
|
+
rss: "application/rss+xml",
|
|
272
|
+
// Executables and scripts
|
|
273
|
+
dll: "application/x-msdownload",
|
|
274
|
+
sh: "application/x-sh",
|
|
275
|
+
py: "text/x-python",
|
|
276
|
+
rb: "text/x-ruby",
|
|
277
|
+
pl: "text/x-perl",
|
|
278
|
+
php: "application/x-httpd-php",
|
|
279
|
+
// Miscellaneous
|
|
280
|
+
torrent: "application/x-bittorrent",
|
|
281
|
+
ipa: "application/vnd.iphone",
|
|
282
|
+
eps: "application/postscript",
|
|
283
|
+
ps: "application/postscript",
|
|
284
|
+
ai: "application/postscript",
|
|
285
|
+
swf: "application/x-shockwave-flash",
|
|
286
|
+
jar: "application/java-archive",
|
|
287
|
+
gcode: "text/x.gcode"
|
|
288
|
+
};
|
|
289
|
+
const defaultMimeType = "application/octet-stream";
|
|
290
|
+
async function getFiles(dir, basePath = "/", ref, option) {
|
|
291
|
+
const files = [];
|
|
292
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
293
|
+
if (runtime == "deno") {
|
|
294
|
+
for await (const entry of Deno.readDir(dir)) {
|
|
295
|
+
const path = `${dir}/${entry.name}`;
|
|
296
|
+
if (entry.isDirectory) {
|
|
297
|
+
files.push(
|
|
298
|
+
...await getFiles(path, `${basePath}/${entry.name}`, ref, option)
|
|
299
|
+
);
|
|
300
|
+
} else {
|
|
301
|
+
const x = `${basePath}/${entry.name}`;
|
|
302
|
+
files.push({
|
|
303
|
+
file: path,
|
|
304
|
+
path: x.replace(/\\/g, "/")
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
const fs = await import('fs/promises');
|
|
310
|
+
const path = await import('path');
|
|
311
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
const fullPath = path.join(dir, entry.name);
|
|
314
|
+
if (entry.isDirectory()) {
|
|
315
|
+
files.push(
|
|
316
|
+
...await getFiles(
|
|
317
|
+
fullPath,
|
|
318
|
+
`${basePath}/${entry.name}`,
|
|
319
|
+
ref,
|
|
320
|
+
option
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
} else {
|
|
324
|
+
const path2 = `${basePath}/${entry.name}`;
|
|
325
|
+
files.push({
|
|
326
|
+
file: fullPath,
|
|
327
|
+
path: path2.replace(/\\/g, "/")
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
files.forEach((r) => {
|
|
333
|
+
ref.get(r.path, (ctx2) => {
|
|
334
|
+
if (option.cacheControl) {
|
|
335
|
+
ctx2.headers.set("Cache-Control", option.cacheControl);
|
|
336
|
+
}
|
|
337
|
+
if (option.headers) {
|
|
338
|
+
ctx2.headers.add(option.headers);
|
|
339
|
+
}
|
|
340
|
+
return ctx2.sendFile(r.file);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
return files;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class TezResponse {
|
|
347
|
+
static json(body, ...args) {
|
|
348
|
+
let status = 200;
|
|
349
|
+
let headers = {
|
|
350
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
351
|
+
};
|
|
352
|
+
if (typeof args[0] === "number") {
|
|
353
|
+
status = args[0];
|
|
354
|
+
if (typeof args[1] === "object") {
|
|
355
|
+
headers = { ...headers, ...args[1] };
|
|
356
|
+
}
|
|
357
|
+
} else if (typeof args[0] === "object") {
|
|
358
|
+
headers = { ...headers, ...args[0] };
|
|
359
|
+
}
|
|
360
|
+
return new Response(JSON.stringify(body), {
|
|
361
|
+
status,
|
|
362
|
+
headers
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
static html(data, ...args) {
|
|
366
|
+
let status = 200;
|
|
367
|
+
let headers = {
|
|
368
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
369
|
+
};
|
|
370
|
+
if (typeof args[0] === "number") {
|
|
371
|
+
status = args[0];
|
|
372
|
+
if (typeof args[1] === "object") {
|
|
373
|
+
headers = { ...headers, ...args[1] };
|
|
374
|
+
}
|
|
375
|
+
} else if (typeof args[0] === "object") {
|
|
376
|
+
headers = { ...headers, ...args[0] };
|
|
377
|
+
}
|
|
378
|
+
return new Response(data, {
|
|
379
|
+
status,
|
|
380
|
+
headers
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
static text(data, ...args) {
|
|
384
|
+
let status = 200;
|
|
385
|
+
let headers = {
|
|
386
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
387
|
+
};
|
|
388
|
+
if (typeof args[0] === "number") {
|
|
389
|
+
status = args[0];
|
|
390
|
+
if (typeof args[1] === "object") {
|
|
391
|
+
headers = { ...headers, ...args[1] };
|
|
392
|
+
}
|
|
393
|
+
} else if (typeof args[0] === "object") {
|
|
394
|
+
headers = { ...headers, ...args[0] };
|
|
395
|
+
}
|
|
396
|
+
return new Response(data, {
|
|
397
|
+
status,
|
|
398
|
+
headers
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
static xml(data, ...args) {
|
|
402
|
+
let status = 200;
|
|
403
|
+
let headers = {
|
|
404
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
405
|
+
};
|
|
406
|
+
if (typeof args[0] === "number") {
|
|
407
|
+
status = args[0];
|
|
408
|
+
if (typeof args[1] === "object") {
|
|
409
|
+
headers = { ...headers, ...args[1] };
|
|
410
|
+
}
|
|
411
|
+
} else if (typeof args[0] === "object") {
|
|
412
|
+
headers = { ...headers, ...args[0] };
|
|
413
|
+
}
|
|
414
|
+
return new Response(data, {
|
|
415
|
+
status,
|
|
416
|
+
headers
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
static send(body, ...args) {
|
|
420
|
+
let status = 200;
|
|
421
|
+
let headers = {};
|
|
422
|
+
if (typeof args[0] === "number") {
|
|
423
|
+
status = args[0];
|
|
424
|
+
if (typeof args[1] === "object") {
|
|
425
|
+
headers = args[1];
|
|
426
|
+
}
|
|
427
|
+
} else if (typeof args[0] === "object") {
|
|
428
|
+
headers = args[0];
|
|
429
|
+
}
|
|
430
|
+
if (!headers["Content-Type"]) {
|
|
431
|
+
if (typeof body === "string") {
|
|
432
|
+
headers["Content-Type"] = "text/plain;";
|
|
433
|
+
} else if (typeof body === "object" && body !== null) {
|
|
434
|
+
headers["Content-Type"] = "application/json;";
|
|
435
|
+
body = JSON.stringify(body);
|
|
436
|
+
} else {
|
|
437
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return new Response(body, {
|
|
441
|
+
status,
|
|
442
|
+
headers
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Redirects to a given URL.
|
|
447
|
+
* @param url - The target URL.
|
|
448
|
+
* @param status - (Optional) HTTP status code (default: 302).
|
|
449
|
+
* @param headers - (Optional) Additional headers.
|
|
450
|
+
* @returns Response object with redirect.
|
|
451
|
+
*/
|
|
452
|
+
static redirect(url, status = 302, headers) {
|
|
453
|
+
return new Response(null, {
|
|
454
|
+
status,
|
|
455
|
+
headers: { Location: url }
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Handles file downloads.
|
|
460
|
+
* @param filePath - The path to the file.
|
|
461
|
+
* @param fileName - The name of the downloaded file.
|
|
462
|
+
* @returns Response object for file download.
|
|
463
|
+
*/
|
|
464
|
+
static async download(filePath, fileName) {
|
|
465
|
+
try {
|
|
466
|
+
let fileExists = false;
|
|
467
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
468
|
+
if (runtime === "node") {
|
|
469
|
+
const { existsSync } = await import('fs');
|
|
470
|
+
fileExists = existsSync(filePath);
|
|
471
|
+
} else if (runtime === "bun") {
|
|
472
|
+
fileExists = Bun.file(filePath).exists();
|
|
473
|
+
} else if (runtime === "deno") {
|
|
474
|
+
try {
|
|
475
|
+
await Deno.stat(filePath);
|
|
476
|
+
fileExists = true;
|
|
477
|
+
} catch {
|
|
478
|
+
fileExists = false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (!fileExists) {
|
|
482
|
+
throw Error("File not found");
|
|
483
|
+
}
|
|
484
|
+
let fileBuffer;
|
|
485
|
+
if (runtime === "node") {
|
|
486
|
+
const { readFileSync } = await import('fs');
|
|
487
|
+
fileBuffer = await readFileSync(filePath);
|
|
488
|
+
} else if (runtime === "bun") {
|
|
489
|
+
fileBuffer = await Bun.file(filePath).arrayBuffer().then((buf) => new Uint8Array(buf));
|
|
490
|
+
} else if (runtime === "deno") {
|
|
491
|
+
fileBuffer = await Deno.readFile(filePath);
|
|
492
|
+
}
|
|
493
|
+
return new Response(fileBuffer, {
|
|
494
|
+
status: 200,
|
|
495
|
+
headers: {
|
|
496
|
+
"Content-Disposition": `attachment; filename="${fileName}"`,
|
|
497
|
+
"Content-Type": "application/octet-stream",
|
|
498
|
+
"Content-Length": fileBuffer.byteLength.toString()
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
} catch (error) {
|
|
502
|
+
throw Error("Internal Server Error" + error?.message);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
static async sendFile(filePath, ...args) {
|
|
506
|
+
try {
|
|
507
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
508
|
+
const resolvedPath = filePath;
|
|
509
|
+
let fileExists = false;
|
|
510
|
+
if (runtime === "node") {
|
|
511
|
+
const { existsSync } = await import('fs');
|
|
512
|
+
fileExists = existsSync(resolvedPath);
|
|
513
|
+
} else if (runtime === "bun") {
|
|
514
|
+
fileExists = Bun.file(resolvedPath).exists();
|
|
515
|
+
} else if (runtime === "deno") {
|
|
516
|
+
try {
|
|
517
|
+
await Deno.stat(resolvedPath);
|
|
518
|
+
fileExists = true;
|
|
519
|
+
} catch {
|
|
520
|
+
fileExists = false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (!fileExists) {
|
|
524
|
+
throw Error("File not found");
|
|
525
|
+
}
|
|
526
|
+
let fileSize = 0;
|
|
527
|
+
if (runtime === "node") {
|
|
528
|
+
const { statSync } = await import('fs');
|
|
529
|
+
fileSize = statSync(resolvedPath).size;
|
|
530
|
+
} else if (runtime === "bun") {
|
|
531
|
+
fileSize = (await Bun.file(resolvedPath).arrayBuffer()).byteLength;
|
|
532
|
+
} else if (runtime === "deno") {
|
|
533
|
+
const fileInfo = await Deno.stat(resolvedPath);
|
|
534
|
+
fileSize = fileInfo.size;
|
|
535
|
+
}
|
|
536
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
537
|
+
const mimeType = mimeTypes[ext] || defaultMimeType;
|
|
538
|
+
let fileStream;
|
|
539
|
+
if (runtime === "node") {
|
|
540
|
+
const { createReadStream } = await import('fs');
|
|
541
|
+
fileStream = createReadStream(resolvedPath);
|
|
542
|
+
} else if (runtime === "bun") {
|
|
543
|
+
fileStream = Bun.file(resolvedPath).stream();
|
|
544
|
+
} else if (runtime === "deno") {
|
|
545
|
+
const file = await Deno.open(resolvedPath, { read: true });
|
|
546
|
+
fileStream = file.readable;
|
|
547
|
+
}
|
|
548
|
+
let headers = {
|
|
549
|
+
"Content-Type": mimeType,
|
|
550
|
+
"Content-Length": fileSize.toString()
|
|
551
|
+
};
|
|
552
|
+
let fileName = "";
|
|
553
|
+
if (typeof args[0] === "string") {
|
|
554
|
+
fileName = args[0];
|
|
555
|
+
if (typeof args[1] === "object") {
|
|
556
|
+
headers = { ...headers, ...args[1] };
|
|
557
|
+
}
|
|
558
|
+
} else if (typeof args[0] === "object") {
|
|
559
|
+
headers = { ...headers, ...args[0] };
|
|
560
|
+
}
|
|
561
|
+
if (fileName) {
|
|
562
|
+
headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
563
|
+
}
|
|
564
|
+
return new Response(fileStream, {
|
|
565
|
+
status: 200,
|
|
566
|
+
headers
|
|
567
|
+
});
|
|
568
|
+
} catch (error) {
|
|
569
|
+
throw Error("Internal Server Error" + error?.message);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
class CommonHandler {
|
|
575
|
+
/**
|
|
576
|
+
* Register a custom 404 handler for missing routes
|
|
577
|
+
* @param {Callback} callback - Handler function to execute when no route matches
|
|
578
|
+
* @returns {this} - Returns current instance for chaining
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* // Register a custom not-found handler
|
|
582
|
+
* app.notFound((ctx) => {
|
|
583
|
+
* ctx.status(404).text('Custom not found message');
|
|
584
|
+
* });
|
|
585
|
+
*/
|
|
586
|
+
notFound(callback) {
|
|
587
|
+
GlobalConfig.notFound = callback;
|
|
588
|
+
return this;
|
|
589
|
+
}
|
|
590
|
+
onError(callback) {
|
|
591
|
+
GlobalConfig.onError = callback;
|
|
592
|
+
return this;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function sanitizePathSplit(basePath, path) {
|
|
597
|
+
const parts = `${basePath}/${path}`.replace(/\\/g, "")?.split("/").filter(Boolean);
|
|
598
|
+
return parts;
|
|
599
|
+
}
|
|
600
|
+
function urlParse(url) {
|
|
601
|
+
const urlPattern = /^(?:(\w+):\/\/)?(?:([^:@]+)?(?::([^@]+))?@)?([^:/?#]+)?(?::(\d+))?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/;
|
|
602
|
+
let matches = url.match(urlPattern);
|
|
603
|
+
const [
|
|
604
|
+
_,
|
|
605
|
+
protocol,
|
|
606
|
+
username,
|
|
607
|
+
password,
|
|
608
|
+
hostname,
|
|
609
|
+
port,
|
|
610
|
+
path,
|
|
611
|
+
queryString,
|
|
612
|
+
hash
|
|
613
|
+
] = matches;
|
|
614
|
+
let origin = hostname;
|
|
615
|
+
if (protocol) {
|
|
616
|
+
origin = protocol + "://" + hostname;
|
|
617
|
+
}
|
|
618
|
+
if (port) {
|
|
619
|
+
origin = origin + ":" + port;
|
|
620
|
+
}
|
|
621
|
+
let p = path;
|
|
622
|
+
if (p?.endsWith("/")) p.slice(0, -1);
|
|
623
|
+
function query() {
|
|
624
|
+
if (queryString) {
|
|
625
|
+
const queryPart = decodeURIComponent(queryString);
|
|
626
|
+
const keyValuePairs = queryPart.split("&");
|
|
627
|
+
const paramsObj = keyValuePairs?.map(
|
|
628
|
+
(keyValue) => {
|
|
629
|
+
const [key, value] = keyValue.split("=");
|
|
630
|
+
return {
|
|
631
|
+
[key]: value
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
return paramsObj.reduce(function(total, value) {
|
|
636
|
+
return { ...total, ...value };
|
|
637
|
+
}, {});
|
|
638
|
+
} else {
|
|
639
|
+
return {};
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
pathname: p,
|
|
644
|
+
hash,
|
|
645
|
+
protocol,
|
|
646
|
+
origin,
|
|
647
|
+
username,
|
|
648
|
+
password,
|
|
649
|
+
hostname,
|
|
650
|
+
href: url,
|
|
651
|
+
port,
|
|
652
|
+
query: query()
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
class TriMiddleware {
|
|
657
|
+
children = /* @__PURE__ */ new Map();
|
|
658
|
+
middlewares = /* @__PURE__ */ new Set();
|
|
659
|
+
isOptional = false;
|
|
660
|
+
pathname;
|
|
661
|
+
constructor(pathname = "/") {
|
|
662
|
+
this.pathname = pathname;
|
|
663
|
+
if (GlobalConfig.allowDuplicateMw) {
|
|
664
|
+
this.middlewares = [];
|
|
665
|
+
} else {
|
|
666
|
+
this.middlewares = /* @__PURE__ */ new Set();
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
class MiddlewareConfigure extends CommonHandler {
|
|
671
|
+
triMiddlewares = new TriMiddleware();
|
|
672
|
+
basePath;
|
|
673
|
+
constructor(basePath = "/") {
|
|
674
|
+
super();
|
|
675
|
+
this.basePath = basePath;
|
|
676
|
+
}
|
|
677
|
+
addMiddleware(pathname, middlewares) {
|
|
678
|
+
const parts = sanitizePathSplit(this.basePath, pathname);
|
|
679
|
+
let node = this.triMiddlewares;
|
|
680
|
+
for (const part of parts) {
|
|
681
|
+
if (part.startsWith("*")) {
|
|
682
|
+
if (!node.children.has("*")) {
|
|
683
|
+
node.children.set("*", new TriMiddleware());
|
|
684
|
+
}
|
|
685
|
+
node = node.children.get("*");
|
|
686
|
+
} else if (part.startsWith(":")) {
|
|
687
|
+
const isOptional = part?.endsWith("?");
|
|
688
|
+
if (isOptional) {
|
|
689
|
+
node.isOptional = isOptional;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (!node.children.has(":")) {
|
|
693
|
+
node.children.set(":", new TriMiddleware());
|
|
694
|
+
}
|
|
695
|
+
node = node.children.get(":");
|
|
696
|
+
} else {
|
|
697
|
+
if (!node.children.has(part)) {
|
|
698
|
+
node.children.set(part, new TriMiddleware());
|
|
699
|
+
}
|
|
700
|
+
node = node.children.get(part);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (GlobalConfig.allowDuplicateMw) {
|
|
704
|
+
node.middlewares.push(...middlewares);
|
|
705
|
+
} else {
|
|
706
|
+
for (const middleware of middlewares) {
|
|
707
|
+
node.middlewares.add(middleware);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
class TrieRouter {
|
|
714
|
+
children = /* @__PURE__ */ new Map();
|
|
715
|
+
// handlers: Map<HTTPMethod, Callback<any>> = new Map();
|
|
716
|
+
handlers = /* @__PURE__ */ new Map();
|
|
717
|
+
pathname;
|
|
718
|
+
// isWildcard: boolean = false;
|
|
719
|
+
paramName;
|
|
720
|
+
isParam = false;
|
|
721
|
+
constructor(pathname = "/") {
|
|
722
|
+
this.children = /* @__PURE__ */ new Map();
|
|
723
|
+
this.pathname = pathname;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
class Router extends MiddlewareConfigure {
|
|
727
|
+
routers = /* @__PURE__ */ new Map();
|
|
728
|
+
env = {};
|
|
729
|
+
triRouter;
|
|
730
|
+
constructor({ basePath = "/", env = {} } = {}) {
|
|
731
|
+
super(basePath);
|
|
732
|
+
this.basePath = basePath;
|
|
733
|
+
this.env = { ...env };
|
|
734
|
+
this.triRouter = new TrieRouter(basePath);
|
|
735
|
+
this.get.bind(this);
|
|
736
|
+
this.post.bind(this);
|
|
737
|
+
this.put.bind(this);
|
|
738
|
+
this.delete.bind(this);
|
|
739
|
+
this.all.bind(this);
|
|
740
|
+
this.#routeAddTriNode.bind(this);
|
|
741
|
+
this.addRouter.bind(this);
|
|
742
|
+
this.group.bind(this);
|
|
743
|
+
}
|
|
744
|
+
static(...args) {
|
|
745
|
+
let route = "";
|
|
746
|
+
let dir;
|
|
747
|
+
let options = {};
|
|
748
|
+
switch (args.length) {
|
|
749
|
+
case 3:
|
|
750
|
+
[route, dir, options] = args;
|
|
751
|
+
break;
|
|
752
|
+
case 2:
|
|
753
|
+
if (typeof args[1] === "object") {
|
|
754
|
+
[dir, options] = args;
|
|
755
|
+
} else {
|
|
756
|
+
[route, dir] = args;
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
case 1:
|
|
760
|
+
[dir] = args;
|
|
761
|
+
break;
|
|
762
|
+
default:
|
|
763
|
+
throw new Error(
|
|
764
|
+
`\x1B[1;31m404 Not Found\x1B[0m \x1B[1;32mInvalid arguments\x1B[0m`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
getFiles(dir, route, this, options);
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
get(path, ...args) {
|
|
771
|
+
this.#registerRoute("GET", path, ...args);
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
post(path, ...args) {
|
|
775
|
+
this.#registerRoute("POST", path, ...args);
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
put(path, ...args) {
|
|
779
|
+
this.#registerRoute("PUT", path, ...args);
|
|
780
|
+
return this;
|
|
781
|
+
}
|
|
782
|
+
patch(path, ...args) {
|
|
783
|
+
this.#registerRoute("PATCH", path, ...args);
|
|
784
|
+
return this;
|
|
785
|
+
}
|
|
786
|
+
delete(path, ...args) {
|
|
787
|
+
this.#registerRoute("DELETE", path, ...args);
|
|
788
|
+
return this;
|
|
789
|
+
}
|
|
790
|
+
options(path, ...args) {
|
|
791
|
+
this.#registerRoute("OPTIONS", path, ...args);
|
|
792
|
+
return this;
|
|
793
|
+
}
|
|
794
|
+
head(path, ...args) {
|
|
795
|
+
this.#registerRoute("HEAD", path, ...args);
|
|
796
|
+
return this;
|
|
797
|
+
}
|
|
798
|
+
all(path, ...args) {
|
|
799
|
+
this.#registerRoute("ALL", path, ...args);
|
|
800
|
+
return this;
|
|
801
|
+
}
|
|
802
|
+
addRoute(method, path, ...args) {
|
|
803
|
+
this.#registerRoute(method, path, ...args);
|
|
804
|
+
return this;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Mount a sub-router at specific path prefix
|
|
808
|
+
* @param path - Base path for the sub-router
|
|
809
|
+
* @param router - Router instance to mount
|
|
810
|
+
* @returns Current instance for chaining
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* const apiRouter = new Router();
|
|
814
|
+
* apiRouter.get('/users', () => { ... });
|
|
815
|
+
* server.addRouter('/api', apiRouter);
|
|
816
|
+
*/
|
|
817
|
+
addRouter(path, router) {
|
|
818
|
+
return this.#routeAddTriNode(path, router);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Create route group with shared path prefix
|
|
822
|
+
* @param prefix - Path prefix for the group
|
|
823
|
+
* @param callback - Function that receives group-specific router
|
|
824
|
+
* @returns Current router instance for chaining
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* app.group('/v1', (group) => {
|
|
828
|
+
* group.get('/users', v1UserHandler);
|
|
829
|
+
* });
|
|
830
|
+
*/
|
|
831
|
+
group(prefix, callback) {
|
|
832
|
+
const router = new Router({
|
|
833
|
+
basePath: prefix
|
|
834
|
+
// env: this.env
|
|
835
|
+
});
|
|
836
|
+
callback(router);
|
|
837
|
+
this.#routeAddTriNode("/", router);
|
|
838
|
+
return this;
|
|
839
|
+
}
|
|
840
|
+
use(...args) {
|
|
841
|
+
let path = "/";
|
|
842
|
+
let middlewares = [];
|
|
843
|
+
let router;
|
|
844
|
+
if (typeof args[0] === "string") {
|
|
845
|
+
path = args[0];
|
|
846
|
+
if (Array.isArray(args[1])) {
|
|
847
|
+
middlewares = args[1];
|
|
848
|
+
router = args[2];
|
|
849
|
+
} else if (typeof args[1] === "function") {
|
|
850
|
+
middlewares = [args[1]];
|
|
851
|
+
router = args[2];
|
|
852
|
+
} else {
|
|
853
|
+
router = args[1];
|
|
854
|
+
}
|
|
855
|
+
} else if (typeof args[0] === "function") {
|
|
856
|
+
if (args.length === 1) {
|
|
857
|
+
middlewares = [args[0]];
|
|
858
|
+
} else {
|
|
859
|
+
middlewares = [args[0]];
|
|
860
|
+
router = args[1];
|
|
861
|
+
}
|
|
862
|
+
} else if (Array.isArray(args[0])) {
|
|
863
|
+
middlewares = args[0];
|
|
864
|
+
router = args[1];
|
|
865
|
+
} else if (args[0] instanceof Router) {
|
|
866
|
+
router = args[0];
|
|
867
|
+
}
|
|
868
|
+
this.#addRouteMiddleware(path, middlewares);
|
|
869
|
+
if (router && router instanceof Router) {
|
|
870
|
+
this.addRouter(path, router);
|
|
871
|
+
}
|
|
872
|
+
return this;
|
|
873
|
+
}
|
|
874
|
+
// Other HTTP methods (PUT, DELETE, etc.) can be added similarly
|
|
875
|
+
#registerRoute(method, path, ...args) {
|
|
876
|
+
if (args.length === 0) {
|
|
877
|
+
throw new Error("At least one handler is required.");
|
|
878
|
+
}
|
|
879
|
+
let middlewares = [];
|
|
880
|
+
let callback;
|
|
881
|
+
if (args.length > 1) {
|
|
882
|
+
if (Array.isArray(args[0])) {
|
|
883
|
+
middlewares = args[0];
|
|
884
|
+
} else if (typeof args[0] === "function") {
|
|
885
|
+
middlewares = [args[0]];
|
|
886
|
+
}
|
|
887
|
+
callback = args[args.length - 1];
|
|
888
|
+
} else {
|
|
889
|
+
callback = args[0];
|
|
890
|
+
}
|
|
891
|
+
if (typeof callback !== "function") {
|
|
892
|
+
throw new Error("Route callback function is missing or invalid.");
|
|
893
|
+
}
|
|
894
|
+
if (!middlewares.every((middleware) => typeof middleware === "function")) {
|
|
895
|
+
throw new Error(
|
|
896
|
+
"Middleware must be a function or an array of functions."
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
this.#addRoute(method, path, callback, middlewares);
|
|
900
|
+
}
|
|
901
|
+
#addRoute(method, path, callback, middlewares) {
|
|
902
|
+
const parts = sanitizePathSplit(this.basePath, path);
|
|
903
|
+
let finalMiddleware = middlewares;
|
|
904
|
+
if (!GlobalConfig.allowDuplicateMw) {
|
|
905
|
+
finalMiddleware = new Set(middlewares);
|
|
906
|
+
}
|
|
907
|
+
let p = parts.join("/");
|
|
908
|
+
if (/(\/\*|\?)/.test(p)) {
|
|
909
|
+
let handler = this.routers.get(p);
|
|
910
|
+
if (!handler) {
|
|
911
|
+
handler = /* @__PURE__ */ new Map();
|
|
912
|
+
handler.set(method, {
|
|
913
|
+
callback,
|
|
914
|
+
middlewares: finalMiddleware
|
|
915
|
+
});
|
|
916
|
+
return this.routers.set(p, handler);
|
|
917
|
+
}
|
|
918
|
+
if (!GlobalConfig.overwriteMethod && handler.has(method)) return;
|
|
919
|
+
return handler.set(method, { callback, middlewares: finalMiddleware });
|
|
920
|
+
}
|
|
921
|
+
let node = this.triRouter;
|
|
922
|
+
for (const part of parts) {
|
|
923
|
+
if (part.startsWith(":")) {
|
|
924
|
+
if (!node.children.has(":")) {
|
|
925
|
+
node.children.set(":", new TrieRouter());
|
|
926
|
+
}
|
|
927
|
+
node = node.children.get(":");
|
|
928
|
+
node.isParam = true;
|
|
929
|
+
if (!node.paramName) {
|
|
930
|
+
node.paramName = part.slice(1);
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
if (!node.children.has(part)) {
|
|
934
|
+
node.children.set(part, new TrieRouter());
|
|
935
|
+
}
|
|
936
|
+
node = node.children.get(part);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (!GlobalConfig.overwriteMethod && node.handlers.has(method)) return;
|
|
940
|
+
node.handlers.set(method, {
|
|
941
|
+
callback,
|
|
942
|
+
middlewares: finalMiddleware
|
|
943
|
+
});
|
|
944
|
+
node.pathname = path;
|
|
945
|
+
}
|
|
946
|
+
#addRouteMiddleware(path, middlewareFunctions) {
|
|
947
|
+
this.addMiddleware(path, middlewareFunctions);
|
|
948
|
+
}
|
|
949
|
+
#routeAddTriNode(path, router) {
|
|
950
|
+
this.env = { ...this.env, ...router.env };
|
|
951
|
+
if (!(router instanceof Router)) {
|
|
952
|
+
throw new Error("Router instance is required.");
|
|
953
|
+
}
|
|
954
|
+
const parts = sanitizePathSplit(this.basePath, path);
|
|
955
|
+
if (router.routers.size) {
|
|
956
|
+
for (const [segment, handlers] of router.routers) {
|
|
957
|
+
let path2 = parts.length ? parts.join("/") + "/" + segment : segment;
|
|
958
|
+
if (this.routers.has(path2)) {
|
|
959
|
+
const baseRouter = this.routers.get(path2);
|
|
960
|
+
for (const [method, handler] of handlers) {
|
|
961
|
+
if (!GlobalConfig.overwriteMethod && baseRouter.has(method)) return;
|
|
962
|
+
baseRouter.set(method, handler);
|
|
963
|
+
}
|
|
964
|
+
} else {
|
|
965
|
+
this.routers.set(path2, new Map(handlers));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
let rootNode = this.triRouter;
|
|
970
|
+
let rootMiddlewares = this.triMiddlewares;
|
|
971
|
+
if (parts.length == 0) {
|
|
972
|
+
this.#addMiddlewareHandlerIDsTriNode(rootNode, rootMiddlewares, router);
|
|
973
|
+
} else {
|
|
974
|
+
for (const part of parts) {
|
|
975
|
+
if (part.startsWith(":")) {
|
|
976
|
+
if (!rootNode.children.has(":")) {
|
|
977
|
+
rootNode.children.set(":", new TrieRouter());
|
|
978
|
+
}
|
|
979
|
+
rootNode = rootNode.children.get(":");
|
|
980
|
+
rootNode.isParam = true;
|
|
981
|
+
if (!rootNode.paramName) {
|
|
982
|
+
rootNode.paramName = part.slice(1);
|
|
983
|
+
}
|
|
984
|
+
} else {
|
|
985
|
+
if (!rootNode.children.has(part)) {
|
|
986
|
+
rootNode.children.set(part, new TrieRouter());
|
|
987
|
+
}
|
|
988
|
+
rootNode = rootNode.children.get(part);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
for (const part of parts) {
|
|
992
|
+
if (part.startsWith("*")) {
|
|
993
|
+
if (!rootMiddlewares.children.has("*")) {
|
|
994
|
+
rootMiddlewares.children.set("*", new TriMiddleware());
|
|
995
|
+
}
|
|
996
|
+
rootMiddlewares = rootMiddlewares.children.get("*");
|
|
997
|
+
} else if (part.startsWith(":")) {
|
|
998
|
+
const isOptional = part?.endsWith("?");
|
|
999
|
+
if (isOptional) {
|
|
1000
|
+
rootMiddlewares.isOptional = isOptional;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (!rootMiddlewares.children.has(":")) {
|
|
1004
|
+
rootMiddlewares.children.set(":", new TriMiddleware());
|
|
1005
|
+
}
|
|
1006
|
+
rootMiddlewares = rootMiddlewares.children.get(":");
|
|
1007
|
+
} else {
|
|
1008
|
+
if (!rootMiddlewares.children.has(part)) {
|
|
1009
|
+
rootMiddlewares.children.set(part, new TriMiddleware());
|
|
1010
|
+
}
|
|
1011
|
+
rootMiddlewares = rootMiddlewares.children.get(part);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
this.#addMiddlewareHandlerIDsTriNode(rootNode, rootMiddlewares, router);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
#addMiddlewareHandlerIDsTriNode(rootNode, rootMiddlewares, router) {
|
|
1018
|
+
function addSubRouter(children, node) {
|
|
1019
|
+
let rtN = node;
|
|
1020
|
+
for (const element of children) {
|
|
1021
|
+
const pathSegment = element[0];
|
|
1022
|
+
const subRouter = element[1];
|
|
1023
|
+
if (rtN.children.has(pathSegment)) {
|
|
1024
|
+
let findNode = rtN.children.get(pathSegment);
|
|
1025
|
+
for (const [method, handlers] of subRouter.handlers) {
|
|
1026
|
+
if (!GlobalConfig.overwriteMethod && node.handlers.has(method))
|
|
1027
|
+
return;
|
|
1028
|
+
findNode.handlers.set(method, handlers);
|
|
1029
|
+
}
|
|
1030
|
+
if (subRouter.children.size) {
|
|
1031
|
+
addSubRouter(subRouter.children, findNode);
|
|
1032
|
+
}
|
|
1033
|
+
} else {
|
|
1034
|
+
rtN.children.set(pathSegment, subRouter);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
let routerNode = router.triRouter;
|
|
1039
|
+
const routerMiddlewares = router.triMiddlewares;
|
|
1040
|
+
for (const [method, handlers] of routerNode.handlers) {
|
|
1041
|
+
if (!GlobalConfig.overwriteMethod) {
|
|
1042
|
+
rootNode.handlers.set(method, handlers);
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (!rootNode.handlers.has(method)) {
|
|
1046
|
+
rootNode.handlers.set(method, handlers);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (routerNode.children.size > 0) {
|
|
1050
|
+
addSubRouter(routerNode.children, rootNode);
|
|
1051
|
+
}
|
|
1052
|
+
function addMiddleware(children, node) {
|
|
1053
|
+
let n = node;
|
|
1054
|
+
for (const [path, middlewareNode] of children) {
|
|
1055
|
+
if (n.children.has(path)) {
|
|
1056
|
+
let findNode = n.children.get(path);
|
|
1057
|
+
if (GlobalConfig.allowDuplicateMw) {
|
|
1058
|
+
findNode.middlewares.push(
|
|
1059
|
+
...middlewareNode.middlewares
|
|
1060
|
+
);
|
|
1061
|
+
} else {
|
|
1062
|
+
for (const mw of middlewareNode.middlewares) {
|
|
1063
|
+
if (findNode.middlewares.has(mw)) {
|
|
1064
|
+
middlewareNode.middlewares.delete(mw);
|
|
1065
|
+
}
|
|
1066
|
+
findNode.middlewares.add(mw);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (middlewareNode.children.size) {
|
|
1070
|
+
addMiddleware(middlewareNode.children, findNode);
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
n.children.set(path, middlewareNode);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (GlobalConfig.allowDuplicateMw) {
|
|
1078
|
+
rootMiddlewares.middlewares.push(
|
|
1079
|
+
...routerMiddlewares.middlewares
|
|
1080
|
+
);
|
|
1081
|
+
} else {
|
|
1082
|
+
for (const mw of routerMiddlewares.middlewares) {
|
|
1083
|
+
if (rootMiddlewares.middlewares.has(mw)) {
|
|
1084
|
+
routerMiddlewares.middlewares.delete(mw);
|
|
1085
|
+
}
|
|
1086
|
+
rootMiddlewares.middlewares.add(mw);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (routerMiddlewares.children.size > 0) {
|
|
1090
|
+
addMiddleware(routerMiddlewares.children, rootMiddlewares);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
class HeadersParser {
|
|
1096
|
+
headers = /* @__PURE__ */ new Map();
|
|
1097
|
+
// Lowercase keys for case-insensitivity
|
|
1098
|
+
constructor(init) {
|
|
1099
|
+
if (init) {
|
|
1100
|
+
this.add(init);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Adds multiple headers to the parser.
|
|
1105
|
+
* @param headers - Headers as an array of tuples or a record object.
|
|
1106
|
+
*/
|
|
1107
|
+
add(headers) {
|
|
1108
|
+
if (Array.isArray(headers)) {
|
|
1109
|
+
for (const [key, value] of headers) {
|
|
1110
|
+
this.set(key, value);
|
|
1111
|
+
}
|
|
1112
|
+
} else if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
1113
|
+
for (const [key, value] of headers.entries()) {
|
|
1114
|
+
this.set(key, value);
|
|
1115
|
+
}
|
|
1116
|
+
} else if (typeof headers === "object") {
|
|
1117
|
+
for (const key in headers) {
|
|
1118
|
+
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
|
1119
|
+
this.set(key, headers[key]);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return this;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Sets a header value.
|
|
1127
|
+
* @param key - Header name.
|
|
1128
|
+
* @param value - Header value(s).
|
|
1129
|
+
*/
|
|
1130
|
+
set(key, value) {
|
|
1131
|
+
this.headers.set(key.toLowerCase(), Array.isArray(value) ? value : [value]);
|
|
1132
|
+
return this;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Retrieves the first value of a header.
|
|
1136
|
+
* @param key - Header name.
|
|
1137
|
+
* @returns The first header value or undefined if not found.
|
|
1138
|
+
*/
|
|
1139
|
+
get(key) {
|
|
1140
|
+
const values = this.headers.get(key.toLowerCase());
|
|
1141
|
+
return values ? values[0] : void 0;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Retrieves all values of a header.
|
|
1145
|
+
* @param key - Header name.
|
|
1146
|
+
* @returns An array of header values.
|
|
1147
|
+
*/
|
|
1148
|
+
getAll(key) {
|
|
1149
|
+
return this.headers.get(key.toLowerCase()) || [];
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Checks if a header exists.
|
|
1153
|
+
* @param key - Header name.
|
|
1154
|
+
* @returns True if the header exists, false otherwise.
|
|
1155
|
+
*/
|
|
1156
|
+
has(key) {
|
|
1157
|
+
return this.headers.has(key.toLowerCase());
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Deletes a header.
|
|
1161
|
+
* @param key - Header name.
|
|
1162
|
+
* @returns True if deleted successfully, false otherwise.
|
|
1163
|
+
*/
|
|
1164
|
+
delete(key) {
|
|
1165
|
+
return this.headers.delete(key.toLowerCase());
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Appends a value to an existing header or creates a new one.
|
|
1169
|
+
* @param key - Header name.
|
|
1170
|
+
* @param value - Value to append.
|
|
1171
|
+
*/
|
|
1172
|
+
append(key, value) {
|
|
1173
|
+
const lowerKey = key.toLowerCase();
|
|
1174
|
+
if (this.headers.has(lowerKey)) {
|
|
1175
|
+
this.headers.get(lowerKey).push(value);
|
|
1176
|
+
} else {
|
|
1177
|
+
this.headers.set(lowerKey, [value]);
|
|
1178
|
+
}
|
|
1179
|
+
return this;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Returns an iterator over header entries.
|
|
1183
|
+
* @returns IterableIterator of header key-value pairs.
|
|
1184
|
+
*/
|
|
1185
|
+
entries() {
|
|
1186
|
+
return this.headers.entries();
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Returns an iterator over header keys.
|
|
1190
|
+
* @returns IterableIterator of header names.
|
|
1191
|
+
*/
|
|
1192
|
+
keys() {
|
|
1193
|
+
return this.headers.keys();
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Returns an iterator over header values.
|
|
1197
|
+
* @returns IterableIterator of header values arrays.
|
|
1198
|
+
*/
|
|
1199
|
+
values() {
|
|
1200
|
+
return this.headers.values();
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Iterates over headers and executes a callback function.
|
|
1204
|
+
* @param callback - Function to execute for each header.
|
|
1205
|
+
*/
|
|
1206
|
+
forEach(callback) {
|
|
1207
|
+
for (const [key, value] of this.headers) {
|
|
1208
|
+
callback(value, key);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Converts headers into a plain object.
|
|
1213
|
+
* @returns A record of headers where single-value headers are returned as a string.
|
|
1214
|
+
*/
|
|
1215
|
+
toObject() {
|
|
1216
|
+
const obj = {};
|
|
1217
|
+
for (const [key, value] of this.headers.entries()) {
|
|
1218
|
+
obj[key] = value.length > 1 ? value : value[0];
|
|
1219
|
+
}
|
|
1220
|
+
return obj;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
Object.defineProperty(HeadersParser, "name", { value: "Headers" });
|
|
1224
|
+
|
|
1225
|
+
async function parseJsonBody(req) {
|
|
1226
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
1227
|
+
if (runtime === "node") {
|
|
1228
|
+
return new Promise((resolve, reject) => {
|
|
1229
|
+
let body = "";
|
|
1230
|
+
req.on("data", (chunk) => {
|
|
1231
|
+
body += chunk.toString();
|
|
1232
|
+
});
|
|
1233
|
+
req.on("end", () => {
|
|
1234
|
+
try {
|
|
1235
|
+
resolve(JSON.parse(body));
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
reject(new Error("Invalid JSON format"));
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
} else if (runtime === "deno" || runtime === "bun") {
|
|
1242
|
+
return await req.json();
|
|
1243
|
+
} else {
|
|
1244
|
+
throw new Error("Unsupported environment for multipart parsing");
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
async function parseTextBody(req) {
|
|
1248
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
1249
|
+
if (runtime === "node") {
|
|
1250
|
+
return new Promise((resolve, reject) => {
|
|
1251
|
+
let body = "";
|
|
1252
|
+
req.on("data", (chunk) => {
|
|
1253
|
+
body += chunk.toString();
|
|
1254
|
+
});
|
|
1255
|
+
req.on("end", () => {
|
|
1256
|
+
try {
|
|
1257
|
+
resolve(body);
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
reject(new Error("Invalid JSON format"));
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
});
|
|
1263
|
+
} else if (runtime === "deno" || runtime === "bun") {
|
|
1264
|
+
return await req.text();
|
|
1265
|
+
} else {
|
|
1266
|
+
throw new Error("Unsupported environment for multipart parsing");
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function parseUrlEncodedBody(req) {
|
|
1270
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
1271
|
+
if (runtime === "node") {
|
|
1272
|
+
return new Promise((resolve, reject) => {
|
|
1273
|
+
let body = "";
|
|
1274
|
+
req.on("data", (chunk) => {
|
|
1275
|
+
body += chunk.toString("binary");
|
|
1276
|
+
});
|
|
1277
|
+
req.on("end", () => {
|
|
1278
|
+
try {
|
|
1279
|
+
const pairs = body.split("&");
|
|
1280
|
+
const formData = {};
|
|
1281
|
+
pairs.forEach((pair) => {
|
|
1282
|
+
const [key, value] = pair.split("=");
|
|
1283
|
+
formData[decodeURIComponent(key)] = decodeURIComponent(value || "");
|
|
1284
|
+
});
|
|
1285
|
+
resolve(formData);
|
|
1286
|
+
} catch {
|
|
1287
|
+
reject(new Error("Invalid x-www-form-urlencoded format"));
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
} else if (runtime === "deno" || runtime === "bun") {
|
|
1292
|
+
const formData = await req.formData();
|
|
1293
|
+
const result = {};
|
|
1294
|
+
for (const [key, value] of formData.entries()) {
|
|
1295
|
+
result[key] = value;
|
|
1296
|
+
}
|
|
1297
|
+
return result;
|
|
1298
|
+
} else {
|
|
1299
|
+
throw new Error("Unsupported environment for multipart parsing");
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
async function parseMultipartBody(req, boundary, options) {
|
|
1303
|
+
const runtime = EnvironmentDetector.getEnvironment;
|
|
1304
|
+
options?.sanitized;
|
|
1305
|
+
if (runtime === "node") {
|
|
1306
|
+
return new Promise((resolve, reject) => {
|
|
1307
|
+
let body = "";
|
|
1308
|
+
req.on("data", (chunk) => {
|
|
1309
|
+
body += chunk.toString("binary");
|
|
1310
|
+
});
|
|
1311
|
+
req.on("end", () => {
|
|
1312
|
+
try {
|
|
1313
|
+
const formDataField = {};
|
|
1314
|
+
const formDataFieldParts = body.split("----------------------------");
|
|
1315
|
+
formDataFieldParts.forEach((part) => {
|
|
1316
|
+
const match = part.match(/name="(.*)"\r\n\r\n(.*)\r\n/);
|
|
1317
|
+
if (match && match.length === 3) {
|
|
1318
|
+
const name = match[1];
|
|
1319
|
+
const value = match[2];
|
|
1320
|
+
formDataField[name] = value;
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
const parts = body.split(`--${boundary}`);
|
|
1324
|
+
for (const part of parts) {
|
|
1325
|
+
if (part.includes("filename")) {
|
|
1326
|
+
const filenameMatch = part.match(/filename="([^"]+)"/);
|
|
1327
|
+
const fieldNameMatch = part.match(/name="([^"]+)"/);
|
|
1328
|
+
const contentTypeMatch = part.match(/Content-Type: ([^\r\n]+)/);
|
|
1329
|
+
if (filenameMatch && fieldNameMatch && contentTypeMatch) {
|
|
1330
|
+
let filename = filenameMatch[1];
|
|
1331
|
+
const fieldName = fieldNameMatch[1];
|
|
1332
|
+
const contentType = contentTypeMatch[1];
|
|
1333
|
+
if (options?.sanitized) {
|
|
1334
|
+
filename = `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
|
|
1335
|
+
}
|
|
1336
|
+
if (Array.isArray(options?.allowedTypes) && !options.allowedTypes?.includes(contentType)) {
|
|
1337
|
+
reject(
|
|
1338
|
+
`Invalid file type: "${contentType}". Allowed types: ${options.allowedTypes.join(", ")}`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
const fileContentStartIndex = part.indexOf("\r\n\r\n") + 4;
|
|
1342
|
+
const fileContent = Buffer.from(
|
|
1343
|
+
part.substring(fileContentStartIndex),
|
|
1344
|
+
"binary"
|
|
1345
|
+
);
|
|
1346
|
+
const arrayBuffer = fileContent.buffer.slice(
|
|
1347
|
+
fileContent.byteOffset,
|
|
1348
|
+
fileContent.byteOffset + fileContent.byteLength
|
|
1349
|
+
);
|
|
1350
|
+
if (typeof options?.maxSize !== "undefined" && fileContent.byteLength > options.maxSize) {
|
|
1351
|
+
reject(
|
|
1352
|
+
`File size exceeds the limit: ${fileContent.byteLength} bytes (Max: ${options.maxSize} bytes)`
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
const file = new File([arrayBuffer], filename, {
|
|
1356
|
+
type: contentType
|
|
1357
|
+
});
|
|
1358
|
+
if (typeof options?.maxFiles != "undefined" && options.maxFiles == 0) {
|
|
1359
|
+
reject(
|
|
1360
|
+
`Field "${fieldName}" exceeds the maximum allowed file count of ${options.maxFiles}.`
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
if (formDataField[fieldName]) {
|
|
1364
|
+
if (Array.isArray(formDataField[fieldName])) {
|
|
1365
|
+
if (typeof options?.maxFiles != "undefined" && formDataField[fieldName]?.length >= options.maxFiles) {
|
|
1366
|
+
reject(
|
|
1367
|
+
`Field "${fieldName}" exceeds the maximum allowed file count of ${options.maxFiles}.`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
formDataField[fieldName].push(file);
|
|
1371
|
+
} else {
|
|
1372
|
+
formDataField[fieldName] = [formDataField[fieldName], file];
|
|
1373
|
+
}
|
|
1374
|
+
} else {
|
|
1375
|
+
formDataField[fieldName] = file;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
resolve(formDataField);
|
|
1381
|
+
} catch {
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
} else if (runtime === "deno" || runtime === "bun") {
|
|
1386
|
+
const formData = await req.formData();
|
|
1387
|
+
const result = {};
|
|
1388
|
+
for (const [key, value] of formData.entries()) {
|
|
1389
|
+
let val = value;
|
|
1390
|
+
if (val instanceof File && typeof options == "object") {
|
|
1391
|
+
let filename = val.name;
|
|
1392
|
+
if (options?.sanitized) {
|
|
1393
|
+
filename = `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
|
|
1394
|
+
}
|
|
1395
|
+
if (Array.isArray(options?.allowedTypes) && !options.allowedTypes?.includes(val.type)) {
|
|
1396
|
+
throw new Error(
|
|
1397
|
+
`Invalid file type: "${val.type}". Allowed types: ${options.allowedTypes.join(", ")}`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
if (typeof options?.maxSize !== "undefined" && val.size > options.maxSize) {
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`File size exceeds the limit: ${val.size} bytes (Max: ${options.maxSize} bytes)`
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
if (typeof options?.maxFiles != "undefined" && options.maxFiles == 0) {
|
|
1406
|
+
throw new Error(
|
|
1407
|
+
`Field "${key}" exceeds the maximum allowed file count of ${options.maxFiles}.`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
val = new File([await val.arrayBuffer()], filename, {
|
|
1411
|
+
type: val.type
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
if (result[key]) {
|
|
1415
|
+
if (Array.isArray(result[key])) {
|
|
1416
|
+
if (val instanceof File && typeof options?.maxFiles != "undefined" && result[key]?.length >= options.maxFiles) {
|
|
1417
|
+
throw new Error(
|
|
1418
|
+
`Field "${key}" exceeds the maximum allowed file count of ${options.maxFiles}.`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
result[key].push(val);
|
|
1422
|
+
} else {
|
|
1423
|
+
result[key] = [result[key], val];
|
|
1424
|
+
}
|
|
1425
|
+
} else {
|
|
1426
|
+
result[key] = val;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return result;
|
|
1430
|
+
} else {
|
|
1431
|
+
throw new Error("Unsupported environment for multipart parsing");
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
class Request {
|
|
1436
|
+
headers = new HeadersParser();
|
|
1437
|
+
/**
|
|
1438
|
+
* Full request URL including protocol and query string
|
|
1439
|
+
* @type {string}
|
|
1440
|
+
*/
|
|
1441
|
+
url;
|
|
1442
|
+
/**
|
|
1443
|
+
* HTTP request method (GET, POST, PUT, DELETE, etc.)
|
|
1444
|
+
* @type {HTTPMethod}
|
|
1445
|
+
*/
|
|
1446
|
+
method;
|
|
1447
|
+
/** Parsed URL reference containing components like query parameters, pathname, etc. */
|
|
1448
|
+
urlRef = {
|
|
1449
|
+
hash: void 0,
|
|
1450
|
+
protocol: void 0,
|
|
1451
|
+
origin: void 0,
|
|
1452
|
+
username: void 0,
|
|
1453
|
+
password: void 0,
|
|
1454
|
+
hostname: void 0,
|
|
1455
|
+
port: void 0,
|
|
1456
|
+
href: void 0,
|
|
1457
|
+
query: {},
|
|
1458
|
+
pathname: "/"
|
|
1459
|
+
};
|
|
1460
|
+
/** Query parameters extracted from the URL */
|
|
1461
|
+
query;
|
|
1462
|
+
#request;
|
|
1463
|
+
/**
|
|
1464
|
+
* Retrieve a parameter by name.
|
|
1465
|
+
* @param name - The parameter name.
|
|
1466
|
+
* @returns The parameter value if found, or undefined.
|
|
1467
|
+
*/
|
|
1468
|
+
params = {};
|
|
1469
|
+
// static statusText: string;
|
|
1470
|
+
// static bodyUsed: boolean;
|
|
1471
|
+
constructor(req, params) {
|
|
1472
|
+
this.headers = new HeadersParser(req?.headers);
|
|
1473
|
+
this.method = req?.method?.toUpperCase();
|
|
1474
|
+
this.params = params;
|
|
1475
|
+
this.#request = req;
|
|
1476
|
+
if (EnvironmentDetector.getEnvironment == "node") {
|
|
1477
|
+
const protocol = EnvironmentDetector.detectProtocol(req);
|
|
1478
|
+
const host = EnvironmentDetector.getHost(this.headers);
|
|
1479
|
+
this.url = `${protocol}://${host}${req.url}`;
|
|
1480
|
+
} else {
|
|
1481
|
+
this.url = req.url;
|
|
1482
|
+
}
|
|
1483
|
+
this.urlRef = urlParse(this.url);
|
|
1484
|
+
this.query = this.urlRef.query;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Parses the request body as plain text.
|
|
1488
|
+
* @returns {Promise<string>} The text content of the request body.
|
|
1489
|
+
*/
|
|
1490
|
+
async text() {
|
|
1491
|
+
return await parseTextBody(this.#request);
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Parses the request body as JSON.
|
|
1495
|
+
* @returns {Promise<Record<string, any>>} The parsed JSON object.
|
|
1496
|
+
* If the Content-Type is not 'application/json', it returns an empty object.
|
|
1497
|
+
*/
|
|
1498
|
+
async json() {
|
|
1499
|
+
const contentType = this.headers.get("content-type") || "";
|
|
1500
|
+
if (contentType.includes("application/json")) {
|
|
1501
|
+
return await parseJsonBody(this.#request);
|
|
1502
|
+
} else {
|
|
1503
|
+
return {};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Parses the request body based on Content-Type.
|
|
1508
|
+
* Supports:
|
|
1509
|
+
* - application/json → JSON parsing
|
|
1510
|
+
* - application/x-www-form-urlencoded → URL-encoded form parsing
|
|
1511
|
+
* - multipart/form-data → Multipart form-data parsing (for file uploads)
|
|
1512
|
+
* @returns {Promise<Record<string, any>>} The parsed form data as an object.
|
|
1513
|
+
* @throws {Error} If the Content-Type is missing or invalid.
|
|
1514
|
+
*/
|
|
1515
|
+
async formData(options) {
|
|
1516
|
+
const contentType = this.headers.get("content-type") || "";
|
|
1517
|
+
if (!contentType) {
|
|
1518
|
+
throw Error("Invalid Content-Type");
|
|
1519
|
+
}
|
|
1520
|
+
if (contentType.includes("application/json")) {
|
|
1521
|
+
return await parseJsonBody(this.#request);
|
|
1522
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1523
|
+
return parseUrlEncodedBody(this.#request);
|
|
1524
|
+
} else if (contentType.includes("multipart/form-data")) {
|
|
1525
|
+
const boundary = contentType?.split("; ")?.[1]?.split("=")?.[1];
|
|
1526
|
+
if (!boundary) {
|
|
1527
|
+
throw Error("Boundary not found");
|
|
1528
|
+
}
|
|
1529
|
+
return await parseMultipartBody(this.#request, boundary, options);
|
|
1530
|
+
} else {
|
|
1531
|
+
return {};
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
class State {
|
|
1537
|
+
state;
|
|
1538
|
+
constructor() {
|
|
1539
|
+
this.state = /* @__PURE__ */ new Map();
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Store a value with a specific key.
|
|
1543
|
+
* @param key - The key for the value.
|
|
1544
|
+
* @param value - The value to be stored.
|
|
1545
|
+
*/
|
|
1546
|
+
set(key, value) {
|
|
1547
|
+
this.state.set(key, value);
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Retrieve a value by key.
|
|
1551
|
+
* @param key - The key of the value to retrieve.
|
|
1552
|
+
* @returns The stored value or undefined if not found.
|
|
1553
|
+
*/
|
|
1554
|
+
get(key) {
|
|
1555
|
+
return this.state.get(key);
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Delete a key from storage.
|
|
1559
|
+
* @param key - The key to remove.
|
|
1560
|
+
* @returns True if the key was deleted, false otherwise.
|
|
1561
|
+
*/
|
|
1562
|
+
delete(key) {
|
|
1563
|
+
return this.state.delete(key);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Check if a key exists in the storage.
|
|
1567
|
+
* @param key - The key to check.
|
|
1568
|
+
* @returns True if the key exists, false otherwise.
|
|
1569
|
+
*/
|
|
1570
|
+
has(key) {
|
|
1571
|
+
return this.state.has(key);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Get an array of all stored keys.
|
|
1575
|
+
* @returns An array of keys.
|
|
1576
|
+
*/
|
|
1577
|
+
keys() {
|
|
1578
|
+
return Array.from(this.state.keys());
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Get an array of all stored values.
|
|
1582
|
+
* @returns An array of values.
|
|
1583
|
+
*/
|
|
1584
|
+
values() {
|
|
1585
|
+
return Array.from(this.state.values());
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Get an array of all key-value pairs.
|
|
1589
|
+
* @returns An array of [key, value] tuples.
|
|
1590
|
+
*/
|
|
1591
|
+
entries() {
|
|
1592
|
+
return Array.from(this.state.entries());
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Remove all entries from storage.
|
|
1596
|
+
*/
|
|
1597
|
+
clear() {
|
|
1598
|
+
this.state.clear();
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const httpStatusMap = {
|
|
1603
|
+
100: "Continue",
|
|
1604
|
+
101: "Switching Protocols",
|
|
1605
|
+
102: "Processing",
|
|
1606
|
+
103: "Early Hints",
|
|
1607
|
+
200: "OK",
|
|
1608
|
+
201: "Created",
|
|
1609
|
+
202: "Accepted",
|
|
1610
|
+
203: "Non-Authoritative Information",
|
|
1611
|
+
204: "No Content",
|
|
1612
|
+
205: "Reset Content",
|
|
1613
|
+
206: "Partial Content",
|
|
1614
|
+
207: "Multi-Status",
|
|
1615
|
+
208: "Already Reported",
|
|
1616
|
+
226: "IM Used",
|
|
1617
|
+
300: "Multiple Choices",
|
|
1618
|
+
301: "Moved Permanently",
|
|
1619
|
+
302: "Found",
|
|
1620
|
+
303: "See Other",
|
|
1621
|
+
304: "Not Modified",
|
|
1622
|
+
305: "Use Proxy",
|
|
1623
|
+
306: "Switch Proxy",
|
|
1624
|
+
307: "Temporary Redirect",
|
|
1625
|
+
308: "Permanent Redirect",
|
|
1626
|
+
400: "Bad Request",
|
|
1627
|
+
401: "Unauthorized",
|
|
1628
|
+
402: "Payment Required",
|
|
1629
|
+
403: "Forbidden",
|
|
1630
|
+
404: "Not Found",
|
|
1631
|
+
405: "Method Not Allowed",
|
|
1632
|
+
406: "Not Acceptable",
|
|
1633
|
+
407: "Proxy Authentication Required",
|
|
1634
|
+
408: "Request Timeout",
|
|
1635
|
+
409: "Conflict",
|
|
1636
|
+
410: "Gone",
|
|
1637
|
+
411: "Length Required",
|
|
1638
|
+
412: "Precondition Failed",
|
|
1639
|
+
413: "Payload Too Large",
|
|
1640
|
+
414: "URI Too Long",
|
|
1641
|
+
415: "Unsupported Media Type",
|
|
1642
|
+
416: "Range Not Satisfiable",
|
|
1643
|
+
417: "Expectation Failed",
|
|
1644
|
+
418: "I'm a Teapot",
|
|
1645
|
+
421: "Misdirected Request",
|
|
1646
|
+
422: "Unprocessable Entity",
|
|
1647
|
+
423: "Locked",
|
|
1648
|
+
424: "Failed Dependency",
|
|
1649
|
+
425: "Too Early",
|
|
1650
|
+
426: "Upgrade Required",
|
|
1651
|
+
428: "Precondition Required",
|
|
1652
|
+
429: "Too Many Requests",
|
|
1653
|
+
431: "Request Header Fields Too Large",
|
|
1654
|
+
451: "Unavailable For Legal Reasons",
|
|
1655
|
+
500: "Internal Server Error",
|
|
1656
|
+
501: "Not Implemented",
|
|
1657
|
+
502: "Bad Gateway",
|
|
1658
|
+
503: "Service Unavailable",
|
|
1659
|
+
504: "Gateway Timeout",
|
|
1660
|
+
505: "HTTP Version Not Supported",
|
|
1661
|
+
506: "Variant Also Negotiates",
|
|
1662
|
+
507: "Insufficient Storage",
|
|
1663
|
+
508: "Loop Detected",
|
|
1664
|
+
510: "Not Extended",
|
|
1665
|
+
511: "Network Authentication Required"
|
|
1666
|
+
};
|
|
1667
|
+
class Context {
|
|
1668
|
+
#rawRequest;
|
|
1669
|
+
/**
|
|
1670
|
+
* Environment variables and configuration
|
|
1671
|
+
* @type {object}
|
|
1672
|
+
*/
|
|
1673
|
+
env = {};
|
|
1674
|
+
/**
|
|
1675
|
+
* Parser for handling and manipulating HTTP headers
|
|
1676
|
+
* @type {HeadersParser}
|
|
1677
|
+
*/
|
|
1678
|
+
headers = new HeadersParser();
|
|
1679
|
+
// /**
|
|
1680
|
+
// * Parser for handling and manipulating HTTP response(Read Only)
|
|
1681
|
+
// * @type {Response}
|
|
1682
|
+
// */
|
|
1683
|
+
// public readonly res: Response = new Response();
|
|
1684
|
+
/**
|
|
1685
|
+
* Request path without query parameters
|
|
1686
|
+
* @type {string}
|
|
1687
|
+
*/
|
|
1688
|
+
pathname;
|
|
1689
|
+
/**
|
|
1690
|
+
* Full request URL including protocol and query string
|
|
1691
|
+
* @type {string}
|
|
1692
|
+
*/
|
|
1693
|
+
url;
|
|
1694
|
+
/**
|
|
1695
|
+
* HTTP request method (GET, POST, PUT, DELETE, etc.)
|
|
1696
|
+
* @type {HTTPMethod}
|
|
1697
|
+
*/
|
|
1698
|
+
method;
|
|
1699
|
+
#status = 200;
|
|
1700
|
+
/**
|
|
1701
|
+
* Public state container for application data
|
|
1702
|
+
* state storage for middleware and plugins
|
|
1703
|
+
* @type {State}
|
|
1704
|
+
*/
|
|
1705
|
+
state = new State();
|
|
1706
|
+
/**
|
|
1707
|
+
* URL parameters extracted from route
|
|
1708
|
+
* @private
|
|
1709
|
+
* @type {Record<string, any>}
|
|
1710
|
+
*/
|
|
1711
|
+
#params = {};
|
|
1712
|
+
// /**
|
|
1713
|
+
// * WebSocket connection instance (null until upgraded)
|
|
1714
|
+
// * @type {WebSocket | null}
|
|
1715
|
+
// */
|
|
1716
|
+
// ws: WebSocket | null = null;
|
|
1717
|
+
/**
|
|
1718
|
+
* Generic variable storage for internal framework use
|
|
1719
|
+
* @private
|
|
1720
|
+
* @type {any}
|
|
1721
|
+
*/
|
|
1722
|
+
#var;
|
|
1723
|
+
/**
|
|
1724
|
+
* Flag indicating if the request processing is complete
|
|
1725
|
+
* @type {boolean}
|
|
1726
|
+
*/
|
|
1727
|
+
finalized = false;
|
|
1728
|
+
/**
|
|
1729
|
+
* HTTP response status code
|
|
1730
|
+
* @private
|
|
1731
|
+
* @type {number}
|
|
1732
|
+
*/
|
|
1733
|
+
/**
|
|
1734
|
+
* Execution context reference
|
|
1735
|
+
* @private
|
|
1736
|
+
* @type {any}
|
|
1737
|
+
*/
|
|
1738
|
+
#executionCtx;
|
|
1739
|
+
/**
|
|
1740
|
+
* Internal headers storage
|
|
1741
|
+
* @private
|
|
1742
|
+
* @type {any}
|
|
1743
|
+
*/
|
|
1744
|
+
#headers;
|
|
1745
|
+
/**
|
|
1746
|
+
* Processed headers ready for sending
|
|
1747
|
+
* @private
|
|
1748
|
+
* @type {any}
|
|
1749
|
+
*/
|
|
1750
|
+
#preparedHeaders;
|
|
1751
|
+
/**
|
|
1752
|
+
* Native response object reference
|
|
1753
|
+
* @private
|
|
1754
|
+
* @type {any}
|
|
1755
|
+
*/
|
|
1756
|
+
#res;
|
|
1757
|
+
/**
|
|
1758
|
+
* Flag indicating if the response is fresh/unmodified
|
|
1759
|
+
* @private
|
|
1760
|
+
* @type {boolean}
|
|
1761
|
+
*/
|
|
1762
|
+
#isFresh = true;
|
|
1763
|
+
/**
|
|
1764
|
+
* Active layout template reference
|
|
1765
|
+
* @private
|
|
1766
|
+
* @type {any}
|
|
1767
|
+
*/
|
|
1768
|
+
#layout;
|
|
1769
|
+
/**
|
|
1770
|
+
* Template renderer instance
|
|
1771
|
+
* @private
|
|
1772
|
+
* @type {any}
|
|
1773
|
+
*/
|
|
1774
|
+
#renderer;
|
|
1775
|
+
/**
|
|
1776
|
+
* Custom 404 handler reference
|
|
1777
|
+
* @private
|
|
1778
|
+
* @type {any}
|
|
1779
|
+
*/
|
|
1780
|
+
#notFoundHandler;
|
|
1781
|
+
/**
|
|
1782
|
+
* Route matching results
|
|
1783
|
+
* @private
|
|
1784
|
+
* @type {any}
|
|
1785
|
+
*/
|
|
1786
|
+
#matchResult;
|
|
1787
|
+
/**
|
|
1788
|
+
* Processed path information
|
|
1789
|
+
* @private
|
|
1790
|
+
* @type {any}
|
|
1791
|
+
*/
|
|
1792
|
+
#path;
|
|
1793
|
+
constructor(req) {
|
|
1794
|
+
this.#rawRequest = req;
|
|
1795
|
+
this.method = req?.method?.toUpperCase();
|
|
1796
|
+
this.pathname = this.req.urlRef.pathname;
|
|
1797
|
+
this.url = this.req.url;
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Cookie handling utility with get/set/delete operations
|
|
1801
|
+
* @returns {{
|
|
1802
|
+
* get: (name: string) => string | undefined,
|
|
1803
|
+
* all: () => Record<string, string>,
|
|
1804
|
+
* delete: (name: string, options?: CookieOptions) => void,
|
|
1805
|
+
* set: (name: string, value: string, options?: CookieOptions) => void
|
|
1806
|
+
* }} Cookie handling interface
|
|
1807
|
+
*/
|
|
1808
|
+
get cookies() {
|
|
1809
|
+
const c = this.headers.getAll("cookie");
|
|
1810
|
+
let cookies = {};
|
|
1811
|
+
if (Array.isArray(c) && c.length != 0) {
|
|
1812
|
+
const cookieHeader = c.join("; ").split(";");
|
|
1813
|
+
for (const pair of cookieHeader) {
|
|
1814
|
+
const [key, value] = pair?.trim()?.split("=");
|
|
1815
|
+
cookies[key] = decodeURIComponent(value);
|
|
1816
|
+
}
|
|
1817
|
+
} else if (typeof c == "string") {
|
|
1818
|
+
const cookieHeader = c.split(";");
|
|
1819
|
+
for (const pair of cookieHeader) {
|
|
1820
|
+
const [key, value] = pair?.trim()?.split("=");
|
|
1821
|
+
cookies[key] = decodeURIComponent(value);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
return {
|
|
1825
|
+
/**
|
|
1826
|
+
* Get a specific cookie by name.
|
|
1827
|
+
* @param {string} cookie - The name of the cookie to retrieve.
|
|
1828
|
+
* @returns {string | undefined} - The cookie value or undefined if not found.
|
|
1829
|
+
*/
|
|
1830
|
+
get: (cookie) => {
|
|
1831
|
+
return cookies?.[cookie];
|
|
1832
|
+
},
|
|
1833
|
+
/**
|
|
1834
|
+
* Get all cookies as an object.
|
|
1835
|
+
* @returns {Record<string, string>} - An object containing all cookies.
|
|
1836
|
+
*/
|
|
1837
|
+
all: () => {
|
|
1838
|
+
return cookies;
|
|
1839
|
+
},
|
|
1840
|
+
/**
|
|
1841
|
+
* Delete a cookie by setting its expiration to the past.
|
|
1842
|
+
* @param {string} name - The name of the cookie to delete.
|
|
1843
|
+
* @param {CookieOptions} [options] - Additional cookie options.
|
|
1844
|
+
*/
|
|
1845
|
+
delete: (name, options) => {
|
|
1846
|
+
const value = "";
|
|
1847
|
+
const cookieOptions = {
|
|
1848
|
+
...options,
|
|
1849
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
1850
|
+
// Set expiration time to the past
|
|
1851
|
+
};
|
|
1852
|
+
const cookieHeader = `${name}=${value};${serializeOptions(cookieOptions)}`;
|
|
1853
|
+
this.headers.set("Set-Cookie", cookieHeader);
|
|
1854
|
+
},
|
|
1855
|
+
/**
|
|
1856
|
+
* Set a new cookie with the given name, value, and options.
|
|
1857
|
+
* @param {string} name - The name of the cookie.
|
|
1858
|
+
* @param {string} value - The value of the cookie.
|
|
1859
|
+
* @param {CookieOptions} [options] - Additional options like expiration.
|
|
1860
|
+
*/
|
|
1861
|
+
set: (name, value, options) => {
|
|
1862
|
+
const cookieHeader = `${name}=${value};${serializeOptions(options || {})}`;
|
|
1863
|
+
this.headers.set("Set-Cookie", cookieHeader);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
json(body, ...args) {
|
|
1868
|
+
let status = this.#status;
|
|
1869
|
+
let headers = {
|
|
1870
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
1871
|
+
};
|
|
1872
|
+
if (typeof args[0] === "number") {
|
|
1873
|
+
status = args[0];
|
|
1874
|
+
if (typeof args[1] === "object") {
|
|
1875
|
+
headers = { ...headers, ...args[1] };
|
|
1876
|
+
}
|
|
1877
|
+
} else if (typeof args[0] === "object") {
|
|
1878
|
+
headers = { ...headers, ...args[0] };
|
|
1879
|
+
}
|
|
1880
|
+
return new Response(JSON.stringify(body), {
|
|
1881
|
+
status,
|
|
1882
|
+
headers
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
send(body, ...args) {
|
|
1886
|
+
let status = this.#status;
|
|
1887
|
+
let headers = {};
|
|
1888
|
+
if (typeof args[0] === "number") {
|
|
1889
|
+
status = args[0];
|
|
1890
|
+
if (typeof args[1] === "object") {
|
|
1891
|
+
headers = args[1];
|
|
1892
|
+
}
|
|
1893
|
+
} else if (typeof args[0] === "object") {
|
|
1894
|
+
headers = args[0];
|
|
1895
|
+
}
|
|
1896
|
+
if (!headers["Content-Type"]) {
|
|
1897
|
+
if (typeof body === "string") {
|
|
1898
|
+
headers["Content-Type"] = "text/plain;";
|
|
1899
|
+
} else if (typeof body === "object" && body !== null) {
|
|
1900
|
+
headers["Content-Type"] = "application/json;";
|
|
1901
|
+
body = JSON.stringify(body);
|
|
1902
|
+
} else {
|
|
1903
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return new Response(body, {
|
|
1907
|
+
status,
|
|
1908
|
+
headers
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
html(data, ...args) {
|
|
1912
|
+
let status = this.#status;
|
|
1913
|
+
let headers = {
|
|
1914
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
1915
|
+
};
|
|
1916
|
+
if (typeof args[0] === "number") {
|
|
1917
|
+
status = args[0];
|
|
1918
|
+
if (typeof args[1] === "object") {
|
|
1919
|
+
headers = { ...headers, ...args[1] };
|
|
1920
|
+
}
|
|
1921
|
+
} else if (typeof args[0] === "object") {
|
|
1922
|
+
headers = { ...headers, ...args[0] };
|
|
1923
|
+
}
|
|
1924
|
+
return new Response(data, {
|
|
1925
|
+
status,
|
|
1926
|
+
headers
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
text(data, ...args) {
|
|
1930
|
+
let status = this.#status;
|
|
1931
|
+
let headers = {
|
|
1932
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1933
|
+
};
|
|
1934
|
+
if (typeof args[0] === "number") {
|
|
1935
|
+
status = args[0];
|
|
1936
|
+
if (typeof args[1] === "object") {
|
|
1937
|
+
headers = { ...headers, ...args[1] };
|
|
1938
|
+
}
|
|
1939
|
+
} else if (typeof args[0] === "object") {
|
|
1940
|
+
headers = { ...headers, ...args[0] };
|
|
1941
|
+
}
|
|
1942
|
+
return new Response(data, {
|
|
1943
|
+
status,
|
|
1944
|
+
headers
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
xml(data, ...args) {
|
|
1948
|
+
let status = this.#status;
|
|
1949
|
+
let headers = {
|
|
1950
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
1951
|
+
};
|
|
1952
|
+
if (typeof args[0] === "number") {
|
|
1953
|
+
status = args[0];
|
|
1954
|
+
if (typeof args[1] === "object") {
|
|
1955
|
+
headers = { ...headers, ...args[1] };
|
|
1956
|
+
}
|
|
1957
|
+
} else if (typeof args[0] === "object") {
|
|
1958
|
+
headers = { ...headers, ...args[0] };
|
|
1959
|
+
}
|
|
1960
|
+
return new Response(data, {
|
|
1961
|
+
status,
|
|
1962
|
+
headers
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* HTTP status code..
|
|
1967
|
+
* @param status - number.
|
|
1968
|
+
* @returns Response object with context all method.
|
|
1969
|
+
*/
|
|
1970
|
+
status = (status) => {
|
|
1971
|
+
this.#status = status;
|
|
1972
|
+
return this;
|
|
1973
|
+
};
|
|
1974
|
+
/**
|
|
1975
|
+
* Redirects to a given URL.
|
|
1976
|
+
* @param url - The target URL.
|
|
1977
|
+
* @param status - (Optional) HTTP status code (default: 302).
|
|
1978
|
+
* @param headers - (Optional) Additional headers.
|
|
1979
|
+
* @returns Response object with redirect.
|
|
1980
|
+
*/
|
|
1981
|
+
redirect(url, status = 302, headers) {
|
|
1982
|
+
return TezResponse.redirect(url, status, headers);
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Handles file downloads.
|
|
1986
|
+
* @param filePath - The path to the file.
|
|
1987
|
+
* @param fileName - The name of the downloaded file.
|
|
1988
|
+
* @returns Response object for file download.
|
|
1989
|
+
*/
|
|
1990
|
+
async download(filePath, fileName) {
|
|
1991
|
+
return await TezResponse.download(filePath, fileName);
|
|
1992
|
+
}
|
|
1993
|
+
async sendFile(filePath, ...args) {
|
|
1994
|
+
return await TezResponse.sendFile(filePath, ...args);
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Getter that creates a standardized Request object from internal state
|
|
1998
|
+
* @returns {Request} - Normalized request object combining:
|
|
1999
|
+
* - Raw platform-specific request
|
|
2000
|
+
* - Parsed headers
|
|
2001
|
+
* - Route parameters
|
|
2002
|
+
*
|
|
2003
|
+
* @example
|
|
2004
|
+
* // Get standardized request
|
|
2005
|
+
* const request = ctx.req;
|
|
2006
|
+
* // Access route params
|
|
2007
|
+
* const id = request.params.get('id');
|
|
2008
|
+
*/
|
|
2009
|
+
get req() {
|
|
2010
|
+
return new Request(this.#rawRequest, this.params);
|
|
2011
|
+
}
|
|
2012
|
+
// attachWebSocket(ws) {
|
|
2013
|
+
// this.ws = ws;
|
|
2014
|
+
// }
|
|
2015
|
+
set params(params) {
|
|
2016
|
+
this.#params = params;
|
|
2017
|
+
}
|
|
2018
|
+
get params() {
|
|
2019
|
+
return this.#params;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
function serializeOptions(options) {
|
|
2023
|
+
const parts = [];
|
|
2024
|
+
if (options.maxAge) {
|
|
2025
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
2026
|
+
}
|
|
2027
|
+
if (options.expires) {
|
|
2028
|
+
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
2029
|
+
}
|
|
2030
|
+
if (options.path) {
|
|
2031
|
+
parts.push(`Path=${options.path}`);
|
|
2032
|
+
}
|
|
2033
|
+
if (options.domain) {
|
|
2034
|
+
parts.push(`Domain=${options.domain}`);
|
|
2035
|
+
}
|
|
2036
|
+
if (options.secure) {
|
|
2037
|
+
parts.push(`Secure`);
|
|
2038
|
+
}
|
|
2039
|
+
if (options.httpOnly) {
|
|
2040
|
+
parts.push(`HttpOnly`);
|
|
2041
|
+
}
|
|
2042
|
+
if (options.sameSite) {
|
|
2043
|
+
parts.push(`SameSite=${options.sameSite}`);
|
|
2044
|
+
}
|
|
2045
|
+
return parts.join("; ");
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function useParams({
|
|
2049
|
+
path,
|
|
2050
|
+
urlPattern
|
|
2051
|
+
}) {
|
|
2052
|
+
let params = {};
|
|
2053
|
+
path = path.replace(/^\/+|\/+$/g, "");
|
|
2054
|
+
urlPattern = urlPattern.replace(/^\/+|\/+$/g, "");
|
|
2055
|
+
const pathSegments = path ? path.split("/") : [];
|
|
2056
|
+
const patternSegments = urlPattern ? urlPattern.split("/") : [];
|
|
2057
|
+
const pathLength = pathSegments.length;
|
|
2058
|
+
const patternLength = patternSegments.length;
|
|
2059
|
+
if (pathLength > patternLength && !urlPattern.includes("*")) {
|
|
2060
|
+
return { success: false, params: {} };
|
|
2061
|
+
}
|
|
2062
|
+
let pathIndex = 0;
|
|
2063
|
+
for (let i = 0; i < patternLength; i++) {
|
|
2064
|
+
const patternSegment = patternSegments[i];
|
|
2065
|
+
if (patternSegment?.startsWith("*")) {
|
|
2066
|
+
const trailingPatterns = patternSegments.slice(i + 1);
|
|
2067
|
+
let paramName = patternSegment.length == 1 ? "*" : patternSegment?.slice(1);
|
|
2068
|
+
if (trailingPatterns.length > 0) {
|
|
2069
|
+
const expectedTrailing = trailingPatterns.join("/");
|
|
2070
|
+
const actualTrailing = pathSegments.slice(pathLength - trailingPatterns.length).join("/");
|
|
2071
|
+
const wildcardPath = pathSegments.slice(pathIndex, pathLength - trailingPatterns.length).join("/");
|
|
2072
|
+
if (expectedTrailing !== actualTrailing || !wildcardPath) {
|
|
2073
|
+
return { success: false, params: {} };
|
|
2074
|
+
}
|
|
2075
|
+
params[paramName] = wildcardPath;
|
|
2076
|
+
return { success: true, params };
|
|
2077
|
+
} else {
|
|
2078
|
+
const wildcardPath = pathSegments.slice(pathIndex).join("/");
|
|
2079
|
+
if (!wildcardPath) {
|
|
2080
|
+
return { success: false, params: {} };
|
|
2081
|
+
}
|
|
2082
|
+
params[paramName] = wildcardPath;
|
|
2083
|
+
return { success: true, params };
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (patternSegment.startsWith(":") && patternSegment.endsWith("?")) {
|
|
2087
|
+
const paramName = patternSegment.slice(1, -1);
|
|
2088
|
+
const nextPattern = patternSegments[i + 1];
|
|
2089
|
+
if (nextPattern && !nextPattern.startsWith(":") && nextPattern !== "*" && pathIndex < pathLength && // !/test == /:user?/test
|
|
2090
|
+
// বর্তমান পথ যদি পরবর্তী প্যাটার্ন মানে স্ট্যাটিক পথ এর সাথে মাইল তাহলে
|
|
2091
|
+
pathSegments[pathIndex] === nextPattern) {
|
|
2092
|
+
params[paramName] = null;
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
const remainingPatterns = patternSegments.slice(i + 1);
|
|
2096
|
+
const requiredCount = remainingPatterns.filter(
|
|
2097
|
+
(seg) => !(seg.startsWith(":") && seg.endsWith("?"))
|
|
2098
|
+
).length;
|
|
2099
|
+
const remainingPath = pathLength - pathIndex;
|
|
2100
|
+
if (remainingPath === requiredCount) {
|
|
2101
|
+
params[paramName] = null;
|
|
2102
|
+
} else if (pathIndex < pathLength) {
|
|
2103
|
+
params[paramName] = pathSegments[pathIndex];
|
|
2104
|
+
pathIndex++;
|
|
2105
|
+
} else {
|
|
2106
|
+
params[paramName] = null;
|
|
2107
|
+
}
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
if (patternSegment.startsWith(":")) {
|
|
2111
|
+
const paramName = patternSegment.slice(1);
|
|
2112
|
+
if (!/^[a-zA-Z0-9_]+$/.test(paramName)) {
|
|
2113
|
+
return { success: false, params: {} };
|
|
2114
|
+
}
|
|
2115
|
+
if (pathIndex < pathLength) {
|
|
2116
|
+
params[paramName] = pathSegments[pathIndex];
|
|
2117
|
+
pathIndex++;
|
|
2118
|
+
} else {
|
|
2119
|
+
return { success: false, params: {} };
|
|
2120
|
+
}
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
const pathSegment = pathSegments[pathIndex];
|
|
2124
|
+
if (patternSegment !== pathSegment) {
|
|
2125
|
+
return { success: false, params: {} };
|
|
2126
|
+
}
|
|
2127
|
+
pathIndex++;
|
|
2128
|
+
}
|
|
2129
|
+
if (pathIndex < pathLength) {
|
|
2130
|
+
return { success: false, params: {} };
|
|
2131
|
+
}
|
|
2132
|
+
return { success: true, params };
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
class TezX extends Router {
|
|
2136
|
+
constructor({
|
|
2137
|
+
basePath = "/",
|
|
2138
|
+
env = {},
|
|
2139
|
+
logger = void 0,
|
|
2140
|
+
allowDuplicateMw = false,
|
|
2141
|
+
overwriteMethod = true
|
|
2142
|
+
} = {}) {
|
|
2143
|
+
GlobalConfig.allowDuplicateMw = allowDuplicateMw;
|
|
2144
|
+
GlobalConfig.overwriteMethod = overwriteMethod;
|
|
2145
|
+
if (logger) {
|
|
2146
|
+
GlobalConfig.loggerFn = logger;
|
|
2147
|
+
GlobalConfig.enableLogger = true;
|
|
2148
|
+
}
|
|
2149
|
+
super({ basePath, env });
|
|
2150
|
+
this.serve = this.serve.bind(this);
|
|
2151
|
+
}
|
|
2152
|
+
#hashRouter(method, pathname) {
|
|
2153
|
+
const routers = this.routers;
|
|
2154
|
+
for (let pattern of this.routers.keys()) {
|
|
2155
|
+
const { success, params } = useParams({
|
|
2156
|
+
path: pathname,
|
|
2157
|
+
urlPattern: pattern
|
|
2158
|
+
});
|
|
2159
|
+
const handlers = routers.get(pattern)?.get(method) || routers.get(pattern)?.get("ALL");
|
|
2160
|
+
if (success && handlers) {
|
|
2161
|
+
return {
|
|
2162
|
+
callback: handlers.callback,
|
|
2163
|
+
middlewares: handlers.middlewares,
|
|
2164
|
+
params
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return null;
|
|
2169
|
+
}
|
|
2170
|
+
#triRouter(method, pathname) {
|
|
2171
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2172
|
+
const params = {};
|
|
2173
|
+
let node = this.triRouter;
|
|
2174
|
+
for (let part of parts) {
|
|
2175
|
+
if (node.children.has(part)) {
|
|
2176
|
+
node = node.children.get(part);
|
|
2177
|
+
} else if (node.children.has(":")) {
|
|
2178
|
+
node = node.children.get(":");
|
|
2179
|
+
if (node.paramName) params[node.paramName] = part;
|
|
2180
|
+
} else {
|
|
2181
|
+
return null;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (node?.handlers?.size && node?.pathname) {
|
|
2185
|
+
const handlers = node.handlers.get(method) || node.handlers.get("ALL");
|
|
2186
|
+
if (handlers) {
|
|
2187
|
+
return {
|
|
2188
|
+
middlewares: handlers.middlewares,
|
|
2189
|
+
callback: handlers.callback,
|
|
2190
|
+
params
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
findRoute(method, pathname) {
|
|
2198
|
+
const route = this.#triRouter(method, pathname) || this.#hashRouter(method, pathname);
|
|
2199
|
+
if (route) {
|
|
2200
|
+
return {
|
|
2201
|
+
...route,
|
|
2202
|
+
middlewares: [...route.middlewares]
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
#createHandler(middlewares, finalCallback) {
|
|
2208
|
+
return async (ctx) => {
|
|
2209
|
+
let index = 0;
|
|
2210
|
+
const next = async () => {
|
|
2211
|
+
if (index < middlewares.length) {
|
|
2212
|
+
return middlewares[index++](ctx, next);
|
|
2213
|
+
} else {
|
|
2214
|
+
const response = await finalCallback(ctx);
|
|
2215
|
+
return response;
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
return await next();
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
#findMiddleware(pathname) {
|
|
2222
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
2223
|
+
let middlewares = [];
|
|
2224
|
+
let node = this.triMiddlewares;
|
|
2225
|
+
for (let part of parts) {
|
|
2226
|
+
if (node.children.has(part)) {
|
|
2227
|
+
node = node.children.get(part);
|
|
2228
|
+
} else if (node.children.has("*")) {
|
|
2229
|
+
node = node.children.get("*");
|
|
2230
|
+
} else if (node.children.has(":")) {
|
|
2231
|
+
node = node.children.get(":");
|
|
2232
|
+
} else {
|
|
2233
|
+
break;
|
|
2234
|
+
}
|
|
2235
|
+
middlewares.push(...node.middlewares);
|
|
2236
|
+
}
|
|
2237
|
+
return middlewares;
|
|
2238
|
+
}
|
|
2239
|
+
async #handleRequest(req) {
|
|
2240
|
+
let ctx = new Context(req);
|
|
2241
|
+
const urlRef = ctx.req.urlRef;
|
|
2242
|
+
const { pathname } = urlRef;
|
|
2243
|
+
let middlewares = this.#findMiddleware(pathname);
|
|
2244
|
+
ctx.env = this.env;
|
|
2245
|
+
const logger = GlobalConfig.loggerFn();
|
|
2246
|
+
if (logger.request) {
|
|
2247
|
+
logger.request(ctx.method, ctx.pathname);
|
|
2248
|
+
}
|
|
2249
|
+
try {
|
|
2250
|
+
return await this.#createHandler(
|
|
2251
|
+
[...this.triMiddlewares.middlewares, ...middlewares],
|
|
2252
|
+
async (ctx2) => {
|
|
2253
|
+
const find = this.findRoute(ctx2.req.method, pathname);
|
|
2254
|
+
if (find?.callback) {
|
|
2255
|
+
ctx2.params = find.params;
|
|
2256
|
+
const callback = find.callback;
|
|
2257
|
+
let middlewares2 = find.middlewares;
|
|
2258
|
+
const response = await this.#createHandler(
|
|
2259
|
+
middlewares2,
|
|
2260
|
+
callback
|
|
2261
|
+
)(ctx2);
|
|
2262
|
+
if (response?.headers) {
|
|
2263
|
+
ctx2.headers.add(response.headers);
|
|
2264
|
+
}
|
|
2265
|
+
const statusText = response?.statusText || httpStatusMap[response?.status] || "";
|
|
2266
|
+
const status = response.status || 200;
|
|
2267
|
+
let headers = ctx2.headers.toObject();
|
|
2268
|
+
if (logger.response) {
|
|
2269
|
+
logger.response(ctx2.method, ctx2.pathname, status);
|
|
2270
|
+
}
|
|
2271
|
+
if (response instanceof Response) {
|
|
2272
|
+
return new Response(response.body, {
|
|
2273
|
+
status,
|
|
2274
|
+
statusText,
|
|
2275
|
+
headers
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
return new Response(response.body, {
|
|
2279
|
+
status,
|
|
2280
|
+
statusText,
|
|
2281
|
+
headers
|
|
2282
|
+
});
|
|
2283
|
+
} else {
|
|
2284
|
+
if (logger.response) {
|
|
2285
|
+
logger.response(ctx2.method, ctx2.pathname, 404);
|
|
2286
|
+
}
|
|
2287
|
+
return GlobalConfig.notFound(ctx2);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
)(ctx);
|
|
2291
|
+
} catch (err) {
|
|
2292
|
+
let error = err;
|
|
2293
|
+
if (err instanceof Error) {
|
|
2294
|
+
error = err.stack;
|
|
2295
|
+
}
|
|
2296
|
+
if (logger.error) {
|
|
2297
|
+
logger.error(`${httpStatusMap[500]}: ${error} `);
|
|
2298
|
+
}
|
|
2299
|
+
return GlobalConfig.onError(error, ctx);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
async serve(req) {
|
|
2303
|
+
return this.#handleRequest(req);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
function parseEnvFile(filePath, result) {
|
|
2308
|
+
try {
|
|
2309
|
+
let fileExists = false;
|
|
2310
|
+
let runtime = EnvironmentDetector.getEnvironment;
|
|
2311
|
+
if (runtime === "node" || runtime === "bun") {
|
|
2312
|
+
const { existsSync } = require("fs");
|
|
2313
|
+
fileExists = existsSync(filePath);
|
|
2314
|
+
} else if (runtime === "deno") {
|
|
2315
|
+
try {
|
|
2316
|
+
Deno.statSync(filePath);
|
|
2317
|
+
fileExists = true;
|
|
2318
|
+
} catch {
|
|
2319
|
+
fileExists = false;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
if (!fileExists) {
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
let fileContent = "";
|
|
2326
|
+
if (runtime === "node" || runtime === "bun") {
|
|
2327
|
+
const { readFileSync } = require("fs");
|
|
2328
|
+
fileContent = readFileSync(filePath, "utf8");
|
|
2329
|
+
} else if (runtime === "deno") {
|
|
2330
|
+
fileContent = new TextDecoder("utf-8").decode(
|
|
2331
|
+
Deno.readFileSync(filePath)
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
const lines = fileContent.split("\n");
|
|
2335
|
+
for (const line of lines) {
|
|
2336
|
+
const trimmedLine = line.trim();
|
|
2337
|
+
if (!trimmedLine || trimmedLine.startsWith("#")) continue;
|
|
2338
|
+
const [key, value] = trimmedLine.split("=", 2).map((part) => part.trim());
|
|
2339
|
+
if (key && value) {
|
|
2340
|
+
const parsedValue = value.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
2341
|
+
result[key] = parsedValue;
|
|
2342
|
+
if (runtime === "node" || runtime === "bun") {
|
|
2343
|
+
process.env[key] = parsedValue;
|
|
2344
|
+
} else if (runtime === "deno") {
|
|
2345
|
+
Deno.env.set(key, parsedValue);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
} catch (error) {
|
|
2350
|
+
console.error(`[dotenv] Error parsing file: ${filePath}`, error);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function loadEnv(basePath = "./") {
|
|
2354
|
+
const result = {};
|
|
2355
|
+
const envFiles = [
|
|
2356
|
+
".env",
|
|
2357
|
+
".env.local",
|
|
2358
|
+
`.env.${process?.env?.NODE_ENV || "development"}`,
|
|
2359
|
+
// Supports NODE_ENV (e.g., .env.development, .env.production)
|
|
2360
|
+
`.env.${process?.env?.NODE_ENV || "development"}.local`
|
|
2361
|
+
];
|
|
2362
|
+
for (const envFile of envFiles) {
|
|
2363
|
+
parseEnvFile(`${basePath}${envFile}`, result);
|
|
2364
|
+
}
|
|
2365
|
+
return result;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
const COLORS = {
|
|
2369
|
+
reset: "\x1B[0m",
|
|
2370
|
+
bold: "\x1B[1m",
|
|
2371
|
+
gray: "\x1B[90m",
|
|
2372
|
+
red: "\x1B[31m",
|
|
2373
|
+
green: "\x1B[32m",
|
|
2374
|
+
yellow: "\x1B[33m",
|
|
2375
|
+
blue: "\x1B[34m",
|
|
2376
|
+
magenta: "\x1B[35m",
|
|
2377
|
+
cyan: "\x1B[36m",
|
|
2378
|
+
bgBlue: "\x1B[44m",
|
|
2379
|
+
bgMagenta: "\x1B[45m"};
|
|
2380
|
+
const loggerOutput = (level, message, ...args) => {
|
|
2381
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2382
|
+
const LEVEL_COLORS = {
|
|
2383
|
+
info: COLORS.blue,
|
|
2384
|
+
warn: COLORS.yellow,
|
|
2385
|
+
error: COLORS.red,
|
|
2386
|
+
debug: COLORS.cyan,
|
|
2387
|
+
success: COLORS.green
|
|
2388
|
+
};
|
|
2389
|
+
const prefix = `${COLORS.gray}[${timestamp}]${COLORS.reset}`;
|
|
2390
|
+
const levelText = `${LEVEL_COLORS[level]}[${level.toUpperCase()}]${COLORS.reset}`;
|
|
2391
|
+
console.log(`${prefix} ${levelText} ${message}`, ...args?.flat());
|
|
2392
|
+
};
|
|
2393
|
+
function logger() {
|
|
2394
|
+
const startTime = performance.now();
|
|
2395
|
+
if (GlobalConfig.enableLogger) {
|
|
2396
|
+
return {
|
|
2397
|
+
request: (method, pathname) => {
|
|
2398
|
+
console.log(
|
|
2399
|
+
`${COLORS.bold}<-- ${COLORS.reset}${COLORS.bgMagenta} ${method} ${COLORS.reset} ${pathname}`
|
|
2400
|
+
);
|
|
2401
|
+
},
|
|
2402
|
+
response: (method, pathname, status) => {
|
|
2403
|
+
const elapsed = performance.now() - startTime;
|
|
2404
|
+
console.log(
|
|
2405
|
+
`${COLORS.bold}--> ${COLORS.reset}${COLORS.bgBlue} ${method} ${COLORS.reset} ${pathname} ${COLORS.yellow}${status}${COLORS.reset} ${COLORS.magenta}${elapsed.toFixed(2)}ms${COLORS.reset}`
|
|
2406
|
+
);
|
|
2407
|
+
},
|
|
2408
|
+
info: (msg, ...args) => loggerOutput("info", msg, ...args),
|
|
2409
|
+
warn: (msg, ...args) => loggerOutput("warn", msg, ...args),
|
|
2410
|
+
error: (msg, ...args) => loggerOutput("error", msg, ...args),
|
|
2411
|
+
debug: (msg, ...args) => loggerOutput("debug", msg, ...args),
|
|
2412
|
+
success: (msg, ...args) => loggerOutput("success", msg, ...args)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
return {
|
|
2416
|
+
request: (method, pathname) => {
|
|
2417
|
+
},
|
|
2418
|
+
response: (method, pathname, status) => {
|
|
2419
|
+
},
|
|
2420
|
+
info: (msg, ...args) => {
|
|
2421
|
+
},
|
|
2422
|
+
warn: (msg, ...args) => {
|
|
2423
|
+
},
|
|
2424
|
+
error: (msg, ...args) => {
|
|
2425
|
+
},
|
|
2426
|
+
debug: (msg, ...args) => {
|
|
2427
|
+
},
|
|
2428
|
+
success: (msg, ...args) => {
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
function cors(option = {}) {
|
|
2434
|
+
const {
|
|
2435
|
+
methods,
|
|
2436
|
+
allowedHeaders,
|
|
2437
|
+
credentials,
|
|
2438
|
+
exposedHeaders,
|
|
2439
|
+
maxAge,
|
|
2440
|
+
origin
|
|
2441
|
+
} = option;
|
|
2442
|
+
return async (ctx, next) => {
|
|
2443
|
+
const reqOrigin = ctx.req.headers.get("origin") || "";
|
|
2444
|
+
let allowOrigin = "*";
|
|
2445
|
+
if (typeof origin === "string") {
|
|
2446
|
+
allowOrigin = origin;
|
|
2447
|
+
} else if (origin instanceof RegExp) {
|
|
2448
|
+
allowOrigin = origin.test(reqOrigin) ? reqOrigin : "";
|
|
2449
|
+
} else if (Array.isArray(origin)) {
|
|
2450
|
+
const isAllowed = origin.some((item) => {
|
|
2451
|
+
if (typeof item === "string") {
|
|
2452
|
+
return item === reqOrigin;
|
|
2453
|
+
} else if (item instanceof RegExp) {
|
|
2454
|
+
return item.test(reqOrigin);
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
allowOrigin = isAllowed ? reqOrigin : "";
|
|
2458
|
+
} else if (typeof origin === "function") {
|
|
2459
|
+
allowOrigin = origin(reqOrigin) ? reqOrigin : "";
|
|
2460
|
+
}
|
|
2461
|
+
ctx.headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
2462
|
+
ctx.headers.set(
|
|
2463
|
+
"Access-Control-Allow-Methods",
|
|
2464
|
+
(methods || ["GET", "POST", "PUT", "DELETE"]).join(", ")
|
|
2465
|
+
);
|
|
2466
|
+
ctx.headers.set(
|
|
2467
|
+
"Access-Control-Allow-Headers",
|
|
2468
|
+
(allowedHeaders || ["Content-Type", "Authorization"]).join(", ")
|
|
2469
|
+
);
|
|
2470
|
+
if (exposedHeaders) {
|
|
2471
|
+
ctx.headers.set(
|
|
2472
|
+
"Access-Control-Expose-Headers",
|
|
2473
|
+
exposedHeaders.join(", ")
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
if (credentials) {
|
|
2477
|
+
ctx.headers.set("Access-Control-Allow-Credentials", "true");
|
|
2478
|
+
}
|
|
2479
|
+
if (maxAge) {
|
|
2480
|
+
ctx.headers.set("Access-Control-Max-Age", maxAge.toString());
|
|
2481
|
+
}
|
|
2482
|
+
if (ctx.req.method === "OPTIONS") {
|
|
2483
|
+
return new Response(null, {
|
|
2484
|
+
status: 204,
|
|
2485
|
+
headers: ctx.headers.toObject()
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
return await next();
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
export { Router, TezResponse, TezX, bunAdapter, cors, denoAdapter, loadEnv, logger, nodeAdapter, useParams };
|