hightjs 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -116
- package/dist/bin/hightjs.js +51 -23
- package/dist/builder.js +139 -4
- package/dist/client/DefaultNotFound.d.ts +1 -1
- package/dist/client/DefaultNotFound.js +72 -46
- package/dist/client/client.d.ts +3 -0
- package/dist/{client.js → client/client.js} +4 -4
- package/dist/client/entry.client.js +39 -29
- package/dist/global/global.d.ts +117 -0
- package/dist/{auth/types.js → global/global.js} +0 -1
- package/dist/helpers.js +80 -2
- package/dist/hotReload.js +2 -2
- package/dist/index.js +16 -16
- package/dist/loaders.d.ts +1 -0
- package/dist/loaders.js +46 -0
- package/dist/renderer.js +158 -4
- package/dist/types.d.ts +44 -0
- package/package.json +36 -31
- package/.idea/HightJS.iml +0 -9
- package/.idea/copilot.data.migration.agent.xml +0 -6
- package/.idea/copilot.data.migration.ask.xml +0 -6
- package/.idea/copilot.data.migration.ask2agent.xml +0 -6
- package/.idea/copilot.data.migration.edit.xml +0 -6
- package/.idea/copilotDiffState.xml +0 -67
- package/.idea/inspectionProfiles/Project_Default.xml +0 -13
- package/.idea/libraries/test_package.xml +0 -9
- package/.idea/libraries/ts_commonjs_default_export.xml +0 -9
- package/.idea/misc.xml +0 -7
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/dist/auth/client.d.ts +0 -24
- package/dist/auth/client.js +0 -146
- package/dist/auth/components.d.ts +0 -29
- package/dist/auth/components.js +0 -100
- package/dist/auth/core.d.ts +0 -55
- package/dist/auth/core.js +0 -189
- package/dist/auth/index.d.ts +0 -7
- package/dist/auth/index.js +0 -45
- package/dist/auth/jwt.d.ts +0 -41
- package/dist/auth/jwt.js +0 -185
- package/dist/auth/providers/credentials.d.ts +0 -60
- package/dist/auth/providers/credentials.js +0 -97
- package/dist/auth/providers/discord.d.ts +0 -63
- package/dist/auth/providers/discord.js +0 -190
- package/dist/auth/providers/google.d.ts +0 -63
- package/dist/auth/providers/google.js +0 -186
- package/dist/auth/providers/index.d.ts +0 -2
- package/dist/auth/providers/index.js +0 -35
- package/dist/auth/providers.d.ts +0 -3
- package/dist/auth/providers.js +0 -26
- package/dist/auth/react/index.d.ts +0 -6
- package/dist/auth/react/index.js +0 -48
- package/dist/auth/react.d.ts +0 -22
- package/dist/auth/react.js +0 -199
- package/dist/auth/routes.d.ts +0 -16
- package/dist/auth/routes.js +0 -152
- package/dist/auth/types.d.ts +0 -76
- package/dist/client.d.ts +0 -3
- package/docs/README.md +0 -58
- package/docs/arquivos-especiais.md +0 -10
- package/docs/autenticacao.md +0 -212
- package/docs/checklist.md +0 -9
- package/docs/cli.md +0 -72
- package/docs/config.md +0 -216
- package/docs/estrutura.md +0 -20
- package/docs/faq.md +0 -10
- package/docs/hot-reload.md +0 -5
- package/docs/integracoes.md +0 -240
- package/docs/middlewares.md +0 -73
- package/docs/rotas-backend.md +0 -45
- package/docs/rotas-frontend.md +0 -66
- package/docs/seguranca.md +0 -8
- package/docs/websocket.md +0 -45
- package/example/certs/cert.pem +0 -20
- package/example/certs/key.pem +0 -27
- package/example/hightjs.config.ts +0 -87
- package/example/package-lock.json +0 -1174
- package/example/package.json +0 -26
- package/example/postcss.config.js +0 -8
- package/example/src/backend/auth.ts +0 -42
- package/example/src/backend/routes/auth.ts +0 -3
- package/example/src/backend/routes/version.ts +0 -13
- package/example/src/web/components/Home.tsx +0 -140
- package/example/src/web/components/LoginPage.tsx +0 -149
- package/example/src/web/globals.css +0 -5
- package/example/src/web/layout.tsx +0 -100
- package/example/src/web/routes/index.tsx +0 -13
- package/example/src/web/routes/login.tsx +0 -30
- package/example/tailwind.config.js +0 -12
- package/example/tsconfig.json +0 -15
- package/src/adapters/express.ts +0 -87
- package/src/adapters/factory.ts +0 -112
- package/src/adapters/fastify.ts +0 -104
- package/src/adapters/native.ts +0 -234
- package/src/api/console.ts +0 -305
- package/src/api/http.ts +0 -535
- package/src/auth/client.ts +0 -171
- package/src/auth/components.tsx +0 -125
- package/src/auth/core.ts +0 -215
- package/src/auth/index.ts +0 -25
- package/src/auth/jwt.ts +0 -210
- package/src/auth/providers/credentials.ts +0 -139
- package/src/auth/providers/discord.ts +0 -239
- package/src/auth/providers/google.ts +0 -234
- package/src/auth/providers/index.ts +0 -20
- package/src/auth/providers.ts +0 -20
- package/src/auth/react/index.ts +0 -25
- package/src/auth/react.tsx +0 -234
- package/src/auth/routes.ts +0 -183
- package/src/auth/types.ts +0 -108
- package/src/bin/hightjs.js +0 -222
- package/src/builder.js +0 -472
- package/src/client/DefaultNotFound.tsx +0 -84
- package/src/client/clientRouter.ts +0 -153
- package/src/client/entry.client.tsx +0 -511
- package/src/client.ts +0 -24
- package/src/components/Link.tsx +0 -38
- package/src/helpers.ts +0 -542
- package/src/hotReload.ts +0 -569
- package/src/index.ts +0 -555
- package/src/renderer.tsx +0 -263
- package/src/router.ts +0 -730
- package/src/types/framework.ts +0 -58
- package/src/types.ts +0 -207
- package/tsconfig.json +0 -17
package/src/router.ts
DELETED
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of the HightJS Project.
|
|
3
|
-
* Copyright (c) 2025 itsmuzin
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import fs from 'fs';
|
|
18
|
-
import path from 'path';
|
|
19
|
-
import { RouteConfig, BackendRouteConfig, HightMiddleware, WebSocketHandler, WebSocketContext } from './types';
|
|
20
|
-
import { WebSocketServer as WSServer, WebSocket } from 'ws';
|
|
21
|
-
import { IncomingMessage } from 'http';
|
|
22
|
-
import { URL } from 'url';
|
|
23
|
-
import Console from "./api/console"
|
|
24
|
-
import {FrameworkAdapterFactory} from "./adapters/factory";
|
|
25
|
-
import {HightJSRequest} from "./api/http";
|
|
26
|
-
|
|
27
|
-
// --- Roteamento do Frontend ---
|
|
28
|
-
|
|
29
|
-
// Guarda todas as rotas de PÁGINA (React) encontradas
|
|
30
|
-
// A rota agora também armazena o caminho do arquivo para ser usado como um ID único no cliente
|
|
31
|
-
let allRoutes: (RouteConfig & { componentPath: string })[] = [];
|
|
32
|
-
|
|
33
|
-
// Guarda o layout se existir
|
|
34
|
-
let layoutComponent: { componentPath: string; metadata?: any } | null = null;
|
|
35
|
-
|
|
36
|
-
// Guarda o componente 404 personalizado se existir
|
|
37
|
-
let notFoundComponent: { componentPath: string } | null = null;
|
|
38
|
-
|
|
39
|
-
// Cache de arquivos carregados para limpeza
|
|
40
|
-
let loadedRouteFiles: Set<string> = new Set();
|
|
41
|
-
let loadedLayoutFiles: Set<string> = new Set();
|
|
42
|
-
let loadedNotFoundFiles: Set<string> = new Set();
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Limpa o cache do require para um arquivo específico
|
|
46
|
-
* @param filePath Caminho do arquivo para limpar
|
|
47
|
-
*/
|
|
48
|
-
function clearRequireCache(filePath: string) {
|
|
49
|
-
try {
|
|
50
|
-
const resolvedPath = require.resolve(filePath);
|
|
51
|
-
delete require.cache[resolvedPath];
|
|
52
|
-
|
|
53
|
-
// Também limpa arquivos temporários relacionados (apenas se existir no cache)
|
|
54
|
-
const tempFile = filePath.replace(/\.(tsx|ts)$/, '.temp.$1');
|
|
55
|
-
const tempResolvedPath = require.cache[require.resolve(tempFile)];
|
|
56
|
-
if (tempResolvedPath) {
|
|
57
|
-
delete require.cache[require.resolve(tempFile)];
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// Arquivo pode não estar no cache ou não ser resolvível
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Limpa todo o cache de rotas carregadas
|
|
66
|
-
*/
|
|
67
|
-
export function clearAllRouteCache() {
|
|
68
|
-
// Limpa cache das rotas
|
|
69
|
-
loadedRouteFiles.forEach(filePath => {
|
|
70
|
-
clearRequireCache(filePath);
|
|
71
|
-
});
|
|
72
|
-
loadedRouteFiles.clear();
|
|
73
|
-
|
|
74
|
-
// Limpa cache do layout
|
|
75
|
-
loadedLayoutFiles.forEach(filePath => {
|
|
76
|
-
clearRequireCache(filePath);
|
|
77
|
-
});
|
|
78
|
-
loadedLayoutFiles.clear();
|
|
79
|
-
|
|
80
|
-
// Limpa cache do notFound
|
|
81
|
-
loadedNotFoundFiles.forEach(filePath => {
|
|
82
|
-
clearRequireCache(filePath);
|
|
83
|
-
});
|
|
84
|
-
loadedNotFoundFiles.clear();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Limpa o cache de um arquivo específico e recarrega as rotas se necessário
|
|
89
|
-
* @param changedFilePath Caminho do arquivo que foi alterado
|
|
90
|
-
*/
|
|
91
|
-
export function clearFileCache(changedFilePath: string) {
|
|
92
|
-
const absolutePath = path.isAbsolute(changedFilePath) ? changedFilePath : path.resolve(changedFilePath);
|
|
93
|
-
|
|
94
|
-
// Limpa o cache do arquivo específico
|
|
95
|
-
clearRequireCache(absolutePath);
|
|
96
|
-
|
|
97
|
-
// Remove das listas de arquivos carregados
|
|
98
|
-
loadedRouteFiles.delete(absolutePath);
|
|
99
|
-
loadedLayoutFiles.delete(absolutePath);
|
|
100
|
-
loadedNotFoundFiles.delete(absolutePath);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Carrega o layout.tsx se existir no diretório web
|
|
105
|
-
* @param webDir O diretório web onde procurar o layout
|
|
106
|
-
* @returns O layout carregado ou null se não existir
|
|
107
|
-
*/
|
|
108
|
-
export function loadLayout(webDir: string): { componentPath: string; metadata?: any } | null {
|
|
109
|
-
const layoutPath = path.join(webDir, 'layout.tsx');
|
|
110
|
-
const layoutPathJs = path.join(webDir, 'layout.ts');
|
|
111
|
-
|
|
112
|
-
const layoutFile = fs.existsSync(layoutPath) ? layoutPath :
|
|
113
|
-
fs.existsSync(layoutPathJs) ? layoutPathJs : null;
|
|
114
|
-
|
|
115
|
-
if (layoutFile) {
|
|
116
|
-
const absolutePath = path.resolve(layoutFile);
|
|
117
|
-
const componentPath = path.relative(process.cwd(), layoutFile).replace(/\\/g, '/');
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
// HACK: Cria uma versão temporária do layout SEM imports de CSS para carregar no servidor
|
|
121
|
-
const layoutContent = fs.readFileSync(layoutFile, 'utf8');
|
|
122
|
-
const tempContent = layoutContent
|
|
123
|
-
.replace(/import\s+['"][^'"]*\.css['"];?/g, '// CSS import removido para servidor')
|
|
124
|
-
.replace(/import\s+['"][^'"]*\.scss['"];?/g, '// SCSS import removido para servidor')
|
|
125
|
-
.replace(/import\s+['"][^'"]*\.sass['"];?/g, '// SASS import removido para servidor');
|
|
126
|
-
|
|
127
|
-
const tempFile = layoutFile.replace(/\.(tsx|ts)$/, '.temp.$1');
|
|
128
|
-
fs.writeFileSync(tempFile, tempContent);
|
|
129
|
-
|
|
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
|
-
|
|
138
|
-
const layoutModule = require(tempFile);
|
|
139
|
-
|
|
140
|
-
// Remove o arquivo temporário
|
|
141
|
-
fs.unlinkSync(tempFile);
|
|
142
|
-
|
|
143
|
-
const metadata = layoutModule.metadata || null;
|
|
144
|
-
|
|
145
|
-
// Registra o arquivo como carregado
|
|
146
|
-
loadedLayoutFiles.add(absolutePath);
|
|
147
|
-
|
|
148
|
-
layoutComponent = { componentPath, metadata };
|
|
149
|
-
return layoutComponent;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
Console.error(`Error loading layout ${layoutFile}:`, error);
|
|
152
|
-
layoutComponent = { componentPath };
|
|
153
|
-
return layoutComponent;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
layoutComponent = null;
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Retorna o layout atual se carregado
|
|
163
|
-
*/
|
|
164
|
-
export function getLayout(): { componentPath: string; metadata?: any } | null {
|
|
165
|
-
return layoutComponent;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Carrega dinamicamente todas as rotas de frontend do diretório do usuário.
|
|
170
|
-
* @param routesDir O diretório onde as rotas de página estão localizadas.
|
|
171
|
-
* @returns A lista de rotas de página que foram carregadas.
|
|
172
|
-
*/
|
|
173
|
-
export function loadRoutes(routesDir: string): (RouteConfig & { componentPath: string })[] {
|
|
174
|
-
if (!fs.existsSync(routesDir)) {
|
|
175
|
-
Console.warn(`Frontend routes directory not found at ${routesDir}. No page will be loaded.`);
|
|
176
|
-
allRoutes = [];
|
|
177
|
-
return allRoutes;
|
|
178
|
-
}
|
|
179
|
-
|
|
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);
|
|
201
|
-
|
|
202
|
-
const loaded: (RouteConfig & { componentPath: string })[] = [];
|
|
203
|
-
const cwdPath = process.cwd();
|
|
204
|
-
|
|
205
|
-
// Otimização: processa arquivos em lote
|
|
206
|
-
for (const file of routeFiles) {
|
|
207
|
-
const filePath = path.join(routesDir, file);
|
|
208
|
-
const absolutePath = path.resolve(filePath);
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
// Otimização: limpa cache apenas se já existir
|
|
212
|
-
const resolvedPath = require.resolve(filePath);
|
|
213
|
-
if (require.cache[resolvedPath]) {
|
|
214
|
-
delete require.cache[resolvedPath];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const routeModule = require(filePath);
|
|
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, '/');
|
|
221
|
-
loaded.push({ ...routeModule.default, componentPath });
|
|
222
|
-
loadedRouteFiles.add(absolutePath);
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
Console.error(`Error loading page route ${filePath}:`, error);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
allRoutes = loaded;
|
|
230
|
-
return allRoutes;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Encontra a rota de página correspondente para uma URL.
|
|
235
|
-
* @param pathname O caminho da URL (ex: "/users/123").
|
|
236
|
-
* @returns Um objeto com a rota e os parâmetros, ou null se não encontrar.
|
|
237
|
-
*/
|
|
238
|
-
export function findMatchingRoute(pathname: string) {
|
|
239
|
-
for (const route of allRoutes) {
|
|
240
|
-
if (!route.pattern) continue;
|
|
241
|
-
|
|
242
|
-
const regexPattern = route.pattern
|
|
243
|
-
// [[...param]] → opcional catch-all
|
|
244
|
-
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
245
|
-
// [...param] → obrigatório catch-all
|
|
246
|
-
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
247
|
-
// /[[param]] → opcional com barra também opcional
|
|
248
|
-
.replace(/\/\[\[(\w+)\]\]/g, '(?:/(?<$1>[^/]+))?')
|
|
249
|
-
// [[param]] → segmento opcional (sem barra anterior)
|
|
250
|
-
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
251
|
-
// [param] → segmento obrigatório
|
|
252
|
-
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
253
|
-
|
|
254
|
-
// permite / opcional no final
|
|
255
|
-
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
256
|
-
const match = pathname.match(regex);
|
|
257
|
-
|
|
258
|
-
if (match) {
|
|
259
|
-
return {
|
|
260
|
-
route,
|
|
261
|
-
params: match.groups || {}
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// --- Roteamento do Backend ---
|
|
271
|
-
|
|
272
|
-
// Guarda todas as rotas de API encontradas
|
|
273
|
-
let allBackendRoutes: BackendRouteConfig[] = [];
|
|
274
|
-
|
|
275
|
-
// Cache de middlewares carregados por diretório
|
|
276
|
-
let loadedMiddlewares: Map<string, HightMiddleware[]> = new Map();
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Carrega middlewares de um diretório específico
|
|
280
|
-
* @param dir O diretório onde procurar por middleware.ts
|
|
281
|
-
* @returns Array de middlewares encontrados
|
|
282
|
-
*/
|
|
283
|
-
function loadMiddlewareFromDirectory(dir: string): HightMiddleware[] {
|
|
284
|
-
const middlewares: HightMiddleware[] = [];
|
|
285
|
-
|
|
286
|
-
// Procura por middleware.ts ou middleware.tsx
|
|
287
|
-
const middlewarePath = path.join(dir, 'middleware.ts');
|
|
288
|
-
const middlewarePathTsx = path.join(dir, 'middleware.tsx');
|
|
289
|
-
|
|
290
|
-
const middlewareFile = fs.existsSync(middlewarePath) ? middlewarePath :
|
|
291
|
-
fs.existsSync(middlewarePathTsx) ? middlewarePathTsx : null;
|
|
292
|
-
|
|
293
|
-
if (middlewareFile) {
|
|
294
|
-
try {
|
|
295
|
-
const absolutePath = path.resolve(middlewareFile);
|
|
296
|
-
clearRequireCache(absolutePath);
|
|
297
|
-
|
|
298
|
-
const middlewareModule = require(middlewareFile);
|
|
299
|
-
|
|
300
|
-
// Suporte para export default (função única) ou export { middleware1, middleware2 }
|
|
301
|
-
if (typeof middlewareModule.default === 'function') {
|
|
302
|
-
middlewares.push(middlewareModule.default);
|
|
303
|
-
} else if (middlewareModule.default && Array.isArray(middlewareModule.default)) {
|
|
304
|
-
middlewares.push(...middlewareModule.default);
|
|
305
|
-
} else {
|
|
306
|
-
// Procura por exports nomeados que sejam funções
|
|
307
|
-
Object.keys(middlewareModule).forEach(key => {
|
|
308
|
-
if (key !== 'default' && typeof middlewareModule[key] === 'function') {
|
|
309
|
-
middlewares.push(middlewareModule[key]);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
} catch (error) {
|
|
315
|
-
Console.error(`Error loading middleware ${middlewareFile}:`, error);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return middlewares;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Coleta middlewares do diretório específico da rota (não herda dos pais)
|
|
324
|
-
* @param routeFilePath Caminho completo do arquivo de rota
|
|
325
|
-
* @param backendRoutesDir Diretório raiz das rotas de backend
|
|
326
|
-
* @returns Array com middlewares apenas do diretório da rota
|
|
327
|
-
*/
|
|
328
|
-
function collectMiddlewaresForRoute(routeFilePath: string, backendRoutesDir: string): HightMiddleware[] {
|
|
329
|
-
const relativePath = path.relative(backendRoutesDir, routeFilePath);
|
|
330
|
-
const routeDir = path.dirname(path.join(backendRoutesDir, relativePath));
|
|
331
|
-
|
|
332
|
-
// Carrega middlewares APENAS do diretório específico da rota (não herda dos pais)
|
|
333
|
-
if (!loadedMiddlewares.has(routeDir)) {
|
|
334
|
-
const middlewares = loadMiddlewareFromDirectory(routeDir);
|
|
335
|
-
loadedMiddlewares.set(routeDir, middlewares);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return loadedMiddlewares.get(routeDir) || [];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Carrega dinamicamente todas as rotas de API do diretório de backend.
|
|
343
|
-
* @param backendRoutesDir O diretório onde as rotas de API estão localizadas.
|
|
344
|
-
*/
|
|
345
|
-
export function loadBackendRoutes(backendRoutesDir: string) {
|
|
346
|
-
if (!fs.existsSync(backendRoutesDir)) {
|
|
347
|
-
// É opcional ter uma API, então não mostramos um aviso se a pasta não existir.
|
|
348
|
-
allBackendRoutes = [];
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Limpa cache de middlewares para recarregar
|
|
353
|
-
loadedMiddlewares.clear();
|
|
354
|
-
|
|
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
|
-
};
|
|
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
|
|
417
|
-
const loaded: BackendRouteConfig[] = [];
|
|
418
|
-
for (const file of routeFiles) {
|
|
419
|
-
const filePath = path.join(backendRoutesDir, file);
|
|
420
|
-
|
|
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
|
-
|
|
428
|
-
const routeModule = require(filePath);
|
|
429
|
-
if (routeModule.default?.pattern) {
|
|
430
|
-
const routeConfig = { ...routeModule.default };
|
|
431
|
-
// Se a rota NÃO tem middleware definido, usa os da pasta
|
|
432
|
-
if (!routeConfig.hasOwnProperty('middleware')) {
|
|
433
|
-
const routeDir = path.dirname(path.resolve(filePath));
|
|
434
|
-
const folderMiddlewares = loadedMiddlewares.get(routeDir);
|
|
435
|
-
if (folderMiddlewares && folderMiddlewares.length > 0) {
|
|
436
|
-
routeConfig.middleware = folderMiddlewares;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
loaded.push(routeConfig);
|
|
441
|
-
}
|
|
442
|
-
} catch (error) {
|
|
443
|
-
Console.error(`Error loading API route ${filePath}:`, error);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
allBackendRoutes = loaded;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Encontra a rota de API correspondente para uma URL e método HTTP.
|
|
452
|
-
* @param pathname O caminho da URL (ex: "/api/users/123").
|
|
453
|
-
* @param method O método HTTP da requisição (GET, POST, etc.).
|
|
454
|
-
* @returns Um objeto com a rota e os parâmetros, ou null se não encontrar.
|
|
455
|
-
*/
|
|
456
|
-
export function findMatchingBackendRoute(pathname: string, method: string) {
|
|
457
|
-
for (const route of allBackendRoutes) {
|
|
458
|
-
// Verifica se a rota tem um handler para o método HTTP atual
|
|
459
|
-
if (!route.pattern || !route[method.toUpperCase() as keyof BackendRouteConfig]) continue;
|
|
460
|
-
const regexPattern = route.pattern
|
|
461
|
-
// [[...param]] → opcional catch-all
|
|
462
|
-
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
463
|
-
// [...param] → obrigatório catch-all
|
|
464
|
-
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
465
|
-
// [[param]] → segmento opcional
|
|
466
|
-
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
467
|
-
// [param] → segmento obrigatório
|
|
468
|
-
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
469
|
-
|
|
470
|
-
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
471
|
-
const match = pathname.match(regex);
|
|
472
|
-
|
|
473
|
-
if (match) {
|
|
474
|
-
return {
|
|
475
|
-
route,
|
|
476
|
-
params: match.groups || {}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Carrega o notFound.tsx se existir no diretório web
|
|
485
|
-
* @param webDir O diretório web onde procurar o notFound
|
|
486
|
-
* @returns O notFound carregado ou null se não existir
|
|
487
|
-
*/
|
|
488
|
-
export function loadNotFound(webDir: string): { componentPath: string } | null {
|
|
489
|
-
const notFoundPath = path.join(webDir, 'notFound.tsx');
|
|
490
|
-
const notFoundPathJs = path.join(webDir, 'notFound.ts');
|
|
491
|
-
|
|
492
|
-
const notFoundFile = fs.existsSync(notFoundPath) ? notFoundPath :
|
|
493
|
-
fs.existsSync(notFoundPathJs) ? notFoundPathJs : null;
|
|
494
|
-
|
|
495
|
-
if (notFoundFile) {
|
|
496
|
-
const absolutePath = path.resolve(notFoundFile);
|
|
497
|
-
const componentPath = path.relative(process.cwd(), notFoundFile).replace(/\\/g, '/');
|
|
498
|
-
|
|
499
|
-
try {
|
|
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 {}
|
|
507
|
-
|
|
508
|
-
// Registra o arquivo como carregado
|
|
509
|
-
loadedNotFoundFiles.add(absolutePath);
|
|
510
|
-
|
|
511
|
-
notFoundComponent = { componentPath };
|
|
512
|
-
return notFoundComponent;
|
|
513
|
-
} catch (error) {
|
|
514
|
-
Console.error(`Error loading notFound ${notFoundFile}:`, error);
|
|
515
|
-
notFoundComponent = { componentPath };
|
|
516
|
-
return notFoundComponent;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
notFoundComponent = null;
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Retorna o componente 404 atual se carregado
|
|
526
|
-
*/
|
|
527
|
-
export function getNotFound(): { componentPath: string } | null {
|
|
528
|
-
return notFoundComponent;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// --- WebSocket Functions ---
|
|
532
|
-
|
|
533
|
-
// Guarda todas as rotas WebSocket encontradas
|
|
534
|
-
let allWebSocketRoutes: { pattern: string; handler: WebSocketHandler; middleware?: HightMiddleware[] }[] = [];
|
|
535
|
-
|
|
536
|
-
// Conexões WebSocket ativas
|
|
537
|
-
let wsConnections: Set<WebSocket> = new Set();
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Processa e registra rotas WebSocket encontradas nas rotas backend
|
|
541
|
-
*/
|
|
542
|
-
export function processWebSocketRoutes() {
|
|
543
|
-
allWebSocketRoutes = [];
|
|
544
|
-
|
|
545
|
-
for (const route of allBackendRoutes) {
|
|
546
|
-
if (route.WS) {
|
|
547
|
-
const wsRoute = {
|
|
548
|
-
pattern: route.pattern,
|
|
549
|
-
handler: route.WS,
|
|
550
|
-
middleware: route.middleware
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
allWebSocketRoutes.push(wsRoute);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Encontra a rota WebSocket correspondente para uma URL
|
|
560
|
-
*/
|
|
561
|
-
export function findMatchingWebSocketRoute(pathname: string) {
|
|
562
|
-
for (const route of allWebSocketRoutes) {
|
|
563
|
-
if (!route.pattern) continue;
|
|
564
|
-
|
|
565
|
-
const regexPattern = route.pattern
|
|
566
|
-
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
567
|
-
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
568
|
-
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
569
|
-
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
570
|
-
|
|
571
|
-
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
572
|
-
const match = pathname.match(regex);
|
|
573
|
-
|
|
574
|
-
if (match) {
|
|
575
|
-
return {
|
|
576
|
-
route,
|
|
577
|
-
params: match.groups || {}
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Trata uma nova conexão WebSocket
|
|
586
|
-
*/
|
|
587
|
-
function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage, hwebReq: HightJSRequest) {
|
|
588
|
-
if (!req.url) return;
|
|
589
|
-
|
|
590
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
591
|
-
const pathname = url.pathname;
|
|
592
|
-
|
|
593
|
-
const matchedRoute = findMatchingWebSocketRoute(pathname);
|
|
594
|
-
if (!matchedRoute) {
|
|
595
|
-
ws.close(1000, 'Route not found');
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const params = extractWebSocketParams(pathname, matchedRoute.route.pattern);
|
|
600
|
-
const query = Object.fromEntries(url.searchParams.entries());
|
|
601
|
-
|
|
602
|
-
const context: WebSocketContext = {
|
|
603
|
-
hightReq: hwebReq,
|
|
604
|
-
ws,
|
|
605
|
-
req,
|
|
606
|
-
url,
|
|
607
|
-
params,
|
|
608
|
-
query,
|
|
609
|
-
send: (data: any) => {
|
|
610
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
611
|
-
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
612
|
-
ws.send(message);
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
close: (code?: number, reason?: string) => {
|
|
616
|
-
ws.close(code || 1000, reason);
|
|
617
|
-
},
|
|
618
|
-
broadcast: (data: any, exclude?: WebSocket[]) => {
|
|
619
|
-
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
620
|
-
const excludeSet = new Set(exclude || []);
|
|
621
|
-
wsConnections.forEach(connection => {
|
|
622
|
-
if (connection.readyState === WebSocket.OPEN && !excludeSet.has(connection)) {
|
|
623
|
-
connection.send(message);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
matchedRoute.route.handler(context);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
console.error('Error in WebSocket handler:', error);
|
|
633
|
-
ws.close(1011, 'Internal server error');
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* Extrai parâmetros da URL para WebSocket
|
|
639
|
-
*/
|
|
640
|
-
function extractWebSocketParams(pathname: string, pattern: string): Record<string, string> {
|
|
641
|
-
const params: Record<string, string> = {};
|
|
642
|
-
|
|
643
|
-
const regexPattern = pattern
|
|
644
|
-
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
645
|
-
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
646
|
-
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
647
|
-
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
648
|
-
|
|
649
|
-
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
650
|
-
const match = pathname.match(regex);
|
|
651
|
-
|
|
652
|
-
if (match && match.groups) {
|
|
653
|
-
Object.assign(params, match.groups);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return params;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Configura WebSocket upgrade no servidor HTTP existente
|
|
661
|
-
* @param server Servidor HTTP (Express, Fastify ou Native)
|
|
662
|
-
* @param hotReloadManager Instância do gerenciador de hot-reload para coordenação
|
|
663
|
-
*/
|
|
664
|
-
export function setupWebSocketUpgrade(server: any, hotReloadManager?: any) {
|
|
665
|
-
// NÃO remove listeners existentes para preservar hot-reload
|
|
666
|
-
// Em vez disso, coordena com o sistema existente
|
|
667
|
-
|
|
668
|
-
// Verifica se já existe um listener de upgrade
|
|
669
|
-
const existingListeners = server.listeners('upgrade');
|
|
670
|
-
|
|
671
|
-
// Se não há listeners, ou se o hot-reload ainda não foi configurado, adiciona o nosso
|
|
672
|
-
if (existingListeners.length === 0) {
|
|
673
|
-
server.on('upgrade', (request: any, socket: any, head: Buffer) => {
|
|
674
|
-
|
|
675
|
-
handleWebSocketUpgrade(request, socket, head, hotReloadManager);
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function handleWebSocketUpgrade(request: any, socket: any, head: Buffer, hotReloadManager?: any) {
|
|
681
|
-
const adapter = FrameworkAdapterFactory.getCurrentAdapter()
|
|
682
|
-
if (!adapter) {
|
|
683
|
-
console.error('❌ Framework adapter not detected. Unable to process WebSocket upgrade.');
|
|
684
|
-
socket.destroy();
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const genericReq = adapter.parseRequest(request);
|
|
688
|
-
const hwebReq = new HightJSRequest(genericReq);
|
|
689
|
-
const { pathname } = new URL(request.url, `http://${request.headers.host}`);
|
|
690
|
-
|
|
691
|
-
// Prioridade 1: Hot reload (sistema interno)
|
|
692
|
-
if (pathname === '/hweb-hotreload/') {
|
|
693
|
-
|
|
694
|
-
if (hotReloadManager) {
|
|
695
|
-
hotReloadManager.handleUpgrade(request, socket, head);
|
|
696
|
-
} else {
|
|
697
|
-
socket.destroy();
|
|
698
|
-
}
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// Prioridade 2: Rotas WebSocket do usuário
|
|
703
|
-
const matchedRoute = findMatchingWebSocketRoute(pathname);
|
|
704
|
-
if (matchedRoute) {
|
|
705
|
-
// Faz upgrade para WebSocket usando noServer
|
|
706
|
-
const wss = new WSServer({
|
|
707
|
-
noServer: true,
|
|
708
|
-
perMessageDeflate: false, // Melhor performance
|
|
709
|
-
maxPayload: 1024 * 1024 // Limite de 1MB
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
713
|
-
wsConnections.add(ws);
|
|
714
|
-
|
|
715
|
-
ws.on('close', () => {
|
|
716
|
-
wsConnections.delete(ws);
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
ws.on('error', (error) => {
|
|
720
|
-
wsConnections.delete(ws);
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// Processa a conexão
|
|
724
|
-
handleWebSocketConnection(ws, request, hwebReq);
|
|
725
|
-
});
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
socket.destroy();
|
|
730
|
-
}
|