@synchjs/ewb 1.0.0 → 1.0.2

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.
@@ -9,10 +9,12 @@ import rateLimit from "express-rate-limit";
9
9
  import Ajv from "ajv";
10
10
  import addFormats from "ajv-formats";
11
11
  import { MetadataStorage } from "../Decorations";
12
- import * as jwt from "jsonwebtoken";
13
- import { AUTH_METADATA_KEY, PUBLIC_METADATA_KEY, } from "../Decorations/Authorized";
12
+ import { createServer } from "http";
13
+ import { Server as SocketServer } from "socket.io";
14
+ import { PUBLIC_METADATA_KEY, ROLES_METADATA_KEY, } from "../Decorations/Authorized";
14
15
  import { SECURITY_METADATA_KEY, } from "../Decorations/Security";
15
16
  import { TAILWIND_METADATA_KEY, } from "../Decorations/Tailwind";
17
+ import { SERVE_HTML_METADATA_KEY } from "../Decorations/Serve";
16
18
  import { ServeMemoryStore } from "./ServeMemoryStore";
17
19
  import boxen from "boxen";
18
20
  import pc from "picocolors";
@@ -20,6 +22,7 @@ export class Server {
20
22
  _app;
21
23
  _port;
22
24
  _controllersDir;
25
+ _viewsDir;
23
26
  _id;
24
27
  _enableSwagger;
25
28
  _swaggerPath;
@@ -28,11 +31,15 @@ export class Server {
28
31
  _ajv;
29
32
  _securityHandlers;
30
33
  _container;
34
+ _roleHandler;
35
+ _userHandler;
31
36
  _serverInstance;
37
+ _io;
32
38
  // Stats
33
39
  _controllerCount = 0;
34
40
  _routeCount = 0;
35
41
  _tailwindEnabled = false;
42
+ _devMode = false;
36
43
  constructor(options) {
37
44
  this._port = options.port;
38
45
  this._app = express();
@@ -42,28 +49,71 @@ export class Server {
42
49
  this._enableLogging =
43
50
  options.logging === undefined ? true : options.logging;
44
51
  this._controllersDir = options.controllersDir || "controllers";
52
+ this._viewsDir = options.viewsDir;
45
53
  this._securityHandlers = options.securityHandlers || {};
46
54
  this._securitySchemes = options.securitySchemes;
47
55
  this._container = options.container;
48
- // Initialize AJV
49
- this._ajv = new Ajv({ allErrors: true, strict: false });
56
+ this._roleHandler = options.roleHandler;
57
+ // Initialize AJV with strict mode for better security
58
+ this._ajv = new Ajv({
59
+ allErrors: true,
60
+ strict: true,
61
+ removeAdditional: true, // Automatically remove properties not in schema
62
+ });
50
63
  addFormats(this._ajv);
51
64
  // Security Middleware
52
- this._app.use(helmet(options.helmetOptions));
65
+ const helmetOptions = options.helmetOptions || {};
66
+ if (process.env.NODE_ENV !== "production") {
67
+ // Relax CSP for HMR in development
68
+ helmetOptions.contentSecurityPolicy = {
69
+ directives: {
70
+ defaultSrc: ["'self'"],
71
+ scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "blob:"],
72
+ connectSrc: ["'self'", "ws:", "wss:", "http:", "https:"],
73
+ styleSrc: ["'self'", "'unsafe-inline'"],
74
+ imgSrc: ["'self'", "data:", "blob:"],
75
+ frameSrc: ["'self'"],
76
+ },
77
+ };
78
+ }
79
+ this._app.use(helmet(helmetOptions));
53
80
  this._app.use(cors(options.corsOptions));
54
81
  this._app.use(rateLimit(options.rateLimitOptions || {
55
82
  windowMs: 15 * 60 * 1000,
56
83
  max: 100,
84
+ skip: (req) => ServeMemoryStore.instance.getAsset(req.path) !== undefined, // Don't rate limit static assets
57
85
  }));
58
- this._app.use(express.json());
86
+ this._app.use(express.json({ limit: "1mb" })); // Protection against large payloads
87
+ // Clear cache on startup
88
+ ServeMemoryStore.instance.clearCache();
89
+ this._devMode = process.env.NODE_ENV !== "production";
90
+ if (this._devMode) {
91
+ this.setupHmr();
92
+ }
59
93
  global.servers.set(this._id, this._app);
60
94
  }
95
+ setupHmr() {
96
+ ServeMemoryStore.instance.setDevMode(true);
97
+ ServeMemoryStore.instance.onRebuild((data) => {
98
+ if (data && data.html) {
99
+ this._io?.emit("rebuild", { html: data.html });
100
+ }
101
+ else {
102
+ this._io?.emit("reload");
103
+ }
104
+ });
105
+ // No redundant global watcher here, we rely on ServeMemoryStore's specific watchers
106
+ // which rebuild before notifying.
107
+ }
61
108
  log(message) {
62
109
  if (this._enableLogging) {
63
110
  console.log(message);
64
111
  }
65
112
  }
66
113
  async init() {
114
+ if (this._userHandler) {
115
+ this.setupAuthRoutes();
116
+ }
67
117
  await this.loadControllers();
68
118
  if (this._enableSwagger) {
69
119
  this.setupSwagger();
@@ -74,6 +124,11 @@ export class Server {
74
124
  return next();
75
125
  const asset = ServeMemoryStore.instance.getAsset(req.path);
76
126
  if (asset) {
127
+ if (this._devMode) {
128
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
129
+ res.setHeader("Pragma", "no-cache");
130
+ res.setHeader("Expires", "0");
131
+ }
77
132
  res.type(asset.type).send(Buffer.from(asset.content));
78
133
  return;
79
134
  }
@@ -81,13 +136,31 @@ export class Server {
81
136
  });
82
137
  // Global Error Handler
83
138
  this._app.use((err, req, res, next) => {
84
- console.error(`[${this._id}] Error:`, err);
139
+ const isProd = process.env.NODE_ENV === "production";
140
+ if (!isProd) {
141
+ console.error(`[${this._id}] Error:`, err);
142
+ }
85
143
  res.status(err.status || 500).json({
86
144
  error: "Internal Server Error",
87
- message: "An unexpected error occurred",
145
+ message: isProd
146
+ ? "An unexpected error occurred"
147
+ : err.message || "An unexpected error occurred",
148
+ // Mask stack trace in production
149
+ stack: isProd ? undefined : err.stack,
88
150
  });
89
151
  });
90
- this._serverInstance = this._app.listen(this._port, () => {
152
+ this._serverInstance = createServer(this._app);
153
+ if (this._devMode) {
154
+ this._io = new SocketServer(this._serverInstance, {
155
+ cors: {
156
+ origin: [/localhost/, /127\.0\.0\.1/], // Restrict HMR to local development origin
157
+ },
158
+ });
159
+ this._io.on("connection", (socket) => {
160
+ // Disconnected client log
161
+ });
162
+ }
163
+ this._serverInstance.listen(this._port, () => {
91
164
  if (this._enableLogging) {
92
165
  this.printStartupMessage();
93
166
  }
@@ -103,7 +176,11 @@ export class Server {
103
176
  `${pc.bold(pad("Controllers:"))}${this._controllerCount}`,
104
177
  `${pc.bold(pad("Routes:"))}${this._routeCount}`,
105
178
  `${pc.bold(pad("Tailwind:"))}${this._tailwindEnabled ? pc.blue("Enabled") : pc.dim("Disabled")}`,
179
+ `${pc.bold(pad("HMR:"))}${this._devMode ? pc.cyan("Active") : pc.dim("Inactive")}`,
106
180
  ];
181
+ if (this._viewsDir) {
182
+ lines.push(`${pc.bold(pad("Views:"))}${this._viewsDir}`);
183
+ }
107
184
  if (this._enableSwagger) {
108
185
  lines.push(`${pc.bold(pad("Swagger:"))}http://localhost:${this._port}${this._swaggerPath}`);
109
186
  }
@@ -124,6 +201,40 @@ export class Server {
124
201
  }
125
202
  global.servers.delete(this._id);
126
203
  }
204
+ setUserHandler(handler) {
205
+ this._userHandler = handler;
206
+ }
207
+ setupAuthRoutes() {
208
+ if (!this._userHandler)
209
+ return;
210
+ this._app.post("/auth/signin", async (req, res, next) => {
211
+ try {
212
+ const result = await this._userHandler.signin(req, res);
213
+ res.json(result);
214
+ }
215
+ catch (err) {
216
+ next(err);
217
+ }
218
+ });
219
+ this._app.post("/auth/signup", async (req, res, next) => {
220
+ try {
221
+ const result = await this._userHandler.signup(req, res);
222
+ res.json(result);
223
+ }
224
+ catch (err) {
225
+ next(err);
226
+ }
227
+ });
228
+ this._app.post("/auth/logout", async (req, res, next) => {
229
+ try {
230
+ const result = await this._userHandler.logout(req, res);
231
+ res.json(result);
232
+ }
233
+ catch (err) {
234
+ next(err);
235
+ }
236
+ });
237
+ }
127
238
  async loadControllers() {
128
239
  const absoluteControllersPath = path.resolve(process.cwd(), this._controllersDir);
129
240
  if (!fs.existsSync(absoluteControllersPath)) {
@@ -151,28 +262,18 @@ export class Server {
151
262
  const instance = this._container?.get
152
263
  ? this._container.get(controller.target)
153
264
  : new controller.target();
154
- // Check class level auth
155
- const classAuthInfo = Reflect.getMetadata(AUTH_METADATA_KEY, controller.target);
156
265
  for (const route of controller.routes) {
157
266
  const fullPath = (controller.path + "/" + route.path).replace(/\/+/g, "/");
158
267
  const middlewares = [];
159
- // Check method level metadata
160
- const methodAuthInfo = Reflect.getMetadata(AUTH_METADATA_KEY, controller.target.prototype, route.handlerName);
268
+ // Check ROLES metadata
269
+ const classRoles = Reflect.getMetadata(ROLES_METADATA_KEY, controller.target);
270
+ const methodRoles = Reflect.getMetadata(ROLES_METADATA_KEY, controller.target.prototype, route.handlerName);
271
+ const requiredRoles = methodRoles || classRoles;
161
272
  const isPublic = Reflect.getMetadata(PUBLIC_METADATA_KEY, controller.target.prototype, route.handlerName);
162
- // Determine if auth is required and which secret to use
163
- let authSecret;
164
- if (isPublic) {
165
- authSecret = undefined;
166
- }
167
- else if (methodAuthInfo) {
168
- authSecret = methodAuthInfo.secret || process.env.JWT_SECRET;
169
- }
170
- else if (classAuthInfo) {
171
- authSecret = classAuthInfo.secret || process.env.JWT_SECRET;
172
- }
173
- // 1. Auth Middleware (New Decorator Logic)
174
- if (authSecret) {
175
- middlewares.push(this.createJwtMiddleware(authSecret));
273
+ // 1. Auth & Role Middlewares
274
+ if (!isPublic) {
275
+ // If not public, we always run the role check (which might include authentication via UserHandler)
276
+ middlewares.push(this.createRoleMiddleware(requiredRoles || []));
176
277
  // Inject Swagger security definition automatically
177
278
  if (!route.swagger) {
178
279
  route.swagger = {
@@ -192,7 +293,8 @@ export class Server {
192
293
  route.swagger.security.push({ bearerAuth: [] });
193
294
  }
194
295
  }
195
- // 2. Generic Security Middleware (@Security, @OAuth, etc)
296
+ // 1.1 Role Middlewares - now handled above for non-public routes
297
+ // 2. Generic Security Middleware (@Security, @ApiKey, etc)
196
298
  const classGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target) || [];
197
299
  const methodGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target.prototype, route.handlerName) || [];
198
300
  let genericRequirements = [];
@@ -274,9 +376,17 @@ export class Server {
274
376
  middlewares.push(...route.middlewares);
275
377
  }
276
378
  // 3. Route Handler
277
- const handler = (req, res, next) => {
379
+ const handler = async (req, res, next) => {
278
380
  try {
279
- const result = instance[route.handlerName](req, res, next);
381
+ const user = this._userHandler
382
+ ? await this._userHandler.authenticate(req, res)
383
+ : req.user;
384
+ const result = instance[route.handlerName]({
385
+ req,
386
+ res,
387
+ user,
388
+ next,
389
+ });
280
390
  const handleResult = (val) => {
281
391
  if (val !== undefined && !res.headersSent) {
282
392
  if (typeof val === "string") {
@@ -300,25 +410,58 @@ export class Server {
300
410
  };
301
411
  this._app[route.method](fullPath, ...middlewares, handler);
302
412
  this._routeCount++;
413
+ // Pre-build if @Serve is used
414
+ const htmlPath = Reflect.getMetadata(SERVE_HTML_METADATA_KEY, controller.target.prototype, route.handlerName);
415
+ if (htmlPath) {
416
+ // Silent pre-build
417
+ await ServeMemoryStore.instance.buildAndCache(htmlPath, tailwindOptions);
418
+ }
303
419
  }
304
420
  }
305
421
  }
306
- createJwtMiddleware(secret) {
307
- return (req, res, next) => {
308
- const authHeader = req.headers.authorization;
309
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
310
- return res
311
- .status(401)
312
- .json({ error: "Unauthorized: Missing Bearer token" });
422
+ createRoleMiddleware(roles) {
423
+ return async (req, res, next) => {
424
+ const user = this._userHandler
425
+ ? await this._userHandler.authenticate(req, res)
426
+ : req.user;
427
+ if (!user) {
428
+ return res.status(401).json({ error: "Unauthorized: No user found" });
429
+ }
430
+ // If no specific roles required, but it's not public, just authentication is enough
431
+ if (roles.length === 0) {
432
+ return next();
433
+ }
434
+ if (!this._roleHandler) {
435
+ // Default role check behavior
436
+ const userRoles = Array.isArray(user.roles)
437
+ ? user.roles
438
+ : user.role
439
+ ? [user.role]
440
+ : [];
441
+ const hasRole = roles.some((role) => userRoles.includes(role));
442
+ if (!hasRole) {
443
+ return res.status(403).json({
444
+ error: "Forbidden: You do not have the required permissions",
445
+ });
446
+ }
447
+ return next();
313
448
  }
314
- const token = authHeader.split(" ")[1];
315
449
  try {
316
- const decoded = jwt.verify(token, secret);
317
- req.user = decoded; // Attach user to request
318
- next();
450
+ const result = await this._roleHandler(req, roles);
451
+ if (result) {
452
+ // Assuming 'result' is a boolean indicating success
453
+ next();
454
+ }
455
+ else {
456
+ // The provided snippet seems to be for serving HTML, which is out of context for a role middleware.
457
+ // Applying the original logic for role failure.
458
+ res.status(403).json({
459
+ error: "Forbidden: You do not have the required permissions",
460
+ });
461
+ }
319
462
  }
320
463
  catch (err) {
321
- return res.status(403).json({ error: "Forbidden: Invalid token" });
464
+ next(err);
322
465
  }
323
466
  };
324
467
  }
@@ -0,0 +1,21 @@
1
+ import { Request, Response } from "express";
2
+ export declare abstract class UserHandler {
3
+ /**
4
+ * Optional: Logic to authenticate the request and return the user object.
5
+ * If this is not implemented, 'user' will be undefined in route parameters.
6
+ */
7
+ authenticate(req: Request, res: Response): Promise<any | null>;
8
+ /**
9
+ * Sign in logic. Usually returns a token or user info.
10
+ */
11
+ abstract signin(req: Request, res: Response): Promise<any> | any;
12
+ /**
13
+ * Sign up logic.
14
+ */
15
+ abstract signup(req: Request, res: Response): Promise<any> | any;
16
+ /**
17
+ * Logout logic.
18
+ */
19
+ abstract logout(req: Request, res: Response): Promise<any> | any;
20
+ }
21
+ //# sourceMappingURL=UserHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserHandler.d.ts","sourceRoot":"","sources":["../../src/Components/UserHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAgB,MAAM,SAAS,CAAC;AAE1D,8BAAsB,WAAW;IAC/B;;;OAGG;IACU,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAI3E;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;IAEvE;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;IAEvE;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;CACxE"}
@@ -0,0 +1,9 @@
1
+ export class UserHandler {
2
+ /**
3
+ * Optional: Logic to authenticate the request and return the user object.
4
+ * If this is not implemented, 'user' will be undefined in route parameters.
5
+ */
6
+ async authenticate(req, res) {
7
+ return req.user;
8
+ }
9
+ }
@@ -1,8 +1,5 @@
1
- export declare const AUTH_METADATA_KEY = "auth:info";
2
1
  export declare const PUBLIC_METADATA_KEY = "auth:public";
3
- export interface AuthInfo {
4
- secret?: string;
5
- }
6
- export declare function BearerAuth(secret?: string): MethodDecorator & ClassDecorator;
2
+ export declare const ROLES_METADATA_KEY = "auth:roles";
3
+ export declare function Authorized(roles?: string[]): MethodDecorator & ClassDecorator;
7
4
  export declare function Public(): MethodDecorator;
8
5
  //# sourceMappingURL=Authorized.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Authorized.d.ts","sourceRoot":"","sources":["../../src/Decorations/Authorized.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,cAAc,CAmB5E;AAED,wBAAgB,MAAM,IAAI,eAAe,CAQxC"}
1
+ {"version":3,"file":"Authorized.d.ts","sourceRoot":"","sources":["../../src/Decorations/Authorized.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,wBAAgB,UAAU,CACxB,KAAK,GAAE,MAAM,EAAO,GACnB,eAAe,GAAG,cAAc,CAclC;AAED,wBAAgB,MAAM,IAAI,eAAe,CAQxC"}
@@ -1,15 +1,15 @@
1
1
  // Key to store auth metadata
2
- export const AUTH_METADATA_KEY = "auth:info";
3
2
  export const PUBLIC_METADATA_KEY = "auth:public";
4
- export function BearerAuth(secret) {
3
+ export const ROLES_METADATA_KEY = "auth:roles";
4
+ export function Authorized(roles = []) {
5
5
  return function (target, propertyKey, descriptor) {
6
- // If used on a method
7
6
  if (propertyKey) {
8
- Reflect.defineMetadata(AUTH_METADATA_KEY, { secret }, target, propertyKey);
7
+ Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target, propertyKey);
8
+ // Explicitly mark as not public to prevent bypass if @Public() is also used
9
+ Reflect.defineMetadata(PUBLIC_METADATA_KEY, false, target, propertyKey);
9
10
  }
10
11
  else {
11
- // If used on a class
12
- Reflect.defineMetadata(AUTH_METADATA_KEY, { secret }, target);
12
+ Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target);
13
13
  }
14
14
  };
15
15
  }
@@ -4,6 +4,5 @@ export interface SecurityRequirement {
4
4
  [name: string]: string[];
5
5
  }
6
6
  export declare function Security(name: string, scopes?: string[]): MethodDecorator & ClassDecorator;
7
- export declare function OAuth(scopes?: string[]): MethodDecorator & ClassDecorator;
8
7
  export declare function ApiKey(name: string): MethodDecorator & ClassDecorator;
9
8
  //# sourceMappingURL=Security.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Security.d.ts","sourceRoot":"","sources":["../../src/Decorations/Security.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,eAAO,MAAM,qBAAqB,eAAqB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,EAAO,GACpB,eAAe,GAAG,cAAc,CAyBlC;AAED,wBAAgB,KAAK,CAAC,MAAM,GAAE,MAAM,EAAO,GAAG,eAAe,GAAG,cAAc,CAE7E;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,cAAc,CAErE"}
1
+ {"version":3,"file":"Security.d.ts","sourceRoot":"","sources":["../../src/Decorations/Security.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,eAAO,MAAM,qBAAqB,eAAqB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,EAAO,GACpB,eAAe,GAAG,cAAc,CAyBlC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,cAAc,CAErE"}
@@ -16,9 +16,6 @@ export function Security(name, scopes = []) {
16
16
  }
17
17
  };
18
18
  }
19
- export function OAuth(scopes = []) {
20
- return Security("oauth2", scopes);
21
- }
22
19
  export function ApiKey(name) {
23
20
  return Security(name, []);
24
21
  }
@@ -1,2 +1,3 @@
1
+ export declare const SERVE_HTML_METADATA_KEY = "serve:html";
1
2
  export declare function Serve(htmlPath: string): MethodDecorator;
2
3
  //# sourceMappingURL=Serve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Serve.d.ts","sourceRoot":"","sources":["../../src/Decorations/Serve.ts"],"names":[],"mappings":"AAIA,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CA2CvD"}
1
+ {"version":3,"file":"Serve.d.ts","sourceRoot":"","sources":["../../src/Decorations/Serve.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAwEvD"}
@@ -1,12 +1,17 @@
1
1
  import { ServeMemoryStore } from "../Components/ServeMemoryStore";
2
2
  import { TAILWIND_METADATA_KEY } from "./Tailwind";
3
+ export const SERVE_HTML_METADATA_KEY = "serve:html";
3
4
  export function Serve(htmlPath) {
4
5
  return function (target, propertyKey, descriptor) {
6
+ // Store the HTML path in metadata for pre-building
7
+ Reflect.defineMetadata(SERVE_HTML_METADATA_KEY, htmlPath, target, propertyKey);
5
8
  const originalMethod = descriptor.value;
6
- descriptor.value = async function (req, res, next) {
9
+ descriptor.value = async function ({ req, res, user, next, }) {
7
10
  try {
8
11
  // Execute the original method
9
- const result = await originalMethod.apply(this, [req, res, next]);
12
+ const result = await originalMethod.apply(this, [
13
+ { req, res, user, next },
14
+ ]);
10
15
  // Check if response has been sent or if result is not undefined/void
11
16
  if (res.headersSent || result !== undefined) {
12
17
  return result;
@@ -16,14 +21,25 @@ export function Serve(htmlPath) {
16
21
  // If no response, build and serve the HTML
17
22
  const html = await ServeMemoryStore.instance.buildAndCache(htmlPath, tailwindOptions);
18
23
  if (html) {
24
+ const devMode = process.env.NODE_ENV === "development";
25
+ if (devMode) {
26
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
27
+ res.setHeader("Pragma", "no-cache");
28
+ res.setHeader("Expires", "0");
29
+ }
19
30
  res.type("html").send(html);
20
31
  }
21
- else {
32
+ else if (next) {
22
33
  next(); // No HTML found?
23
34
  }
24
35
  }
25
36
  catch (error) {
26
- next(error);
37
+ if (next) {
38
+ next(error);
39
+ }
40
+ else {
41
+ throw error;
42
+ }
27
43
  }
28
44
  };
29
45
  return descriptor;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import "./globals";
2
2
  export * from "./Components/Server";
3
3
  export * from "./Components/ServeMemoryStore";
4
+ export * from "./Components/UserHandler";
4
5
  export * from "./Decorations";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,CAAC;AACnB,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,CAAC;AACnB,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -2,4 +2,5 @@ global.servers = new Map();
2
2
  import "./globals";
3
3
  export * from "./Components/Server";
4
4
  export * from "./Components/ServeMemoryStore";
5
+ export * from "./Components/UserHandler";
5
6
  export * from "./Decorations";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synchjs/ewb",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -26,26 +26,29 @@
26
26
  "example": "bun run --watch example/index.ts"
27
27
  },
28
28
  "dependencies": {
29
+ "@types/cors": "^2.8.19",
30
+ "@types/express": "^5.0.6",
31
+ "@types/express-serve-static-core": "^5.1.1",
32
+ "@types/jsonwebtoken": "^9.0.10",
33
+ "@types/socket.io": "^3.0.2",
34
+ "@types/swagger-jsdoc": "^6.0.4",
35
+ "@types/swagger-ui-express": "^4.1.8",
29
36
  "ajv": "^8.18.0",
30
37
  "ajv-formats": "^3.0.1",
31
38
  "boxen": "^8.0.1",
32
39
  "bun-plugin-tailwind": "^0.1.2",
40
+ "cheerio": "^1.2.0",
33
41
  "cors": "^2.8.6",
34
42
  "express": "^5.2.1",
35
43
  "express-rate-limit": "^8.2.1",
36
44
  "helmet": "^8.1.0",
37
45
  "jsonwebtoken": "^9.0.3",
46
+ "openapi-types": "^12.1.3",
38
47
  "picocolors": "^1.1.1",
39
48
  "reflect-metadata": "^0.2.2",
49
+ "socket.io": "^4.8.3",
40
50
  "swagger-jsdoc": "^6.2.8",
41
51
  "swagger-ui-express": "^5.0.1",
42
- "tailwindcss": "^4.1.18",
43
- "openapi-types": "^12.1.3",
44
- "@types/express": "^5.0.6",
45
- "@types/cors": "^2.8.19",
46
- "@types/jsonwebtoken": "^9.0.10",
47
- "@types/swagger-jsdoc": "^6.0.4",
48
- "@types/swagger-ui-express": "^4.1.8",
49
- "@types/express-serve-static-core": "^5.1.1"
52
+ "tailwindcss": "^4.1.18"
50
53
  }
51
54
  }