@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.
@@ -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
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AA6CA,eAAO,MAAM,MAAM;gBACL,MAAM;gBAEN,MAAM;iBACL,MAAM;mBAEJ,MAAM;gBAET,MAAM;kBAEL,MAAM;iBAIN,MAAM;aAOV,MAAM;;mBAQA,MAAM;CAGtB,CAAC"}
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 'pino';
2
- import pretty from 'pino-pretty';
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: 'pid,hostname,time,level',
5
+ ignore: "pid,hostname,time,level",
7
6
  levelFirst: false,
8
- // Remove "INFO: " antes da msg
9
- messageKey: 'msg',
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 === 'string' ? msg : String(msg);
11
+ return typeof msg === "string" ? msg : String(msg);
14
12
  },
15
13
  });
16
14
  const base = pino({
17
- level: 'info',
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('')} ${colors.white(msg)}`),
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
  },
@@ -1,4 +1,5 @@
1
- import { Application, Request, Response, NextFunction } from 'express';
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
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAOvE,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"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAmC,MAAM,SAAS,CAAC;AAGvE;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,iBAclB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CASlD;AAwGD;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,mBA+ErB"}
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
- * Converte caminho do arquivo em rota Express (somente arquivos route.ts)
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 (filename !== 'route.ts' && filename !== 'route.js')
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
- const segments = parts
15
- .map((s) => s.startsWith('[') && s.endsWith(']') ? `:${s.slice(1, -1)}` : s)
16
- .filter(Boolean);
17
- return `${baseRoute}/${segments.join('/')}`.replace(/\/+/g, '/');
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
- let out = [];
24
- const items = fs.readdirSync(dir, { withFileTypes: true });
25
- for (const it of items) {
26
- const full = path.join(dir, it.name);
27
- if (it.isDirectory())
28
- out = out.concat(collectFiles(full));
29
- else if (/\.(ts|js|mts|mjs)$/.test(it.name))
30
- out.push(full);
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 out;
70
+ return result;
33
71
  }
34
- /**
35
- * Wrapper para suportar return JSON/string/number e capturar erros runtime
36
- */
37
- function wrapHandler(fn, routePath, filePath) {
38
- return async (req, res, next) => {
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 ? await fn(req, res) : await fn(req);
41
- if (res.headersSent)
42
- return;
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
- // Suporte a status e cookies customizado
46
- // if (
47
- // typeof result === 'object' &&
48
- // 'status' in result &&
49
- // 'body' in result &&
50
- // typeof result.status === 'number'
51
- // ) {
52
- // return res.status(result.status).json(result.body);
53
- // }
54
- if (typeof result === 'object' && result !== null) {
55
- const typedResult = result;
56
- // redirect
57
- if (typedResult.redirect) {
58
- return res.redirect(typedResult.status ?? 302, typedResult.redirect);
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
- //file
67
- if (typedResult.file) {
68
- const { path, filename, options } = typedResult.file;
69
- if (filename) {
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
- return res.download(path, options);
73
- }
74
- // stream
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
- return typedResult.stream.pipe(res);
81
- }
82
- // raw
83
- if (typedResult.raw) {
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
- return res.status(typedResult.status ?? 200).send(typedResult.raw);
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
- const statusCode = typeof result.status === 'number' ? result.status : 200;
96
- const body = result.body ?? result; // se não existir body, retorna o objeto inteiro
97
- return res.status(statusCode).json(body);
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 stack = err?.stack?.split('\n').slice(0, 3).join('\n') || '';
108
- logger.error(`${err.name}: ${err.message}`);
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 cont = 0;
123
- logger.group('Routes Loaded');
124
- for (const file of files) {
125
- const route = filePathToRoute(apiDir, file, baseRoute);
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
- const fileUrl = pathToFileURL(file).href;
130
- const mod = await import(fileUrl);
131
- const httpMethods = [
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
- if (mod.default && typeof mod.default === 'function') {
148
- app.get(route, wrapHandler(mod.default, route, file));
149
- cont++;
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 (err) {
154
- const stack = err?.stack?.split('\n').slice(0, 3).join('\n') || '';
155
- // logger.error(
156
- // `✗ Boot Error importing ${file}\n` +
157
- // ` Message: ${err.message || 'Unknown error'}\n` +
158
- // ` Stack: ${stack}`
159
- // );
160
- let fileInfo = '';
161
- if (err.stack) {
162
- const stackLine = err.stack.split('\n')[1]; // pega primeira linha depois do erro
163
- const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
164
- if (match) {
165
- const [_, file, line, col] = match;
166
- fileInfo = `${file}:${line}:${col}`;
167
- // Tenta mostrar o trecho da linha que deu erro
168
- if (fs.existsSync(file)) {
169
- const codeLines = fs.readFileSync(file, 'utf-8').split('\n');
170
- const codeSnippet = codeLines[parseInt(line) - 1].trim();
171
- fileInfo += ` ${codeSnippet}`;
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
- // logger.group(`✗ Boot Error importing ${file}`);
176
- logger.error(`${err.name}: ${err.message}`);
177
- if (fileInfo)
178
- logger.error(`Location: ${fileInfo}`);
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 cont;
210
+ return count;
182
211
  }
@@ -1,4 +1,5 @@
1
- import { Request, Response, NextFunction } from 'express';
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