@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/README.md +13 -1434
- package/dist/app.d.ts +24 -41
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +141 -53
- 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/middleware.js +12 -12
- 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 +11 -3
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.
|
|
@@ -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
|
-
*
|
|
89
|
-
*
|
|
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
|
-
*
|
|
99
|
+
* If true, enables cross-origin cookie sending.
|
|
94
100
|
* Default: false
|
|
95
101
|
*/
|
|
96
102
|
credentials?: boolean;
|
|
97
103
|
/**
|
|
98
|
-
*
|
|
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
|
-
*
|
|
109
|
+
* List of headers allowed in the request.
|
|
104
110
|
* Default: "Content-Type, Authorization"
|
|
105
111
|
*/
|
|
106
112
|
headers?: string;
|
|
107
113
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
114
|
+
* Headers displayed to the customer.
|
|
115
|
+
* Example: ["X-Custom-Header"]
|
|
110
116
|
*/
|
|
111
117
|
exposedHeaders?: string;
|
|
112
118
|
/**
|
|
113
|
-
*
|
|
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:
|
|
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;
|
|
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
|
|
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,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(
|
|
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;
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
107
|
+
else if (key === "jsonOptions") {
|
|
67
108
|
app.use(express.json(value));
|
|
68
109
|
}
|
|
69
|
-
else if (key ===
|
|
110
|
+
else if (key === "urlencodedOptions") {
|
|
70
111
|
app.use(express.urlencoded(value));
|
|
71
112
|
}
|
|
72
113
|
}
|
|
73
114
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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(
|
|
132
|
+
logger.group("Fastay Middlewares");
|
|
92
133
|
const apply = createMiddleware(opts.middlewares);
|
|
93
134
|
apply(app);
|
|
94
135
|
}
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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(
|
|
155
|
+
res.setHeader("X-Powered-By", "Syntay Engine");
|
|
102
156
|
req.cookies = new RequestCookies(req.headers.cookie);
|
|
103
157
|
const corsOpts = opts?.enableCors || {};
|
|
104
|
-
//
|
|
105
|
-
let origin =
|
|
158
|
+
// Determine the origin
|
|
159
|
+
let origin = "*";
|
|
106
160
|
if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
|
|
107
|
-
//
|
|
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 =
|
|
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(
|
|
120
|
-
res.setHeader(
|
|
121
|
-
res.setHeader(
|
|
122
|
-
res.setHeader(
|
|
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(
|
|
178
|
+
res.setHeader("Access-Control-Expose-Headers", corsOpts.exposedHeaders);
|
|
125
179
|
}
|
|
126
180
|
if (corsOpts.maxAge) {
|
|
127
|
-
res.setHeader(
|
|
181
|
+
res.setHeader("Access-Control-Max-Age", corsOpts.maxAge.toString());
|
|
128
182
|
}
|
|
129
|
-
if (req.method ===
|
|
183
|
+
if (req.method === "OPTIONS")
|
|
130
184
|
return res.sendStatus(204);
|
|
131
185
|
next();
|
|
132
186
|
});
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
}
|
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"}
|