@syntay/fastay 0.2.9 → 1.0.1

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.
@@ -75,6 +76,11 @@ export type CreateAppOptions = {
75
76
  * Default: "/api"
76
77
  */
77
78
  baseRoute?: string;
79
+ /**
80
+ * Application mode
81
+ * Default: "dev"
82
+ */
83
+ mode?: "dev" | "prod" | "test";
78
84
  /**
79
85
  * Configuration to enable CORS (Cross-Origin Resource Sharing) in Fastay.
80
86
  */
@@ -85,32 +91,32 @@ export type CreateAppOptions = {
85
91
  */
86
92
  allowAnyOrigin?: boolean;
87
93
  /**
88
- * Lista de origens específicas permitidas para envio de cookies.
89
- * Exemplo: ["http://localhost:3000", "https://meusite.com"]
94
+ * List of specific origins allowed for sending cookies.
95
+ * Example: ["http://localhost:3000", "https://mysite.com"]
90
96
  */
91
97
  cookieOrigins?: string[];
92
98
  /**
93
- * Se true, habilita envio de cookies cross-origin.
99
+ * If true, enables cross-origin cookie sending.
94
100
  * Default: false
95
101
  */
96
102
  credentials?: boolean;
97
103
  /**
98
- * Lista de métodos HTTP permitidos, separados por vírgula.
104
+ * List of allowed HTTP methods, separated by commas.
99
105
  * Default: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
100
106
  */
101
107
  methods?: string;
102
108
  /**
103
- * Lista de cabeçalhos permitidos na requisição.
109
+ * List of headers allowed in the request.
104
110
  * Default: "Content-Type, Authorization"
105
111
  */
106
112
  headers?: string;
107
113
  /**
108
- * Cabeçalhos expostos ao cliente.
109
- * Exemplo: ["X-Custom-Header"]
114
+ * Headers displayed to the customer.
115
+ * Example: ["X-Custom-Header"]
110
116
  */
111
117
  exposedHeaders?: string;
112
118
  /**
113
- * Tempo máximo de cache para requisições prévias (preflight), em segundos.
119
+ * Maximum cache time for preflight requests, in seconds.
114
120
  */
115
121
  maxAge?: number;
116
122
  };
@@ -129,38 +135,15 @@ export type CreateAppOptions = {
129
135
  * but before route mounting.
130
136
  */
131
137
  middlewares?: MiddlewareMap;
138
+ /**
139
+ * Controls the display of the X-Powered-By header in HTTP responses.
140
+ *
141
+ * **With `powered: true` (default):
142
+ **/
143
+ powered?: boolean;
132
144
  };
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
145
  export declare function createApp(opts?: CreateAppOptions): Promise<{
163
146
  app: import("express-serve-static-core").Express;
164
- server: import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
147
+ server: Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
165
148
  }>;
166
149
  //# 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;;;OAGG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAE/B;;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;;;GA2LtD"}
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,106 +36,194 @@ 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;
88
+ const mode = opts?.mode ?? "dev";
46
89
  logger.success(`API directory: ${apiDir}`);
47
90
  logger.success(`Base route: ${baseRoute}`);
48
91
  const app = express();
49
92
  const server = createServer(app);
50
93
  if (opts?.expressOptions) {
51
94
  for (const [key, value] of Object.entries(opts.expressOptions)) {
52
- // Se for array → assume middleware global
53
95
  if (Array.isArray(value)) {
54
- value.forEach(mw => app.use(mw));
96
+ value.forEach((mw) => app.use(mw));
55
97
  }
56
- // Se o app tiver método com esse nome
57
- else if (typeof app[key] === 'function') {
98
+ else if (typeof app[key] === "function") {
58
99
  // TS-safe
59
100
  app[key](value);
60
101
  }
61
102
  // special cases
62
- else if (key === 'static' && value && typeof value === 'object') {
103
+ else if (key === "static" && value && typeof value === "object") {
63
104
  const v = value;
64
105
  app.use(express.static(v.path, v.options));
65
106
  }
66
- else if (key === 'jsonOptions') {
107
+ else if (key === "jsonOptions") {
67
108
  app.use(express.json(value));
68
109
  }
69
- else if (key === 'urlencodedOptions') {
110
+ else if (key === "urlencodedOptions") {
70
111
  app.use(express.urlencoded(value));
71
112
  }
72
113
  }
73
114
  }
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}`);
78
- });
79
- // external middlewares
80
- if (opts?.expressOptions?.middlewares) {
81
- logger.group('Express Middlewares');
82
- for (const mw of opts.expressOptions.middlewares) {
83
- logger.gear(`Loaded: ${mw.name || 'anonymous'}`);
84
- app.use(mw);
85
- }
115
+ if (mode == "dev" || mode == "prod") {
116
+ server.listen(port, () => {
117
+ logger.success(`Server running at http://localhost:${port}${baseRoute}`);
118
+ });
119
+ }
120
+ else {
121
+ logger.info("Test mode: server.listen skipped");
122
+ }
123
+ // CORS handler
124
+ const corsHandler = createCorsHandler(opts?.enableCors);
125
+ if (corsHandler) {
126
+ app.use(corsHandler);
86
127
  }
87
128
  // FormData middleware
88
129
  app.use(formDataMiddleware());
89
130
  // Fastay middlewares
90
131
  if (opts?.middlewares) {
91
- logger.group('Fastay Middlewares');
132
+ logger.group("Fastay Middlewares");
92
133
  const apply = createMiddleware(opts.middlewares);
93
134
  apply(app);
94
135
  }
95
- // automatic middlewares
96
- // logger.group('Fastay Auto-Middlewares');
97
- const isMiddleware = await loadFastayMiddlewares(app);
98
- // health check
99
- app.get('/_health', (_, res) => res.json({ ok: true }));
136
+ // Auto middlewares
137
+ await loadFastayMiddlewares(app);
138
+ // Health check
139
+ app.get("/health", (_, res) => {
140
+ res.setHeader("Content-Type", "application/json");
141
+ res.send('{"ok":true}');
142
+ });
143
+ // External middlewares
144
+ if (opts?.expressOptions?.middlewares) {
145
+ logger.group("Express Middlewares");
146
+ for (const mw of opts.expressOptions.middlewares) {
147
+ logger.gear(`Loaded: ${mw.name || "anonymous"}`);
148
+ app.use(mw);
149
+ }
150
+ }
151
+ if (!opts?.expressOptions?.jsonOptions) {
152
+ app.use(express.json({ limit: "10mb" }));
153
+ }
100
154
  app.use((req, res, next) => {
101
- res.setHeader('X-Powered-By', 'Syntay Engine');
155
+ res.setHeader("X-Powered-By", "Syntay Engine");
102
156
  req.cookies = new RequestCookies(req.headers.cookie);
103
157
  const corsOpts = opts?.enableCors || {};
104
- // Determina a origem
105
- let origin = '*';
158
+ // Determine the origin
159
+ let origin = "*";
106
160
  if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
107
- // Se a origem estiver na lista de cookieOrigins, permite cookies
161
+ // If the origin is in the cookieOrigins list, cookies are allowed.
108
162
  if (req.headers.origin &&
109
163
  corsOpts.cookieOrigins.includes(req.headers.origin)) {
110
164
  origin = req.headers.origin;
111
165
  }
112
166
  else {
113
- origin = ''; // bloqueia cookies para outras origens
167
+ origin = ""; // blocks cookies from other sources
114
168
  }
115
169
  }
116
170
  else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
117
- origin = '*';
171
+ origin = "*";
118
172
  }
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');
173
+ res.setHeader("Access-Control-Allow-Origin", origin);
174
+ res.setHeader("Access-Control-Allow-Credentials", corsOpts.credentials ? "true" : "false");
175
+ res.setHeader("Access-Control-Allow-Methods", corsOpts.methods || "GET,POST,PUT,PATCH,DELETE,OPTIONS");
176
+ res.setHeader("Access-Control-Allow-Headers", corsOpts.headers || "Content-Type, Authorization");
123
177
  if (corsOpts.exposedHeaders) {
124
- res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
178
+ res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
125
179
  }
126
180
  if (corsOpts.maxAge) {
127
- res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
181
+ res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
128
182
  }
129
- if (req.method === 'OPTIONS')
183
+ if (req.method === "OPTIONS")
130
184
  return res.sendStatus(204);
131
185
  next();
132
186
  });
