@syntay/fastay 0.2.9 → 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":"AAIA,OAAO,EAAE,WAAW,EAAmC,MAAM,SAAS,CAAC;AAGvE;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,iBAYlB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CASlD;AA8HD;;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,197 +1,211 @@
1
1
  import fs from 'fs';
2
2
  import path from 'node:path';
3
- import mime from 'mime-types'; // npm i mime-types
3
+ import mime from 'mime-types';
4
4
  import { pathToFileURL } from 'url';
5
+ import { Router } from 'express';
5
6
  import { logger } from './logger.js';
6
- /**
7
- * Converte caminho do arquivo em rota Express (somente arquivos route.ts)
8
- */
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 */
9
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
10
30
  const rel = path.relative(apiDir, filePath);
31
+ // Divide the relative path into parts.
11
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.
12
34
  const filename = parts.pop();
13
- 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);
14
38
  return null;
15
- const segments = parts
16
- .map(s => (s.startsWith('[') && s.endsWith(']') ? `:${s.slice(1, -1)}` : s))
17
- .filter(Boolean);
18
- 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;
19
45
  }
20
- /**
21
- * Retorna todos arquivos .ts/.js recursivamente
22
- */
46
+ /** Walk recursion limit */
23
47
  export function collectFiles(dir) {
24
- let out = [];
25
- const items = fs.readdirSync(dir, { withFileTypes: true });
26
- for (const it of items) {
27
- const full = path.join(dir, it.name);
28
- if (it.isDirectory())
29
- out = out.concat(collectFiles(full));
30
- else if (/\.(ts|js|mts|mjs)$/.test(it.name))
31
- 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
+ }
32
69
  }
33
- return out;
70
+ return result;
71
+ }
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;
34
80
  }
35
- /**
36
- * Wrapper para suportar return JSON/string/number e capturar erros runtime
37
- */
38
- function wrapHandler(fn, routePath, filePath) {
39
- return async (req, res, next) => {
81
+ /** Highly optimized handler with fewer branches. */
82
+ function wrapHandler(fn) {
83
+ return async (request, res, next) => {
40
84
  try {
41
- const result = fn.length >= 2 ? await fn(req, res) : await fn(req);
42
- if (res.headersSent)
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)
43
88
  return;
44
- if (result === undefined)
45
- return;
46
- // Suporte a status e cookies customizado
47
- // if (
48
- // typeof result === 'object' &&
49
- // 'status' in result &&
50
- // 'body' in result &&
51
- // typeof result.status === 'number'
52
- // ) {
53
- // return res.status(result.status).json(result.body);
54
- // }
55
- if (typeof result === 'object' && result !== null) {
56
- const typedResult = result;
57
- // redirect
58
- if (typedResult.redirect) {
59
- return res.redirect(typedResult.status ?? 302, typedResult.redirect);
60
- }
61
- //headers
62
- if (typedResult.headers) {
63
- for (const [h, v] of Object.entries(typedResult.headers)) {
64
- 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
+ }
65
104
  }
66
- }
67
- //file
68
- if (typedResult.file) {
69
- const { path, filename, options } = typedResult.file;
70
- if (filename) {
71
- 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
+ }
72
110
  }
73
- return res.download(path, options);
74
- }
75
- // stream
76
- if (typedResult.stream) {
77
- if (typedResult.headers) {
78
- for (const [h, v] of Object.entries(typedResult.headers))
79
- res.setHeader(h, v);
111
+ // Redirections e files
112
+ if (response.redirect) {
113
+ return res.redirect(response.status ?? 302, response.redirect);
80
114
  }
81
- return typedResult.stream.pipe(res);
82
- }
83
- // raw
84
- if (typedResult.raw) {
85
- if (typedResult.headers) {
86
- for (const [h, v] of Object.entries(typedResult.headers))
87
- 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);
88
119
  }
89
- return res.status(typedResult.status ?? 200).send(typedResult.raw);
90
- }
91
- if (typedResult.cookies) {
92
- for (const [name, data] of Object.entries(typedResult.cookies)) {
93
- res.cookie(name, data.value, data.options || {});
120
+ if (response.stream) {
121
+ return response.stream.pipe(res);
94
122
  }
95
- }
96
- if (typedResult.static) {
97
- const filePath = typedResult.static.path;
98
- let contentType = typedResult.static.contentType;
99
- if (!contentType) {
100
- contentType = mime.lookup(filePath) || 'application/octet-stream';
123
+ if (response.raw) {
124
+ return res.status(response.status ?? 200).send(response.raw);
101
125
  }
102
- res.setHeader('Content-Type', contentType);
103
- return res.sendFile(path.resolve(filePath), (err) => {
104
- if (err) {
105
- console.error(err);
106
- res.status(500).send('Internal Server Error');
107
- }
108
- });
109
- }
110
- const statusCode = typeof result.status === 'number' ? result.status : 200;
111
- const body = result.body ?? result; // se não existir body, retorna o objeto inteiro
112
- return res.status(statusCode).json(body);
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);
113
137
  }
114
- // Suporte a retorno simples
115
- if (typeof result === 'string')
116
- return res.send(result);
117
- if (typeof result === 'number')
118
- return res.send(String(result));
119
- return res.json(result);
120
138
  }
121
139
  catch (err) {
122
- const stack = err?.stack?.split('\n').slice(0, 3).join('\n') || '';
123
- logger.error(`${err.name}: ${err.message}`);
140
+ const error = err;
141
+ logger.error(`Handler Error [${request.method} ${request.path}]: ${error.message}`);
124
142
  next(err);
125
143
  }
126
144
  };
127
145
  }
