@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 +22 -41
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +140 -43
- package/dist/error-handler.d.ts +13 -2
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +164 -30
- package/dist/error-hanler2.d.ts +14 -0
- package/dist/error-hanler2.d.ts.map +1 -0
- package/dist/error-hanler2.js +180 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +14 -18
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/router.d.ts +3 -9
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +171 -142
- package/dist/utils/wrapMiddleware.d.ts +2 -1
- package/dist/utils/wrapMiddleware.d.ts.map +1 -1
- package/dist/utils/wrapMiddleware.js +10 -0
- package/package.json +12 -2
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import express from
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
-
*
|
|
89
|
-
*
|
|
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
|
-
*
|
|
94
|
+
* If true, enables cross-origin cookie sending.
|
|
94
95
|
* Default: false
|
|
95
96
|
*/
|
|
96
97
|
credentials?: boolean;
|
|
97
98
|
/**
|
|
98
|
-
*
|
|
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
|
-
*
|
|
104
|
+
* List of headers allowed in the request.
|
|
104
105
|
* Default: "Content-Type, Authorization"
|
|
105
106
|
*/
|
|
106
107
|
headers?: string;
|
|
107
108
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
109
|
+
* Headers displayed to the customer.
|
|
110
|
+
* Example: ["X-Custom-Header"]
|
|
110
111
|
*/
|
|
111
112
|
exposedHeaders?: string;
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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(
|
|
43
|
-
const apiDir = opts?.apiDir ?? path.resolve(process.cwd(),
|
|
44
|
-
const baseRoute = opts?.baseRoute ??
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
106
|
+
else if (key === "jsonOptions") {
|
|
65
107
|
app.use(express.json(value));
|
|
66
108
|
}
|
|
67
|
-
else if (key ===
|
|
109
|
+
else if (key === "urlencodedOptions") {
|
|
68
110
|
app.use(express.urlencoded(value));
|
|
69
111
|
}
|
|
70
112
|
}
|
|
71
113
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
//
|
|
137
|
+
// External middlewares
|
|
78
138
|
if (opts?.expressOptions?.middlewares) {
|
|
79
|
-
logger.group(
|
|
139
|
+
logger.group("Express Middlewares");
|
|
80
140
|
for (const mw of opts.expressOptions.middlewares) {
|
|
81
|
-
logger.gear(`Loaded: ${mw.name ||
|
|
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(
|
|
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(
|
|
160
|
+
app.get("/_health", (_, res) => res.json({ ok: true }));
|
|
98
161
|
app.use((req, res, next) => {
|
|
99
|
-
res.setHeader(
|
|
162
|
+
res.setHeader("X-Powered-By", "Syntay Engine");
|
|
100
163
|
req.cookies = new RequestCookies(req.headers.cookie);
|
|
101
164
|
const corsOpts = opts?.enableCors || {};
|
|
102
|
-
//
|
|
103
|
-
let origin =
|
|
165
|
+
// Determine the origin
|
|
166
|
+
let origin = "*";
|
|
104
167
|
if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
|
|
105
|
-
//
|
|
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 =
|
|
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(
|
|
118
|
-
res.setHeader(
|
|
119
|
-
res.setHeader(
|
|
120
|
-
res.setHeader(
|
|
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(
|
|
185
|
+
res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
|
|
123
186
|
}
|
|
124
187
|
if (corsOpts.maxAge) {
|
|
125
|
-
res.setHeader(
|
|
188
|
+
res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
|
|
126
189
|
}
|
|
127
|
-
if (req.method ===
|
|
190
|
+
if (req.method === "OPTIONS")
|
|
128
191
|
return res.sendStatus(204);
|
|
129
192
|
next();
|
|
130
193
|
});
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/error-handler.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from
|
|
2
|
-
|
|
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;
|
|
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"}
|
package/dist/error-handler.js
CHANGED
|
@@ -1,36 +1,170 @@
|
|
|
1
|
-
import { logger } from
|
|
2
|
-
import fs from
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
let fileInfo =
|
|
8
|
-
if (
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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"}
|