hightjs 0.3.3 → 0.3.4

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/src/router.ts CHANGED
@@ -50,16 +50,14 @@ function clearRequireCache(filePath: string) {
50
50
  const resolvedPath = require.resolve(filePath);
51
51
  delete require.cache[resolvedPath];
52
52
 
53
- // Também limpa arquivos temporários relacionados
53
+ // Também limpa arquivos temporários relacionados (apenas se existir no cache)
54
54
  const tempFile = filePath.replace(/\.(tsx|ts)$/, '.temp.$1');
55
- try {
56
- const tempResolvedPath = require.resolve(tempFile);
57
- delete require.cache[tempResolvedPath];
58
- } catch {
59
- // Arquivo temporário pode não existir
55
+ const tempResolvedPath = require.cache[require.resolve(tempFile)];
56
+ if (tempResolvedPath) {
57
+ delete require.cache[require.resolve(tempFile)];
60
58
  }
61
- } catch (error) {
62
- // Arquivo pode não estar no cache
59
+ } catch {
60
+ // Arquivo pode não estar no cache ou não ser resolvível
63
61
  }
64
62
  }
65
63
 
@@ -115,13 +113,10 @@ export function loadLayout(webDir: string): { componentPath: string; metadata?:
115
113
  fs.existsSync(layoutPathJs) ? layoutPathJs : null;
116
114
 
117
115
  if (layoutFile) {
118
- const componentPath = path.relative(process.cwd(), layoutFile).replace(/\\/g, '/');
119
116
  const absolutePath = path.resolve(layoutFile);
117
+ const componentPath = path.relative(process.cwd(), layoutFile).replace(/\\/g, '/');
120
118
 
121
119
  try {
122
- // Limpa o cache antes de recarregar
123
- clearRequireCache(absolutePath);
124
-
125
120
  // HACK: Cria uma versão temporária do layout SEM imports de CSS para carregar no servidor
126
121
  const layoutContent = fs.readFileSync(layoutFile, 'utf8');
127
122
  const tempContent = layoutContent
@@ -132,8 +127,14 @@ export function loadLayout(webDir: string): { componentPath: string; metadata?:
132
127
  const tempFile = layoutFile.replace(/\.(tsx|ts)$/, '.temp.$1');
133
128
  fs.writeFileSync(tempFile, tempContent);
134
129
 
135
- // Carrega o arquivo temporário sem CSS
136
- delete require.cache[require.resolve(tempFile)];
130
+ // Otimização: limpa cache apenas se existir
131
+ try {
132
+ const resolvedPath = require.resolve(tempFile);
133
+ if (require.cache[resolvedPath]) {
134
+ delete require.cache[resolvedPath];
135
+ }
136
+ } catch {}
137
+
137
138
  const layoutModule = require(tempFile);
138
139
 
139
140
  // Remove o arquivo temporário
@@ -147,7 +148,7 @@ export function loadLayout(webDir: string): { componentPath: string; metadata?:
147
148
  layoutComponent = { componentPath, metadata };
148
149
  return layoutComponent;
149
150
  } catch (error) {
150
- Console.error(`Erro ao carregar layout ${layoutFile}:`, error);
151
+ Console.error(`Error loading layout ${layoutFile}:`, error);
151
152
  layoutComponent = { componentPath };
152
153
  return layoutComponent;
153
154
  }
@@ -171,41 +172,60 @@ export function getLayout(): { componentPath: string; metadata?: any } | null {
171
172
  */
172
173
  export function loadRoutes(routesDir: string): (RouteConfig & { componentPath: string })[] {
173
174
  if (!fs.existsSync(routesDir)) {
174
- Console.warn(`Diretório de rotas de frontend não encontrado em ${routesDir}. Nenhuma página será carregada.`);
175
+ Console.warn(`Frontend routes directory not found at ${routesDir}. No page will be loaded.`);
175
176
  allRoutes = [];
176
177
  return allRoutes;
177
178
  }
178
179
 
179
- const files = fs.readdirSync(routesDir, { recursive: true, encoding: 'utf-8' });
180
- // Corrigindo o filtro para excluir corretamente o diretório backend
181
- const routeFiles = files.filter(file => {
182
- const isTypeScriptFile = file.endsWith('.ts') || file.endsWith('.tsx');
183
- const isNotBackend = !file.includes('backend' + path.sep) && !file.includes('backend/');
184
- return isTypeScriptFile && isNotBackend;
185
- });
180
+ // Otimização: usa função recursiva manual para evitar overhead do recursive: true
181
+ const routeFiles: string[] = [];
182
+ const scanDirectory = (dir: string, baseDir: string = '') => {
183
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
184
+ for (const entry of entries) {
185
+ const relativePath = baseDir ? path.join(baseDir, entry.name) : entry.name;
186
+
187
+ if (entry.isDirectory()) {
188
+ // Pula diretório backend inteiro
189
+ if (entry.name === 'backend') continue;
190
+ scanDirectory(path.join(dir, entry.name), relativePath);
191
+ } else if (entry.isFile()) {
192
+ // Filtra apenas arquivos .ts/.tsx
193
+ if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
194
+ routeFiles.push(relativePath);
195
+ }
196
+ }
197
+ }
198
+ };
199
+
200
+ scanDirectory(routesDir);
186
201
 
187
202
  const loaded: (RouteConfig & { componentPath: string })[] = [];
203
+ const cwdPath = process.cwd();
204
+
205
+ // Otimização: processa arquivos em lote
188
206
  for (const file of routeFiles) {
189
207
  const filePath = path.join(routesDir, file);
190
208
  const absolutePath = path.resolve(filePath);
191
- // Usamos um caminho relativo ao CWD como um ID estável para o componente.
192
- const componentPath = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
193
209
 
194
210
  try {
195
- // Limpa o cache antes de recarregar para pegar alterações nos metadados
196
- clearRequireCache(absolutePath);
211
+ // Otimização: limpa cache apenas se existir
212
+ const resolvedPath = require.resolve(filePath);
213
+ if (require.cache[resolvedPath]) {
214
+ delete require.cache[resolvedPath];
215
+ }
197
216
 
198
217
  const routeModule = require(filePath);
199
- if (routeModule.default && routeModule.default.pattern && routeModule.default.component) {
218
+ if (routeModule.default?.pattern && routeModule.default?.component) {
219
+ // Otimização: calcula componentPath apenas uma vez
220
+ const componentPath = path.relative(cwdPath, filePath).replace(/\\/g, '/');
200
221
  loaded.push({ ...routeModule.default, componentPath });
201
-
202
- // Registra o arquivo como carregado
203
222
  loadedRouteFiles.add(absolutePath);
204
223
  }
205
224
  } catch (error) {
206
- Console.error(`Erro ao carregar a rota de página ${filePath}:`, error);
225
+ Console.error(`Error loading page route ${filePath}:`, error);
207
226
  }
208
227
  }
228
+
209
229
  allRoutes = loaded;
210
230
  return allRoutes;
211
231
  }
@@ -292,7 +312,7 @@ function loadMiddlewareFromDirectory(dir: string): HightMiddleware[] {
292
312
  }
293
313
 
294
314
  } catch (error) {
295
- Console.error(`Erro ao carregar middleware ${middlewareFile}:`, error);
315
+ Console.error(`Error loading middleware ${middlewareFile}:`, error);
296
316
  }
297
317
  }
298
318
 
@@ -332,36 +352,98 @@ export function loadBackendRoutes(backendRoutesDir: string) {
332
352
  // Limpa cache de middlewares para recarregar
333
353
  loadedMiddlewares.clear();
334
354
 
335
- const files = fs.readdirSync(backendRoutesDir, { recursive: true, encoding: 'utf-8' });
336
- const routeFiles = files.filter(file => {
337
- const isTypeScript = file.endsWith('.ts') || file.endsWith('.tsx');
338
- const isNotMiddleware = !path.basename(file).startsWith('middleware');
339
- return isTypeScript && isNotMiddleware;
340
- });
355
+ // Otimização: usa função recursiva manual e coleta middlewares durante o scan
356
+ const routeFiles: string[] = [];
357
+ const middlewareFiles: Map<string, string> = new Map(); // dir -> filepath
358
+
359
+ const scanDirectory = (dir: string, baseDir: string = '') => {
360
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
361
+
362
+ for (const entry of entries) {
363
+ const relativePath = baseDir ? path.join(baseDir, entry.name) : entry.name;
364
+
365
+ if (entry.isDirectory()) {
366
+ scanDirectory(path.join(dir, entry.name), relativePath);
367
+ } else if (entry.isFile()) {
368
+ const isTypeScript = entry.name.endsWith('.ts') || entry.name.endsWith('.tsx');
369
+ if (!isTypeScript) continue;
370
+
371
+ // Identifica middlewares durante o scan
372
+ if (entry.name.startsWith('middleware')) {
373
+ const dirPath = path.dirname(path.join(backendRoutesDir, relativePath));
374
+ middlewareFiles.set(dirPath, path.join(backendRoutesDir, relativePath));
375
+ } else {
376
+ routeFiles.push(relativePath);
377
+ }
378
+ }
379
+ }
380
+ };
341
381
 
382
+ scanDirectory(backendRoutesDir);
383
+
384
+ // Otimização: pré-carrega todos os middlewares em um único passe
385
+ for (const [dirPath, middlewarePath] of middlewareFiles) {
386
+ try {
387
+ const resolvedPath = require.resolve(middlewarePath);
388
+ if (require.cache[resolvedPath]) {
389
+ delete require.cache[resolvedPath];
390
+ }
391
+
392
+ const middlewareModule = require(middlewarePath);
393
+ const middlewares: HightMiddleware[] = [];
394
+
395
+ if (typeof middlewareModule.default === 'function') {
396
+ middlewares.push(middlewareModule.default);
397
+ } else if (Array.isArray(middlewareModule.default)) {
398
+ middlewares.push(...middlewareModule.default);
399
+ } else {
400
+ // Exports nomeados
401
+ for (const key in middlewareModule) {
402
+ if (key !== 'default' && typeof middlewareModule[key] === 'function') {
403
+ middlewares.push(middlewareModule[key]);
404
+ }
405
+ }
406
+ }
407
+
408
+ if (middlewares.length > 0) {
409
+ loadedMiddlewares.set(dirPath, middlewares);
410
+ }
411
+ } catch (error) {
412
+ Console.error(`Error loading middleware ${middlewarePath}:`, error);
413
+ }
414
+ }
415
+
416
+ // Otimização: processa rotas com cache já limpo
342
417
  const loaded: BackendRouteConfig[] = [];
343
418
  for (const file of routeFiles) {
344
419
  const filePath = path.join(backendRoutesDir, file);
420
+
345
421
  try {
422
+ // Otimização: limpa cache apenas se existir
423
+ const resolvedPath = require.resolve(filePath);
424
+ if (require.cache[resolvedPath]) {
425
+ delete require.cache[resolvedPath];
426
+ }
427
+
346
428
  const routeModule = require(filePath);
347
- if (routeModule.default && routeModule.default.pattern) {
429
+ if (routeModule.default?.pattern) {
348
430
  const routeConfig = { ...routeModule.default };
349
-
350
- // Se a rota NÃO tem a propriedade middleware definida, carrega os da pasta
431
+ // Se a rota NÃO tem middleware definido, usa os da pasta
351
432
  if (!routeConfig.hasOwnProperty('middleware')) {
352
- const folderMiddlewares = collectMiddlewaresForRoute(filePath, backendRoutesDir);
353
- if (folderMiddlewares.length > 0) {
433
+ const routeDir = path.dirname(path.resolve(filePath));
434
+ const folderMiddlewares = loadedMiddlewares.get(routeDir);
435
+ if (folderMiddlewares && folderMiddlewares.length > 0) {
354
436
  routeConfig.middleware = folderMiddlewares;
355
437
  }
356
438
  }
357
- // Se tem middleware definido (mesmo que seja []), usa só esses (não adiciona os da pasta)
358
439
 
359
440
  loaded.push(routeConfig);
360
441
  }
361
442
  } catch (error) {
362
- Console.error(`Erro ao carregar a rota de API ${filePath}:`, error);
443
+ Console.error(`Error loading API route ${filePath}:`, error);
363
444
  }
364
445
  }
446
+
365
447
  allBackendRoutes = loaded;
366
448
  }
367
449
 
@@ -411,12 +493,17 @@ export function loadNotFound(webDir: string): { componentPath: string } | null {
411
493
  fs.existsSync(notFoundPathJs) ? notFoundPathJs : null;
412
494
 
413
495
  if (notFoundFile) {
414
- const componentPath = path.relative(process.cwd(), notFoundFile).replace(/\\/g, '/');
415
496
  const absolutePath = path.resolve(notFoundFile);
497
+ const componentPath = path.relative(process.cwd(), notFoundFile).replace(/\\/g, '/');
416
498
 
417
499
  try {
418
- // Limpa o cache antes de recarregar
419
- clearRequireCache(absolutePath);
500
+ // Otimização: limpa cache apenas se existir
501
+ try {
502
+ const resolvedPath = require.resolve(notFoundFile);
503
+ if (require.cache[resolvedPath]) {
504
+ delete require.cache[resolvedPath];
505
+ }
506
+ } catch {}
420
507
 
421
508
  // Registra o arquivo como carregado
422
509
  loadedNotFoundFiles.add(absolutePath);
@@ -424,7 +511,7 @@ export function loadNotFound(webDir: string): { componentPath: string } | null {
424
511
  notFoundComponent = { componentPath };
425
512
  return notFoundComponent;
426
513
  } catch (error) {
427
- Console.error(`Erro ao carregar notFound ${notFoundFile}:`, error);
514
+ Console.error(`Error loading notFound ${notFoundFile}:`, error);
428
515
  notFoundComponent = { componentPath };
429
516
  return notFoundComponent;
430
517
  }
@@ -464,7 +551,6 @@ export function processWebSocketRoutes() {
464
551
  };
465
552
 
466
553
  allWebSocketRoutes.push(wsRoute);
467
- Console.info(`WebSocket route registered: ${route.pattern}`);
468
554
  }
469
555
  }
470
556
  }
@@ -506,7 +592,7 @@ function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage, hwebReq:
506
592
 
507
593
  const matchedRoute = findMatchingWebSocketRoute(pathname);
508
594
  if (!matchedRoute) {
509
- ws.close(1000, 'Rota não encontrada');
595
+ ws.close(1000, 'Route not found');
510
596
  return;
511
597
  }
512
598
 
@@ -543,8 +629,8 @@ function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage, hwebReq:
543
629
  try {
544
630
  matchedRoute.route.handler(context);
545
631
  } catch (error) {
546
- console.error('Erro no handler WebSocket:', error);
547
- ws.close(1011, 'Erro interno do servidor');
632
+ console.error('Error in WebSocket handler:', error);
633
+ ws.close(1011, 'Internal server error');
548
634
  }
549
635
  }
550
636
 
@@ -587,17 +673,13 @@ export function setupWebSocketUpgrade(server: any, hotReloadManager?: any) {
587
673
  server.on('upgrade', (request: any, socket: any, head: Buffer) => {
588
674
  handleWebSocketUpgrade(request, socket, head, hotReloadManager);
589
675
  });
590
- } else {
591
- // Se já existe um listener (provavelmente do hot-reload),
592
- // vamos interceptar e coordenar
593
- console.log('🔧 Coordenando WebSocket upgrade com sistema existente');
594
676
  }
595
677
  }
596
678
 
597
679
  function handleWebSocketUpgrade(request: any, socket: any, head: Buffer, hotReloadManager?: any) {
598
680
  const adapter = FrameworkAdapterFactory.getCurrentAdapter()
599
681
  if (!adapter) {
600
- console.error('❌ Framework adapter não detectado. Não é possível processar upgrade WebSocket.');
682
+ console.error('❌ Framework adapter not detected. Unable to process WebSocket upgrade.');
601
683
  socket.destroy();
602
684
  return;
603
685
  }
@@ -627,15 +709,12 @@ function handleWebSocketUpgrade(request: any, socket: any, head: Buffer, hotRelo
627
709
 
628
710
  wss.handleUpgrade(request, socket, head, (ws) => {
629
711
  wsConnections.add(ws);
630
- console.log(`✅ WebSocket conectado em ${pathname}`);
631
712
 
632
713
  ws.on('close', () => {
633
714
  wsConnections.delete(ws);
634
- console.log(`❌ WebSocket desconectado de ${pathname}`);
635
715
  });
636
716
 
637
717
  ws.on('error', (error) => {
638
- console.error(`💥 Erro WebSocket em ${pathname}:`, error);
639
718
  wsConnections.delete(ws);
640
719
  });
641
720
 
@@ -645,7 +724,5 @@ function handleWebSocketUpgrade(request: any, socket: any, head: Buffer, hotRelo
645
724
  return;
646
725
  }
647
726
 
648
- // Nenhuma rota encontrada - rejeita conexão
649
- console.log(`🚫 Nenhuma rota WebSocket encontrada para: ${pathname}`);
650
727
  socket.destroy();
651
728
  }
package/src/types.ts CHANGED
@@ -47,6 +47,58 @@ export interface HightJSOptions {
47
47
  ca?: string;
48
48
  };
49
49
  }
50
+
51
+ // --- Tipos de Configuração ---
52
+
53
+ /**
54
+ * Interface para as configurações avançadas do servidor HightJS.
55
+ * Essas configurações podem ser definidas no arquivo hightjs.config.js
56
+ */
57
+ export interface HightConfig {
58
+ /**
59
+ * Limita o número máximo de headers HTTP permitidos por requisição.
60
+ * Padrão: 100
61
+ */
62
+ maxHeadersCount?: number;
63
+
64
+ /**
65
+ * Timeout em milissegundos para receber os headers HTTP.
66
+ * Padrão: 60000 (60 segundos)
67
+ */
68
+ headersTimeout?: number;
69
+
70
+ /**
71
+ * Timeout em milissegundos para uma requisição completa.
72
+ * Padrão: 30000 (30 segundos)
73
+ */
74
+ requestTimeout?: number;
75
+
76
+ /**
77
+ * Timeout geral do servidor em milissegundos.
78
+ * Padrão: 35000 (35 segundos)
79
+ */
80
+ serverTimeout?: number;
81
+
82
+ /**
83
+ * Timeout por requisição individual em milissegundos.
84
+ * Padrão: 30000 (30 segundos)
85
+ */
86
+ individualRequestTimeout?: number;
87
+
88
+ /**
89
+ * Tamanho máximo permitido para a URL em caracteres.
90
+ * Padrão: 2048
91
+ */
92
+ maxUrlLength?: number;
93
+ }
94
+
95
+ /**
96
+ * Tipo da função de configuração que pode ser exportada no hightjs.config.js
97
+ */
98
+ export type HightConfigFunction = (
99
+ phase: string,
100
+ context: { defaultConfig: HightConfig }
101
+ ) => HightConfig | Promise<HightConfig>;
50
102
  export interface Metadata {
51
103
  title?: string;
52
104
  description?: string;
@@ -1,24 +0,0 @@
1
- // Arquivo gerado automaticamente pelo hweb
2
- import route0 from '../src/web/routes/index.tsx';
3
- import route1 from '../src/web/routes/login.tsx';
4
- import LayoutComponent from '../src/web/layout.tsx';
5
-
6
- import DefaultNotFound from '../node_modules/hightjs/src/client/DefaultNotFound.tsx';
7
-
8
- // Registra os componentes para o cliente
9
- window.__HWEB_COMPONENTS__ = {
10
- 'src/web/routes/index.tsx': route0.component || route0.default?.component,
11
- 'src/web/routes/login.tsx': route1.component || route1.default?.component,
12
- };
13
-
14
- // Registra o layout se existir
15
- window.__HWEB_LAYOUT__ = LayoutComponent.default || LayoutComponent;
16
-
17
- // Registra o notFound se existir
18
- window.__HWEB_NOT_FOUND__ = null;
19
-
20
- // Registra o DefaultNotFound do hweb
21
- window.__HWEB_DEFAULT_NOT_FOUND__ = DefaultNotFound;
22
-
23
- // Importa e executa o entry.client.tsx
24
- import '../node_modules/hightjs/src/client/entry.client.tsx';