@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.
package/dist/app.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import express from 'express';
2
- import { MiddlewareMap } from './middleware.js';
3
- import type { ServeStaticOptions } from 'serve-static';
1
+ import express from "express";
2
+ import { Server } from "node:http";
3
+ import { MiddlewareMap } from "./middleware.js";
4
+ import type { ServeStaticOptions } from "serve-static";
4
5
  /**
5
6
  * Express configuration options applied automatically by Fastay
6
7
  * before internal middleware and route loading.
@@ -85,32 +86,32 @@ export type CreateAppOptions = {
85
86
  */
86
87
  allowAnyOrigin?: boolean;
87
88
  /**
88
- * Lista de origens específicas permitidas para envio de cookies.
89
- * Exemplo: ["http://localhost:3000", "https://meusite.com"]
89
+ * List of specific origins allowed for sending cookies.
90
+ * Example: ["http://localhost:3000", "https://mysite.com"]
90
91
  */
91
92
  cookieOrigins?: string[];
92
93
  /**
93
- * Se true, habilita envio de cookies cross-origin.
94
+ * If true, enables cross-origin cookie sending.
94
95
  * Default: false
95
96
  */
96
97
  credentials?: boolean;
97
98
  /**
98
- * Lista de métodos HTTP permitidos, separados por vírgula.
99
+ * List of allowed HTTP methods, separated by commas.
99
100
  * Default: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
100
101
  */
101
102
  methods?: string;
102
103
  /**
103
- * Lista de cabeçalhos permitidos na requisição.
104
+ * List of headers allowed in the request.
104
105
  * Default: "Content-Type, Authorization"
105
106
  */
106
107
  headers?: string;
107
108
  /**
108
- * Cabeçalhos expostos ao cliente.
109
- * Exemplo: ["X-Custom-Header"]
109
+ * Headers displayed to the customer.
110
+ * Example: ["X-Custom-Header"]
110
111
  */
111
112
  exposedHeaders?: string;
112
113
  /**
113
- * Tempo máximo de cache para requisições prévias (preflight), em segundos.
114
+ * Maximum cache time for preflight requests, in seconds.
114
115
  */
115
116
  maxAge?: number;
116
117
  };
@@ -129,38 +130,15 @@ export type CreateAppOptions = {
129
130
  * but before route mounting.
130
131
  */
131
132
  middlewares?: MiddlewareMap;
133
+ /**
134
+ * Controls the display of the X-Powered-By header in HTTP responses.
135
+ *
136
+ * **With `powered: true` (default):
137
+ **/
138
+ powered?: boolean;
132
139
  };
133
- /**
134
- * Bootstraps and configures a Fastay application.
135
- *
136
- * Fastay automatically:
137
- * - Discovers and registers routes defined in `apiDir`.
138
- * - Applies both built-in and user-provided middlewares.
139
- * - Exposes a health-check endpoint at `/_health`.
140
- *
141
- * @param opts - Configuration options for the Fastay application.
142
- * @returns A Promise that resolves to an Express `Application` instance.
143
- *
144
- * @example
145
- * ```ts
146
- * import { createApp } from '@syntay/fastay';
147
- * import cors from 'cors';
148
- * import helmet from 'helmet';
149
- *
150
- * void (async () => {
151
- * await createApp({
152
- * apiDir: './src/api',
153
- * baseRoute: '/api',
154
- * port: 5555,
155
- * expressOptions: {
156
- * middlewares: [cors(), helmet()],
157
- * },
158
- * });
159
- * })();
160
- * ```
161
- */
162
140
  export declare function createApp(opts?: CreateAppOptions): Promise<{
163
141
  app: import("express-serve-static-core").Express;
164
- server: import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
142
+ server: Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
165
143
  }>;
