@syntay/fastay 0.2.8 → 1.0.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/dist/app.d.ts +22 -41
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +140 -43
- package/dist/error-handler.d.ts +13 -2
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +164 -30
- package/dist/error-hanler2.d.ts +14 -0
- package/dist/error-hanler2.d.ts.map +1 -0
- package/dist/error-hanler2.js +180 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +14 -18
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/router.d.ts +3 -9
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +171 -142
- package/dist/utils/wrapMiddleware.d.ts +2 -1
- package/dist/utils/wrapMiddleware.d.ts.map +1 -1
- package/dist/utils/wrapMiddleware.js +10 -0
- package/package.json +12 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
// Cache para arquivos lidos (evita leitura repetida)
|
|
5
|
+
const fileCache = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Extrai informações do stack trace de forma otimizada
|
|
8
|
+
*/
|
|
9
|
+
function extractErrorInfo(err) {
|
|
10
|
+
if (!err.stack)
|
|
11
|
+
return null;
|
|
12
|
+
// Pega a primeira linha relevante do stack (ignora a linha do erro)
|
|
13
|
+
const stackLines = err.stack.split("\n");
|
|
14
|
+
const relevantLine = stackLines.find((line) => line.includes("(") &&
|
|
15
|
+
line.includes(".ts:") &&
|
|
16
|
+
!line.includes("node_modules") &&
|
|
17
|
+
!line.includes("Error:"));
|
|
18
|
+
if (!relevantLine)
|
|
19
|
+
return null;
|
|
20
|
+
// Regex otimizado para capturar arquivo, linha e coluna
|
|
21
|
+
const match = relevantLine.match(/\((.*?):(\d+):(\d+)\)/);
|
|
22
|
+
if (!match)
|
|
23
|
+
return null;
|
|
24
|
+
const [, file, line, column] = match;
|
|
25
|
+
return { file, line, column, snippet: "" };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Lê snippet de código de forma otimizada com cache
|
|
29
|
+
*/
|
|
30
|
+
async function getCodeSnippet(filePath, lineNumber) {
|
|
31
|
+
try {
|
|
32
|
+
// Normalizar caminho do arquivo
|
|
33
|
+
const normalizedPath = path.resolve(filePath);
|
|
34
|
+
// Verificar cache
|
|
35
|
+
if (fileCache.has(normalizedPath)) {
|
|
36
|
+
const content = fileCache.get(normalizedPath);
|
|
37
|
+
const lines = content.split("\n");
|
|
38
|
+
return lines[lineNumber - 1]?.trim() || "";
|
|
39
|
+
}
|
|
40
|
+
// Ler arquivo apenas se for um arquivo .ts/.js do projeto
|
|
41
|
+
if (!normalizedPath.includes("node_modules") &&
|
|
42
|
+
(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".js"))) {
|
|
43
|
+
const content = await fs.readFile(normalizedPath, "utf-8");
|
|
44
|
+
// Cache apenas em desenvolvimento
|
|
45
|
+
if (process.env.NODE_ENV === "development") {
|
|
46
|
+
fileCache.set(normalizedPath, content);
|
|
47
|
+
}
|
|
48
|
+
const lines = content.split("\n");
|
|
49
|
+
return lines[lineNumber - 1]?.trim() || "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Ignorar erros de leitura
|
|
54
|
+
}
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Log de erro otimizado
|
|
59
|
+
*/
|
|
60
|
+
function logError(err, route, fileInfo) {
|
|
61
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
62
|
+
if (isDev) {
|
|
63
|
+
logger.group(`✗ Error [${route}]`);
|
|
64
|
+
logger.error(`${err.name}: ${err.message}`);
|
|
65
|
+
if (fileInfo) {
|
|
66
|
+
logger.error(`Location: ${fileInfo}`);
|
|
67
|
+
}
|
|
68
|
+
// Stack trace apenas para erros não esperados
|
|
69
|
+
if (err instanceof TypeError || err instanceof ReferenceError) {
|
|
70
|
+
logger.raw(err.stack?.split("\n").slice(0, 5).join("\n") || "");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Log minimalista em produção
|
|
75
|
+
logger.error(`[${route}] ${err.name}: ${err.message}`);
|
|
76
|
+
// Log detalhado apenas para erros críticos
|
|
77
|
+
if (err instanceof SyntaxError || err.message?.includes("Unexpected")) {
|
|
78
|
+
logger.raw(err.stack?.split("\n")[0] || "");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Handler de erros otimizado para Fastay
|
|
84
|
+
*/
|
|
85
|
+
export function errorHandler(err, req, res, next) {
|
|
86
|
+
// Se headers já foram enviados, delegar para próximo handler
|
|
87
|
+
if (res.headersSent) {
|
|
88
|
+
return next(err);
|
|
89
|
+
}
|
|
90
|
+
const route = `${req.method} ${req.originalUrl}`;
|
|
91
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
92
|
+
// Informações adicionais apenas em desenvolvimento
|
|
93
|
+
let fileInfo = "";
|
|
94
|
+
if (process.env.NODE_ENV === "development") {
|
|
95
|
+
const errorInfo = extractErrorInfo(error);
|
|
96
|
+
if (errorInfo) {
|
|
97
|
+
fileInfo = `${path.basename(errorInfo.file)}:${errorInfo.line}:${errorInfo.column}`;
|
|
98
|
+
// Carregar snippet de forma assíncrona
|
|
99
|
+
getCodeSnippet(errorInfo.file, parseInt(errorInfo.line))
|
|
100
|
+
.then((snippet) => {
|
|
101
|
+
if (snippet) {
|
|
102
|
+
logger.error(`Code: ${snippet}`);
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.catch(() => { }); // Ignorar erros de leitura
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Log otimizado
|
|
109
|
+
logError(error, route, fileInfo);
|
|
110
|
+
// Determinar status code
|
|
111
|
+
let statusCode = 500;
|
|
112
|
+
let errorMessage = "Internal Server Error";
|
|
113
|
+
if (error instanceof SyntaxError || error.message?.includes("Unexpected")) {
|
|
114
|
+
statusCode = 400;
|
|
115
|
+
errorMessage = "Invalid Request";
|
|
116
|
+
}
|
|
117
|
+
else if (error.name === "ValidationError") {
|
|
118
|
+
statusCode = 422;
|
|
119
|
+
errorMessage = "Validation Failed";
|
|
120
|
+
}
|
|
121
|
+
// Resposta otimizada
|
|
122
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
123
|
+
res.status(statusCode);
|
|
124
|
+
// JSON response otimizado
|
|
125
|
+
const response = {
|
|
126
|
+
error: errorMessage,
|
|
127
|
+
status: statusCode,
|
|
128
|
+
path: req.originalUrl,
|
|
129
|
+
};
|
|
130
|
+
// Detalhes apenas em desenvolvimento
|
|
131
|
+
if (isDev) {
|
|
132
|
+
response.message = error.message;
|
|
133
|
+
if (error.stack && statusCode === 500) {
|
|
134
|
+
response.stack = error.stack.split("\n").slice(0, 3);
|
|
135
|
+
}
|
|
136
|
+
if (fileInfo) {
|
|
137
|
+
response.location = fileInfo;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Cabeçalhos de segurança
|
|
141
|
+
res.setHeader("Content-Type", "application/json");
|
|
142
|
+
res.setHeader("X-Error-Type", error.name);
|
|
143
|
+
// Cache-control para respostas de erro
|
|
144
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
145
|
+
return res.json(response);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Factory para criar error handlers customizados
|
|
149
|
+
*/
|
|
150
|
+
export function createErrorHandler(options) {
|
|
151
|
+
const opts = {
|
|
152
|
+
logDetails: process.env.NODE_ENV === "development",
|
|
153
|
+
includeStack: process.env.NODE_ENV === "development",
|
|
154
|
+
customMessages: {},
|
|
155
|
+
...options,
|
|
156
|
+
};
|
|
157
|
+
return (err, req, res, next) => {
|
|
158
|
+
if (res.headersSent)
|
|
159
|
+
return next(err);
|
|
160
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
161
|
+
const route = `${req.method} ${req.originalUrl}`;
|
|
162
|
+
// Log customizado
|
|
163
|
+
if (opts.logDetails) {
|
|
164
|
+
logger.error(`[${route}] ${error.name}: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
// Status code customizado
|
|
167
|
+
let statusCode = 500;
|
|
168
|
+
if (error.name in opts.customMessages) {
|
|
169
|
+
statusCode = 400;
|
|
170
|
+
}
|
|
171
|
+
// Response
|
|
172
|
+
res.status(statusCode).json({
|
|
173
|
+
error: opts.customMessages[error.name] || "Internal Server Error",
|
|
174
|
+
...(opts.includeStack && {
|
|
175
|
+
details: error.message,
|
|
176
|
+
...(statusCode === 500 && { stack: error.stack?.split("\n")[0] }),
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
}
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,MAAM;gBACL,MAAM;gBAEN,MAAM;iBACL,MAAM;mBAEJ,MAAM;gBAET,MAAM;kBAEL,MAAM;iBAIN,MAAM;aAOV,MAAM;;mBAQA,MAAM;CAGtB,CAAC"}
|
package/dist/logger.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import pino from
|
|
2
|
-
import pretty from
|
|
3
|
-
// stream configurado para remover INFO:, timestamps e etc
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
import pretty from "pino-pretty";
|
|
4
3
|
const stream = pretty({
|
|
5
4
|
colorize: true,
|
|
6
|
-
ignore:
|
|
5
|
+
ignore: "pid,hostname,time,level",
|
|
7
6
|
levelFirst: false,
|
|
8
|
-
// Remove "INFO: "
|
|
9
|
-
messageKey:
|
|
10
|
-
// Maneira correta TS-safe
|
|
7
|
+
// Remove "INFO: "
|
|
8
|
+
messageKey: "msg",
|
|
11
9
|
messageFormat: (log, messageKey) => {
|
|
12
10
|
const msg = log[messageKey];
|
|
13
|
-
return typeof msg ===
|
|
11
|
+
return typeof msg === "string" ? msg : String(msg);
|
|
14
12
|
},
|
|
15
13
|
});
|
|
16
14
|
const base = pino({
|
|
17
|
-
level:
|
|
15
|
+
level: "info",
|
|
18
16
|
timestamp: false, // remove [HH:mm:ss]
|
|
19
17
|
base: undefined, // remove pid, hostname
|
|
20
18
|
}, stream);
|
|
21
|
-
// helpers para cores ANSI
|
|
22
19
|
const colors = {
|
|
23
20
|
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
|
24
21
|
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
@@ -26,26 +23,25 @@ const colors = {
|
|
|
26
23
|
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
27
24
|
gray: (s) => `\x1b[90m${s}\x1b[0m`,
|
|
28
25
|
};
|
|
29
|
-
// emojis Fastay
|
|
30
26
|
const ICONS = {
|
|
31
|
-
info:
|
|
32
|
-
success:
|
|
33
|
-
error:
|
|
34
|
-
gear:
|
|
27
|
+
info: "○",
|
|
28
|
+
success: "✓",
|
|
29
|
+
error: "✗",
|
|
30
|
+
gear: "⚙️",
|
|
35
31
|
};
|
|
36
32
|
export const logger = {
|
|
37
33
|
info: (msg) => base.info(` ${colors.white(ICONS.info)} ${colors.white(msg)}`),
|
|
38
|
-
warn: (msg) => base.info(` ${colors.red(
|
|
34
|
+
warn: (msg) => base.info(` ${colors.red("⚠")} ${colors.white(msg)}`),
|
|
39
35
|
error: (msg) => base.info(` ${colors.red(ICONS.error)} ${colors.white(msg)}`),
|
|
40
36
|
success: (msg) => base.info(` ${colors.green(ICONS.success)} ${colors.white(msg)}`),
|
|
41
37
|
gear: (msg) => base.info(` ${ICONS.gear} ${colors.white(msg)}`),
|
|
42
38
|
space(lines = 1) {
|
|
43
39
|
for (let i = 0; i < lines; i++)
|
|
44
|
-
base.info(
|
|
40
|
+
base.info(" ");
|
|
45
41
|
},
|
|
46
42
|
group(title) {
|
|
47
43
|
this.space();
|
|
48
|
-
base.info(
|
|
44
|
+
base.info("");
|
|
49
45
|
base.info(colors.cyan(title));
|
|
50
46
|
// this.space();
|
|
51
47
|
},
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Application,
|
|
1
|
+
import { Application, Response, NextFunction } from 'express';
|
|
2
|
+
import { Request } from './types/request.js';
|
|
2
3
|
type MiddlewareFn = (req: Request, res: Response, next: NextFunction) => any;
|
|
3
4
|
/**
|
|
4
5
|
* Defines a map of routes and the middleware functions that Fastay
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM9D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,GAAG,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,IAE1D,KAAK,WAAW,UASzB;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,WAAW,iBA2B3D"}
|
package/dist/router.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { Application } from 'express';
|
|
2
|
-
/**
|
|
3
|
-
* Converte caminho do arquivo em rota Express (somente arquivos route.ts)
|
|
4
|
-
*/
|
|
2
|
+
/** Path → route with cache */
|
|
5
3
|
export declare function filePathToRoute(apiDir: string, filePath: string, baseRoute: string): string | null;
|
|
6
|
-
/**
|
|
7
|
-
* Retorna todos arquivos .ts/.js recursivamente
|
|
8
|
-
*/
|
|
4
|
+
/** Walk recursion limit */
|
|
9
5
|
export declare function collectFiles(dir: string): string[];
|
|
10
|
-
/**
|
|
11
|
-
* Carrega todas as rotas do diretório apiDir
|
|
12
|
-
*/
|
|
6
|
+
/** Optimized route loader with parallel import */
|
|
13
7
|
export declare function loadApiRoutes(app: Application, baseRoute: string, apiDirectory: string): Promise<number>;
|
|
14
8
|
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAA2C,MAAM,SAAS,CAAC;AAgD/E,8BAA8B;AAC9B,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CA+Bf;AAED,2BAA2B;AAC3B,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BlD;AA+FD,kDAAkD;AAClD,wBAAsB,aAAa,CACjC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CA4EjB"}
|
package/dist/router.js
CHANGED
|
@@ -1,182 +1,211 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import mime from 'mime-types';
|
|
3
4
|
import { pathToFileURL } from 'url';
|
|
5
|
+
import { Router } from 'express';
|
|
4
6
|
import { logger } from './logger.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Optimized cache with WeakMap for garbage collection
|
|
8
|
+
const mimeCache = new Map();
|
|
9
|
+
const routeCache = new Map();
|
|
10
|
+
const moduleCache = new Map();
|
|
11
|
+
// Predefined HTTP methods as an array for compatibility
|
|
12
|
+
const HTTP_METHODS = [
|
|
13
|
+
'GET',
|
|
14
|
+
'POST',
|
|
15
|
+
'PUT',
|
|
16
|
+
'DELETE',
|
|
17
|
+
'PATCH',
|
|
18
|
+
'OPTIONS',
|
|
19
|
+
'HEAD',
|
|
20
|
+
];
|
|
21
|
+
/** Path → route with cache */
|
|
8
22
|
export function filePathToRoute(apiDir, filePath, baseRoute) {
|
|
23
|
+
// Creates a unique key for the route cache.
|
|
24
|
+
const key = `${apiDir}:${filePath}`;
|
|
25
|
+
// Check if the route is already in the cache routeCache.
|
|
26
|
+
if (routeCache.has(key)) {
|
|
27
|
+
return routeCache.get(key);
|
|
28
|
+
}
|
|
29
|
+
// the relative path of the file in relation to the API folder
|
|
9
30
|
const rel = path.relative(apiDir, filePath);
|
|
31
|
+
// Divide the relative path into parts.
|
|
10
32
|
const parts = rel.split(path.sep);
|
|
33
|
+
// The `parts` array moves and returns the last element of the `parts` array, which is the filename.
|
|
11
34
|
const filename = parts.pop();
|
|
12
|
-
if
|
|
35
|
+
//Check if the file exists.
|
|
36
|
+
if (!filename || !(filename === 'route.ts' || filename === 'route.js')) {
|
|
37
|
+
routeCache.set(key, null);
|
|
13
38
|
return null;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
}
|
|
40
|
+
// Converts dynamic folders into Express route parameters.
|
|
41
|
+
const segments = parts.map((s) => s.startsWith('[') && s.endsWith(']') ? `:${s.slice(1, -1)}` : s);
|
|
42
|
+
const route = `${baseRoute}/${segments.join('/')}`.replace(/\/+/g, '/');
|
|
43
|
+
routeCache.set(key, route);
|
|
44
|
+
return route;
|
|
18
45
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Retorna todos arquivos .ts/.js recursivamente
|
|
21
|
-
*/
|
|
46
|
+
/** Walk recursion limit */
|
|
22
47
|
export function collectFiles(dir) {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
const result = [];
|
|
49
|
+
const stack = [dir];
|
|
50
|
+
const maxDepth = 20;
|
|
51
|
+
while (stack.length > 0 && stack.length < maxDepth) {
|
|
52
|
+
const current = stack.pop();
|
|
53
|
+
try {
|
|
54
|
+
const items = fs.readdirSync(current, { withFileTypes: true });
|
|
55
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
56
|
+
const it = items[i];
|
|
57
|
+
const full = path.join(current, it.name);
|
|
58
|
+
if (it.isDirectory()) {
|
|
59
|
+
stack.push(full);
|
|
60
|
+
}
|
|
61
|
+
else if (/\.(ts|js|mts|mjs)$/.test(it.name)) {
|
|
62
|
+
result.push(full);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
logger.warn(`Cannot read directory: ${current}`);
|
|
68
|
+
}
|
|
31
69
|
}
|
|
32
|
-
return
|
|
70
|
+
return result;
|
|
33
71
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
72
|
+
/** Cache mime */
|
|
73
|
+
function getMime(filePath) {
|
|
74
|
+
const cached = mimeCache.get(filePath);
|
|
75
|
+
if (cached)
|
|
76
|
+
return cached;
|
|
77
|
+
const type = mime.lookup(filePath) || 'application/octet-stream';
|
|
78
|
+
mimeCache.set(filePath, type);
|
|
79
|
+
return type;
|
|
80
|
+
}
|
|
81
|
+
/** Highly optimized handler with fewer branches. */
|
|
82
|
+
function wrapHandler(fn) {
|
|
83
|
+
return async (request, res, next) => {
|
|
39
84
|
try {
|
|
40
|
-
const result = fn.length >= 2 ?
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (result === undefined)
|
|
85
|
+
const result = await (fn.length >= 2 ? fn(request, res) : fn(request));
|
|
86
|
+
// If response has already been sent, exit
|
|
87
|
+
if (res.headersSent || result === undefined)
|
|
44
88
|
return;
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//headers
|
|
61
|
-
if (typedResult.headers) {
|
|
62
|
-
for (const [h, v] of Object.entries(typedResult.headers)) {
|
|
63
|
-
res.setHeader(h, v);
|
|
89
|
+
//Optimized processing by type
|
|
90
|
+
switch (typeof result) {
|
|
91
|
+
case 'string':
|
|
92
|
+
case 'number':
|
|
93
|
+
case 'boolean':
|
|
94
|
+
return res.send(String(result));
|
|
95
|
+
case 'object':
|
|
96
|
+
if (result === null)
|
|
97
|
+
return res.send('null');
|
|
98
|
+
const response = result;
|
|
99
|
+
// Headers first
|
|
100
|
+
if (response.headers) {
|
|
101
|
+
for (const [k, v] of Object.entries(response.headers)) {
|
|
102
|
+
res.setHeader(k, v);
|
|
103
|
+
}
|
|
64
104
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return res.download(path, filename, options);
|
|
105
|
+
// Cookies
|
|
106
|
+
if (response.cookies) {
|
|
107
|
+
for (const [name, cookie] of Object.entries(response.cookies)) {
|
|
108
|
+
res.cookie(name, cookie.value, cookie.options || {});
|
|
109
|
+
}
|
|
71
110
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (typedResult.stream) {
|
|
76
|
-
if (typedResult.headers) {
|
|
77
|
-
for (const [h, v] of Object.entries(typedResult.headers))
|
|
78
|
-
res.setHeader(h, v);
|
|
111
|
+
// Redirections e files
|
|
112
|
+
if (response.redirect) {
|
|
113
|
+
return res.redirect(response.status ?? 302, response.redirect);
|
|
79
114
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (typedResult.headers) {
|
|
85
|
-
for (const [h, v] of Object.entries(typedResult.headers))
|
|
86
|
-
res.setHeader(h, v);
|
|
115
|
+
if (response.file) {
|
|
116
|
+
return response.file.filename
|
|
117
|
+
? res.download(response.file.path, response.file.filename, response.file.options)
|
|
118
|
+
: res.download(response.file.path, response.file.options);
|
|
87
119
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (typedResult.cookies) {
|
|
91
|
-
for (const [name, data] of Object.entries(typedResult.cookies)) {
|
|
92
|
-
res.cookie(name, data.value, data.options || {});
|
|
120
|
+
if (response.stream) {
|
|
121
|
+
return response.stream.pipe(res);
|
|
93
122
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
if (response.raw) {
|
|
124
|
+
return res.status(response.status ?? 200).send(response.raw);
|
|
125
|
+
}
|
|
126
|
+
if (response.static) {
|
|
127
|
+
const type = response.static.contentType || getMime(response.static.path);
|
|
128
|
+
res.setHeader('Content-Type', type);
|
|
129
|
+
return res.sendFile(path.resolve(response.static.path));
|
|
130
|
+
}
|
|
131
|
+
// JSON response default
|
|
132
|
+
return res
|
|
133
|
+
.status(response.status ?? 200)
|
|
134
|
+
.json(response.body ?? result);
|
|
135
|
+
default:
|
|
136
|
+
return res.json(result);
|
|
98
137
|
}
|
|
99
|
-
// Suporte a retorno simples
|
|
100
|
-
if (typeof result === 'string')
|
|
101
|
-
return res.send(result);
|
|
102
|
-
if (typeof result === 'number')
|
|
103
|
-
return res.send(String(result));
|
|
104
|
-
return res.json(result);
|
|
105
138
|
}
|
|
106
139
|
catch (err) {
|
|
107
|
-
const
|
|
108
|
-
logger.error(
|
|
140
|
+
const error = err;
|
|
141
|
+
logger.error(`Handler Error [${request.method} ${request.path}]: ${error.message}`);
|
|
109
142
|
next(err);
|
|
110
143
|
}
|
|
111
144
|
};
|
|
112
145
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Carrega todas as rotas do diretório apiDir
|
|
115
|
-
*/
|
|
146
|
+
/** Optimized route loader with parallel import */
|
|
116
147
|
export async function loadApiRoutes(app, baseRoute, apiDirectory) {
|
|
117
148
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
118
149
|
const apiDir = path.join(process.cwd(), isDev ? apiDirectory : 'dist/api');
|
|
119
|
-
if (!fs.existsSync(apiDir))
|
|
150
|
+
if (!fs.existsSync(apiDir)) {
|
|
151
|
+
logger.warn(`API directory not found: ${apiDir}`);
|
|
120
152
|
return 0;
|
|
153
|
+
}
|
|
121
154
|
const files = collectFiles(apiDir);
|
|
122
|
-
let
|
|
123
|
-
logger.group('Routes
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (!route)
|
|
127
|
-
continue;
|
|
155
|
+
let count = 0;
|
|
156
|
+
logger.group('Loading Routes');
|
|
157
|
+
// Optimized parallel loading
|
|
158
|
+
const modulePromises = files.map(async (file) => {
|
|
128
159
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
'GET',
|
|
133
|
-
'POST',
|
|
134
|
-
'PUT',
|
|
135
|
-
'DELETE',
|
|
136
|
-
'PATCH',
|
|
137
|
-
'OPTIONS',
|
|
138
|
-
'HEAD',
|
|
139
|
-
];
|
|
140
|
-
for (const m of httpMethods) {
|
|
141
|
-
if (typeof mod[m] === 'function') {
|
|
142
|
-
app[m.toLowerCase()](route, wrapHandler(mod[m], route, file));
|
|
143
|
-
cont++;
|
|
144
|
-
logger.success(`Route: [${m}] ${route}`);
|
|
145
|
-
}
|
|
160
|
+
// Module cache in production
|
|
161
|
+
if (process.env.NODE_ENV === 'production' && moduleCache.has(file)) {
|
|
162
|
+
return { file, module: moduleCache.get(file) };
|
|
146
163
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
logger.success(`Route: [GET] ${route}`);
|
|
164
|
+
const module = await import(pathToFileURL(file).href);
|
|
165
|
+
if (process.env.NODE_ENV === 'production') {
|
|
166
|
+
moduleCache.set(file, module);
|
|
151
167
|
}
|
|
168
|
+
return { file, module };
|
|
152
169
|
}
|
|
153
|
-
catch (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
catch (error) {
|
|
171
|
+
logger.error(`Failed to load ${file}: ${error.message}`);
|
|
172
|
+
return { file, module: null, error };
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
const modules = await Promise.all(modulePromises);
|
|
176
|
+
// Router per file
|
|
177
|
+
for (const { file, module } of modules) {
|
|
178
|
+
if (!module || module.error)
|
|
179
|
+
continue;
|
|
180
|
+
const route = filePathToRoute(apiDir, file, baseRoute);
|
|
181
|
+
if (!route)
|
|
182
|
+
continue;
|
|
183
|
+
const routeRouter = Router({ mergeParams: true });
|
|
184
|
+
let hasRoutes = false;
|
|
185
|
+
// Register HTTP methods
|
|
186
|
+
for (const method of HTTP_METHODS) {
|
|
187
|
+
const handler = module[method];
|
|
188
|
+
if (typeof handler === 'function') {
|
|
189
|
+
// Using type assertion is safe.
|
|
190
|
+
const routerMethod = method.toLowerCase();
|
|
191
|
+
if (typeof routeRouter[routerMethod] === 'function') {
|
|
192
|
+
routeRouter[routerMethod]('/', wrapHandler(handler));
|
|
193
|
+
logger.success(`[${method}] ${route}`);
|
|
194
|
+
hasRoutes = true;
|
|
195
|
+
count++;
|
|
173
196
|
}
|
|
174
197
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
}
|
|
199
|
+
// Default export
|
|
200
|
+
if (typeof module.default === 'function') {
|
|
201
|
+
routeRouter.get('/', wrapHandler(module.default));
|
|
202
|
+
logger.success(`[GET] ${route}`);
|
|
203
|
+
hasRoutes = true;
|
|
204
|
+
count++;
|
|
205
|
+
}
|
|
206
|
+
if (hasRoutes) {
|
|
207
|
+
app.use(route, routeRouter);
|
|
179
208
|
}
|
|
180
209
|
}
|
|
181
|
-
return
|
|
210
|
+
return count;
|
|
182
211
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Response, NextFunction } from 'express';
|
|
2
|
+
import { Request } from 'src/types/request.js';
|
|
2
3
|
export type MiddlewareFn = (req: Request, res: Response, next: NextFunction) => any;
|
|
3
4
|
/**
|
|
4
5
|
* Wrapper final
|