@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 +19 -41
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +139 -44
- 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 +169 -155
- 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 +10 -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,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:
|
|
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;
|
|
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
|
|
2
|
-
import { createServer } from
|
|
3
|
-
import path from
|
|
4
|
-
import { loadApiRoutes } from
|
|
5
|
-
import { loadFastayMiddlewares, createMiddleware } from
|
|
6
|
-
import { logger } from
|
|
7
|
-
import { printBanner } from
|
|
8
|
-
import { RequestCookies } from
|
|
9
|
-
import { formDataMiddleware } from
|
|
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(
|
|
44
|
-
const apiDir = opts?.apiDir ?? path.resolve(process.cwd(),
|
|
45
|
-
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;
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
106
|
+
else if (key === "jsonOptions") {
|
|
67
107
|
app.use(express.json(value));
|
|
68
108
|
}
|
|
69
|
-
else if (key ===
|
|
109
|
+
else if (key === "urlencodedOptions") {
|
|
70
110
|
app.use(express.urlencoded(value));
|
|
71
111
|
}
|
|
72
112
|
}
|
|
73
113
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
//
|
|
137
|
+
// External middlewares
|
|
80
138
|
if (opts?.expressOptions?.middlewares) {
|
|
81
|
-
logger.group(
|
|
139
|
+
logger.group("Express Middlewares");
|
|
82
140
|
for (const mw of opts.expressOptions.middlewares) {
|
|
83
|
-
logger.gear(`Loaded: ${mw.name ||
|
|
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(
|
|
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(
|
|
160
|
+
app.get("/_health", (_, res) => res.json({ ok: true }));
|
|
100
161
|
app.use((req, res, next) => {
|
|
101
|
-
res.setHeader(
|
|
162
|
+
res.setHeader("X-Powered-By", "Syntay Engine");
|
|
102
163
|
req.cookies = new RequestCookies(req.headers.cookie);
|
|
103
164
|
const corsOpts = opts?.enableCors || {};
|
|
104
|
-
//
|
|
105
|
-
let origin =
|
|
165
|
+
// Determine the origin
|
|
166
|
+
let origin = "*";
|
|
106
167
|
if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
|
|
107
|
-
//
|
|
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 =
|
|
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(
|
|
120
|
-
res.setHeader(
|
|
121
|
-
res.setHeader(
|
|
122
|
-
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");
|
|
123
184
|
if (corsOpts.exposedHeaders) {
|
|
124
|
-
res.setHeader(
|
|
185
|
+
res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
|
|
125
186
|
}
|
|
126
187
|
if (corsOpts.maxAge) {
|
|
127
|
-
res.setHeader(
|
|
188
|
+
res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
|
|
128
189
|
}
|
|
129
|
-
if (req.method ===
|
|
190
|
+
if (req.method === "OPTIONS")
|
|
130
191
|
return res.sendStatus(204);
|
|
131
192
|
next();
|
|
132
193
|
});
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
}
|
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"}
|