@danielgl/steampunk 0.0.1
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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/main.cjs +1 -0
- package/dist/main.d.cts +162 -0
- package/dist/main.d.ts +162 -0
- package/dist/main.js +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011-2025 Daniel Lopes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>⚙️ Steampunk</h1>
|
|
3
|
+
<p>A high-performance, DX-focused web framework for <a href="https://bun.sh">Bun</a>, highly inspired by ASP.NET Core.</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Dependency Injection**: Powerful `ServiceCollection` and `ServiceProvider` out of the box (`Singleton`, `Scoped`, `Transient`).
|
|
9
|
+
- **Builder Pattern**: Familiar `WebApplication.createBuilder()` approach.
|
|
10
|
+
- **Decorators**: Beautiful Controller and Routing decorators (`@Controller()`, `@Get()`, `@Post()`, `@FromRoute()`, etc).
|
|
11
|
+
- **Extremely Fast**: Built natively on top of `Bun.serve()`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @danielgl/steampunk reflect-metadata
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Ensure your `tsconfig.json` has `experimentalDecorators` and `emitDecoratorMetadata` enabled:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"compilerOptions": {
|
|
24
|
+
"experimentalDecorators": true,
|
|
25
|
+
"emitDecoratorMetadata": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Create a Service
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Injectable } from "@danielgl/steampunk";
|
|
36
|
+
|
|
37
|
+
export interface User {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Injectable()
|
|
43
|
+
export class UsersService {
|
|
44
|
+
private users: User[] = [{ id: "1", name: "Alice" }];
|
|
45
|
+
|
|
46
|
+
public getAll() {
|
|
47
|
+
return this.users;
|
|
48
|
+
}
|
|
49
|
+
public getById(id: string) {
|
|
50
|
+
return this.users.find((u) => u.id === id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Create a Controller
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { Controller, Get, FromRoute, HttpResult } from "@danielgl/steampunk";
|
|
59
|
+
import { UsersService } from "./users-service";
|
|
60
|
+
import type { User } from "./users-service";
|
|
61
|
+
|
|
62
|
+
@Controller("users")
|
|
63
|
+
export class UsersController {
|
|
64
|
+
// Services are automatically injected!
|
|
65
|
+
constructor(private usersService: UsersService) {}
|
|
66
|
+
|
|
67
|
+
@Get()
|
|
68
|
+
public getAllUsers(): HttpResult {
|
|
69
|
+
const users = this.usersService.getAll();
|
|
70
|
+
return HttpResult.ok(users);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Get(":id")
|
|
74
|
+
public getUserById(@FromRoute("id") id: string): HttpResult {
|
|
75
|
+
const user = this.usersService.getById(id);
|
|
76
|
+
if (!user) {
|
|
77
|
+
return HttpResult.notFound("User not found");
|
|
78
|
+
}
|
|
79
|
+
return HttpResult.ok(user);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Bootstrap your App
|
|
85
|
+
|
|
86
|
+
````typescript
|
|
87
|
+
import { WebApplication } from "@danielgl/steampunk";
|
|
88
|
+
import { UsersService } from "./users-service";
|
|
89
|
+
import { UsersController } from "./users-controller";
|
|
90
|
+
|
|
91
|
+
const builder = WebApplication.createBuilder();
|
|
92
|
+
|
|
93
|
+
// 1. Register DI Services
|
|
94
|
+
builder.services.addSingleton(UsersService);
|
|
95
|
+
|
|
96
|
+
const app = builder.build();
|
|
97
|
+
|
|
98
|
+
// 2. Add Middlewares
|
|
99
|
+
app.use(async (context, next) => {
|
|
100
|
+
console.log(`[${context.method}] ${context.path}`);
|
|
101
|
+
return next();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 3. Map Controllers
|
|
105
|
+
app.mapControllers([UsersController]);
|
|
106
|
+
|
|
107
|
+
### 4. Run!
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bun run main.ts
|
|
111
|
+
````
|
|
112
|
+
|
|
113
|
+
## Response Helpers (`HttpResult`)
|
|
114
|
+
|
|
115
|
+
Steampunk provides a powerful `HttpResult` class to simplify returning HTTP responses.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Return 200 OK with an object (auto-serialized to JSON)
|
|
119
|
+
return HttpResult.ok({ id: 1, name: "John" });
|
|
120
|
+
|
|
121
|
+
// Return 201 Created with a Location header
|
|
122
|
+
return HttpResult.created(newUser, `/users/${newUser.id}`);
|
|
123
|
+
|
|
124
|
+
// Return 400 Bad Request with a message
|
|
125
|
+
return HttpResult.badRequest("Invalid input");
|
|
126
|
+
|
|
127
|
+
// Return 404 Not Found
|
|
128
|
+
return HttpResult.notFound("User not found");
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
All methods support flexible payloads: `string`, `object`, or `array`.
|
|
132
|
+
|
|
133
|
+
## Authentication & Authorization
|
|
134
|
+
|
|
135
|
+
Protect your routes easily using the `@Authorize()` decorator.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
@Authorize() // Multi-level: Class or Method
|
|
139
|
+
@Controller("admin")
|
|
140
|
+
export class AdminController {
|
|
141
|
+
@Get("secret")
|
|
142
|
+
public getSecret() {
|
|
143
|
+
return HttpResult.ok("shhh!");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
Available scripts for contributing or local development:
|
|
151
|
+
|
|
152
|
+
- `bun run lint`: Run ESLint checks.
|
|
153
|
+
- `bun run format`: Format code with Prettier.
|
|
154
|
+
- `bun run test`: Run unit tests with Bun.
|
|
155
|
+
|
|
156
|
+
Pre-commit hooks are enforced via Husky to ensure code quality!
|
|
157
|
+
|
|
158
|
+
Made with ❤️ focusing on Developer Experience.
|
package/dist/main.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var N=Object.defineProperty;var fe=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var a=(t,e)=>N(t,"name",{value:e,configurable:!0});var w=(t,e)=>()=>(t&&(e=t(t=0)),e);var D=(t,e)=>{for(var r in e)N(t,r,{get:e[r],enumerable:!0})},me=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of pe(e))!he.call(t,s)&&s!==r&&N(t,s,{get:()=>e[s],enumerable:!(n=fe(e,s))||n.enumerable});return t};var J=t=>me(N({},"__esModule",{value:!0}),t);function A(){return t=>{Reflect.defineMetadata(ge,!0,t)}}function K(t){return(e,r,n)=>{let s=Reflect.getMetadata(H,e)||[];s.push({index:n,token:t}),Reflect.defineMetadata(H,s,e)}}var Te,ge,H,I=w(()=>{"use strict";Te=require("reflect-metadata"),ge=Symbol("INJECTABLE_WATERMARK"),H=Symbol("INJECT_METADATA");a(A,"Injectable");a(K,"Inject")});function z(t){return(e,r)=>{let n={required:!0,roles:t};r!==void 0?Reflect.defineMetadata(O,n,e.constructor,r):Reflect.defineMetadata(O,n,e)}}var ke,O,P=w(()=>{"use strict";ke=require("reflect-metadata"),O=Symbol("AUTHORIZE_METADATA");a(z,"Authorize")});var S,q=w(()=>{"use strict";S=class t{static{a(this,"HttpResult")}status;body;headers;constructor(e,r,n={}){this.status=e,this.body=r!==void 0&&typeof r!="string"?JSON.stringify(r):r,this.headers=n}static ok(e){return new t(200,e)}static created(e,r){let n={};return r&&(n.Location=r),new t(201,e,n)}static noContent(){return new t(204)}static badRequest(e="Bad Request",r){let n=typeof e=="string"?{message:e,errors:r}:e;return new t(400,n)}static unauthorized(e="Unauthorized"){let r=typeof e=="string"?{message:e}:e;return new t(401,r)}static forbidden(e="Forbidden"){let r=typeof e=="string"?{message:e}:e;return new t(403,r)}static notFound(e="Not Found"){let r=typeof e=="string"?{message:e}:e;return new t(404,r)}static conflict(e="Conflict"){let r=typeof e=="string"?{message:e}:e;return new t(409,r)}static unprocessableEntity(e="Unprocessable Entity",r){let n=typeof e=="string"?{message:e,errors:r}:e;return new t(422,n)}static internalServerError(e="Internal Server Error"){let r=typeof e=="string"?{message:e}:e;return new t(500,r)}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})}}});function G(t=""){return e=>{Reflect.defineMetadata(L,t,e),A()(e)}}function k(t,e,r){let n=`${U.toString()}_${e.toString()}`;Reflect.hasMetadata(n,t.constructor)||Reflect.defineMetadata(n,[],t.constructor);let s=Reflect.getMetadata(n,t.constructor);s.push(r),Reflect.defineMetadata(n,s,t.constructor)}var _e,L,v,U,j,X,Q,Z,V,Y,C,ee,te,re,B=w(()=>{"use strict";_e=require("reflect-metadata");I();L=Symbol("CONTROLLER_WATERMARK"),v=Symbol("ROUTE_METADATA"),U=Symbol("PARAM_METADATA");a(G,"Controller");j=a(t=>(e="")=>(r,n)=>{Reflect.hasMetadata(v,r.constructor)||Reflect.defineMetadata(v,[],r.constructor);let s=Reflect.getMetadata(v,r.constructor);s.push({method:t,path:e,methodName:n.toString()}),Reflect.defineMetadata(v,s,r.constructor)},"createRouteDecorator"),X=j("GET"),Q=j("POST"),Z=j("PUT"),V=j("DELETE"),Y=j("PATCH"),C=(function(t){return t[t.Route=0]="Route",t[t.Query=1]="Query",t[t.Body=2]="Body",t[t.Context=3]="Context",t})({}),ee=a(t=>(e,r,n)=>{k(e,r,{type:0,index:n,name:t})},"FromRoute"),te=a(t=>(e,r,n)=>{k(e,r,{type:1,index:n,name:t})},"FromQuery"),re=a(()=>(t,e,r)=>{k(t,e,{type:2,index:r})},"FromBody");a(k,"addParamMetadata")});var oe={};D(oe,{JwtAuthService:()=>y});function ye(t,e,r,n){var s=arguments.length,o=s<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,r):n,i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(t,e,r,n);else for(var c=t.length-1;c>=0;c--)(i=t[c])&&(o=(s<3?i(o):s>3?i(e,r,o):i(e,r))||o);return s>3&&o&&Object.defineProperty(e,r,o),o}function ne(t,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(t,e)}function $(t){return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function se(t){let e=t+"==".slice(2-t.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var y,F=w(()=>{"use strict";I();a(ye,"_ts_decorate");a(ne,"_ts_metadata");a($,"base64UrlEncode");a(se,"base64UrlDecode");y=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 r={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),s={...e,iat:n,exp:n+this.expiresIn},o=$(JSON.stringify(r)),i=$(JSON.stringify(s)),c=`${o}.${i}`,l=await this.getKey(),u=new TextEncoder,p=await crypto.subtle.sign("HMAC",l,u.encode(c)),f=$(String.fromCharCode(...new Uint8Array(p)));return`${c}.${f}`}async verify(e){let r=e.split(".");if(r.length!==3)throw new Error("Invalid JWT format");let n=r[0],s=r[1],o=r[2],i=`${n}.${s}`,c=await this.getKey(),l=new TextEncoder,u=Uint8Array.from(se(o),m=>m.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,u,l.encode(i)))throw new Error("Invalid JWT signature");let f=JSON.parse(se(s)),b=Math.floor(Date.now()/1e3);if(f.exp!==void 0&&f.exp<b)throw new Error("JWT expired");return f}};y=ye([A(),ne("design:type",Function),ne("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],y)});var ie={};D(ie,{Router:()=>W});async function we(t,e,r){if(!r.required)return null;let n=t.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),o;try{let{JwtAuthService:c}=(F(),J(oe));o=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 o.verify(s)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(r.roles&&r.roles.length>0){let c=i.roles??[];if(!r.roles.some(u=>c.includes(u)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return t.items.set("user",i),null}var W,ae=w(()=>{"use strict";P();q();B();a(we,"checkAuth");W=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let r of e){let n=Reflect.getMetadata(L,r);if(n===void 0)continue;let s=Reflect.getMetadata(v,r)||[];for(let o of s){let i=`/${n}/${o.path}`.replace(/\/+/g,"/");i!=="/"&&i.endsWith("/")&&(i=i.slice(0,-1));let c=new URLPattern({pathname:i});this.routes.push({method:o.method,pattern:c,controller:r,methodName:o.methodName})}}}middleware(){return async(e,r)=>{for(let n of this.routes){if(n.method!==e.method)continue;let s=n.pattern.exec({pathname:e.path});if(s){let o=e.items.get("scope");if(!o)throw new Error("DI Scope not found in HttpContext");let i=Reflect.getMetadata(O,n.controller,n.methodName),c=Reflect.getMetadata(O,n.controller),l=i??c;if(l){let d=await we(e,o,l);if(d)return d}let u=o.resolve(n.controller),p=`${U.toString()}_${n.methodName}`,f=Reflect.getMetadata(p,n.controller)||[],b=u[n.methodName].length,m=new Array(b).fill(void 0),de=f.some(d=>d.type===C.Body),_;de&&(_=await e.request.json().catch(()=>({})));let ue=new URL(e.request.url);for(let d of f)d.type===C.Route&&d.name?m[d.index]=s.pathname.groups[d.name]:d.type===C.Query&&d.name?m[d.index]=ue.searchParams.get(d.name):d.type===C.Body?m[d.index]=_:d.type===C.Context&&(m[d.index]=e);let g=await u[n.methodName](...m);return g instanceof Response?g:g instanceof S?g.toResponse():typeof g=="object"?Response.json(g):new Response(String(g))}}return r()}}}});var Ae={};D(Ae,{Authorize:()=>z,Controller:()=>G,Delete:()=>V,FromBody:()=>re,FromQuery:()=>te,FromRoute:()=>ee,Get:()=>X,HttpContext:()=>x,HttpResult:()=>S,Inject:()=>K,Injectable:()=>A,JwtAuthService:()=>y,Patch:()=>Y,Post:()=>Q,Put:()=>Z,ServiceCollection:()=>T,ServiceProvider:()=>R,WebApplication:()=>M,WebApplicationBuilder:()=>E,cors:()=>ce,throttle:()=>le});module.exports=J(Ae);var Se=require("reflect-metadata");I();var h=(function(t){return t[t.Singleton=0]="Singleton",t[t.Transient=1]="Transient",t[t.Scoped=2]="Scoped",t})({});var R=class t{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;constructor(e,r){this.descriptors=e,this.parent=r}createScope(){return new t(this.descriptors,this.parent||this)}resolve(e){let r=this.descriptors.find(n=>n.token===e);if(!r){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(r.lifetime){case h.Singleton:return this.resolveSingleton(r);case h.Scoped:return this.resolveScoped(r);case h.Transient:return this.resolveTransient(r)}}resolveSingleton(e){let r=this.parent||this;if(r.singletonInstances.has(e.token))return r.singletonInstances.get(e.token);let n=this.createInstance(e);return r.singletonInstances.set(e.token,n),n}resolveScoped(e){if(this.parent,this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let r=this.createInstance(e);return this.scopedInstances.set(e.token,r),r}resolveTransient(e){return this.createInstance(e)}createInstance(e){if(e.implementationInstance!==void 0)return e.implementationInstance;if(e.implementationFactory)return e.implementationFactory(this);if(e.implementationType)return this.instantiateClass(e.implementationType);throw new Error(`Invalid service descriptor for token: ${e.token.toString()}`)}instantiateClass(e){let r=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(H,e)||[],s=r.map((o,i)=>{let c=n.find(u=>u.index===i),l=c?c.token:o;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(...s)}};var T=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Singleton})}addSingletonInstance(e,r){return this.add({token:e,implementationInstance:r,lifetime:h.Singleton})}addTransient(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Transient})}addScoped(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Scoped})}buildServiceProvider(){return new R(this.descriptors)}addControllers(e){return e.forEach(r=>this.addTransient(r)),this}};var x=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 E=class{static{a(this,"WebApplicationBuilder")}services;constructor(){this.services=new T}build(){let e=this.services.buildServiceProvider();return new M(e)}};var M=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];constructor(e){this.services=e}static createBuilder(){return new E}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}async executePipeline(e){let r=-1,n=a(async o=>{if(o<=r)throw new Error("next() called multiple times");r=o;let i=this.middlewares[o];if(o===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(i)return i(e,n.bind(null,o+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let r=this;if(this.controllersToMap.length>0){let{Router:n}=(ae(),J(ie)),s=new n;s.registerControllers(this.controllersToMap),this.use(s.middleware())}Bun.serve({port:e,async fetch(n){let s=r.services.createScope(),o=new x(n);return o.items.set("scope",s),r.executePipeline(o)}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};q();I();B();P();F();function ce(t={}){return async(e,r)=>{let{request:n}=e,s=n.headers.get("origin"),o=await r();o||(o=new Response);let i=new Response(o.body,o);if(t.origin?typeof t.origin=="string"?i.headers.set("Access-Control-Allow-Origin",t.origin):Array.isArray(t.origin)&&s?t.origin.includes(s)&&i.headers.set("Access-Control-Allow-Origin",s):typeof t.origin=="function"&&s&&t.origin(s)&&i.headers.set("Access-Control-Allow-Origin",s):i.headers.set("Access-Control-Allow-Origin","*"),t.methods){let c=Array.isArray(t.methods)?t.methods.join(", "):t.methods;i.headers.set("Access-Control-Allow-Methods",c)}else i.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(t.allowedHeaders){let c=Array.isArray(t.allowedHeaders)?t.allowedHeaders.join(", "):t.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(t.exposedHeaders){let c=Array.isArray(t.exposedHeaders)?t.exposedHeaders.join(", "):t.exposedHeaders;i.headers.set("Access-Control-Expose-Headers",c)}return t.credentials&&i.headers.set("Access-Control-Allow-Credentials","true"),t.maxAge!==void 0&&i.headers.set("Access-Control-Max-Age",t.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:i.headers,status:204}):i}}a(ce,"cors");function le(t){let e=a(s=>{let o=s.request?.headers?.get("x-forwarded-for");return o?o.split(",")[0].trim():"global"},"defaultKeyGenerator"),r=t.keyGenerator||e,n=new Map;return async(s,o)=>{let i=r(s),c=Date.now(),l=n.get(i);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+t.windowMs):l.count++:(l={count:1,resetTime:c+t.windowMs},n.set(i,l)),Math.random()<.05)for(let[f,b]of n.entries())Date.now()>b.resetTime&&n.delete(f);if(l.count>t.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":t.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let u=await o();u||(u=new Response);let p=new Response(u.body,u);return p.headers.set("X-RateLimit-Limit",t.limit.toString()),p.headers.set("X-RateLimit-Remaining",Math.max(0,t.limit-l.count).toString()),p.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),p}}a(le,"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});
|
package/dist/main.d.cts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
declare enum ServiceLifetime {
|
|
2
|
+
Singleton = 0,
|
|
3
|
+
Transient = 1,
|
|
4
|
+
Scoped = 2
|
|
5
|
+
}
|
|
6
|
+
type Type<T = any> = new (...args: Array<any>) => T;
|
|
7
|
+
type InjectionToken<T = any> = Type<T> | string | symbol;
|
|
8
|
+
interface ServiceDescriptor<T = any> {
|
|
9
|
+
token: InjectionToken<T>;
|
|
10
|
+
lifetime: ServiceLifetime;
|
|
11
|
+
implementationType?: Type<T>;
|
|
12
|
+
implementationInstance?: T;
|
|
13
|
+
implementationFactory?: (provider: any) => T;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class ServiceProvider {
|
|
17
|
+
private descriptors;
|
|
18
|
+
private parent?;
|
|
19
|
+
private singletonInstances;
|
|
20
|
+
private scopedInstances;
|
|
21
|
+
constructor(descriptors: Array<ServiceDescriptor>, parent?: ServiceProvider | undefined);
|
|
22
|
+
createScope(): ServiceProvider;
|
|
23
|
+
resolve<T>(token: InjectionToken<T>): T;
|
|
24
|
+
private resolveSingleton;
|
|
25
|
+
private resolveScoped;
|
|
26
|
+
private resolveTransient;
|
|
27
|
+
private createInstance;
|
|
28
|
+
private instantiateClass;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare class ServiceCollection {
|
|
32
|
+
private descriptors;
|
|
33
|
+
add<T>(descriptor: ServiceDescriptor<T>): this;
|
|
34
|
+
addSingleton<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
35
|
+
addSingleton<T>(type: Type<T>): this;
|
|
36
|
+
addSingletonInstance<T>(token: InjectionToken<T>, instance: T): this;
|
|
37
|
+
addTransient<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
38
|
+
addTransient<T>(type: Type<T>): this;
|
|
39
|
+
addScoped<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
40
|
+
addScoped<T>(type: Type<T>): this;
|
|
41
|
+
buildServiceProvider(): ServiceProvider;
|
|
42
|
+
addControllers(controllers: Array<Type<any>>): this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare class HttpContext {
|
|
46
|
+
readonly request: Request;
|
|
47
|
+
items: Map<string, any>;
|
|
48
|
+
constructor(request: Request);
|
|
49
|
+
get method(): string;
|
|
50
|
+
get url(): string;
|
|
51
|
+
get path(): string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare class WebApplicationBuilder {
|
|
55
|
+
services: ServiceCollection;
|
|
56
|
+
constructor();
|
|
57
|
+
build(): WebApplication;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type Middleware = (context: HttpContext, next: () => Promise<Response | void>) => Promise<Response | void> | Response | void;
|
|
61
|
+
declare class WebApplication {
|
|
62
|
+
services: ServiceProvider;
|
|
63
|
+
private middlewares;
|
|
64
|
+
private controllersToMap;
|
|
65
|
+
constructor(services: ServiceProvider);
|
|
66
|
+
static createBuilder(): WebApplicationBuilder;
|
|
67
|
+
use(middleware: Middleware): this;
|
|
68
|
+
mapControllers(controllers: Array<any>): this;
|
|
69
|
+
private executePipeline;
|
|
70
|
+
run(port?: number): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare class HttpResult {
|
|
74
|
+
readonly status: number;
|
|
75
|
+
readonly body: unknown;
|
|
76
|
+
readonly headers: Record<string, string>;
|
|
77
|
+
private constructor();
|
|
78
|
+
/** 200 OK */
|
|
79
|
+
static ok(data?: any): HttpResult;
|
|
80
|
+
/** 201 Created */
|
|
81
|
+
static created(data?: any, location?: string): HttpResult;
|
|
82
|
+
/** 204 No Content */
|
|
83
|
+
static noContent(): HttpResult;
|
|
84
|
+
/** 400 Bad Request */
|
|
85
|
+
static badRequest(data?: any, errors?: unknown): HttpResult;
|
|
86
|
+
/** 401 Unauthorized */
|
|
87
|
+
static unauthorized(data?: any): HttpResult;
|
|
88
|
+
/** 403 Forbidden */
|
|
89
|
+
static forbidden(data?: any): HttpResult;
|
|
90
|
+
/** 404 Not Found */
|
|
91
|
+
static notFound(data?: any): HttpResult;
|
|
92
|
+
/** 409 Conflict */
|
|
93
|
+
static conflict(data?: any): HttpResult;
|
|
94
|
+
/** 422 Unprocessable Entity */
|
|
95
|
+
static unprocessableEntity(data?: any, errors?: unknown): HttpResult;
|
|
96
|
+
/** 500 Internal Server Error */
|
|
97
|
+
static internalServerError(data?: any): HttpResult;
|
|
98
|
+
toResponse(): Response;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare function Injectable(): ClassDecorator;
|
|
102
|
+
declare function Inject(token: InjectionToken): ParameterDecorator;
|
|
103
|
+
|
|
104
|
+
declare function Controller(prefix?: string): ClassDecorator;
|
|
105
|
+
declare const Get: (path?: string) => MethodDecorator;
|
|
106
|
+
declare const Post: (path?: string) => MethodDecorator;
|
|
107
|
+
declare const Put: (path?: string) => MethodDecorator;
|
|
108
|
+
declare const Delete: (path?: string) => MethodDecorator;
|
|
109
|
+
declare const Patch: (path?: string) => MethodDecorator;
|
|
110
|
+
declare const FromRoute: (name: string) => ParameterDecorator;
|
|
111
|
+
declare const FromQuery: (name: string) => ParameterDecorator;
|
|
112
|
+
declare const FromBody: () => ParameterDecorator;
|
|
113
|
+
|
|
114
|
+
interface AuthorizeOptions {
|
|
115
|
+
required: boolean;
|
|
116
|
+
roles?: Array<string>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Marks a controller class or a specific method as requiring JWT authentication.
|
|
120
|
+
* When applied to a class, all methods in the controller are protected.
|
|
121
|
+
* When applied to a method, only that route is protected.
|
|
122
|
+
*
|
|
123
|
+
* @param roles Optional list of required roles (checked against `payload.roles`)
|
|
124
|
+
*/
|
|
125
|
+
declare function Authorize(roles?: Array<string>): ClassDecorator & MethodDecorator;
|
|
126
|
+
|
|
127
|
+
interface JwtOptions {
|
|
128
|
+
secret: string;
|
|
129
|
+
expiresIn?: number;
|
|
130
|
+
}
|
|
131
|
+
interface JwtPayload {
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
iat?: number;
|
|
134
|
+
exp?: number;
|
|
135
|
+
}
|
|
136
|
+
declare class JwtAuthService {
|
|
137
|
+
private secret;
|
|
138
|
+
private expiresIn;
|
|
139
|
+
constructor(options: JwtOptions);
|
|
140
|
+
private getKey;
|
|
141
|
+
sign(payload: JwtPayload): Promise<string>;
|
|
142
|
+
verify(token: string): Promise<JwtPayload>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface CorsOptions {
|
|
146
|
+
origin?: string | Array<string> | ((origin: string) => boolean);
|
|
147
|
+
methods?: string | Array<string>;
|
|
148
|
+
allowedHeaders?: string | Array<string>;
|
|
149
|
+
exposedHeaders?: string | Array<string>;
|
|
150
|
+
credentials?: boolean;
|
|
151
|
+
maxAge?: number;
|
|
152
|
+
}
|
|
153
|
+
declare function cors(options?: CorsOptions): Middleware;
|
|
154
|
+
|
|
155
|
+
interface ThrottleOptions {
|
|
156
|
+
limit: number;
|
|
157
|
+
windowMs: number;
|
|
158
|
+
keyGenerator?: (context: HttpContext) => string;
|
|
159
|
+
}
|
|
160
|
+
declare function throttle(options: ThrottleOptions): Middleware;
|
|
161
|
+
|
|
162
|
+
export { Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, Patch, Post, Put, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, throttle };
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
declare enum ServiceLifetime {
|
|
2
|
+
Singleton = 0,
|
|
3
|
+
Transient = 1,
|
|
4
|
+
Scoped = 2
|
|
5
|
+
}
|
|
6
|
+
type Type<T = any> = new (...args: Array<any>) => T;
|
|
7
|
+
type InjectionToken<T = any> = Type<T> | string | symbol;
|
|
8
|
+
interface ServiceDescriptor<T = any> {
|
|
9
|
+
token: InjectionToken<T>;
|
|
10
|
+
lifetime: ServiceLifetime;
|
|
11
|
+
implementationType?: Type<T>;
|
|
12
|
+
implementationInstance?: T;
|
|
13
|
+
implementationFactory?: (provider: any) => T;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class ServiceProvider {
|
|
17
|
+
private descriptors;
|
|
18
|
+
private parent?;
|
|
19
|
+
private singletonInstances;
|
|
20
|
+
private scopedInstances;
|
|
21
|
+
constructor(descriptors: Array<ServiceDescriptor>, parent?: ServiceProvider | undefined);
|
|
22
|
+
createScope(): ServiceProvider;
|
|
23
|
+
resolve<T>(token: InjectionToken<T>): T;
|
|
24
|
+
private resolveSingleton;
|
|
25
|
+
private resolveScoped;
|
|
26
|
+
private resolveTransient;
|
|
27
|
+
private createInstance;
|
|
28
|
+
private instantiateClass;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare class ServiceCollection {
|
|
32
|
+
private descriptors;
|
|
33
|
+
add<T>(descriptor: ServiceDescriptor<T>): this;
|
|
34
|
+
addSingleton<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
35
|
+
addSingleton<T>(type: Type<T>): this;
|
|
36
|
+
addSingletonInstance<T>(token: InjectionToken<T>, instance: T): this;
|
|
37
|
+
addTransient<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
38
|
+
addTransient<T>(type: Type<T>): this;
|
|
39
|
+
addScoped<T>(token: InjectionToken<T>, implementationType: Type<T>): this;
|
|
40
|
+
addScoped<T>(type: Type<T>): this;
|
|
41
|
+
buildServiceProvider(): ServiceProvider;
|
|
42
|
+
addControllers(controllers: Array<Type<any>>): this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare class HttpContext {
|
|
46
|
+
readonly request: Request;
|
|
47
|
+
items: Map<string, any>;
|
|
48
|
+
constructor(request: Request);
|
|
49
|
+
get method(): string;
|
|
50
|
+
get url(): string;
|
|
51
|
+
get path(): string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare class WebApplicationBuilder {
|
|
55
|
+
services: ServiceCollection;
|
|
56
|
+
constructor();
|
|
57
|
+
build(): WebApplication;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type Middleware = (context: HttpContext, next: () => Promise<Response | void>) => Promise<Response | void> | Response | void;
|
|
61
|
+
declare class WebApplication {
|
|
62
|
+
services: ServiceProvider;
|
|
63
|
+
private middlewares;
|
|
64
|
+
private controllersToMap;
|
|
65
|
+
constructor(services: ServiceProvider);
|
|
66
|
+
static createBuilder(): WebApplicationBuilder;
|
|
67
|
+
use(middleware: Middleware): this;
|
|
68
|
+
mapControllers(controllers: Array<any>): this;
|
|
69
|
+
private executePipeline;
|
|
70
|
+
run(port?: number): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare class HttpResult {
|
|
74
|
+
readonly status: number;
|
|
75
|
+
readonly body: unknown;
|
|
76
|
+
readonly headers: Record<string, string>;
|
|
77
|
+
private constructor();
|
|
78
|
+
/** 200 OK */
|
|
79
|
+
static ok(data?: any): HttpResult;
|
|
80
|
+
/** 201 Created */
|
|
81
|
+
static created(data?: any, location?: string): HttpResult;
|
|
82
|
+
/** 204 No Content */
|
|
83
|
+
static noContent(): HttpResult;
|
|
84
|
+
/** 400 Bad Request */
|
|
85
|
+
static badRequest(data?: any, errors?: unknown): HttpResult;
|
|
86
|
+
/** 401 Unauthorized */
|
|
87
|
+
static unauthorized(data?: any): HttpResult;
|
|
88
|
+
/** 403 Forbidden */
|
|
89
|
+
static forbidden(data?: any): HttpResult;
|
|
90
|
+
/** 404 Not Found */
|
|
91
|
+
static notFound(data?: any): HttpResult;
|
|
92
|
+
/** 409 Conflict */
|
|
93
|
+
static conflict(data?: any): HttpResult;
|
|
94
|
+
/** 422 Unprocessable Entity */
|
|
95
|
+
static unprocessableEntity(data?: any, errors?: unknown): HttpResult;
|
|
96
|
+
/** 500 Internal Server Error */
|
|
97
|
+
static internalServerError(data?: any): HttpResult;
|
|
98
|
+
toResponse(): Response;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare function Injectable(): ClassDecorator;
|
|
102
|
+
declare function Inject(token: InjectionToken): ParameterDecorator;
|
|
103
|
+
|
|
104
|
+
declare function Controller(prefix?: string): ClassDecorator;
|
|
105
|
+
declare const Get: (path?: string) => MethodDecorator;
|
|
106
|
+
declare const Post: (path?: string) => MethodDecorator;
|
|
107
|
+
declare const Put: (path?: string) => MethodDecorator;
|
|
108
|
+
declare const Delete: (path?: string) => MethodDecorator;
|
|
109
|
+
declare const Patch: (path?: string) => MethodDecorator;
|
|
110
|
+
declare const FromRoute: (name: string) => ParameterDecorator;
|
|
111
|
+
declare const FromQuery: (name: string) => ParameterDecorator;
|
|
112
|
+
declare const FromBody: () => ParameterDecorator;
|
|
113
|
+
|
|
114
|
+
interface AuthorizeOptions {
|
|
115
|
+
required: boolean;
|
|
116
|
+
roles?: Array<string>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Marks a controller class or a specific method as requiring JWT authentication.
|
|
120
|
+
* When applied to a class, all methods in the controller are protected.
|
|
121
|
+
* When applied to a method, only that route is protected.
|
|
122
|
+
*
|
|
123
|
+
* @param roles Optional list of required roles (checked against `payload.roles`)
|
|
124
|
+
*/
|
|
125
|
+
declare function Authorize(roles?: Array<string>): ClassDecorator & MethodDecorator;
|
|
126
|
+
|
|
127
|
+
interface JwtOptions {
|
|
128
|
+
secret: string;
|
|
129
|
+
expiresIn?: number;
|
|
130
|
+
}
|
|
131
|
+
interface JwtPayload {
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
iat?: number;
|
|
134
|
+
exp?: number;
|
|
135
|
+
}
|
|
136
|
+
declare class JwtAuthService {
|
|
137
|
+
private secret;
|
|
138
|
+
private expiresIn;
|
|
139
|
+
constructor(options: JwtOptions);
|
|
140
|
+
private getKey;
|
|
141
|
+
sign(payload: JwtPayload): Promise<string>;
|
|
142
|
+
verify(token: string): Promise<JwtPayload>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface CorsOptions {
|
|
146
|
+
origin?: string | Array<string> | ((origin: string) => boolean);
|
|
147
|
+
methods?: string | Array<string>;
|
|
148
|
+
allowedHeaders?: string | Array<string>;
|
|
149
|
+
exposedHeaders?: string | Array<string>;
|
|
150
|
+
credentials?: boolean;
|
|
151
|
+
maxAge?: number;
|
|
152
|
+
}
|
|
153
|
+
declare function cors(options?: CorsOptions): Middleware;
|
|
154
|
+
|
|
155
|
+
interface ThrottleOptions {
|
|
156
|
+
limit: number;
|
|
157
|
+
windowMs: number;
|
|
158
|
+
keyGenerator?: (context: HttpContext) => string;
|
|
159
|
+
}
|
|
160
|
+
declare function throttle(options: ThrottleOptions): Middleware;
|
|
161
|
+
|
|
162
|
+
export { Authorize, type AuthorizeOptions, Controller, type CorsOptions, Delete, FromBody, FromQuery, FromRoute, Get, HttpContext, HttpResult, Inject, Injectable, JwtAuthService, type JwtOptions, type JwtPayload, Patch, Post, Put, ServiceCollection, ServiceProvider, type ThrottleOptions, WebApplication, WebApplicationBuilder, cors, throttle };
|
package/dist/main.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var N=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var ee=Object.getOwnPropertyNames;var te=Object.prototype.hasOwnProperty;var a=(t,e)=>N(t,"name",{value:e,configurable:!0});var y=(t,e)=>()=>(t&&(e=t(t=0)),e);var W=(t,e)=>{for(var r in e)N(t,r,{get:e[r],enumerable:!0})},re=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ee(e))!te.call(t,s)&&s!==r&&N(t,s,{get:()=>e[s],enumerable:!(n=Y(e,s))||n.enumerable});return t};var _=t=>re(N({},"__esModule",{value:!0}),t);import"reflect-metadata";function x(){return t=>{Reflect.defineMetadata(ne,!0,t)}}function se(t){return(e,r,n)=>{let s=Reflect.getMetadata(H,e)||[];s.push({index:n,token:t}),Reflect.defineMetadata(H,s,e)}}var ne,H,E=y(()=>{"use strict";ne=Symbol("INJECTABLE_WATERMARK"),H=Symbol("INJECT_METADATA");a(x,"Injectable");a(se,"Inject")});import"reflect-metadata";function oe(t){return(e,r)=>{let n={required:!0,roles:t};r!==void 0?Reflect.defineMetadata(I,n,e.constructor,r):Reflect.defineMetadata(I,n,e)}}var I,D=y(()=>{"use strict";I=Symbol("AUTHORIZE_METADATA");a(oe,"Authorize")});var O,J=y(()=>{"use strict";O=class t{static{a(this,"HttpResult")}status;body;headers;constructor(e,r,n={}){this.status=e,this.body=r!==void 0&&typeof r!="string"?JSON.stringify(r):r,this.headers=n}static ok(e){return new t(200,e)}static created(e,r){let n={};return r&&(n.Location=r),new t(201,e,n)}static noContent(){return new t(204)}static badRequest(e="Bad Request",r){let n=typeof e=="string"?{message:e,errors:r}:e;return new t(400,n)}static unauthorized(e="Unauthorized"){let r=typeof e=="string"?{message:e}:e;return new t(401,r)}static forbidden(e="Forbidden"){let r=typeof e=="string"?{message:e}:e;return new t(403,r)}static notFound(e="Not Found"){let r=typeof e=="string"?{message:e}:e;return new t(404,r)}static conflict(e="Conflict"){let r=typeof e=="string"?{message:e}:e;return new t(409,r)}static unprocessableEntity(e="Unprocessable Entity",r){let n=typeof e=="string"?{message:e,errors:r}:e;return new t(422,n)}static internalServerError(e="Internal Server Error"){let r=typeof e=="string"?{message:e}:e;return new t(500,r)}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})}}});import"reflect-metadata";function ie(t=""){return e=>{Reflect.defineMetadata(P,t,e),x()(e)}}function L(t,e,r){let n=`${q.toString()}_${e.toString()}`;Reflect.hasMetadata(n,t.constructor)||Reflect.defineMetadata(n,[],t.constructor);let s=Reflect.getMetadata(n,t.constructor);s.push(r),Reflect.defineMetadata(n,s,t.constructor)}var P,w,q,j,ae,ce,le,de,ue,A,fe,pe,he,U=y(()=>{"use strict";E();P=Symbol("CONTROLLER_WATERMARK"),w=Symbol("ROUTE_METADATA"),q=Symbol("PARAM_METADATA");a(ie,"Controller");j=a(t=>(e="")=>(r,n)=>{Reflect.hasMetadata(w,r.constructor)||Reflect.defineMetadata(w,[],r.constructor);let s=Reflect.getMetadata(w,r.constructor);s.push({method:t,path:e,methodName:n.toString()}),Reflect.defineMetadata(w,s,r.constructor)},"createRouteDecorator"),ae=j("GET"),ce=j("POST"),le=j("PUT"),de=j("DELETE"),ue=j("PATCH"),A=(function(t){return t[t.Route=0]="Route",t[t.Query=1]="Query",t[t.Body=2]="Body",t[t.Context=3]="Context",t})({}),fe=a(t=>(e,r,n)=>{L(e,r,{type:0,index:n,name:t})},"FromRoute"),pe=a(t=>(e,r,n)=>{L(e,r,{type:1,index:n,name:t})},"FromQuery"),he=a(()=>(t,e,r)=>{L(t,e,{type:2,index:r})},"FromBody");a(L,"addParamMetadata")});var G={};W(G,{JwtAuthService:()=>R});function me(t,e,r,n){var s=arguments.length,o=s<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,r):n,i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(t,e,r,n);else for(var c=t.length-1;c>=0;c--)(i=t[c])&&(o=(s<3?i(o):s>3?i(e,r,o):i(e,r))||o);return s>3&&o&&Object.defineProperty(e,r,o),o}function K(t,e){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(t,e)}function k(t){return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function z(t){let e=t+"==".slice(2-t.length*3&3);return atob(e.replace(/-/g,"+").replace(/_/g,"/"))}var R,B=y(()=>{"use strict";E();a(me,"_ts_decorate");a(K,"_ts_metadata");a(k,"base64UrlEncode");a(z,"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 r={alg:"HS256",typ:"JWT"},n=Math.floor(Date.now()/1e3),s={...e,iat:n,exp:n+this.expiresIn},o=k(JSON.stringify(r)),i=k(JSON.stringify(s)),c=`${o}.${i}`,l=await this.getKey(),u=new TextEncoder,p=await crypto.subtle.sign("HMAC",l,u.encode(c)),f=k(String.fromCharCode(...new Uint8Array(p)));return`${c}.${f}`}async verify(e){let r=e.split(".");if(r.length!==3)throw new Error("Invalid JWT format");let n=r[0],s=r[1],o=r[2],i=`${n}.${s}`,c=await this.getKey(),l=new TextEncoder,u=Uint8Array.from(z(o),m=>m.charCodeAt(0));if(!await crypto.subtle.verify("HMAC",c,u,l.encode(i)))throw new Error("Invalid JWT signature");let f=JSON.parse(z(s)),T=Math.floor(Date.now()/1e3);if(f.exp!==void 0&&f.exp<T)throw new Error("JWT expired");return f}};R=me([x(),K("design:type",Function),K("design:paramtypes",[typeof JwtOptions>"u"?Object:JwtOptions])],R)});var X={};W(X,{Router:()=>$});async function ge(t,e,r){if(!r.required)return null;let n=t.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),o;try{let{JwtAuthService:c}=(B(),_(G));o=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 o.verify(s)}catch{return new Response(JSON.stringify({message:"Unauthorized: invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json"}})}if(r.roles&&r.roles.length>0){let c=i.roles??[];if(!r.roles.some(u=>c.includes(u)))return new Response(JSON.stringify({message:"Forbidden: insufficient permissions"}),{status:403,headers:{"Content-Type":"application/json"}})}return t.items.set("user",i),null}var $,Q=y(()=>{"use strict";D();J();U();a(ge,"checkAuth");$=class{static{a(this,"Router")}routes=[];registerControllers(e){for(let r of e){let n=Reflect.getMetadata(P,r);if(n===void 0)continue;let s=Reflect.getMetadata(w,r)||[];for(let o of s){let i=`/${n}/${o.path}`.replace(/\/+/g,"/");i!=="/"&&i.endsWith("/")&&(i=i.slice(0,-1));let c=new URLPattern({pathname:i});this.routes.push({method:o.method,pattern:c,controller:r,methodName:o.methodName})}}}middleware(){return async(e,r)=>{for(let n of this.routes){if(n.method!==e.method)continue;let s=n.pattern.exec({pathname:e.path});if(s){let o=e.items.get("scope");if(!o)throw new Error("DI Scope not found in HttpContext");let i=Reflect.getMetadata(I,n.controller,n.methodName),c=Reflect.getMetadata(I,n.controller),l=i??c;if(l){let d=await ge(e,o,l);if(d)return d}let u=o.resolve(n.controller),p=`${q.toString()}_${n.methodName}`,f=Reflect.getMetadata(p,n.controller)||[],T=u[n.methodName].length,m=new Array(T).fill(void 0),Z=f.some(d=>d.type===A.Body),F;Z&&(F=await e.request.json().catch(()=>({})));let V=new URL(e.request.url);for(let d of f)d.type===A.Route&&d.name?m[d.index]=s.pathname.groups[d.name]:d.type===A.Query&&d.name?m[d.index]=V.searchParams.get(d.name):d.type===A.Body?m[d.index]=F:d.type===A.Context&&(m[d.index]=e);let g=await u[n.methodName](...m);return g instanceof Response?g:g instanceof O?g.toResponse():typeof g=="object"?Response.json(g):new Response(String(g))}}return r()}}}});E();import"reflect-metadata";var h=(function(t){return t[t.Singleton=0]="Singleton",t[t.Transient=1]="Transient",t[t.Scoped=2]="Scoped",t})({});var M=class t{static{a(this,"ServiceProvider")}descriptors;parent;singletonInstances=new Map;scopedInstances=new Map;constructor(e,r){this.descriptors=e,this.parent=r}createScope(){return new t(this.descriptors,this.parent||this)}resolve(e){let r=this.descriptors.find(n=>n.token===e);if(!r){if(this.parent)return this.parent.resolve(e);throw new Error(`Service not registered for token: ${e.toString()}`)}switch(r.lifetime){case h.Singleton:return this.resolveSingleton(r);case h.Scoped:return this.resolveScoped(r);case h.Transient:return this.resolveTransient(r)}}resolveSingleton(e){let r=this.parent||this;if(r.singletonInstances.has(e.token))return r.singletonInstances.get(e.token);let n=this.createInstance(e);return r.singletonInstances.set(e.token,n),n}resolveScoped(e){if(this.parent,this.scopedInstances.has(e.token))return this.scopedInstances.get(e.token);let r=this.createInstance(e);return this.scopedInstances.set(e.token,r),r}resolveTransient(e){return this.createInstance(e)}createInstance(e){if(e.implementationInstance!==void 0)return e.implementationInstance;if(e.implementationFactory)return e.implementationFactory(this);if(e.implementationType)return this.instantiateClass(e.implementationType);throw new Error(`Invalid service descriptor for token: ${e.token.toString()}`)}instantiateClass(e){let r=Reflect.getMetadata("design:paramtypes",e)||[],n=Reflect.getMetadata(H,e)||[],s=r.map((o,i)=>{let c=n.find(u=>u.index===i),l=c?c.token:o;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(...s)}};var S=class{static{a(this,"ServiceCollection")}descriptors=[];add(e){return this.descriptors.push(e),this}addSingleton(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Singleton})}addSingletonInstance(e,r){return this.add({token:e,implementationInstance:r,lifetime:h.Singleton})}addTransient(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Transient})}addScoped(e,r){return this.add({token:e,implementationType:r||e,lifetime:h.Scoped})}buildServiceProvider(){return new M(this.descriptors)}addControllers(e){return e.forEach(r=>this.addTransient(r)),this}};var v=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 C=class{static{a(this,"WebApplicationBuilder")}services;constructor(){this.services=new S}build(){let e=this.services.buildServiceProvider();return new b(e)}};var b=class{static{a(this,"WebApplication")}services;middlewares=[];controllersToMap=[];constructor(e){this.services=e}static createBuilder(){return new C}use(e){return this.middlewares.push(e),this}mapControllers(e){return this.controllersToMap.push(...e),this}async executePipeline(e){let r=-1,n=a(async o=>{if(o<=r)throw new Error("next() called multiple times");r=o;let i=this.middlewares[o];if(o===this.middlewares.length)return new Response(JSON.stringify({message:"Not found"}),{status:404});if(i)return i(e,n.bind(null,o+1))},"dispatch");return await n(0)||new Response(JSON.stringify({message:"Internal Server Error"}),{status:500})}run(e=3e3){let r=this;if(this.controllersToMap.length>0){let{Router:n}=(Q(),_(X)),s=new n;s.registerControllers(this.controllersToMap),this.use(s.middleware())}Bun.serve({port:e,async fetch(n){let s=r.services.createScope(),o=new v(n);return o.items.set("scope",s),r.executePipeline(o)}}),console.log(`\u{1F680} Steampunk application is running on http://localhost:${e}`)}};J();E();U();D();B();function ye(t={}){return async(e,r)=>{let{request:n}=e,s=n.headers.get("origin"),o=await r();o||(o=new Response);let i=new Response(o.body,o);if(t.origin?typeof t.origin=="string"?i.headers.set("Access-Control-Allow-Origin",t.origin):Array.isArray(t.origin)&&s?t.origin.includes(s)&&i.headers.set("Access-Control-Allow-Origin",s):typeof t.origin=="function"&&s&&t.origin(s)&&i.headers.set("Access-Control-Allow-Origin",s):i.headers.set("Access-Control-Allow-Origin","*"),t.methods){let c=Array.isArray(t.methods)?t.methods.join(", "):t.methods;i.headers.set("Access-Control-Allow-Methods",c)}else i.headers.set("Access-Control-Allow-Methods","GET,HEAD,PUT,PATCH,POST,DELETE");if(t.allowedHeaders){let c=Array.isArray(t.allowedHeaders)?t.allowedHeaders.join(", "):t.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(t.exposedHeaders){let c=Array.isArray(t.exposedHeaders)?t.exposedHeaders.join(", "):t.exposedHeaders;i.headers.set("Access-Control-Expose-Headers",c)}return t.credentials&&i.headers.set("Access-Control-Allow-Credentials","true"),t.maxAge!==void 0&&i.headers.set("Access-Control-Max-Age",t.maxAge.toString()),n.method==="OPTIONS"?new Response(null,{headers:i.headers,status:204}):i}}a(ye,"cors");function we(t){let e=a(s=>{let o=s.request?.headers?.get("x-forwarded-for");return o?o.split(",")[0].trim():"global"},"defaultKeyGenerator"),r=t.keyGenerator||e,n=new Map;return async(s,o)=>{let i=r(s),c=Date.now(),l=n.get(i);if(l?c>l.resetTime?(l.count=1,l.resetTime=c+t.windowMs):l.count++:(l={count:1,resetTime:c+t.windowMs},n.set(i,l)),Math.random()<.05)for(let[f,T]of n.entries())Date.now()>T.resetTime&&n.delete(f);if(l.count>t.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":t.limit.toString(),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":Math.ceil(l.resetTime/1e3).toString()}})}let u=await o();u||(u=new Response);let p=new Response(u.body,u);return p.headers.set("X-RateLimit-Limit",t.limit.toString()),p.headers.set("X-RateLimit-Remaining",Math.max(0,t.limit-l.count).toString()),p.headers.set("X-RateLimit-Reset",Math.ceil(l.resetTime/1e3).toString()),p}}a(we,"throttle");export{oe as Authorize,ie as Controller,de as Delete,he as FromBody,pe as FromQuery,fe as FromRoute,ae as Get,v as HttpContext,O as HttpResult,se as Inject,x as Injectable,R as JwtAuthService,ue as Patch,ce as Post,le as Put,S as ServiceCollection,M as ServiceProvider,b as WebApplication,C as WebApplicationBuilder,ye as cors,we as throttle};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@danielgl/steampunk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A high-performance, DX-focused web framework for Bun, inspired by ASP.NET Core.",
|
|
6
|
+
"main": "./dist/main.cjs",
|
|
7
|
+
"module": "./dist/main.js",
|
|
8
|
+
"types": "./dist/main.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/main.d.ts",
|
|
12
|
+
"import": "./dist/main.js",
|
|
13
|
+
"require": "./dist/main.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "bun run --watch src/main.ts",
|
|
23
|
+
"build": "tsup src/main.ts --format cjs,esm --dts --clean --minify",
|
|
24
|
+
"lint": "eslint \"{src,tests,example}/**/*.ts\"",
|
|
25
|
+
"lint:fix": "eslint \"{src,tests,example}/**/*.ts\" --fix",
|
|
26
|
+
"format": "prettier --write \"{src,tests,example}/**/*.ts\"",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"prepare": "husky",
|
|
29
|
+
"prepublishOnly": "bun run lint && bun run test && bun run build"
|
|
30
|
+
},
|
|
31
|
+
"author": "Daniel Lopes",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/DanielDxD/steampunk.git"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"bun",
|
|
39
|
+
"framework",
|
|
40
|
+
"web",
|
|
41
|
+
"aspnet",
|
|
42
|
+
"dependency-injection",
|
|
43
|
+
"steampunk"
|
|
44
|
+
],
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@eslint/js": "^10.0.1",
|
|
47
|
+
"@types/bun": "latest",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
49
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
50
|
+
"eslint": "^10.0.2",
|
|
51
|
+
"eslint-config-prettier": "^10.1.8",
|
|
52
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
53
|
+
"husky": "^9.1.7",
|
|
54
|
+
"prettier": "^3.8.1",
|
|
55
|
+
"tsup": "^8.5.1",
|
|
56
|
+
"typescript-eslint": "^8.56.1"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"typescript": "^5"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"reflect-metadata": "^0.2.2"
|
|
63
|
+
}
|
|
64
|
+
}
|