vatts 1.0.1 → 1.0.2-alpha.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.
package/dist/router.js CHANGED
@@ -39,113 +39,116 @@ const url_1 = require("url");
39
39
  const console_1 = __importDefault(require("./api/console"));
40
40
  const factory_1 = require("./adapters/factory");
41
41
  const http_1 = require("./api/http");
42
- // --- Roteamento do Frontend ---
43
- // Guarda todas as rotas de PÁGINA (React) encontradas
44
- // A rota agora também armazena o caminho do arquivo para ser usado como um ID único no cliente
42
+ // --- Estado Global ---
45
43
  let allRoutes = [];
46
- // Guarda o layout se existir
44
+ let allBackendRoutes = [];
45
+ let allWebSocketRoutes = [];
46
+ // Cache de arquivos para Hot Reload
47
+ const loadedFiles = new Set();
48
+ // Componentes Especiais
47
49
  let layoutComponent = null;
48
- // Guarda o componente 404 personalizado se existir
49
50
  let notFoundComponent = null;
50
- // Cache de arquivos carregados para limpeza
51
- let loadedRouteFiles = new Set();
52
- let loadedLayoutFiles = new Set();
53
- let loadedNotFoundFiles = new Set();
54
- /**
55
- * Limpa o cache do require para um arquivo específico
56
- * @param filePath Caminho do arquivo para limpar
57
- */
58
- function clearRequireCache(filePath) {
51
+ // Conexões ativas
52
+ let wsConnections = new Set();
53
+ // --- Helpers de Regex ---
54
+ // Re-implementação da compilação usando Named Groups para segurança e facilidade (como no original, mas cached)
55
+ function compileRoutePatternWithGroups(pattern) {
56
+ const regexPattern = pattern
57
+ .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
58
+ .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
59
+ .replace(/\/\[\[(\w+)\]\]/g, '(?:/(?<$1>[^/]+))?')
60
+ .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
61
+ .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
62
+ return new RegExp(`^${regexPattern}/?$`);
63
+ }
64
+ // --- Gerenciamento de Cache ---
65
+ function safeClearCache(filePath) {
59
66
  try {
60
- const resolvedPath = require.resolve(filePath);
61
- delete require.cache[resolvedPath];
62
- // Também limpa arquivos temporários relacionados (apenas se existir no cache)
63
- const tempFile = filePath.replace(/\.(tsx|ts|jsx|js)$/, '.temp.$1');
64
- const tempResolvedPath = require.cache[require.resolve(tempFile)];
65
- if (tempResolvedPath) {
66
- delete require.cache[require.resolve(tempFile)];
67
+ // Tenta deletar direto pela chave do caminho absoluto (mais rápido)
68
+ if (require.cache[filePath]) {
69
+ delete require.cache[filePath];
70
+ return;
71
+ }
72
+ // Fallback: resolve o caminho (tem custo de I/O)
73
+ const resolved = require.resolve(filePath);
74
+ if (require.cache[resolved]) {
75
+ delete require.cache[resolved];
67
76
  }
68
77
  }
69
- catch {
70
- // Arquivo pode não estar no cache ou não ser resolvível
78
+ catch (e) {
79
+ // Ignora erro se arquivo não for resolvível
71
80
  }
72
81
  }
73
- // Nota: Suporte apenas para TypeScript (.ts, .tsx). Não tratamos .jsx aqui.
74
- /**
75
- * Limpa todo o cache de rotas carregadas
76
- */
77
82
  function clearAllRouteCache() {
78
- // Limpa cache das rotas
79
- loadedRouteFiles.forEach(filePath => {
80
- clearRequireCache(filePath);
81
- });
82
- loadedRouteFiles.clear();
83
- // Limpa cache do layout
84
- loadedLayoutFiles.forEach(filePath => {
85
- clearRequireCache(filePath);
86
- });
87
- loadedLayoutFiles.clear();
88
- // Limpa cache do notFound
89
- loadedNotFoundFiles.forEach(filePath => {
90
- clearRequireCache(filePath);
91
- });
92
- loadedNotFoundFiles.clear();
83
+ loadedFiles.forEach(file => safeClearCache(file));
84
+ loadedFiles.clear();
93
85
  }
94
- /**
95
- * Limpa o cache de um arquivo específico e recarrega as rotas se necessário
96
- * @param changedFilePath Caminho do arquivo que foi alterado
97
- */
98
86
  function clearFileCache(changedFilePath) {
99
- const absolutePath = path_1.default.isAbsolute(changedFilePath) ? changedFilePath : path_1.default.resolve(changedFilePath);
100
- // Limpa o cache do arquivo específico
101
- clearRequireCache(absolutePath);
102
- // Remove das listas de arquivos carregados
103
- loadedRouteFiles.delete(absolutePath);
104
- loadedLayoutFiles.delete(absolutePath);
105
- loadedNotFoundFiles.delete(absolutePath);
87
+ const absolutePath = path_1.default.resolve(changedFilePath);
88
+ // tenta limpar se realmente já foi carregado (evita I/O desnecessário)
89
+ if (loadedFiles.has(absolutePath)) {
90
+ safeClearCache(absolutePath);
91
+ loadedFiles.delete(absolutePath);
92
+ }
106
93
  }
107
94
  /**
108
- * Carrega o layout.tsx se existir no diretório web
109
- * @param webDir O diretório web onde procurar o layout
110
- * @returns O layout carregado ou null se não existir
95
+ * Hook para ignorar importações de CSS/SCSS/SASS durante o require do servidor.
96
+ * Isso evita a necessidade de processar arquivos pesados que não afetam a rota.
111
97
  */
98
+ function requireWithoutStyles(modulePath) {
99
+ const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
100
+ const originalHandlers = {};
101
+ // Salva handlers originais e substitui por no-op
102
+ extensions.forEach(ext => {
103
+ originalHandlers[ext] = require.extensions[ext];
104
+ require.extensions[ext] = (m, filename) => {
105
+ // Retorna módulo vazio para imports de estilo/assets
106
+ // @ts-ignore
107
+ m.exports = {};
108
+ };
109
+ });
110
+ try {
111
+ // Não chamamos safeClearCache aqui explicitamente; quem chama loadRoutes já deve ter gerenciado o ciclo
112
+ return require(modulePath);
113
+ }
114
+ finally {
115
+ // Restaura handlers originais
116
+ extensions.forEach(ext => {
117
+ if (originalHandlers[ext]) {
118
+ require.extensions[ext] = originalHandlers[ext];
119
+ }
120
+ else {
121
+ delete require.extensions[ext];
122
+ }
123
+ });
124
+ }
125
+ }
126
+ // --- Carregamento de Layout (Otimizado - Sem I/O de Disco) ---
112
127
  function loadLayout(webDir) {
113
- const layoutPath = path_1.default.join(webDir, 'layout.tsx');
114
- const layoutPathTs = path_1.default.join(webDir, 'layout.ts');
115
- const layoutFile = fs_1.default.existsSync(layoutPath) ? layoutPath :
116
- fs_1.default.existsSync(layoutPathTs) ? layoutPathTs : null;
128
+ const extensions = ['layout.tsx', 'layout.ts'];
129
+ let layoutFile = null;
130
+ for (const ext of extensions) {
131
+ const fullPath = path_1.default.join(webDir, ext);
132
+ if (fs_1.default.existsSync(fullPath)) {
133
+ layoutFile = fullPath;
134
+ break;
135
+ }
136
+ }
117
137
  if (layoutFile) {
118
138
  const absolutePath = path_1.default.resolve(layoutFile);
119
139
  const componentPath = path_1.default.relative(process.cwd(), layoutFile).replace(/\\/g, '/');
120
140
  try {
121
- // HACK: Cria uma versão temporária do layout SEM imports de CSS para carregar no servidor
122
- const layoutContent = fs_1.default.readFileSync(layoutFile, 'utf8');
123
- const tempContent = layoutContent
124
- .replace(/import\s+['"][^'"]*\.css['"];?/g, '// CSS import removido para servidor')
125
- .replace(/import\s+['"][^'"]*\.scss['"];?/g, '// SCSS import removido para servidor')
126
- .replace(/import\s+['"][^'"]*\.sass['"];?/g, '// SASS import removido para servidor');
127
- // Escreve um arquivo temporário .temp.tsx ou .temp.ts sem imports de CSS
128
- const ext = path_1.default.extname(layoutFile).toLowerCase();
129
- const tempFile = layoutFile.replace(/\.(tsx|ts)$/i, '.temp.$1');
130
- fs_1.default.writeFileSync(tempFile, tempContent, 'utf8');
131
- // Otimização: limpa cache apenas se existir
132
- try {
133
- const resolvedPath = require.resolve(tempFile);
134
- if (require.cache[resolvedPath]) {
135
- delete require.cache[resolvedPath];
136
- }
141
+ // Se carregamos antes, limpa o cache para garantir reload
142
+ if (loadedFiles.has(absolutePath)) {
143
+ safeClearCache(absolutePath);
137
144
  }
138
- catch { }
139
- const layoutModule = require(tempFile);
140
- // Remove o arquivo temporário
141
- try {
142
- fs_1.default.unlinkSync(tempFile);
143
- }
144
- catch { }
145
- const metadata = layoutModule.metadata || null;
146
- // Registra o arquivo como carregado
147
- loadedLayoutFiles.add(absolutePath);
148
- layoutComponent = { componentPath, metadata };
145
+ // OTIMIZAÇÃO: Carrega em memória ignorando CSS
146
+ const layoutModule = requireWithoutStyles(layoutFile);
147
+ loadedFiles.add(absolutePath);
148
+ layoutComponent = {
149
+ componentPath,
150
+ metadata: layoutModule.metadata || null
151
+ };
149
152
  return layoutComponent;
150
153
  }
151
154
  catch (error) {
@@ -157,322 +160,208 @@ function loadLayout(webDir) {
157
160
  layoutComponent = null;
158
161
  return null;
159
162
  }
160
- /**
161
- * Retorna o layout atual se carregado
162
- */
163
- function getLayout() {
164
- return layoutComponent;
165
- }
166
- /**
167
- * Carrega dinamicamente todas as rotas de frontend do diretório do usuário.
168
- * @param routesDir O diretório onde as rotas de página estão localizadas.
169
- * @returns A lista de rotas de página que foram carregadas.
170
- */
163
+ function getLayout() { return layoutComponent; }
164
+ // --- Carregamento de Rotas Frontend ---
171
165
  function loadRoutes(routesDir) {
172
166
  if (!fs_1.default.existsSync(routesDir)) {
173
- console_1.default.warn(`Frontend routes directory not found at ${routesDir}. No page will be loaded.`);
167
+ console_1.default.warn(`Frontend routes directory not found at ${routesDir}.`);
174
168
  allRoutes = [];
175
- return allRoutes;
169
+ return [];
176
170
  }
177
- // Otimização: usa função recursiva manual para evitar overhead do recursive: true
178
- const routeFiles = [];
179
- const scanDirectory = (dir, baseDir = '') => {
171
+ const loaded = [];
172
+ const cwdPath = process.cwd();
173
+ // Função recursiva otimizada
174
+ const scanAndLoad = (dir) => {
180
175
  const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
181
176
  for (const entry of entries) {
182
- const relativePath = baseDir ? path_1.default.join(baseDir, entry.name) : entry.name;
177
+ const name = entry.name;
178
+ // Skip arquivos ocultos ou de sistema para acelerar scan
179
+ if (name.startsWith('.') || name.startsWith('_'))
180
+ continue;
181
+ const fullPath = path_1.default.join(dir, name);
183
182
  if (entry.isDirectory()) {
184
- // Pula diretório backend inteiro
185
- if (entry.name === 'backend')
183
+ if (name === 'backend')
186
184
  continue;
187
- scanDirectory(path_1.default.join(dir, entry.name), relativePath);
185
+ scanAndLoad(fullPath);
188
186
  }
189
- else if (entry.isFile()) {
190
- // Filtra apenas arquivos .ts/.tsx
191
- if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
192
- routeFiles.push(relativePath);
187
+ else if (entry.isFile() && (name.endsWith('.tsx') || name.endsWith('.ts'))) {
188
+ try {
189
+ const absolutePath = path_1.default.resolve(fullPath);
190
+ // OTIMIZAÇÃO CRÍTICA: Só limpa cache se o arquivo já foi carregado antes.
191
+ // Isso evita chamadas caras de require.resolve() na inicialização do servidor.
192
+ if (loadedFiles.has(absolutePath)) {
193
+ safeClearCache(absolutePath);
194
+ }
195
+ // OTIMIZAÇÃO: Usa requireWithoutStyles também para rotas!
196
+ // Isso evita que o Node tente processar imports de CSS/Assets dentro das páginas durante o roteamento.
197
+ const routeModule = requireWithoutStyles(absolutePath);
198
+ const defaultConfig = routeModule.default;
199
+ if (defaultConfig?.pattern && defaultConfig?.component) {
200
+ const componentPath = path_1.default.relative(cwdPath, fullPath).replace(/\\/g, '/');
201
+ // OTIMIZAÇÃO: Pré-compila a regex aqui
202
+ const regex = compileRoutePatternWithGroups(defaultConfig.pattern);
203
+ loaded.push({
204
+ config: defaultConfig,
205
+ componentPath,
206
+ regex,
207
+ paramNames: [] // Usando named groups na regex, não precisamos mapear manual
208
+ });
209
+ loadedFiles.add(absolutePath);
210
+ }
211
+ }
212
+ catch (error) {
213
+ console_1.default.error(`Error loading route ${fullPath}:`, error);
193
214
  }
194
215
  }
195
216
  }
196
217
  };
197
- scanDirectory(routesDir);
198
- const loaded = [];
199
- const cwdPath = process.cwd();
200
- // Otimização: processa arquivos em lote
201
- for (const file of routeFiles) {
202
- const filePath = path_1.default.join(routesDir, file);
203
- const absolutePath = path_1.default.resolve(filePath);
204
- try {
205
- // Otimização: limpa cache apenas se já existir
206
- const resolvedPath = require.resolve(filePath);
207
- if (require.cache[resolvedPath]) {
208
- delete require.cache[resolvedPath];
209
- }
210
- const routeModule = require(filePath);
211
- if (routeModule.default?.pattern && routeModule.default?.component) {
212
- // Otimização: calcula componentPath apenas uma vez
213
- const componentPath = path_1.default.relative(cwdPath, filePath).replace(/\\/g, '/');
214
- loaded.push({ ...routeModule.default, componentPath });
215
- loadedRouteFiles.add(absolutePath);
216
- }
217
- }
218
- catch (error) {
219
- console_1.default.error(`Error loading page route ${filePath}:`, error);
220
- }
221
- }
218
+ scanAndLoad(routesDir);
219
+ // Atualiza variável global e retorna formato esperado pelo sistema (sem a regex exposta para não quebrar tipagem antiga se for estrita)
222
220
  allRoutes = loaded;
223
- return allRoutes;
221
+ return allRoutes.map(r => ({ ...r.config, componentPath: r.componentPath }));
224
222
  }
225
- /**
226
- * Encontra a rota de página correspondente para uma URL.
227
- * @param pathname O caminho da URL (ex: "/users/123").
228
- * @returns Um objeto com a rota e os parâmetros, ou null se não encontrar.
229
- */
230
223
  function findMatchingRoute(pathname) {
224
+ // OTIMIZAÇÃO: Loop usando regex pré-compilada. Muito mais rápido.
231
225
  for (const route of allRoutes) {
232
- if (!route.pattern)
233
- continue;
234
- const regexPattern = route.pattern
235
- // [[...param]] → opcional catch-all
236
- .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
237
- // [...param] → obrigatório catch-all
238
- .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
239
- // /[[param]] → opcional com barra também opcional
240
- .replace(/\/\[\[(\w+)\]\]/g, '(?:/(?<$1>[^/]+))?')
241
- // [[param]] → segmento opcional (sem barra anterior)
242
- .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
243
- // [param] → segmento obrigatório
244
- .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
245
- // permite / opcional no final
246
- const regex = new RegExp(`^${regexPattern}/?$`);
247
- const match = pathname.match(regex);
226
+ const match = pathname.match(route.regex);
248
227
  if (match) {
249
228
  return {
250
- route,
229
+ route: { ...route.config, componentPath: route.componentPath },
251
230
  params: match.groups || {}
252
231
  };
253
232
  }
254
233
  }
255
234
  return null;
256
235
  }
257
- // --- Roteamento do Backend ---
258
- // Guarda todas as rotas de API encontradas
259
- let allBackendRoutes = [];
260
- // Cache de middlewares carregados por diretório
261
- let loadedMiddlewares = new Map();
262
- /**
263
- * Carrega middlewares de um diretório específico
264
- * @param dir O diretório onde procurar por middleware.ts
265
- * @returns Array de middlewares encontrados
266
- */
267
- function loadMiddlewareFromDirectory(dir) {
268
- const middlewares = [];
269
- // Procura por middleware.ts, middleware.tsx
270
- const middlewarePath = path_1.default.join(dir, 'middleware.ts');
271
- const middlewarePathTsx = path_1.default.join(dir, 'middleware.tsx');
272
- const middlewareFile = fs_1.default.existsSync(middlewarePath) ? middlewarePath :
273
- fs_1.default.existsSync(middlewarePathTsx) ? middlewarePathTsx : null;
274
- if (middlewareFile) {
275
- try {
276
- const absolutePath = path_1.default.resolve(middlewareFile);
277
- clearRequireCache(absolutePath);
278
- const middlewareModule = require(middlewareFile);
279
- // Suporte para export default (função única) ou export { middleware1, middleware2 }
280
- if (typeof middlewareModule.default === 'function') {
281
- middlewares.push(middlewareModule.default);
282
- }
283
- else if (middlewareModule.default && Array.isArray(middlewareModule.default)) {
284
- middlewares.push(...middlewareModule.default);
285
- }
286
- else {
287
- // Procura por exports nomeados que sejam funções
288
- Object.keys(middlewareModule).forEach(key => {
289
- if (key !== 'default' && typeof middlewareModule[key] === 'function') {
290
- middlewares.push(middlewareModule[key]);
236
+ // --- Carregamento de Rotas Backend ---
237
+ // Cache de middlewares simples
238
+ const middlewareCache = new Map();
239
+ function getMiddlewaresForDir(dir) {
240
+ if (middlewareCache.has(dir))
241
+ return middlewareCache.get(dir);
242
+ const files = ['middleware.ts', 'middleware.tsx'];
243
+ let middlewares = [];
244
+ for (const file of files) {
245
+ const fullPath = path_1.default.join(dir, file);
246
+ if (fs_1.default.existsSync(fullPath)) {
247
+ try {
248
+ const absolutePath = path_1.default.resolve(fullPath);
249
+ if (loadedFiles.has(absolutePath)) {
250
+ safeClearCache(absolutePath);
251
+ }
252
+ // Middlewares backend não usam requireWithoutStyles pois não devem ter CSS,
253
+ // e podem precisar de outros módulos backend.
254
+ const mod = require(fullPath);
255
+ loadedFiles.add(absolutePath);
256
+ // Extração robusta de exports
257
+ if (typeof mod.default === 'function')
258
+ middlewares.push(mod.default);
259
+ else if (Array.isArray(mod.default))
260
+ middlewares.push(...mod.default);
261
+ Object.keys(mod).forEach(key => {
262
+ if (key !== 'default' && typeof mod[key] === 'function') {
263
+ middlewares.push(mod[key]);
291
264
  }
292
265
  });
266
+ // Só processa o primeiro arquivo de middleware encontrado na pasta
267
+ break;
268
+ }
269
+ catch (e) {
270
+ console_1.default.error(`Error loading middleware ${fullPath}`, e);
293
271
  }
294
- }
295
- catch (error) {
296
- console_1.default.error(`Error loading middleware ${middlewareFile}:`, error);
297
272
  }
298
273
  }
274
+ middlewareCache.set(dir, middlewares);
299
275
  return middlewares;
300
276
  }
301
- /**
302
- * Coleta middlewares do diretório específico da rota (não herda dos pais)
303
- * @param routeFilePath Caminho completo do arquivo de rota
304
- * @param backendRoutesDir Diretório raiz das rotas de backend
305
- * @returns Array com middlewares apenas do diretório da rota
306
- */
307
- function collectMiddlewaresForRoute(routeFilePath, backendRoutesDir) {
308
- const relativePath = path_1.default.relative(backendRoutesDir, routeFilePath);
309
- const routeDir = path_1.default.dirname(path_1.default.join(backendRoutesDir, relativePath));
310
- // Carrega middlewares APENAS do diretório específico da rota (não herda dos pais)
311
- if (!loadedMiddlewares.has(routeDir)) {
312
- const middlewares = loadMiddlewareFromDirectory(routeDir);
313
- loadedMiddlewares.set(routeDir, middlewares);
314
- }
315
- return loadedMiddlewares.get(routeDir) || [];
316
- }
317
- /**
318
- * Carrega dinamicamente todas as rotas de API do diretório de backend.
319
- * @param backendRoutesDir O diretório onde as rotas de API estão localizadas.
320
- */
321
277
  function loadBackendRoutes(backendRoutesDir) {
322
278
  if (!fs_1.default.existsSync(backendRoutesDir)) {
323
- // É opcional ter uma API, então não mostramos um aviso se a pasta não existir.
324
279
  allBackendRoutes = [];
325
280
  return;
326
281
  }
327
- // Limpa cache de middlewares para recarregar
328
- loadedMiddlewares.clear();
329
- // Otimização: usa função recursiva manual e coleta middlewares durante o scan
330
- const routeFiles = [];
331
- const middlewareFiles = new Map(); // dir -> filepath
332
- const scanDirectory = (dir, baseDir = '') => {
282
+ middlewareCache.clear(); // Limpa cache de middleware ao recarregar rotas
283
+ const loaded = [];
284
+ const scanAndLoadAPI = (dir) => {
333
285
  const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
286
+ // Primeiro carrega middleware deste diretório para cachear
287
+ getMiddlewaresForDir(dir);
334
288
  for (const entry of entries) {
335
- const relativePath = baseDir ? path_1.default.join(baseDir, entry.name) : entry.name;
289
+ const name = entry.name;
290
+ if (name.startsWith('.') || name.startsWith('_'))
291
+ continue;
292
+ const fullPath = path_1.default.join(dir, name);
336
293
  if (entry.isDirectory()) {
337
- scanDirectory(path_1.default.join(dir, entry.name), relativePath);
294
+ scanAndLoadAPI(fullPath);
338
295
  }
339
- else if (entry.isFile()) {
340
- const isSupported = entry.name.endsWith('.ts') || entry.name.endsWith('.tsx');
341
- if (!isSupported)
296
+ else if (entry.isFile() && (name.endsWith('.ts') || name.endsWith('.tsx'))) {
297
+ if (name.startsWith('middleware'))
342
298
  continue;
343
- // Identifica middlewares durante o scan
344
- if (entry.name.startsWith('middleware')) {
345
- const dirPath = path_1.default.dirname(path_1.default.join(backendRoutesDir, relativePath));
346
- middlewareFiles.set(dirPath, path_1.default.join(backendRoutesDir, relativePath));
347
- }
348
- else {
349
- routeFiles.push(relativePath);
350
- }
351
- }
352
- }
353
- };
354
- scanDirectory(backendRoutesDir);
355
- // Otimização: pré-carrega todos os middlewares em um único passe
356
- for (const [dirPath, middlewarePath] of middlewareFiles) {
357
- try {
358
- const resolvedPath = require.resolve(middlewarePath);
359
- if (require.cache[resolvedPath]) {
360
- delete require.cache[resolvedPath];
361
- }
362
- const middlewareModule = require(middlewarePath);
363
- const middlewares = [];
364
- if (typeof middlewareModule.default === 'function') {
365
- middlewares.push(middlewareModule.default);
366
- }
367
- else if (Array.isArray(middlewareModule.default)) {
368
- middlewares.push(...middlewareModule.default);
369
- }
370
- else {
371
- // Exports nomeados
372
- for (const key in middlewareModule) {
373
- if (key !== 'default' && typeof middlewareModule[key] === 'function') {
374
- middlewares.push(middlewareModule[key]);
299
+ try {
300
+ const absolutePath = path_1.default.resolve(fullPath);
301
+ if (loadedFiles.has(absolutePath)) {
302
+ safeClearCache(absolutePath);
375
303
  }
376
- }
377
- }
378
- if (middlewares.length > 0) {
379
- loadedMiddlewares.set(dirPath, middlewares);
380
- }
381
- }
382
- catch (error) {
383
- console_1.default.error(`Error loading middleware ${middlewarePath}:`, error);
384
- }
385
- }
386
- // Otimização: processa rotas com cache já limpo
387
- const loaded = [];
388
- for (const file of routeFiles) {
389
- const filePath = path_1.default.join(backendRoutesDir, file);
390
- try {
391
- // Otimização: limpa cache apenas se existir
392
- const resolvedPath = require.resolve(filePath);
393
- if (require.cache[resolvedPath]) {
394
- delete require.cache[resolvedPath];
395
- }
396
- const routeModule = require(filePath);
397
- if (routeModule.default?.pattern) {
398
- const routeConfig = { ...routeModule.default };
399
- // Se a rota NÃO tem middleware definido, usa os da pasta
400
- if (!routeConfig.hasOwnProperty('middleware')) {
401
- const routeDir = path_1.default.dirname(path_1.default.resolve(filePath));
402
- const folderMiddlewares = loadedMiddlewares.get(routeDir);
403
- if (folderMiddlewares && folderMiddlewares.length > 0) {
404
- routeConfig.middleware = folderMiddlewares;
304
+ const mod = require(fullPath);
305
+ loadedFiles.add(absolutePath);
306
+ const config = mod.default;
307
+ if (config?.pattern) {
308
+ // Aplica middleware automaticamente se não definido
309
+ if (!config.middleware) {
310
+ const dirMiddlewares = getMiddlewaresForDir(dir);
311
+ if (dirMiddlewares.length > 0) {
312
+ config.middleware = dirMiddlewares;
313
+ }
314
+ }
315
+ // OTIMIZAÇÃO: Pré-compila regex
316
+ loaded.push({
317
+ config,
318
+ regex: compileRoutePatternWithGroups(config.pattern),
319
+ paramNames: []
320
+ });
405
321
  }
406
322
  }
407
- loaded.push(routeConfig);
323
+ catch (e) {
324
+ console_1.default.error(`Error loading API route ${fullPath}`, e);
325
+ }
408
326
  }
409
327
  }
410
- catch (error) {
411
- console_1.default.error(`Error loading API route ${filePath}:`, error);
412
- }
413
- }
328
+ };
329
+ scanAndLoadAPI(backendRoutesDir);
414
330
  allBackendRoutes = loaded;
331
+ // Processa WebSockets após carregar rotas HTTP
332
+ processWebSocketRoutes();
415
333
  }
416
- /**
417
- * Encontra a rota de API correspondente para uma URL e método HTTP.
418
- * @param pathname O caminho da URL (ex: "/api/users/123").
419
- * @param method O método HTTP da requisição (GET, POST, etc.).
420
- * @returns Um objeto com a rota e os parâmetros, ou null se não encontrar.
421
- */
422
334
  function findMatchingBackendRoute(pathname, method) {
335
+ const methodUpper = method.toUpperCase();
423
336
  for (const route of allBackendRoutes) {
424
- // Verifica se a rota tem um handler para o método HTTP atual
425
- if (!route.pattern || !route[method.toUpperCase()])
337
+ // Verifica método antes de rodar regex (otimização barata)
338
+ // @ts-ignore
339
+ if (!route.config[methodUpper])
426
340
  continue;
427
- const regexPattern = route.pattern
428
- // [[...param]] → opcional catch-all
429
- .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
430
- // [...param] → obrigatório catch-all
431
- .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
432
- // [[param]] → segmento opcional
433
- .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
434
- // [param] → segmento obrigatório
435
- .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
436
- const regex = new RegExp(`^${regexPattern}/?$`);
437
- const match = pathname.match(regex);
341
+ const match = pathname.match(route.regex);
438
342
  if (match) {
439
343
  return {
440
- route,
344
+ route: route.config,
441
345
  params: match.groups || {}
442
346
  };
443
347
  }
444
348
  }
445
349
  return null;
446
350
  }
447
- /**
448
- * Carrega o notFound.tsx se existir no diretório web
449
- * @param webDir O diretório web onde procurar o notFound
450
- * @returns O notFound carregado ou null se não existir
451
- */
351
+ // --- 404 Not Found ---
452
352
  function loadNotFound(webDir) {
453
- const notFoundPath = path_1.default.join(webDir, 'notFound.tsx');
454
- const notFoundPathTs = path_1.default.join(webDir, 'notFound.ts');
455
- const notFoundFile = fs_1.default.existsSync(notFoundPath) ? notFoundPath :
456
- fs_1.default.existsSync(notFoundPathTs) ? notFoundPathTs : null;
457
- if (notFoundFile) {
458
- const absolutePath = path_1.default.resolve(notFoundFile);
459
- const componentPath = path_1.default.relative(process.cwd(), notFoundFile).replace(/\\/g, '/');
460
- try {
461
- // Otimização: limpa cache apenas se existir
462
- try {
463
- const resolvedPath = require.resolve(notFoundFile);
464
- if (require.cache[resolvedPath]) {
465
- delete require.cache[resolvedPath];
466
- }
353
+ const files = ['notFound.tsx', 'notFound.ts'];
354
+ for (const file of files) {
355
+ const fullPath = path_1.default.join(webDir, file);
356
+ if (fs_1.default.existsSync(fullPath)) {
357
+ const absolutePath = path_1.default.resolve(fullPath);
358
+ const componentPath = path_1.default.relative(process.cwd(), fullPath).replace(/\\/g, '/');
359
+ if (loadedFiles.has(absolutePath)) {
360
+ safeClearCache(absolutePath);
467
361
  }
468
- catch { }
469
- // Registra o arquivo como carregado
470
- loadedNotFoundFiles.add(absolutePath);
471
- notFoundComponent = { componentPath };
472
- return notFoundComponent;
473
- }
474
- catch (error) {
475
- console_1.default.error(`Error loading notFound ${notFoundFile}:`, error);
362
+ // Também usa requireWithoutStyles para o 404
363
+ requireWithoutStyles(absolutePath);
364
+ loadedFiles.add(absolutePath);
476
365
  notFoundComponent = { componentPath };
477
366
  return notFoundComponent;
478
367
  }
@@ -480,180 +369,105 @@ function loadNotFound(webDir) {
480
369
  notFoundComponent = null;
481
370
  return null;
482
371
  }
483
- /**
484
- * Retorna o componente 404 atual se carregado
485
- */
486
- function getNotFound() {
487
- return notFoundComponent;
488
- }
489
- // --- WebSocket Functions ---
490
- // Guarda todas as rotas WebSocket encontradas
491
- let allWebSocketRoutes = [];
492
- // Conexões WebSocket ativas
493
- let wsConnections = new Set();
494
- /**
495
- * Processa e registra rotas WebSocket encontradas nas rotas backend
496
- */
372
+ function getNotFound() { return notFoundComponent; }
373
+ // --- WebSocket (Mantendo lógica, otimizando lookup) ---
497
374
  function processWebSocketRoutes() {
498
- allWebSocketRoutes = [];
499
- for (const route of allBackendRoutes) {
500
- if (route.WS) {
501
- const wsRoute = {
502
- pattern: route.pattern,
503
- handler: route.WS,
504
- middleware: route.middleware
505
- };
506
- allWebSocketRoutes.push(wsRoute);
507
- }
508
- }
375
+ allWebSocketRoutes = allBackendRoutes
376
+ .filter(r => r.config.WS)
377
+ .map(r => ({
378
+ config: r.config,
379
+ regex: r.regex, // Reusa a regex já compilada da rota HTTP!
380
+ handler: r.config.WS,
381
+ middleware: r.config.middleware
382
+ }));
509
383
  }
510
- /**
511
- * Encontra a rota WebSocket correspondente para uma URL
512
- */
513
384
  function findMatchingWebSocketRoute(pathname) {
514
- for (const route of allWebSocketRoutes) {
515
- if (!route.pattern)
516
- continue;
517
- const regexPattern = route.pattern
518
- .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
519
- .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
520
- .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
521
- .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
522
- const regex = new RegExp(`^${regexPattern}/?$`);
523
- const match = pathname.match(regex);
385
+ for (const wsRoute of allWebSocketRoutes) {
386
+ const match = pathname.match(wsRoute.regex);
524
387
  if (match) {
525
388
  return {
526
- route,
389
+ route: {
390
+ pattern: wsRoute.config.pattern,
391
+ handler: wsRoute.handler,
392
+ middleware: wsRoute.middleware
393
+ },
527
394
  params: match.groups || {}
528
395
  };
529
396
  }
530
397
  }
531
398
  return null;
532
399
  }
533
- /**
534
- * Trata uma nova conexão WebSocket
535
- */
536
400
  function handleWebSocketConnection(ws, req, hwebReq) {
537
401
  if (!req.url)
538
402
  return;
539
403
  const url = new url_1.URL(req.url, `http://${req.headers.host}`);
540
- const pathname = url.pathname;
541
- const matchedRoute = findMatchingWebSocketRoute(pathname);
542
- if (!matchedRoute) {
404
+ const match = findMatchingWebSocketRoute(url.pathname);
405
+ if (!match) {
543
406
  ws.close(1000, 'Route not found');
544
407
  return;
545
408
  }
546
- const params = extractWebSocketParams(pathname, matchedRoute.route.pattern);
547
- const query = Object.fromEntries(url.searchParams.entries());
548
409
  const context = {
549
410
  vattsReq: hwebReq,
550
411
  ws,
551
412
  req,
552
413
  url,
553
- params,
554
- query,
414
+ params: match.params,
415
+ query: Object.fromEntries(url.searchParams.entries()),
555
416
  send: (data) => {
556
417
  if (ws.readyState === ws_1.WebSocket.OPEN) {
557
- const message = typeof data === 'string' ? data : JSON.stringify(data);
558
- ws.send(message);
418
+ ws.send(typeof data === 'string' ? data : JSON.stringify(data));
559
419
  }
560
420
  },
561
- close: (code, reason) => {
562
- ws.close(code || 1000, reason);
563
- },
421
+ close: (code, reason) => ws.close(code || 1000, reason),
564
422
  broadcast: (data, exclude) => {
565
- const message = typeof data === 'string' ? data : JSON.stringify(data);
423
+ const msg = typeof data === 'string' ? data : JSON.stringify(data);
566
424
  const excludeSet = new Set(exclude || []);
567
- wsConnections.forEach(connection => {
568
- if (connection.readyState === ws_1.WebSocket.OPEN && !excludeSet.has(connection)) {
569
- connection.send(message);
425
+ for (const conn of wsConnections) {
426
+ if (conn.readyState === ws_1.WebSocket.OPEN && !excludeSet.has(conn)) {
427
+ conn.send(msg);
570
428
  }
571
- });
429
+ }
572
430
  }
573
431
  };
574
432
  try {
575
- matchedRoute.route.handler(context);
433
+ match.route.handler(context);
576
434
  }
577
435
  catch (error) {
578
436
  console.error('Error in WebSocket handler:', error);
579
437
  ws.close(1011, 'Internal server error');
580
438
  }
581
439
  }
582
- /**
583
- * Extrai parâmetros da URL para WebSocket
584
- */
585
- function extractWebSocketParams(pathname, pattern) {
586
- const params = {};
587
- const regexPattern = pattern
588
- .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
589
- .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
590
- .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
591
- .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
592
- const regex = new RegExp(`^${regexPattern}/?$`);
593
- const match = pathname.match(regex);
594
- if (match && match.groups) {
595
- Object.assign(params, match.groups);
596
- }
597
- return params;
598
- }
599
- /**
600
- * Configura WebSocket upgrade no servidor HTTP existente
601
- * @param server Servidor HTTP (Express, Fastify ou Native)
602
- * @param hotReloadManager Instância do gerenciador de hot-reload para coordenação
603
- */
604
440
  function setupWebSocketUpgrade(server, hotReloadManager) {
605
- // NÃO remove listeners existentes para preservar hot-reload
606
- // Em vez disso, coordena com o sistema existente
607
- // Verifica se já existe um listener de upgrade
608
- const existingListeners = server.listeners('upgrade');
609
- // Se não há listeners, ou se o hot-reload ainda não foi configurado, adiciona o nosso
610
- if (existingListeners.length === 0) {
611
- server.on('upgrade', (request, socket, head) => {
612
- handleWebSocketUpgrade(request, socket, head, hotReloadManager);
613
- });
614
- }
615
- }
616
- function handleWebSocketUpgrade(request, socket, head, hotReloadManager) {
617
- const adapter = factory_1.FrameworkAdapterFactory.getCurrentAdapter();
618
- if (!adapter) {
619
- console.error('❌ Framework adapter not detected. Unable to process WebSocket upgrade.');
620
- socket.destroy();
441
+ if (server.listeners('upgrade').length > 0)
621
442
  return;
622
- }
623
- const genericReq = adapter.parseRequest(request);
624
- const hwebReq = new http_1.VattsRequest(genericReq);
625
- const { pathname } = new url_1.URL(request.url, `http://${request.headers.host}`);
626
- // Prioridade 1: Hot reload (sistema interno)
627
- if (pathname === '/hweb-hotreload/') {
628
- if (hotReloadManager) {
629
- hotReloadManager.handleUpgrade(request, socket, head);
630
- }
631
- else {
443
+ server.on('upgrade', (request, socket, head) => {
444
+ const adapter = factory_1.FrameworkAdapterFactory.getCurrentAdapter();
445
+ if (!adapter) {
632
446
  socket.destroy();
447
+ return;
633
448
  }
634
- return;
635
- }
636
- // Prioridade 2: Rotas WebSocket do usuário
637
- const matchedRoute = findMatchingWebSocketRoute(pathname);
638
- if (matchedRoute) {
639
- // Faz upgrade para WebSocket usando noServer
640
- const wss = new ws_1.WebSocketServer({
641
- noServer: true,
642
- perMessageDeflate: false, // Melhor performance
643
- maxPayload: 1024 * 1024 // Limite de 1MB
644
- });
645
- wss.handleUpgrade(request, socket, head, (ws) => {
646
- wsConnections.add(ws);
647
- ws.on('close', () => {
648
- wsConnections.delete(ws);
649
- });
650
- ws.on('error', (error) => {
651
- wsConnections.delete(ws);
449
+ const { pathname } = new url_1.URL(request.url, `http://${request.headers.host}`);
450
+ // Prioridade 1: Hot Reload
451
+ if (pathname === '/hweb-hotreload/') {
452
+ if (hotReloadManager)
453
+ hotReloadManager.handleUpgrade(request, socket, head);
454
+ else
455
+ socket.destroy();
456
+ return;
457
+ }
458
+ // Prioridade 2: Rotas App
459
+ const match = findMatchingWebSocketRoute(pathname);
460
+ if (match) {
461
+ const wss = new ws_1.WebSocketServer({ noServer: true, perMessageDeflate: false, maxPayload: 1024 * 1024 });
462
+ wss.handleUpgrade(request, socket, head, (ws) => {
463
+ wsConnections.add(ws);
464
+ ws.on('close', () => wsConnections.delete(ws));
465
+ ws.on('error', () => wsConnections.delete(ws));
466
+ const hwebReq = new http_1.VattsRequest(adapter.parseRequest(request));
467
+ handleWebSocketConnection(ws, request, hwebReq);
652
468
  });
653
- // Processa a conexão
654
- handleWebSocketConnection(ws, request, hwebReq);
655
- });
656
- return;
657
- }
658
- socket.destroy();
469
+ return;
470
+ }
471
+ socket.destroy();
472
+ });
659
473
  }