@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 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,35 +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
- export declare function createApp(opts?: CreateAppOptions): Promise<import("express-serve-static-core").Express>;
140
+ export declare function createApp(opts?: CreateAppOptions): Promise<{
141
+ app: import("express-serve-static-core").Express;
142
+ server: Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
143
+ }>;
163
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;AAGnE,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,wDAsItD"}
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,11 +1,12 @@
1
- import express from 'express';
2
- import path from 'path';
3
- import { loadApiRoutes } from './router.js';
4
- import { loadFastayMiddlewares, createMiddleware, } from './middleware.js';
5
- import { logger } from './logger.js';
6
- import { printBanner } from './banner.js';
7
- import { RequestCookies } from './utils/cookies.js';
8
- 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";
9
10
  /**
10
11
  * Bootstraps and configures a Fastay application.
11
12
  *
@@ -35,50 +36,109 @@ import { formDataMiddleware } from './utils/formDataMiddleware.js';
35
36
  * })();
36
37
  * ```
37
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
+ }
38
80
  export async function createApp(opts) {
39
81
  const start = logger.timeStart();
40
82
  printBanner();
41
83
  // logger.group('Fastay');
42
- logger.info('Initializing server...');
43
- const apiDir = opts?.apiDir ?? path.resolve(process.cwd(), 'src', 'api');
44
- 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;
45
88
  logger.success(`API directory: ${apiDir}`);
46
89
  logger.success(`Base route: ${baseRoute}`);
47
90
  const app = express();
91
+ const server = createServer(app);
48
92
  if (opts?.expressOptions) {
49
93
  for (const [key, value] of Object.entries(opts.expressOptions)) {
50
- // Se for array → assume middleware global
51
94
  if (Array.isArray(value)) {
52
95
  value.forEach((mw) => app.use(mw));
53
96
  }
54
- // Se o app tiver método com esse nome
55
- else if (typeof app[key] === 'function') {
97
+ else if (typeof app[key] === "function") {
56
98
  // TS-safe
57
99
  app[key](value);
58
100
  }
59
101
  // special cases
60
- else if (key === 'static' && value && typeof value === 'object') {
102
+ else if (key === "static" && value && typeof value === "object") {
61
103
  const v = value;
62
104
  app.use(express.static(v.path, v.options));
63
105
  }
64
- else if (key === 'jsonOptions') {
106
+ else if (key === "jsonOptions") {
65
107
  app.use(express.json(value));
66
108
  }
67
- else if (key === 'urlencodedOptions') {
109
+ else if (key === "urlencodedOptions") {
68
110
  app.use(express.urlencoded(value));
69
111
  }
70
112
  }
71
113
  }
72
- app.use(express.json());
73
- const defaltPort = opts?.port ? opts.port : 6000;
74
- app.listen(defaltPort, () => {
75
- 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}');
76
136
  });
77
- // external middlewares
137
+ // External middlewares
78
138
  if (opts?.expressOptions?.middlewares) {
79
- logger.group('Express Middlewares');
139
+ logger.group("Express Middlewares");
80
140
  for (const mw of opts.expressOptions.middlewares) {
81
- logger.gear(`Loaded: ${mw.name || 'anonymous'}`);
141
+ logger.gear(`Loaded: ${mw.name || "anonymous"}`);
82
142
  app.use(mw);
83
143
  }
84
144
  }
@@ -86,54 +146,91 @@ export async function createApp(opts) {
86
146
  app.use(formDataMiddleware());
87
147
  // Fastay middlewares
88
148
  if (opts?.middlewares) {
89
- logger.group('Fastay Middlewares');
149
+ logger.group("Fastay Middlewares");
90
150
  const apply = createMiddleware(opts.middlewares);
91
151
  apply(app);
92
152
  }
93
153
  // automatic middlewares
94
154
  // logger.group('Fastay Auto-Middlewares');
95
155
  const isMiddleware = await loadFastayMiddlewares(app);
156
+ if (!opts?.expressOptions?.jsonOptions) {
157
+ app.use(express.json({ limit: "10mb" }));
158
+ }
96
159
  // health check
97
- app.get('/_health', (_, res) => res.json({ ok: true }));
160
+ app.get("/_health", (_, res) => res.json({ ok: true }));
98
161
  app.use((req, res, next) => {
99
- res.setHeader('X-Powered-By', 'Syntay Engine');
162
+ res.setHeader("X-Powered-By", "Syntay Engine");
100
163
  req.cookies = new RequestCookies(req.headers.cookie);
101
164
  const corsOpts = opts?.enableCors || {};
102
- // Determina a origem
103
- let origin = '*';
165
+ // Determine the origin
166
+ let origin = "*";
104
167
  if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
105
- // Se a origem estiver na lista de cookieOrigins, permite cookies
168
+ // If the origin is in the cookieOrigins list, cookies are allowed.
106
169
  if (req.headers.origin &&
107
170
  corsOpts.cookieOrigins.includes(req.headers.origin)) {
108
171
  origin = req.headers.origin;
109
172
  }
110
173
  else {
111
- origin = ''; // bloqueia cookies para outras origens
174
+ origin = ""; // blocks cookies from other sources
112
175
  }
113
176
  }
114
177
  else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
115
- origin = '*';
178
+ origin = "*";
116
179
  }
117
- res.setHeader('Access-Control-Allow-Origin', origin);
118
- res.setHeader('Access-Control-Allow-Credentials', corsOpts.credentials ? 'true' : 'false');
119
- res.setHeader('Access-Control-Allow-Methods', corsOpts.methods || 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
120
- 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");
121
184
  if (corsOpts.exposedHeaders) {
122
- res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
185
+ res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
123
186
  }
124
187
  if (corsOpts.maxAge) {
125
- res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
188
+ res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
126
189
  }
127
- if (req.method === 'OPTIONS')
190
+ if (req.method === "OPTIONS")
128
191
  return res.sendStatus(204);
129
192
  next();
130
193
  });
131
- // load routes
132
- // 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
133
201
  const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
134
- 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);
135
227
  // app.use(errorHandler);
136
228
  const time = logger.timeEnd(start);
229
+ logger.success(`Total routes loaded: ${totalRoutes}`);
137
230
  logger.success(`Boot completed in ${time}ms`);
138
- return app;
231
+ if (process.env.NODE_ENV === "development") {
232
+ const used = process.memoryUsage();
233
+ // logger.info(`Memory: ${Math.round(used.heapUsed / 1024 / 1024)}MB`);
234
+ }
235
+ return { app, server };
139
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"}