create-fluxstack 1.1.0 → 1.4.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.
Files changed (64) hide show
  1. package/app/server/backend-only.ts +5 -5
  2. package/app/server/index.ts +63 -54
  3. package/app/server/live/FluxStackConfig.ts +43 -39
  4. package/app/server/live/SystemMonitorIntegration.ts +2 -2
  5. package/app/server/live/register-components.ts +1 -1
  6. package/app/server/middleware/errorHandling.ts +6 -4
  7. package/app/server/routes/config.ts +145 -0
  8. package/app/server/routes/index.ts +5 -3
  9. package/config/app.config.ts +113 -0
  10. package/config/build.config.ts +24 -0
  11. package/config/database.config.ts +99 -0
  12. package/config/index.ts +68 -0
  13. package/config/logger.config.ts +27 -0
  14. package/config/runtime.config.ts +92 -0
  15. package/config/server.config.ts +46 -0
  16. package/config/services.config.ts +130 -0
  17. package/config/system.config.ts +105 -0
  18. package/core/build/index.ts +10 -4
  19. package/core/cli/generators/index.ts +5 -2
  20. package/core/cli/generators/plugin.ts +290 -0
  21. package/core/cli/index.ts +117 -15
  22. package/core/config/env.ts +37 -95
  23. package/core/config/runtime-config.ts +61 -58
  24. package/core/config/schema.ts +4 -0
  25. package/core/framework/server.ts +22 -10
  26. package/core/plugins/built-in/index.ts +7 -17
  27. package/core/plugins/built-in/swagger/index.ts +228 -228
  28. package/core/plugins/built-in/vite/index.ts +374 -358
  29. package/core/plugins/dependency-manager.ts +5 -5
  30. package/core/plugins/manager.ts +12 -12
  31. package/core/plugins/registry.ts +3 -3
  32. package/core/server/index.ts +0 -1
  33. package/core/server/live/ComponentRegistry.ts +34 -8
  34. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  35. package/core/server/live/websocket-plugin.ts +434 -434
  36. package/core/server/middleware/README.md +488 -0
  37. package/core/server/middleware/elysia-helpers.ts +227 -0
  38. package/core/server/middleware/index.ts +25 -9
  39. package/core/server/plugins/static-files-plugin.ts +231 -231
  40. package/core/utils/config-schema.ts +484 -0
  41. package/core/utils/env.ts +306 -0
  42. package/core/utils/helpers.ts +4 -4
  43. package/core/utils/logger/colors.ts +114 -0
  44. package/core/utils/logger/config.ts +35 -0
  45. package/core/utils/logger/formatter.ts +82 -0
  46. package/core/utils/logger/group-logger.ts +101 -0
  47. package/core/utils/logger/index.ts +199 -250
  48. package/core/utils/logger/stack-trace.ts +92 -0
  49. package/core/utils/logger/startup-banner.ts +92 -0
  50. package/core/utils/logger/winston-logger.ts +152 -0
  51. package/core/utils/version.ts +5 -0
  52. package/create-fluxstack.ts +118 -8
  53. package/fluxstack.config.ts +2 -2
  54. package/package.json +117 -115
  55. package/core/config/env-dynamic.ts +0 -326
  56. package/core/plugins/built-in/logger/index.ts +0 -180
  57. package/core/server/plugins/logger.ts +0 -47
  58. package/core/utils/env-runtime-v2.ts +0 -232
  59. package/core/utils/env-runtime.ts +0 -259
  60. package/core/utils/logger/formatters.ts +0 -222
  61. package/core/utils/logger/middleware.ts +0 -253
  62. package/core/utils/logger/performance.ts +0 -384
  63. package/core/utils/logger/transports.ts +0 -365
  64. package/core/utils/logger.ts +0 -106