166
144
  //# sourceMappingURL=app.d.ts.map
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAA4C,MAAM,SAAS,CAAC;AAInE,OAAO,EACL,aAAa,EAGd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKvD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;IAEvC;;;OAGG;IACH,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7D;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IAE3C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,kBAAkB,CAAC;KAC9B,CAAC;IAEF;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IAEF;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,UAAU,CAAC,EAAE;QACX;;;WAGG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;QAEzB;;;WAGG;QACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QAEzB;;;WAGG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;QAEtB;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;QAExB;;WAEG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAC,IAAI,CAAC,EAAE,gBAAgB;;;GAuItD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAA4C,MAAM,SAAS,CAAC;AACnE,OAAO,EAAgB,MAAM,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EACL,aAAa,EAGd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKvD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;IAEvC;;;OAGG;IACH,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7D;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IAE3C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,kBAAkB,CAAC;KAC9B,CAAC;IAEF;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IAEF;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,UAAU,CAAC,EAAE;QACX;;;WAGG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;QAEzB;;;WAGG;QACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QAEzB;;;WAGG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;QAEtB;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;QAExB;;WAEG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;IAE5B;;;;QAII;IACJ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAwFF,wBAAsB,SAAS,CAAC,IAAI,CAAC,EAAE,gBAAgB;;;GAqMtD"}
package/dist/app.js CHANGED
@@ -1,12 +1,12 @@
1
- import express from 'express';
2
- import { createServer } from 'node:http';
3
- import path from 'path';
4
- import { loadApiRoutes } from './router.js';
5
- import { loadFastayMiddlewares, createMiddleware } from './middleware.js';
6
- import { logger } from './logger.js';
7
- import { printBanner } from './banner.js';
8
- import { RequestCookies } from './utils/cookies.js';
9
- import { formDataMiddleware } from './utils/formDataMiddleware.js';
1
+ import express from "express";
2
+ import { createServer } from "node:http";
3
+ import path from "path";
4
+ import { loadApiRoutes } from "./router.js";
5
+ import { loadFastayMiddlewares, createMiddleware, } from "./middleware.js";
6
+ import { logger } from "./logger.js";
7
+ import { printBanner } from "./banner.js";
8
+ import { RequestCookies } from "./utils/cookies.js";
9
+ import { formDataMiddleware } from "./utils/formDataMiddleware.js";
10
10
  /**
11
11
  * Bootstraps and configures a Fastay application.
12
12
  *
@@ -36,51 +36,109 @@ import { formDataMiddleware } from './utils/formDataMiddleware.js';
36
36
  * })();
37
37
  * ```
38
38
  */
