@syntay/fastay 0.1.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/LICENSE +21 -0
- package/README.md +0 -0
- package/dist/app.d.ts +123 -0
- package/dist/app.js +106 -0
- package/dist/banner.d.ts +1 -0
- package/dist/banner.js +9 -0
- package/dist/error-handler.d.ts +2 -0
- package/dist/error-handler.js +36 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +61 -0
- package/dist/middleware.d.ts +62 -0
- package/dist/middleware.js +59 -0
- package/dist/router.d.ts +13 -0
- package/dist/router.js +151 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/wrapMiddleware.d.ts +6 -0
- package/dist/utils/wrapMiddleware.js +74 -0
- package/package.json +25 -0
- package/src/app.ts +233 -0
- package/src/banner.ts +11 -0
- package/src/error-handler.ts +48 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +78 -0
- package/src/middleware.ts +107 -0
- package/src/router.ts +175 -0
- package/src/types/index.ts +13 -0
- package/src/utils/wrapMiddleware.ts +95 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Request e Response do Express
|
|
4
|
+
* Podem ser usados nos handlers do usuário
|
|
5
|
+
*/
|
|
6
|
+
export type Request = ExpressRequest;
|
|
7
|
+
export type Response = ExpressResponse;
|
|
8
|
+
export type Next = NextFunction;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { logger } from '../logger.js';
|
|
2
|
+
const color = {
|
|
3
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
4
|
+
gray: (s) => `\x1b[90m${s}\x1b[0m`,
|
|
5
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
6
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
7
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
8
|
+
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Formata uma função para exibição bonitinha
|
|
12
|
+
*/
|
|
13
|
+
function formatFunction(fn) {
|
|
14
|
+
let code = fn.toString();
|
|
15
|
+
// deixa com quebras de linha
|
|
16
|
+
code = code.replace(/;/g, ';\n');
|
|
17
|
+
// tenta dar indentação
|
|
18
|
+
code = code
|
|
19
|
+
.replace(/{/g, '{\n ')
|
|
20
|
+
.replace(/}/g, '\n}')
|
|
21
|
+
.replace(/\n\s*\n/g, '\n');
|
|
22
|
+
return code.trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Verifica se next() ou return existem
|
|
26
|
+
*/
|
|
27
|
+
function validateMiddlewareCode(mw) {
|
|
28
|
+
const raw = mw.toString();
|
|
29
|
+
const name = mw.name || 'anonymous';
|
|
30
|
+
const cleaned = raw
|
|
31
|
+
.replace(/\/\/.*$/gm, '')
|
|
32
|
+
.replace(/\/\*[\s\S]*?\*\//gm, '');
|
|
33
|
+
const hasNext = /next\s*\(/.test(cleaned);
|
|
34
|
+
const hasReturn = /\breturn\b/.test(cleaned);
|
|
35
|
+
if (!hasNext && !hasReturn) {
|
|
36
|
+
const prettyCode = formatFunction(mw);
|
|
37
|
+
const message = [
|
|
38
|
+
`${color.red('⨯ Fastay Middleware Error')}`,
|
|
39
|
+
``,
|
|
40
|
+
`The middleware ${color.yellow(`"${name}"`)} does not call next() or return any value.`,
|
|
41
|
+
`This will halt the middleware chain and block the request pipeline.`,
|
|
42
|
+
``,
|
|
43
|
+
`▌ Middleware Source`,
|
|
44
|
+
color.gray(prettyCode),
|
|
45
|
+
``,
|
|
46
|
+
`${color.cyan('▌ How to fix')}`,
|
|
47
|
+
`Ensure your middleware ends with either:`,
|
|
48
|
+
` • ${color.green('next()')}`,
|
|
49
|
+
` • ${color.green('return ...')}`,
|
|
50
|
+
``,
|
|
51
|
+
`Fastay cannot continue until this middleware is fixed.`,
|
|
52
|
+
].join('\n');
|
|
53
|
+
const err = new Error(message);
|
|
54
|
+
err.name = 'FastayMiddlewareError';
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wrapper final
|
|
60
|
+
*/
|
|
61
|
+
export function wrapMiddleware(mw) {
|
|
62
|
+
const name = mw.name || 'anonymous';
|
|
63
|
+
validateMiddlewareCode(mw);
|
|
64
|
+
// retorna middleware "puro"
|
|
65
|
+
return async (req, res, next) => {
|
|
66
|
+
try {
|
|
67
|
+
await mw(req, res, next);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
logger.error(`[${name}] middleware error: ${err}`);
|
|
71
|
+
next(err);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syntay/fastay",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.build.json",
|
|
9
|
+
"prepare": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"express": "^5.1.0",
|
|
16
|
+
"pino": "^10.1.0",
|
|
17
|
+
"pino-pretty": "^13.1.2"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/express": "^5.0.5",
|
|
21
|
+
"@types/node": "^20.19.25",
|
|
22
|
+
"ts-node": "^10.9.2",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { loadApiRoutes } from './router.js';
|
|
4
|
+
import {
|
|
5
|
+
MiddlewareMap,
|
|
6
|
+
loadFastayMiddlewares,
|
|
7
|
+
createMiddleware,
|
|
8
|
+
} from './middleware.js';
|
|
9
|
+
import { logger } from './logger.js';
|
|
10
|
+
import { printBanner } from './banner.js';
|
|
11
|
+
import type { ServeStaticOptions } from 'serve-static';
|
|
12
|
+
import { Next, Request, Response } from './types/index.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Express configuration options applied automatically by Fastay
|
|
16
|
+
* before internal middleware and route loading.
|
|
17
|
+
*/
|
|
18
|
+
export interface ExpressOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Global middlewares applied to all routes.
|
|
21
|
+
* Example: [cors(), helmet()]
|
|
22
|
+
*/
|
|
23
|
+
middlewares?: express.RequestHandler[];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options passed to express.json().
|
|
27
|
+
* Useful for customizing JSON payload limits or behavior.
|
|
28
|
+
*/
|
|
29
|
+
jsonOptions?: Parameters<typeof express.json>[0];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options passed to express.urlencoded().
|
|
33
|
+
* Useful when handling form submissions or URL-encoded bodies.
|
|
34
|
+
*/
|
|
35
|
+
urlencodedOptions?: Parameters<typeof express.urlencoded>[0];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Custom global error handler.
|
|
39
|
+
* If provided, Fastay will use this instead of the default one.
|
|
40
|
+
*/
|
|
41
|
+
errorHandler?: express.ErrorRequestHandler;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Static file serving configuration.
|
|
45
|
+
* Example:
|
|
46
|
+
* {
|
|
47
|
+
* path: "public",
|
|
48
|
+
* options: { maxAge: "1d" }
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
static?: {
|
|
52
|
+
path: string;
|
|
53
|
+
options?: ServeStaticOptions;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* View engine configuration for Express.
|
|
58
|
+
* Example:
|
|
59
|
+
* {
|
|
60
|
+
* engine: "pug",
|
|
61
|
+
* dir: "views"
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
views?: {
|
|
65
|
+
engine: string;
|
|
66
|
+
dir: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enables or disables Express' "trust proxy" mode.
|
|
71
|
+
* Typically required when using reverse proxies (Nginx, Cloudflare, etc.).
|
|
72
|
+
*/
|
|
73
|
+
trustProxy?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Local variables available to all templates and responses.
|
|
77
|
+
* Fastay automatically injects them into `response.locals`.
|
|
78
|
+
*/
|
|
79
|
+
locals?: Record<string, any>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Options applied when creating a Fastay.js application.
|
|
84
|
+
*/
|
|
85
|
+
export type CreateAppOptions = {
|
|
86
|
+
/**
|
|
87
|
+
* Directory where API route modules are located.
|
|
88
|
+
* Default: "src/api"
|
|
89
|
+
*/
|
|
90
|
+
apiDir?: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Base route where all API routes will be mounted.
|
|
94
|
+
* Default: "/api"
|
|
95
|
+
*/
|
|
96
|
+
baseRoute?: string;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Port on which `.listen()` will run the server.
|
|
100
|
+
* Default: 3000
|
|
101
|
+
*/
|
|
102
|
+
port?: number;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Express-level configuration such as middleware, body parsers,
|
|
106
|
+
* view engine, static assets, error handler, etc.
|
|
107
|
+
*/
|
|
108
|
+
expressOptions?: ExpressOptions;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Internal Fastay middlewares applied after Express initialization
|
|
112
|
+
* but before route mounting.
|
|
113
|
+
*/
|
|
114
|
+
middlewares?: MiddlewareMap;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Bootstraps and configures a Fastay application.
|
|
119
|
+
*
|
|
120
|
+
* Fastay automatically:
|
|
121
|
+
* - Discovers and registers routes defined in `apiDir`.
|
|
122
|
+
* - Applies both built-in and user-provided middlewares.
|
|
123
|
+
* - Exposes a health-check endpoint at `/_health`.
|
|
124
|
+
*
|
|
125
|
+
* @param opts - Configuration options for the Fastay application.
|
|
126
|
+
* @returns A Promise that resolves to an Express `Application` instance.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import { createApp } from '@syntay/fastay';
|
|
131
|
+
* import cors from 'cors';
|
|
132
|
+
* import helmet from 'helmet';
|
|
133
|
+
*
|
|
134
|
+
* void (async () => {
|
|
135
|
+
* await createApp({
|
|
136
|
+
* apiDir: './src/api',
|
|
137
|
+
* baseRoute: '/api',
|
|
138
|
+
* port: 5555,
|
|
139
|
+
* expressOptions: {
|
|
140
|
+
* middlewares: [cors(), helmet()],
|
|
141
|
+
* },
|
|
142
|
+
* });
|
|
143
|
+
* })();
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export async function createApp(opts?: CreateAppOptions) {
|
|
147
|
+
const start = logger.timeStart();
|
|
148
|
+
|
|
149
|
+
printBanner();
|
|
150
|
+
|
|
151
|
+
// logger.group('Fastay');
|
|
152
|
+
logger.info('Initializing server...');
|
|
153
|
+
|
|
154
|
+
const apiDir = opts?.apiDir ?? path.resolve(process.cwd(), 'src', 'api');
|
|
155
|
+
const baseRoute = opts?.baseRoute ?? '/api';
|
|
156
|
+
|
|
157
|
+
logger.success(`API directory: ${apiDir}`);
|
|
158
|
+
logger.success(`Base route: ${baseRoute}`);
|
|
159
|
+
|
|
160
|
+
const app = express();
|
|
161
|
+
|
|
162
|
+
if (opts?.expressOptions) {
|
|
163
|
+
for (const [key, value] of Object.entries(opts.expressOptions)) {
|
|
164
|
+
// Se for array → assume middleware global
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
value.forEach((mw) => app.use(mw));
|
|
167
|
+
}
|
|
168
|
+
// Se o app tiver método com esse nome
|
|
169
|
+
else if (typeof (app as any)[key] === 'function') {
|
|
170
|
+
// TS-safe
|
|
171
|
+
((app as any)[key] as Function)(value);
|
|
172
|
+
}
|
|
173
|
+
// special cases
|
|
174
|
+
else if (key === 'static' && value && typeof value === 'object') {
|
|
175
|
+
const v = value as { path: string; options?: any };
|
|
176
|
+
app.use(express.static(v.path, v.options));
|
|
177
|
+
} else if (key === 'jsonOptions') {
|
|
178
|
+
app.use(express.json(value as any));
|
|
179
|
+
} else if (key === 'urlencodedOptions') {
|
|
180
|
+
app.use(express.urlencoded(value as any));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
app.use(express.json());
|
|
186
|
+
|
|
187
|
+
const defaltPort = opts?.port ? opts.port : 6000;
|
|
188
|
+
|
|
189
|
+
app.listen(defaltPort, () => {
|
|
190
|
+
logger.success(
|
|
191
|
+
`Server running at http://localhost:${defaltPort}${baseRoute}`
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// external middlewares
|
|
196
|
+
if (opts?.expressOptions?.middlewares) {
|
|
197
|
+
logger.group('Express Middlewares');
|
|
198
|
+
for (const mw of opts.expressOptions.middlewares) {
|
|
199
|
+
logger.gear(`Loaded: ${mw.name || 'anonymous'}`);
|
|
200
|
+
app.use(mw);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Fastay middlewares
|
|
205
|
+
if (opts?.middlewares) {
|
|
206
|
+
logger.group('Fastay Middlewares');
|
|
207
|
+
const apply = createMiddleware(opts.middlewares);
|
|
208
|
+
apply(app);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// automatic middlewares
|
|
212
|
+
// logger.group('Fastay Auto-Middlewares');
|
|
213
|
+
const isMiddleware = await loadFastayMiddlewares(app);
|
|
214
|
+
|
|
215
|
+
// health check
|
|
216
|
+
app.get('/_health', (_, res) => res.json({ ok: true }));
|
|
217
|
+
app.use((_req: Request, res: Response, next: Next) => {
|
|
218
|
+
res.setHeader('X-Powered-By', 'Syntay Engine');
|
|
219
|
+
next();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// load routes
|
|
223
|
+
// logger.group('Routes Loaded');
|
|
224
|
+
const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
|
|
225
|
+
logger.success(`Total routes loaded: ${totalRoutes}`);
|
|
226
|
+
|
|
227
|
+
// app.use(errorHandler);
|
|
228
|
+
|
|
229
|
+
const time = logger.timeEnd(start);
|
|
230
|
+
logger.success(`Boot completed in ${time}ms`);
|
|
231
|
+
|
|
232
|
+
return app;
|
|
233
|
+
}
|
package/src/banner.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { logger } from './logger.js';
|
|
2
|
+
|
|
3
|
+
export function printBanner() {
|
|
4
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
5
|
+
const white = (s: string) => `\x1b[37m${s}\x1b[0m`;
|
|
6
|
+
|
|
7
|
+
logger.raw('');
|
|
8
|
+
logger.raw(`${cyan('⥨ Fastay.js')} ${white('1.0.0')}`);
|
|
9
|
+
logger.raw(` ${white('- Runtime: Node.js\n')}`);
|
|
10
|
+
// logger.raw('\n');
|
|
11
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { logger } from './logger';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
export function errorHandler(
|
|
6
|
+
err: any,
|
|
7
|
+
req: Request,
|
|
8
|
+
res: Response,
|
|
9
|
+
next: NextFunction
|
|
10
|
+
) {
|
|
11
|
+
const isSyntaxError =
|
|
12
|
+
err.name === 'SyntaxError' || err.message?.includes('Unexpected');
|
|
13
|
+
const route = `${req.method} ${req.originalUrl}`;
|
|
14
|
+
|
|
15
|
+
// Tenta extrair arquivo, linha e coluna (quando stack estiver presente)
|
|
16
|
+
let fileInfo = '';
|
|
17
|
+
if (err.stack) {
|
|
18
|
+
const stackLine = err.stack.split('\n')[1]; // pega primeira linha depois do erro
|
|
19
|
+
const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
|
|
20
|
+
if (match) {
|
|
21
|
+
const [_, file, line, col] = match;
|
|
22
|
+
fileInfo = `${file}:${line}:${col}`;
|
|
23
|
+
|
|
24
|
+
// Tenta mostrar o trecho da linha que deu erro
|
|
25
|
+
if (fs.existsSync(file)) {
|
|
26
|
+
const codeLines = fs.readFileSync(file, 'utf-8').split('\n');
|
|
27
|
+
const codeSnippet = codeLines[parseInt(line) - 1].trim();
|
|
28
|
+
fileInfo += ` → ${codeSnippet}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
logger.group(`✗ Runtime Error in route [${route}]`);
|
|
34
|
+
logger.error(`${err.name}: ${err.message}`);
|
|
35
|
+
if (fileInfo) logger.error(`Location: ${fileInfo}`);
|
|
36
|
+
|
|
37
|
+
if (process.env.NODE_ENV === 'production') {
|
|
38
|
+
return res.status(500).json({
|
|
39
|
+
error: 'Internal server error',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return res.status(500).json({
|
|
44
|
+
error: err.message,
|
|
45
|
+
stack: err.stack,
|
|
46
|
+
file: fileInfo || undefined,
|
|
47
|
+
});
|
|
48
|
+
}
|
package/src/index.ts
ADDED
package/src/logger.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import pino, { LogDescriptor } from 'pino';
|
|
2
|
+
import pretty, { PrettyOptions } from 'pino-pretty';
|
|
3
|
+
|
|
4
|
+
// stream configurado para remover INFO:, timestamps e etc
|
|
5
|
+
const stream = pretty({
|
|
6
|
+
colorize: true,
|
|
7
|
+
ignore: 'pid,hostname,time,level',
|
|
8
|
+
levelFirst: false,
|
|
9
|
+
|
|
10
|
+
// Remove "INFO: " antes da msg
|
|
11
|
+
messageKey: 'msg',
|
|
12
|
+
|
|
13
|
+
// Maneira correta TS-safe
|
|
14
|
+
messageFormat: (log: LogDescriptor, messageKey: string) => {
|
|
15
|
+
const msg = log[messageKey];
|
|
16
|
+
return typeof msg === 'string' ? msg : String(msg);
|
|
17
|
+
},
|
|
18
|
+
} as PrettyOptions);
|
|
19
|
+
|
|
20
|
+
const base = pino(
|
|
21
|
+
{
|
|
22
|
+
level: 'info',
|
|
23
|
+
timestamp: false, // remove [HH:mm:ss]
|
|
24
|
+
base: undefined, // remove pid, hostname
|
|
25
|
+
},
|
|
26
|
+
stream
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// helpers para cores ANSI
|
|
30
|
+
const colors = {
|
|
31
|
+
white: (s: string) => `\x1b[37m${s}\x1b[0m`,
|
|
32
|
+
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
33
|
+
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
34
|
+
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
35
|
+
gray: (s: string) => `\x1b[90m${s}\x1b[0m`,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// emojis Fastay
|
|
39
|
+
const ICONS = {
|
|
40
|
+
info: '○',
|
|
41
|
+
success: '✓',
|
|
42
|
+
error: '✗',
|
|
43
|
+
gear: '⚙️',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const logger = {
|
|
47
|
+
info: (msg: string) =>
|
|
48
|
+
base.info(` ${colors.white(ICONS.info)} ${colors.white(msg)}`),
|
|
49
|
+
warn: (msg: string) => base.info(` ${colors.red('⚠')} ${colors.white(msg)}`),
|
|
50
|
+
error: (msg: string) =>
|
|
51
|
+
base.info(` ${colors.red(ICONS.error)} ${colors.white(msg)}`),
|
|
52
|
+
success: (msg: string) =>
|
|
53
|
+
base.info(` ${colors.green(ICONS.success)} ${colors.white(msg)}`),
|
|
54
|
+
gear: (msg: string) => base.info(` ${ICONS.gear} ${colors.white(msg)}`),
|
|
55
|
+
|
|
56
|
+
space(lines: number = 1) {
|
|
57
|
+
for (let i = 0; i < lines; i++) base.info(' ');
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
group(title: string) {
|
|
61
|
+
this.space();
|
|
62
|
+
base.info('');
|
|
63
|
+
base.info(colors.cyan(title));
|
|
64
|
+
// this.space();
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
raw(msg: string) {
|
|
68
|
+
base.info(msg);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
timeStart() {
|
|
72
|
+
return performance.now();
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
timeEnd(start: number) {
|
|
76
|
+
return (performance.now() - start).toFixed(1);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Application, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
import { wrapMiddleware } from './utils/wrapMiddleware.js';
|
|
7
|
+
|
|
8
|
+
type MiddlewareFn = (req: Request, res: Response, next: NextFunction) => any;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Defines a map of routes and the middleware functions that Fastay
|
|
12
|
+
* will automatically load and attach during boot.
|
|
13
|
+
*
|
|
14
|
+
* Keys represent route prefixes (e.g. `/auth`, `/admin`), and
|
|
15
|
+
* values are arrays of Fastay middleware functions.
|
|
16
|
+
*
|
|
17
|
+
* Middleware functions use Fastay’s extended `Request`, `Response`,
|
|
18
|
+
* and `Next` types — not the raw Express versions.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // src/middleware.ts
|
|
23
|
+
* import { createMiddleware } from '@syntay/fastay';
|
|
24
|
+
* import { authMiddleware } from './auth';
|
|
25
|
+
* import { auditLogger } from './audit';
|
|
26
|
+
*
|
|
27
|
+
* export const middleware = createMiddleware({
|
|
28
|
+
* '/auth': [authMiddleware],
|
|
29
|
+
* '/admin': [auditLogger]
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* // src/auth.ts
|
|
36
|
+
* import { Request, Response, Next } from '@syntay/fastay';
|
|
37
|
+
*
|
|
38
|
+
* export async function authMiddleware(req: Request, _res: Response, next: Next) {
|
|
39
|
+
* // Custom logic using extended Fastay types
|
|
40
|
+
* req.user = { id: 1, role: "admin" };
|
|
41
|
+
* next();
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export type MiddlewareMap = Record<string, MiddlewareFn[]>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a Fastay middleware loader.
|
|
49
|
+
*
|
|
50
|
+
* Fastay uses this internally to attach user-defined middleware to the
|
|
51
|
+
* Express application during boot. The framework automatically discovers
|
|
52
|
+
* and loads any `middleware` exported from the project's `src/` directory.
|
|
53
|
+
*
|
|
54
|
+
* Middleware functions are wrapped so both synchronous and asynchronous
|
|
55
|
+
* handlers behave consistently.
|
|
56
|
+
*
|
|
57
|
+
* @param map - A map of route prefixes and the middleware stack for each route.
|
|
58
|
+
* @returns A function that Fastay will call to register the mapped middleware.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* export const middleware = createMiddleware({
|
|
63
|
+
* '/auth': [authMiddleware],
|
|
64
|
+
* '/admin': [adminGuard, auditLogger]
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function createMiddleware(map: Record<string, MiddlewareFn[]>) {
|
|
69
|
+
return (app: Application) => {
|
|
70
|
+
for (const [route, middlewares] of Object.entries(map)) {
|
|
71
|
+
for (const mw of middlewares) {
|
|
72
|
+
const wrapped = wrapMiddleware(mw);
|
|
73
|
+
|
|
74
|
+
app.use(route, wrapped);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function loadFastayMiddlewares(app: Application) {
|
|
81
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
82
|
+
const mwDir = path.resolve(
|
|
83
|
+
process.cwd(),
|
|
84
|
+
isDev ? 'src/middlewares' : 'dist/middlewares'
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const file = path.join(mwDir, isDev ? 'middleware.ts' : 'middleware.js');
|
|
88
|
+
if (!fs.existsSync(file)) return;
|
|
89
|
+
|
|
90
|
+
const mod = await import(pathToFileURL(file).href);
|
|
91
|
+
|
|
92
|
+
if (!mod.middleware) return;
|
|
93
|
+
|
|
94
|
+
logger.group('Fastay Auto-Middlewares');
|
|
95
|
+
|
|
96
|
+
if (typeof mod.middleware === 'function') {
|
|
97
|
+
mod.middleware(app);
|
|
98
|
+
logger.info('Loading Fastay core middleware...');
|
|
99
|
+
} else {
|
|
100
|
+
const map = mod.middleware as Record<string, any[]>;
|
|
101
|
+
for (const [route, middlewares] of Object.entries(map)) {
|
|
102
|
+
for (const mw of middlewares) {
|
|
103
|
+
app.use(route, mw);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|