128
- /**
129
- * Carrega todas as rotas do diretório apiDir
130
- */
146
+ /** Optimized route loader with parallel import */
131
147
  export async function loadApiRoutes(app, baseRoute, apiDirectory) {
132
148
  const isDev = process.env.NODE_ENV !== 'production';
133
149
  const apiDir = path.join(process.cwd(), isDev ? apiDirectory : 'dist/api');
134
- if (!fs.existsSync(apiDir))
150
+ if (!fs.existsSync(apiDir)) {
151
+ logger.warn(`API directory not found: ${apiDir}`);
135
152
  return 0;
153
+ }
136
154
  const files = collectFiles(apiDir);
137
- let cont = 0;
138
- logger.group('Routes Loaded');
139
- for (const file of files) {
140
- const route = filePathToRoute(apiDir, file, baseRoute);
141
- if (!route)
142
- continue;
155
+ let count = 0;
156
+ logger.group('Loading Routes');
157
+ // Optimized parallel loading
158
+ const modulePromises = files.map(async (file) => {
143
159
  try {
144
- const fileUrl = pathToFileURL(file).href;
145
- const mod = await import(fileUrl);
146
- const httpMethods = [
147
- 'GET',
148
- 'POST',
149
- 'PUT',
150
- 'DELETE',
151
- 'PATCH',
152
- 'OPTIONS',
153
- 'HEAD'
154
- ];
155
- for (const m of httpMethods) {
156
- if (typeof mod[m] === 'function') {
157
- app[m.toLowerCase()](route, wrapHandler(mod[m], route, file));
158
- cont++;
159
- logger.success(`Route: [${m}] ${route}`);
160
- }
160
+ // Module cache in production
161
+ if (process.env.NODE_ENV === 'production' && moduleCache.has(file)) {
162
+ return { file, module: moduleCache.get(file) };
161
163
  }
162
- if (mod.default && typeof mod.default === 'function') {
163
- app.get(route, wrapHandler(mod.default, route, file));
164
- cont++;
165
- 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);
166
167
  }
168
+ return { file, module };
167
169
  }
168
- catch (err) {
169
- const stack = err?.stack?.split('\n').slice(0, 3).join('\n') || '';
170
- // logger.error(
171
- // `✗ Boot Error importing ${file}\n` +
172
- // ` Message: ${err.message || 'Unknown error'}\n` +
173
- // ` Stack: ${stack}`
174
- // );
175
- let fileInfo = '';
176
- if (err.stack) {
177
- const stackLine = err.stack.split('\n')[1]; // pega primeira linha depois do erro
178
- const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
179
- if (match) {
180
- const [_, file, line, col] = match;
181
- fileInfo = `${file}:${line}:${col}`;
182
- // Tenta mostrar o trecho da linha que deu erro
183
- if (fs.existsSync(file)) {
184
- const codeLines = fs.readFileSync(file, 'utf-8').split('\n');
185
- const codeSnippet = codeLines[parseInt(line) - 1].trim();
186
- fileInfo += ` ${codeSnippet}`;
187
- }
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++;
188
196
  }
189
197
  }
190
- // logger.group(`✗ Boot Error importing ${file}`);
191
- logger.error(`${err.name}: ${err.message}`);
192
- if (fileInfo)
193
- 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);
194
208
  }
195
209
  }
196
- return cont;
210
+ return count;
197
211
  }