@synchjs/ewb 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.
Files changed (41) hide show
  1. package/README.md +292 -0
  2. package/dist/Components/ServeMemoryStore.d.ts +14 -0
  3. package/dist/Components/ServeMemoryStore.d.ts.map +1 -0
  4. package/dist/Components/ServeMemoryStore.js +77 -0
  5. package/dist/Components/Server.d.ts +48 -0
  6. package/dist/Components/Server.d.ts.map +1 -0
  7. package/dist/Components/Server.js +441 -0
  8. package/dist/Decorations/Authorized.d.ts +8 -0
  9. package/dist/Decorations/Authorized.d.ts.map +1 -0
  10. package/dist/Decorations/Authorized.js +20 -0
  11. package/dist/Decorations/Controller.d.ts +8 -0
  12. package/dist/Decorations/Controller.d.ts.map +1 -0
  13. package/dist/Decorations/Controller.js +21 -0
  14. package/dist/Decorations/Metadata.d.ts +30 -0
  15. package/dist/Decorations/Metadata.d.ts.map +1 -0
  16. package/dist/Decorations/Metadata.js +32 -0
  17. package/dist/Decorations/Methods.d.ts +8 -0
  18. package/dist/Decorations/Methods.d.ts.map +1 -0
  19. package/dist/Decorations/Methods.js +52 -0
  20. package/dist/Decorations/Middleware.d.ts +5 -0
  21. package/dist/Decorations/Middleware.d.ts.map +1 -0
  22. package/dist/Decorations/Middleware.js +19 -0
  23. package/dist/Decorations/Security.d.ts +9 -0
  24. package/dist/Decorations/Security.d.ts.map +1 -0
  25. package/dist/Decorations/Security.js +24 -0
  26. package/dist/Decorations/Serve.d.ts +2 -0
  27. package/dist/Decorations/Serve.d.ts.map +1 -0
  28. package/dist/Decorations/Serve.js +31 -0
  29. package/dist/Decorations/Tailwind.d.ts +7 -0
  30. package/dist/Decorations/Tailwind.d.ts.map +1 -0
  31. package/dist/Decorations/Tailwind.js +8 -0
  32. package/dist/Decorations/index.d.ts +9 -0
  33. package/dist/Decorations/index.d.ts.map +1 -0
  34. package/dist/Decorations/index.js +8 -0
  35. package/dist/globals.d.ts +6 -0
  36. package/dist/globals.d.ts.map +1 -0
  37. package/dist/globals.js +0 -0
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +5 -0
  41. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # @synchjs/ewb
