@synchjs/ewb 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # @synchjs/ewb
2
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).
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**, **Custom User Management**, and **Frontend Asset Serving** with **HMR (Hot Module Replacement)**.
4
4
 
5
5
  ## Features
6
6
 
7
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`).
8
+ - 👥 **Flexible User Management**: Pluggable `UserHandler` for custom Auth/Identity logic (JWT, Session, Database, etc.).
9
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.
10
+ - ✅ **Request Validation**: Strict schema-based validation using AJV.
11
+ - 🔥 **Hot Module Replacement (HMR)**: Real-time frontend updates without full page reloads using Socket.io and hot script swapping.
12
+ - 🎨 **Frontend Serving**: Serve HTML/CSS/JS assets from memory, with built-in **Tailwind CSS** processing.
13
+ - 🐚 **Security Hardened**: Built-in protection with **Helmet**, **CORS**, **Rate Limiting**, and path traversal protection.
14
+ - 🖥 **Premium TUI Logs**: Clean, informative, and beautiful startup messages.
15
15
 
16
16
  ## Installation
17
17
 
@@ -26,16 +26,14 @@ bun add @synchjs/ewb
26
26
  Create a file `controllers/HomeController.ts`:
27
27
 
28
28
  ```typescript
29
- import { Controller, Get } from "ewb";
30
- import type { Request, Response } from "express";
29
+ import { Controller, Get } from "@synchjs/ewb";
31
30
 
32
31
  @Controller("/")