133
- // load routes
134
- // logger.group('Routes Loaded');
187
+ app.use((req, res, next) => {
188
+ opts?.powered && res.setHeader("X-Powered-By", "Syntay Engine");
189
+ // Optimized cookie parsing
190
+ req.cookies = new RequestCookies(req.headers.cookie);
191
+ next();
192
+ });
193
+ // Route loading
135
194
  const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
136
- logger.success(`Total routes loaded: ${totalRoutes}`);
195
+ // The error handler should come after the routes.
196
+ if (opts?.expressOptions?.errorHandler) {
197
+ app.use(opts.expressOptions.errorHandler);
198
+ }
199
+ else {
200
+ // Optimized default error handler
201
+ app.use((err, req, res, next) => {
202
+ logger.error(`Unhandled Error [${req.method} ${req.path}]: ${err.message}`);
203
+ res.status(500).json({
204
+ error: "Internal Server Error",
205
+ ...(process.env.NODE_ENV === "development" && { detail: err.message }),
206
+ });
207
+ });
208
+ }
209
+ // // load routes
210
+ // // logger.group('Routes Loaded');
211
+ // const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
212
+ // 404 handler
213
+ app.use((req, res) => {
214
+ res.status(404).json({
215
+ error: "Not Found",
216
+ path: req.originalUrl,
217
+ });
218
+ });
219
+ // server.listen(port);
137
220
  // app.use(errorHandler);
138
221
  const time = logger.timeEnd(start);
222
+ logger.success(`Total routes loaded: ${totalRoutes}`);
139
223
  logger.success(`Boot completed in ${time}ms`);
224
+ if (process.env.NODE_ENV === "development") {
225
+ const used = process.memoryUsage();
226
+ // logger.info(`Memory: ${Math.round(used.heapUsed / 1024 / 1024)}MB`);
227
+ }
140
228
  return { app, server };
141
229
  }
@@ -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"}