create-fluxstack 1.16.0 → 1.17.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/app/client/src/App.tsx +8 -0
  3. package/app/client/src/live/AuthDemo.tsx +4 -4
  4. package/core/build/bundler.ts +40 -26
  5. package/core/build/flux-plugins-generator.ts +325 -325
  6. package/core/build/index.ts +92 -21
  7. package/core/cli/command-registry.ts +44 -46
  8. package/core/cli/commands/build.ts +11 -6
  9. package/core/cli/commands/create.ts +7 -5
  10. package/core/cli/commands/dev.ts +6 -5
  11. package/core/cli/commands/help.ts +3 -2
  12. package/core/cli/commands/make-plugin.ts +8 -7
  13. package/core/cli/commands/plugin-add.ts +60 -43
  14. package/core/cli/commands/plugin-deps.ts +73 -57
  15. package/core/cli/commands/plugin-list.ts +44 -41
  16. package/core/cli/commands/plugin-remove.ts +33 -22
  17. package/core/cli/generators/component.ts +770 -769
  18. package/core/cli/generators/controller.ts +9 -8
  19. package/core/cli/generators/index.ts +148 -146
  20. package/core/cli/generators/interactive.ts +228 -227
  21. package/core/cli/generators/plugin.ts +11 -10
  22. package/core/cli/generators/prompts.ts +83 -82
  23. package/core/cli/generators/route.ts +7 -6
  24. package/core/cli/generators/service.ts +10 -9
  25. package/core/cli/generators/template-engine.ts +2 -1
  26. package/core/cli/generators/types.ts +7 -7
  27. package/core/cli/generators/utils.ts +191 -191
  28. package/core/cli/index.ts +9 -8
  29. package/core/cli/plugin-discovery.ts +2 -2
  30. package/core/client/hooks/useAuth.ts +48 -48
  31. package/core/client/standalone.ts +18 -17
  32. package/core/client/state/createStore.ts +192 -192
  33. package/core/client/state/index.ts +14 -14
  34. package/core/config/index.ts +1 -0
  35. package/core/framework/client.ts +131 -131
  36. package/core/framework/index.ts +7 -7
  37. package/core/framework/server.ts +72 -112
  38. package/core/framework/types.ts +2 -2
  39. package/core/plugins/built-in/live-components/commands/create-live-component.ts +6 -3
  40. package/core/plugins/built-in/monitoring/index.ts +110 -68
  41. package/core/plugins/built-in/static/index.ts +2 -2
  42. package/core/plugins/built-in/swagger/index.ts +9 -9
  43. package/core/plugins/built-in/vite/index.ts +3 -3
  44. package/core/plugins/built-in/vite/vite-dev.ts +3 -3
  45. package/core/plugins/config.ts +50 -47
  46. package/core/plugins/discovery.ts +10 -4
  47. package/core/plugins/executor.ts +2 -2
  48. package/core/plugins/index.ts +206 -203
  49. package/core/plugins/manager.ts +21 -20
  50. package/core/plugins/registry.ts +76 -12
  51. package/core/plugins/types.ts +14 -14
  52. package/core/server/framework.ts +3 -189
  53. package/core/server/live/auto-generated-components.ts +11 -29
  54. package/core/server/live/index.ts +41 -31
  55. package/core/server/live/websocket-plugin.ts +11 -1
  56. package/core/server/middleware/elysia-helpers.ts +16 -15
  57. package/core/server/middleware/errorHandling.ts +14 -14
  58. package/core/server/middleware/index.ts +31 -31
  59. package/core/server/plugins/database.ts +181 -180
  60. package/core/server/plugins/static-files-plugin.ts +4 -3
  61. package/core/server/plugins/swagger.ts +11 -8
  62. package/core/server/rooms/RoomBroadcaster.ts +11 -10
  63. package/core/server/rooms/RoomSystem.ts +14 -11
  64. package/core/server/services/BaseService.ts +7 -7
  65. package/core/server/services/ServiceContainer.ts +5 -5
  66. package/core/server/services/index.ts +8 -8
  67. package/core/templates/create-project.ts +28 -27
  68. package/core/testing/index.ts +9 -9
  69. package/core/testing/setup.ts +73 -73
  70. package/core/types/api.ts +168 -168
  71. package/core/types/config.ts +5 -5
  72. package/core/types/index.ts +1 -1
  73. package/core/types/plugin.ts +2 -2
  74. package/core/types/types.ts +3 -3
  75. package/core/utils/build-logger.ts +324 -324
  76. package/core/utils/config-schema.ts +480 -480
  77. package/core/utils/env.ts +10 -8
  78. package/core/utils/errors/codes.ts +114 -114
  79. package/core/utils/errors/handlers.ts +30 -20
  80. package/core/utils/errors/index.ts +54 -46
  81. package/core/utils/errors/middleware.ts +113 -113
  82. package/core/utils/helpers.ts +19 -16
  83. package/core/utils/logger/colors.ts +114 -114
  84. package/core/utils/logger/config.ts +2 -2
  85. package/core/utils/logger/formatter.ts +82 -82
  86. package/core/utils/logger/group-logger.ts +101 -101
  87. package/core/utils/logger/index.ts +13 -3
  88. package/core/utils/logger/startup-banner.ts +2 -2
  89. package/core/utils/logger/winston-logger.ts +152 -152
  90. package/core/utils/monitoring/index.ts +211 -211
  91. package/core/utils/sync-version.ts +67 -66
  92. package/core/utils/version.ts +1 -1
  93. package/package.json +104 -100
  94. package/playwright-report/index.html +85 -0
  95. package/playwright.config.ts +31 -0
  96. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  97. package/plugins/crypto-auth/client/components/index.ts +11 -11
  98. package/plugins/crypto-auth/client/index.ts +11 -11
  99. package/plugins/crypto-auth/package.json +65 -65
  100. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  101. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +6 -5
  102. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +6 -5
  103. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +3 -3
  104. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  105. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  106. package/vite.config.ts +13 -0
  107. package/app/client/.live-stubs/LiveAdminPanel.js +0 -5
  108. package/app/client/.live-stubs/LiveCounter.js +0 -9
  109. package/app/client/.live-stubs/LiveForm.js +0 -11
  110. package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
  111. package/app/client/.live-stubs/LivePingPong.js +0 -10
  112. package/app/client/.live-stubs/LiveRoomChat.js +0 -11
  113. package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
  114. package/app/client/.live-stubs/LiveUpload.js +0 -15
  115. package/app/server/live/register-components.ts +0 -19
  116. package/core/build/live-components-generator.ts +0 -321
  117. package/core/live/ComponentRegistry.ts +0 -403
  118. package/core/live/types.ts +0 -241
  119. package/workspace.json +0 -6