33
32
  export class HomeController {
34
33
  @Get("/", {
35
34
  summary: "Welcome endpoint",
36
- description: "Returns a welcome message.",
37
35
  })
38
- public index(req: Request, res: Response) {
36
+ public index() {
39
37
  return { message: "Hello from @synchjs/ewb!" };
40
38
  }
41
39
  }
@@ -46,246 +44,128 @@ export class HomeController {
46
44
  Create `index.ts`:
47
45
 
48
46
  ```typescript
49
- import { Server } from "ewb";
47
+ import { Server } from "@synchjs/ewb";
50
48
 
51
49
  const server = new Server({
52
50
  id: "main",
53
51
  port: 3000,
54
- controllersDir: "controllers", // Directory where your controllers are located
55
- enableSwagger: true, // Enable Swagger UI at /api-docs
52
+ controllersDir: "controllers",
56
53
  });
57
54
 
58
- server.init();
55
+ await server.init();
59
56
  ```
60
57
 
61
- Run with Bun:
62
-
63
- ```bash
64
- bun run index.ts
65
- ```
58
+ ## Authentication & User Management
66
59
 
67
- ### 3. Customize Swagger Path
60
+ The framework uses a `UserHandler` system to give you full control over identity.
68
61
 
69
- You can change where Swagger UI is served:
62
+ ### 1. Define your User Handler
70
63
 
71
64
  ```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";
65
+ import { UserHandler } from "@synchjs/ewb";
88
66
  import type { Request, Response } from "express";
89
67
 
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 };
68
+ class MyUserHandler extends UserHandler {
69
+ // Determine who the user is for every request
70
+ public async authenticate(req: Request, res: Response) {
71
+ const token = req.headers.authorization?.replace("Bearer ", "");
72
+ if (token === "admin-secret") {
73
+ return { id: 1, name: "Admin", roles: ["admin"] };
74
+ }
75
+ return null;
96
76
  }
97
77
 
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 };
78
+ // Optional: Automatic routes for /auth/signin, signup, logout
79
+ public signin(req: Request, res: Response) {
80
+ /* ... */
107
81
  }
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 };
82
+ public signup(req: Request, res: Response) {
83
+ /* ... */
130
84
  }
131
-
132
- @Get("/status")
133
- @Public() // Accessible without token
134
- public status(req: Request, res: Response) {
135
- return { status: "OK" };
85
+ public logout(req: Request, res: Response) {
86
+ /* ... */
136
87
  }
137
88
  }
138
89
  ```
139
90
 
140
- #### Advanced Security (@Security, @OAuth, @ApiKey)
91
+ ### 2. Protect Routes
141
92
 
142
- For other authentication methods like OAuth2 or API Keys, use generic decorators:
93
+ Use `@Authorized` to require a user, or specify roles.
143
94
 
144
95
  ```typescript
145
- import { Controller, Get, OAuth, ApiKey } from "ewb";
96
+ import { Controller, Get, Authorized } from "@synchjs/ewb";
146
97
 
147
- @Controller("/api")
148
- export class ApiController {
98
+ @Controller("/dashboard")
99
+ export class DashboardController {
149
100
  @Get("/profile")
150
- @OAuth(["read:profile"]) // Marks route as requiring OAuth2 with specific scope
151
- public getProfile() {
152
- return { name: "John Doe" };
101
+ @Authorized() // Requires a non-null return from your UserHandler
102
+ public getProfile({ user }: { user: any }) {
103
+ return { message: `Hello ${user.name}` };
153
104
  }
154
105
 
155
- @Get("/data")
156
- @ApiKey("X-API-KEY") // Custom security scheme name
157
- public getData() {
158
- return { sensitive: "data" };
106
+ @Get("/admin")
107
+ @Authorized(["admin"]) // Checks roles array from UserHandler
108
+ public adminOnly() {
109
+ return { message: "Admin area" };
159
110
  }
160
111
  }
161
112
  ```
162
113
 
163
- **Configure Handlers and Schemes:**
114
+ ### 3. Register the Handler
164
115
 
165
116
  ```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
- });
117
+ const server = new Server({ ... });
118
+ server.setUserHandler(new MyUserHandler()); // Routes /auth/* are created automatically
119
+ await server.init();
183
120
  ```
184
121
 
185
- ### Request Validation
186
-
187
- Define a JSON schema in the `@Post` (or other method) decorator to automatically validate the request body using AJV.
122
+ ## Hot Module Replacement (HMR)
188
123
 
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
- ```
124
+ HMR is active by default when `NODE_ENV` is not set to `production`. It uses Socket.io to sync changes.
213
125
 
214
- ### Serving Frontend Assets (with Tailwind CSS)
126
+ - **Fast Refresh**: Specifically optimized for React applications.
127
+ - **Hot Script Swapping**: Updates code in the browser without losing state or full page reloads.
128
+ - **Cache Control**: Automatically disables browser caching during development.
215
129
 
216
- You can serve compiled HTML and automatically process Tailwind CSS using the `@Serve` and `@Tailwindcss` decorators.
130
+ ## Serving Frontend Assets
217
131
 
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**:
132
+ Use `@Serve` to bundle and serve frontend entry points.
229
133
 
230
134
  ```typescript
231
- import { Controller, Get, Serve, Tailwindcss } from "ewb";
135
+ import { Controller, Get, Serve, Tailwindcss } from "@synchjs/ewb";
232
136
 
233
137
  @Controller("/")
234
- @Tailwindcss({
235
- enable: true,
236
- plugins: [
237
- /* Bun plugins or custom PostCSS wrappers */
238
- ],
239
- })
138
+ @Tailwindcss({ enable: true })
240
139
  export class FrontendController {
241
140
  @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.
141
+ @Serve("views/src/index.html")
142
+ public app() {
143
+ // Handled by memory store
245
144
  }
246
145
  }
247
146
  ```
248
147
 
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
- };
148
+ ## Security & Best Practices
260
149
 
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
- ```
150
+ - **Strict Validation**: AJV validates all request bodies defined in Swagger options. It automatically removes additional properties and enforces strict types.
151
+ - **Rate Limiting**: Protects your API from brute-force/DoS. (Static assets are automatically exempt).
152
+ - **Security Headers**: Powered by Helmet, with dynamic CSP adjustments for HMR.
153
+ - **Error Handling**: Production mode masks internal errors and stack traces.
270
154
 
271
155
  ## Server Configuration
272
156
 
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). |
157
+ | Option | Type | Description |
158
+ | ------------------ | ---------- | ------------------------------------------------------ |
159
+ | `id` | `string` | ID for logging and console output. |
160
+ | `port` | `number` | Port to listen on. |
161
+ | `controllersDir` | `string` | Location of your controllers (default: `controllers`). |
162
+ | `viewsDir` | `string` | Base directory for frontend views. |
163
+ | `enableSwagger` | `boolean` | Enable Swagger UI (default: `false`). |
164
+ | `swaggerPath` | `string` | Path for docs (default: `/api-docs`). |
165
+ | `helmetOptions` | `object` | Custom Helmet configuration. |
166
+ | `corsOptions` | `object` | Custom CORS configuration. |
167
+ | `rateLimitOptions` | `object` | Custom Rate Limit configuration. |
168
+ | `roleHandler` | `function` | Custom function for advanced role checking logic. |
289
169
 
290
170
  ## License
291
171
 
@@ -8,11 +8,15 @@ export declare class ServeMemoryStore {
8
8
  private _watchers;
9
9
  private _listeners;
10
10
  private constructor();
11
+ private ensureCacheDir;
12
+ clearCache(): void;
11
13
  static get instance(): ServeMemoryStore;
12
14
  setDevMode(enabled: boolean): void;
13
- onRebuild(listener: () => void): void;
15
+ onRebuild(listener: (data?: {
16
+ html: string;
17
+ }) => void): void;
14
18
  private notify;
15
- getAsset(path: string): {
19
+ getAsset(rawPath: string): {
16
20
  type: string;
17
21
  content: Uint8Array;
18
22
  };
@@ -1 +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;AAI/D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAmB;IAC3C,OAAO,CAAC,OAAO,CACH;IACZ,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,UAAU,CAAsB;IAExC,OAAO;IAUP,WAAkB,QAAQ,IAAI,gBAAgB,CAK7C;IAEM,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI;IAIrC,OAAO,CAAC,MAAM;IAIP,QAAQ,CAAC,IAAI,EAAE,MAAM;cArCS,MAAM;iBAAW,UAAU;;IAyChE,OAAO,CAAC,WAAW;YAQL,YAAY;IAuB1B,OAAO,CAAC,YAAY;IA4CP,aAAa,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAgD,GACxD,OAAO,CAAC,MAAM,CAAC;CAqHnB"}
1
+ {"version":3,"file":"ServeMemoryStore.d.ts","sourceRoot":"","sources":["../../src/Components/ServeMemoryStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAK/D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAmB;IAC3C,OAAO,CAAC,OAAO,CACH;IACZ,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,UAAU,CAA6C;IAE/D,OAAO;IAIP,OAAO,CAAC,cAAc;IAUf,UAAU;IAgBjB,WAAkB,QAAQ,IAAI,gBAAgB,CAK7C;IAEM,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI;IAI5D,OAAO,CAAC,MAAM;IAIP,QAAQ,CAAC,OAAO,EAAE,MAAM;cAzDM,MAAM;iBAAW,UAAU;;IAgEhE,OAAO,CAAC,WAAW;YAQL,YAAY;IAyB1B,OAAO,CAAC,YAAY;IA4CP,aAAa,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAgD,GACxD,OAAO,CAAC,MAAM,CAAC;CA4JnB"}
@@ -1,6 +1,7 @@
1
1
  import tailwindPlugin from "bun-plugin-tailwind";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
+ import { load } from "cheerio";
4
5
  export class ServeMemoryStore {
5
6
  static _instance;
6
7
  _assets = new Map();
@@ -10,6 +11,9 @@ export class ServeMemoryStore {
10
11
  _watchers = new Map();
11
12
  _listeners = [];
12
13
  constructor() {
14
+ this.ensureCacheDir();
15
+ }
16
+ ensureCacheDir() {
13
17
  if (!fs.existsSync(this._cacheDir)) {
14
18
  try {
15
19
  fs.mkdirSync(this._cacheDir, { recursive: true });
@@ -19,6 +23,22 @@ export class ServeMemoryStore {
19
23
  }
20
24
  }
21
25
  }
26
+ clearCache() {
27
+ if (fs.existsSync(this._cacheDir)) {
28
+ try {
29
+ const files = fs.readdirSync(this._cacheDir);
30
+ for (const file of files) {
31
+ fs.unlinkSync(path.join(this._cacheDir, file));
32
+ }
33
+ // Silent clear
34
+ }
35
+ catch (e) {
36
+ console.error("[Cache] Error clearing cache:", e);
37
+ }
38
+ }
39
+ this._htmlCache.clear();
40
+ this._assets.clear();
41
+ }
22
42
  static get instance() {
23
43
  if (!ServeMemoryStore._instance) {
24
44
  ServeMemoryStore._instance = new ServeMemoryStore();
@@ -31,11 +51,15 @@ export class ServeMemoryStore {
31
51
  onRebuild(listener) {
32
52
  this._listeners.push(listener);
33
53
  }
34
- notify() {
35
- this._listeners.forEach((l) => l());
54
+ notify(data) {
55
+ this._listeners.forEach((l) => l(data));
36
56
  }
37
- getAsset(path) {
38
- return this._assets.get(path);
57
+ getAsset(rawPath) {
58
+ // Basic path traversal protection
59
+ const normalizedPath = path.posix.normalize(rawPath);
60
+ if (normalizedPath.includes(".."))
61
+ return undefined;
62
+ return this._assets.get(normalizedPath);
39
63
  }
40
64
  getCacheKey(htmlPath, options) {
41
65
  return (htmlPath +
@@ -43,6 +67,8 @@ export class ServeMemoryStore {
43
67
  (options.plugins ? ":" + options.plugins.length : ""));
44
68
  }
45
69
  async loadFromDisk(cacheKey) {
70
+ if (this._devMode)
71
+ return null; // Skip disk cache in dev mode
46
72
  const hash = Bun.hash(cacheKey).toString(16);
47
73
  const cacheFile = path.join(this._cacheDir, `${hash}.json`);
48
74
  if (fs.existsSync(cacheFile)) {
@@ -77,7 +103,7 @@ export class ServeMemoryStore {
77
103
  filename.includes(".git") ||
78
104
  filename.includes(".ebw-cache"))
79
105
  return;
80
- console.log(`[HMR] Change detected in ${filename}. Rebuilding...`);
106
+ // Silent detect
81
107
  // Clear cache for this entry
82
108
  this._htmlCache.delete(cacheKey);
83
109
  // Clear disk cache by deleting file
@@ -87,8 +113,8 @@ export class ServeMemoryStore {
87
113
  fs.unlinkSync(cacheFile);
88
114
  }
89
115
  try {
90
- await this.buildAndCache(htmlPath, options);
91
- this.notify();
116
+ const newHtml = await this.buildAndCache(htmlPath, options);
117
+ this.notify({ html: newHtml });
92
118
  }
93
119
  catch (e) {
94
120
  console.error("[HMR] Rebuild failed:", e);
@@ -99,11 +125,10 @@ export class ServeMemoryStore {
99
125
  async buildAndCache(htmlPath, options = { enable: false, plugins: [] }) {
100
126
  const cacheKey = this.getCacheKey(htmlPath, options);
101
127
  // 1. Check Memory Cache
102
- if (this._htmlCache.has(cacheKey)) {
128
+ if (!this._devMode && this._htmlCache.has(cacheKey)) {
103
129
  return this._htmlCache.get(cacheKey);
104
130
  }
105
- // 2. Check Disk Cache (unless in dev mode and we want to ensure fresh start,
106
- // but usually disk cache is fine as watcher will invalidate it)
131
+ // 2. Check Disk Cache
107
132
  const diskHtml = await this.loadFromDisk(cacheKey);
108
133
  if (diskHtml) {
109
134
  if (this._devMode) {
@@ -154,37 +179,76 @@ export class ServeMemoryStore {
154
179
  // Inject HMR/Reload script in dev mode
155
180
  if (this._devMode) {
156
181
  const reloadScript = `
182
+ <script src="/socket.io/socket.io.js"></script>
157
183
  <script id="ebw-hmr-script">
158
184
  (function() {
159
- const sse = new EventSource('/ebw-hmr');
160
- sse.onmessage = (e) => {
161
- if (e.data === 'reload') {
162
- console.log('[HMR] Reloading page...');
185
+ const socket = io(window.location.origin);
186
+
187
+ socket.on('rebuild', (data) => {
188
+ if (!data || !data.html) return;
189
+ console.log('[HMR] Rebuild detected. Hot swapping scripts...');
190
+
191
+ const parser = new DOMParser();
192
+ const newDoc = parser.parseFromString(data.html, 'text/html');
193
+ const newScripts = Array.from(newDoc.querySelectorAll('script[src]'))
194
+ .filter(s => !s.id && s.getAttribute('src').includes('-')); // Find bundled scripts
195
+
196
+ if (newScripts.length === 0) {
197
+ console.warn('[HMR] No bundled scripts found in rebuild. Falling back to reload.');
163
198
  location.reload();
199
+ return;
164
200
  }
165
- };
166
- sse.onerror = () => {
201
+
202
+ // Remove old bundled scripts
203
+ const oldScripts = Array.from(document.querySelectorAll('script[src]'))
204
+ .filter(s => !s.id && s.getAttribute('src').includes('-'));
205
+
206
+ oldScripts.forEach(s => s.remove());
207
+
208
+ // Add new scripts
209
+ newScripts.forEach(s => {
210
+ const script = document.createElement('script');
211
+ Array.from(s.attributes).forEach(attr => script.setAttribute(attr.name, attr.value));
212
+ document.body.appendChild(script);
213
+ });
214
+
215
+ console.log('[HMR] Hot swap complete!');
216
+ });
217
+
218
+ socket.on('reload', () => {
219
+ console.log('[HMR] Hard reload signal received.');
220
+ location.reload();
221
+ });
222
+
223
+ socket.on('disconnect', () => {
167
224
  console.warn('[HMR] Connection lost. Attempting to reconnect...');
168
- };
225
+ });
169
226
  })();
170
227
  </script>
171
228
  `;
172
- if (htmlContent.includes("</body>")) {
173
- htmlContent = htmlContent.replace("</body>", `${reloadScript}</body>`);
229
+ // In dev mode, we might want to prevent caching by appending a timestamp to asset links in the HTML
230
+ const timestamp = Date.now();
231
+ htmlContent = htmlContent.replace(/(\.js|\.css)(\?.*)?/g, `$1?t=${timestamp}`);
232
+ const $ = load(htmlContent);
233
+ if ($("body").length > 0) {
234
+ $("body").append(reloadScript);
174
235
  }
175
236
  else {
176
- htmlContent += reloadScript;
237
+ $.root().append(reloadScript);
177
238
  }
239
+ htmlContent = $.html();
178
240
  this.setupWatcher(htmlPath, options);
179
241
  }
180
242
  // 4. Save to Memory and Disk
181
243
  this._htmlCache.set(cacheKey, htmlContent);
182
- const hash = Bun.hash(cacheKey).toString(16);
183
- const cacheFile = path.join(this._cacheDir, `${hash}.json`);
184
- fs.writeFileSync(cacheFile, JSON.stringify({
185
- html: htmlContent,
186
- assets: currentBuildAssets,
187
- }));
244
+ if (!this._devMode) {
245
+ const hash = Bun.hash(cacheKey).toString(16);
246
+ const cacheFile = path.join(this._cacheDir, `${hash}.json`);
247
+ fs.writeFileSync(cacheFile, JSON.stringify({
248
+ html: htmlContent,
249
+ assets: currentBuildAssets,
250
+ }));
251
+ }
188
252
  return htmlContent;
189
253
  }
190
254
  catch (error) {
@@ -2,10 +2,12 @@ import { type Request, type Response, type NextFunction } from "express";
2
2
  import { type HelmetOptions } from "helmet";
3
3
  import cors from "cors";
4
4
  import { type Options as RateLimitOptions } from "express-rate-limit";
5
+ import { UserHandler } from "./UserHandler";
5
6
  export declare class Server {
6
7
  private readonly _app;
7
8
  private readonly _port;
8
9
  private readonly _controllersDir;
10
+ private readonly _viewsDir?;
9
11
  readonly _id: string;
10
12
  private readonly _enableSwagger;
11
13
  private readonly _swaggerPath;
@@ -14,20 +16,24 @@ export declare class Server {
14
16
  private readonly _ajv;
15
17
  private readonly _securityHandlers;
16
18
  private readonly _container?;
19
+ private readonly _roleHandler?;
20
+ private _userHandler?;
17
21
  private _serverInstance?;
22
+ private _io?;
18
23
  private _controllerCount;
19
24
  private _routeCount;
20
25
  private _tailwindEnabled;
21
26
  private _devMode;
22
- private _sseClients;
23
27
  constructor(options: ServerOptions);
24
28
  private setupHmr;
25
29
  private log;
26
30
  init(): Promise<void>;
27
31
  private printStartupMessage;
28
32
  close(): void;
33
+ setUserHandler(handler: UserHandler): void;
34
+ private setupAuthRoutes;
29
35
  private loadControllers;
30
- private createJwtMiddleware;
36
+ private createRoleMiddleware;
31
37
  private createAuthMiddleware;
32
38
  private createValidationMiddleware;
33
39
  private setupSwagger;
@@ -39,7 +45,7 @@ export interface ServerOptions {
39
45
  logging?: boolean;
40
46
  id: string;
41
47
  controllersDir?: string;
42
- devMode?: boolean;
48
+ viewsDir?: string;
43
49
  corsOptions?: cors.CorsOptions;
44
50
  helmetOptions?: HelmetOptions;
45
51
  rateLimitOptions?: Partial<RateLimitOptions>;
@@ -48,5 +54,6 @@ export interface ServerOptions {
48
54
  container?: {
49
55
  get: (target: any) => any;
50
56
  };
57
+ roleHandler?: (req: Request, roles: string[]) => boolean | Promise<boolean>;
51
58
  }
52
59
  //# sourceMappingURL=Server.d.ts.map
@@ -1 +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;AAwB5B,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;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAkB;gBAEzB,OAAO,EAAE,aAAa;IAuClC,OAAO,CAAC,QAAQ;IAyBhB,OAAO,CAAC,GAAG;IAME,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqClC,OAAO,CAAC,mBAAmB;IAsCpB,KAAK,IAAI,IAAI;YASN,eAAe;IAkQ7B,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,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,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"}
1
+ {"version":3,"file":"Server.d.ts","sourceRoot":"","sources":["../../src/Components/Server.ts"],"names":[],"mappings":"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"}