39
+ /** pre-compiled CORS */
40
+ function createCorsHandler(opts) {
41
+ if (!opts)
42
+ return null;
43
+ const { allowAnyOrigin = false, cookieOrigins = [], credentials = false, methods = "GET,POST,PUT,PATCH,DELETE,OPTIONS", headers = "Content-Type, Authorization", exposedHeaders, maxAge, } = opts;
44
+ return (req, res, next) => {
45
+ // Determine the origin in an optimized way.
46
+ let origin = "*";
47
+ if (credentials && cookieOrigins.length > 0) {
48
+ const requestOrigin = req.headers.origin;
49
+ if (requestOrigin && cookieOrigins.includes(requestOrigin)) {
50
+ origin = requestOrigin;
51
+ }
52
+ else {
53
+ origin = "";
54
+ }
55
+ }
56
+ else if (!credentials && allowAnyOrigin) {
57
+ origin = "*";
58
+ }
59
+ const corsHeaders = {
60
+ "Access-Control-Allow-Origin": origin,
61
+ "Access-Control-Allow-Credentials": credentials ? "true" : "false",
62
+ "Access-Control-Allow-Methods": methods,
63
+ "Access-Control-Allow-Headers": headers,
64
+ };
65
+ if (exposedHeaders) {
66
+ corsHeaders["Access-Control-Expose-Headers"] = exposedHeaders;
67
+ }
68
+ if (maxAge) {
69
+ corsHeaders["Access-Control-Max-Age"] = maxAge.toString();
70
+ }
71
+ for (const [key, value] of Object.entries(corsHeaders)) {
72
+ res.setHeader(key, value);
73
+ }
74
+ if (req.method === "OPTIONS") {
75
+ return res.sendStatus(204);
76
+ }
77
+ next();
78
+ };
79
+ }
39
80
  export async function createApp(opts) {
40
81
  const start = logger.timeStart();
41
82
  printBanner();
42
83
  // logger.group('Fastay');
43
- logger.info('Initializing server...');
44
- const apiDir = opts?.apiDir ?? path.resolve(process.cwd(), 'src', 'api');
45
- const baseRoute = opts?.baseRoute ?? '/api';
84
+ logger.info("Initializing server...");
85
+ const apiDir = opts?.apiDir ?? path.resolve(process.cwd(), "src", "api");
86
+ const baseRoute = opts?.baseRoute ?? "/api";
87
+ const port = opts?.port ?? 5000;
46
88
  logger.success(`API directory: ${apiDir}`);
47
89
  logger.success(`Base route: ${baseRoute}`);
48
90
  const app = express();
49
91
  const server = createServer(app);
50
92
  if (opts?.expressOptions) {
51
93
  for (const [key, value] of Object.entries(opts.expressOptions)) {
52
- // Se for array → assume middleware global
53
94
  if (Array.isArray(value)) {
54
- value.forEach(mw => app.use(mw));
95
+ value.forEach((mw) => app.use(mw));
55
96
  }
56
- // Se o app tiver método com esse nome
57
- else if (typeof app[key] === 'function') {
97
+ else if (typeof app[key] === "function") {
58
98
  // TS-safe
59
99
  app[key](value);
60
100
  }
61
101
  // special cases
62
- else if (key === 'static' && value && typeof value === 'object') {
102
+ else if (key === "static" && value && typeof value === "object") {
63
103
  const v = value;
64
104
  app.use(express.static(v.path, v.options));
65
105
  }
66
- else if (key === 'jsonOptions') {
106
+ else if (key === "jsonOptions") {
67
107
  app.use(express.json(value));
68
108
  }
69
- else if (key === 'urlencodedOptions') {
109
+ else if (key === "urlencodedOptions") {
70
110
  app.use(express.urlencoded(value));
71
111
  }
72
112
  }
73
113
  }
74
- app.use(express.json());
75
- const defaltPort = opts?.port ? opts.port : 6000;
76
- server.listen(defaltPort, () => {
77
- logger.success(`Server running at http://localhost:${defaltPort}${baseRoute}`);
114
+ server.listen(port, () => {
115
+ logger.success(`Server running at http://localhost:${port}${baseRoute}`);
116
+ });
117
+ // CORS handler
118
+ const corsHandler = createCorsHandler(opts?.enableCors);
119
+ if (corsHandler) {
120
+ app.use(corsHandler);
121
+ }
122
+ // FormData middleware
123
+ app.use(formDataMiddleware());
124
+ // Fastay middlewares
125
+ if (opts?.middlewares) {
126
+ logger.group("Fastay Middlewares");
127
+ const apply = createMiddleware(opts.middlewares);
128
+ apply(app);
129
+ }
130
+ // Auto middlewares
131
+ await loadFastayMiddlewares(app);
132
+ // Health check
133
+ app.get("/health", (_, res) => {
134
+ res.setHeader("Content-Type", "application/json");
135
+ res.send('{"ok":true}');
78
136
  });
79
- // external middlewares
137
+ // External middlewares
80
138
  if (opts?.expressOptions?.middlewares) {
81
- logger.group('Express Middlewares');
139
+ logger.group("Express Middlewares");
82
140
  for (const mw of opts.expressOptions.middlewares) {
83
- logger.gear(`Loaded: ${mw.name || 'anonymous'}`);
141
+ logger.gear(`Loaded: ${mw.name || "anonymous"}`);
84
142
  app.use(mw);
85
143
  }
86
144
  }
@@ -88,54 +146,91 @@ export async function createApp(opts) {
88
146
  app.use(formDataMiddleware());
89
147
  // Fastay middlewares
90
148
  if (opts?.middlewares) {
91
- logger.group('Fastay Middlewares');
149
+ logger.group("Fastay Middlewares");
92
150
  const apply = createMiddleware(opts.middlewares);
93
151
  apply(app);
94
152
  }
95
153
  // automatic middlewares
96
154
  // logger.group('Fastay Auto-Middlewares');
97
155
  const isMiddleware = await loadFastayMiddlewares(app);
156
+ if (!opts?.expressOptions?.jsonOptions) {
157
+ app.use(express.json({ limit: "10mb" }));
158
+ }
98
159
  // health check
99
- app.get('/_health', (_, res) => res.json({ ok: true }));
160
+ app.get("/_health", (_, res) => res.json({ ok: true }));
100
161
  app.use((req, res, next) => {
101
- res.setHeader('X-Powered-By', 'Syntay Engine');
162
+ res.setHeader("X-Powered-By", "Syntay Engine");
102
163
  req.cookies = new RequestCookies(req.headers.cookie);
103
164
  const corsOpts = opts?.enableCors || {};
104
- // Determina a origem
105
- let origin = '*';
165
+ // Determine the origin
166
+ let origin = "*";
106
167
  if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
107
- // Se a origem estiver na lista de cookieOrigins, permite cookies
168
+ // If the origin is in the cookieOrigins list, cookies are allowed.
108
169
  if (req.headers.origin &&
109
170
  corsOpts.cookieOrigins.includes(req.headers.origin)) {
110
171
  origin = req.headers.origin;
111
172
  }
112
173
  else {
113
- origin = ''; // bloqueia cookies para outras origens
174
+ origin = ""; // blocks cookies from other sources
114
175
  }
115
176
  }
116
177
  else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
117
- origin = '*';
178
+ origin = "*";
118
179
  }
119
- res.setHeader('Access-Control-Allow-Origin', origin);
120
- res.setHeader('Access-Control-Allow-Credentials', corsOpts.credentials ? 'true' : 'false');
121
- res.setHeader('Access-Control-Allow-Methods', corsOpts.methods || 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
122
- res.setHeader('Access-Control-Allow-Headers', corsOpts.headers || 'Content-Type, Authorization');
180
+ res.setHeader("Access-Control-Allow-Origin", origin);
181
+ res.setHeader("Access-Control-Allow-Credentials", corsOpts.credentials ? "true" : "false");
182
+ res.setHeader("Access-Control-Allow-Methods", corsOpts.methods || "GET,POST,PUT,PATCH,DELETE,OPTIONS");
183
+ res.setHeader("Access-Control-Allow-Headers", corsOpts.headers || "Content-Type, Authorization");
123
184
  if (corsOpts.exposedHeaders) {
124
- res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
185
+ res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
125
186
  }
126
187
  if (corsOpts.maxAge) {
127
- res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
188
+ res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
128
189
  }
129
- if (req.method === 'OPTIONS')
190
+ if (req.method === "OPTIONS")
130
191
  return res.sendStatus(204);
131
192
  next();
132
193
  });
133
- // load routes
134
- // logger.group('Routes Loaded');
194
+ app.use((req, res, next) => {
195
+ opts?.powered && res.setHeader("X-Powered-By", "Syntay Engine");
196
+ // Optimized cookie parsing
197
+ req.cookies = new RequestCookies(req.headers.cookie);
198
+ next();
199
+ });
200
+ // Route loading
135
201
  const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
136
- logger.success(`Total routes loaded: ${totalRoutes}`);
202
+ // The error handler should come after the routes.
203
+ if (opts?.expressOptions?.errorHandler) {
204
+ app.use(opts.expressOptions.errorHandler);
205
+ }
206
+ else {
207
+ // Optimized default error handler
208
+ app.use((err, req, res, next) => {
209
+ logger.error(`Unhandled Error [${req.method} ${req.path}]: ${err.message}`);
210
+ res.status(500).json({
211
+ error: "Internal Server Error",
212
+ ...(process.env.NODE_ENV === "development" && { detail: err.message }),
213
+ });
214
+ });
215
+ }
216
+ // // load routes
217
+ // // logger.group('Routes Loaded');
218
+ // const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
219
+ // 404 handler
220
+ app.use((req, res) => {
221
+ res.status(404).json({
222
+ error: "Not Found",
223
+ path: req.originalUrl,
224
+ });
225
+ });
226
+ // server.listen(port);
137
227
  // app.use(errorHandler);
138
228
  const time = logger.timeEnd(start);
229
+ logger.success(`Total routes loaded: ${totalRoutes}`);
139
230
  logger.success(`Boot completed in ${time}ms`);
231
+ if (process.env.NODE_ENV === "development") {
232
+ const used = process.memoryUsage();
233
+ // logger.info(`Memory: ${Math.round(used.heapUsed / 1024 / 1024)}MB`);
234
+ }
140
235
  return { app, server };
141
236
  }
@@ -1,3 +1,14 @@
1
- import { Request, Response, NextFunction } from 'express';
2
- export declare function errorHandler(err: any, req: Request, res: Response, next: NextFunction): Response<any, Record<string, any>>;
1
+ import { Request, Response, NextFunction } from "express";
2
+ /**
3
+ * Handler error
4
+ */
5
+ export declare function errorHandler(err: any, req: Request, res: Response, next: NextFunction): void | Response<any, Record<string, any>>;
6
+ /**
7
+ * Factory
8
+ */
9
+ export declare function createErrorHandler(options?: {
10
+ logDetails?: boolean;
11
+ includeStack?: boolean;
12
+ customMessages?: Record<string, string>;
13
+ }): (err: any, req: Request, res: Response, next: NextFunction) => void;
3
14
  //# sourceMappingURL=error-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI1D,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,sCAuCnB"}
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA4G1D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,6CAoEnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC,IAQS,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAwBlE"}
@@ -1,36 +1,170 @@
1
- import { logger } from './logger';
2
- import fs from 'fs';
1
+ import { logger } from "./logger.js";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ // Cache for read files
5
+ const fileCache = new Map();
6
+ /**
7
+ * Extracts information from the stack trace in an optimized way.
8
+ */
9
+ function extractErrorInfo(err) {
10
+ if (!err.stack)
11
+ return null;
12
+ // Get the first relevant line from the stack.
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
+ // Optimized regex to capture file, line and column.
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
+ * Reads code snippets in an optimized way with caching.
29
+ */
30
+ async function getCodeSnippet(filePath, lineNumber) {
31
+ try {
32
+ // Normalize file path
33
+ const normalizedPath = path.resolve(filePath);
34
+ // Check 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
+ // Read the file only if it is a .ts/.js file from the project.
41
+ if (!normalizedPath.includes("node_modules") &&
42
+ (normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".js"))) {
43
+ const content = await fs.readFile(normalizedPath, "utf-8");
44
+ // Cache only in development
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
+ // Ignore reading errors
54
+ }
55
+ return "";
56
+ }
57
+ /**
58
+ * Optimized error logging
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 only for unexpected errors
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
+ // Minimalist log in production
75
+ logger.error(`[${route}] ${err.name}: ${err.message}`);
76
+ // Detailed log for critical errors only
77
+ if (err instanceof SyntaxError || err.message?.includes("Unexpected")) {
78
+ logger.raw(err.stack?.split("\n")[0] || "");
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * Handler error
84
+ */
3
85
  export function errorHandler(err, req, res, next) {
4
- const isSyntaxError = err.name === 'SyntaxError' || err.message?.includes('Unexpected');
86
+ // If headers have already been sent, delegate to the next handler.
87
+ if (res.headersSent) {
88
+ return next(err);
89
+ }
5
90
  const route = `${req.method} ${req.originalUrl}`;
6
- // Tenta extrair arquivo, linha e coluna (quando stack estiver presente)
7
- let fileInfo = '';
8
- if (err.stack) {
9
- const stackLine = err.stack.split('\n')[1]; // pega primeira linha depois do erro
10
- const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
11
- if (match) {
12
- const [_, file, line, col] = match;
13
- fileInfo = `${file}:${line}:${col}`;
14
- // Tenta mostrar o trecho da linha que deu erro
15
- if (fs.existsSync(file)) {
16
- const codeLines = fs.readFileSync(file, 'utf-8').split('\n');
17
- const codeSnippet = codeLines[parseInt(line) - 1].trim();
18
- fileInfo += ` → ${codeSnippet}`;
19
- }
91
+ const error = err instanceof Error ? err : new Error(String(err));
92
+ let fileInfo = "";
93
+ if (process.env.NODE_ENV === "development") {
94
+ const errorInfo = extractErrorInfo(error);
95
+ if (errorInfo) {
96
+ fileInfo = `${path.basename(errorInfo.file)}:${errorInfo.line}:${errorInfo.column}`;
97
+ // Load snippet asynchronously
98
+ getCodeSnippet(errorInfo.file, parseInt(errorInfo.line))
99
+ .then((snippet) => {
100
+ if (snippet) {
101
+ logger.error(`Code: ${snippet}`);
102
+ }
103
+ })
104
+ .catch(() => { }); // Ignore reading errors
20
105
  }
21
106
  }
22
- logger.group(`✗ Runtime Error in route [${route}]`);
23
- logger.error(`${err.name}: ${err.message}`);
24
- if (fileInfo)
25
- logger.error(`Location: ${fileInfo}`);
26
- if (process.env.NODE_ENV === 'production') {
27
- return res.status(500).json({
28
- error: 'Internal server error',
29
- });
107
+ logError(error, route, fileInfo);
108
+ let statusCode = 500;
109
+ let errorMessage = "Internal Server Error";
110
+ if (error instanceof SyntaxError || error.message?.includes("Unexpected")) {
111
+ statusCode = 400;
112
+ errorMessage = "Invalid Request";
113
+ }
114
+ else if (error.name === "ValidationError") {
115
+ statusCode = 422;
116
+ errorMessage = "Validation Failed";
117
+ }
118
+ const isDev = process.env.NODE_ENV === "development";
119
+ res.status(statusCode);
120
+ const response = {
121
+ error: errorMessage,
122
+ status: statusCode,
123
+ path: req.originalUrl,
124
+ };
125
+ if (isDev) {
126
+ response.message = error.message;
127
+ if (error.stack && statusCode === 500) {
128
+ response.stack = error.stack.split("\n").slice(0, 3);
129
+ }
130
+ if (fileInfo) {
131
+ response.location = fileInfo;
132
+ }
30
133
  }
31
- return res.status(500).json({
32
- error: err.message,
33
- stack: err.stack,
34
- file: fileInfo || undefined,
35
- });
134
+ res.setHeader("Content-Type", "application/json");
135
+ res.setHeader("X-Error-Type", error.name);
136
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
137
+ return res.json(response);
138
+ }
139
+ /**
140
+ * Factory
141
+ */
142
+ export function createErrorHandler(options) {
143
+ const opts = {
144
+ logDetails: process.env.NODE_ENV === "development",
145
+ includeStack: process.env.NODE_ENV === "development",
146
+ customMessages: {},
147
+ ...options,
148
+ };
149
+ return (err, req, res, next) => {
150
+ if (res.headersSent)
151
+ return next(err);
152
+ const error = err instanceof Error ? err : new Error(String(err));
153
+ const route = `${req.method} ${req.originalUrl}`;
154
+ if (opts.logDetails) {
155
+ logger.error(`[${route}] ${error.name}: ${error.message}`);
156
+ }
157
+ let statusCode = 500;
158
+ if (error.name in opts.customMessages) {
159
+ statusCode = 400;
160
+ }
161
+ // Response
162
+ res.status(statusCode).json({
163
+ error: opts.customMessages[error.name] || "Internal Server Error",
164
+ ...(opts.includeStack && {
165
+ details: error.message,
166
+ ...(statusCode === 500 && { stack: error.stack?.split("\n")[0] }),
167
+ }),
168
+ });
169
+ };
36
170
  }
@@ -0,0 +1,14 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ /**
3
+ * Handler de erros otimizado para Fastay
4
+ */
5
+ export declare function errorHandler(err: any, req: Request, res: Response, next: NextFunction): void | Response<any, Record<string, any>>;
6
+ /**
7
+ * Factory para criar error handlers customizados
8
+ */
9
+ export declare function createErrorHandler(options?: {
10
+ logDetails?: boolean;
11
+ includeStack?: boolean;
12
+ customMessages?: Record<string, string>;
13
+ }): (err: any, req: Request, res: Response, next: NextFunction) => void;
14
+ //# sourceMappingURL=error-hanler2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-hanler2.d.ts","sourceRoot":"","sources":["../src/error-hanler2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA4G1D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,6CA4EnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC,IAQS,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UA0BlE"}