@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.
@@ -1,27 +1,142 @@
1
1
  import tailwindPlugin from "bun-plugin-tailwind";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { load } from "cheerio";
2
5
  export class ServeMemoryStore {
3
6
  static _instance;
4
7
  _assets = new Map();
5
8
  _htmlCache = new Map();
6
- constructor() { }
9
+ _cacheDir = path.join(process.cwd(), ".ebw-cache");
10
+ _devMode = false;
11
+ _watchers = new Map();
12
+ _listeners = [];
13
+ constructor() {
14
+ this.ensureCacheDir();
15
+ }
16
+ ensureCacheDir() {
17
+ if (!fs.existsSync(this._cacheDir)) {
18
+ try {
19
+ fs.mkdirSync(this._cacheDir, { recursive: true });
20
+ }
21
+ catch (e) {
22
+ // Fallback if we can't create directory
23
+ }
24
+ }
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
+ }
7
42
  static get instance() {
8
43
  if (!ServeMemoryStore._instance) {
9
44
  ServeMemoryStore._instance = new ServeMemoryStore();
10
45
  }
11
46
  return ServeMemoryStore._instance;
12
47
  }
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);
48
+ setDevMode(enabled) {
49
+ this._devMode = enabled;
17
50
  }
18
- async buildAndCache(htmlPath, options = { enable: false, plugins: [] }) {
19
- const cacheKey = htmlPath +
51
+ onRebuild(listener) {
52
+ this._listeners.push(listener);
53
+ }
54
+ notify(data) {
55
+ this._listeners.forEach((l) => l(data));
56
+ }
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);
63
+ }
64
+ getCacheKey(htmlPath, options) {
65
+ return (htmlPath +
20
66
  (options.enable ? ":tw" : "") +
21
- (options.plugins ? ":" + options.plugins.length : "");
22
- if (this._htmlCache.has(cacheKey)) {
67
+ (options.plugins ? ":" + options.plugins.length : ""));
68
+ }
69
+ async loadFromDisk(cacheKey) {
70
+ if (this._devMode)
71
+ return null; // Skip disk cache in dev mode
72
+ const hash = Bun.hash(cacheKey).toString(16);
73
+ const cacheFile = path.join(this._cacheDir, `${hash}.json`);
74
+ if (fs.existsSync(cacheFile)) {
75
+ try {
76
+ const data = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
77
+ this._htmlCache.set(cacheKey, data.html);
78
+ for (const [webPath, asset] of Object.entries(data.assets)) {
79
+ this._assets.set(webPath, {
80
+ type: asset.type,
81
+ content: Buffer.from(asset.content, "base64"),
82
+ });
83
+ }
84
+ return data.html;
85
+ }
86
+ catch (e) {
87
+ console.error("Error loading cache from disk:", e);
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ setupWatcher(htmlPath, options) {
93
+ const cacheKey = this.getCacheKey(htmlPath, options);
94
+ if (this._watchers.has(cacheKey))
95
+ return;
96
+ const absolutePath = path.resolve(process.cwd(), htmlPath);
97
+ const watchDir = path.dirname(absolutePath);
98
+ const watcher = fs.watch(watchDir, { recursive: true }, async (event, filename) => {
99
+ if (!filename)
100
+ return;
101
+ // Ignore common noise
102
+ if (filename.includes("node_modules") ||
103
+ filename.includes(".git") ||
104
+ filename.includes(".ebw-cache"))
105
+ return;
106
+ // Silent detect
107
+ // Clear cache for this entry
108
+ this._htmlCache.delete(cacheKey);
109
+ // Clear disk cache by deleting file
110
+ const hash = Bun.hash(cacheKey).toString(16);
111
+ const cacheFile = path.join(this._cacheDir, `${hash}.json`);
112
+ if (fs.existsSync(cacheFile)) {
113
+ fs.unlinkSync(cacheFile);
114
+ }
115
+ try {
116
+ const newHtml = await this.buildAndCache(htmlPath, options);
117
+ this.notify({ html: newHtml });
118
+ }
119
+ catch (e) {
120
+ console.error("[HMR] Rebuild failed:", e);
121
+ }
122
+ });
123
+ this._watchers.set(cacheKey, watcher);
124
+ }
125
+ async buildAndCache(htmlPath, options = { enable: false, plugins: [] }) {
126
+ const cacheKey = this.getCacheKey(htmlPath, options);
127
+ // 1. Check Memory Cache
128
+ if (!this._devMode && this._htmlCache.has(cacheKey)) {
23
129
  return this._htmlCache.get(cacheKey);
24
130
  }
131
+ // 2. Check Disk Cache
132
+ const diskHtml = await this.loadFromDisk(cacheKey);
133
+ if (diskHtml) {
134
+ if (this._devMode) {
135
+ this.setupWatcher(htmlPath, options);
136
+ }
137
+ return diskHtml;
138
+ }
139
+ // 3. Perform Build
25
140
  try {
26
141
  const plugins = [...(options.plugins || [])];
27
142
  if (options.enable) {
@@ -30,9 +145,9 @@ export class ServeMemoryStore {
30
145
  const build = await Bun.build({
31
146
  entrypoints: [htmlPath],
32
147
  target: "browser",
33
- minify: true,
34
- naming: "[name]-[hash].[ext]", // Ensure unique names
35
- publicPath: "/", // Assets served from root
148
+ minify: !this._devMode,
149
+ naming: "[name]-[hash].[ext]",
150
+ publicPath: "/",
36
151
  plugins: plugins,
37
152
  });
38
153
  if (!build.success) {
@@ -40,33 +155,100 @@ export class ServeMemoryStore {
40
155
  throw new Error("Build failed");
41
156
  }
42
157
  let htmlContent = "";
158
+ const currentBuildAssets = {};
43
159
  for (const output of build.outputs) {
44
160
  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.
161
+ const uint8 = new Uint8Array(content);
56
162
  const filename = output.path.split(/[/\\]/).pop();
57
163
  const webPath = "/" + filename;
58
164
  if (output.type === "text/html" || filename?.endsWith(".html")) {
59
- htmlContent = text;
165
+ htmlContent = await output.text();
60
166
  }
61
167
  else {
62
- let finalContent = new Uint8Array(content);
63
- this._assets.set(webPath, {
168
+ const asset = {
64
169
  type: output.type,
65
- content: finalContent,
66
- });
170
+ content: uint8,
171
+ };
172
+ this._assets.set(webPath, asset);
173
+ currentBuildAssets[webPath] = {
174
+ type: output.type,
175
+ content: Buffer.from(uint8).toString("base64"),
176
+ };
67
177
  }
68
178
  }
179
+ // Inject HMR/Reload script in dev mode
180
+ if (this._devMode) {
181
+ const reloadScript = `
182
+ <script src="/socket.io/socket.io.js"></script>
183
+ <script id="ebw-hmr-script">
184
+ (function() {
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.');
198
+ location.reload();
199
+ return;
200
+ }
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', () => {
224
+ console.warn('[HMR] Connection lost. Attempting to reconnect...');
225
+ });
226
+ })();
227
+ </script>
228
+ `;
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);
235
+ }
236
+ else {
237
+ $.root().append(reloadScript);
238
+ }
239
+ htmlContent = $.html();
240
+ this.setupWatcher(htmlPath, options);
241
+ }
242
+ // 4. Save to Memory and Disk
69
243
  this._htmlCache.set(cacheKey, htmlContent);
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
+ }
70
252
  return htmlContent;
71
253
  }
72
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,17 +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;
26
+ private _devMode;
21
27
  constructor(options: ServerOptions);
28
+ private setupHmr;
22
29
  private log;
23
30
  init(): Promise<void>;
24
31
  private printStartupMessage;
25
32
  close(): void;
33
+ setUserHandler(handler: UserHandler): void;
34
+ private setupAuthRoutes;
26
35
  private loadControllers;
27
- private createJwtMiddleware;
36
+ private createRoleMiddleware;
28
37
  private createAuthMiddleware;
29
38
  private createValidationMiddleware;
30
39
  private setupSwagger;
@@ -36,6 +45,7 @@ export interface ServerOptions {
36
45
  logging?: boolean;
37
46
  id: string;
38
47
  controllersDir?: string;
48
+ viewsDir?: string;
39
49
  corsOptions?: cors.CorsOptions;
40
50
  helmetOptions?: HelmetOptions;
41
51
  rateLimitOptions?: Partial<RateLimitOptions>;
@@ -44,5 +54,6 @@ export interface ServerOptions {
44
54
  container?: {
45
55
  get: (target: any) => any;
46
56
  };
57
+ roleHandler?: (req: Request, roles: string[]) => boolean | Promise<boolean>;
47
58
  }
48
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;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"}
1
+ {"version":3,"file":"Server.d.ts","sourceRoot":"","sources":["../../src/Components/Server.ts"],"names":[],"mappings":"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"}