@sourceregistry/node-webserver 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,9 +31,9 @@ import { WebServer, json, text } from "@sourceregistry/node-webserver";
31
31
 
32
32
  const app = new WebServer();
33
33
 
34
- app.router.GET("/", () => text("hello world"));
34
+ app.GET("/", () => text("hello world"));
35
35
 
36
- app.router.GET("/health", () => json({
36
+ app.GET("/health", () => json({
37
37
  ok: true
38
38
  }));
39
39
 
@@ -52,6 +52,8 @@ import { WebServer } from "@sourceregistry/node-webserver";
52
52
  const app = new WebServer();
53
53
  ```
54
54
 
55
+ `WebServer` extends `Router`, so you can register routes and middleware directly on `app`.
56
+
55
57
  You can also pass handler callbacks for `locals` and `platform`:
56
58
 
57
59
  ```ts
@@ -69,15 +71,15 @@ const app = new WebServer({
69
71
  ### Register routes
70
72
 
71
73
  ```ts
72
- app.router.GET("/users", async () => {
74
+ app.GET("/users", async () => {
73
75
  return new Response("all users");
74
76
  });
75
77
 
76
- app.router.GET("/users/[id]", async (event) => {
78
+ app.GET("/users/[id]", async (event) => {
77
79
  return new Response(`user ${event.params.id}`);
78
80
  });
79
81
 
80
- app.router.POST("/users", async (event) => {
82
+ app.POST("/users", async (event) => {
81
83
  const body = await event.request.json();
82
84
  return json({ created: true, body }, { status: 201 });
83
85
  });
@@ -103,7 +105,7 @@ const api = new Router();
103
105
 
104
106
  api.GET("/status", () => new Response("ok"));
105
107
 
106
- app.router.use("/api", api);
108
+ app.use("/api", api);
107
109
  ```
108
110
 
109
111
  ### Response helpers
@@ -113,9 +115,9 @@ The library exports helpers for common content types:
113
115
  ```ts
114
116
  import { html, json, text } from "@sourceregistry/node-webserver";
115
117
 
116
- app.router.GET("/", () => html("<h1>Hello</h1>"));
117
- app.router.GET("/message", () => text("plain text"));
118
- app.router.GET("/data", () => json({ ok: true }));
118
+ app.GET("/", () => html("<h1>Hello</h1>"));
119
+ app.GET("/message", () => text("plain text"));
120
+ app.GET("/data", () => json({ ok: true }));
119
121
  ```
120
122
 
121
123
  ## Request Handling
@@ -123,7 +125,7 @@ app.router.GET("/data", () => json({ ok: true }));
123
125
  Route handlers receive a web-standard `Request` plus extra routing data:
124
126
 
125
127
  ```ts
126
- app.router.GET("/posts/[slug]", async (event) => {
128
+ app.GET("/posts/[slug]", async (event) => {
127
129
  const userAgent = event.request.headers.get("user-agent");
128
130
  const slug = event.params.slug;
129
131
  const ip = event.getClientAddress();
@@ -179,7 +181,7 @@ The server will use those `App.Locals` and `App.Platform` definitions automatica
179
181
  Middleware wraps request handling and can short-circuit the chain.
180
182
 
181
183
  ```ts
182
- app.router.useMiddleware(async (event, next) => {
184
+ app.useMiddleware(async (event, next) => {
183
185
  const startedAt = Date.now();
184
186
  const response = await next();
185
187
 
@@ -204,7 +206,7 @@ const requireApiKey = async (event, next) => {
204
206
  return next();
205
207
  };
206
208
 
207
- app.router.GET("/admin", () => new Response("secret"), requireApiKey);
209
+ app.GET("/admin", () => new Response("secret"), requireApiKey);
208
210
  ```
209
211
 
210
212
  ## Router Lifecycle Hooks
@@ -216,7 +218,7 @@ Use `pre()` for logic that should run before route resolution, and `post()` for
216
218
  `pre()` can short-circuit the request by returning a `Response`.
217
219
 
218
220
  ```ts
219
- app.router.pre(async (event) => {
221
+ app.pre(async (event) => {
220
222
  if (!event.request.headers.get("authorization")) {
221
223
  return new Response("Unauthorized", { status: 401 });
222
224
  }
@@ -228,7 +230,7 @@ app.router.pre(async (event) => {
228
230
  `post()` receives the final response and may replace it.
229
231
 
230
232
  ```ts
231
- app.router.post(async (_event, response) => {
233
+ app.post(async (_event, response) => {
232
234
  const nextResponse = new Response(response.body, response);
233
235
  nextResponse.headers.set("x-powered-by", "node-webserver");
234
236
  return nextResponse;
@@ -238,7 +240,7 @@ app.router.post(async (_event, response) => {
238
240
  ## Cookies
239
241
 
240
242
  ```ts
241
- app.router.GET("/login", async (event) => {
243
+ app.GET("/login", async (event) => {
242
244
  event.cookies.set("session", "abc123", {
243
245
  path: "/",
244
246
  httpOnly: true,
@@ -249,12 +251,12 @@ app.router.GET("/login", async (event) => {
249
251
  return new Response("logged in");
250
252
  });
251
253
 
252
- app.router.GET("/me", async (event) => {
254
+ app.GET("/me", async (event) => {
253
255
  const session = event.cookies.get("session");
254
256
  return json({ session });
255
257
  });
256
258
 
257
- app.router.POST("/logout", async (event) => {
259
+ app.POST("/logout", async (event) => {
258
260
  event.cookies.delete("session", {
259
261
  path: "/",
260
262
  httpOnly: true,
@@ -268,7 +270,7 @@ app.router.POST("/logout", async (event) => {
268
270
  ## WebSocket Routes
269
271
 
270
272
  ```ts
271
- app.router.WS("/ws/chat/[room]", async (event) => {
273
+ app.WS("/ws/chat/[room]", async (event) => {
272
274
  const room = event.params.room;
273
275
  const ws = event.websocket;
274
276
 
@@ -287,8 +289,8 @@ Use `dir()` to expose a directory through a route, or `serveStatic()` directly i
287
289
  ```ts
288
290
  import { dir } from "@sourceregistry/node-webserver";
289
291
 
290
- app.router.GET("/assets/[...path]", dir("./public/assets"));
291
- app.router.GET("/", dir("./public"));
292
+ app.GET("/assets/[...path]", dir("./public/assets"));
293
+ app.GET("/", dir("./public"));
292
294
  ```
293
295
 
294
296
  Manual usage:
@@ -296,7 +298,7 @@ Manual usage:
296
298
  ```ts
297
299
  import { serveStatic } from "@sourceregistry/node-webserver";
298
300
 
299
- app.router.GET("/downloads/[...path]", (event) => {
301
+ app.GET("/downloads/[...path]", (event) => {
300
302
  return serveStatic("./downloads", event, {
301
303
  cacheControl: "public, max-age=3600"
302
304
  });
@@ -341,7 +343,7 @@ Available options:
341
343
  ```ts
342
344
  import { CORS } from "@sourceregistry/node-webserver";
343
345
 
344
- app.router.useMiddleware(CORS.policy({
346
+ app.useMiddleware(CORS.policy({
345
347
  origin: ["https://app.example.com"],
346
348
  credentials: true,
347
349
  methods: ["GET", "POST", "DELETE"]
@@ -353,7 +355,7 @@ app.router.useMiddleware(CORS.policy({
353
355
  ```ts
354
356
  import { RateLimiter } from "@sourceregistry/node-webserver";
355
357
 
356
- app.router.useMiddleware(RateLimiter.fixedWindowLimit({
358
+ app.useMiddleware(RateLimiter.fixedWindowLimit({
357
359
  windowMs: 60_000,
358
360
  max: 100
359
361
  }));
@@ -373,7 +375,7 @@ const app = new WebServer({
373
375
  }
374
376
  });
375
377
 
376
- app.router.GET("/", () => new Response("secure"));
378
+ app.GET("/", () => new Response("secure"));
377
379
  app.listen(3443);
378
380
  ```
379
381
 
@@ -400,7 +402,7 @@ const app = new WebServer({
400
402
  }
401
403
  });
402
404
 
403
- app.router.pre(async (event) => {
405
+ app.pre(async (event) => {
404
406
  if (event.url.pathname.startsWith("/private")) {
405
407
  const auth = event.request.headers.get("authorization");
406
408
  if (!auth) {
@@ -409,7 +411,7 @@ app.router.pre(async (event) => {
409
411
  }
410
412
  });
411
413
 
412
- app.router.useMiddleware(
414
+ app.useMiddleware(
413
415
  CORS.policy({
414
416
  origin: "https://app.example.com",
415
417
  credentials: true
@@ -420,16 +422,16 @@ app.router.useMiddleware(
420
422
  })
421
423
  );
422
424
 
423
- app.router.GET("/", () => text("hello"));
425
+ app.GET("/", () => text("hello"));
424
426
 
425
- app.router.GET("/users/[id]", (event) => {
427
+ app.GET("/users/[id]", (event) => {
426
428
  return json({
427
429
  id: event.params.id,
428
430
  requestId: event.locals.startedAt
429
431
  });
430
432
  });
431
433
 
432
- app.router.post(async (_event, response) => {
434
+ app.post(async (_event, response) => {
433
435
  const nextResponse = new Response(response.body, response);
434
436
  nextResponse.headers.set("x-server", "node-webserver");
435
437
  return nextResponse;
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=require("node:fs"),A=require("node:fs/promises"),S=require("node:path"),q=require("node:stream"),U=require("http"),F=require("https"),z=require("tls"),T=require("stream"),b=require("ws"),C=require("cookie"),B={".avif":"image/avif",".css":"text/css; charset=utf-8",".gif":"image/gif",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".jpg":"image/jpeg",".jpeg":"image/jpeg",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".pdf":"application/pdf",".png":"image/png",".svg":"image/svg+xml; charset=utf-8",".txt":"text/plain; charset=utf-8",".wasm":"application/wasm",".webp":"image/webp",".xml":"application/xml; charset=utf-8"},$={index:"index.html",cacheControl:"public, max-age=0",dotFiles:"ignore"};async function N(a,e,t={}){const s=K(e),r={...$,...t},n=await X(a),o=G(s,r.dotFiles);if(o instanceof Response)return o;const i=o.length>0?o.join(S.sep):"",u=S.resolve(n,i);if(!j(n,u))return new Response("Forbidden",{status:403});const c=await J(u,n,r.index);if(c instanceof Response)return c;const l=await A.stat(c),d=new Headers({"content-length":String(l.size),"content-type":V(c),"cache-control":r.cacheControl,"last-modified":l.mtime.toUTCString(),"x-content-type-options":"nosniff"});if(t.headers){const h=typeof t.headers=="function"?t.headers(c,l):t.headers;new Headers(h).forEach((f,y)=>{d.set(y,f)})}return new Response(q.Readable.toWeb(D.createReadStream(c)),{status:200,headers:d})}async function X(a){return A.realpath(a)}function G(a,e){if(a.includes("\0"))return new Response("Bad Request",{status:400});const t=a.replace(/\\/g,"/").split("/").filter(Boolean),s=[];for(const r of t){let n;try{n=decodeURIComponent(r)}catch{return new Response("Bad Request",{status:400})}if(!(!n||n===".")){if(n===".."||n.includes("/")||n.includes("\\")||n.includes("\0"))return new Response("Forbidden",{status:403});if(n.startsWith(".")){if(e==="deny")return new Response("Forbidden",{status:403});if(e!=="allow")return new Response("Not Found",{status:404})}s.push(n)}}return s}async function J(a,e,t){try{if((await A.lstat(a)).isDirectory()){const r=S.resolve(a,t);return O(r,e)}return O(a,e)}catch{return new Response("Not Found",{status:404})}}async function O(a,e){try{const t=await A.realpath(a);return j(e,t)?(await A.stat(t)).isFile()?t:new Response("Not Found",{status:404}):new Response("Forbidden",{status:403})}catch{return new Response("Not Found",{status:404})}}function j(a,e){const t=S.relative(a,e);return t===""||!t.startsWith("..")&&!S.isAbsolute(t)}function V(a){return B[S.extname(a).toLowerCase()]??"application/octet-stream"}function K(a){return typeof a.params.path=="string"?a.params.path:a.url.pathname.replace(/^\/+/,"")}function W(a){return a instanceof Response&&a.status>=400&&a.status<600}function L(a){return a instanceof Response&&a.status>=300&&a.status<400}class Y{constructor(e){this.data=new Map,this.windowMs=e.windowMs,this.startCleanup()}async incr(e){const t=Date.now();let s=this.data.get(e);return!s||t>=s.reset?(s={count:1,reset:t+this.windowMs},this.data.set(e,s)):s.count++,{current:s.count,reset:s.reset}}startCleanup(){this.cleanupInterval=setInterval(()=>{const e=Date.now();for(const[t,{reset:s}]of this.data)e>=s&&this.data.delete(t)},Math.min(this.windowMs,3e5))}stop(){this.cleanupInterval&&clearInterval(this.cleanupInterval)}async resetAll(){this.data.clear()}}function Q(a){const{windowMs:e=6e4,max:t,key:s=c=>c.getClientAddress(),message:r="Too many requests, please try again later.",statusCode:n=429,headers:o="include",onRateLimit:i,store:u=new Y({windowMs:e})}=a;return async(c,l)=>{const d=`rl:${s(c)}`,{current:h,reset:f}=await u.incr(d),y=Math.ceil((f-Date.now())/1e3);if(h>t){i&&i(c,{current:h,max:t,key:d});const w={status:n,headers:new Headers},p=w.headers;o==="include"&&(p.set("X-RateLimit-Limit",String(t)),p.set("X-RateLimit-Remaining","0"),p.set("X-RateLimit-Reset",String(Math.floor(f/1e3))),p.set("Retry-After",String(y)));let m;return typeof r=="string"?(m=r,p.set("Content-Type","text/plain")):(m=JSON.stringify(r),p.set("Content-Type","application/json")),new Response(m,w)}if(c.rateLimit={current:h,limit:t,reset:new Date(f),remaining:t-h},o==="include"){const w={"X-RateLimit-Limit":String(t),"X-RateLimit-Remaining":String(t-h),"X-RateLimit-Reset":String(Math.floor(f/1e3))},p=c.setHeaders;c.setHeaders=m=>{p({...w,...m})},p(w)}return l()}}const Z=Object.freeze(Object.defineProperty({__proto__:null,fixedWindowLimit:Q},Symbol.toStringTag,{value:"Module"})),ee=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],te=["Accept","Accept-Language","Content-Language","Content-Type","Range"],se=["Authorization","X-Auth-Token","X-Requested-With","X-CSRF-Token","X-HTTP-Method-Override","X-Forwarded-For","X-Real-IP","X-Custom-Header"];function M(a,e){return!a||!e?!1:e==="*"?!0:e==="null"?a==="null":Array.isArray(e)?e.some(t=>M(a,t)):typeof e=="function"?e(a):e instanceof RegExp?e.test(a):a===e}function re(a,e){const{origin:t="*"}=e;return a?t==="*"?e.credentials?a:"*":M(a,t)?a:null:t==="*"?"*":null}function ne(a={}){const{methods:e=ee,allowedHeaders:t=se,exposedHeaders:s,credentials:r=!1,maxAge:n=86400,onResponse:o}=a,i="Origin,Access-Control-Request-Method,Access-Control-Request-Headers",u=e.join(","),c=[...te,...t].join(","),l=[["Vary",i],["Access-Control-Allow-Methods",u],["Access-Control-Allow-Headers",c]];return s&&l.push(["Access-Control-Expose-Headers",s.join(",")]),r&&l.push(["Access-Control-Allow-Credentials","true"]),n&&l.push(["Access-Control-Max-Age",n.toString()]),async(d,h)=>{const f=d.request,y=f.headers.get("Origin"),w=f.method==="OPTIONS"&&y!==null&&f.headers.has("Access-Control-Request-Method"),p=re(y,a);if(w){if(!p)return new Response(null,{status:403});const g=new Response(null,{status:204});for(const[E,I]of l)g.headers.set(E,I);return g.headers.set("Access-Control-Allow-Origin",p),g}const m=await h();if(!m)return;if(!p)return m;const x=new Response(m.body,m);for(const[g,E]of l)x.headers.set(g,E);x.headers.set("Access-Control-Allow-Origin",p);let P=x;if(o){const g=o(P);g&&(P=g)}return P}}const oe=Object.freeze(Object.defineProperty({__proto__:null,policy:ne},Symbol.toStringTag,{value:"Module"})),k=["GET","PUT","POST","DELETE","PATCH","HEAD","OPTIONS"];class v{static{this.cache=new Map}static get(e){return this.cache.get(e)}static set(e,t){this.cache.set(e,t)}}class H{constructor(){this._routes=[],this._wsRoutes=[],this._nestedRouters=[],this._middlewares=[],this._preHandlers=[],this._postHandlers=[],this.routesSorted=!1,this.wsRoutesSorted=!1}get routes(){return this._routes}get nestedRouters(){return this._nestedRouters}GET(e,t,...s){return this.addHandler("GET",e,t,s)}POST(e,t,...s){return this.addHandler("POST",e,t,s)}PUT(e,t,...s){return this.addHandler("PUT",e,t,s)}PATCH(e,t,...s){return this.addHandler("PATCH",e,t,s)}DELETE(e,t,...s){return this.addHandler("DELETE",e,t,s)}HEAD(e,t,...s){return this.addHandler("HEAD",e,t,s)}OPTIONS(e,t,...s){return this.addHandler("OPTIONS",e,t,s)}USE(e,t,...s){return k.forEach(r=>this.addHandler(r,e,t,s)),this}action(e,t,...s){const r=async n=>{try{const o=await t(n);return this.formatActionResult(o)}catch(o){return this.handleActionError(o)}};return this.addHandler("POST",e,r,s)}use(e,t,...s){let r,n,o=s;Array.isArray(e)?([r,n]=e,o=e.length>2?e.slice(2):[]):(r=e,n=t);const i=this.normalizePrefix(r),{regex:u,paramNames:c,isCatchAll:l,priority:d}=this.createPrefixRegex(i);return this._nestedRouters.push({prefix:i,router:n,regex:u,paramNames:c,isCatchAll:l,priority:d,middlewares:o}),this}useMiddleware(...e){return this._middlewares.push(...e),this}pre(...e){return this._preHandlers.push(...e),this}post(...e){return this._postHandlers.push(...e),this}discard(e,t){return this._nestedRouters=this._nestedRouters.filter(s=>s.prefix!==e),this._routes=this._routes.filter(s=>s.path!==e||t&&s.method!==t),this}WS(e,t,...s){const{regex:r,paramNames:n,isCatchAll:o,priority:i}=this.createPathRegex(e);return this._wsRoutes.push({path:e,regex:r,paramNames:n,isCatchAll:o,priority:i,handler:t,middlewares:s}),this.wsRoutesSorted=!1,this}async canHandleWebSocket(e){return this.canHandleWebSocketAtPath(e,e.url.pathname)}async canHandleWebSocketAtPath(e,t){this.wsRoutesSorted||this.sortWsRoutes();for(const s of[...this._nestedRouters].sort((r,n)=>n.priority-r.priority)){const r=t.match(s.regex);if(!r||r.index!==0)continue;const n=r[0],o=t.slice(n.length)||"/",i={...e,params:{...e.params,...this.extractPrefixParams(s,n)}};if(await s.router.canHandleWebSocketAtPath(i,o))return!0}for(const s of this._wsRoutes)if(s.regex.test(t))return!0;return!1}async handleWebSocket(e,t){return this.handleWebSocketAtPath(e,t,e.url.pathname)}async handleWebSocketAtPath(e,t,s){this.wsRoutesSorted||this.sortWsRoutes();for(const r of[...this._nestedRouters].sort((n,o)=>o.priority-n.priority)){const n=s.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=s.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=[...this._middlewares,...r.middlewares],d=()=>r.router.handleWebSocketAtPath(c,t,i);if(await this.applyMiddlewaresWithList(c,l,d))return!0}for(const r of this._wsRoutes){if(!r.regex.test(s))continue;const n=s.match(r.regex);if(!n)continue;const o=Object.fromEntries(r.paramNames.map((l,d)=>[l,n[d+1]||""])),i={...e,params:{...e.params,...o},route:{...e.route,id:r.path},websocket:t},u=[...this._middlewares,...r.middlewares];if(await this.applyMiddlewaresWithList(i,u,()=>r.handler(i))===void 0)return!0}return!1}async handle(e){return this.handleAtPath(e,e.url.pathname)}async handleAtPath(e,t){const s=e.request.method;let r=await this.runPreHandlers(e);if(!r){const o=async()=>{const i=await this.handleNestedRouters(e,t);return i||(this.routesSorted||this.sortRoutes(),this.handleLocalRoutes(e,s,t))};r=await this.applyMiddlewaresWithList(e,this._middlewares,o)}const n=r||new Response("No Content",{status:204});return this.runPostHandlers(e,n)}async applyMiddlewaresWithList(e,t,s){const r=[...t],n=async o=>o>=r.length?s():r[o](e,()=>n(o+1));return n(0)}async runPreHandlers(e){for(const t of this._preHandlers){const s=await t(e);if(s instanceof Response)return s}}async runPostHandlers(e,t){let s=t;for(const r of this._postHandlers){const n=await r(e,s);n instanceof Response&&(s=n)}return s}addHandler(e,t,s,r=[]){const{regex:n,paramNames:o,isCatchAll:i,priority:u}=this.createPathRegex(t);return this._routes.push({method:e,path:t,regex:n,paramNames:o,isCatchAll:i,priority:u,handler:s,middlewares:r}),this.routesSorted=!1,this}createPathRegex(e){const t=v.get(e);if(t)return t;const s=[];let r=!1,n;const o=e.split("/").filter(Boolean);n=o.reduce((c,l)=>l.startsWith("[...")?c-10:l.startsWith("[[")?c-5:l.startsWith("[")?c-1:c+1,0);let i="^";for(const c of o)if(c.startsWith("[...")&&c.endsWith("]")){r=!0;const l=c.slice(4,-1);s.push(l),i+="/(.+)"}else if(c.startsWith("[[")&&c.endsWith("]]")){const l=c.slice(2,-2);s.push(l),i+="(?:/([^/]+))?"}else if(c.startsWith("[")&&c.endsWith("]")){const l=c.slice(1,-1);s.push(l),i+="/([^/]+)"}else i+="/"+c.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");i+="/?$";const u={regex:new RegExp(i),paramNames:s,isCatchAll:r,priority:n};return v.set(e,u),u}createPrefixRegex(e){const t=[];let s=!1,r;const n=e.split("/").filter(Boolean);r=n.reduce((i,u)=>u.startsWith("[...")?i-10:u.startsWith("[[")?i-5:u.startsWith("[")?i-1:i+1,0);let o="^";for(const i of n)if(i.startsWith("[...")&&i.endsWith("]")){s=!0;const u=i.slice(4,-1);t.push(u),o+="/(.+)"}else if(i.startsWith("[[")&&i.endsWith("]]")){const u=i.slice(2,-2);t.push(u),o+="(?:/([^/]+))?"}else if(i.startsWith("[")&&i.endsWith("]")){const u=i.slice(1,-1);t.push(u),o+="/([^/]+)"}else o+="/"+i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");return o+="(?=/|$)",{regex:new RegExp(o),paramNames:t,isCatchAll:s,priority:r}}normalizePrefix(e){return e.startsWith("/")?e.replace(/\/$/,""):`/${e.replace(/\/$/,"")}`}extractPrefixParams(e,t){const s=t.match(e.regex);if(!s)return{};const r={};return e.isCatchAll&&e.paramNames.length===1?r[e.paramNames[0]]=s[1]?.replace(/^\//,"")||"":e.paramNames.forEach((n,o)=>{r[n]=s[o+1]||""}),r}async handleNestedRouters(e,t){const s=[...this._nestedRouters].sort((r,n)=>n.priority-r.priority);for(const r of s){const n=t.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=t.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=async()=>await r.router.handleAtPath(c,i),d=await this.applyMiddlewaresWithList(c,r.middlewares,l);if(d)return d}return null}async handleLocalRoutes(e,t,s){const r=new Set;let n=!1;for(const o of this._routes){if(!o.regex.test(s)||(r.add(o.method),o.method==="GET"&&(n=!0,r.add("HEAD")),!(o.method===t||t==="HEAD"&&o.method==="GET")))continue;const u=s.match(o.regex);if(!u)continue;const c=Object.fromEntries(o.paramNames.map((d,h)=>[d,u[h+1]||""]));e.params={...e.params,...c},e.route={...e.route,id:o.path};const l=()=>o.handler(e);return await this.applyMiddlewaresWithList(e,o.middlewares,l)}if(r.size>0||t==="HEAD"&&n){const o=[...r].join(", ");return t==="OPTIONS"?new Response(null,{status:200,headers:{Allow:o}}):new Response("Method Not Allowed",{status:405,headers:{Allow:o}})}return new Response("Not Found",{status:404})}sortRoutes(){this._routes.sort((e,t)=>t.priority-e.priority),this.routesSorted=!0}sortWsRoutes(){this._wsRoutes.sort((e,t)=>t.priority-e.priority),this.wsRoutesSorted=!0}formatActionResult(e){return e instanceof Response?e:e?.type==="failure"&&"status"in e?R.fail(e.status,e.data):R.success(200,e??void 0)}handleActionError(e){if(W(e))return R.error(e.status,e);if(L(e)){const t=e.headers.get("Location")||"/";return R.redirect(e.status,t)}return console.error(e),R.error(500,{message:"Internal Server Error"})}static New(){return new H}}const R={success:(a=200,e)=>new Response(JSON.stringify({data:e,type:"success",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),redirect:(a=302,e)=>new Response(JSON.stringify({location:e,type:"redirect",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),error:(a=500,e)=>new Response(JSON.stringify({error:e,type:"error",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),fail:(a=400,e)=>new Response(JSON.stringify({data:e,type:"failure",status:a}),{status:a,headers:{"Content-Type":"application/json"}})};class ae{constructor(e,t){this.raw=e.headers.get("cookie")??"",this.setCookieHeader=t}get(e,t){return C.parse(this.raw,t)[e]}getAll(e){return Object.entries(C.parse(this.raw,e)).filter(([,t])=>t!==void 0).map(([t,s])=>({name:t,value:s}))}set(e,t,s){this.setCookieHeader(C.serialize(e,t,s))}delete(e,t){this.set(e,"",{...t,maxAge:0})}}class _ extends Error{constructor(e="Payload Too Large"){super(e),this.status=413,this.name="PayloadTooLargeError"}}class ie{constructor(e){this.upgradeHandlerInstalled=!1,this.config=e??{type:"http",options:{}},this.router=new H,this.wss=new b.WebSocketServer({noServer:!0,maxPayload:this.config.security?.maxWebSocketPayload??1024*1024})}get server(){if(!this._server){const e=(t,s)=>{this.handleRequest(t,s).catch(r=>{console.error("Unhandled request error:",r),s.statusCode=500,s.end("Internal Server Error")})};this._server=this.config.type==="https"?F.createServer(this.config.options,e):U.createServer(this.config.options,e)}return this._server}discard(e,t){return this.router.discard(e,t),this}listen(...e){return this.upgradeHandlerInstalled||(this.installUpgradeHandler(),this.upgradeHandlerInstalled=!0),this.server.listen(...e),this}installUpgradeHandler(){this.server.on("upgrade",(e,t,s)=>{if(e.headers.upgrade?.toLowerCase()!=="websocket"){t.destroy();return}let r,n;try{r=this.toURL(e,!0),n=this.toRequest(e,r,!0)}catch{t.destroy();return}const o=this.toRequestEvent(n,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:()=>{},pushSetCookie:()=>{}});this.router.canHandleWebSocket(o).then(i=>{if(!i||!this.isAllowedWebSocketOrigin(e)){t.destroy();return}this.wss.handleUpgrade(e,t,s,u=>{this.router.handleWebSocket(o,u).then(c=>{!c&&u.readyState===b.WebSocket.OPEN&&u.close(1008,"Route not found")}).catch(c=>{console.error("WebSocket routing error:",c),u.readyState===b.WebSocket.OPEN&&u.close(1011,"Internal error")})})}).catch(()=>t.destroy())})}close(e){this.wss.close(()=>{this.server.close(e)})}address(){return this.server.address()}get listening(){return this.server.listening}async handleRequest(e,t){if(!this.isRequestBodyAllowed(e)){t.statusCode=413,t.end("Payload Too Large");return}const s=this.toWebRequest(e),r=new URL(s.url),n={},o=[],i=this.toRequestEvent(s,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:(c,l)=>{n[c.toLowerCase()]=l},pushSetCookie:c=>{o.push(c)}});let u;try{u=await this.router.handle(i)}catch(c){u=this.handleError(c)}for(const[c,l]of Object.entries(n))t.setHeader(c,l);o.length>0&&t.setHeader("Set-Cookie",o),await this.sendWebResponse(t,u)}toWebRequest(e){const t=this.toURL(e,!1);return this.toRequest(e,t,!1)}toRequest(e,t,s){const r={method:s?"GET":e.method,headers:this.toHeaders(e.headers),duplex:"half"};return!s&&e.method!=="GET"&&e.method!=="HEAD"&&(r.body=T.Readable.toWeb(this.wrapRequestBody(e))),new Request(t,r)}wrapRequestBody(e){const t=this.config.security?.maxRequestBodySize;if(!t)return e;let s=0;const r=new T.Transform({transform(n,o,i){if(s+=Buffer.byteLength(n),s>t){i(new _);return}i(null,n)}});return e.on("aborted",()=>r.destroy(new Error("Request aborted"))),e.on("error",n=>r.destroy(n)),e.pipe(r),r}toURL(e,t){const s=e.socket instanceof z.TLSSocket?t?"wss":"https":t?"ws":"http",r=this.resolveAuthority(e);return new URL(e.url??"/",`${s}://${r}`)}resolveAuthority(e){const t=this.config.security?.trustHostHeader?this.normalizeTrustedHost(e.headers.host):null;if(t)return t;const s=this.server.address();return s&&typeof s=="object"?`${s.address.includes(":")?`[${s.address}]`:s.address}:${s.port}`:e.socket.localPort?`127.0.0.1:${e.socket.localPort}`:"localhost"}normalizeTrustedHost(e){if(!e)return null;let t;try{t=new URL(`http://${e}`)}catch{return null}if(t.username||t.password||t.pathname!=="/"||t.search||t.hash)return null;const s=t.port?`${t.hostname}:${t.port}`:t.hostname,r=this.config.security?.allowedHosts;return!r||this.matchesValue(s,r)?s:null}matchesValue(e,t){return(Array.isArray(t)?t:[t]).some(r=>typeof r=="string"?r===e:r instanceof RegExp?r.test(e):r(e))}toHeaders(e){const t=new Headers;for(const[s,r]of Object.entries(e))if(r!==void 0){if(Array.isArray(r)){const n=s.toLowerCase()==="cookie"?r.join("; "):r.join(", ");t.set(s,n);continue}t.set(s,r)}return t}isRequestBodyAllowed(e){const t=this.config.security?.maxRequestBodySize;if(!t)return!0;const s=e.headers["content-length"];if(!s)return!0;const r=Number.parseInt(Array.isArray(s)?s[0]:s,10);return Number.isFinite(r)&&r<=t}handleError(e){if(e instanceof _)return new Response(e.message,{status:e.status});if(W(e))return new Response(JSON.stringify({error:e.statusText||"Error",status:e.status}),{status:e.status,headers:{"Content-Type":"application/json"}});if(L(e)){const t=e.headers.get("Location")||"/";return new Response(null,{status:e.status,headers:{Location:t}})}return console.error("Unhandled error:",e),new Response("Internal Server Error",{status:500})}async sendWebResponse(e,t){if(e.statusCode=t.status,t.headers.forEach((n,o)=>{e.setHeader(o,n)}),e.hasHeader("Server")||e.setHeader("Server","WebHTTPServer"),!t.body||this.shouldOmitResponseBody(t,e.req?.method)){e.end();return}const s=t.body.getReader(),r=T.Writable.toWeb(e).getWriter();try{for(;;){const{done:n,value:o}=await s.read();if(n)break;await r.write(o)}}finally{await r.close().catch(()=>{})}}shouldOmitResponseBody(e,t){return t==="HEAD"?!0:e.status===204||e.status===205||e.status===304}isAllowedWebSocketOrigin(e){const t=e.headers.origin;if(!t)return!0;const s=this.config.security?.allowedWebSocketOrigins;return s?this.matchesValue(t,s):!0}toRequestEvent(e,t,s){const r=new ae(e,s.pushSetCookie),n=this.config,o={},i={name:"WebHTTPServer"},u=new Set,c={request:e,url:t,cookies:r,getClientAddress:s.getClientAddress,get locals(){return o},get platform(){return i},params:{},route:{id:""},setHeaders:l=>{for(const[d,h]of Object.entries(l)){const f=d.toLowerCase();if(f==="set-cookie")throw new TypeError("Use event.cookies for Set-Cookie headers");if(u.has(f))throw new TypeError(`Header "${d}" has already been set`);u.add(f),s.setHeader(d,h)}}};return n.locals&&Object.assign(o,n.locals(c)),n.platform&&Object.assign(i,n.platform(c)),c}}const ce=async(a,e)=>{const t=JSON.stringify(await a);return new Response(t,{...e,headers:{"content-type":"application/json","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},ue=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/plain","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},le=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/html","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},de=(a,e={})=>t=>N(a,t,e);exports.Action=R;exports.CORS=oe;exports.RateLimiter=Z;exports.RequestMethods=k;exports.Router=H;exports.WebServer=ie;exports.dir=de;exports.html=le;exports.isHttpError=W;exports.isRedirect=L;exports.json=ce;exports.serveStatic=N;exports.text=ue;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=require("node:fs"),A=require("node:fs/promises"),S=require("node:path"),q=require("node:stream"),U=require("http"),F=require("https"),z=require("tls"),T=require("stream"),b=require("ws"),C=require("cookie"),B={".avif":"image/avif",".css":"text/css; charset=utf-8",".gif":"image/gif",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".jpg":"image/jpeg",".jpeg":"image/jpeg",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".pdf":"application/pdf",".png":"image/png",".svg":"image/svg+xml; charset=utf-8",".txt":"text/plain; charset=utf-8",".wasm":"application/wasm",".webp":"image/webp",".xml":"application/xml; charset=utf-8"},$={index:"index.html",cacheControl:"public, max-age=0",dotFiles:"ignore"};async function N(a,e,t={}){const s=K(e),r={...$,...t},n=await X(a),o=G(s,r.dotFiles);if(o instanceof Response)return o;const i=o.length>0?o.join(S.sep):"",u=S.resolve(n,i);if(!j(n,u))return new Response("Forbidden",{status:403});const c=await J(u,n,r.index);if(c instanceof Response)return c;const l=await A.stat(c),d=new Headers({"content-length":String(l.size),"content-type":V(c),"cache-control":r.cacheControl,"last-modified":l.mtime.toUTCString(),"x-content-type-options":"nosniff"});if(t.headers){const h=typeof t.headers=="function"?t.headers(c,l):t.headers;new Headers(h).forEach((f,y)=>{d.set(y,f)})}return new Response(q.Readable.toWeb(D.createReadStream(c)),{status:200,headers:d})}async function X(a){return A.realpath(a)}function G(a,e){if(a.includes("\0"))return new Response("Bad Request",{status:400});const t=a.replace(/\\/g,"/").split("/").filter(Boolean),s=[];for(const r of t){let n;try{n=decodeURIComponent(r)}catch{return new Response("Bad Request",{status:400})}if(!(!n||n===".")){if(n===".."||n.includes("/")||n.includes("\\")||n.includes("\0"))return new Response("Forbidden",{status:403});if(n.startsWith(".")){if(e==="deny")return new Response("Forbidden",{status:403});if(e!=="allow")return new Response("Not Found",{status:404})}s.push(n)}}return s}async function J(a,e,t){try{if((await A.lstat(a)).isDirectory()){const r=S.resolve(a,t);return O(r,e)}return O(a,e)}catch{return new Response("Not Found",{status:404})}}async function O(a,e){try{const t=await A.realpath(a);return j(e,t)?(await A.stat(t)).isFile()?t:new Response("Not Found",{status:404}):new Response("Forbidden",{status:403})}catch{return new Response("Not Found",{status:404})}}function j(a,e){const t=S.relative(a,e);return t===""||!t.startsWith("..")&&!S.isAbsolute(t)}function V(a){return B[S.extname(a).toLowerCase()]??"application/octet-stream"}function K(a){return typeof a.params.path=="string"?a.params.path:a.url.pathname.replace(/^\/+/,"")}function W(a){return a instanceof Response&&a.status>=400&&a.status<600}function L(a){return a instanceof Response&&a.status>=300&&a.status<400}class Y{constructor(e){this.data=new Map,this.windowMs=e.windowMs,this.startCleanup()}async incr(e){const t=Date.now();let s=this.data.get(e);return!s||t>=s.reset?(s={count:1,reset:t+this.windowMs},this.data.set(e,s)):s.count++,{current:s.count,reset:s.reset}}startCleanup(){this.cleanupInterval=setInterval(()=>{const e=Date.now();for(const[t,{reset:s}]of this.data)e>=s&&this.data.delete(t)},Math.min(this.windowMs,3e5))}stop(){this.cleanupInterval&&clearInterval(this.cleanupInterval)}async resetAll(){this.data.clear()}}function Q(a){const{windowMs:e=6e4,max:t,key:s=c=>c.getClientAddress(),message:r="Too many requests, please try again later.",statusCode:n=429,headers:o="include",onRateLimit:i,store:u=new Y({windowMs:e})}=a;return async(c,l)=>{const d=`rl:${s(c)}`,{current:h,reset:f}=await u.incr(d),y=Math.ceil((f-Date.now())/1e3);if(h>t){i&&i(c,{current:h,max:t,key:d});const w={status:n,headers:new Headers},p=w.headers;o==="include"&&(p.set("X-RateLimit-Limit",String(t)),p.set("X-RateLimit-Remaining","0"),p.set("X-RateLimit-Reset",String(Math.floor(f/1e3))),p.set("Retry-After",String(y)));let m;return typeof r=="string"?(m=r,p.set("Content-Type","text/plain")):(m=JSON.stringify(r),p.set("Content-Type","application/json")),new Response(m,w)}if(c.rateLimit={current:h,limit:t,reset:new Date(f),remaining:t-h},o==="include"){const w={"X-RateLimit-Limit":String(t),"X-RateLimit-Remaining":String(t-h),"X-RateLimit-Reset":String(Math.floor(f/1e3))},p=c.setHeaders;c.setHeaders=m=>{p({...w,...m})},p(w)}return l()}}const Z=Object.freeze(Object.defineProperty({__proto__:null,fixedWindowLimit:Q},Symbol.toStringTag,{value:"Module"})),ee=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],te=["Accept","Accept-Language","Content-Language","Content-Type","Range"],se=["Authorization","X-Auth-Token","X-Requested-With","X-CSRF-Token","X-HTTP-Method-Override","X-Forwarded-For","X-Real-IP","X-Custom-Header"];function M(a,e){return!a||!e?!1:e==="*"?!0:e==="null"?a==="null":Array.isArray(e)?e.some(t=>M(a,t)):typeof e=="function"?e(a):e instanceof RegExp?e.test(a):a===e}function re(a,e){const{origin:t="*"}=e;return a?t==="*"?e.credentials?a:"*":M(a,t)?a:null:t==="*"?"*":null}function ne(a={}){const{methods:e=ee,allowedHeaders:t=se,exposedHeaders:s,credentials:r=!1,maxAge:n=86400,onResponse:o}=a,i="Origin,Access-Control-Request-Method,Access-Control-Request-Headers",u=e.join(","),c=[...te,...t].join(","),l=[["Vary",i],["Access-Control-Allow-Methods",u],["Access-Control-Allow-Headers",c]];return s&&l.push(["Access-Control-Expose-Headers",s.join(",")]),r&&l.push(["Access-Control-Allow-Credentials","true"]),n&&l.push(["Access-Control-Max-Age",n.toString()]),async(d,h)=>{const f=d.request,y=f.headers.get("Origin"),w=f.method==="OPTIONS"&&y!==null&&f.headers.has("Access-Control-Request-Method"),p=re(y,a);if(w){if(!p)return new Response(null,{status:403});const g=new Response(null,{status:204});for(const[E,I]of l)g.headers.set(E,I);return g.headers.set("Access-Control-Allow-Origin",p),g}const m=await h();if(!m)return;if(!p)return m;const x=new Response(m.body,m);for(const[g,E]of l)x.headers.set(g,E);x.headers.set("Access-Control-Allow-Origin",p);let P=x;if(o){const g=o(P);g&&(P=g)}return P}}const oe=Object.freeze(Object.defineProperty({__proto__:null,policy:ne},Symbol.toStringTag,{value:"Module"})),k=["GET","PUT","POST","DELETE","PATCH","HEAD","OPTIONS"];class v{static{this.cache=new Map}static get(e){return this.cache.get(e)}static set(e,t){this.cache.set(e,t)}}class H{constructor(){this._routes=[],this._wsRoutes=[],this._nestedRouters=[],this._middlewares=[],this._preHandlers=[],this._postHandlers=[],this.routesSorted=!1,this.wsRoutesSorted=!1}get routes(){return this._routes}get nestedRouters(){return this._nestedRouters}GET(e,t,...s){return this.addHandler("GET",e,t,s)}POST(e,t,...s){return this.addHandler("POST",e,t,s)}PUT(e,t,...s){return this.addHandler("PUT",e,t,s)}PATCH(e,t,...s){return this.addHandler("PATCH",e,t,s)}DELETE(e,t,...s){return this.addHandler("DELETE",e,t,s)}HEAD(e,t,...s){return this.addHandler("HEAD",e,t,s)}OPTIONS(e,t,...s){return this.addHandler("OPTIONS",e,t,s)}USE(e,t,...s){return k.forEach(r=>this.addHandler(r,e,t,s)),this}action(e,t,...s){const r=async n=>{try{const o=await t(n);return this.formatActionResult(o)}catch(o){return this.handleActionError(o)}};return this.addHandler("POST",e,r,s)}use(e,t,...s){let r,n,o=s;Array.isArray(e)?([r,n]=e,o=e.length>2?e.slice(2):[]):(r=e,n=t);const i=this.normalizePrefix(r),{regex:u,paramNames:c,isCatchAll:l,priority:d}=this.createPrefixRegex(i);return this._nestedRouters.push({prefix:i,router:n,regex:u,paramNames:c,isCatchAll:l,priority:d,middlewares:o}),this}useMiddleware(...e){return this._middlewares.push(...e),this}pre(...e){return this._preHandlers.push(...e),this}post(...e){return this._postHandlers.push(...e),this}discard(e,t){return this._nestedRouters=this._nestedRouters.filter(s=>s.prefix!==e),this._routes=this._routes.filter(s=>s.path!==e||t&&s.method!==t),this}WS(e,t,...s){const{regex:r,paramNames:n,isCatchAll:o,priority:i}=this.createPathRegex(e);return this._wsRoutes.push({path:e,regex:r,paramNames:n,isCatchAll:o,priority:i,handler:t,middlewares:s}),this.wsRoutesSorted=!1,this}async canHandleWebSocket(e){return this.canHandleWebSocketAtPath(e,e.url.pathname)}async canHandleWebSocketAtPath(e,t){this.wsRoutesSorted||this.sortWsRoutes();for(const s of[...this._nestedRouters].sort((r,n)=>n.priority-r.priority)){const r=t.match(s.regex);if(!r||r.index!==0)continue;const n=r[0],o=t.slice(n.length)||"/",i={...e,params:{...e.params,...this.extractPrefixParams(s,n)}};if(await s.router.canHandleWebSocketAtPath(i,o))return!0}for(const s of this._wsRoutes)if(s.regex.test(t))return!0;return!1}async handleWebSocket(e,t){return this.handleWebSocketAtPath(e,t,e.url.pathname)}async handleWebSocketAtPath(e,t,s){this.wsRoutesSorted||this.sortWsRoutes();for(const r of[...this._nestedRouters].sort((n,o)=>o.priority-n.priority)){const n=s.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=s.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=[...this._middlewares,...r.middlewares],d=()=>r.router.handleWebSocketAtPath(c,t,i);if(await this.applyMiddlewaresWithList(c,l,d))return!0}for(const r of this._wsRoutes){if(!r.regex.test(s))continue;const n=s.match(r.regex);if(!n)continue;const o=Object.fromEntries(r.paramNames.map((l,d)=>[l,n[d+1]||""])),i={...e,params:{...e.params,...o},route:{...e.route,id:r.path},websocket:t},u=[...this._middlewares,...r.middlewares];if(await this.applyMiddlewaresWithList(i,u,()=>r.handler(i))===void 0)return!0}return!1}async handle(e){return this.handleAtPath(e,e.url.pathname)}async handleAtPath(e,t){const s=e.request.method;let r=await this.runPreHandlers(e);if(!r){const o=async()=>{const i=await this.handleNestedRouters(e,t);return i||(this.routesSorted||this.sortRoutes(),this.handleLocalRoutes(e,s,t))};r=await this.applyMiddlewaresWithList(e,this._middlewares,o)}const n=r||new Response("No Content",{status:204});return this.runPostHandlers(e,n)}async applyMiddlewaresWithList(e,t,s){const r=[...t],n=async o=>o>=r.length?s():r[o](e,()=>n(o+1));return n(0)}async runPreHandlers(e){for(const t of this._preHandlers){const s=await t(e);if(s instanceof Response)return s}}async runPostHandlers(e,t){let s=t;for(const r of this._postHandlers){const n=await r(e,s);n instanceof Response&&(s=n)}return s}addHandler(e,t,s,r=[]){const{regex:n,paramNames:o,isCatchAll:i,priority:u}=this.createPathRegex(t);return this._routes.push({method:e,path:t,regex:n,paramNames:o,isCatchAll:i,priority:u,handler:s,middlewares:r}),this.routesSorted=!1,this}createPathRegex(e){const t=v.get(e);if(t)return t;const s=[];let r=!1,n;const o=e.split("/").filter(Boolean);n=o.reduce((c,l)=>l.startsWith("[...")?c-10:l.startsWith("[[")?c-5:l.startsWith("[")?c-1:c+1,0);let i="^";for(const c of o)if(c.startsWith("[...")&&c.endsWith("]")){r=!0;const l=c.slice(4,-1);s.push(l),i+="/(.+)"}else if(c.startsWith("[[")&&c.endsWith("]]")){const l=c.slice(2,-2);s.push(l),i+="(?:/([^/]+))?"}else if(c.startsWith("[")&&c.endsWith("]")){const l=c.slice(1,-1);s.push(l),i+="/([^/]+)"}else i+="/"+c.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");i+="/?$";const u={regex:new RegExp(i),paramNames:s,isCatchAll:r,priority:n};return v.set(e,u),u}createPrefixRegex(e){const t=[];let s=!1,r;const n=e.split("/").filter(Boolean);r=n.reduce((i,u)=>u.startsWith("[...")?i-10:u.startsWith("[[")?i-5:u.startsWith("[")?i-1:i+1,0);let o="^";for(const i of n)if(i.startsWith("[...")&&i.endsWith("]")){s=!0;const u=i.slice(4,-1);t.push(u),o+="/(.+)"}else if(i.startsWith("[[")&&i.endsWith("]]")){const u=i.slice(2,-2);t.push(u),o+="(?:/([^/]+))?"}else if(i.startsWith("[")&&i.endsWith("]")){const u=i.slice(1,-1);t.push(u),o+="/([^/]+)"}else o+="/"+i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");return o+="(?=/|$)",{regex:new RegExp(o),paramNames:t,isCatchAll:s,priority:r}}normalizePrefix(e){return e.startsWith("/")?e.replace(/\/$/,""):`/${e.replace(/\/$/,"")}`}extractPrefixParams(e,t){const s=t.match(e.regex);if(!s)return{};const r={};return e.isCatchAll&&e.paramNames.length===1?r[e.paramNames[0]]=s[1]?.replace(/^\//,"")||"":e.paramNames.forEach((n,o)=>{r[n]=s[o+1]||""}),r}async handleNestedRouters(e,t){const s=[...this._nestedRouters].sort((r,n)=>n.priority-r.priority);for(const r of s){const n=t.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=t.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=async()=>await r.router.handleAtPath(c,i),d=await this.applyMiddlewaresWithList(c,r.middlewares,l);if(d)return d}return null}async handleLocalRoutes(e,t,s){const r=new Set;let n=!1;for(const o of this._routes){if(!o.regex.test(s)||(r.add(o.method),o.method==="GET"&&(n=!0,r.add("HEAD")),!(o.method===t||t==="HEAD"&&o.method==="GET")))continue;const u=s.match(o.regex);if(!u)continue;const c=Object.fromEntries(o.paramNames.map((d,h)=>[d,u[h+1]||""]));e.params={...e.params,...c},e.route={...e.route,id:o.path};const l=()=>o.handler(e);return await this.applyMiddlewaresWithList(e,o.middlewares,l)}if(r.size>0||t==="HEAD"&&n){const o=[...r].join(", ");return t==="OPTIONS"?new Response(null,{status:200,headers:{Allow:o}}):new Response("Method Not Allowed",{status:405,headers:{Allow:o}})}return new Response("Not Found",{status:404})}sortRoutes(){this._routes.sort((e,t)=>t.priority-e.priority),this.routesSorted=!0}sortWsRoutes(){this._wsRoutes.sort((e,t)=>t.priority-e.priority),this.wsRoutesSorted=!0}formatActionResult(e){return e instanceof Response?e:e?.type==="failure"&&"status"in e?R.fail(e.status,e.data):R.success(200,e??void 0)}handleActionError(e){if(W(e))return R.error(e.status,e);if(L(e)){const t=e.headers.get("Location")||"/";return R.redirect(e.status,t)}return console.error(e),R.error(500,{message:"Internal Server Error"})}static New(){return new H}}const R={success:(a=200,e)=>new Response(JSON.stringify({data:e,type:"success",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),redirect:(a=302,e)=>new Response(JSON.stringify({location:e,type:"redirect",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),error:(a=500,e)=>new Response(JSON.stringify({error:e,type:"error",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),fail:(a=400,e)=>new Response(JSON.stringify({data:e,type:"failure",status:a}),{status:a,headers:{"Content-Type":"application/json"}})};class ae{constructor(e,t){this.raw=e.headers.get("cookie")??"",this.setCookieHeader=t}get(e,t){return C.parse(this.raw,t)[e]}getAll(e){return Object.entries(C.parse(this.raw,e)).filter(([,t])=>t!==void 0).map(([t,s])=>({name:t,value:s}))}set(e,t,s){this.setCookieHeader(C.serialize(e,t,s))}delete(e,t){this.set(e,"",{...t,maxAge:0})}}class _ extends Error{constructor(e="Payload Too Large"){super(e),this.status=413,this.name="PayloadTooLargeError"}}class ie extends H{constructor(e){super(),this.upgradeHandlerInstalled=!1,this.config=e??{type:"http",options:{}},this.wss=new b.WebSocketServer({noServer:!0,maxPayload:this.config.security?.maxWebSocketPayload??1024*1024})}get server(){if(!this._server){const e=(t,s)=>{this.handleRequest(t,s).catch(r=>{console.error("Unhandled request error:",r),s.statusCode=500,s.end("Internal Server Error")})};this._server=this.config.type==="https"?F.createServer(this.config.options,e):U.createServer(this.config.options,e)}return this._server}listen(...e){return this.upgradeHandlerInstalled||(this.installUpgradeHandler(),this.upgradeHandlerInstalled=!0),this.server.listen(...e),this}installUpgradeHandler(){this.server.on("upgrade",(e,t,s)=>{if(e.headers.upgrade?.toLowerCase()!=="websocket"){t.destroy();return}let r,n;try{r=this.toURL(e,!0),n=this.toRequest(e,r,!0)}catch{t.destroy();return}const o=this.toRequestEvent(n,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:()=>{},pushSetCookie:()=>{}});this.canHandleWebSocket(o).then(i=>{if(!i||!this.isAllowedWebSocketOrigin(e)){t.destroy();return}this.wss.handleUpgrade(e,t,s,u=>{this.handleWebSocket(o,u).then(c=>{!c&&u.readyState===b.WebSocket.OPEN&&u.close(1008,"Route not found")}).catch(c=>{console.error("WebSocket routing error:",c),u.readyState===b.WebSocket.OPEN&&u.close(1011,"Internal error")})})}).catch(()=>t.destroy())})}close(e){this.wss.close(()=>{this.server.close(e)})}address(){return this.server.address()}get listening(){return this.server.listening}async handleRequest(e,t){if(!this.isRequestBodyAllowed(e)){t.statusCode=413,t.end("Payload Too Large");return}const s=this.toWebRequest(e),r=new URL(s.url),n={},o=[],i=this.toRequestEvent(s,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:(c,l)=>{n[c.toLowerCase()]=l},pushSetCookie:c=>{o.push(c)}});let u;try{u=await this.handle(i)}catch(c){u=this.handleError(c)}for(const[c,l]of Object.entries(n))t.setHeader(c,l);o.length>0&&t.setHeader("Set-Cookie",o),await this.sendWebResponse(t,u)}toWebRequest(e){const t=this.toURL(e,!1);return this.toRequest(e,t,!1)}toRequest(e,t,s){const r={method:s?"GET":e.method,headers:this.toHeaders(e.headers),duplex:"half"};return!s&&e.method!=="GET"&&e.method!=="HEAD"&&(r.body=T.Readable.toWeb(this.wrapRequestBody(e))),new Request(t,r)}wrapRequestBody(e){const t=this.config.security?.maxRequestBodySize;if(!t)return e;let s=0;const r=new T.Transform({transform(n,o,i){if(s+=Buffer.byteLength(n),s>t){i(new _);return}i(null,n)}});return e.on("aborted",()=>r.destroy(new Error("Request aborted"))),e.on("error",n=>r.destroy(n)),e.pipe(r),r}toURL(e,t){const s=e.socket instanceof z.TLSSocket?t?"wss":"https":t?"ws":"http",r=this.resolveAuthority(e);return new URL(e.url??"/",`${s}://${r}`)}resolveAuthority(e){const t=this.config.security?.trustHostHeader?this.normalizeTrustedHost(e.headers.host):null;if(t)return t;const s=this.server.address();return s&&typeof s=="object"?`${s.address.includes(":")?`[${s.address}]`:s.address}:${s.port}`:e.socket.localPort?`127.0.0.1:${e.socket.localPort}`:"localhost"}normalizeTrustedHost(e){if(!e)return null;let t;try{t=new URL(`http://${e}`)}catch{return null}if(t.username||t.password||t.pathname!=="/"||t.search||t.hash)return null;const s=t.port?`${t.hostname}:${t.port}`:t.hostname,r=this.config.security?.allowedHosts;return!r||this.matchesValue(s,r)?s:null}matchesValue(e,t){return(Array.isArray(t)?t:[t]).some(r=>typeof r=="string"?r===e:r instanceof RegExp?r.test(e):r(e))}toHeaders(e){const t=new Headers;for(const[s,r]of Object.entries(e))if(r!==void 0){if(Array.isArray(r)){const n=s.toLowerCase()==="cookie"?r.join("; "):r.join(", ");t.set(s,n);continue}t.set(s,r)}return t}isRequestBodyAllowed(e){const t=this.config.security?.maxRequestBodySize;if(!t)return!0;const s=e.headers["content-length"];if(!s)return!0;const r=Number.parseInt(Array.isArray(s)?s[0]:s,10);return Number.isFinite(r)&&r<=t}handleError(e){if(e instanceof _)return new Response(e.message,{status:e.status});if(W(e))return new Response(JSON.stringify({error:e.statusText||"Error",status:e.status}),{status:e.status,headers:{"Content-Type":"application/json"}});if(L(e)){const t=e.headers.get("Location")||"/";return new Response(null,{status:e.status,headers:{Location:t}})}return console.error("Unhandled error:",e),new Response("Internal Server Error",{status:500})}async sendWebResponse(e,t){if(e.statusCode=t.status,t.headers.forEach((n,o)=>{e.setHeader(o,n)}),e.hasHeader("Server")||e.setHeader("Server","WebHTTPServer"),!t.body||this.shouldOmitResponseBody(t,e.req?.method)){e.end();return}const s=t.body.getReader(),r=T.Writable.toWeb(e).getWriter();try{for(;;){const{done:n,value:o}=await s.read();if(n)break;await r.write(o)}}finally{await r.close().catch(()=>{})}}shouldOmitResponseBody(e,t){return t==="HEAD"?!0:e.status===204||e.status===205||e.status===304}isAllowedWebSocketOrigin(e){const t=e.headers.origin;if(!t)return!0;const s=this.config.security?.allowedWebSocketOrigins;return s?this.matchesValue(t,s):!0}toRequestEvent(e,t,s){const r=new ae(e,s.pushSetCookie),n=this.config,o={},i={name:"WebHTTPServer"},u=new Set,c={request:e,url:t,cookies:r,getClientAddress:s.getClientAddress,get locals(){return o},get platform(){return i},params:{},route:{id:""},setHeaders:l=>{for(const[d,h]of Object.entries(l)){const f=d.toLowerCase();if(f==="set-cookie")throw new TypeError("Use event.cookies for Set-Cookie headers");if(u.has(f))throw new TypeError(`Header "${d}" has already been set`);u.add(f),s.setHeader(d,h)}}};return n.locals&&Object.assign(o,n.locals(c)),n.platform&&Object.assign(i,n.platform(c)),c}}const ce=async(a,e)=>{const t=JSON.stringify(await a);return new Response(t,{...e,headers:{"content-type":"application/json","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},ue=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/plain","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},le=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/html","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},de=(a,e={})=>t=>N(a,t,e);exports.Action=R;exports.CORS=oe;exports.RateLimiter=Z;exports.RequestMethods=k;exports.Router=H;exports.WebServer=ie;exports.dir=de;exports.html=le;exports.isHttpError=W;exports.isRedirect=L;exports.json=ce;exports.serveStatic=N;exports.text=ue;
2
2
  //# sourceMappingURL=index.cjs.js.map