@je-es/server 0.1.3 → 0.1.5

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/main.d.cts CHANGED
@@ -248,6 +248,7 @@ type RouteHandler = (ctx: AppContext) => Response | Promise<Response>;
248
248
  interface RouteMatch {
249
249
  handler: RouteHandler;
250
250
  params: Record<string, string>;
251
+ metadata?: unknown;
251
252
  }
252
253
  interface RouteInfo {
253
254
  method: string;
@@ -261,7 +262,7 @@ declare class Router {
261
262
  getAll(): RouteInfo[];
262
263
  clear(): void;
263
264
  remove(method: string, path: string): boolean;
264
- register(method: string, path: string, handler: RouteHandler, _?: unknown): void;
265
+ register(method: string, path: string, handler: RouteHandler, metadata?: unknown): void;
265
266
  private pathToRegex;
266
267
  }
267
268
 
package/dist/main.d.ts CHANGED
@@ -248,6 +248,7 @@ type RouteHandler = (ctx: AppContext) => Response | Promise<Response>;
248
248
  interface RouteMatch {
249
249
  handler: RouteHandler;
250
250
  params: Record<string, string>;
251
+ metadata?: unknown;
251
252
  }
252
253
  interface RouteInfo {
253
254
  method: string;
@@ -261,7 +262,7 @@ declare class Router {
261
262
  getAll(): RouteInfo[];
262
263
  clear(): void;
263
264
  remove(method: string, path: string): boolean;
264
- register(method: string, path: string, handler: RouteHandler, _?: unknown): void;
265
+ register(method: string, path: string, handler: RouteHandler, metadata?: unknown): void;
265
266
  private pathToRegex;
266
267
  }
267
268
 
package/dist/main.js CHANGED
@@ -1,3 +1,3 @@
1
- import*as ee from'@je-es/sdb';export{DB,blob,column,defaultValue,integer,notNull,numeric,primaryKey,real,references,table,text,unique}from'@je-es/sdb';import re from'crypto';import {Logger}from'@je-es/slog';export{Logger}from'@je-es/slog';import {resolve,join,relative,extname}from'path';import {existsSync,statSync}from'fs';var O=class{constructor(){this.routes=new Map;this.regexRoutes=[];}match(e,r){let t=`${e}:${r}`;if(this.routes.has(t))return {handler:this.routes.get(t),params:{}};for(let s of this.regexRoutes)if(s.method===e){let o=r.match(s.pattern);if(o){let c=o.groups||{};return {handler:s.handler,params:c}}}return null}getAll(){let e=Array.from(this.routes.entries()).map(([t,s])=>{let o=t.indexOf(":"),c=t.substring(0,o),a=t.substring(o+1);return {method:c,path:a,handler:s}}),r=this.regexRoutes.map(t=>{let s=t.key.indexOf(":");return {method:t.method,path:t.key.substring(s+1),handler:t.handler}});return [...e,...r]}clear(){this.routes.clear(),this.regexRoutes=[];}remove(e,r){let t=`${e}:${r}`;if(this.routes.has(t))return this.routes.delete(t),true;let s=this.regexRoutes.findIndex(o=>o.key===t);return s>=0?(this.regexRoutes.splice(s,1),true):false}register(e,r,t,s={}){let o=`${e}:${r}`;if(r.includes(":")||r.includes("*")){let c=this.pathToRegex(r),a=this.regexRoutes.findIndex(h=>h.key===o),m={pattern:c,method:e,handler:t,key:o};a>=0?this.regexRoutes[a]=m:this.regexRoutes.push(m);}else this.routes.set(o,t);}pathToRegex(e){let r=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&");return r=r.replace(/:(\w+)/g,"(?<$1>[^/]+)"),r=r.replace(/\*/g,".*"),new RegExp(`^${r}$`)}};var j=class{constructor(){this.rateLimitStore=new Map;this.csrfTokens=new Map;this.requestLog=new Map;this.MAX_REQUEST_LOG_SIZE=1e3;}checkRateLimit(e,r,t){let s=Date.now(),o=this.rateLimitStore.get(e);return o?s<o.reset?o.count>=r?false:(o.count++,true):(this.rateLimitStore.set(e,{count:1,reset:s+t}),true):(this.rateLimitStore.set(e,{count:1,reset:s+t}),true)}cleanupRateLimit(){let e=Date.now();for(let[r,t]of this.rateLimitStore.entries())e>t.reset&&this.rateLimitStore.delete(r);}generateCsrfToken(e,r=36e5){let t=re.randomBytes(32).toString("hex");return this.csrfTokens.set(t,{sessionId:e,expires:Date.now()+r}),t}validateCsrfToken(e,r){let t=this.csrfTokens.get(e);return t?Date.now()>t.expires?(this.csrfTokens.delete(e),false):t.sessionId===r?(this.csrfTokens.delete(e),true):false:false}cleanupCsrfTokens(){let e=Date.now();for(let[r,t]of this.csrfTokens.entries())e>t.expires&&this.csrfTokens.delete(r);}sanitizeHtml(e){return e?e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;"):""}sanitizeSql(e){return e?e.replace(/\\/g,"\\\\").replace(/;/g,"").replace(/'/g,"''").replace(/"/g,'\\"').replace(/\u0000/g,""):""}logRequest(e,r,t,s,o,c){if(this.requestLog.set(e,{timestamp:new Date().toISOString(),method:r,path:t,ip:s,status:o,duration:c}),this.requestLog.size>this.MAX_REQUEST_LOG_SIZE){let{value:a}=this.requestLog.keys().next()||{value:null};a&&this.requestLog.delete(a);}}getRequestLog(e){return this.requestLog.get(e)}getAllRequestLogs(){return Array.from(this.requestLog.values())}clearAll(){this.rateLimitStore.clear(),this.csrfTokens.clear(),this.requestLog.clear();}getStats(){return {rateLimitEntries:this.rateLimitStore.size,csrfTokens:this.csrfTokens.size,requestLogs:this.requestLog.size}}};var T=class extends Error{constructor(r,t=500,s){super(r);this.message=r;this.statusCode=t;this.code=s;this.name="AppError";}},k=class extends T{constructor(r,t){super(r,400,"VALIDATION_ERROR");this.issues=t;this.name="ValidationError";}},K=class extends T{constructor(e){super(e,500,"DATABASE_ERROR"),this.name="DatabaseError";}},z=class extends T{constructor(e="Request timeout"){super(e,408,"TIMEOUT_ERROR"),this.name="TimeoutError";}},Z=class extends T{constructor(e="Too many requests"){super(e,429,"RATE_LIMIT_ERROR"),this.name="RateLimitError";}};var q=class{constructor(e){this.fileCache=new Map;this.CACHE_MAX_SIZE=1e3;if(!existsSync(e.directory))throw new Error(`Static directory does not exist: ${e.directory}`);if(!statSync(e.directory).isDirectory())throw new Error(`Static path is not a directory: ${e.directory}`);this.resolvedDir=resolve(e.directory),this.config={path:e.path,directory:e.directory,maxAge:e.maxAge??3600,index:e.index??["index.html"],dotfiles:e.dotfiles??"deny",etag:e.etag??true,lastModified:e.lastModified??true,immutable:e.immutable??false,extensions:e.extensions??[],fallthrough:e.fallthrough??false,setHeaders:e.setHeaders};}handler(){return async e=>{let r=e.request.url,s=new URL(r).pathname;s.startsWith(this.config.path)&&(s=s.slice(this.config.path.length));try{s=decodeURIComponent(s);}catch{return e.json({error:"Invalid URL encoding"},400)}if(s.includes("..")||s.includes("\\"))return e.json({error:"Forbidden"},403);if(this.config.dotfiles!=="allow"&&s.split("/").some(a=>a.startsWith(".")))return this.config.dotfiles==="deny"?e.json({error:"Forbidden"},403):this.handleNotFound(e);let o=this.resolveFilePath(s);if(!o)return this.handleNotFound(e);if(!this.isPathSafe(o))return e.json({error:"Forbidden"},403);if(!existsSync(o))return this.handleNotFound(e);let c=statSync(o);return c.isDirectory()?this.serveDirectory(e,o,s):this.serveFile(e,o,c)}}getPathPattern(){return `${this.config.path}/*`}resolveFilePath(e){e.startsWith("/")&&(e=e.slice(1));let r=join(this.resolvedDir,e);if(!existsSync(r)&&this.config.extensions.length>0)for(let t of this.config.extensions){let s=`${r}.${t}`;if(existsSync(s))return s}return r}isPathSafe(e){return !relative(this.resolvedDir,resolve(e)).startsWith("..")&&!resolve(e).startsWith("..")}async serveDirectory(e,r,t){for(let s of this.config.index){let o=join(r,s);if(existsSync(o)){let c=statSync(o);if(c.isFile())return this.serveFile(e,o,c)}}return this.handleNotFound(e)}async serveFile(e,r,t){let s=e.request.method.toUpperCase();if(s!=="GET"&&s!=="HEAD")return e.json({error:"Method not allowed"},405);let o=r,c=this.fileCache.get(o);if(c&&c.mtime!==t.mtimeMs&&(c=void 0),!c){if(c={etag:this.generateEtag(t),lastModified:new Date(t.mtime),size:t.size,mtime:t.mtimeMs},this.fileCache.size>=this.CACHE_MAX_SIZE){let A=this.fileCache.keys().next().value;A&&this.fileCache.delete(A);}this.fileCache.set(o,c);}let a=e.request.headers.get("if-none-match"),m=e.request.headers.get("if-modified-since");if(this.config.etag&&a===c.etag)return new Response(null,{status:304,headers:this.buildHeaders(r,c)});if(this.config.lastModified&&m){let A=new Date(m);if(c.lastModified<=A)return new Response(null,{status:304,headers:this.buildHeaders(r,c)})}let h=Bun.file(r),y=this.buildHeaders(r,c);return this.config.setHeaders&&this.config.setHeaders(e,r),s==="HEAD"?new Response(null,{status:200,headers:y}):new Response(h,{status:200,headers:y})}buildHeaders(e,r){let t=new Headers,s=this.getMimeType(e);if(t.set("Content-Type",s),t.set("Content-Length",r.size.toString()),this.config.etag&&t.set("ETag",r.etag),this.config.lastModified&&t.set("Last-Modified",r.lastModified.toUTCString()),this.config.maxAge>0){let o=`public, max-age=${this.config.maxAge}`;this.config.immutable&&(o+=", immutable"),t.set("Cache-Control",o);}else t.set("Cache-Control","no-cache");return t.set("Accept-Ranges","bytes"),t}generateEtag(e){return `"${e.size.toString(16)}-${e.mtimeMs.toString(16)}"`}getMimeType(e){let r=extname(e).toLowerCase();return ae[r]||"application/octet-stream"}handleNotFound(e){return this.config.fallthrough?e.json({error:"Not Found"},404):e.json({error:"Not Found"},404)}clearCache(){this.fileCache.clear();}getCacheStats(){return {entries:this.fileCache.size,maxSize:this.CACHE_MAX_SIZE}}};function ie(n){return new q(n)}var ae={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".txt":"text/plain; charset=utf-8",".xml":"text/xml; charset=utf-8",".csv":"text/csv; charset=utf-8",".md":"text/markdown; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".jsonld":"application/ld+json",".map":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".ico":"image/x-icon",".webp":"image/webp",".avif":"image/avif",".bmp":"image/bmp",".tiff":"image/tiff",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".mp3":"audio/mpeg",".wav":"audio/wav",".ogg":"audio/ogg",".m4a":"audio/mp4",".aac":"audio/aac",".flac":"audio/flac",".mp4":"video/mp4",".webm":"video/webm",".ogv":"video/ogg",".mov":"video/quicktime",".avi":"video/x-msvideo",".mkv":"video/x-matroska",".pdf":"application/pdf",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".zip":"application/zip",".rar":"application/x-rar-compressed",".7z":"application/x-7z-compressed",".tar":"application/x-tar",".gz":"application/gzip",".wasm":"application/wasm",".manifest":"text/cache-manifest",".webmanifest":"application/manifest+json"};var F=new j,C=new O;function ue(n={}){let e=Number(n.port)||3e3,r=n.hostname||"localhost",t=n.maxRequestSize||10*1024*1024,s=n.requestTimeout||3e4,o=n.gracefulShutdownTimeout||1e4,c=typeof n.logging=="object"?n.logging:{},a=n.logging?new Logger(c.level||"info",c.pretty):null,m=new Map,h=[],y=new Set,A=setInterval(()=>{F.cleanupRateLimit(),F.cleanupCsrfTokens();},120*1e3);async function H(i,d){let p=Date.now(),l=crypto.randomUUID(),S=new URL(i.url).pathname,L=i.method.toUpperCase(),w=pe(i,d);y.add(l);try{let b=i.headers.get("content-length");if(b&&parseInt(b)>t)return a?.warn({requestId:l,size:b,ip:w},"Request too large"),new Response(JSON.stringify({error:"Payload too large"}),{status:413,headers:{"Content-Type":"application/json"}});let I=ge(i,n);if(L==="OPTIONS")return new Response(null,{status:204,headers:I});if(n.security&&typeof n.security=="object"&&n.security.rateLimit){let v=typeof n.security.rateLimit=="object"?n.security.rateLimit:{},D=v.max||100,_=v.windowMs||6e4,J=v.keyGenerator?v.keyGenerator({request:i,ip:w}):w;if(!F.checkRateLimit(J,D,_))return a?.warn({requestId:l,ip:w,key:J},"Rate limit exceeded"),new Response(JSON.stringify({error:v.message||"Too many requests"}),{status:429,headers:{"Content-Type":"application/json"}})}let B=null;["POST","PUT","PATCH"].includes(L)&&(B=await de(i,a,t));let V=m.get("default"),$=C.match(L,S);if(!$){let v=Y(w,i,{},V,a,l);return a?.warn({requestId:l,method:L,path:S,ip:w},"Route not found"),v.json({error:"Not Found",path:S},404)}let N=Y(w,i,$.params||{},V,a,l);N.body=B,N.request=i;let X=new AbortController,te=new Promise((v,D)=>{let _=setTimeout(()=>{X.abort(),D(new z("Request timeout"));},s);X.signal.addEventListener("abort",()=>clearTimeout(_));}),M=await Promise.race([$.handler(N),te]),E=new Headers(M.headers);I.forEach((v,D)=>{E.has(D)||E.set(D,v);}),E.set("X-Request-ID",l),E.set("X-Content-Type-Options","nosniff"),E.set("X-Frame-Options","DENY"),E.set("X-XSS-Protection","1; mode=block"),E.set("Referrer-Policy","strict-origin-when-cross-origin");let W=Date.now()-p;return F.logRequest(l,L,S,w,M.status,W),a?.info({requestId:l,method:L,path:S,status:M.status,duration:W,ip:w},"Request completed"),new Response(M.body,{status:M.status,headers:E})}catch(b){if(b instanceof T)return a?.warn({error:b.message,requestId:l,ip:w},`App error: ${b.message}`),new Response(JSON.stringify({error:b.message,code:b.code,requestId:l}),{status:b.statusCode,headers:{"Content-Type":"application/json"}});a?.error({error:String(b),requestId:l,ip:w},"Unhandled error");let I=process.env.NODE_ENV==="production"?"Internal Server Error":b.message;return new Response(JSON.stringify({error:I,requestId:l}),{status:500,headers:{"Content-Type":"application/json"}})}finally{y.delete(l);}}let u={method:"GET",path:"/health",handler:i=>i.json({status:"healthy",timestamp:new Date().toISOString(),uptime:process.uptime(),activeRequests:y.size})},g={method:"GET",path:"/readiness",handler:i=>{let d=m.size>0,p=d||m.size===0;return i.json({ready:p,checks:{database:d?"connected":"not configured",activeRequests:y.size},timestamp:new Date().toISOString()},p?200:503)}};if(n.routes&&n.routes.forEach(i=>{h.push(i),(Array.isArray(i.method)?i.method:[i.method]).forEach(p=>{C.register(p,i.path,i.handler,i);});}),n.static){let i=Array.isArray(n.static)?n.static:[n.static];for(let d of i)try{let l=new q(d).handler(),x={method:"GET",path:d.path==="/"?"/*":`${d.path}/*`,handler:l};h.push(x),d.path==="/"?(C.register("GET","/",l,x),C.register("HEAD","/",l,x),C.register("GET","/*",l,x),C.register("HEAD","/*",l,x)):(C.register("GET",`${d.path}/*`,l,x),C.register("HEAD",`${d.path}/*`,l,x));}catch(p){throw a?.error({error:String(p),path:d.path},"Failed to initialize static file server"),p}}h.push(u,g),C.register("GET","/health",u.handler,u),C.register("GET","/readiness",g.handler,g);let f=null,R={app:null,logger:a,db:m,bunServer:null,async start(){if(n.database){let d=Array.isArray(n.database)?n.database:[n.database];for(let p of d){let l=p.name||"default";try{if(typeof p.connection=="string"){let x=new ee.DB(p.connection);if(p.schema&&typeof p.schema=="object")for(let[,S]of Object.entries(p.schema))S&&typeof S=="object"&&x.defineSchema(S);m.set(l,x),a?.info({name:l,connection:p.connection},"\u2714 Database connected");}else throw new Error(`Database connection must be a string path (got ${typeof p.connection})`)}catch(x){throw a?.error({error:String(x),name:l},"Failed to connect to database"),x}}}f=Bun.serve({port:e,hostname:r,fetch:(d,p)=>H(d,p)}),R.bunServer=f;let i=`http://${r}:${e}`;a?.info({url:i},"Server started");},async stop(){if(a?.info("Stopping server..."),y.size>0){a?.info({count:y.size},"Waiting for active requests...");let i=Date.now()+o;for(;y.size>0&&Date.now()<i;)await new Promise(d=>setTimeout(d,100));y.size>0&&a?.warn({count:y.size},"Force closing with active requests");}if(clearInterval(A),n.onShutdown)try{await n.onShutdown();}catch(i){a?.error({error:String(i)},"Error in shutdown handler");}for(let[i,d]of m.entries())try{d&&typeof d.close=="function"&&d.close(),a?.info({name:i},"Database closed");}catch(p){a?.error({error:String(p),name:i},"Error closing database");}f&&typeof f.stop=="function"&&(f.stop(),a?.info("Bun server stopped")),a?.info("Server stopped successfully");},addRoute(i){h.push(i),(Array.isArray(i.method)?i.method:[i.method]).forEach(p=>{C.register(p,i.path,i.handler,i);}),a?.info({method:i.method,path:i.path},"Route added");},addRoutes(i){i.forEach(d=>this.addRoute(d));},getRoutes(){return h}};return R}async function de(n,e,r){let t=n.headers.get("content-type")||"";try{if(t.includes("application/json")){let s=await n.text();if(s.length>r)throw new k("Payload too large");if(!s.trim())return {};try{return JSON.parse(s)}catch(o){throw e?.warn({error:String(o),bodyPreview:s.substring(0,100)},"Invalid JSON in request body"),new k("Invalid JSON in request body")}}if(t.includes("application/x-www-form-urlencoded")){let s=await n.text();if(s.length>r)throw new k("Payload too large");return Object.fromEntries(new URLSearchParams(s))}if(t.includes("multipart/form-data"))return await n.formData()}catch(s){throw s instanceof k?s:(e?.error({error:String(s)},"Error parsing request body"),new k("Failed to parse request body"))}return {}}function le(n){let e=new Map;if(!n)return e;let r=n.split(";");for(let t of r){let[s,...o]=t.trim().split("=");if(s){let c=o.join("=");e.set(s,c?decodeURIComponent(c):"");}}return e}function Y(n,e,r,t,s,o){let c=new URL(e.url),a=Object.fromEntries(c.searchParams),m=e.headers,h=200,y=new Map,A=le(m.get("cookie")||""),H={ip:n,request:e,params:r,query:a,headers:m,db:t,logger:s,requestId:o,get statusCode(){return h},set statusCode(u){h=u;},body:null,json(u,g){return new Response(JSON.stringify(u),{status:g??h,headers:{"Content-Type":"application/json",...this._setCookieHeaders()}})},text(u,g){return new Response(u,{status:g??h,headers:{"Content-Type":"text/plain",...this._setCookieHeaders()}})},html(u,g){return new Response(u,{status:g??h,headers:{"Content-Type":"text/html; charset=utf-8",...this._setCookieHeaders()}})},redirect(u,g=302){return new Response(null,{status:g,headers:{Location:u,...this._setCookieHeaders()}})},file(u,g="application/octet-stream"){let f=Bun.file(u);return new Response(f,{headers:{"Content-Type":g,...this._setCookieHeaders()}})},setCookie(u,g,f={}){let R=`${u}=${encodeURIComponent(g)}`;return f.maxAge!==void 0&&(R+=`; Max-Age=${f.maxAge}`),f.expires&&(R+=`; Expires=${f.expires.toUTCString()}`),f.path&&(R+=`; Path=${f.path}`),f.domain&&(R+=`; Domain=${f.domain}`),f.secure&&(R+="; Secure"),f.httpOnly&&(R+="; HttpOnly"),f.sameSite&&(R+=`; SameSite=${f.sameSite}`),y.set(u,R),H},getCookie(u){return A.get(u)},deleteCookie(u,g={}){return H.setCookie(u,"",{...g,maxAge:0,path:g.path||"/"})},setHeader(u,g){return m.set(u,g),H},getHeader(u){return m.get(u)||void 0},status(u){return h=u,H},_setCookieHeaders(){let u={};return y.size>0&&(u["Set-Cookie"]=Array.from(y.values())),u}};return H}function pe(n,e){let r=n.headers.get("x-forwarded-for");if(r)return r.split(",").map(o=>o.trim())[0]||"unknown";let t=n.headers.get("x-real-ip");if(t)return t;if(e)try{let o=e.requestIP?.(n);if(o?.address)return o.address}catch{}return "unknown"}function ge(n,e){let r=new Headers;if(!e.security||typeof e.security!="object"||!e.security.cors)return r;let t=typeof e.security.cors=="object"?e.security.cors:{},s=n.headers.get("Origin");if(s){typeof t.origin=="function"?t.origin(s)&&r.set("Access-Control-Allow-Origin",s):Array.isArray(t.origin)?t.origin.includes(s)&&r.set("Access-Control-Allow-Origin",s):typeof t.origin=="string"?r.set("Access-Control-Allow-Origin",t.origin):r.set("Access-Control-Allow-Origin",s);let o=t.methods||["GET","POST","PUT","DELETE","PATCH","OPTIONS"];r.set("Access-Control-Allow-Methods",o.join(", "));let c=t.allowedHeaders||["Content-Type","Authorization","X-Requested-With"];r.set("Access-Control-Allow-Headers",c.join(", ")),t.credentials&&r.set("Access-Control-Allow-Credentials","true"),t.maxAge&&r.set("Access-Control-Max-Age",t.maxAge.toString());}return r}var Se=ue;
2
- export{T as AppError,K as DatabaseError,Z as RateLimitError,O as Router,j as SecurityManager,q as StaticFileServer,z as TimeoutError,k as ValidationError,ie as createStatic,Se as default,ue as server};//# sourceMappingURL=main.js.map
1
+ import*as ne from'@je-es/sdb';export{DB,blob,column,defaultValue,integer,notNull,numeric,primaryKey,real,references,table,text,unique}from'@je-es/sdb';import oe from'crypto';import {Logger}from'@je-es/slog';export{Logger}from'@je-es/slog';import {resolve,join,relative,extname}from'path';import {existsSync,statSync}from'fs';var $=class{constructor(){this.routes=new Map;this.regexRoutes=[];}match(e,r){let t=`${e}:${r}`;if(this.routes.has(t)){let n=this.routes.get(t);return {handler:n.handler,params:{},metadata:n.metadata}}for(let n of this.regexRoutes)if(n.method===e){let i=r.match(n.pattern);if(i){let c=i.groups||{};return {handler:n.handler,params:c,metadata:n.metadata}}}return null}getAll(){let e=Array.from(this.routes.entries()).map(([t,n])=>{let i=t.indexOf(":"),c=t.substring(0,i),a=t.substring(i+1);return {method:c,path:a,handler:n.handler}}),r=this.regexRoutes.map(t=>{let n=t.key.indexOf(":");return {method:t.method,path:t.key.substring(n+1),handler:t.handler}});return [...e,...r]}clear(){this.routes.clear(),this.regexRoutes=[];}remove(e,r){let t=`${e}:${r}`;if(this.routes.has(t))return this.routes.delete(t),true;let n=this.regexRoutes.findIndex(i=>i.key===t);return n>=0?(this.regexRoutes.splice(n,1),true):false}register(e,r,t,n={}){let i=`${e}:${r}`;if(r.includes(":")||r.includes("*")){let c=this.pathToRegex(r),a=this.regexRoutes.findIndex(b=>b.key===i),h={pattern:c,method:e,handler:t,key:i,metadata:n};a>=0?this.regexRoutes[a]=h:this.regexRoutes.push(h);}else this.routes.set(i,{handler:t,metadata:n});}pathToRegex(e){let r=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&");return r=r.replace(/:(\w+)/g,"(?<$1>[^/]+)"),r=r.replace(/\*/g,".*"),new RegExp(`^${r}$`)}};var N=class{constructor(){this.rateLimitStore=new Map;this.csrfTokens=new Map;this.requestLog=new Map;this.MAX_REQUEST_LOG_SIZE=1e3;}checkRateLimit(e,r,t){let n=Date.now(),i=this.rateLimitStore.get(e);return i?n<i.reset?i.count>=r?false:(i.count++,true):(this.rateLimitStore.set(e,{count:1,reset:n+t}),true):(this.rateLimitStore.set(e,{count:1,reset:n+t}),true)}cleanupRateLimit(){let e=Date.now();for(let[r,t]of this.rateLimitStore.entries())e>t.reset&&this.rateLimitStore.delete(r);}generateCsrfToken(e,r=36e5){let t=oe.randomBytes(32).toString("hex");return this.csrfTokens.set(t,{sessionId:e,expires:Date.now()+r}),t}validateCsrfToken(e,r){let t=this.csrfTokens.get(e);return t?Date.now()>t.expires?(this.csrfTokens.delete(e),false):t.sessionId===r?(this.csrfTokens.delete(e),true):false:false}cleanupCsrfTokens(){let e=Date.now();for(let[r,t]of this.csrfTokens.entries())e>t.expires&&this.csrfTokens.delete(r);}sanitizeHtml(e){return e?e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;"):""}sanitizeSql(e){return e?e.replace(/\\/g,"\\\\").replace(/;/g,"").replace(/'/g,"''").replace(/"/g,'\\"').replace(/\u0000/g,""):""}logRequest(e,r,t,n,i,c){if(this.requestLog.set(e,{timestamp:new Date().toISOString(),method:r,path:t,ip:n,status:i,duration:c}),this.requestLog.size>this.MAX_REQUEST_LOG_SIZE){let{value:a}=this.requestLog.keys().next()||{value:null};a&&this.requestLog.delete(a);}}getRequestLog(e){return this.requestLog.get(e)}getAllRequestLogs(){return Array.from(this.requestLog.values())}clearAll(){this.rateLimitStore.clear(),this.csrfTokens.clear(),this.requestLog.clear();}getStats(){return {rateLimitEntries:this.rateLimitStore.size,csrfTokens:this.csrfTokens.size,requestLogs:this.requestLog.size}}};var L=class extends Error{constructor(r,t=500,n){super(r);this.message=r;this.statusCode=t;this.code=n;this.name="AppError";}},P=class extends L{constructor(r,t){super(r,400,"VALIDATION_ERROR");this.issues=t;this.name="ValidationError";}},Y=class extends L{constructor(e){super(e,500,"DATABASE_ERROR"),this.name="DatabaseError";}},_=class extends L{constructor(e="Request timeout"){super(e,408,"TIMEOUT_ERROR"),this.name="TimeoutError";}},ee=class extends L{constructor(e="Too many requests"){super(e,429,"RATE_LIMIT_ERROR"),this.name="RateLimitError";}};var I=class{constructor(e){this.fileCache=new Map;this.CACHE_MAX_SIZE=1e3;if(!existsSync(e.directory))throw new Error(`Static directory does not exist: ${e.directory}`);if(!statSync(e.directory).isDirectory())throw new Error(`Static path is not a directory: ${e.directory}`);this.resolvedDir=resolve(e.directory),this.config={path:e.path,directory:e.directory,maxAge:e.maxAge??3600,index:e.index??["index.html"],dotfiles:e.dotfiles??"deny",etag:e.etag??true,lastModified:e.lastModified??true,immutable:e.immutable??false,extensions:e.extensions??[],fallthrough:e.fallthrough??false,setHeaders:e.setHeaders};}handler(){return async e=>{let r=e.request.url,n=new URL(r).pathname;n.startsWith(this.config.path)&&(n=n.slice(this.config.path.length));try{n=decodeURIComponent(n);}catch{return e.json({error:"Invalid URL encoding"},400)}if(n.includes("..")||n.includes("\\"))return e.json({error:"Forbidden"},403);if(this.config.dotfiles!=="allow"&&n.split("/").some(a=>a.startsWith(".")))return this.config.dotfiles==="deny"?e.json({error:"Forbidden"},403):this.handleNotFound(e);let i=this.resolveFilePath(n);if(!i)return this.handleNotFound(e);if(!this.isPathSafe(i))return e.json({error:"Forbidden"},403);if(!existsSync(i))return this.handleNotFound(e);let c=statSync(i);return c.isDirectory()?this.serveDirectory(e,i,n):this.serveFile(e,i,c)}}getPathPattern(){return `${this.config.path}/*`}resolveFilePath(e){e.startsWith("/")&&(e=e.slice(1));let r=join(this.resolvedDir,e);if(!existsSync(r)&&this.config.extensions.length>0)for(let t of this.config.extensions){let n=`${r}.${t}`;if(existsSync(n))return n}return r}isPathSafe(e){return !relative(this.resolvedDir,resolve(e)).startsWith("..")&&!resolve(e).startsWith("..")}async serveDirectory(e,r,t){for(let n of this.config.index){let i=join(r,n);if(existsSync(i)){let c=statSync(i);if(c.isFile())return this.serveFile(e,i,c)}}return this.handleNotFound(e)}async serveFile(e,r,t){let n=e.request.method.toUpperCase();if(n!=="GET"&&n!=="HEAD")return e.json({error:"Method not allowed"},405);let i=r,c=this.fileCache.get(i);if(c&&c.mtime!==t.mtimeMs&&(c=void 0),!c){if(c={etag:this.generateEtag(t),lastModified:new Date(t.mtime),size:t.size,mtime:t.mtimeMs},this.fileCache.size>=this.CACHE_MAX_SIZE){let H=this.fileCache.keys().next().value;H&&this.fileCache.delete(H);}this.fileCache.set(i,c);}let a=e.request.headers.get("if-none-match"),h=e.request.headers.get("if-modified-since");if(this.config.etag&&a===c.etag)return new Response(null,{status:304,headers:this.buildHeaders(r,c)});if(this.config.lastModified&&h){let H=new Date(h);if(c.lastModified<=H)return new Response(null,{status:304,headers:this.buildHeaders(r,c)})}let b=Bun.file(r),R=this.buildHeaders(r,c);return this.config.setHeaders&&this.config.setHeaders(e,r),n==="HEAD"?new Response(null,{status:200,headers:R}):new Response(b,{status:200,headers:R})}buildHeaders(e,r){let t=new Headers,n=this.getMimeType(e);if(t.set("Content-Type",n),t.set("Content-Length",r.size.toString()),this.config.etag&&t.set("ETag",r.etag),this.config.lastModified&&t.set("Last-Modified",r.lastModified.toUTCString()),this.config.maxAge>0){let i=`public, max-age=${this.config.maxAge}`;this.config.immutable&&(i+=", immutable"),t.set("Cache-Control",i);}else t.set("Cache-Control","no-cache");return t.set("Accept-Ranges","bytes"),t}generateEtag(e){return `"${e.size.toString(16)}-${e.mtimeMs.toString(16)}"`}getMimeType(e){let r=extname(e).toLowerCase();return de[r]||"application/octet-stream"}handleNotFound(e){return this.config.fallthrough?e.json({error:"Not Found"},404):e.json({error:"Not Found"},404)}clearCache(){this.fileCache.clear();}getCacheStats(){return {entries:this.fileCache.size,maxSize:this.CACHE_MAX_SIZE}}};function ue(o){return new I(o)}var de={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".txt":"text/plain; charset=utf-8",".xml":"text/xml; charset=utf-8",".csv":"text/csv; charset=utf-8",".md":"text/markdown; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".jsonld":"application/ld+json",".map":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".ico":"image/x-icon",".webp":"image/webp",".avif":"image/avif",".bmp":"image/bmp",".tiff":"image/tiff",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".mp3":"audio/mpeg",".wav":"audio/wav",".ogg":"audio/ogg",".m4a":"audio/mp4",".aac":"audio/aac",".flac":"audio/flac",".mp4":"video/mp4",".webm":"video/webm",".ogv":"video/ogg",".mov":"video/quicktime",".avi":"video/x-msvideo",".mkv":"video/x-matroska",".pdf":"application/pdf",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".zip":"application/zip",".rar":"application/x-rar-compressed",".7z":"application/x-7z-compressed",".tar":"application/x-tar",".gz":"application/gzip",".wasm":"application/wasm",".manifest":"text/cache-manifest",".webmanifest":"application/manifest+json"};var U=new N,A=new $;function pe(o={}){let e=Number(o.port)||3e3,r=o.hostname||"localhost",t=o.maxRequestSize||10*1024*1024,n=o.requestTimeout||3e4,i=o.gracefulShutdownTimeout||1e4,c=typeof o.logging=="object"?o.logging:{},a=o.logging?new Logger(c.level||"info",c.pretty):null,h=new Map,b=[],R=new Set,H=setInterval(()=>{U.cleanupRateLimit(),U.cleanupCsrfTokens();},120*1e3);async function M(s,l){let p=Date.now(),u=crypto.randomUUID(),v=new URL(s.url).pathname,E=s.method.toUpperCase(),C=fe(s,l);R.add(u);try{let x=s.headers.get("content-length");if(x&&parseInt(x)>t)return a?.warn({requestId:u,size:x,ip:C},"Request too large"),new Response(JSON.stringify({error:"Payload too large"}),{status:413,headers:{"Content-Type":"application/json"}});let q=he(s,o);if(E==="OPTIONS")return new Response(null,{status:204,headers:q});if(o.security&&typeof o.security=="object"&&o.security.rateLimit){let T=typeof o.security.rateLimit=="object"?o.security.rateLimit:{},j=T.max||100,B=T.windowMs||6e4,Q=T.keyGenerator?T.keyGenerator({request:s,ip:C}):C;if(!U.checkRateLimit(Q,j,B))return a?.warn({requestId:u,ip:C,key:Q},"Rate limit exceeded"),new Response(JSON.stringify({error:T.message||"Too many requests"}),{status:429,headers:{"Content-Type":"application/json"}})}let S=null;["POST","PUT","PATCH"].includes(E)&&(S=await ge(s,a,t));let k=h.get("default"),y=A.match(E,v);if(!y){let T=re(C,s,{},k,a,u);return a?.warn({requestId:u,method:E,path:v,ip:C},"Route not found"),T.json({error:"Not Found",path:v},404)}let F=re(C,s,y.params||{},k,a,u);F.body=S,F.request=s;let W=new AbortController,se=new Promise((T,j)=>{let B=setTimeout(()=>{W.abort(),j(new _("Request timeout"));},n);W.signal.addEventListener("abort",()=>clearTimeout(B));}),K=y.metadata?.middlewares||[],G;K.length>0?G=d(F,K,y.handler):G=Promise.resolve(y.handler(F));let O=await Promise.race([G,se]),D=new Headers(O.headers);q.forEach((T,j)=>{D.has(j)||D.set(j,T);}),D.set("X-Request-ID",u),D.set("X-Content-Type-Options","nosniff"),D.set("X-Frame-Options","DENY"),D.set("X-XSS-Protection","1; mode=block"),D.set("Referrer-Policy","strict-origin-when-cross-origin");let Z=Date.now()-p;return U.logRequest(u,E,v,C,O.status,Z),a?.info({requestId:u,method:E,path:v,status:O.status,duration:Z,ip:C},"Request completed"),new Response(O.body,{status:O.status,headers:D})}catch(x){if(x instanceof L)return a?.warn({error:x.message,requestId:u,ip:C},`App error: ${x.message}`),new Response(JSON.stringify({error:x.message,code:x.code,requestId:u}),{status:x.statusCode,headers:{"Content-Type":"application/json"}});a?.error({error:String(x),requestId:u,ip:C},"Unhandled error");let q=process.env.NODE_ENV==="production"?"Internal Server Error":x.message;return new Response(JSON.stringify({error:q,requestId:u}),{status:500,headers:{"Content-Type":"application/json"}})}finally{R.delete(u);}}async function d(s,l,p){let u=0,g=null,v=s.json.bind(s),E=s.text.bind(s),C=s.html.bind(s),x=s.redirect.bind(s);s.json=function(S,k){let y=v(S,k);return g=y,y},s.text=function(S,k){let y=E(S,k);return g=y,y},s.html=function(S,k){let y=C(S,k);return g=y,y},s.redirect=function(S,k){let y=x(S,k);return g=y,y};async function q(){if(!g&&u<l.length){let S=l[u];u++,await S(s,q);}}return await q(),s.json=v,s.text=E,s.html=C,s.redirect=x,g||p(s)}let m={method:"GET",path:"/health",handler:s=>s.json({status:"healthy",timestamp:new Date().toISOString(),uptime:process.uptime(),activeRequests:R.size})},f={method:"GET",path:"/readiness",handler:s=>{let l=h.size>0,p=l||h.size===0;return s.json({ready:p,checks:{database:l?"connected":"not configured",activeRequests:R.size},timestamp:new Date().toISOString()},p?200:503)}};if(o.routes&&o.routes.forEach(s=>{b.push(s),(Array.isArray(s.method)?s.method:[s.method]).forEach(p=>{A.register(p,s.path,s.handler,s);});}),o.static){let s=Array.isArray(o.static)?o.static:[o.static];for(let l of s)try{let u=new I(l).handler(),g={method:"GET",path:l.path==="/"?"/*":`${l.path}/*`,handler:u};b.push(g),l.path==="/"?(A.register("GET","/",u,g),A.register("HEAD","/",u,g),A.register("GET","/*",u,g),A.register("HEAD","/*",u,g)):(A.register("GET",`${l.path}/*`,u,g),A.register("HEAD",`${l.path}/*`,u,g));}catch(p){throw a?.error({error:String(p),path:l.path},"Failed to initialize static file server"),p}}b.push(m,f),A.register("GET","/health",m.handler,m),A.register("GET","/readiness",f.handler,f);let w=null,J={app:null,logger:a,db:h,bunServer:null,async start(){if(o.database){let l=Array.isArray(o.database)?o.database:[o.database];for(let p of l){let u=p.name||"default";try{if(typeof p.connection=="string"){let g=new ne.DB(p.connection);if(p.schema&&typeof p.schema=="object")for(let[,v]of Object.entries(p.schema))v&&typeof v=="object"&&g.defineSchema(v);h.set(u,g),a?.info({name:u,connection:p.connection},"\u2714 Database connected");}else throw new Error(`Database connection must be a string path (got ${typeof p.connection})`)}catch(g){throw a?.error({error:String(g),name:u},"Failed to connect to database"),g}}}w=Bun.serve({port:e,hostname:r,fetch:(l,p)=>M(l,p)}),J.bunServer=w;let s=`http://${r}:${e}`;a?.info({url:s},"\u2714 Server started");},async stop(){if(a?.info("Stopping server..."),R.size>0){a?.info({count:R.size},"Waiting for active requests...");let s=Date.now()+i;for(;R.size>0&&Date.now()<s;)await new Promise(l=>setTimeout(l,100));R.size>0&&a?.warn({count:R.size},"Force closing with active requests");}if(clearInterval(H),o.onShutdown)try{await o.onShutdown();}catch(s){a?.error({error:String(s)},"Error in shutdown handler");}for(let[s,l]of h.entries())try{l&&typeof l.close=="function"&&l.close(),a?.info({name:s},"Database closed");}catch(p){a?.error({error:String(p),name:s},"Error closing database");}w&&typeof w.stop=="function"&&(w.stop(),a?.info("Bun server stopped")),a?.info("Server stopped successfully");},addRoute(s){b.push(s),(Array.isArray(s.method)?s.method:[s.method]).forEach(p=>{A.register(p,s.path,s.handler,s);}),a?.info({method:s.method,path:s.path},"Route added");},addRoutes(s){s.forEach(l=>this.addRoute(l));},getRoutes(){return b}};return J}async function ge(o,e,r){let t=o.headers.get("content-type")||"";try{if(t.includes("application/json")){let n=await o.text();if(n.length>r)throw new P("Payload too large");if(!n.trim())return {};try{return JSON.parse(n)}catch(i){throw e?.warn({error:String(i),bodyPreview:n.substring(0,100)},"Invalid JSON in request body"),new P("Invalid JSON in request body")}}if(t.includes("application/x-www-form-urlencoded")){let n=await o.text();if(n.length>r)throw new P("Payload too large");return Object.fromEntries(new URLSearchParams(n))}if(t.includes("multipart/form-data"))return await o.formData()}catch(n){throw n instanceof P?n:(e?.error({error:String(n)},"Error parsing request body"),new P("Failed to parse request body"))}return {}}function me(o){let e=new Map;if(!o)return e;let r=o.split(";");for(let t of r){let[n,...i]=t.trim().split("=");if(n){let c=i.join("=");e.set(n,c?decodeURIComponent(c):"");}}return e}function re(o,e,r,t,n,i){let c=new URL(e.url),a=Object.fromEntries(c.searchParams),h=e.headers,b=200,R=new Map,H=me(h.get("cookie")||""),M={ip:o,request:e,params:r,query:a,headers:h,db:t,logger:n,requestId:i,get statusCode(){return b},set statusCode(d){b=d;},body:null,json(d,m){return new Response(JSON.stringify(d),{status:m??b,headers:{"Content-Type":"application/json",...this._setCookieHeaders()}})},text(d,m){return new Response(d,{status:m??b,headers:{"Content-Type":"text/plain",...this._setCookieHeaders()}})},html(d,m){return new Response(d,{status:m??b,headers:{"Content-Type":"text/html; charset=utf-8",...this._setCookieHeaders()}})},redirect(d,m=302){return new Response(null,{status:m,headers:{Location:d,...this._setCookieHeaders()}})},file(d,m="application/octet-stream"){let f=Bun.file(d);return new Response(f,{headers:{"Content-Type":m,...this._setCookieHeaders()}})},setCookie(d,m,f={}){let w=`${d}=${encodeURIComponent(m)}`;return f.maxAge!==void 0&&(w+=`; Max-Age=${f.maxAge}`),f.expires&&(w+=`; Expires=${f.expires.toUTCString()}`),f.path&&(w+=`; Path=${f.path}`),f.domain&&(w+=`; Domain=${f.domain}`),f.secure&&(w+="; Secure"),f.httpOnly&&(w+="; HttpOnly"),f.sameSite&&(w+=`; SameSite=${f.sameSite}`),R.set(d,w),M},getCookie(d){return H.get(d)},deleteCookie(d,m={}){return M.setCookie(d,"",{...m,maxAge:0,path:m.path||"/"})},setHeader(d,m){return h.set(d,m),M},getHeader(d){return h.get(d)||void 0},status(d){return b=d,M},_setCookieHeaders(){let d={};return R.size>0&&(d["Set-Cookie"]=Array.from(R.values())),d}};return M}function fe(o,e){let r=o.headers.get("x-forwarded-for");if(r)return r.split(",").map(i=>i.trim())[0]||"unknown";let t=o.headers.get("x-real-ip");if(t)return t;if(e)try{let i=e.requestIP?.(o);if(i?.address)return i.address}catch{}return "unknown"}function he(o,e){let r=new Headers;if(!e.security||typeof e.security!="object"||!e.security.cors)return r;let t=typeof e.security.cors=="object"?e.security.cors:{},n=o.headers.get("Origin");if(n){typeof t.origin=="function"?t.origin(n)&&r.set("Access-Control-Allow-Origin",n):Array.isArray(t.origin)?t.origin.includes(n)&&r.set("Access-Control-Allow-Origin",n):typeof t.origin=="string"?r.set("Access-Control-Allow-Origin",t.origin):r.set("Access-Control-Allow-Origin",n);let i=t.methods||["GET","POST","PUT","DELETE","PATCH","OPTIONS"];r.set("Access-Control-Allow-Methods",i.join(", "));let c=t.allowedHeaders||["Content-Type","Authorization","X-Requested-With"];r.set("Access-Control-Allow-Headers",c.join(", ")),t.credentials&&r.set("Access-Control-Allow-Credentials","true"),t.maxAge&&r.set("Access-Control-Max-Age",t.maxAge.toString());}return r}var Ee=pe;
2
+ export{L as AppError,Y as DatabaseError,ee as RateLimitError,$ as Router,N as SecurityManager,I as StaticFileServer,_ as TimeoutError,P as ValidationError,ue as createStatic,Ee as default,pe as server};//# sourceMappingURL=main.js.map
3
3
  //# sourceMappingURL=main.js.map