@@ -4,7 +4,7 @@ export interface ErrorMetadata {
4
4
  requestId?: string
5
5
  userAgent?: string
6
6
  ip?: string
7
- [key: string]: any
7
+ [key: string]: unknown
8
8
  }
9
9
 
10
10
  export interface ErrorSerializedResponse {
@@ -12,7 +12,7 @@ export interface ErrorSerializedResponse {
12
12
  message: string
13
13
  code: string
14
14
  statusCode: number
15
- details?: any
15
+ details?: unknown
16
16
  timestamp: string
17
17
  correlationId?: string
18
18
  stack?: string
@@ -22,7 +22,7 @@ export interface ErrorSerializedResponse {
22
22
  export class FluxStackError extends Error {
23
23
  public readonly code: string
24
24
  public readonly statusCode: number
25
- public readonly context?: any
25
+ public readonly context?: unknown
26
26
  public readonly timestamp: Date
27
27
  public readonly metadata: ErrorMetadata
28
28
  public readonly isOperational: boolean
@@ -32,7 +32,7 @@ export class FluxStackError extends Error {
32
32
  message: string,
33
33
  code: string,
34
34
  statusCode: number = 500,
35
- context?: any,
35
+ context?: unknown,
36
36
  metadata: ErrorMetadata = {},
37
37
  isOperational: boolean = true,
38
38
  userMessage?: string
@@ -66,7 +66,7 @@ export class FluxStackError extends Error {
66
66
  const stackValue = this.stack as unknown
67
67
  if (Array.isArray(stackValue)) {
68
68
  return stackValue
69
- .map((site: any) => {
69
+ .map((site: { getFileName?: () => string; getLineNumber?: () => number; getColumnNumber?: () => number; getFunctionName?: () => string }) => {
70
70
  try {
71
71
  const fileName = site.getFileName?.() || 'unknown'
72
72
  const lineNumber = site.getLineNumber?.() || 0
@@ -106,7 +106,7 @@ export class FluxStackError extends Error {
106
106
  message: this.userMessage || this.message,
107
107
  code: this.code,
108
108
  statusCode: this.statusCode,
109
- ...(this.context && { details: this.context }),
109
+ ...(this.context !== undefined ? { details: this.context } : {}),
110
110
  timestamp: this.timestamp.toISOString(),
111
111
  ...(this.metadata.correlationId && { correlationId: this.metadata.correlationId }),
112
112
  ...(isDevelopment && { stack: this.formatStack() })
@@ -133,7 +133,7 @@ export class FluxStackError extends Error {
133
133
 
134
134
  // Validation Errors (400)
135
135
  export class ValidationError extends FluxStackError {
136
- constructor(message: string, context?: any, metadata?: ErrorMetadata) {
136
+ constructor(message: string, context?: unknown, metadata?: ErrorMetadata) {
137
137
  super(
138
138
  message,
139
139
  'VALIDATION_ERROR',
@@ -148,7 +148,7 @@ export class ValidationError extends FluxStackError {
148
148
  }
149
149
 
150
150
  export class InvalidInputError extends FluxStackError {
151
- constructor(field: string, value?: any, metadata?: ErrorMetadata) {
151
+ constructor(field: string, value?: unknown, metadata?: ErrorMetadata) {
152
152
  super(
153
153
  `Invalid input for field: ${field}`,
154
154
  'INVALID_INPUT',
@@ -179,7 +179,7 @@ export class MissingRequiredFieldError extends FluxStackError {
179
179
 
180
180
  // Authentication Errors (401)
181
181
  export class UnauthorizedError extends FluxStackError {
182
- constructor(message: string = 'Authentication required', context?: any, metadata?: ErrorMetadata) {
182
+ constructor(message: string = 'Authentication required', context?: unknown, metadata?: ErrorMetadata) {
183
183
  super(
184
184
  message,
185
185
  'UNAUTHORIZED',
@@ -225,7 +225,7 @@ export class TokenExpiredError extends FluxStackError {
225
225
 
226
226
  // Authorization Errors (403)
227
227
  export class ForbiddenError extends FluxStackError {
228
- constructor(message: string = 'Access forbidden', context?: any, metadata?: ErrorMetadata) {
228
+ constructor(message: string = 'Access forbidden', context?: unknown, metadata?: ErrorMetadata) {
229
229
  super(
230
230
  message,
231
231
  'FORBIDDEN',
@@ -256,7 +256,7 @@ export class InsufficientPermissionsError extends FluxStackError {
256
256
 
257
257
  // Not Found Errors (404)
258
258
  export class NotFoundError extends FluxStackError {
259
- constructor(resource: string, context?: any, metadata?: ErrorMetadata) {
259
+ constructor(resource: string, context?: unknown, metadata?: ErrorMetadata) {
260
260
  super(
261
261
  `${resource} not found`,
262
262
  'NOT_FOUND',
@@ -302,7 +302,7 @@ export class EndpointNotFoundError extends FluxStackError {
302
302
 
303
303
  // Conflict Errors (409)
304
304
  export class ConflictError extends FluxStackError {
305
- constructor(message: string, context?: any, metadata?: ErrorMetadata) {
305
+ constructor(message: string, context?: unknown, metadata?: ErrorMetadata) {
306
306
  super(
307
307
  message,
308
308
  'CONFLICT',
@@ -349,7 +349,7 @@ export class RateLimitExceededError extends FluxStackError {
349
349
 
350
350
  // Server Errors (500)
351
351
  export class InternalServerError extends FluxStackError {
352
- constructor(message: string = 'Internal server error', context?: any, metadata?: ErrorMetadata) {
352
+ constructor(message: string = 'Internal server error', context?: unknown, metadata?: ErrorMetadata) {
353
353
  super(
354
354
  message,
355
355
  'INTERNAL_SERVER_ERROR',
@@ -364,7 +364,7 @@ export class InternalServerError extends FluxStackError {
364
364
  }
365
365
 
366
366
  export class DatabaseError extends FluxStackError {
367
- constructor(operation: string, details?: any, metadata?: ErrorMetadata) {
367
+ constructor(operation: string, details?: unknown, metadata?: ErrorMetadata) {
368
368
  super(
369
369
  `Database operation failed: ${operation}`,
370
370
  'DATABASE_ERROR',
@@ -379,7 +379,7 @@ export class DatabaseError extends FluxStackError {
379
379
  }
380
380
 
381
381
  export class ExternalServiceError extends FluxStackError {
382
- constructor(service: string, details?: any, metadata?: ErrorMetadata) {
382
+ constructor(service: string, details?: unknown, metadata?: ErrorMetadata) {
383
383
  super(
384
384
  `External service error: ${service}`,
385
385
  'EXTERNAL_SERVICE_ERROR',
@@ -395,7 +395,7 @@ export class ExternalServiceError extends FluxStackError {
395
395
 
396
396
  // Service Unavailable Errors (503)
397
397
  export class ServiceUnavailableError extends FluxStackError {
398
- constructor(message: string = 'Service unavailable', context?: any, metadata?: ErrorMetadata) {
398
+ constructor(message: string = 'Service unavailable', context?: unknown, metadata?: ErrorMetadata) {
399
399
  super(
400
400
  message,
401
401
  'SERVICE_UNAVAILABLE',
@@ -428,7 +428,7 @@ export class MaintenanceModeError extends FluxStackError {
428
428
 
429
429
  // Plugin Errors
430
430
  export class PluginError extends FluxStackError {
431
- constructor(pluginName: string, message: string, context?: any, metadata?: ErrorMetadata) {
431
+ constructor(pluginName: string, message: string, context?: Record<string, unknown>, metadata?: ErrorMetadata) {
432
432
  super(
433
433
  `Plugin error in ${pluginName}: ${message}`,
434
434
  'PLUGIN_ERROR',
@@ -459,7 +459,7 @@ export class PluginNotFoundError extends FluxStackError {
459
459
 
460
460
  // Configuration Errors
461
461
  export class ConfigError extends FluxStackError {
462
- constructor(message: string, context?: any, metadata?: ErrorMetadata) {
462
+ constructor(message: string, context?: unknown, metadata?: ErrorMetadata) {
463
463
  super(
464
464
  `Configuration error: ${message}`,
465
465
  'CONFIG_ERROR',
@@ -474,7 +474,7 @@ export class ConfigError extends FluxStackError {
474
474
  }
475
475
 
476
476
  export class InvalidConfigError extends FluxStackError {
477
- constructor(field: string, value?: any, metadata?: ErrorMetadata) {
477
+ constructor(field: string, value?: unknown, metadata?: ErrorMetadata) {
478
478
  super(
479
479
  `Invalid configuration for field: ${field}`,
480
480
  'INVALID_CONFIG',
@@ -490,7 +490,7 @@ export class InvalidConfigError extends FluxStackError {
490
490
 
491
491
  // Build Errors
492
492
  export class BuildError extends FluxStackError {
493
- constructor(message: string, context?: any, metadata?: ErrorMetadata) {
493
+ constructor(message: string, context?: unknown, metadata?: ErrorMetadata) {
494
494
  super(
495
495
  `Build error: ${message}`,
496
496
  'BUILD_ERROR',
@@ -505,7 +505,7 @@ export class BuildError extends FluxStackError {
505
505
  }
506
506
 
507
507
  export class CompilationError extends FluxStackError {
508
- constructor(file: string, details?: any, metadata?: ErrorMetadata) {
508
+ constructor(file: string, details?: unknown, metadata?: ErrorMetadata) {
509
509
  super(
510
510
  `Compilation failed for file: ${file}`,
511
511
  'COMPILATION_ERROR',
@@ -520,27 +520,32 @@ export class CompilationError extends FluxStackError {
520
520
  }
521
521
 
522
522
  // Utility functions for error handling
523
- export const isFluxStackError = (error: any): error is FluxStackError => {
523
+ export const isFluxStackError = (error: unknown): error is FluxStackError => {
524
524
  return error instanceof FluxStackError
525
525
  }
526
526
 
527
- export const isOperationalError = (error: any): boolean => {
527
+ export const isOperationalError = (error: unknown): boolean => {
528
528
  return isFluxStackError(error) && error.isOperational
529
529
  }
530
530
 
531
531
  export const createErrorFromCode = (
532
532
  code: string,
533
533
  message?: string,
534
- context?: any,
534
+ context?: Record<string, unknown>,
535
535
  metadata?: ErrorMetadata
536
536
  ): FluxStackError => {
537
+ const str = (val: unknown, fallback: string): string =>
538
+ typeof val === 'string' ? val : fallback
539
+ const num = (val: unknown, fallback: number): number =>
540
+ typeof val === 'number' ? val : fallback
541
+
537
542
  switch (code) {
538
543
  case 'VALIDATION_ERROR':
539
544
  return new ValidationError(message || 'Validation failed', context, metadata)
540
545
  case 'INVALID_INPUT':
541
- return new InvalidInputError(context?.field || 'unknown', context?.value, metadata)
546
+ return new InvalidInputError(str(context?.field, 'unknown'), context?.value, metadata)
542
547
  case 'MISSING_REQUIRED_FIELD':
543
- return new MissingRequiredFieldError(context?.field || 'unknown', metadata)
548
+ return new MissingRequiredFieldError(str(context?.field, 'unknown'), metadata)
544
549
  case 'UNAUTHORIZED':
545
550
  return new UnauthorizedError(message, context, metadata)
546
551
  case 'INVALID_TOKEN':
@@ -550,62 +555,65 @@ export const createErrorFromCode = (
550
555
  case 'FORBIDDEN':
551
556
  return new ForbiddenError(message, context, metadata)
552
557
  case 'INSUFFICIENT_PERMISSIONS':
553
- return new InsufficientPermissionsError(context?.requiredPermission || 'unknown', metadata)
558
+ return new InsufficientPermissionsError(str(context?.requiredPermission, 'unknown'), metadata)
554
559
  case 'NOT_FOUND':
555
- return new NotFoundError(context?.resource || 'Resource', context, metadata)
560
+ return new NotFoundError(str(context?.resource, 'Resource'), context, metadata)
556
561
  case 'RESOURCE_NOT_FOUND':
557
562
  return new ResourceNotFoundError(
558
- context?.resourceType || 'Resource',
559
- context?.identifier || 'unknown',
563
+ str(context?.resourceType, 'Resource'),
564
+ str(context?.identifier, 'unknown'),
560
565
  metadata
561
566
  )
562
567
  case 'ENDPOINT_NOT_FOUND':
563
568
  return new EndpointNotFoundError(
564
- context?.method || 'GET',
565
- context?.path || '/unknown',
569
+ str(context?.method, 'GET'),
570
+ str(context?.path, '/unknown'),
566
571
  metadata
567
572
  )
568
573
  case 'CONFLICT':
569
574
  return new ConflictError(message || 'Resource conflict', context, metadata)
570
575
  case 'RESOURCE_ALREADY_EXISTS':
571
576
  return new ResourceAlreadyExistsError(
572
- context?.resourceType || 'Resource',
573
- context?.identifier || 'unknown',
577
+ str(context?.resourceType, 'Resource'),
578
+ str(context?.identifier, 'unknown'),
574
579
  metadata
575
580
  )
576
581
  case 'RATE_LIMIT_EXCEEDED':
577
582
  return new RateLimitExceededError(
578
- context?.limit || 100,
579
- context?.windowMs || 60000,
583
+ num(context?.limit, 100),
584
+ num(context?.windowMs, 60000),
580
585
  metadata
581
586
  )
582
587
  case 'INTERNAL_SERVER_ERROR':
583
588
  return new InternalServerError(message, context, metadata)
584
589
  case 'DATABASE_ERROR':
585
- return new DatabaseError(context?.operation || 'unknown', context?.details, metadata)
590
+ return new DatabaseError(str(context?.operation, 'unknown'), context?.details, metadata)
586
591
  case 'EXTERNAL_SERVICE_ERROR':
587
- return new ExternalServiceError(context?.service || 'unknown', context?.details, metadata)
592
+ return new ExternalServiceError(str(context?.service, 'unknown'), context?.details, metadata)
588
593
  case 'SERVICE_UNAVAILABLE':
589
594
  return new ServiceUnavailableError(message, context, metadata)
590
595
  case 'MAINTENANCE_MODE':
591
- return new MaintenanceModeError(context?.estimatedDuration, metadata)
596
+ return new MaintenanceModeError(
597
+ typeof context?.estimatedDuration === 'string' ? context.estimatedDuration : undefined,
598
+ metadata
599
+ )
592
600
  case 'PLUGIN_ERROR':
593
601
  return new PluginError(
594
- context?.pluginName || 'unknown',
602
+ str(context?.pluginName, 'unknown'),
595
603
  message || 'Plugin error',
596
604
  context,
597
605
  metadata
598
606
  )
599
607
  case 'PLUGIN_NOT_FOUND':
600
- return new PluginNotFoundError(context?.pluginName || 'unknown', metadata)
608
+ return new PluginNotFoundError(str(context?.pluginName, 'unknown'), metadata)
601
609
  case 'CONFIG_ERROR':
602
610
  return new ConfigError(message || 'Configuration error', context, metadata)
603
611
  case 'INVALID_CONFIG':
604
- return new InvalidConfigError(context?.field || 'unknown', context?.value, metadata)
612
+ return new InvalidConfigError(str(context?.field, 'unknown'), context?.value, metadata)
605
613
  case 'BUILD_ERROR':
606
614
  return new BuildError(message || 'Build error', context, metadata)
607
615
  case 'COMPILATION_ERROR':
608
- return new CompilationError(context?.file || 'unknown', context?.details, metadata)
616
+ return new CompilationError(str(context?.file, 'unknown'), context?.details, metadata)
609
617
  default:
610
618
  return new FluxStackError(message || 'Unknown error', code, 500, context, metadata)
611
619
  }
@@ -617,13 +625,13 @@ export const wrapError = (error: Error, metadata?: ErrorMetadata): FluxStackErro
617
625
  }
618
626
 
619
627
  // Detect Elysia validation errors (thrown by TypeBox schema validation)
620
- const errorAny = error as any
628
+ const errorWithStatus = error as Error & { status?: number }
621
629
  if (
622
630
  error.constructor?.name === 'ValidationError' ||
623
631
  error.constructor?.name === 'TransformDecodeError' ||
624
- (typeof errorAny.status === 'number' && errorAny.status >= 400 && errorAny.status < 500)
632
+ (typeof errorWithStatus.status === 'number' && errorWithStatus.status >= 400 && errorWithStatus.status < 500)
625
633
  ) {
626
- const status = errorAny.status ?? 422
634
+ const status = errorWithStatus.status ?? 422
627
635
  const message = error.message || 'Validation failed'
628
636
  return new ValidationError(message, { originalError: error.name, status }, metadata)
629
637
  }
@@ -1,114 +1,114 @@
1
- import { Elysia } from 'elysia'
2
- import { EnhancedErrorHandler, type ErrorHandlerContext, type ErrorHandlerOptions, type ErrorMetricsCollector } from './handlers'
3
- import type { Logger } from '../logger/index'
4
-
5
- export interface ErrorMiddlewareOptions extends ErrorHandlerOptions {
6
- logger?: Logger
7
- isDevelopment?: boolean
8
- enableRequestContext?: boolean
9
- metricsCollector?: ErrorMetricsCollector
10
- }
11
-
12
- export const errorMiddleware = (options: ErrorMiddlewareOptions = {}) => {
13
- const handler = new EnhancedErrorHandler(options)
14
-
15
- return new Elysia({ name: 'error-handler' })
16
- .onError(async ({ error, request, path, set }) => {
17
- // Extract request context
18
- const context: ErrorHandlerContext = {
19
- logger: options.logger || console as any, // Fallback to console if no logger provided
20
- isDevelopment: options.isDevelopment ?? process.env.NODE_ENV === 'development',
21
- request,
22
- path,
23
- method: request.method,
24
- correlationId: request.headers.get('x-correlation-id') || undefined,
25
- userId: request.headers.get('x-user-id') || undefined,
26
- userAgent: request.headers.get('user-agent') || undefined,
27
- ip: request.headers.get('x-forwarded-for') ||
28
- request.headers.get('x-real-ip') ||
29
- 'unknown',
30
- metricsCollector: options.metricsCollector
31
- }
32
-
33
- try {
34
- // Convert Elysia error to standard Error if needed
35
- const standardError = error instanceof Error ? error : new Error(String(error))
36
- const errorResponse = await handler.handle(standardError, context)
37
-
38
- // Set response status code
39
- set.status = errorResponse.error.statusCode
40
-
41
- // Set correlation ID header if available
42
- if (errorResponse.error.correlationId) {
43
- set.headers['x-correlation-id'] = errorResponse.error.correlationId
44
- }
45
-
46
- return errorResponse
47
- } catch (handlerError) {
48
- // Fallback error handling if the error handler itself fails
49
- const fallbackLogger = options.logger || console as any
50
- fallbackLogger.error('Error handler failed', {
51
- originalError: error instanceof Error ? error.message : String(error),
52
- handlerError: handlerError instanceof Error ? handlerError.message : handlerError
53
- })
54
-
55
- set.status = 500
56
- return {
57
- error: {
58
- message: 'Internal server error',
59
- code: 'INTERNAL_ERROR',
60
- statusCode: 500,
61
- timestamp: new Date().toISOString()
62
- }
63
- }
64
- }
65
- })
66
- }
67
-
68
- // Correlation ID middleware to add correlation IDs to requests
69
- export const correlationIdMiddleware = () => {
70
- return new Elysia({ name: 'correlation-id' })
71
- .onRequest(({ request, set }) => {
72
- // Check if correlation ID already exists in headers
73
- let correlationId = request.headers.get('x-correlation-id')
74
-
75
- // Generate new correlation ID if not present
76
- if (!correlationId) {
77
- correlationId = crypto.randomUUID()
78
- }
79
-
80
- // Add correlation ID to response headers
81
- set.headers['x-correlation-id'] = correlationId
82
-
83
- // Store correlation ID in request context for later use
84
- // Note: This would typically be stored in a request-scoped context
85
- // For now, we'll rely on the error handler to extract it from headers
86
- })
87
- }
88
-
89
- // Request context middleware to extract and store request information
90
- export const requestContextMiddleware = () => {
91
- return new Elysia({ name: 'request-context' })
92
- .onRequest(({ request, set }) => {
93
- // Extract useful request information and store in headers for error handling
94
- const userAgent = request.headers.get('user-agent')
95
- const ip = request.headers.get('x-forwarded-for') ||
96
- request.headers.get('x-real-ip') ||
97
- 'unknown'
98
-
99
- // Store in custom headers for error handler access
100
- // In a real implementation, this would use request-scoped storage
101
- if (userAgent) {
102
- set.headers['x-internal-user-agent'] = userAgent
103
- }
104
- set.headers['x-internal-ip'] = ip
105
- })
106
- }
107
-
108
- // Combined error handling middleware with all features
109
- export const fullErrorHandlingMiddleware = (options: ErrorMiddlewareOptions = {}) => {
110
- return new Elysia({ name: 'full-error-handling' })
111
- .use(correlationIdMiddleware())
112
- .use(requestContextMiddleware())
113
- .use(errorMiddleware(options))
1
+ import { Elysia } from 'elysia'
2
+ import { EnhancedErrorHandler, type ErrorHandlerContext, type ErrorHandlerOptions, type ErrorMetricsCollector } from './handlers'
3
+ import type { Logger } from '../logger/index'
4
+
5
+ export interface ErrorMiddlewareOptions extends ErrorHandlerOptions {
6
+ logger?: Logger
7
+ isDevelopment?: boolean
8
+ enableRequestContext?: boolean
9
+ metricsCollector?: ErrorMetricsCollector
10
+ }
11
+
12
+ export const errorMiddleware = (options: ErrorMiddlewareOptions = {}) => {
13
+ const handler = new EnhancedErrorHandler(options)
14
+
15
+ return new Elysia({ name: 'error-handler' })
16
+ .onError(async ({ error, request, path, set }) => {
17
+ // Extract request context
18
+ const context: ErrorHandlerContext = {
19
+ logger: options.logger || console as unknown as Logger, // Fallback to console if no logger provided
20
+ isDevelopment: options.isDevelopment ?? process.env.NODE_ENV === 'development',
21
+ request,
22
+ path,
23
+ method: request.method,
24
+ correlationId: request.headers.get('x-correlation-id') || undefined,
25
+ userId: request.headers.get('x-user-id') || undefined,
26
+ userAgent: request.headers.get('user-agent') || undefined,
27
+ ip: request.headers.get('x-forwarded-for') ||
28
+ request.headers.get('x-real-ip') ||
29
+ 'unknown',
30
+ metricsCollector: options.metricsCollector
31
+ }
32
+
33
+ try {
34
+ // Convert Elysia error to standard Error if needed
35
+ const standardError = error instanceof Error ? error : new Error(String(error))
36
+ const errorResponse = await handler.handle(standardError, context)
37
+
38
+ // Set response status code
39
+ set.status = errorResponse.error.statusCode
40
+
41
+ // Set correlation ID header if available
42
+ if (errorResponse.error.correlationId) {
43
+ set.headers['x-correlation-id'] = errorResponse.error.correlationId
44
+ }
45
+
46
+ return errorResponse
47
+ } catch (handlerError) {
48
+ // Fallback error handling if the error handler itself fails
49
+ const fallbackLogger = options.logger || console as unknown as Logger
50
+ fallbackLogger.error('Error handler failed', {
51
+ originalError: error instanceof Error ? error.message : String(error),
52
+ handlerError: handlerError instanceof Error ? handlerError.message : handlerError
53
+ })
54
+
55
+ set.status = 500
56
+ return {
57
+ error: {
58
+ message: 'Internal server error',
59
+ code: 'INTERNAL_ERROR',
60
+ statusCode: 500,
61
+ timestamp: new Date().toISOString()
62
+ }
63
+ }
64
+ }
65
+ })
66
+ }
67
+
68
+ // Correlation ID middleware to add correlation IDs to requests
69
+ export const correlationIdMiddleware = () => {
70
+ return new Elysia({ name: 'correlation-id' })
71
+ .onRequest(({ request, set }) => {
72
+ // Check if correlation ID already exists in headers
73
+ let correlationId = request.headers.get('x-correlation-id')
74
+
75
+ // Generate new correlation ID if not present
76
+ if (!correlationId) {
77
+ correlationId = crypto.randomUUID()
78
+ }
79
+
80
+ // Add correlation ID to response headers
81
+ set.headers['x-correlation-id'] = correlationId
82
+
83
+ // Store correlation ID in request context for later use
84
+ // Note: This would typically be stored in a request-scoped context
85
+ // For now, we'll rely on the error handler to extract it from headers
86
+ })
87
+ }
88
+
89
+ // Request context middleware to extract and store request information
90
+ export const requestContextMiddleware = () => {
91
+ return new Elysia({ name: 'request-context' })
92
+ .onRequest(({ request, set }) => {
93
+ // Extract useful request information and store in headers for error handling
94
+ const userAgent = request.headers.get('user-agent')
95
+ const ip = request.headers.get('x-forwarded-for') ||
96
+ request.headers.get('x-real-ip') ||
97
+ 'unknown'
98
+
99
+ // Store in custom headers for error handler access
100
+ // In a real implementation, this would use request-scoped storage
101
+ if (userAgent) {
102
+ set.headers['x-internal-user-agent'] = userAgent
103
+ }
104
+ set.headers['x-internal-ip'] = ip
105
+ })
106
+ }
107
+
108
+ // Combined error handling middleware with all features
109
+ export const fullErrorHandlingMiddleware = (options: ErrorMiddlewareOptions = {}) => {
110
+ return new Elysia({ name: 'full-error-handling' })
111
+ .use(correlationIdMiddleware())
112
+ .use(requestContextMiddleware())
113
+ .use(errorMiddleware(options))
114
114
  }
@@ -54,13 +54,13 @@ export const retry = async <T>(
54
54
  throw lastError!
55
55
  }
56
56
 
57
- export const debounce = <T extends (...args: any[]) => any>(
58
- func: T,
57
+ export const debounce = <A extends unknown[]>(
58
+ func: (...args: A) => void,
59
59
  wait: number
60
- ): ((...args: Parameters<T>) => void) => {
60
+ ): ((...args: A) => void) => {
61
61
  let timeout: NodeJS.Timeout | null = null
62
-
63
- return (...args: Parameters<T>) => {
62
+
63
+ return (...args: A) => {
64
64
  if (timeout) {
65
65
  clearTimeout(timeout)
66
66
  }
@@ -71,13 +71,13 @@ export const debounce = <T extends (...args: any[]) => any>(
71
71
  }
72
72
  }
73
73
 
74
- export const throttle = <T extends (...args: any[]) => any>(
75
- func: T,
74
+ export const throttle = <A extends unknown[]>(
75
+ func: (...args: A) => void,
76
76
  limit: number
77
- ): ((...args: Parameters<T>) => void) => {
77
+ ): ((...args: A) => void) => {
78
78
  let inThrottle: boolean = false
79
-
80
- return (...args: Parameters<T>) => {
79
+
80
+ return (...args: A) => {
81
81
  if (!inThrottle) {
82
82
  func(...args)
83
83
  inThrottle = true
@@ -99,7 +99,7 @@ export const isDevelopment = (): boolean => getNodeEnv() === 'development'
99
99
 
100
100
  export const isTest = (): boolean => getNodeEnv() === 'test'
101
101
 
102
- export const deepMerge = <T extends Record<string, any>>(target: T, source: Partial<T>): T => {
102
+ export const deepMerge = <T extends Record<string, unknown>>(target: T, source: Partial<T>): T => {
103
103
  const result = { ...target }
104
104
 
105
105
  for (const key in source) {
@@ -115,7 +115,10 @@ export const deepMerge = <T extends Record<string, any>>(target: T, source: Part
115
115
  typeof targetValue === 'object' &&
116
116
  !Array.isArray(targetValue)
117
117
  ) {
118
- result[key] = deepMerge(targetValue, sourceValue)
118
+ result[key] = deepMerge(
119
+ targetValue as Record<string, unknown>,
120
+ sourceValue as Partial<Record<string, unknown>>
121
+ ) as T[Extract<keyof T, string>]
119
122
  } else {
120
123
  result[key] = sourceValue as T[Extract<keyof T, string>]
121
124
  }
@@ -125,7 +128,7 @@ export const deepMerge = <T extends Record<string, any>>(target: T, source: Part
125
128
  return result
126
129
  }
127
130
 
128
- export const pick = <T extends Record<string, any>, K extends keyof T>(
131
+ export const pick = <T extends Record<string, unknown>, K extends keyof T>(
129
132
  obj: T,
130
133
  keys: K[]
131
134
  ): Pick<T, K> => {
@@ -140,7 +143,7 @@ export const pick = <T extends Record<string, any>, K extends keyof T>(
140
143
  return result
141
144
  }
142
145
 
143
- export const omit = <T extends Record<string, any>, K extends keyof T>(
146
+ export const omit = <T extends Record<string, unknown>, K extends keyof T>(
144
147
  obj: T,
145
148
  keys: K[]
146
149
  ): Omit<T, K> => {
@@ -164,7 +167,7 @@ export const generateId = (length: number = 8): string => {
164
167
  return result
165
168
  }
166
169
 
167
- export const safeJsonParse = <T = any>(json: string, fallback: T): T => {
170
+ export const safeJsonParse = <T = unknown>(json: string, fallback: T): T => {
168
171
  try {
169
172
  return JSON.parse(json)
170
173
  } catch {
@@ -172,7 +175,7 @@ export const safeJsonParse = <T = any>(json: string, fallback: T): T => {
172
175
  }
173
176
  }
174
177
 
175
- export const safeJsonStringify = (obj: any, fallback: string = '{}'): string => {
178
+ export const safeJsonStringify = (obj: unknown, fallback: string = '{}'): string => {
176
179
  try {
177
180
  return JSON.stringify(obj)
178
181
  } catch {