2
+
3
+ A robust, decorator-based web server framework built on top of **Express** and optimized for **Bun**. It provides a structured way to build scalable APIs with built-in support for **OpenAPI (Swagger)**, **Validation**, **Authentication**, and **Frontend Asset Serving** (with Tailwind CSS support).
4
+
5
+ ## Features
6
+
7
+ - 🏗 **Decorator-based Routing**: Define controllers and routes using concise decorators (`@Controller`, `@Get`, `@Post`).
8
+ - 🔒 **Built-in Authentication**: Easy-to-use Bearer Token authentication (`@BearerAuth`).
9
+ - 📜 **Auto-generated Swagger Docs**: Your API documentation is generated automatically from your code.
10
+ - ✅ **Request Validation**: Schema-based validation using AJV.
11
+ - 🎨 **Frontend Serving**: Serve HTML/CSS/JS assets from memory, with built-in **Tailwind CSS** processing via Bun plugins.
12
+ - 🧩 **Dependency Injection**: Support for IoC containers.
13
+ - 🚀 **Bun Optimized**: Leverages Bun's build capabilities for static assets.
14
+ - 🖥 **TUI-style Logs**: Clean and informative startup messages.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun add @synchjs/ewb
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### 1. Create a Controller
25
+
26
+ Create a file `controllers/HomeController.ts`:
27
+
28
+ ```typescript
29
+ import { Controller, Get } from "ewb";
30
+ import type { Request, Response } from "express";
31
+
32
+ @Controller("/")
33
+ export class HomeController {
34
+ @Get("/", {
35
+ summary: "Welcome endpoint",
36
+ description: "Returns a welcome message.",
37
+ })
38
+ public index(req: Request, res: Response) {
39
+ return { message: "Hello from @synchjs/ewb!" };
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### 2. Start the Server
45
+
46
+ Create `index.ts`:
47
+
48
+ ```typescript
49
+ import { Server } from "ewb";
50
+
51
+ const server = new Server({
52
+ id: "main",
53
+ port: 3000,
54
+ controllersDir: "controllers", // Directory where your controllers are located
55
+ enableSwagger: true, // Enable Swagger UI at /api-docs
56
+ });
57
+
58
+ server.init();
59
+ ```
60
+
61
+ Run with Bun:
62
+
63
+ ```bash
64
+ bun run index.ts
65
+ ```
66
+
67
+ ### 3. Customize Swagger Path
68
+
69
+ You can change where Swagger UI is served:
70
+
71
+ ```typescript
72
+ const server = new Server({
73
+ id: "main",
74
+ port: 3000,
75
+ enableSwagger: true,
76
+ swaggerPath: "/docs", // Now available at http://localhost:3000/docs
77
+ });
78
+ ```
79
+
80
+ ## Detailed Usage
81
+
82
+ ### Controllers & Routing
83
+
84
+ Use `@Controller` to define a base path and HTTP method decorators for routes.
85
+
86
+ ```typescript
87
+ import { Controller, Get, Post, Put, Delete } from "ewb";
88
+ import type { Request, Response } from "express";
89
+
90
+ @Controller("/users", { tags: ["Users"] })
91
+ export class UserController {
92
+ @Get("/:id")
93
+ public getUser(req: Request, res: Response) {
94
+ const { id } = req.params;
95
+ return { id, name: "User " + id };
96
+ }
97
+
98
+ @Post("/", {
99
+ summary: "Create User",
100
+ responses: {
101
+ 201: { description: "User created" },
102
+ },
103
+ })
104
+ public createUser(req: Request, res: Response) {
105
+ // Logic to create user
106
+ return { id: 1, ...req.body };
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Authentication
112
+
113
+ Secure your endpoints using `@BearerAuth`. It integrates with JWT verification automatically.
114
+
115
+ - **Class Level:** Protects all routes in the controller.
116
+ - **Method Level:** Protects specific routes.
117
+ - **@Public():** Excludes a route from class-level auth.
118
+
119
+ ```typescript
120
+ import { Controller, Get, BearerAuth, Public } from "ewb";
121
+
122
+ @Controller("/secure")
123
+ @BearerAuth("my_secret_key") // Optional: Custom secret, defaults to process.env.JWT_SECRET
124
+ export class SecureController {
125
+ @Get("/dashboard")
126
+ public dashboard(req: Request, res: Response) {
127
+ // Access user data attached by middleware (req.user)
128
+ const user = (req as any).user;
129
+ return { message: "Secret data", user };
130
+ }
131
+
132
+ @Get("/status")
133
+ @Public() // Accessible without token
134
+ public status(req: Request, res: Response) {
135
+ return { status: "OK" };
136
+ }
137
+ }
138
+ ```
139
+
140
+ #### Advanced Security (@Security, @OAuth, @ApiKey)
141
+
142
+ For other authentication methods like OAuth2 or API Keys, use generic decorators:
143
+
144
+ ```typescript
145
+ import { Controller, Get, OAuth, ApiKey } from "ewb";
146
+
147
+ @Controller("/api")
148
+ export class ApiController {
149
+ @Get("/profile")
150
+ @OAuth(["read:profile"]) // Marks route as requiring OAuth2 with specific scope
151
+ public getProfile() {
152
+ return { name: "John Doe" };
153
+ }
154
+
155
+ @Get("/data")
156
+ @ApiKey("X-API-KEY") // Custom security scheme name
157
+ public getData() {
158
+ return { sensitive: "data" };
159
+ }
160
+ }
161
+ ```
162
+
163
+ **Configure Handlers and Schemes:**
164
+
165
+ ```typescript
166
+ const server = new Server({
167
+ // ...,
168
+ securitySchemes: {
169
+ oauth2: {
170
+ type: "oauth2",
171
+ flows: {
172
+ /* ... standard OpenAPI flow definition ... */
173
+ },
174
+ },
175
+ },
176
+ securityHandlers: {
177
+ oauth2: (req, res, next) => {
178
+ // Your OAuth validation logic here
179
+ next();
180
+ },
181
+ },
182
+ });
183
+ ```
184
+
185
+ ### Request Validation
186
+
187
+ Define a JSON schema in the `@Post` (or other method) decorator to automatically validate the request body using AJV.
188
+
189
+ ```typescript
190
+ @Post("/register", {
191
+ summary: "Register new user",
192
+ requestBody: {
193
+ content: {
194
+ "application/json": {
195
+ schema: {
196
+ type: "object",
197
+ properties: {
198
+ username: { type: "string" },
199
+ email: { type: "string", format: "email" },
200
+ age: { type: "integer", minimum: 18 }
201
+ },
202
+ required: ["username", "email"]
203
+ }
204
+ }
205
+ }
206
+ }
207
+ })
208
+ public register(req: Request, res: Response) {
209
+ // If execution reaches here, req.body is valid
210
+ return { success: true };
211
+ }
212
+ ```
213
+
214
+ ### Serving Frontend Assets (with Tailwind CSS)
215
+
216
+ You can serve compiled HTML and automatically process Tailwind CSS using the `@Serve` and `@Tailwindcss` decorators.
217
+
218
+ 1. **Project Structure**:
219
+
220
+ ```
221
+ /views
222
+ src/
223
+ index.html
224
+ index.css (imports tailwind, or handled via plugin)
225
+ frontend.tsx
226
+ ```
227
+
228
+ 2. **Controller Setup**:
229
+
230
+ ```typescript
231
+ import { Controller, Get, Serve, Tailwindcss } from "ewb";
232
+
233
+ @Controller("/")
234
+ @Tailwindcss({
235
+ enable: true,
236
+ plugins: [
237
+ /* Bun plugins or custom PostCSS wrappers */
238
+ ],
239
+ })
240
+ export class FrontendController {
241
+ @Get("/")
242
+ @Serve("views/src/index.html") // Path to your HTML entry point
243
+ public app(req: Request, res: Response) {
244
+ // The decorator handles the response.
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### Middleware
250
+
251
+ Apply custom Express middleware to controllers or routes using `@Middleware`.
252
+
253
+ ```typescript
254
+ import { Controller, Get, Middleware } from "ewb";
255
+
256
+ const logAccess = (req: Request, res: Response, next: NextFunction) => {
257
+ console.log("Accessed!");
258
+ next();
259
+ };
260
+
261
+ @Controller("/audit")
262
+ @Middleware(logAccess)
263
+ export class AuditController {
264
+ @Get("/")
265
+ public index(req: Request, res: Response) {
266
+ return { message: "Audited" };
267
+ }
268
+ }
269
+ ```
270
+
271
+ ## Server Configuration
272
+
273
+ The `Server` class accepts the following options:
274
+
275
+ | Option | Type | Description |
276
+ | ------------------ | ------------------ | ---------------------------------------------------------------- |
277
+ | `id` | `string` | Unique identifier for the server instance. |
278
+ | `port` | `number` | Port to listen on. |
279
+ | `controllersDir` | `string` | Directory containing controller files. |
280
+ | `enableSwagger` | `boolean` | Enable OpenAPI documentation. |
281
+ | `swaggerPath` | `string` | Custom path for Swagger UI (default: `/api-docs`). |
282
+ | `logging` | `boolean` | Enable/disable TUI startup messages (default: true). |
283
+ | `corsOptions` | `CorsOptions` | Configuration for CORS. |
284
+ | `helmetOptions` | `HelmetOptions` | Configuration for Helmet security headers. |
285
+ | `rateLimitOptions` | `RateLimitOptions` | Configuration for rate limiting. |
286
+ | `securitySchemes` | `object` | custom OpenAPI security schemes definitions. |
287
+ | `securityHandlers` | `object` | Middleware handlers for security schemes. |
288
+ | `container` | `object` | IoC container for dependency injection (must have `get` method). |
289
+
290
+ ## License
291
+
292
+ MIT
@@ -0,0 +1,14 @@
1
+ import type { TailwindOptions } from "../Decorations/Tailwind";
2
+ export declare class ServeMemoryStore {
3
+ private static _instance;
4
+ private _assets;
5
+ private _htmlCache;
6
+ private constructor();
7
+ static get instance(): ServeMemoryStore;
8
+ getAsset(path: string): {
9
+ type: string;
10
+ content: Uint8Array;
11
+ };
12
+ buildAndCache(htmlPath: string, options?: TailwindOptions): Promise<string>;
13
+ }
14
+ //# sourceMappingURL=ServeMemoryStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServeMemoryStore.d.ts","sourceRoot":"","sources":["../../src/Components/ServeMemoryStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAmB;IAC3C,OAAO,CAAC,OAAO,CACH;IACZ,OAAO,CAAC,UAAU,CAAkC;IAEpD,OAAO;IAEP,WAAkB,QAAQ,IAAI,gBAAgB,CAK7C;IAEM,QAAQ,CAAC,IAAI,EAAE,MAAM;cAbS,MAAM;iBAAW,UAAU;;IAmBnD,aAAa,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAgD,GACxD,OAAO,CAAC,MAAM,CAAC;CAsEnB"}
@@ -0,0 +1,77 @@
1
+ import tailwindPlugin from "bun-plugin-tailwind";
2
+ export class ServeMemoryStore {
3
+ static _instance;
4
+ _assets = new Map();
5
+ _htmlCache = new Map();
6
+ constructor() { }
7
+ static get instance() {
8
+ if (!ServeMemoryStore._instance) {
9
+ ServeMemoryStore._instance = new ServeMemoryStore();
10
+ }
11
+ return ServeMemoryStore._instance;
12
+ }
13
+ getAsset(path) {
14
+ // Remove leading slash for matching if stored without it, or normalize.
15
+ // We will store with leading slash.
16
+ return this._assets.get(path);
17
+ }
18
+ async buildAndCache(htmlPath, options = { enable: false, plugins: [] }) {
19
+ const cacheKey = htmlPath +
20
+ (options.enable ? ":tw" : "") +
21
+ (options.plugins ? ":" + options.plugins.length : "");
22
+ if (this._htmlCache.has(cacheKey)) {
23
+ return this._htmlCache.get(cacheKey);
24
+ }
25
+ try {
26
+ const plugins = [...(options.plugins || [])];
27
+ if (options.enable) {
28
+ plugins.push(tailwindPlugin);
29
+ }
30
+ const build = await Bun.build({
31
+ entrypoints: [htmlPath],
32
+ target: "browser",
33
+ minify: true,
34
+ naming: "[name]-[hash].[ext]", // Ensure unique names
35
+ publicPath: "/", // Assets served from root
36
+ plugins: plugins,
37
+ });
38
+ if (!build.success) {
39
+ console.error("Build failed:", build.logs);
40
+ throw new Error("Build failed");
41
+ }
42
+ let htmlContent = "";
43
+ for (const output of build.outputs) {
44
+ const content = await output.arrayBuffer();
45
+ let text = await output.text(); // For HTML/CSS
46
+ // output.path is the absolute path if we were writing, or the name.
47
+ // With naming option, output.path usually contains the generated name.
48
+ // For artifacts, we need to map the requested URL to this content.
49
+ // output.kind gives us hint.
50
+ // Parse the relative path (URL) from the output.
51
+ // Since we didn't specify outdir, Bun might give us just the name or path relative to cwd.
52
+ // However, with `publicPath: "/"`, the HTML imports will look like `/foo-hash.js`.
53
+ // We need to store keys as `/foo-hash.js`.
54
+ // Let's rely on the fact that `output.path` usually returns what would be written to disk.
55
+ // We need the filename part.
56
+ const filename = output.path.split(/[/\\]/).pop();
57
+ const webPath = "/" + filename;
58
+ if (output.type === "text/html" || filename?.endsWith(".html")) {
59
+ htmlContent = text;
60
+ }
61
+ else {
62
+ let finalContent = new Uint8Array(content);
63
+ this._assets.set(webPath, {
64
+ type: output.type,
65
+ content: finalContent,
66
+ });
67
+ }
68
+ }
69
+ this._htmlCache.set(cacheKey, htmlContent);
70
+ return htmlContent;
71
+ }
72
+ catch (error) {
73
+ console.error("Serve build error:", error);
74
+ throw error;
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,48 @@
1
+ import { type Request, type Response, type NextFunction } from "express";
2
+ import { type HelmetOptions } from "helmet";
3
+ import cors from "cors";
4
+ import { type Options as RateLimitOptions } from "express-rate-limit";
5
+ export declare class Server {
6
+ private readonly _app;
7
+ private readonly _port;
8
+ private readonly _controllersDir;
9
+ readonly _id: string;
10
+ private readonly _enableSwagger;
11
+ private readonly _swaggerPath;
12
+ private readonly _enableLogging;
13
+ private readonly _securitySchemes?;
14
+ private readonly _ajv;
15
+ private readonly _securityHandlers;
16
+ private readonly _container?;
17
+ private _serverInstance?;
18
+ private _controllerCount;
19
+ private _routeCount;
20
+ private _tailwindEnabled;
21
+ constructor(options: ServerOptions);
22
+ private log;
23
+ init(): Promise<void>;
24
+ private printStartupMessage;
25
+ close(): void;
26
+ private loadControllers;
27
+ private createJwtMiddleware;
28
+ private createAuthMiddleware;
29
+ private createValidationMiddleware;
30
+ private setupSwagger;
31
+ }
32
+ export interface ServerOptions {
33
+ port: number;
34
+ enableSwagger?: boolean;
35
+ swaggerPath?: string;
36
+ logging?: boolean;
37
+ id: string;
38
+ controllersDir?: string;
39
+ corsOptions?: cors.CorsOptions;
40
+ helmetOptions?: HelmetOptions;
41
+ rateLimitOptions?: Partial<RateLimitOptions>;
42
+ securityHandlers?: Record<string, (req: Request, res: Response, next: NextFunction) => void>;
43
+ securitySchemes?: Record<string, any>;
44
+ container?: {
45
+ get: (target: any) => any;
46
+ };
47
+ }
48
+ //# sourceMappingURL=Server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Server.d.ts","sourceRoot":"","sources":["../../src/Components/Server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAEd,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,YAAY,EAClB,MAAM,SAAS,CAAC;AAKjB,OAAe,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAkB,EAChB,KAAK,OAAO,IAAI,gBAAgB,EACjC,MAAM,oBAAoB,CAAC;AAuB5B,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAU;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAM;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAGhC;IACF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAgC;IAC5D,OAAO,CAAC,eAAe,CAAC,CAAa;IAErC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,EAAE,aAAa;IAkClC,OAAO,CAAC,GAAG;IAME,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqClC,OAAO,CAAC,mBAAmB;IAmCpB,KAAK,IAAI,IAAI;YASN,eAAe;IAoP7B,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,oBAAoB;IAyD5B,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,YAAY;CAmErB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;IAC/B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CACvB,MAAM,EACN,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAC1D,CAAC;IACF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,CAAA;KAAE,CAAC;CAC3C"}