@danielgl/steampunk 0.0.5 → 1.0.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/dist/main.cjs CHANGED
@@ -1 +1,16 @@
1
- "use strict";var P=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var pe=Object.prototype.hasOwnProperty;var a=(r,e)=>P(r,"name",{value:e,configurable:!0});var B=(r,e)=>()=>(r&&(e=r(r=0)),e);var _=(r,e)=>{for(var t in e)P(r,t,{get:e[t],enumerable:!0})},he=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of fe(e))!pe.call(r,s)&&s!==t&&P(r,s,{get:()=>e[s],enumerable:!(n=ue(e,s))||n.enumerable});return r};var F=r=>he(P({},"__esModule",{value:!0}),r);function R(){return r=>{Reflect.defineMetadata(me,!0,r)}}function K(r){return(e,t,n)=>{let s=Reflect.getMetadata(j,e)||[];s.push({index:n,token:r}),Reflect.defineMetadata(j,s,e)}}var Re,me,j,v=B(()=>{"use strict";Re=require("reflect-metadata"),me=Symbol("INJECTABLE_WATERMARK"),j=Symbol("INJECT_METADATA");a(R,"Injectable");a(K,"Inject")});var oe={};_(oe,{JwtAuthService:()=>w});function ye(r,e,t,n){var s=arguments.length,i=s<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,t):n,o;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")i=Reflect.decorate(r,e,t,n);else for(var c=r.length-1;c>=0;c--)(o=r[c])&&(i=(s<3?o(i):s>3?o(e,t,i):o(e,t))||i);return s>3&&i&&Object.defineProperty(e,t,i),i}function ne(r,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(r,e)}function J(r){return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function se(r){let e=r+"==".slice(2-r.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var w,U=B(()=>{"use strict";v();a(ye,"_ts_decorate");a(ne,"_ts_metadata");a(J,"base64UrlEncode");a(se,"base64UrlDecode");w=class{static{a(this,"JwtAuthService")}secret;expiresIn;constructor(e){this.secret=e.secret,this.expiresIn=e.expiresIn??3600}async getKey(){let e=new TextEncoder;return crypto.subtle.importKey("raw",e.encode(this.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async sign(e){let t={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),s={...e,iat:n,exp:n+this.expiresIn},i=J(JSON.stringify(t)),o=J(JSON.stringify(s)),c=`${i}.${o}`,l=await this.getKey(),u=new TextEncoder,p=await crypto.subtle.sign("HMAC",l,u.encode(c)),f=J(String.fromCharCode(...new Uint8Array(p)));return`${c}.${f}`}async verify(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT format");let n=t[0],s=t[1],i=t[2],o=`${n}.${s}`,c=await this.getKey(),l=new TextEncoder,u=Uint8Array.from(se(i),D=>D.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,u,l.encode(o)))throw new Error("Invalid JWT signature");let f=JSON.parse(se(s)),A=Math.floor(Date.now()/1e3);if(f.exp!==void 0&&f.exp<A)throw new Error("JWT expired");return f}};w=ye([R(),ne("design:type",Function),ne("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],w)});var we={};_(we,{Authorize:()=>z,Controller:()=>G,Delete:()=>V,FromBody:()=>re,FromQuery:()=>te,FromRoute:()=>ee,Get:()=>X,HttpContext:()=>E,HttpResult:()=>y,Inject:()=>K,Injectable:()=>R,JwtAuthService:()=>w,Patch:()=>Y,Post:()=>Q,Put:()=>Z,ServiceCollection:()=>x,ServiceProvider:()=>T,WebApplication:()=>C,WebApplicationBuilder:()=>S,cors:()=>ae,throttle:()=>ce});module.exports=F(we);var Me=require("reflect-metadata");v();var h=(function(r){return r[r.Singleton=0]="Singleton",r[r.Transient=1]="Transient",r[r.Scoped=2]="Scoped",r})({});var T=class r{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;trackedInstances=new Set;constructor(e,t){this.descriptors=e,this.parent=t}createScope(){return new r(this.descriptors,this.parent||this)}async dispose(){for(let e of this.trackedInstances)typeof e.onDestroy=="function"&&await e.onDestroy();this.trackedInstances.clear(),this.scopedInstances.clear(),this.singletonInstances.clear()}async resolve(e){let t=this.descriptors.find(n=>n.token===e);if(!t){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(t.lifetime){case h.Singleton:return this.resolveSingleton(t);case h.Scoped:return this.resolveScoped(t);case h.Transient:return this.resolveTransient(t)}}async resolveSingleton(e){let t=this.getRoot();if(t.singletonInstances.has(e.token))return t.singletonInstances.get(e.token);let n=await this.createInstance(t,e);return t.singletonInstances.set(e.token,n),n}async resolveScoped(e){if(this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let t=await this.createInstance(this,e);return this.scopedInstances.set(e.token,t),t}async resolveTransient(e){return this.createInstance(this,e)}async createInstance(e,t){let n;if(t.implementationInstance!==void 0)n=t.implementationInstance;else if(t.implementationFactory)n=await t.implementationFactory(this);else if(t.implementationType)n=await this.instantiateClass(t.implementationType);else throw new Error(`Invalid service descriptor for token: ${t.token.toString()}`);let s=Symbol.for("steampunk:initialized");return typeof n.onInit=="function"&&!n[s]&&(await n.onInit(),n[s]=!0),typeof n.onDestroy=="function"&&t.lifetime!==h.Transient&&e.trackedInstances.add(n),n}getRoot(){return this.parent?this.parent.getRoot():this}async instantiateClass(e){let t=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(j,e)||[],s=await Promise.all(t.map(async(i,o)=>{let c=n.find(u=>u.index===o),l=c?c.token:i;if(!l||l===Object||l===String||l===Number||l===Boolean)throw new Error(`Cannot resolve parameter at index ${o} for ${e.name}. Token could not be determined or is primitive. Cannot inject primitive types directly without @Inject().`);return this.resolve(l)}));return new e(...s)}};var x=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Singleton})}addSingletonInstance(e,t){return this.add({token:e,implementationInstance:t,lifetime:h.Singleton})}addTransient(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Transient})}addScoped(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Scoped})}buildServiceProvider(){return new T(this.descriptors)}addControllers(e){return e.forEach(t=>this.addTransient(t)),this}};var E=class{static{a(this,"HttpContext")}request;items=new Map;constructor(e){this.request=e}get method(){return this.request.method}get url(){return this.request.url}get path(){return new URL(this.request.url).pathname}};var He=require("reflect-metadata");var b=Symbol("AUTHORIZE_METADATA");function z(r){return(e,t)=>{let n={required:!0,roles:r};t!==void 0?Reflect.defineMetadata(b,n,e.constructor,t):Reflect.defineMetadata(b,n,e)}}a(z,"Authorize");var y=class r{static{a(this,"HttpResult")}status;body;headers;constructor(e,t,n={}){this.status=e,this.body=t!==void 0&&typeof t!="string"?JSON.stringify(t):t,this.headers=n}static ok(e){return new r(200,e)}static created(e,t){let n={};return t&&(n.Location=t),new r(201,e,n)}static noContent(){return new r(204)}static badRequest(e="Bad Request",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(400,n)}static unauthorized(e="Unauthorized"){let t=typeof e=="string"?{message:e}:e;return new r(401,t)}static forbidden(e="Forbidden"){let t=typeof e=="string"?{message:e}:e;return new r(403,t)}static notFound(e="Not Found"){let t=typeof e=="string"?{message:e}:e;return new r(404,t)}static conflict(e="Conflict"){let t=typeof e=="string"?{message:e}:e;return new r(409,t)}static unprocessableEntity(e="Unprocessable Entity",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(422,n)}static internalServerError(e="Internal Server Error"){let t=typeof e=="string"?{message:e}:e;return new r(500,t)}toResponse(){if(this.status===204||this.body===void 0)return new Response(null,{status:this.status,headers:this.headers});let e={"Content-Type":"application/json",...this.headers};return new Response(this.body,{status:this.status,headers:e})}};var We=require("reflect-metadata");v();var I=Symbol("CONTROLLER_WATERMARK"),M=Symbol("ROUTE_METADATA"),k=Symbol("PARAM_METADATA");function G(r=""){return e=>{Reflect.defineMetadata(I,r,e),R()(e)}}a(G,"Controller");var O=a(r=>(e="")=>(t,n)=>{Reflect.hasMetadata(M,t.constructor)||Reflect.defineMetadata(M,[],t.constructor);let s=Reflect.getMetadata(M,t.constructor);s.push({method:r,path:e,methodName:n.toString()}),Reflect.defineMetadata(M,s,t.constructor)},"createRouteDecorator"),X=O("GET"),Q=O("POST"),Z=O("PUT"),V=O("DELETE"),Y=O("PATCH"),g=(function(r){return r[r.Route=0]="Route",r[r.Query=1]="Query",r[r.Body=2]="Body",r[r.Context=3]="Context",r})({});function ee(r,e){return(t,n,s)=>{L(t,n,{type:0,index:s,name:r,schema:e})}}a(ee,"FromRoute");function te(r,e){return(t,n,s)=>{L(t,n,{type:1,index:s,name:r,schema:e})}}a(te,"FromQuery");function re(r){return(e,t,n)=>{L(e,t,{type:2,index:n,schema:r})}}a(re,"FromBody");function L(r,e,t){let n=`${k.toString()}_${e.toString()}`;Reflect.hasMetadata(n,r.constructor)||Reflect.defineMetadata(n,[],r.constructor);let s=Reflect.getMetadata(n,r.constructor);s.push(t),Reflect.defineMetadata(n,s,r.constructor)}a(L,"addParamMetadata");async function ge(r,e,t){if(!t.required)return null;let n=r.request.headers.get("authorization");if(!n||!n.startsWith("Bearer "))return new Response(JSON.stringify({message:"Unauthorized"}),{status:401,headers:{"Content-Type":"application/json"}});let s=n.slice(7),i;try{let{JwtAuthService:c}=(U(),F(oe));i=await e.resolve(c)}catch{return new Response(JSON.stringify({message:"JWT service not configured"}),{status:500,headers:{"Content-Type":"application/json"}})}let o;try{o=await i.verify(s)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(t.roles&&t.roles.length>0){let c=o.roles??[];if(!t.roles.some(u=>c.includes(u)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return r.items.set("user",o),null}a(ge,"checkAuth");var N=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let t of e){let n=Reflect.getMetadata(I,t);if(n===void 0)continue;let s=Reflect.getMetadata(M,t)||[];for(let i of s){let o=`/${n}/${i.path}`.replace(/\/+/g,"/");o!=="/"&&o.endsWith("/")&&(o=o.slice(0,-1));let c=new URLPattern({pathname:o});this.routes.push({method:i.method,pattern:c,controller:t,methodName:i.methodName})}}}map(e,t,n){let s=t.startsWith("/")?t:`/${t}`;s!=="/"&&s.endsWith("/")&&(s=s.slice(0,-1));let i=new URLPattern({pathname:s});this.routes.push({method:e.toUpperCase(),pattern:i,handler:n})}middleware(){return async(e,t)=>{for(let n of this.routes){if(n.method!==e.method)continue;let s=n.pattern.exec({pathname:e.path});if(s){let i=e.items.get("scope");if(!i)throw new Error("DI Scope not found in HttpContext");let o;if(n.handler)o=await n.handler(e);else if(n.controller&&n.methodName){let c=Reflect.getMetadata(b,n.controller,n.methodName),l=Reflect.getMetadata(b,n.controller),u=c??l;if(u){let d=await ge(e,i,u);if(d)return d}let p=await i.resolve(n.controller),f=`${k.toString()}_${n.methodName}`,A=Reflect.getMetadata(f,n.controller)||[],D=p[n.methodName].length,$=new Array(D).fill(void 0),le=A.some(d=>d.type===g.Body),q;le&&(q=await e.request.json().catch(()=>({})));let de=new URL(e.request.url);for(let d of A){let m;if(d.type===g.Route&&d.name?m=s.pathname.groups[d.name]:d.type===g.Query&&d.name?m=de.searchParams.get(d.name):d.type===g.Body?m=q:d.type===g.Context&&(m=e),d.schema){let H=d.schema.safeParse(m);if(!H.success)return y.unprocessableEntity("Validation failed",{errors:H.error.errors,target:g[d.type].toLowerCase(),name:d.name}).toResponse();m=H.data}$[d.index]=m}o=await p[n.methodName](...$)}return o instanceof Response?o:o instanceof y?o.toResponse():typeof o=="object"?Response.json(o):new Response(String(o))}}return t()}}};var ie=require("fs/promises"),W=require("path");var S=class{static{a(this,"WebApplicationBuilder")}services;controllers=[];constructor(){this.services=new x}async addControllers(e="controllers"){let t=(0,W.join)(process.cwd(),e);try{let n=await(0,ie.readdir)(t,{recursive:!0});for(let s of n)if(typeof s=="string"&&(s.endsWith(".ts")||s.endsWith(".js"))&&!s.endsWith(".d.ts")){let o=await import((0,W.join)(t,s));for(let c in o){let l=o[c];typeof l=="function"&&Reflect.hasMetadata(I,l)&&this.controllers.push(l)}}this.controllers.length>0&&this.services.addControllers(this.controllers)}catch(n){console.warn(`Could not discover controllers at ${t}:`,n)}return this}build(){let e=this.services.buildServiceProvider();return new C(e,this.controllers)}};var C=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];router;constructor(e,t=[]){this.services=e,this.controllersToMap.push(...t),this.router=new N}static createBuilder(){return new S}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}mapGet(e,t){return this.router.map("GET",e,t),this}mapPost(e,t){return this.router.map("POST",e,t),this}mapPut(e,t){return this.router.map("PUT",e,t),this}mapDelete(e,t){return this.router.map("DELETE",e,t),this}mapPatch(e,t){return this.router.map("PATCH",e,t),this}async executePipeline(e){let t=-1,n=a(async i=>{if(i<=t)throw new Error("next() called multiple times");t=i;let o=this.middlewares[i];if(i===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(o)return o(e,n.bind(null,i+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let t=this;this.controllersToMap.length>0&&this.router.registerControllers(this.controllersToMap),this.use(this.router.middleware()),Bun.serve({port:e,async fetch(n){let s=t.services.createScope();try{let i=new E(n);return i.items.set("scope",s),await t.executePipeline(i)}finally{await s.dispose()}}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};v();U();function ae(r={}){return async(e,t)=>{let{request:n}=e,s=n.headers.get("origin"),i=await t();i||(i=new Response);let o=new Response(i.body,i);if(r.origin?typeof r.origin=="string"?o.headers.set("Access-Control-Allow-Origin",r.origin):Array.isArray(r.origin)&&s?r.origin.includes(s)&&o.headers.set("Access-Control-Allow-Origin",s):typeof r.origin=="function"&&s&&r.origin(s)&&o.headers.set("Access-Control-Allow-Origin",s):o.headers.set("Access-Control-Allow-Origin","*"),r.methods){let c=Array.isArray(r.methods)?r.methods.join(", "):r.methods;o.headers.set("Access-Control-Allow-Methods",c)}else o.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(r.allowedHeaders){let c=Array.isArray(r.allowedHeaders)?r.allowedHeaders.join(", "):r.allowedHeaders;o.headers.set("Access-Control-Allow-Headers",c)}else{let c=n.headers.get("access-control-request-headers");c&&o.headers.set("Access-Control-Allow-Headers",c)}if(r.exposedHeaders){let c=Array.isArray(r.exposedHeaders)?r.exposedHeaders.join(", "):r.exposedHeaders;o.headers.set("Access-Control-Expose-Headers",c)}return r.credentials&&o.headers.set("Access-Control-Allow-Credentials","true"),r.maxAge!==void 0&&o.headers.set("Access-Control-Max-Age",r.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:o.headers,status:204}):o}}a(ae,"cors");function ce(r){let e=a(s=>{let i=s.request?.headers?.get("x-forwarded-for");return i?i.split(",")[0].trim():"global"},"defaultKeyGenerator"),t=r.keyGenerator||e,n=new Map;return async(s,i)=>{let o=t(s),c=Date.now(),l=n.get(o);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+r.windowMs):l.count++:(l={count:1,resetTime:c+r.windowMs},n.set(o,l)),Math.random()<.05)for(let[f,A]of n.entries())Date.now()>A.resetTime&&n.delete(f);if(l.count>r.limit){let f=Math.ceil((l.resetTime-c)/1e3);return new Response("Too Many Requests",{status:429,headers:{"Retry-After":f.toString(),"X-RateLimit-Limit":r.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let u=await i();u||(u=new Response);let p=new Response(u.body,u);return p.headers.set("X-RateLimit-Limit",r.limit.toString()),p.headers.set("X-RateLimit-Remaining",Math.max(0,r.limit-l.count).toString()),p.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),p}}a(ce,"throttle");0&&(module.exports={Authorize,Controller,Delete,FromBody,FromQuery,FromRoute,Get,HttpContext,HttpResult,Inject,Injectable,JwtAuthService,Patch,Post,Put,ServiceCollection,ServiceProvider,WebApplication,WebApplicationBuilder,cors,throttle});
1
+ "use strict";var H=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var qe=Object.prototype.hasOwnProperty;var a=(r,e)=>H(r,"name",{value:e,configurable:!0});var te=(r,e)=>()=>(r&&(e=r(r=0)),e);var re=(r,e)=>{for(var t in e)H(r,t,{get:e[t],enumerable:!0})},$e=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Ne(e))!qe.call(r,o)&&o!==t&&H(r,o,{get:()=>e[o],enumerable:!(n=De(e,o))||n.enumerable});return r};var ne=r=>$e(H({},"__esModule",{value:!0}),r);function C(){return r=>{Reflect.defineMetadata(ke,!0,r)}}function se(r){return(e,t,n)=>{let o=Reflect.getMetadata(L,e)||[];o.push({index:n,token:r}),Reflect.defineMetadata(L,o,e)}}var We,ke,L,D=te(()=>{"use strict";We=require("reflect-metadata"),ke=Symbol("INJECTABLE_WATERMARK"),L=Symbol("INJECT_METADATA");a(C,"Injectable");a(se,"Inject")});var Ie={};re(Ie,{JwtAuthService:()=>S});function Le(r,e,t,n){var o=arguments.length,s=o<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,t):n,i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(r,e,t,n);else for(var c=r.length-1;c>=0;c--)(i=r[c])&&(s=(o<3?i(s):o>3?i(e,t,s):i(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s}function Se(r,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(r,e)}function Z(r){return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function be(r){let e=r+"==".slice(2-r.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var S,V=te(()=>{"use strict";D();a(Le,"_ts_decorate");a(Se,"_ts_metadata");a(Z,"base64UrlEncode");a(be,"base64UrlDecode");S=class{static{a(this,"JwtAuthService")}secret;expiresIn;constructor(e){this.secret=e.secret,this.expiresIn=e.expiresIn??3600}async getKey(){let e=new TextEncoder;return crypto.subtle.importKey("raw",e.encode(this.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async sign(e){let t={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),o={...e,iat:n,exp:n+this.expiresIn},s=Z(JSON.stringify(t)),i=Z(JSON.stringify(o)),c=`${s}.${i}`,l=await this.getKey(),p=new TextEncoder,f=await crypto.subtle.sign("HMAC",l,p.encode(c)),m=Z(String.fromCharCode(...new Uint8Array(f)));return`${c}.${m}`}async verify(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT format");let n=t[0],o=t[1],s=t[2],i=`${n}.${o}`,c=await this.getKey(),l=new TextEncoder,p=Uint8Array.from(be(s),b=>b.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,p,l.encode(i)))throw new Error("Invalid JWT signature");let m=JSON.parse(be(o)),w=Math.floor(Date.now()/1e3);if(m.exp!==void 0&&m.exp<w)throw new Error("JWT expired");return m}};S=Le([C(),Se("design:type",Function),Se("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],S)});var Je={};re(Je,{ApiBody:()=>xe,ApiOperation:()=>we,ApiProperty:()=>ye,ApiQuery:()=>Me,ApiResponse:()=>Te,ApiTags:()=>Re,Authorize:()=>Ee,Controller:()=>oe,Delete:()=>le,FromBody:()=>fe,FromContext:()=>me,FromQuery:()=>ue,FromRoute:()=>de,Get:()=>ie,HttpContext:()=>O,HttpResult:()=>E,Inject:()=>se,Injectable:()=>C,JwtAuthService:()=>S,Patch:()=>pe,Post:()=>ae,Put:()=>ce,ServiceCollection:()=>P,ServiceProvider:()=>v,WebApplication:()=>_,WebApplicationBuilder:()=>j,cors:()=>ve,scalar:()=>K,throttle:()=>Pe});module.exports=ne(Je);var ze=require("reflect-metadata");D();var A=(function(r){return r[r.Singleton=0]="Singleton",r[r.Transient=1]="Transient",r[r.Scoped=2]="Scoped",r})({});var v=class r{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;trackedInstances=new Set;constructor(e,t){this.descriptors=e,this.parent=t}createScope(){return new r(this.descriptors,this.parent||this)}async dispose(){for(let e of this.trackedInstances)typeof e.onDestroy=="function"&&await e.onDestroy();this.trackedInstances.clear(),this.scopedInstances.clear(),this.singletonInstances.clear()}async resolve(e){let t=this.descriptors.find(n=>n.token===e);if(!t){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(t.lifetime){case A.Singleton:return this.resolveSingleton(t);case A.Scoped:return this.resolveScoped(t);case A.Transient:return this.resolveTransient(t)}}async resolveSingleton(e){let t=this.getRoot();if(t.singletonInstances.has(e.token))return t.singletonInstances.get(e.token);let n=await this.createInstance(t,e);return t.singletonInstances.set(e.token,n),n}async resolveScoped(e){if(this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let t=await this.createInstance(this,e);return this.scopedInstances.set(e.token,t),t}async resolveTransient(e){return this.createInstance(this,e)}async createInstance(e,t){let n;if(t.implementationInstance!==void 0)n=t.implementationInstance;else if(t.implementationFactory)n=await t.implementationFactory(this);else if(t.implementationType)n=await this.instantiateClass(t.implementationType);else throw new Error(`Invalid service descriptor for token: ${t.token.toString()}`);let o=Symbol.for("steampunk:initialized");return typeof n.onInit=="function"&&!n[o]&&(await n.onInit(),n[o]=!0),typeof n.onDestroy=="function"&&t.lifetime!==A.Transient&&e.trackedInstances.add(n),n}getRoot(){return this.parent?this.parent.getRoot():this}async instantiateClass(e){let t=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(L,e)||[],o=await Promise.all(t.map(async(s,i)=>{let c=n.find(p=>p.index===i),l=c?c.token:s;if(!l||l===Object||l===String||l===Number||l===Boolean)throw new Error(`Cannot resolve parameter at index ${i} for ${e.name}. Token could not be determined or is primitive. Cannot inject primitive types directly without @Inject().`);return this.resolve(l)}));return new e(...o)}};var P=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Singleton})}addSingletonInstance(e,t){return this.add({token:e,implementationInstance:t,lifetime:A.Singleton})}addTransient(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Transient})}addScoped(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Scoped})}buildServiceProvider(){return new v(this.descriptors)}addControllers(e){return e.forEach(t=>this.addTransient(t)),this}};var O=class{static{a(this,"HttpContext")}request;items=new Map;constructor(e){this.request=e}get method(){return this.request.method}get url(){return this.request.url}get path(){return new URL(this.request.url).pathname}};var ot=require("reflect-metadata");D();var M=Symbol("CONTROLLER_WATERMARK"),T=Symbol("ROUTE_METADATA"),N=Symbol("PARAM_METADATA");function oe(r=""){return e=>{Reflect.defineMetadata(M,r,e),C()(e)}}a(oe,"Controller");var q=a(r=>(e="")=>(t,n)=>{Reflect.hasMetadata(T,t.constructor)||Reflect.defineMetadata(T,[],t.constructor);let o=Reflect.getMetadata(T,t.constructor);o.push({method:r,path:e,methodName:n.toString()}),Reflect.defineMetadata(T,o,t.constructor)},"createRouteDecorator"),ie=q("GET"),ae=q("POST"),ce=q("PUT"),le=q("DELETE"),pe=q("PATCH"),h=(function(r){return r[r.Route=0]="Route",r[r.Query=1]="Query",r[r.Body=2]="Body",r[r.Context=3]="Context",r})({});function de(r,e){return(t,n,o)=>{B(t,n,{type:0,index:o,name:r,schema:e})}}a(de,"FromRoute");function ue(r,e){return(t,n,o)=>{B(t,n,{type:1,index:o,name:r,schema:e})}}a(ue,"FromQuery");function fe(r){return(e,t,n)=>{B(e,t,{type:2,index:n,schema:r})}}a(fe,"FromBody");function me(){return(r,e,t)=>{B(r,e,{type:3,index:t})}}a(me,"FromContext");function B(r,e,t){let n=`${N.toString()}_${e.toString()}`;Reflect.hasMetadata(n,r.constructor)||Reflect.defineMetadata(n,[],r.constructor);let o=Reflect.getMetadata(n,r.constructor);o.push(t),Reflect.defineMetadata(n,o,r.constructor)}a(B,"addParamMetadata");var lt=require("reflect-metadata");var J="steampunk:api_property";function ye(r={}){return(e,t)=>{let n=Reflect.getOwnMetadata(J,e.constructor)||[],o=Reflect.getMetadata("design:type",e,t),s;o&&(o===String?s="string":o===Number?s="number":o===Boolean?s="boolean":o===Array?s="array":o===Object&&(s="object")),n.push({propertyKey:t.toString(),type:r.type??s,...r}),Reflect.defineMetadata(J,n,e.constructor)}}a(ye,"ApiProperty");function ge(r){let e=Reflect.getOwnMetadata(J,r)||[],t=[],n={};for(let s of e){if(s.required!==!1&&t.push(s.propertyKey),s.schema){n[s.propertyKey]=s.schema;continue}let i={};s.type!==void 0&&(typeof s.type=="string"?i.type=s.type:typeof s.type=="object"&&"type"in s.type&&(i.type=s.type.type,s.type.items&&(i.items=he(s.type.items)))),s.type==="array"&&s.items&&(i.items=he(s.items)),s.description&&(i.description=s.description),s.example!==void 0&&(i.example=s.example),s.nullable&&(i.nullable=!0),s.enum&&(i.enum=s.enum),s.default!==void 0&&(i.default=s.default),n[s.propertyKey]=i}let o={type:"object"};return Object.keys(n).length>0&&(o.properties=n),t.length>0&&(o.required=t),o}a(ge,"buildSchemaFromClass");function he(r){return typeof r=="string"?{type:r}:typeof r=="object"&&"$ref"in r||typeof r=="object"&&"type"in r?r:{}}a(he,"buildItemsSchema");function Ae(r){return typeof r=="function"&&Reflect.hasOwnMetadata(J,r)}a(Ae,"isDto");var ut=require("reflect-metadata");var Q="steampunk:api_tags",X="steampunk:api_operation",U="steampunk:api_response",Y="steampunk:api_body",W="steampunk:api_query";function Re(...r){return e=>{Reflect.defineMetadata(Q,r,e)}}a(Re,"ApiTags");function we(r){return(e,t)=>{Reflect.defineMetadata(X,r,e.constructor,t)}}a(we,"ApiOperation");function Te(r){return(e,t)=>{let n=Reflect.getMetadata(U,e.constructor,t)||[];n.push(r),Reflect.defineMetadata(U,n,e.constructor,t)}}a(Te,"ApiResponse");function xe(r){return(e,t)=>{Reflect.defineMetadata(Y,r,e.constructor,t)}}a(xe,"ApiBody");function Me(r){return(e,t)=>{let n=Reflect.getMetadata(W,e.constructor,t)||[];n.push(r),Reflect.defineMetadata(W,n,e.constructor,t)}}a(Me,"ApiQuery");var F=class{static{a(this,"OpenApiGenerator")}controllers;options;schemaRegistry=new Map;constructor(e,t){this.controllers=e,this.options=t}refFromClass(e){let t=e.name;return this.schemaRegistry.has(t)||this.schemaRegistry.set(t,ge(e)),{$ref:`#/components/schemas/${t}`}}resolveSchema(e,t){if(t)return t;if(e&&Ae(e))return this.refFromClass(e);if(e)return{type:"object",description:e.name}}generate(){let e={},t=[],n=new Set;for(let s of this.controllers){let i=Reflect.getMetadata(M,s);if(i===void 0)continue;let c=Reflect.getMetadata(Q,s)||[];for(let p of c)n.has(p)||(n.add(p),t.push({name:p}));let l=Reflect.getMetadata(T,s)||[];for(let p of l){let f=`/${i}/${p.path}`.replace(/\/+/g,"/");f!=="/"&&f.endsWith("/")&&(f=f.slice(0,-1));let m=f.replace(/:([^/]+)/g,"{$1}");e[m]||(e[m]={});let w=`${N.toString()}_${p.methodName}`,b=Reflect.getMetadata(w,s)||[],y=Reflect.getMetadata(X,s,p.methodName),k=Reflect.getMetadata(U,s,p.methodName)||[],g=Reflect.getMetadata(Y,s,p.methodName),z=Reflect.getMetadata(W,s,p.methodName)||[],u=[];for(let d of b)d.type===h.Route&&d.name?u.push({name:d.name,in:"path",required:!0,schema:d.schema??{type:"string"}}):d.type===h.Query&&d.name&&u.push({name:d.name,in:"query",required:!1,schema:d.schema??{type:"string"}});for(let d of z)u.find(_e=>_e.name===d.name)||u.push({name:d.name,in:"query",required:d.required??!1,description:d.description,schema:d.schema??{type:"string"},...d.example!==void 0?{example:d.example}:{}});let R=b.some(d=>d.type===h.Body),x;if(R||g){let d=this.resolveSchema(g?.type,g?.schema),I=g?.example;x={...g?.description?{description:g.description}:{},required:g?.required??!0,content:{"application/json":{...d?{schema:d}:{},...I!==void 0?{example:I}:{}}}}}let Oe=k.length>0?Object.fromEntries(k.map(d=>{let I=this.resolveSchema(d.type,d.schema);return[String(d.status),{description:d.description,...I?{content:{"application/json":{schema:I,...d.example!==void 0?{example:d.example}:{}}}}:{}}]})):{200:{description:"Success"}},je={...c.length>0?{tags:c}:{},...y?.summary?{summary:y.summary}:{},...y?.description?{description:y.description}:{},...y?.operationId?{operationId:y.operationId}:{operationId:`${s.name}_${p.methodName}`},...y?.deprecated?{deprecated:!0}:{},...u.length>0?{parameters:u}:{},...x?{requestBody:x}:{},responses:Oe};e[m][p.method.toLowerCase()]=je}}let o=this.schemaRegistry.size>0?{schemas:Object.fromEntries(this.schemaRegistry)}:void 0;return{openapi:"3.1.0",info:this.options.info,...this.options.servers?{servers:this.options.servers}:{},...t.length>0?{tags:t}:{},paths:e,...o?{components:o}:{}}}};var He=a((r,e)=>`<!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>${e} - API Docs</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ </head>
8
+ <body>
9
+ <script
10
+ id="api-reference"
11
+ data-url="${r}"
12
+ data-configuration='{"theme":"purple"}'
13
+ ></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
15
+ </body>
16
+ </html>`,"SCALAR_HTML");function K(r,e,t={}){let n=t.docsPath??"/docs",o=t.specPath??"/openapi.json",s=null;return async(i,c)=>{let{path:l}=i;if(l===o){if(!s){let p=new F(r,e);s=JSON.stringify(p.generate(),null,2)}return new Response(s,{headers:{"Content-Type":"application/json"}})}return l===n||l===`${n}/`?new Response(He(o,e.info.title),{headers:{"Content-Type":"text/html; charset=utf-8"}}):c()}}a(K,"scalar");var Mt=require("reflect-metadata");var $=Symbol("AUTHORIZE_METADATA");function Ee(r){return(e,t)=>{let n={required:!0,roles:r};t!==void 0?Reflect.defineMetadata($,n,e.constructor,t):Reflect.defineMetadata($,n,e)}}a(Ee,"Authorize");var E=class r{static{a(this,"HttpResult")}status;body;headers;constructor(e,t,n={}){this.status=e,this.body=t!==void 0&&typeof t!="string"?JSON.stringify(t):t,this.headers=n}static ok(e){return new r(200,e)}static created(e,t){let n={};return t&&(n.Location=t),new r(201,e,n)}static noContent(){return new r(204)}static badRequest(e="Bad Request",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(400,n)}static unauthorized(e="Unauthorized"){let t=typeof e=="string"?{message:e}:e;return new r(401,t)}static forbidden(e="Forbidden"){let t=typeof e=="string"?{message:e}:e;return new r(403,t)}static notFound(e="Not Found"){let t=typeof e=="string"?{message:e}:e;return new r(404,t)}static conflict(e="Conflict"){let t=typeof e=="string"?{message:e}:e;return new r(409,t)}static unprocessableEntity(e="Unprocessable Entity",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(422,n)}static internalServerError(e="Internal Server Error"){let t=typeof e=="string"?{message:e}:e;return new r(500,t)}toResponse(){if(this.status===204||this.body===void 0)return new Response(null,{status:this.status,headers:this.headers});let e={"Content-Type":"application/json",...this.headers};return new Response(this.body,{status:this.status,headers:e})}};async function Be(r,e,t){if(!t.required)return null;let n=r.request.headers.get("authorization");if(!n||!n.startsWith("Bearer "))return new Response(JSON.stringify({message:"Unauthorized"}),{status:401,headers:{"Content-Type":"application/json"}});let o=n.slice(7),s;try{let{JwtAuthService:c}=(V(),ne(Ie));s=await e.resolve(c)}catch{return new Response(JSON.stringify({message:"JWT service not configured"}),{status:500,headers:{"Content-Type":"application/json"}})}let i;try{i=await s.verify(o)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(t.roles&&t.roles.length>0){let c=i.roles??[];if(!t.roles.some(p=>c.includes(p)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return r.items.set("user",i),null}a(Be,"checkAuth");var G=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let t of e){let n=Reflect.getMetadata(M,t);if(n===void 0)continue;let o=Reflect.getMetadata(T,t)||[];for(let s of o){let i=`/${n}/${s.path}`.replace(/\/+/g,"/");i!=="/"&&i.endsWith("/")&&(i=i.slice(0,-1));let c=new URLPattern({pathname:i});this.routes.push({method:s.method,pattern:c,controller:t,methodName:s.methodName})}}}map(e,t,n){let o=t.startsWith("/")?t:`/${t}`;o!=="/"&&o.endsWith("/")&&(o=o.slice(0,-1));let s=new URLPattern({pathname:o});this.routes.push({method:e.toUpperCase(),pattern:s,handler:n})}middleware(){return async(e,t)=>{for(let n of this.routes){if(n.method!==e.method)continue;let o=n.pattern.exec({pathname:e.path});if(o){let s=e.items.get("scope");if(!s)throw new Error("DI Scope not found in HttpContext");let i;if(n.handler)i=await n.handler(e);else if(n.controller&&n.methodName){let c=Reflect.getMetadata($,n.controller,n.methodName),l=Reflect.getMetadata($,n.controller),p=c??l;if(p){let u=await Be(e,s,p);if(u)return u}let f=await s.resolve(n.controller),m=`${N.toString()}_${n.methodName}`,w=Reflect.getMetadata(m,n.controller)||[],b=f[n.methodName].length,y=new Array(b).fill(void 0),k=w.some(u=>u.type===h.Body),g;k&&(g=await e.request.json().catch(()=>({})));let z=new URL(e.request.url);for(let u of w){let R;if(u.type===h.Route&&u.name?R=o.pathname.groups[u.name]:u.type===h.Query&&u.name?R=z.searchParams.get(u.name):u.type===h.Body?R=g:u.type===h.Context&&(R=e),u.schema){let x=u.schema.safeParse(R);if(!x.success)return E.unprocessableEntity("Validation failed",{errors:x.error.errors,target:h[u.type].toLowerCase(),name:u.name}).toResponse();R=x.data}y[u.index]=R}i=await f[n.methodName](...y)}return i instanceof Response?i:i instanceof E?i.toResponse():typeof i=="object"?Response.json(i):new Response(String(i))}}return t()}}};var Ce=require("fs/promises"),ee=require("path");var j=class{static{a(this,"WebApplicationBuilder")}services;controllers=[];constructor(){this.services=new P}async addControllers(e="controllers"){let t=(0,ee.join)(process.cwd(),e);try{let n=await(0,Ce.readdir)(t,{recursive:!0});for(let o of n)if(typeof o=="string"&&(o.endsWith(".ts")||o.endsWith(".js"))&&!o.endsWith(".d.ts")){let i=await import((0,ee.join)(t,o));for(let c in i){let l=i[c];typeof l=="function"&&Reflect.hasMetadata(M,l)&&this.controllers.push(l)}}this.controllers.length>0&&this.services.addControllers(this.controllers)}catch(n){console.warn(`Could not discover controllers at ${t}:`,n)}return this}build(){let e=this.services.buildServiceProvider();return new _(e,this.controllers)}};var _=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];router;constructor(e,t=[]){this.services=e,this.controllersToMap.push(...t),this.router=new G}static createBuilder(){return new j}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}useOpenApi(e,t){let n=K(this.controllersToMap,e,t);return this.middlewares.push(n),this}mapGet(e,t){return this.router.map("GET",e,t),this}mapPost(e,t){return this.router.map("POST",e,t),this}mapPut(e,t){return this.router.map("PUT",e,t),this}mapDelete(e,t){return this.router.map("DELETE",e,t),this}mapPatch(e,t){return this.router.map("PATCH",e,t),this}async executePipeline(e){let t=-1,n=a(async s=>{if(s<=t)throw new Error("next() called multiple times");t=s;let i=this.middlewares[s];if(s===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(i)return i(e,n.bind(null,s+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let t=this;this.controllersToMap.length>0&&this.router.registerControllers(this.controllersToMap),this.use(this.router.middleware()),Bun.serve({port:e,async fetch(n){let o=t.services.createScope();try{let s=new O(n);return s.items.set("scope",o),await t.executePipeline(s)}finally{await o.dispose()}}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};D();V();function ve(r={}){return async(e,t)=>{let{request:n}=e,o=n.headers.get("origin"),s=await t();s||(s=new Response);let i=new Response(s.body,s);if(r.origin?typeof r.origin=="string"?i.headers.set("Access-Control-Allow-Origin",r.origin):Array.isArray(r.origin)&&o?r.origin.includes(o)&&i.headers.set("Access-Control-Allow-Origin",o):typeof r.origin=="function"&&o&&r.origin(o)&&i.headers.set("Access-Control-Allow-Origin",o):i.headers.set("Access-Control-Allow-Origin","*"),r.methods){let c=Array.isArray(r.methods)?r.methods.join(", "):r.methods;i.headers.set("Access-Control-Allow-Methods",c)}else i.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(r.allowedHeaders){let c=Array.isArray(r.allowedHeaders)?r.allowedHeaders.join(", "):r.allowedHeaders;i.headers.set("Access-Control-Allow-Headers",c)}else{let c=n.headers.get("access-control-request-headers");c&&i.headers.set("Access-Control-Allow-Headers",c)}if(r.exposedHeaders){let c=Array.isArray(r.exposedHeaders)?r.exposedHeaders.join(", "):r.exposedHeaders;i.headers.set("Access-Control-Expose-Headers",c)}return r.credentials&&i.headers.set("Access-Control-Allow-Credentials","true"),r.maxAge!==void 0&&i.headers.set("Access-Control-Max-Age",r.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:i.headers,status:204}):i}}a(ve,"cors");function Pe(r){let e=a(o=>{let s=o.request?.headers?.get("x-forwarded-for");return s?s.split(",")[0].trim():"global"},"defaultKeyGenerator"),t=r.keyGenerator||e,n=new Map;return async(o,s)=>{let i=t(o),c=Date.now(),l=n.get(i);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+r.windowMs):l.count++:(l={count:1,resetTime:c+r.windowMs},n.set(i,l)),Math.random()<.05)for(let[m,w]of n.entries())Date.now()>w.resetTime&&n.delete(m);if(l.count>r.limit){let m=Math.ceil((l.resetTime-c)/1e3);return new Response("Too Many Requests",{status:429,headers:{"Retry-After":m.toString(),"X-RateLimit-Limit":r.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let p=await s();p||(p=new Response);let f=new Response(p.body,p);return f.headers.set("X-RateLimit-Limit",r.limit.toString()),f.headers.set("X-RateLimit-Remaining",Math.max(0,r.limit-l.count).toString()),f.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),f}}a(Pe,"throttle");0&&(module.exports={ApiBody,ApiOperation,ApiProperty,ApiQuery,ApiResponse,ApiTags,Authorize,Controller,Delete,FromBody,FromContext,FromQuery,FromRoute,Get,HttpContext,HttpResult,Inject,Injectable,JwtAuthService,Patch,Post,Put,ServiceCollection,ServiceProvider,WebApplication,WebApplicationBuilder,cors,scalar,throttle});
package/dist/main.d.cts CHANGED
@@ -60,6 +60,36 @@ declare class HttpContext {
60
60
  get path(): string;
61
61
  }
62
62
 
63
+ interface ApiInfo {
64
+ title: string;
65
+ version: string;
66
+ description?: string;
67
+ contact?: {
68
+ name?: string;
69
+ email?: string;
70
+ url?: string;
71
+ };
72
+ license?: {
73
+ name: string;
74
+ url?: string;
75
+ };
76
+ }
77
+ interface OpenApiOptions {
78
+ info: ApiInfo;
79
+ servers?: Array<{
80
+ url: string;
81
+ description?: string;
82
+ }>;
83
+ }
84
+
85
+ interface ScalarOptions {
86
+ /** Path to serve the Scalar UI at. Default: "/docs" */
87
+ docsPath?: string;
88
+ /** Path to serve the OpenAPI JSON spec at. Default: "/openapi.json" */
89
+ specPath?: string;
90
+ }
91
+ declare function scalar(controllers: Array<any>, apiOptions: OpenApiOptions, scalarOptions?: ScalarOptions): Middleware;
92
+
63
93
  declare class WebApplicationBuilder {
64
94
  services: ServiceCollection;
65
95
  private controllers;
@@ -78,6 +108,8 @@ declare class WebApplication {
78
108
  static createBuilder(): WebApplicationBuilder;
79
109
  use(middleware: Middleware): this;
80
110
  mapControllers(controllers: Array<any>): this;
111
+ /** Mount the Scalar UI and OpenAPI spec automatically using the registered controllers. */
112
+ useOpenApi(apiOptions: OpenApiOptions, scalarOptions?: ScalarOptions): this;
81
113
  mapGet(path: string, handler: (context: HttpContext) => any): this;
82
114
  mapPost(path: string, handler: (context: HttpContext) => any): this;
83
115
  mapPut(path: string, handler: (context: HttpContext) => any): this;
@@ -127,6 +159,7 @@ declare const Patch: (path?: string) => MethodDecorator;
127
159
  declare function FromRoute(name: string, schema?: any): ParameterDecorator;
128
160
  declare function FromQuery(name: string, schema?: any): ParameterDecorator;
129
161
  declare function FromBody(schema?: any): ParameterDecorator;
162
+ declare function FromContext(): ParameterDecorator;
130
163
 
131
164
  interface AuthorizeOptions {
132
165
  required: boolean;
@@ -176,4 +209,95 @@ interface ThrottleOptions {
176
209
  }
177
210
  declare function throttle(options: ThrottleOptions): Middleware;
178
211
 
179
- export { Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, type OnDestroy, type OnInit, Patch, Post, Put, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, throttle };
212
+ type ApiPropertyType = "string" | "number" | "integer" | "boolean" | "object" | "array" | {
213
+ type: "array";
214
+ items: string | Record<string, unknown>;
215
+ } | {
216
+ $ref: string;
217
+ };
218
+ interface ApiPropertyOptions {
219
+ description?: string;
220
+ example?: unknown;
221
+ required?: boolean;
222
+ type?: ApiPropertyType | string;
223
+ /** For array types, define the item type */
224
+ items?: ApiPropertyType | Record<string, unknown>;
225
+ /** Override with a raw JSON Schema fragment */
226
+ schema?: Record<string, unknown>;
227
+ /** Whether the property can be null */
228
+ nullable?: boolean;
229
+ /** Enum values */
230
+ enum?: Array<unknown>;
231
+ /** Default value */
232
+ default?: unknown;
233
+ }
234
+ /**
235
+ * Documents a class property for OpenAPI schema generation.
236
+ * Use this on DTO classes to enable automatic JSON Schema generation.
237
+ *
238
+ * @example
239
+ * class LoginDto {
240
+ * @ApiProperty({ description: 'Username', example: 'admin' })
241
+ * username: string;
242
+ *
243
+ * @ApiProperty({ description: 'Password', example: '1234' })
244
+ * password: string;
245
+ * }
246
+ */
247
+ declare function ApiProperty(options?: ApiPropertyOptions): PropertyDecorator;
248
+
249
+ interface ApiOperationOptions {
250
+ summary?: string;
251
+ description?: string;
252
+ operationId?: string;
253
+ deprecated?: boolean;
254
+ }
255
+ interface ApiResponseOptions {
256
+ status: number;
257
+ description: string;
258
+ /** Pass a DTO class decorated with @ApiProperty to auto-generate the schema */
259
+ type?: new (...args: Array<any>) => any;
260
+ /** Raw JSON Schema override (takes precedence over type) */
261
+ schema?: Record<string, unknown>;
262
+ example?: unknown;
263
+ }
264
+ interface ApiBodyOptions {
265
+ description?: string;
266
+ required?: boolean;
267
+ /** Pass a DTO class decorated with @ApiProperty to auto-generate the schema */
268
+ type?: new (...args: Array<any>) => any;
269
+ /** Raw JSON Schema override (takes precedence over type) */
270
+ schema?: Record<string, unknown>;
271
+ example?: unknown;
272
+ }
273
+ interface ApiQueryParamOptions {
274
+ name: string;
275
+ description?: string;
276
+ required?: boolean;
277
+ schema?: Record<string, unknown>;
278
+ example?: unknown;
279
+ }
280
+ /** Groups routes under tags in the Scalar/Swagger UI */
281
+ declare function ApiTags(...tags: Array<string>): ClassDecorator;
282
+ /** Documents the operation with summary, description, operationId, deprecated */
283
+ declare function ApiOperation(options: ApiOperationOptions): MethodDecorator;
284
+ /**
285
+ * Documents a possible response for the route.
286
+ * Can be stacked multiple times for different status codes.
287
+ *
288
+ * @example
289
+ * @ApiResponse({ status: 200, type: UserDto })
290
+ * @ApiResponse({ status: 404, description: 'Not found' })
291
+ */
292
+ declare function ApiResponse(options: ApiResponseOptions): MethodDecorator;
293
+ /**
294
+ * Documents the request body.
295
+ *
296
+ * @example
297
+ * @ApiBody({ type: CreateUserDto })
298
+ */
299
+ declare function ApiBody(options: ApiBodyOptions): MethodDecorator;
300
+ /** Documents an additional query parameter (alongside or instead of @FromQuery) */
301
+ declare function ApiQuery(options: ApiQueryParamOptions): MethodDecorator;
302
+
303
+ export { ApiBody, type ApiBodyOptions, type ApiInfo, ApiOperation, type ApiOperationOptions, ApiProperty, type ApiPropertyOptions, ApiQuery, type ApiQueryParamOptions, ApiResponse, type ApiResponseOptions, ApiTags, Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromContext, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, type OnDestroy, type OnInit, type OpenApiOptions, Patch, Post, Put, type ScalarOptions, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, scalar, throttle };
package/dist/main.d.ts CHANGED
@@ -60,6 +60,36 @@ declare class HttpContext {
60
60
  get path(): string;
61
61
  }
62
62
 
63
+ interface ApiInfo {
64
+ title: string;
65
+ version: string;
66
+ description?: string;
67
+ contact?: {
68
+ name?: string;
69
+ email?: string;
70
+ url?: string;
71
+ };
72
+ license?: {
73
+ name: string;
74
+ url?: string;
75
+ };
76
+ }
77
+ interface OpenApiOptions {
78
+ info: ApiInfo;
79
+ servers?: Array<{
80
+ url: string;
81
+ description?: string;
82
+ }>;
83
+ }
84
+
85
+ interface ScalarOptions {
86
+ /** Path to serve the Scalar UI at. Default: "/docs" */
87
+ docsPath?: string;
88
+ /** Path to serve the OpenAPI JSON spec at. Default: "/openapi.json" */
89
+ specPath?: string;
90
+ }
91
+ declare function scalar(controllers: Array<any>, apiOptions: OpenApiOptions, scalarOptions?: ScalarOptions): Middleware;
92
+
63
93
  declare class WebApplicationBuilder {
64
94
  services: ServiceCollection;
65
95
  private controllers;
@@ -78,6 +108,8 @@ declare class WebApplication {
78
108
  static createBuilder(): WebApplicationBuilder;
79
109
  use(middleware: Middleware): this;
80
110
  mapControllers(controllers: Array<any>): this;
111
+ /** Mount the Scalar UI and OpenAPI spec automatically using the registered controllers. */
112
+ useOpenApi(apiOptions: OpenApiOptions, scalarOptions?: ScalarOptions): this;
81
113
  mapGet(path: string, handler: (context: HttpContext) => any): this;
82
114
  mapPost(path: string, handler: (context: HttpContext) => any): this;
83
115
  mapPut(path: string, handler: (context: HttpContext) => any): this;
@@ -127,6 +159,7 @@ declare const Patch: (path?: string) => MethodDecorator;
127
159
  declare function FromRoute(name: string, schema?: any): ParameterDecorator;
128
160
  declare function FromQuery(name: string, schema?: any): ParameterDecorator;
129
161
  declare function FromBody(schema?: any): ParameterDecorator;
162
+ declare function FromContext(): ParameterDecorator;
130
163
 
131
164
  interface AuthorizeOptions {
132
165
  required: boolean;
@@ -176,4 +209,95 @@ interface ThrottleOptions {
176
209
  }
177
210
  declare function throttle(options: ThrottleOptions): Middleware;
178
211
 
179
- export { Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, type OnDestroy, type OnInit, Patch, Post, Put, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, throttle };
212
+ type ApiPropertyType = "string" | "number" | "integer" | "boolean" | "object" | "array" | {
213
+ type: "array";
214
+ items: string | Record<string, unknown>;
215
+ } | {
216
+ $ref: string;
217
+ };
218
+ interface ApiPropertyOptions {
219
+ description?: string;
220
+ example?: unknown;
221
+ required?: boolean;
222
+ type?: ApiPropertyType | string;
223
+ /** For array types, define the item type */
224
+ items?: ApiPropertyType | Record<string, unknown>;
225
+ /** Override with a raw JSON Schema fragment */
226
+ schema?: Record<string, unknown>;
227
+ /** Whether the property can be null */
228
+ nullable?: boolean;
229
+ /** Enum values */
230
+ enum?: Array<unknown>;
231
+ /** Default value */
232
+ default?: unknown;
233
+ }
234
+ /**
235
+ * Documents a class property for OpenAPI schema generation.
236
+ * Use this on DTO classes to enable automatic JSON Schema generation.
237
+ *
238
+ * @example
239
+ * class LoginDto {
240
+ * @ApiProperty({ description: 'Username', example: 'admin' })
241
+ * username: string;
242
+ *
243
+ * @ApiProperty({ description: 'Password', example: '1234' })
244
+ * password: string;
245
+ * }
246
+ */
247
+ declare function ApiProperty(options?: ApiPropertyOptions): PropertyDecorator;
248
+
249
+ interface ApiOperationOptions {
250
+ summary?: string;
251
+ description?: string;
252
+ operationId?: string;
253
+ deprecated?: boolean;
254
+ }
255
+ interface ApiResponseOptions {
256
+ status: number;
257
+ description: string;
258
+ /** Pass a DTO class decorated with @ApiProperty to auto-generate the schema */
259
+ type?: new (...args: Array<any>) => any;
260
+ /** Raw JSON Schema override (takes precedence over type) */
261
+ schema?: Record<string, unknown>;
262
+ example?: unknown;
263
+ }
264
+ interface ApiBodyOptions {
265
+ description?: string;
266
+ required?: boolean;
267
+ /** Pass a DTO class decorated with @ApiProperty to auto-generate the schema */
268
+ type?: new (...args: Array<any>) => any;
269
+ /** Raw JSON Schema override (takes precedence over type) */
270
+ schema?: Record<string, unknown>;
271
+ example?: unknown;
272
+ }
273
+ interface ApiQueryParamOptions {
274
+ name: string;
275
+ description?: string;
276
+ required?: boolean;
277
+ schema?: Record<string, unknown>;
278
+ example?: unknown;
279
+ }
280
+ /** Groups routes under tags in the Scalar/Swagger UI */
281
+ declare function ApiTags(...tags: Array<string>): ClassDecorator;
282
+ /** Documents the operation with summary, description, operationId, deprecated */
283
+ declare function ApiOperation(options: ApiOperationOptions): MethodDecorator;
284
+ /**
285
+ * Documents a possible response for the route.
286
+ * Can be stacked multiple times for different status codes.
287
+ *
288
+ * @example
289
+ * @ApiResponse({ status: 200, type: UserDto })
290
+ * @ApiResponse({ status: 404, description: 'Not found' })
291
+ */
292
+ declare function ApiResponse(options: ApiResponseOptions): MethodDecorator;
293
+ /**
294
+ * Documents the request body.
295
+ *
296
+ * @example
297
+ * @ApiBody({ type: CreateUserDto })
298
+ */
299
+ declare function ApiBody(options: ApiBodyOptions): MethodDecorator;
300
+ /** Documents an additional query parameter (alongside or instead of @FromQuery) */
301
+ declare function ApiQuery(options: ApiQueryParamOptions): MethodDecorator;
302
+
303
+ export { ApiBody, type ApiBodyOptions, type ApiInfo, ApiOperation, type ApiOperationOptions, ApiProperty, type ApiPropertyOptions, ApiQuery, type ApiQueryParamOptions, ApiResponse, type ApiResponseOptions, ApiTags, Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromContext, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, type OnDestroy, type OnInit, type OpenApiOptions, Patch, Post, Put, type ScalarOptions, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, scalar, throttle };
package/dist/main.js CHANGED
@@ -1 +1,16 @@
1
- var P=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var a=(r,e)=>P(r,"name",{value:e,configurable:!0});var q=(r,e)=>()=>(r&&(e=r(r=0)),e);var V=(r,e)=>{for(var t in e)P(r,t,{get:e[t],enumerable:!0})},Y=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Q(e))!Z.call(r,s)&&s!==t&&P(r,s,{get:()=>e[s],enumerable:!(n=X(e,s))||n.enumerable});return r};var ee=r=>Y(P({},"__esModule",{value:!0}),r);import"reflect-metadata";function T(){return r=>{Reflect.defineMetadata(te,!0,r)}}function re(r){return(e,t,n)=>{let s=Reflect.getMetadata(j,e)||[];s.push({index:n,token:r}),Reflect.defineMetadata(j,s,e)}}var te,j,x=q(()=>{"use strict";te=Symbol("INJECTABLE_WATERMARK"),j=Symbol("INJECT_METADATA");a(T,"Injectable");a(re,"Inject")});var F={};V(F,{JwtAuthService:()=>R});function pe(r,e,t,n){var s=arguments.length,i=s<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,t):n,o;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")i=Reflect.decorate(r,e,t,n);else for(var c=r.length-1;c>=0;c--)(o=r[c])&&(i=(s<3?o(i):s>3?o(e,t,i):o(e,t))||i);return s>3&&i&&Object.defineProperty(e,t,i),i}function B(r,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(r,e)}function J(r){return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function _(r){let e=r+"==".slice(2-r.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var R,U=q(()=>{"use strict";x();a(pe,"_ts_decorate");a(B,"_ts_metadata");a(J,"base64UrlEncode");a(_,"base64UrlDecode");R=class{static{a(this,"JwtAuthService")}secret;expiresIn;constructor(e){this.secret=e.secret,this.expiresIn=e.expiresIn??3600}async getKey(){let e=new TextEncoder;return crypto.subtle.importKey("raw",e.encode(this.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async sign(e){let t={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),s={...e,iat:n,exp:n+this.expiresIn},i=J(JSON.stringify(t)),o=J(JSON.stringify(s)),c=`${i}.${o}`,l=await this.getKey(),u=new TextEncoder,p=await crypto.subtle.sign("HMAC",l,u.encode(c)),f=J(String.fromCharCode(...new Uint8Array(p)));return`${c}.${f}`}async verify(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT format");let n=t[0],s=t[1],i=t[2],o=`${n}.${s}`,c=await this.getKey(),l=new TextEncoder,u=Uint8Array.from(_(i),D=>D.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,u,l.encode(o)))throw new Error("Invalid JWT signature");let f=JSON.parse(_(s)),g=Math.floor(Date.now()/1e3);if(f.exp!==void 0&&f.exp<g)throw new Error("JWT expired");return f}};R=pe([T(),B("design:type",Function),B("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],R)});x();import"reflect-metadata";var h=(function(r){return r[r.Singleton=0]="Singleton",r[r.Transient=1]="Transient",r[r.Scoped=2]="Scoped",r})({});var E=class r{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;trackedInstances=new Set;constructor(e,t){this.descriptors=e,this.parent=t}createScope(){return new r(this.descriptors,this.parent||this)}async dispose(){for(let e of this.trackedInstances)typeof e.onDestroy=="function"&&await e.onDestroy();this.trackedInstances.clear(),this.scopedInstances.clear(),this.singletonInstances.clear()}async resolve(e){let t=this.descriptors.find(n=>n.token===e);if(!t){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(t.lifetime){case h.Singleton:return this.resolveSingleton(t);case h.Scoped:return this.resolveScoped(t);case h.Transient:return this.resolveTransient(t)}}async resolveSingleton(e){let t=this.getRoot();if(t.singletonInstances.has(e.token))return t.singletonInstances.get(e.token);let n=await this.createInstance(t,e);return t.singletonInstances.set(e.token,n),n}async resolveScoped(e){if(this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let t=await this.createInstance(this,e);return this.scopedInstances.set(e.token,t),t}async resolveTransient(e){return this.createInstance(this,e)}async createInstance(e,t){let n;if(t.implementationInstance!==void 0)n=t.implementationInstance;else if(t.implementationFactory)n=await t.implementationFactory(this);else if(t.implementationType)n=await this.instantiateClass(t.implementationType);else throw new Error(`Invalid service descriptor for token: ${t.token.toString()}`);let s=Symbol.for("steampunk:initialized");return typeof n.onInit=="function"&&!n[s]&&(await n.onInit(),n[s]=!0),typeof n.onDestroy=="function"&&t.lifetime!==h.Transient&&e.trackedInstances.add(n),n}getRoot(){return this.parent?this.parent.getRoot():this}async instantiateClass(e){let t=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(j,e)||[],s=await Promise.all(t.map(async(i,o)=>{let c=n.find(u=>u.index===o),l=c?c.token:i;if(!l||l===Object||l===String||l===Number||l===Boolean)throw new Error(`Cannot resolve parameter at index ${o} for ${e.name}. Token could not be determined or is primitive. Cannot inject primitive types directly without @Inject().`);return this.resolve(l)}));return new e(...s)}};var M=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Singleton})}addSingletonInstance(e,t){return this.add({token:e,implementationInstance:t,lifetime:h.Singleton})}addTransient(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Transient})}addScoped(e,t){return this.add({token:e,implementationType:t||e,lifetime:h.Scoped})}buildServiceProvider(){return new E(this.descriptors)}addControllers(e){return e.forEach(t=>this.addTransient(t)),this}};var S=class{static{a(this,"HttpContext")}request;items=new Map;constructor(e){this.request=e}get method(){return this.request.method}get url(){return this.request.url}get path(){return new URL(this.request.url).pathname}};import"reflect-metadata";var C=Symbol("AUTHORIZE_METADATA");function ne(r){return(e,t)=>{let n={required:!0,roles:r};t!==void 0?Reflect.defineMetadata(C,n,e.constructor,t):Reflect.defineMetadata(C,n,e)}}a(ne,"Authorize");var w=class r{static{a(this,"HttpResult")}status;body;headers;constructor(e,t,n={}){this.status=e,this.body=t!==void 0&&typeof t!="string"?JSON.stringify(t):t,this.headers=n}static ok(e){return new r(200,e)}static created(e,t){let n={};return t&&(n.Location=t),new r(201,e,n)}static noContent(){return new r(204)}static badRequest(e="Bad Request",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(400,n)}static unauthorized(e="Unauthorized"){let t=typeof e=="string"?{message:e}:e;return new r(401,t)}static forbidden(e="Forbidden"){let t=typeof e=="string"?{message:e}:e;return new r(403,t)}static notFound(e="Not Found"){let t=typeof e=="string"?{message:e}:e;return new r(404,t)}static conflict(e="Conflict"){let t=typeof e=="string"?{message:e}:e;return new r(409,t)}static unprocessableEntity(e="Unprocessable Entity",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(422,n)}static internalServerError(e="Internal Server Error"){let t=typeof e=="string"?{message:e}:e;return new r(500,t)}toResponse(){if(this.status===204||this.body===void 0)return new Response(null,{status:this.status,headers:this.headers});let e={"Content-Type":"application/json",...this.headers};return new Response(this.body,{status:this.status,headers:e})}};x();import"reflect-metadata";var v=Symbol("CONTROLLER_WATERMARK"),A=Symbol("ROUTE_METADATA"),k=Symbol("PARAM_METADATA");function se(r=""){return e=>{Reflect.defineMetadata(v,r,e),T()(e)}}a(se,"Controller");var b=a(r=>(e="")=>(t,n)=>{Reflect.hasMetadata(A,t.constructor)||Reflect.defineMetadata(A,[],t.constructor);let s=Reflect.getMetadata(A,t.constructor);s.push({method:r,path:e,methodName:n.toString()}),Reflect.defineMetadata(A,s,t.constructor)},"createRouteDecorator"),oe=b("GET"),ie=b("POST"),ae=b("PUT"),ce=b("DELETE"),le=b("PATCH"),y=(function(r){return r[r.Route=0]="Route",r[r.Query=1]="Query",r[r.Body=2]="Body",r[r.Context=3]="Context",r})({});function de(r,e){return(t,n,s)=>{L(t,n,{type:0,index:s,name:r,schema:e})}}a(de,"FromRoute");function ue(r,e){return(t,n,s)=>{L(t,n,{type:1,index:s,name:r,schema:e})}}a(ue,"FromQuery");function fe(r){return(e,t,n)=>{L(e,t,{type:2,index:n,schema:r})}}a(fe,"FromBody");function L(r,e,t){let n=`${k.toString()}_${e.toString()}`;Reflect.hasMetadata(n,r.constructor)||Reflect.defineMetadata(n,[],r.constructor);let s=Reflect.getMetadata(n,r.constructor);s.push(t),Reflect.defineMetadata(n,s,r.constructor)}a(L,"addParamMetadata");async function he(r,e,t){if(!t.required)return null;let n=r.request.headers.get("authorization");if(!n||!n.startsWith("Bearer "))return new Response(JSON.stringify({message:"Unauthorized"}),{status:401,headers:{"Content-Type":"application/json"}});let s=n.slice(7),i;try{let{JwtAuthService:c}=(U(),ee(F));i=await e.resolve(c)}catch{return new Response(JSON.stringify({message:"JWT service not configured"}),{status:500,headers:{"Content-Type":"application/json"}})}let o;try{o=await i.verify(s)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(t.roles&&t.roles.length>0){let c=o.roles??[];if(!t.roles.some(u=>c.includes(u)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return r.items.set("user",o),null}a(he,"checkAuth");var N=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let t of e){let n=Reflect.getMetadata(v,t);if(n===void 0)continue;let s=Reflect.getMetadata(A,t)||[];for(let i of s){let o=`/${n}/${i.path}`.replace(/\/+/g,"/");o!=="/"&&o.endsWith("/")&&(o=o.slice(0,-1));let c=new URLPattern({pathname:o});this.routes.push({method:i.method,pattern:c,controller:t,methodName:i.methodName})}}}map(e,t,n){let s=t.startsWith("/")?t:`/${t}`;s!=="/"&&s.endsWith("/")&&(s=s.slice(0,-1));let i=new URLPattern({pathname:s});this.routes.push({method:e.toUpperCase(),pattern:i,handler:n})}middleware(){return async(e,t)=>{for(let n of this.routes){if(n.method!==e.method)continue;let s=n.pattern.exec({pathname:e.path});if(s){let i=e.items.get("scope");if(!i)throw new Error("DI Scope not found in HttpContext");let o;if(n.handler)o=await n.handler(e);else if(n.controller&&n.methodName){let c=Reflect.getMetadata(C,n.controller,n.methodName),l=Reflect.getMetadata(C,n.controller),u=c??l;if(u){let d=await he(e,i,u);if(d)return d}let p=await i.resolve(n.controller),f=`${k.toString()}_${n.methodName}`,g=Reflect.getMetadata(f,n.controller)||[],D=p[n.methodName].length,W=new Array(D).fill(void 0),z=g.some(d=>d.type===y.Body),$;z&&($=await e.request.json().catch(()=>({})));let G=new URL(e.request.url);for(let d of g){let m;if(d.type===y.Route&&d.name?m=s.pathname.groups[d.name]:d.type===y.Query&&d.name?m=G.searchParams.get(d.name):d.type===y.Body?m=$:d.type===y.Context&&(m=e),d.schema){let H=d.schema.safeParse(m);if(!H.success)return w.unprocessableEntity("Validation failed",{errors:H.error.errors,target:y[d.type].toLowerCase(),name:d.name}).toResponse();m=H.data}W[d.index]=m}o=await p[n.methodName](...W)}return o instanceof Response?o:o instanceof w?o.toResponse():typeof o=="object"?Response.json(o):new Response(String(o))}}return t()}}};import{readdir as me}from"fs/promises";import{join as K}from"path";var I=class{static{a(this,"WebApplicationBuilder")}services;controllers=[];constructor(){this.services=new M}async addControllers(e="controllers"){let t=K(process.cwd(),e);try{let n=await me(t,{recursive:!0});for(let s of n)if(typeof s=="string"&&(s.endsWith(".ts")||s.endsWith(".js"))&&!s.endsWith(".d.ts")){let o=await import(K(t,s));for(let c in o){let l=o[c];typeof l=="function"&&Reflect.hasMetadata(v,l)&&this.controllers.push(l)}}this.controllers.length>0&&this.services.addControllers(this.controllers)}catch(n){console.warn(`Could not discover controllers at ${t}:`,n)}return this}build(){let e=this.services.buildServiceProvider();return new O(e,this.controllers)}};var O=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];router;constructor(e,t=[]){this.services=e,this.controllersToMap.push(...t),this.router=new N}static createBuilder(){return new I}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}mapGet(e,t){return this.router.map("GET",e,t),this}mapPost(e,t){return this.router.map("POST",e,t),this}mapPut(e,t){return this.router.map("PUT",e,t),this}mapDelete(e,t){return this.router.map("DELETE",e,t),this}mapPatch(e,t){return this.router.map("PATCH",e,t),this}async executePipeline(e){let t=-1,n=a(async i=>{if(i<=t)throw new Error("next() called multiple times");t=i;let o=this.middlewares[i];if(i===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(o)return o(e,n.bind(null,i+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let t=this;this.controllersToMap.length>0&&this.router.registerControllers(this.controllersToMap),this.use(this.router.middleware()),Bun.serve({port:e,async fetch(n){let s=t.services.createScope();try{let i=new S(n);return i.items.set("scope",s),await t.executePipeline(i)}finally{await s.dispose()}}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};x();U();function ye(r={}){return async(e,t)=>{let{request:n}=e,s=n.headers.get("origin"),i=await t();i||(i=new Response);let o=new Response(i.body,i);if(r.origin?typeof r.origin=="string"?o.headers.set("Access-Control-Allow-Origin",r.origin):Array.isArray(r.origin)&&s?r.origin.includes(s)&&o.headers.set("Access-Control-Allow-Origin",s):typeof r.origin=="function"&&s&&r.origin(s)&&o.headers.set("Access-Control-Allow-Origin",s):o.headers.set("Access-Control-Allow-Origin","*"),r.methods){let c=Array.isArray(r.methods)?r.methods.join(", "):r.methods;o.headers.set("Access-Control-Allow-Methods",c)}else o.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(r.allowedHeaders){let c=Array.isArray(r.allowedHeaders)?r.allowedHeaders.join(", "):r.allowedHeaders;o.headers.set("Access-Control-Allow-Headers",c)}else{let c=n.headers.get("access-control-request-headers");c&&o.headers.set("Access-Control-Allow-Headers",c)}if(r.exposedHeaders){let c=Array.isArray(r.exposedHeaders)?r.exposedHeaders.join(", "):r.exposedHeaders;o.headers.set("Access-Control-Expose-Headers",c)}return r.credentials&&o.headers.set("Access-Control-Allow-Credentials","true"),r.maxAge!==void 0&&o.headers.set("Access-Control-Max-Age",r.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:o.headers,status:204}):o}}a(ye,"cors");function ge(r){let e=a(s=>{let i=s.request?.headers?.get("x-forwarded-for");return i?i.split(",")[0].trim():"global"},"defaultKeyGenerator"),t=r.keyGenerator||e,n=new Map;return async(s,i)=>{let o=t(s),c=Date.now(),l=n.get(o);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+r.windowMs):l.count++:(l={count:1,resetTime:c+r.windowMs},n.set(o,l)),Math.random()<.05)for(let[f,g]of n.entries())Date.now()>g.resetTime&&n.delete(f);if(l.count>r.limit){let f=Math.ceil((l.resetTime-c)/1e3);return new Response("Too Many Requests",{status:429,headers:{"Retry-After":f.toString(),"X-RateLimit-Limit":r.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let u=await i();u||(u=new Response);let p=new Response(u.body,u);return p.headers.set("X-RateLimit-Limit",r.limit.toString()),p.headers.set("X-RateLimit-Remaining",Math.max(0,r.limit-l.count).toString()),p.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),p}}a(ge,"throttle");export{ne as Authorize,se as Controller,ce as Delete,fe as FromBody,ue as FromQuery,de as FromRoute,oe as Get,S as HttpContext,w as HttpResult,re as Inject,T as Injectable,R as JwtAuthService,le as Patch,ie as Post,ae as Put,M as ServiceCollection,E as ServiceProvider,O as WebApplication,I as WebApplicationBuilder,ye as cors,ge as throttle};
1
+ var H=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var ue=Object.getOwnPropertyNames;var fe=Object.prototype.hasOwnProperty;var a=(r,e)=>H(r,"name",{value:e,configurable:!0});var ee=(r,e)=>()=>(r&&(e=r(r=0)),e);var me=(r,e)=>{for(var t in e)H(r,t,{get:e[t],enumerable:!0})},he=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of ue(e))!fe.call(r,o)&&o!==t&&H(r,o,{get:()=>e[o],enumerable:!(n=de(e,o))||n.enumerable});return r};var ye=r=>he(H({},"__esModule",{value:!0}),r);import"reflect-metadata";function C(){return r=>{Reflect.defineMetadata(ge,!0,r)}}function Ae(r){return(e,t,n)=>{let o=Reflect.getMetadata(L,e)||[];o.push({index:n,token:r}),Reflect.defineMetadata(L,o,e)}}var ge,L,v=ee(()=>{"use strict";ge=Symbol("INJECTABLE_WATERMARK"),L=Symbol("INJECT_METADATA");a(C,"Injectable");a(Ae,"Inject")});var ie={};me(ie,{JwtAuthService:()=>I});function $e(r,e,t,n){var o=arguments.length,s=o<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,t):n,i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(r,e,t,n);else for(var c=r.length-1;c>=0;c--)(i=r[c])&&(s=(o<3?i(s):o>3?i(e,t,s):i(e,t))||s);return o>3&&s&&Object.defineProperty(e,t,s),s}function se(r,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(r,e)}function Z(r){return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function oe(r){let e=r+"==".slice(2-r.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var I,V=ee(()=>{"use strict";v();a($e,"_ts_decorate");a(se,"_ts_metadata");a(Z,"base64UrlEncode");a(oe,"base64UrlDecode");I=class{static{a(this,"JwtAuthService")}secret;expiresIn;constructor(e){this.secret=e.secret,this.expiresIn=e.expiresIn??3600}async getKey(){let e=new TextEncoder;return crypto.subtle.importKey("raw",e.encode(this.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async sign(e){let t={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),o={...e,iat:n,exp:n+this.expiresIn},s=Z(JSON.stringify(t)),i=Z(JSON.stringify(o)),c=`${s}.${i}`,l=await this.getKey(),p=new TextEncoder,f=await crypto.subtle.sign("HMAC",l,p.encode(c)),m=Z(String.fromCharCode(...new Uint8Array(f)));return`${c}.${m}`}async verify(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT format");let n=t[0],o=t[1],s=t[2],i=`${n}.${o}`,c=await this.getKey(),l=new TextEncoder,p=Uint8Array.from(oe(s),E=>E.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,p,l.encode(i)))throw new Error("Invalid JWT signature");let m=JSON.parse(oe(o)),w=Math.floor(Date.now()/1e3);if(m.exp!==void 0&&m.exp<w)throw new Error("JWT expired");return m}};I=$e([C(),se("design:type",Function),se("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],I)});v();import"reflect-metadata";var A=(function(r){return r[r.Singleton=0]="Singleton",r[r.Transient=1]="Transient",r[r.Scoped=2]="Scoped",r})({});var P=class r{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;trackedInstances=new Set;constructor(e,t){this.descriptors=e,this.parent=t}createScope(){return new r(this.descriptors,this.parent||this)}async dispose(){for(let e of this.trackedInstances)typeof e.onDestroy=="function"&&await e.onDestroy();this.trackedInstances.clear(),this.scopedInstances.clear(),this.singletonInstances.clear()}async resolve(e){let t=this.descriptors.find(n=>n.token===e);if(!t){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(t.lifetime){case A.Singleton:return this.resolveSingleton(t);case A.Scoped:return this.resolveScoped(t);case A.Transient:return this.resolveTransient(t)}}async resolveSingleton(e){let t=this.getRoot();if(t.singletonInstances.has(e.token))return t.singletonInstances.get(e.token);let n=await this.createInstance(t,e);return t.singletonInstances.set(e.token,n),n}async resolveScoped(e){if(this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let t=await this.createInstance(this,e);return this.scopedInstances.set(e.token,t),t}async resolveTransient(e){return this.createInstance(this,e)}async createInstance(e,t){let n;if(t.implementationInstance!==void 0)n=t.implementationInstance;else if(t.implementationFactory)n=await t.implementationFactory(this);else if(t.implementationType)n=await this.instantiateClass(t.implementationType);else throw new Error(`Invalid service descriptor for token: ${t.token.toString()}`);let o=Symbol.for("steampunk:initialized");return typeof n.onInit=="function"&&!n[o]&&(await n.onInit(),n[o]=!0),typeof n.onDestroy=="function"&&t.lifetime!==A.Transient&&e.trackedInstances.add(n),n}getRoot(){return this.parent?this.parent.getRoot():this}async instantiateClass(e){let t=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(L,e)||[],o=await Promise.all(t.map(async(s,i)=>{let c=n.find(p=>p.index===i),l=c?c.token:s;if(!l||l===Object||l===String||l===Number||l===Boolean)throw new Error(`Cannot resolve parameter at index ${i} for ${e.name}. Token could not be determined or is primitive. Cannot inject primitive types directly without @Inject().`);return this.resolve(l)}));return new e(...o)}};var O=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Singleton})}addSingletonInstance(e,t){return this.add({token:e,implementationInstance:t,lifetime:A.Singleton})}addTransient(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Transient})}addScoped(e,t){return this.add({token:e,implementationType:t||e,lifetime:A.Scoped})}buildServiceProvider(){return new P(this.descriptors)}addControllers(e){return e.forEach(t=>this.addTransient(t)),this}};var j=class{static{a(this,"HttpContext")}request;items=new Map;constructor(e){this.request=e}get method(){return this.request.method}get url(){return this.request.url}get path(){return new URL(this.request.url).pathname}};v();import"reflect-metadata";var M=Symbol("CONTROLLER_WATERMARK"),T=Symbol("ROUTE_METADATA"),_=Symbol("PARAM_METADATA");function Re(r=""){return e=>{Reflect.defineMetadata(M,r,e),C()(e)}}a(Re,"Controller");var D=a(r=>(e="")=>(t,n)=>{Reflect.hasMetadata(T,t.constructor)||Reflect.defineMetadata(T,[],t.constructor);let o=Reflect.getMetadata(T,t.constructor);o.push({method:r,path:e,methodName:n.toString()}),Reflect.defineMetadata(T,o,t.constructor)},"createRouteDecorator"),we=D("GET"),Te=D("POST"),xe=D("PUT"),Me=D("DELETE"),Ee=D("PATCH"),h=(function(r){return r[r.Route=0]="Route",r[r.Query=1]="Query",r[r.Body=2]="Body",r[r.Context=3]="Context",r})({});function Se(r,e){return(t,n,o)=>{B(t,n,{type:0,index:o,name:r,schema:e})}}a(Se,"FromRoute");function be(r,e){return(t,n,o)=>{B(t,n,{type:1,index:o,name:r,schema:e})}}a(be,"FromQuery");function Ie(r){return(e,t,n)=>{B(e,t,{type:2,index:n,schema:r})}}a(Ie,"FromBody");function Ce(){return(r,e,t)=>{B(r,e,{type:3,index:t})}}a(Ce,"FromContext");function B(r,e,t){let n=`${_.toString()}_${e.toString()}`;Reflect.hasMetadata(n,r.constructor)||Reflect.defineMetadata(n,[],r.constructor);let o=Reflect.getMetadata(n,r.constructor);o.push(t),Reflect.defineMetadata(n,o,r.constructor)}a(B,"addParamMetadata");import"reflect-metadata";var J="steampunk:api_property";function ve(r={}){return(e,t)=>{let n=Reflect.getOwnMetadata(J,e.constructor)||[],o=Reflect.getMetadata("design:type",e,t),s;o&&(o===String?s="string":o===Number?s="number":o===Boolean?s="boolean":o===Array?s="array":o===Object&&(s="object")),n.push({propertyKey:t.toString(),type:r.type??s,...r}),Reflect.defineMetadata(J,n,e.constructor)}}a(ve,"ApiProperty");function re(r){let e=Reflect.getOwnMetadata(J,r)||[],t=[],n={};for(let s of e){if(s.required!==!1&&t.push(s.propertyKey),s.schema){n[s.propertyKey]=s.schema;continue}let i={};s.type!==void 0&&(typeof s.type=="string"?i.type=s.type:typeof s.type=="object"&&"type"in s.type&&(i.type=s.type.type,s.type.items&&(i.items=te(s.type.items)))),s.type==="array"&&s.items&&(i.items=te(s.items)),s.description&&(i.description=s.description),s.example!==void 0&&(i.example=s.example),s.nullable&&(i.nullable=!0),s.enum&&(i.enum=s.enum),s.default!==void 0&&(i.default=s.default),n[s.propertyKey]=i}let o={type:"object"};return Object.keys(n).length>0&&(o.properties=n),t.length>0&&(o.required=t),o}a(re,"buildSchemaFromClass");function te(r){return typeof r=="string"?{type:r}:typeof r=="object"&&"$ref"in r||typeof r=="object"&&"type"in r?r:{}}a(te,"buildItemsSchema");function ne(r){return typeof r=="function"&&Reflect.hasOwnMetadata(J,r)}a(ne,"isDto");import"reflect-metadata";var z="steampunk:api_tags",Q="steampunk:api_operation",U="steampunk:api_response",X="steampunk:api_body",W="steampunk:api_query";function Pe(...r){return e=>{Reflect.defineMetadata(z,r,e)}}a(Pe,"ApiTags");function Oe(r){return(e,t)=>{Reflect.defineMetadata(Q,r,e.constructor,t)}}a(Oe,"ApiOperation");function je(r){return(e,t)=>{let n=Reflect.getMetadata(U,e.constructor,t)||[];n.push(r),Reflect.defineMetadata(U,n,e.constructor,t)}}a(je,"ApiResponse");function _e(r){return(e,t)=>{Reflect.defineMetadata(X,r,e.constructor,t)}}a(_e,"ApiBody");function De(r){return(e,t)=>{let n=Reflect.getMetadata(W,e.constructor,t)||[];n.push(r),Reflect.defineMetadata(W,n,e.constructor,t)}}a(De,"ApiQuery");var F=class{static{a(this,"OpenApiGenerator")}controllers;options;schemaRegistry=new Map;constructor(e,t){this.controllers=e,this.options=t}refFromClass(e){let t=e.name;return this.schemaRegistry.has(t)||this.schemaRegistry.set(t,re(e)),{$ref:`#/components/schemas/${t}`}}resolveSchema(e,t){if(t)return t;if(e&&ne(e))return this.refFromClass(e);if(e)return{type:"object",description:e.name}}generate(){let e={},t=[],n=new Set;for(let s of this.controllers){let i=Reflect.getMetadata(M,s);if(i===void 0)continue;let c=Reflect.getMetadata(z,s)||[];for(let p of c)n.has(p)||(n.add(p),t.push({name:p}));let l=Reflect.getMetadata(T,s)||[];for(let p of l){let f=`/${i}/${p.path}`.replace(/\/+/g,"/");f!=="/"&&f.endsWith("/")&&(f=f.slice(0,-1));let m=f.replace(/:([^/]+)/g,"{$1}");e[m]||(e[m]={});let w=`${_.toString()}_${p.methodName}`,E=Reflect.getMetadata(w,s)||[],y=Reflect.getMetadata(Q,s,p.methodName),k=Reflect.getMetadata(U,s,p.methodName)||[],g=Reflect.getMetadata(X,s,p.methodName),G=Reflect.getMetadata(W,s,p.methodName)||[],u=[];for(let d of E)d.type===h.Route&&d.name?u.push({name:d.name,in:"path",required:!0,schema:d.schema??{type:"string"}}):d.type===h.Query&&d.name&&u.push({name:d.name,in:"query",required:!1,schema:d.schema??{type:"string"}});for(let d of G)u.find(pe=>pe.name===d.name)||u.push({name:d.name,in:"query",required:d.required??!1,description:d.description,schema:d.schema??{type:"string"},...d.example!==void 0?{example:d.example}:{}});let R=E.some(d=>d.type===h.Body),x;if(R||g){let d=this.resolveSchema(g?.type,g?.schema),S=g?.example;x={...g?.description?{description:g.description}:{},required:g?.required??!0,content:{"application/json":{...d?{schema:d}:{},...S!==void 0?{example:S}:{}}}}}let ce=k.length>0?Object.fromEntries(k.map(d=>{let S=this.resolveSchema(d.type,d.schema);return[String(d.status),{description:d.description,...S?{content:{"application/json":{schema:S,...d.example!==void 0?{example:d.example}:{}}}}:{}}]})):{200:{description:"Success"}},le={...c.length>0?{tags:c}:{},...y?.summary?{summary:y.summary}:{},...y?.description?{description:y.description}:{},...y?.operationId?{operationId:y.operationId}:{operationId:`${s.name}_${p.methodName}`},...y?.deprecated?{deprecated:!0}:{},...u.length>0?{parameters:u}:{},...x?{requestBody:x}:{},responses:ce};e[m][p.method.toLowerCase()]=le}}let o=this.schemaRegistry.size>0?{schemas:Object.fromEntries(this.schemaRegistry)}:void 0;return{openapi:"3.1.0",info:this.options.info,...this.options.servers?{servers:this.options.servers}:{},...t.length>0?{tags:t}:{},paths:e,...o?{components:o}:{}}}};var Ne=a((r,e)=>`<!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>${e} - API Docs</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ </head>
8
+ <body>
9
+ <script
10
+ id="api-reference"
11
+ data-url="${r}"
12
+ data-configuration='{"theme":"purple"}'
13
+ ></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
15
+ </body>
16
+ </html>`,"SCALAR_HTML");function Y(r,e,t={}){let n=t.docsPath??"/docs",o=t.specPath??"/openapi.json",s=null;return async(i,c)=>{let{path:l}=i;if(l===o){if(!s){let p=new F(r,e);s=JSON.stringify(p.generate(),null,2)}return new Response(s,{headers:{"Content-Type":"application/json"}})}return l===n||l===`${n}/`?new Response(Ne(o,e.info.title),{headers:{"Content-Type":"text/html; charset=utf-8"}}):c()}}a(Y,"scalar");import"reflect-metadata";var N=Symbol("AUTHORIZE_METADATA");function qe(r){return(e,t)=>{let n={required:!0,roles:r};t!==void 0?Reflect.defineMetadata(N,n,e.constructor,t):Reflect.defineMetadata(N,n,e)}}a(qe,"Authorize");var b=class r{static{a(this,"HttpResult")}status;body;headers;constructor(e,t,n={}){this.status=e,this.body=t!==void 0&&typeof t!="string"?JSON.stringify(t):t,this.headers=n}static ok(e){return new r(200,e)}static created(e,t){let n={};return t&&(n.Location=t),new r(201,e,n)}static noContent(){return new r(204)}static badRequest(e="Bad Request",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(400,n)}static unauthorized(e="Unauthorized"){let t=typeof e=="string"?{message:e}:e;return new r(401,t)}static forbidden(e="Forbidden"){let t=typeof e=="string"?{message:e}:e;return new r(403,t)}static notFound(e="Not Found"){let t=typeof e=="string"?{message:e}:e;return new r(404,t)}static conflict(e="Conflict"){let t=typeof e=="string"?{message:e}:e;return new r(409,t)}static unprocessableEntity(e="Unprocessable Entity",t){let n=typeof e=="string"?{message:e,errors:t}:e;return new r(422,n)}static internalServerError(e="Internal Server Error"){let t=typeof e=="string"?{message:e}:e;return new r(500,t)}toResponse(){if(this.status===204||this.body===void 0)return new Response(null,{status:this.status,headers:this.headers});let e={"Content-Type":"application/json",...this.headers};return new Response(this.body,{status:this.status,headers:e})}};async function ke(r,e,t){if(!t.required)return null;let n=r.request.headers.get("authorization");if(!n||!n.startsWith("Bearer "))return new Response(JSON.stringify({message:"Unauthorized"}),{status:401,headers:{"Content-Type":"application/json"}});let o=n.slice(7),s;try{let{JwtAuthService:c}=(V(),ye(ie));s=await e.resolve(c)}catch{return new Response(JSON.stringify({message:"JWT service not configured"}),{status:500,headers:{"Content-Type":"application/json"}})}let i;try{i=await s.verify(o)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(t.roles&&t.roles.length>0){let c=i.roles??[];if(!t.roles.some(p=>c.includes(p)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return r.items.set("user",i),null}a(ke,"checkAuth");var K=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let t of e){let n=Reflect.getMetadata(M,t);if(n===void 0)continue;let o=Reflect.getMetadata(T,t)||[];for(let s of o){let i=`/${n}/${s.path}`.replace(/\/+/g,"/");i!=="/"&&i.endsWith("/")&&(i=i.slice(0,-1));let c=new URLPattern({pathname:i});this.routes.push({method:s.method,pattern:c,controller:t,methodName:s.methodName})}}}map(e,t,n){let o=t.startsWith("/")?t:`/${t}`;o!=="/"&&o.endsWith("/")&&(o=o.slice(0,-1));let s=new URLPattern({pathname:o});this.routes.push({method:e.toUpperCase(),pattern:s,handler:n})}middleware(){return async(e,t)=>{for(let n of this.routes){if(n.method!==e.method)continue;let o=n.pattern.exec({pathname:e.path});if(o){let s=e.items.get("scope");if(!s)throw new Error("DI Scope not found in HttpContext");let i;if(n.handler)i=await n.handler(e);else if(n.controller&&n.methodName){let c=Reflect.getMetadata(N,n.controller,n.methodName),l=Reflect.getMetadata(N,n.controller),p=c??l;if(p){let u=await ke(e,s,p);if(u)return u}let f=await s.resolve(n.controller),m=`${_.toString()}_${n.methodName}`,w=Reflect.getMetadata(m,n.controller)||[],E=f[n.methodName].length,y=new Array(E).fill(void 0),k=w.some(u=>u.type===h.Body),g;k&&(g=await e.request.json().catch(()=>({})));let G=new URL(e.request.url);for(let u of w){let R;if(u.type===h.Route&&u.name?R=o.pathname.groups[u.name]:u.type===h.Query&&u.name?R=G.searchParams.get(u.name):u.type===h.Body?R=g:u.type===h.Context&&(R=e),u.schema){let x=u.schema.safeParse(R);if(!x.success)return b.unprocessableEntity("Validation failed",{errors:x.error.errors,target:h[u.type].toLowerCase(),name:u.name}).toResponse();R=x.data}y[u.index]=R}i=await f[n.methodName](...y)}return i instanceof Response?i:i instanceof b?i.toResponse():typeof i=="object"?Response.json(i):new Response(String(i))}}return t()}}};import{readdir as He}from"fs/promises";import{join as ae}from"path";var q=class{static{a(this,"WebApplicationBuilder")}services;controllers=[];constructor(){this.services=new O}async addControllers(e="controllers"){let t=ae(process.cwd(),e);try{let n=await He(t,{recursive:!0});for(let o of n)if(typeof o=="string"&&(o.endsWith(".ts")||o.endsWith(".js"))&&!o.endsWith(".d.ts")){let i=await import(ae(t,o));for(let c in i){let l=i[c];typeof l=="function"&&Reflect.hasMetadata(M,l)&&this.controllers.push(l)}}this.controllers.length>0&&this.services.addControllers(this.controllers)}catch(n){console.warn(`Could not discover controllers at ${t}:`,n)}return this}build(){let e=this.services.buildServiceProvider();return new $(e,this.controllers)}};var $=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];router;constructor(e,t=[]){this.services=e,this.controllersToMap.push(...t),this.router=new K}static createBuilder(){return new q}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}useOpenApi(e,t){let n=Y(this.controllersToMap,e,t);return this.middlewares.push(n),this}mapGet(e,t){return this.router.map("GET",e,t),this}mapPost(e,t){return this.router.map("POST",e,t),this}mapPut(e,t){return this.router.map("PUT",e,t),this}mapDelete(e,t){return this.router.map("DELETE",e,t),this}mapPatch(e,t){return this.router.map("PATCH",e,t),this}async executePipeline(e){let t=-1,n=a(async s=>{if(s<=t)throw new Error("next() called multiple times");t=s;let i=this.middlewares[s];if(s===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(i)return i(e,n.bind(null,s+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let t=this;this.controllersToMap.length>0&&this.router.registerControllers(this.controllersToMap),this.use(this.router.middleware()),Bun.serve({port:e,async fetch(n){let o=t.services.createScope();try{let s=new j(n);return s.items.set("scope",o),await t.executePipeline(s)}finally{await o.dispose()}}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};v();V();function Le(r={}){return async(e,t)=>{let{request:n}=e,o=n.headers.get("origin"),s=await t();s||(s=new Response);let i=new Response(s.body,s);if(r.origin?typeof r.origin=="string"?i.headers.set("Access-Control-Allow-Origin",r.origin):Array.isArray(r.origin)&&o?r.origin.includes(o)&&i.headers.set("Access-Control-Allow-Origin",o):typeof r.origin=="function"&&o&&r.origin(o)&&i.headers.set("Access-Control-Allow-Origin",o):i.headers.set("Access-Control-Allow-Origin","*"),r.methods){let c=Array.isArray(r.methods)?r.methods.join(", "):r.methods;i.headers.set("Access-Control-Allow-Methods",c)}else i.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(r.allowedHeaders){let c=Array.isArray(r.allowedHeaders)?r.allowedHeaders.join(", "):r.allowedHeaders;i.headers.set("Access-Control-Allow-Headers",c)}else{let c=n.headers.get("access-control-request-headers");c&&i.headers.set("Access-Control-Allow-Headers",c)}if(r.exposedHeaders){let c=Array.isArray(r.exposedHeaders)?r.exposedHeaders.join(", "):r.exposedHeaders;i.headers.set("Access-Control-Expose-Headers",c)}return r.credentials&&i.headers.set("Access-Control-Allow-Credentials","true"),r.maxAge!==void 0&&i.headers.set("Access-Control-Max-Age",r.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:i.headers,status:204}):i}}a(Le,"cors");function Be(r){let e=a(o=>{let s=o.request?.headers?.get("x-forwarded-for");return s?s.split(",")[0].trim():"global"},"defaultKeyGenerator"),t=r.keyGenerator||e,n=new Map;return async(o,s)=>{let i=t(o),c=Date.now(),l=n.get(i);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+r.windowMs):l.count++:(l={count:1,resetTime:c+r.windowMs},n.set(i,l)),Math.random()<.05)for(let[m,w]of n.entries())Date.now()>w.resetTime&&n.delete(m);if(l.count>r.limit){let m=Math.ceil((l.resetTime-c)/1e3);return new Response("Too Many Requests",{status:429,headers:{"Retry-After":m.toString(),"X-RateLimit-Limit":r.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let p=await s();p||(p=new Response);let f=new Response(p.body,p);return f.headers.set("X-RateLimit-Limit",r.limit.toString()),f.headers.set("X-RateLimit-Remaining",Math.max(0,r.limit-l.count).toString()),f.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),f}}a(Be,"throttle");export{_e as ApiBody,Oe as ApiOperation,ve as ApiProperty,De as ApiQuery,je as ApiResponse,Pe as ApiTags,qe as Authorize,Re as Controller,Me as Delete,Ie as FromBody,Ce as FromContext,be as FromQuery,Se as FromRoute,we as Get,j as HttpContext,b as HttpResult,Ae as Inject,C as Injectable,I as JwtAuthService,Ee as Patch,Te as Post,xe as Put,O as ServiceCollection,P as ServiceProvider,$ as WebApplication,q as WebApplicationBuilder,Le as cors,Y as scalar,Be as throttle};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielgl/steampunk",
3
- "version": "0.0.5",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "A high-performance, DX-focused web framework for Bun, inspired by ASP.NET Core.",
6
6
  "main": "./dist/main.cjs",