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.
- package/CHANGELOG.md +80 -0
- package/app/client/src/App.tsx +8 -0
- package/app/client/src/live/AuthDemo.tsx +4 -4
- package/core/build/bundler.ts +40 -26
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +92 -21
- package/core/cli/command-registry.ts +44 -46
- package/core/cli/commands/build.ts +11 -6
- package/core/cli/commands/create.ts +7 -5
- package/core/cli/commands/dev.ts +6 -5
- package/core/cli/commands/help.ts +3 -2
- package/core/cli/commands/make-plugin.ts +8 -7
- package/core/cli/commands/plugin-add.ts +60 -43
- package/core/cli/commands/plugin-deps.ts +73 -57
- package/core/cli/commands/plugin-list.ts +44 -41
- package/core/cli/commands/plugin-remove.ts +33 -22
- package/core/cli/generators/component.ts +770 -769
- package/core/cli/generators/controller.ts +9 -8
- package/core/cli/generators/index.ts +148 -146
- package/core/cli/generators/interactive.ts +228 -227
- package/core/cli/generators/plugin.ts +11 -10
- package/core/cli/generators/prompts.ts +83 -82
- package/core/cli/generators/route.ts +7 -6
- package/core/cli/generators/service.ts +10 -9
- package/core/cli/generators/template-engine.ts +2 -1
- package/core/cli/generators/types.ts +7 -7
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +9 -8
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/standalone.ts +18 -17
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +1 -0
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +72 -112
- package/core/framework/types.ts +2 -2
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +6 -3
- package/core/plugins/built-in/monitoring/index.ts +110 -68
- package/core/plugins/built-in/static/index.ts +2 -2
- package/core/plugins/built-in/swagger/index.ts +9 -9
- package/core/plugins/built-in/vite/index.ts +3 -3
- package/core/plugins/built-in/vite/vite-dev.ts +3 -3
- package/core/plugins/config.ts +50 -47
- package/core/plugins/discovery.ts +10 -4
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +206 -203
- package/core/plugins/manager.ts +21 -20
- package/core/plugins/registry.ts +76 -12
- package/core/plugins/types.ts +14 -14
- package/core/server/framework.ts +3 -189
- package/core/server/live/auto-generated-components.ts +11 -29
- package/core/server/live/index.ts +41 -31
- package/core/server/live/websocket-plugin.ts +11 -1
- package/core/server/middleware/elysia-helpers.ts +16 -15
- package/core/server/middleware/errorHandling.ts +14 -14
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +181 -180
- package/core/server/plugins/static-files-plugin.ts +4 -3
- package/core/server/plugins/swagger.ts +11 -8
- package/core/server/rooms/RoomBroadcaster.ts +11 -10
- package/core/server/rooms/RoomSystem.ts +14 -11
- package/core/server/services/BaseService.ts +7 -7
- package/core/server/services/ServiceContainer.ts +5 -5
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +28 -27
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/config.ts +5 -5
- package/core/types/index.ts +1 -1
- package/core/types/plugin.ts +2 -2
- package/core/types/types.ts +3 -3
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +10 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +30 -20
- package/core/utils/errors/index.ts +54 -46
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +19 -16
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +2 -2
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +13 -3
- package/core/utils/logger/startup-banner.ts +2 -2
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +67 -66
- package/core/utils/version.ts +1 -1
- package/package.json +104 -100
- package/playwright-report/index.html +85 -0
- package/playwright.config.ts +31 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +6 -5
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +6 -5
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/vite.config.ts +13 -0
- package/app/client/.live-stubs/LiveAdminPanel.js +0 -5
- package/app/client/.live-stubs/LiveCounter.js +0 -9
- package/app/client/.live-stubs/LiveForm.js +0 -11
- package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
- package/app/client/.live-stubs/LivePingPong.js +0 -10
- package/app/client/.live-stubs/LiveRoomChat.js +0 -11
- package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
- package/app/client/.live-stubs/LiveUpload.js +0 -15
- package/app/server/live/register-components.ts +0 -19
- package/core/build/live-components-generator.ts +0 -321
- package/core/live/ComponentRegistry.ts +0 -403
- package/core/live/types.ts +0 -241
- 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]:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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:
|
|
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
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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:
|
|
523
|
+
export const isFluxStackError = (error: unknown): error is FluxStackError => {
|
|
524
524
|
return error instanceof FluxStackError
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
export const isOperationalError = (error:
|
|
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?:
|
|
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
|
|
546
|
+
return new InvalidInputError(str(context?.field, 'unknown'), context?.value, metadata)
|
|
542
547
|
case 'MISSING_REQUIRED_FIELD':
|
|
543
|
-
return new MissingRequiredFieldError(context?.field
|
|
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
|
|
558
|
+
return new InsufficientPermissionsError(str(context?.requiredPermission, 'unknown'), metadata)
|
|
554
559
|
case 'NOT_FOUND':
|
|
555
|
-
return new NotFoundError(context?.resource
|
|
560
|
+
return new NotFoundError(str(context?.resource, 'Resource'), context, metadata)
|
|
556
561
|
case 'RESOURCE_NOT_FOUND':
|
|
557
562
|
return new ResourceNotFoundError(
|
|
558
|
-
context?.resourceType
|
|
559
|
-
context?.identifier
|
|
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
|
|
565
|
-
context?.path
|
|
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
|
|
573
|
-
context?.identifier
|
|
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
|
|
579
|
-
context?.windowMs
|
|
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
|
|
590
|
+
return new DatabaseError(str(context?.operation, 'unknown'), context?.details, metadata)
|
|
586
591
|
case 'EXTERNAL_SERVICE_ERROR':
|
|
587
|
-
return new ExternalServiceError(context?.service
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
632
|
+
(typeof errorWithStatus.status === 'number' && errorWithStatus.status >= 400 && errorWithStatus.status < 500)
|
|
625
633
|
) {
|
|
626
|
-
const status =
|
|
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
|
|
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
|
|
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
|
}
|
package/core/utils/helpers.ts
CHANGED
|
@@ -54,13 +54,13 @@ export const retry = async <T>(
|
|
|
54
54
|
throw lastError!
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export const debounce = <
|
|
58
|
-
func:
|
|
57
|
+
export const debounce = <A extends unknown[]>(
|
|
58
|
+
func: (...args: A) => void,
|
|
59
59
|
wait: number
|
|
60
|
-
): ((...args:
|
|
60
|
+
): ((...args: A) => void) => {
|
|
61
61
|
let timeout: NodeJS.Timeout | null = null
|
|
62
|
-
|
|
63
|
-
return (...args:
|
|
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 = <
|
|
75
|
-
func:
|
|
74
|
+
export const throttle = <A extends unknown[]>(
|
|
75
|
+
func: (...args: A) => void,
|
|
76
76
|
limit: number
|
|
77
|
-
): ((...args:
|
|
77
|
+
): ((...args: A) => void) => {
|
|
78
78
|
let inThrottle: boolean = false
|
|
79
|
-
|
|
80
|
-
return (...args:
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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 =
|
|
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:
|
|
178
|
+
export const safeJsonStringify = (obj: unknown, fallback: string = '{}'): string => {
|
|
176
179
|
try {
|
|
177
180
|
return JSON.stringify(obj)
|
|
178
181
|
} catch {
|