create-fluxstack 1.0.13 → 1.0.15
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/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +196 -33
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// 🔥 FluxStack Static Files Plugin - Serve Public Files
|
|
2
|
+
|
|
3
|
+
import { existsSync, statSync } from 'fs'
|
|
4
|
+
import { join, extname, resolve } from 'path'
|
|
5
|
+
import type { Plugin, PluginContext } from '../plugins/types'
|
|
6
|
+
|
|
7
|
+
export interface StaticFilesConfig {
|
|
8
|
+
publicDir?: string // Default: 'public'
|
|
9
|
+
uploadsDir?: string // Default: 'uploads'
|
|
10
|
+
cacheMaxAge?: number // Default: 1 year in seconds
|
|
11
|
+
enableUploads?: boolean // Default: true
|
|
12
|
+
enablePublic?: boolean // Default: true
|
|
13
|
+
publicRoute?: string // Default: '/public' (can be '/static' in dev)
|
|
14
|
+
uploadsRoute?: string // Default: '/uploads'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const staticFilesPlugin: Plugin = {
|
|
18
|
+
name: 'static-files',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
description: 'Serve static files and uploads with proper caching and security',
|
|
21
|
+
author: 'FluxStack Team',
|
|
22
|
+
priority: 'normal',
|
|
23
|
+
category: 'core',
|
|
24
|
+
tags: ['static', 'files', 'uploads', 'public'],
|
|
25
|
+
|
|
26
|
+
setup: async (context: PluginContext) => {
|
|
27
|
+
context.logger.info('📁 Setting up Static Files plugin...')
|
|
28
|
+
|
|
29
|
+
const config: StaticFilesConfig = {
|
|
30
|
+
publicDir: 'public',
|
|
31
|
+
uploadsDir: 'uploads',
|
|
32
|
+
cacheMaxAge: 31536000, // 1 year
|
|
33
|
+
enableUploads: true,
|
|
34
|
+
enablePublic: true,
|
|
35
|
+
publicRoute: '/api/static', // Use /api/static in dev to avoid Vite conflicts
|
|
36
|
+
uploadsRoute: '/api/uploads',
|
|
37
|
+
...context.config.staticFiles
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const projectRoot = process.cwd()
|
|
41
|
+
const publicPath = resolve(projectRoot, config.publicDir!)
|
|
42
|
+
const uploadsPath = resolve(projectRoot, config.uploadsDir!)
|
|
43
|
+
|
|
44
|
+
// MIME types mapping
|
|
45
|
+
const getMimeType = (extension: string): string => {
|
|
46
|
+
const mimeTypes: Record<string, string> = {
|
|
47
|
+
// Images
|
|
48
|
+
'.jpg': 'image/jpeg',
|
|
49
|
+
'.jpeg': 'image/jpeg',
|
|
50
|
+
'.png': 'image/png',
|
|
51
|
+
'.gif': 'image/gif',
|
|
52
|
+
'.webp': 'image/webp',
|
|
53
|
+
'.svg': 'image/svg+xml',
|
|
54
|
+
'.ico': 'image/x-icon',
|
|
55
|
+
|
|
56
|
+
// Documents
|
|
57
|
+
'.pdf': 'application/pdf',
|
|
58
|
+
'.txt': 'text/plain',
|
|
59
|
+
'.json': 'application/json',
|
|
60
|
+
'.xml': 'application/xml',
|
|
61
|
+
|
|
62
|
+
// Web assets
|
|
63
|
+
'.css': 'text/css',
|
|
64
|
+
'.js': 'application/javascript',
|
|
65
|
+
'.html': 'text/html',
|
|
66
|
+
'.htm': 'text/html',
|
|
67
|
+
|
|
68
|
+
// Fonts
|
|
69
|
+
'.woff': 'font/woff',
|
|
70
|
+
'.woff2': 'font/woff2',
|
|
71
|
+
'.ttf': 'font/ttf',
|
|
72
|
+
'.otf': 'font/otf',
|
|
73
|
+
|
|
74
|
+
// Audio/Video
|
|
75
|
+
'.mp3': 'audio/mpeg',
|
|
76
|
+
'.mp4': 'video/mp4',
|
|
77
|
+
'.webm': 'video/webm',
|
|
78
|
+
'.ogg': 'audio/ogg'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return mimeTypes[extension.toLowerCase()] || 'application/octet-stream'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Security check for path traversal
|
|
85
|
+
const isPathSafe = (filePath: string, basePath: string): boolean => {
|
|
86
|
+
const resolvedPath = resolve(basePath, filePath)
|
|
87
|
+
return resolvedPath.startsWith(basePath)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Generic file serving function
|
|
91
|
+
const serveFile = async (filePath: string, set: any) => {
|
|
92
|
+
try {
|
|
93
|
+
if (!existsSync(filePath)) {
|
|
94
|
+
set.status = 404
|
|
95
|
+
return {
|
|
96
|
+
error: 'File not found',
|
|
97
|
+
path: filePath.replace(projectRoot, ''),
|
|
98
|
+
timestamp: new Date().toISOString()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stats = statSync(filePath)
|
|
103
|
+
if (!stats.isFile()) {
|
|
104
|
+
set.status = 404
|
|
105
|
+
return { error: 'Not a file' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Set appropriate headers
|
|
109
|
+
const extension = extname(filePath).toLowerCase()
|
|
110
|
+
const mimeType = getMimeType(extension)
|
|
111
|
+
|
|
112
|
+
set.headers['content-type'] = mimeType
|
|
113
|
+
set.headers['content-length'] = stats.size.toString()
|
|
114
|
+
set.headers['last-modified'] = stats.mtime.toUTCString()
|
|
115
|
+
set.headers['cache-control'] = `public, max-age=${config.cacheMaxAge}`
|
|
116
|
+
set.headers['etag'] = `"${stats.mtime.getTime()}-${stats.size}"`
|
|
117
|
+
|
|
118
|
+
// Security headers for images
|
|
119
|
+
if (mimeType.startsWith('image/')) {
|
|
120
|
+
set.headers['x-content-type-options'] = 'nosniff'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
context.logger.debug(`📁 Serving file: ${filePath.replace(projectRoot, '')}`, {
|
|
124
|
+
size: stats.size,
|
|
125
|
+
mimeType,
|
|
126
|
+
lastModified: stats.mtime
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return Bun.file(filePath)
|
|
130
|
+
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
context.logger.error('❌ File serving error:', error.message)
|
|
133
|
+
set.status = 500
|
|
134
|
+
return { error: 'Failed to serve file' }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add static file routes
|
|
139
|
+
if (config.enablePublic) {
|
|
140
|
+
const publicRoutePattern = `${config.publicRoute}/*`
|
|
141
|
+
context.app.get(publicRoutePattern, ({ params, set }) => {
|
|
142
|
+
const filePath = params['*'] || ''
|
|
143
|
+
|
|
144
|
+
if (!isPathSafe(filePath, publicPath)) {
|
|
145
|
+
set.status = 400
|
|
146
|
+
return { error: 'Invalid file path' }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fullPath = join(publicPath, filePath)
|
|
150
|
+
return serveFile(fullPath, set)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
context.logger.info(`📁 Public files route enabled: ${publicRoutePattern} → ${config.publicDir}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (config.enableUploads) {
|
|
157
|
+
const uploadsRoutePattern = `${config.uploadsRoute}/*`
|
|
158
|
+
context.app.get(uploadsRoutePattern, ({ params, set }) => {
|
|
159
|
+
const filePath = params['*'] || ''
|
|
160
|
+
|
|
161
|
+
if (!isPathSafe(filePath, uploadsPath)) {
|
|
162
|
+
set.status = 400
|
|
163
|
+
return { error: 'Invalid file path' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fullPath = join(uploadsPath, filePath)
|
|
167
|
+
return serveFile(fullPath, set)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
context.logger.info(`📁 Uploads route enabled: ${uploadsRoutePattern} → ${config.uploadsDir}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Static files info endpoint
|
|
174
|
+
context.app.get('/api/static/info', () => {
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
config: {
|
|
178
|
+
publicDir: config.publicDir,
|
|
179
|
+
uploadsDir: config.uploadsDir,
|
|
180
|
+
enablePublic: config.enablePublic,
|
|
181
|
+
enableUploads: config.enableUploads,
|
|
182
|
+
cacheMaxAge: config.cacheMaxAge
|
|
183
|
+
},
|
|
184
|
+
paths: {
|
|
185
|
+
publicPath,
|
|
186
|
+
uploadsPath,
|
|
187
|
+
publicUrl: config.publicRoute,
|
|
188
|
+
uploadsUrl: config.uploadsRoute
|
|
189
|
+
},
|
|
190
|
+
timestamp: new Date().toISOString()
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Create directories if they don't exist
|
|
195
|
+
const { mkdir } = await import('fs/promises')
|
|
196
|
+
|
|
197
|
+
if (config.enablePublic && !existsSync(publicPath)) {
|
|
198
|
+
await mkdir(publicPath, { recursive: true })
|
|
199
|
+
context.logger.info(`📁 Created public directory: ${publicPath}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (config.enableUploads && !existsSync(uploadsPath)) {
|
|
203
|
+
await mkdir(uploadsPath, { recursive: true })
|
|
204
|
+
await mkdir(join(uploadsPath, 'avatars'), { recursive: true })
|
|
205
|
+
context.logger.info(`📁 Created uploads directory: ${uploadsPath}`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
context.logger.info('📁 Static Files plugin setup complete', {
|
|
209
|
+
publicEnabled: config.enablePublic,
|
|
210
|
+
uploadsEnabled: config.enableUploads,
|
|
211
|
+
publicPath: config.enablePublic ? publicPath : 'disabled',
|
|
212
|
+
uploadsPath: config.enableUploads ? uploadsPath : 'disabled'
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
onServerStart: async (context: PluginContext) => {
|
|
217
|
+
const config = {
|
|
218
|
+
enablePublic: true,
|
|
219
|
+
enableUploads: true,
|
|
220
|
+
publicRoute: '/api/static',
|
|
221
|
+
uploadsRoute: '/api/uploads',
|
|
222
|
+
...context.config.staticFiles
|
|
223
|
+
}
|
|
224
|
+
context.logger.info('📁 Static Files plugin ready', {
|
|
225
|
+
routes: [
|
|
226
|
+
config.enablePublic ? `${config.publicRoute}/*` : null,
|
|
227
|
+
config.enableUploads ? `${config.uploadsRoute}/*` : null,
|
|
228
|
+
'/api/static/info'
|
|
229
|
+
].filter(Boolean)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Service Class
|
|
3
|
+
* Core FluxStack service infrastructure
|
|
4
|
+
*
|
|
5
|
+
* Provides common functionality for all services including:
|
|
6
|
+
* - Logging with service context
|
|
7
|
+
* - Configuration access
|
|
8
|
+
* - Service container integration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Logger } from '../../utils/logger/index.js'
|
|
12
|
+
|
|
13
|
+
export interface ServiceContext {
|
|
14
|
+
config: any
|
|
15
|
+
logger: Logger
|
|
16
|
+
services?: ServiceContainer
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ServiceContainer {
|
|
20
|
+
get<T>(name: string): T
|
|
21
|
+
register<T>(name: string, service: T): void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export abstract class BaseService {
|
|
25
|
+
protected config: any
|
|
26
|
+
protected logger: Logger
|
|
27
|
+
protected services?: ServiceContainer
|
|
28
|
+
|
|
29
|
+
constructor(context: ServiceContext) {
|
|
30
|
+
this.config = context.config
|
|
31
|
+
this.logger = context.logger.child({ service: this.constructor.name })
|
|
32
|
+
this.services = context.services
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get service from container
|
|
37
|
+
*/
|
|
38
|
+
protected getService<T>(name: string): T {
|
|
39
|
+
if (!this.services) {
|
|
40
|
+
throw new Error('Service container not available')
|
|
41
|
+
}
|
|
42
|
+
return this.services.get<T>(name)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Log service operation
|
|
47
|
+
*/
|
|
48
|
+
protected logOperation(operation: string, data?: any) {
|
|
49
|
+
this.logger.info(`${operation}`, data)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Log service error
|
|
54
|
+
*/
|
|
55
|
+
protected logError(operation: string, error: Error, data?: any) {
|
|
56
|
+
this.logger.error(`${operation} failed`, { error: error.message, data })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize service (override in subclasses)
|
|
61
|
+
*/
|
|
62
|
+
async initialize(): Promise<void> {
|
|
63
|
+
this.logger.info('Service initialized')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute operation with logging
|
|
68
|
+
*/
|
|
69
|
+
protected async executeWithLogging<T>(
|
|
70
|
+
operation: string,
|
|
71
|
+
fn: () => Promise<T> | T,
|
|
72
|
+
metadata?: any
|
|
73
|
+
): Promise<T> {
|
|
74
|
+
this.logOperation(`Starting ${operation}`, metadata)
|
|
75
|
+
try {
|
|
76
|
+
const result = await fn()
|
|
77
|
+
this.logOperation(`Completed ${operation}`, metadata)
|
|
78
|
+
return result
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.logError(operation, error as Error, metadata)
|
|
81
|
+
throw error
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate required fields
|
|
87
|
+
*/
|
|
88
|
+
protected validateRequired(data: any, fields: string[]): void {
|
|
89
|
+
for (const field of fields) {
|
|
90
|
+
if (!data[field]) {
|
|
91
|
+
throw new Error(`Missing required field: ${field}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Container
|
|
3
|
+
* Core FluxStack dependency injection container
|
|
4
|
+
*
|
|
5
|
+
* Provides service registration, resolution, and lifecycle management
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Logger } from '../../utils/logger/index.js'
|
|
9
|
+
|
|
10
|
+
export interface ServiceDefinition {
|
|
11
|
+
factory: (container: ServiceContainer) => any
|
|
12
|
+
singleton?: boolean
|
|
13
|
+
dependencies?: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ServiceContainer {
|
|
17
|
+
private services = new Map<string, any>()
|
|
18
|
+
private definitions = new Map<string, ServiceDefinition>()
|
|
19
|
+
private singletons = new Map<string, any>()
|
|
20
|
+
private logger: Logger
|
|
21
|
+
|
|
22
|
+
constructor(logger: Logger) {
|
|
23
|
+
this.logger = logger.child({ component: 'ServiceContainer' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a service definition
|
|
28
|
+
*/
|
|
29
|
+
register<T>(name: string, definition: ServiceDefinition | T): void {
|
|
30
|
+
if (typeof definition === 'object' && definition !== null && 'factory' in definition) {
|
|
31
|
+
this.definitions.set(name, definition as ServiceDefinition)
|
|
32
|
+
this.logger.debug(`Registered service definition: ${name}`)
|
|
33
|
+
} else {
|
|
34
|
+
this.services.set(name, definition)
|
|
35
|
+
this.logger.debug(`Registered service instance: ${name}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get a service instance
|
|
41
|
+
*/
|
|
42
|
+
get<T>(name: string): T {
|
|
43
|
+
// Check for direct service instance
|
|
44
|
+
if (this.services.has(name)) {
|
|
45
|
+
return this.services.get(name) as T
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for singleton instance
|
|
49
|
+
if (this.singletons.has(name)) {
|
|
50
|
+
return this.singletons.get(name) as T
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check for service definition
|
|
54
|
+
const definition = this.definitions.get(name)
|
|
55
|
+
if (!definition) {
|
|
56
|
+
throw new Error(`Service not found: ${name}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Resolve dependencies first
|
|
60
|
+
if (definition.dependencies) {
|
|
61
|
+
for (const dep of definition.dependencies) {
|
|
62
|
+
if (!this.has(dep)) {
|
|
63
|
+
throw new Error(`Dependency not found: ${dep} (required by ${name})`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create service instance
|
|
69
|
+
const instance = definition.factory(this)
|
|
70
|
+
|
|
71
|
+
// Store singleton if needed
|
|
72
|
+
if (definition.singleton) {
|
|
73
|
+
this.singletons.set(name, instance)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.logger.debug(`Created service instance: ${name}`)
|
|
77
|
+
return instance as T
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if service exists
|
|
82
|
+
*/
|
|
83
|
+
has(name: string): boolean {
|
|
84
|
+
return this.services.has(name) ||
|
|
85
|
+
this.definitions.has(name) ||
|
|
86
|
+
this.singletons.has(name)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove a service
|
|
91
|
+
*/
|
|
92
|
+
remove(name: string): void {
|
|
93
|
+
this.services.delete(name)
|
|
94
|
+
this.definitions.delete(name)
|
|
95
|
+
this.singletons.delete(name)
|
|
96
|
+
this.logger.debug(`Removed service: ${name}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all registered service names
|
|
101
|
+
*/
|
|
102
|
+
getServiceNames(): string[] {
|
|
103
|
+
const names = new Set<string>()
|
|
104
|
+
|
|
105
|
+
// Use Array.from to avoid iterator issues
|
|
106
|
+
Array.from(this.services.keys()).forEach(name => names.add(name))
|
|
107
|
+
Array.from(this.definitions.keys()).forEach(name => names.add(name))
|
|
108
|
+
Array.from(this.singletons.keys()).forEach(name => names.add(name))
|
|
109
|
+
|
|
110
|
+
return Array.from(names)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Register multiple services at once
|
|
115
|
+
*/
|
|
116
|
+
registerMany(services: Array<{
|
|
117
|
+
name: string
|
|
118
|
+
constructor: any
|
|
119
|
+
dependencies?: string[]
|
|
120
|
+
singleton?: boolean
|
|
121
|
+
}>): void {
|
|
122
|
+
for (const service of services) {
|
|
123
|
+
this.register(service.name, {
|
|
124
|
+
factory: (container) => new service.constructor({
|
|
125
|
+
config: {},
|
|
126
|
+
logger: this.logger,
|
|
127
|
+
services: container
|
|
128
|
+
}),
|
|
129
|
+
dependencies: service.dependencies,
|
|
130
|
+
singleton: service.singleton
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clear all services
|
|
137
|
+
*/
|
|
138
|
+
clear(): void {
|
|
139
|
+
this.services.clear()
|
|
140
|
+
this.definitions.clear()
|
|
141
|
+
this.singletons.clear()
|
|
142
|
+
this.logger.debug('Cleared all services')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Server Services
|
|
3
|
+
* FluxStack service infrastructure exports
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { BaseService } from './BaseService.js'
|
|
7
|
+
export { ServiceContainer } from './ServiceContainer.js'
|
|
8
|
+
export type { ServiceContext, ServiceContainer as IServiceContainer } from './BaseService.js'
|
|
9
|
+
export type { ServiceDefinition } from './ServiceContainer.js'
|