@@ -0,0 +1,488 @@
1
+ # Elysia Middleware Helpers
2
+
3
+ Utilitários para simplificar a criação de middlewares Elysia no FluxStack.
4
+
5
+ ## 📚 Índice
6
+
7
+ - [Instalação](#instalação)
8
+ - [Helpers Disponíveis](#helpers-disponíveis)
9
+ - [createMiddleware](#createmiddleware)
10
+ - [createDerive](#createderive)
11
+ - [createGuard](#createguard)
12
+ - [createRateLimit](#createratelimit)
13
+ - [composeMiddleware](#composemiddleware)
14
+ - [Exemplos Práticos](#exemplos-práticos)
15
+ - [Comparação: Antes vs Depois](#comparação-antes-vs-depois)
16
+
17
+ ## 🚀 Instalação
18
+
19
+ Os helpers já estão disponíveis no core do FluxStack:
20
+
21
+ ```typescript
22
+ import {
23
+ createMiddleware,
24
+ createDerive,
25
+ createGuard,
26
+ createRateLimit,
27
+ composeMiddleware,
28
+ isDevelopment
29
+ } from '../../../core/server/middleware'
30
+ ```
31
+
32
+ ## 🛠️ Helpers Disponíveis
33
+
34
+ ### `createMiddleware`
35
+
36
+ Cria um middleware Elysia simples que pode bloquear ou adicionar ao contexto.
37
+
38
+ **Parâmetros:**
39
+ ```typescript
40
+ interface MiddlewareOptions<TContext = any> {
41
+ name: string // Nome único do middleware
42
+ handler: (context: TContext) // Handler que roda antes da rota
43
+ => void | any // Retorne algo para bloquear
44
+ nonBlocking?: boolean // Use derive() em vez de onBeforeHandle()
45
+ }
46
+ ```
47
+
48
+ **Exemplo:**
49
+ ```typescript
50
+ const addRequestId = createMiddleware({
51
+ name: 'requestId',
52
+ handler: ({ store }) => {
53
+ Object.assign(store, { requestId: crypto.randomUUID() })
54
+ }
55
+ })
56
+
57
+ app.use(addRequestId)
58
+ ```
59
+
60
+ ### `createDerive`
61
+
62
+ Cria um middleware que **adiciona** dados ao contexto sem bloquear a execução.
63
+
64
+ **Exemplo:**
65
+ ```typescript
66
+ const addTimestamp = createDerive({
67
+ name: 'timestamp',
68
+ derive: () => ({
69
+ timestamp: Date.now(),
70
+ requestTime: new Date().toISOString()
71
+ })
72
+ })
73
+
74
+ app
75
+ .use(addTimestamp)
76
+ .get('/', ({ timestamp, requestTime }) => ({
77
+ timestamp,
78
+ requestTime
79
+ }))
80
+ ```
81
+
82
+ ### `createGuard`
83
+
84
+ Cria um middleware de validação/proteção que bloqueia se uma condição não for atendida.
85
+
86
+ **Exemplo:**
87
+ ```typescript
88
+ const requireAuth = createGuard({
89
+ name: 'authGuard',
90
+ check: ({ store }) => {
91
+ // Retorne true para permitir, false para bloquear
92
+ return store.user?.isAuthenticated === true
93
+ },
94
+ onFail: (set) => {
95
+ set.status = 401
96
+ return { error: 'Unauthorized' }
97
+ }
98
+ })
99
+
100
+ app.use(requireAuth).get('/protected', () => 'Secret data')
101
+ ```
102
+
103
+ **Exemplo Assíncrono:**
104
+ ```typescript
105
+ const requireEmailVerified = createGuard({
106
+ name: 'emailVerified',
107
+ check: async ({ store }) => {
108
+ const user = store.user
109
+ if (!user) return false
110
+
111
+ // Pode fazer queries assíncronas
112
+ const dbUser = await User.findById(user.id)
113
+ return dbUser?.isEmailVerified === true
114
+ },
115
+ onFail: (set) => {
116
+ set.status = 403
117
+ return { error: 'Email verification required' }
118
+ }
119
+ })
120
+ ```
121
+
122
+ ### `createRateLimit`
123
+
124
+ Cria um middleware de rate limiting com janela deslizante.
125
+
126
+ **Parâmetros:**
127
+ ```typescript
128
+ {
129
+ name: string // Nome do middleware
130
+ maxRequests: number // Máximo de requests na janela
131
+ windowMs: number // Tamanho da janela em ms
132
+ keyGenerator?: Function // Como gerar chave única (default: IP)
133
+ message?: string // Mensagem de erro customizada
134
+ }
135
+ ```
136
+
137
+ **Exemplo:**
138
+ ```typescript
139
+ const apiRateLimit = createRateLimit({
140
+ name: 'apiLimit',
141
+ maxRequests: 100,
142
+ windowMs: 60000, // 1 minuto
143
+ keyGenerator: ({ request }) =>
144
+ request.headers.get('x-api-key') || 'anonymous',
145
+ message: 'API rate limit exceeded'
146
+ })
147
+
148
+ app.use(apiRateLimit)
149
+ ```
150
+
151
+ **Exemplo com ambiente:**
152
+ ```typescript
153
+ import { isDevelopment } from '../../../core/server/middleware'
154
+
155
+ const loginRateLimit = createRateLimit({
156
+ name: 'loginLimit',
157
+ maxRequests: isDevelopment() ? 100 : 5,
158
+ windowMs: isDevelopment() ? 60000 : 900000, // 1min dev, 15min prod
159
+ keyGenerator: ({ request }) => {
160
+ const ip = request.headers.get('x-forwarded-for') || 'unknown'
161
+ return `login:${ip}`
162
+ }
163
+ })
164
+ ```
165
+
166
+ ### `composeMiddleware`
167
+
168
+ Combina múltiplos middlewares em um único plugin reutilizável.
169
+
170
+ **Exemplo:**
171
+ ```typescript
172
+ const protectedAdminRoute = composeMiddleware({
173
+ name: 'protectedAdmin',
174
+ middlewares: [
175
+ requireAuth(),
176
+ requireEmailVerified(),
177
+ requireAdmin(),
178
+ apiRateLimit()
179
+ ]
180
+ })
181
+
182
+ // Use em várias rotas
183
+ app
184
+ .use(protectedAdminRoute)
185
+ .get('/admin/users', () => getUsers())
186
+ .delete('/admin/users/:id', ({ params }) => deleteUser(params.id))
187
+ ```
188
+
189
+ ## 💡 Exemplos Práticos
190
+
191
+ ### 1. Sistema de Autenticação Completo
192
+
193
+ ```typescript
194
+ // Auth obrigatório
195
+ export const auth = () =>
196
+ createGuard({
197
+ name: 'auth',
198
+ check: async ({ headers, store }) => {
199
+ const token = headers.authorization?.replace('Bearer ', '')
200
+ if (!token) return false
201
+
202
+ try {
203
+ const payload = jwt.verify(token)
204
+ const user = await User.findById(payload.userId)
205
+
206
+ if (!user?.isActive) return false
207
+
208
+ Object.assign(store, { user, token })
209
+ return true
210
+ } catch {
211
+ return false
212
+ }
213
+ },
214
+ onFail: (set) => {
215
+ set.status = 401
216
+ return { error: 'Authentication required' }
217
+ }
218
+ })
219
+
220
+ // Auth opcional
221
+ export const optionalAuth = () =>
222
+ createDerive({
223
+ name: 'optionalAuth',
224
+ derive: async ({ headers }) => {
225
+ const token = headers.authorization?.replace('Bearer ', '')
226
+ if (!token) return { user: null }
227
+
228
+ try {
229
+ const payload = jwt.verify(token)
230
+ const user = await User.findById(payload.userId)
231
+ return { user: user?.isActive ? user : null }
232
+ } catch {
233
+ return { user: null }
234
+ }
235
+ }
236
+ })
237
+ ```
238
+
239
+ ### 2. Validação de Permissões de Sala
240
+
241
+ ```typescript
242
+ export const requireRoomAccess = (paramName = 'roomId') =>
243
+ createGuard({
244
+ name: 'roomAccess',
245
+ check: async ({ store, params }) => {
246
+ const user = store.user
247
+ const roomId = params[paramName]
248
+
249
+ if (!user || !roomId) return false
250
+
251
+ const room = await Room.findById(roomId)
252
+ if (!room) return false
253
+
254
+ const hasAccess = room.participants.some(
255
+ p => p.userId.toString() === user.id
256
+ )
257
+
258
+ if (hasAccess) {
259
+ Object.assign(store, { room })
260
+ }
261
+
262
+ return hasAccess
263
+ },
264
+ onFail: (set, { params }) => {
265
+ if (!params[paramName]) {
266
+ set.status = 400
267
+ return { error: 'Room ID required' }
268
+ }
269
+ set.status = 403
270
+ return { error: 'Room access denied' }
271
+ }
272
+ })
273
+ ```
274
+
275
+ ### 3. Logging e Métricas
276
+
277
+ ```typescript
278
+ const requestLogger = createDerive({
279
+ name: 'logger',
280
+ derive: ({ request, store }) => {
281
+ const startTime = Date.now()
282
+ const requestId = crypto.randomUUID()
283
+
284
+ console.log(`[${requestId}] ${request.method} ${request.url}`)
285
+
286
+ return {
287
+ requestId,
288
+ startTime,
289
+ logEnd: () => {
290
+ const duration = Date.now() - startTime
291
+ console.log(`[${requestId}] Completed in ${duration}ms`)
292
+ }
293
+ }
294
+ }
295
+ })
296
+
297
+ app.use(requestLogger).get('/', ({ logEnd }) => {
298
+ const result = doWork()
299
+ logEnd() // Log duration
300
+ return result
301
+ })
302
+ ```
303
+
304
+ ### 4. Validação de Roles
305
+
306
+ ```typescript
307
+ const requireRole = (...roles: string[]) =>
308
+ createGuard({
309
+ name: `requireRole:${roles.join(',')}`,
310
+ check: ({ store }) => {
311
+ const user = store.user
312
+ if (!user?.roles) return false
313
+ return roles.some(role => user.roles.includes(role))
314
+ },
315
+ onFail: (set) => {
316
+ set.status = 403
317
+ return {
318
+ error: 'Insufficient permissions',
319
+ required: roles
320
+ }
321
+ }
322
+ })
323
+
324
+ // Uso
325
+ app
326
+ .use(requireRole('admin', 'moderator'))
327
+ .delete('/posts/:id', deletePost)
328
+ ```
329
+
330
+ ## 🔄 Comparação: Antes vs Depois
331
+
332
+ ### ❌ Antes (Verboso)
333
+
334
+ ```typescript
335
+ export const auth = () =>
336
+ new Elysia({ name: 'auth' })
337
+ .onBeforeHandle(async ({ headers, set, store }) => {
338
+ const authHeader = headers.authorization
339
+ if (!authHeader) {
340
+ set.status = 401
341
+ return {
342
+ success: false,
343
+ error: 'Authorization header missing',
344
+ message: 'Authorization header missing'
345
+ }
346
+ }
347
+
348
+ const token = jwtService.extractTokenFromAuthHeader(authHeader)
349
+ if (!token) {
350
+ set.status = 401
351
+ return {
352
+ success: false,
353
+ error: 'Invalid authorization format',
354
+ message: 'Invalid authorization format. Expected: Bearer <token>'
355
+ }
356
+ }
357
+
358
+ let payload
359
+ try {
360
+ payload = jwtService.verifyAccessToken(token)
361
+ } catch (err) {
362
+ set.status = 401
363
+ return {
364
+ success: false,
365
+ error: 'Invalid token',
366
+ message: err instanceof Error ? err.message : 'Invalid token'
367
+ }
368
+ }
369
+
370
+ const userDoc = await User.findById(payload.userId)
371
+ if (!userDoc) {
372
+ set.status = 401
373
+ return {
374
+ success: false,
375
+ error: 'User not found',
376
+ message: 'User not found'
377
+ }
378
+ }
379
+
380
+ Object.assign(store, { user: userDoc, token })
381
+ })
382
+ .as('plugin')
383
+ ```
384
+
385
+ ### ✅ Depois (Limpo e Direto)
386
+
387
+ ```typescript
388
+ export const auth = () =>
389
+ createGuard({
390
+ name: 'auth',
391
+ check: async ({ headers, store }) => {
392
+ const token = headers.authorization?.replace('Bearer ', '')
393
+ if (!token) return false
394
+
395
+ try {
396
+ const payload = jwtService.verifyAccessToken(token)
397
+ const user = await User.findById(payload.userId)
398
+
399
+ if (!user) return false
400
+
401
+ Object.assign(store, { user, token })
402
+ return true
403
+ } catch {
404
+ return false
405
+ }
406
+ },
407
+ onFail: (set) => {
408
+ set.status = 401
409
+ return {
410
+ success: false,
411
+ error: 'Authentication required'
412
+ }
413
+ }
414
+ })
415
+ ```
416
+
417
+ ### Benefícios:
418
+ - ✅ **50% menos código**
419
+ - ✅ **Mais legível** - lógica de validação separada da resposta de erro
420
+ - ✅ **Mais reutilizável** - padrão consistente
421
+ - ✅ **Type-safe** - TypeScript infere tipos automaticamente
422
+ - ✅ **Menos bugs** - menos código boilerplate manual
423
+
424
+ ## 🎯 Boas Práticas
425
+
426
+ ### 1. Nomeie seus middlewares descritivamente
427
+
428
+ ```typescript
429
+ // ❌ Ruim
430
+ const m = createMiddleware({ name: 'm', ... })
431
+
432
+ // ✅ Bom
433
+ const requireEmailVerification = createMiddleware({
434
+ name: 'emailVerification',
435
+ ...
436
+ })
437
+ ```
438
+
439
+ ### 2. Use composição para rotas complexas
440
+
441
+ ```typescript
442
+ // ✅ Crie middlewares compostos reutilizáveis
443
+ const protectedRoute = composeMiddleware({
444
+ name: 'protected',
445
+ middlewares: [auth(), rateLimit()]
446
+ })
447
+
448
+ const adminRoute = composeMiddleware({
449
+ name: 'admin',
450
+ middlewares: [protectedRoute(), requireAdmin()]
451
+ })
452
+ ```
453
+
454
+ ### 3. Use helpers de ambiente
455
+
456
+ ```typescript
457
+ import { isDevelopment, isProduction } from '../../../core/server/middleware'
458
+
459
+ const debugMiddleware = createMiddleware({
460
+ name: 'debug',
461
+ handler: ({ request }) => {
462
+ if (isDevelopment()) {
463
+ console.log('Request:', request.method, request.url)
464
+ }
465
+ },
466
+ nonBlocking: true
467
+ })
468
+ ```
469
+
470
+ ### 4. Separe validação de resposta de erro
471
+
472
+ ```typescript
473
+ // ✅ Bom - lógica clara e separada
474
+ const requirePremium = createGuard({
475
+ name: 'premium',
476
+ check: ({ store }) => store.user?.subscription === 'premium',
477
+ onFail: (set) => {
478
+ set.status = 402
479
+ return { error: 'Premium subscription required' }
480
+ }
481
+ })
482
+ ```
483
+
484
+ ## 📖 Ver Também
485
+
486
+ - [Documentação Elysia](https://elysiajs.com)
487
+ - [Exemplo completo em `auth-refactored-example.ts`](../../../app/server/middleware/auth-refactored-example.ts)
488
+ - [Código dos helpers](./elysia-helpers.ts)
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Elysia Middleware Helpers
3
+ * Utilities to simplify middleware creation for FluxStack apps using Elysia
4
+ */
5
+
6
+ import { Elysia } from 'elysia'
7
+
8
+ /**
9
+ * Options for creating middleware
10
+ */
11
+ export interface MiddlewareOptions<TContext = any> {
12
+ /** Unique name for the middleware (required for Elysia) */
13
+ name: string
14
+
15
+ /**
16
+ * Handler function that runs before the route handler.
17
+ * Return undefined to continue, or return a response to block execution.
18
+ */
19
+ handler: (context: TContext) => void | Promise<void> | any | Promise<any>
20
+
21
+ /**
22
+ * If true, uses derive() instead of onBeforeHandle().
23
+ * Use this when you want to add data to context without blocking.
24
+ */
25
+ nonBlocking?: boolean
26
+ }
27
+
28
+ /**
29
+ * Create a simple Elysia middleware plugin
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const myAuth = createMiddleware({
34
+ * name: 'myAuth',
35
+ * handler: ({ headers, set }) => {
36
+ * const token = headers.authorization
37
+ * if (!token) {
38
+ * set.status = 401
39
+ * return { error: 'Unauthorized' }
40
+ * }
41
+ * }
42
+ * })
43
+ *
44
+ * app.use(myAuth)
45
+ * ```
46
+ */
47
+ export function createMiddleware<TContext = any>(
48
+ options: MiddlewareOptions<TContext>
49
+ ) {
50
+ const { name, handler, nonBlocking = false } = options
51
+
52
+ if (nonBlocking) {
53
+ // Non-blocking: use derive() - adds to context without stopping execution
54
+ return new Elysia({ name }).derive(handler as any)
55
+ } else {
56
+ // Blocking: use onBeforeHandle() - can stop execution by returning a response
57
+ return new Elysia({ name }).onBeforeHandle(handler as any).as('plugin')
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Create a middleware that derives (adds) data to the context without blocking
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const addTimestamp = createDerive({
67
+ * name: 'timestamp',
68
+ * derive: () => ({ timestamp: Date.now() })
69
+ * })
70
+ *
71
+ * app.use(addTimestamp).get('/', ({ timestamp }) => ({ timestamp }))
72
+ * ```
73
+ */
74
+ export function createDerive<TDerived extends Record<string, any>>(options: {
75
+ name: string
76
+ derive: (context: any) => TDerived | Promise<TDerived>
77
+ }) {
78
+ return new Elysia({ name: options.name }).derive(options.derive)
79
+ }
80
+
81
+ /**
82
+ * Create a guard middleware (blocking middleware that validates conditions)
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const requireAdmin = createGuard({
87
+ * name: 'adminGuard',
88
+ * check: ({ store }) => {
89
+ * return store.user?.isAdmin === true
90
+ * },
91
+ * onFail: (set) => {
92
+ * set.status = 403
93
+ * return { error: 'Admin access required' }
94
+ * }
95
+ * })
96
+ *
97
+ * app.use(requireAdmin).get('/admin', () => 'Admin panel')
98
+ * ```
99
+ */
100
+ export function createGuard<TContext = any>(options: {
101
+ name: string
102
+ check: (context: TContext) => boolean | Promise<boolean>
103
+ onFail: (set: any, context: TContext) => any
104
+ }) {
105
+ return new Elysia({ name: options.name })
106
+ .onBeforeHandle(async (ctx) => {
107
+ const passed = await options.check(ctx as TContext)
108
+ if (!passed) {
109
+ return options.onFail((ctx as any).set, ctx as TContext)
110
+ }
111
+ })
112
+ .as('plugin')
113
+ }
114
+
115
+ /**
116
+ * Create a rate limiter middleware
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const apiRateLimit = createRateLimit({
121
+ * name: 'apiRateLimit',
122
+ * maxRequests: 100,
123
+ * windowMs: 60000, // 1 minute
124
+ * keyGenerator: ({ request }) => request.headers.get('x-api-key') || 'anonymous'
125
+ * })
126
+ * ```
127
+ */
128
+ export function createRateLimit(options: {
129
+ name: string
130
+ maxRequests: number
131
+ windowMs: number
132
+ keyGenerator?: (context: any) => string
133
+ message?: string
134
+ }) {
135
+ const {
136
+ name,
137
+ maxRequests,
138
+ windowMs,
139
+ keyGenerator = ({ request }: any) =>
140
+ request.headers.get('x-forwarded-for') ||
141
+ request.headers.get('x-real-ip') ||
142
+ 'unknown',
143
+ message = 'Too many requests'
144
+ } = options
145
+
146
+ const requests = new Map<string, { count: number; resetTime: number }>()
147
+
148
+ // Cleanup old entries periodically
149
+ setInterval(() => {
150
+ const now = Date.now()
151
+ for (const [key, data] of requests.entries()) {
152
+ if (now > data.resetTime) {
153
+ requests.delete(key)
154
+ }
155
+ }
156
+ }, Math.max(windowMs, 60000)) // At least every minute
157
+
158
+ return new Elysia({ name })
159
+ .onBeforeHandle((ctx) => {
160
+ const key = keyGenerator(ctx)
161
+ const now = Date.now()
162
+ const entry = requests.get(key)
163
+
164
+ if (entry) {
165
+ if (now > entry.resetTime) {
166
+ // Reset window
167
+ requests.set(key, { count: 1, resetTime: now + windowMs })
168
+ } else if (entry.count >= maxRequests) {
169
+ // Rate limit exceeded
170
+ ;(ctx as any).set.status = 429
171
+ return {
172
+ success: false,
173
+ error: 'RATE_LIMIT_EXCEEDED',
174
+ message
175
+ }
176
+ } else {
177
+ // Increment count
178
+ entry.count++
179
+ }
180
+ } else {
181
+ // First request
182
+ requests.set(key, { count: 1, resetTime: now + windowMs })
183
+ }
184
+ })
185
+ .as('plugin')
186
+ }
187
+
188
+ /**
189
+ * Create a composable middleware that runs multiple middlewares in sequence
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * const protectedRoute = composeMiddleware({
194
+ * name: 'protectedRoute',
195
+ * middlewares: [auth(), requireEmailVerification(), rateLimit()]
196
+ * })
197
+ *
198
+ * app.use(protectedRoute).get('/protected', () => 'Protected content')
199
+ * ```
200
+ */
201
+ export function composeMiddleware(options: {
202
+ name: string
203
+ middlewares: Elysia[]
204
+ }) {
205
+ let composed = new Elysia({ name: options.name })
206
+
207
+ for (const middleware of options.middlewares) {
208
+ composed = composed.use(middleware)
209
+ }
210
+
211
+ return composed.as('plugin')
212
+ }
213
+
214
+ /**
215
+ * Environment-aware configuration helper
216
+ */
217
+ export function isDevelopment() {
218
+ return process.env.NODE_ENV === 'development'
219
+ }
220
+
221
+ export function isProduction() {
222
+ return process.env.NODE_ENV === 'production'
223
+ }
224
+
225
+ export function isTest() {
226
+ return process.env.NODE_ENV === 'test'
227
+ }