@syntay/fastay 0.2.3 → 0.2.5

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/src/app.ts DELETED
@@ -1,328 +0,0 @@
1
- import express, { Request, Response, NextFunction } 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
-
13
- import { RequestCookies } from './utils/cookies.js';
14
- import { formDataMiddleware } from './utils/formDataMiddleware.js';
15
-
16
- /**
17
- * Express configuration options applied automatically by Fastay
18
- * before internal middleware and route loading.
19
- */
20
- export interface ExpressOptions {
21
- /**
22
- * Global middlewares applied to all routes.
23
- * Example: [cors(), helmet()]
24
- */
25
- middlewares?: express.RequestHandler[];
26
-
27
- /**
28
- * Options passed to express.json().
29
- * Useful for customizing JSON payload limits or behavior.
30
- */
31
- jsonOptions?: Parameters<typeof express.json>[0];
32
-
33
- /**
34
- * Options passed to express.urlencoded().
35
- * Useful when handling form submissions or URL-encoded bodies.
36
- */
37
- urlencodedOptions?: Parameters<typeof express.urlencoded>[0];
38
-
39
- /**
40
- * Custom global error handler.
41
- * If provided, Fastay will use this instead of the default one.
42
- */
43
- errorHandler?: express.ErrorRequestHandler;
44
-
45
- /**
46
- * Static file serving configuration.
47
- * Example:
48
- * {
49
- * path: "public",
50
- * options: { maxAge: "1d" }
51
- * }
52
- */
53
- static?: {
54
- path: string;
55
- options?: ServeStaticOptions;
56
- };
57
-
58
- /**
59
- * View engine configuration for Express.
60
- * Example:
61
- * {
62
- * engine: "pug",
63
- * dir: "views"
64
- * }
65
- */
66
- views?: {
67
- engine: string;
68
- dir: string;
69
- };
70
-
71
- /**
72
- * Enables or disables Express' "trust proxy" mode.
73
- * Typically required when using reverse proxies (Nginx, Cloudflare, etc.).
74
- */
75
- trustProxy?: boolean;
76
-
77
- /**
78
- * Local variables available to all templates and responses.
79
- * Fastay automatically injects them into `response.locals`.
80
- */
81
- locals?: Record<string, any>;
82
- }
83
-
84
- /**
85
- * Options applied when creating a Fastay.js application.
86
- */
87
- export type CreateAppOptions = {
88
- /**
89
- * Directory where API route modules are located.
90
- * Default: "src/api"
91
- */
92
- apiDir?: string;
93
-
94
- /**
95
- * Base route where all API routes will be mounted.
96
- * Default: "/api"
97
- */
98
- baseRoute?: string;
99
-
100
- /**
101
- * Configuration to enable CORS (Cross-Origin Resource Sharing) in Fastay.
102
- */
103
- enableCors?: {
104
- /**
105
- * If true, permite requisições de qualquer origem.
106
- * Default: false
107
- */
108
- allowAnyOrigin?: boolean;
109
-
110
- /**
111
- * Lista de origens específicas permitidas para envio de cookies.
112
- * Exemplo: ["http://localhost:3000", "https://meusite.com"]
113
- */
114
- cookieOrigins?: string[];
115
-
116
- /**
117
- * Se true, habilita envio de cookies cross-origin.
118
- * Default: false
119
- */
120
- credentials?: boolean;
121
-
122
- /**
123
- * Lista de métodos HTTP permitidos, separados por vírgula.
124
- * Default: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
125
- */
126
- methods?: string;
127
-
128
- /**
129
- * Lista de cabeçalhos permitidos na requisição.
130
- * Default: "Content-Type, Authorization"
131
- */
132
- headers?: string;
133
-
134
- /**
135
- * Cabeçalhos expostos ao cliente.
136
- * Exemplo: ["X-Custom-Header"]
137
- */
138
- exposedHeaders?: string;
139
-
140
- /**
141
- * Tempo máximo de cache para requisições prévias (preflight), em segundos.
142
- */
143
- maxAge?: number;
144
- };
145
-
146
- /**
147
- * Port on which `.listen()` will run the server.
148
- * Default: 3000
149
- */
150
- port?: number;
151
-
152
- /**
153
- * Express-level configuration such as middleware, body parsers,
154
- * view engine, static assets, error handler, etc.
155
- */
156
- expressOptions?: ExpressOptions;
157
-
158
- /**
159
- * Internal Fastay middlewares applied after Express initialization
160
- * but before route mounting.
161
- */
162
- middlewares?: MiddlewareMap;
163
- };
164
-
165
- /**
166
- * Bootstraps and configures a Fastay application.
167
- *
168
- * Fastay automatically:
169
- * - Discovers and registers routes defined in `apiDir`.
170
- * - Applies both built-in and user-provided middlewares.
171
- * - Exposes a health-check endpoint at `/_health`.
172
- *
173
- * @param opts - Configuration options for the Fastay application.
174
- * @returns A Promise that resolves to an Express `Application` instance.
175
- *
176
- * @example
177
- * ```ts
178
- * import { createApp } from '@syntay/fastay';
179
- * import cors from 'cors';
180
- * import helmet from 'helmet';
181
- *
182
- * void (async () => {
183
- * await createApp({
184
- * apiDir: './src/api',
185
- * baseRoute: '/api',
186
- * port: 5555,
187
- * expressOptions: {
188
- * middlewares: [cors(), helmet()],
189
- * },
190
- * });
191
- * })();
192
- * ```
193
- */
194
- export async function createApp(opts?: CreateAppOptions) {
195
- const start = logger.timeStart();
196
-
197
- printBanner();
198
-
199
- // logger.group('Fastay');
200
- logger.info('Initializing server...');
201
-
202
- const apiDir = opts?.apiDir ?? path.resolve(process.cwd(), 'src', 'api');
203
- const baseRoute = opts?.baseRoute ?? '/api';
204
-
205
- logger.success(`API directory: ${apiDir}`);
206
- logger.success(`Base route: ${baseRoute}`);
207
-
208
- const app = express();
209
-
210
- if (opts?.expressOptions) {
211
- for (const [key, value] of Object.entries(opts.expressOptions)) {
212
- // Se for array → assume middleware global
213
- if (Array.isArray(value)) {
214
- value.forEach((mw) => app.use(mw));
215
- }
216
- // Se o app tiver método com esse nome
217
- else if (typeof (app as any)[key] === 'function') {
218
- // TS-safe
219
- ((app as any)[key] as Function)(value);
220
- }
221
- // special cases
222
- else if (key === 'static' && value && typeof value === 'object') {
223
- const v = value as { path: string; options?: any };
224
- app.use(express.static(v.path, v.options));
225
- } else if (key === 'jsonOptions') {
226
- app.use(express.json(value as any));
227
- } else if (key === 'urlencodedOptions') {
228
- app.use(express.urlencoded(value as any));
229
- }
230
- }
231
- }
232
-
233
- app.use(express.json());
234
-
235
- const defaltPort = opts?.port ? opts.port : 6000;
236
-
237
- app.listen(defaltPort, () => {
238
- logger.success(
239
- `Server running at http://localhost:${defaltPort}${baseRoute}`
240
- );
241
- });
242
-
243
- // external middlewares
244
- if (opts?.expressOptions?.middlewares) {
245
- logger.group('Express Middlewares');
246
- for (const mw of opts.expressOptions.middlewares) {
247
- logger.gear(`Loaded: ${mw.name || 'anonymous'}`);
248
- app.use(mw);
249
- }
250
- }
251
-
252
- // FormData middleware
253
- app.use(formDataMiddleware());
254
-
255
- // Fastay middlewares
256
- if (opts?.middlewares) {
257
- logger.group('Fastay Middlewares');
258
- const apply = createMiddleware(opts.middlewares);
259
- apply(app);
260
- }
261
-
262
- // automatic middlewares
263
- // logger.group('Fastay Auto-Middlewares');
264
- const isMiddleware = await loadFastayMiddlewares(app);
265
-
266
- // health check
267
- app.get('/_health', (_, res) => res.json({ ok: true }));
268
- app.use((req: Request, res: Response, next: NextFunction) => {
269
- res.setHeader('X-Powered-By', 'Syntay Engine');
270
- (req as any).cookies = new RequestCookies(req.headers.cookie);
271
-
272
- const corsOpts = opts?.enableCors || {};
273
-
274
- // Determina a origem
275
- let origin = '*';
276
-
277
- if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
278
- // Se a origem estiver na lista de cookieOrigins, permite cookies
279
- if (
280
- req.headers.origin &&
281
- corsOpts.cookieOrigins.includes(req.headers.origin)
282
- ) {
283
- origin = req.headers.origin;
284
- } else {
285
- origin = ''; // bloqueia cookies para outras origens
286
- }
287
- } else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
288
- origin = '*';
289
- }
290
-
291
- res.setHeader('Access-Control-Allow-Origin', origin);
292
- res.setHeader(
293
- 'Access-Control-Allow-Credentials',
294
- corsOpts.credentials ? 'true' : 'false'
295
- );
296
- res.setHeader(
297
- 'Access-Control-Allow-Methods',
298
- corsOpts.methods || 'GET,POST,PUT,PATCH,DELETE,OPTIONS'
299
- );
300
- res.setHeader(
301
- 'Access-Control-Allow-Headers',
302
- corsOpts.headers || 'Content-Type, Authorization'
303
- );
304
-
305
- if (corsOpts.exposedHeaders) {
306
- res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
307
- }
308
-
309
- if (corsOpts.maxAge) {
310
- res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
311
- }
312
-
313
- if (req.method === 'OPTIONS') return res.sendStatus(204);
314
- next();
315
- });
316
-
317
- // load routes
318
- // logger.group('Routes Loaded');
319
- const totalRoutes = await loadApiRoutes(app, baseRoute, apiDir);
320
- logger.success(`Total routes loaded: ${totalRoutes}`);
321
-
322
- // app.use(errorHandler);
323
-
324
- const time = logger.timeEnd(start);
325
- logger.success(`Boot completed in ${time}ms`);
326
-
327
- return app;
328
- }
package/src/banner.ts DELETED
@@ -1,11 +0,0 @@
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
- }
@@ -1,48 +0,0 @@
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 DELETED
@@ -1,5 +0,0 @@
1
- export { createApp } from './app.js';
2
- export { createMiddleware } from './middleware.js';
3
- export type { CreateAppOptions } from './app.js';
4
- export type { Request, Response, Next } from './types';
5
- export { cookies } from './utils/cookies.js';
package/src/logger.ts DELETED
@@ -1,78 +0,0 @@
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
- };
package/src/middleware.ts DELETED
@@ -1,107 +0,0 @@
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
- }