create-fluxstack 1.1.0 → 1.4.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 (62) hide show
  1. package/app/server/backend-only.ts +5 -5
  2. package/app/server/index.ts +63 -54
  3. package/app/server/live/FluxStackConfig.ts +43 -39
  4. package/app/server/live/SystemMonitorIntegration.ts +2 -2
  5. package/app/server/live/register-components.ts +1 -1
  6. package/app/server/middleware/errorHandling.ts +6 -4
  7. package/app/server/routes/config.ts +145 -0
  8. package/app/server/routes/index.ts +5 -3
  9. package/config/app.config.ts +113 -0
  10. package/config/build.config.ts +24 -0
  11. package/config/database.config.ts +99 -0
  12. package/config/index.ts +68 -0
  13. package/config/logger.config.ts +27 -0
  14. package/config/runtime.config.ts +92 -0
  15. package/config/server.config.ts +46 -0
  16. package/config/services.config.ts +130 -0
  17. package/config/system.config.ts +105 -0
  18. package/core/build/index.ts +10 -4
  19. package/core/cli/index.ts +29 -12
  20. package/core/config/env.ts +37 -95
  21. package/core/config/runtime-config.ts +61 -58
  22. package/core/config/schema.ts +4 -0
  23. package/core/framework/server.ts +22 -10
  24. package/core/plugins/built-in/index.ts +7 -17
  25. package/core/plugins/built-in/swagger/index.ts +228 -228
  26. package/core/plugins/built-in/vite/index.ts +374 -358
  27. package/core/plugins/dependency-manager.ts +5 -5
  28. package/core/plugins/manager.ts +12 -12
  29. package/core/plugins/registry.ts +3 -3
  30. package/core/server/index.ts +0 -1
  31. package/core/server/live/ComponentRegistry.ts +34 -8
  32. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  33. package/core/server/live/websocket-plugin.ts +434 -434
  34. package/core/server/middleware/README.md +488 -0
  35. package/core/server/middleware/elysia-helpers.ts +227 -0
  36. package/core/server/middleware/index.ts +25 -9
  37. package/core/server/plugins/static-files-plugin.ts +231 -231
  38. package/core/utils/config-schema.ts +484 -0
  39. package/core/utils/env.ts +306 -0
  40. package/core/utils/helpers.ts +4 -4
  41. package/core/utils/logger/colors.ts +114 -0
  42. package/core/utils/logger/config.ts +35 -0
  43. package/core/utils/logger/formatter.ts +82 -0
  44. package/core/utils/logger/group-logger.ts +101 -0
  45. package/core/utils/logger/index.ts +199 -250
  46. package/core/utils/logger/stack-trace.ts +92 -0
  47. package/core/utils/logger/startup-banner.ts +92 -0
  48. package/core/utils/logger/winston-logger.ts +152 -0
  49. package/core/utils/version.ts +5 -0
  50. package/create-fluxstack.ts +1 -0
  51. package/fluxstack.config.ts +2 -2
  52. package/package.json +117 -115
  53. package/core/config/env-dynamic.ts +0 -326
  54. package/core/plugins/built-in/logger/index.ts +0 -180
  55. package/core/server/plugins/logger.ts +0 -47
  56. package/core/utils/env-runtime-v2.ts +0 -232
  57. package/core/utils/env-runtime.ts +0 -259
  58. package/core/utils/logger/formatters.ts +0 -222
  59. package/core/utils/logger/middleware.ts +0 -253
  60. package/core/utils/logger/performance.ts +0 -384
  61. package/core/utils/logger/transports.ts +0 -365
  62. package/core/utils/logger.ts +0 -106
@@ -0,0 +1,484 @@
1
+ /**
2
+ * ⚡ FluxStack Config Schema System
3
+ *
4
+ * Laravel-inspired declarative configuration system with:
5
+ * - Schema-based config declaration
6
+ * - Automatic validation
7
+ * - Type casting
8
+ * - Default values
9
+ * - Environment variable mapping
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const appConfig = defineConfig({
14
+ * name: {
15
+ * type: 'string',
16
+ * env: 'APP_NAME',
17
+ * default: 'MyApp',
18
+ * required: true
19
+ * },
20
+ * port: {
21
+ * type: 'number',
22
+ * env: 'PORT',
23
+ * default: 3000,
24
+ * validate: (value) => value > 0 && value < 65536
25
+ * },
26
+ * debug: {
27
+ * type: 'boolean',
28
+ * env: 'DEBUG',
29
+ * default: false
30
+ * }
31
+ * })
32
+ *
33
+ * // Access with full type safety
34
+ * appConfig.name // string
35
+ * appConfig.port // number
36
+ * appConfig.debug // boolean
37
+ * ```
38
+ */
39
+
40
+ import { env } from './env'
41
+
42
+ /**
43
+ * Config field types
44
+ */
45
+ export type ConfigFieldType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'enum'
46
+
47
+ /**
48
+ * Config field definition
49
+ */
50
+ export interface ConfigField<T = any> {
51
+ /** Field type */
52
+ type: ConfigFieldType
53
+
54
+ /** Environment variable name */
55
+ env?: string
56
+
57
+ /** Default value */
58
+ default?: T
59
+
60
+ /** Is field required? */
61
+ required?: boolean
62
+
63
+ /** Custom validation function */
64
+ validate?: (value: T) => boolean | string
65
+
66
+ /** For enum type: allowed values */
67
+ values?: readonly T[]
68
+
69
+ /** Field description (for documentation) */
70
+ description?: string
71
+
72
+ /** Custom transformer function */
73
+ transform?: (value: any) => T
74
+ }
75
+
76
+ /**
77
+ * Config schema definition
78
+ */
79
+ export type ConfigSchema = Record<string, ConfigField>
80
+
81
+ /**
82
+ * Infer TypeScript type from config schema
83
+ */
84
+ export type InferConfig<T extends ConfigSchema> = {
85
+ [K in keyof T]: T[K]['default'] extends infer D
86
+ ? D extends undefined
87
+ ? T[K]['required'] extends true
88
+ ? InferFieldType<T[K]>
89
+ : InferFieldType<T[K]> | undefined
90
+ : InferFieldType<T[K]>
91
+ : InferFieldType<T[K]>
92
+ }
93
+
94
+ /**
95
+ * Infer field type from field definition
96
+ * Uses the generic T from ConfigField<T> for better type inference
97
+ */
98
+ type InferFieldType<F> =
99
+ F extends ConfigField<infer T>
100
+ ? T extends undefined
101
+ ? (
102
+ F extends { type: 'string' } ? string :
103
+ F extends { type: 'number' } ? number :
104
+ F extends { type: 'boolean' } ? boolean :
105
+ F extends { type: 'array' } ? string[] :
106
+ F extends { type: 'object' } ? Record<string, any> :
107
+ F extends { type: 'enum'; values: readonly (infer U)[] } ? U :
108
+ any
109
+ )
110
+ : T
111
+ : any
112
+
113
+ /**
114
+ * Validation error
115
+ */
116
+ export interface ValidationError {
117
+ field: string
118
+ message: string
119
+ value?: any
120
+ }
121
+
122
+ /**
123
+ * Config validation result
124
+ */
125
+ export interface ValidationResult {
126
+ valid: boolean
127
+ errors: ValidationError[]
128
+ warnings?: string[]
129
+ }
130
+
131
+ /**
132
+ * Cast value to specific type
133
+ */
134
+ function castValue(value: any, type: ConfigFieldType): any {
135
+ if (value === undefined || value === null) {
136
+ return undefined
137
+ }
138
+
139
+ switch (type) {
140
+ case 'string':
141
+ return String(value)
142
+
143
+ case 'number':
144
+ const num = Number(value)
145
+ return isNaN(num) ? undefined : num
146
+
147
+ case 'boolean':
148
+ if (typeof value === 'boolean') return value
149
+ if (typeof value === 'string') {
150
+ return ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
151
+ }
152
+ return Boolean(value)
153
+
154
+ case 'array':
155
+ if (Array.isArray(value)) return value
156
+ if (typeof value === 'string') {
157
+ return value.split(',').map(v => v.trim()).filter(Boolean)
158
+ }
159
+ return [value]
160
+
161
+ case 'object':
162
+ if (typeof value === 'object' && value !== null) return value
163
+ if (typeof value === 'string') {
164
+ try {
165
+ return JSON.parse(value)
166
+ } catch {
167
+ return {}
168
+ }
169
+ }
170
+ return {}
171
+
172
+ case 'enum':
173
+ return value
174
+
175
+ default:
176
+ return value
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Validate config value
182
+ */
183
+ function validateField(
184
+ fieldName: string,
185
+ value: any,
186
+ field: ConfigField
187
+ ): ValidationError | null {
188
+ // Check required
189
+ if (field.required && (value === undefined || value === null || value === '')) {
190
+ return {
191
+ field: fieldName,
192
+ message: `Field '${fieldName}' is required but not provided`
193
+ }
194
+ }
195
+
196
+ // Skip validation if value is undefined and not required
197
+ if (value === undefined && !field.required) {
198
+ return null
199
+ }
200
+
201
+ // Check enum values
202
+ if (field.type === 'enum' && field.values) {
203
+ if (!field.values.includes(value)) {
204
+ return {
205
+ field: fieldName,
206
+ message: `Field '${fieldName}' must be one of: ${field.values.join(', ')}`,
207
+ value
208
+ }
209
+ }
210
+ }
211
+
212
+ // Custom validation
213
+ if (field.validate) {
214
+ const result = field.validate(value)
215
+ if (result === false) {
216
+ return {
217
+ field: fieldName,
218
+ message: `Field '${fieldName}' failed validation`,
219
+ value
220
+ }
221
+ }
222
+ if (typeof result === 'string') {
223
+ return {
224
+ field: fieldName,
225
+ message: result,
226
+ value
227
+ }
228
+ }
229
+ }
230
+
231
+ return null
232
+ }
233
+
234
+ /**
235
+ * Reactive config instance that can reload in runtime
236
+ */
237
+ export class ReactiveConfig<T extends ConfigSchema> {
238
+ private schema: T
239
+ private config: InferConfig<T>
240
+ private watchers: Array<(config: InferConfig<T>) => void> = []
241
+
242
+ constructor(schema: T) {
243
+ this.schema = schema
244
+ this.config = this.loadConfig()
245
+ }
246
+
247
+ /**
248
+ * Load config from environment
249
+ */
250
+ private loadConfig(): InferConfig<T> {
251
+ const config: any = {}
252
+ const errors: ValidationError[] = []
253
+
254
+ for (const [fieldName, field] of Object.entries(this.schema)) {
255
+ let value: any
256
+
257
+ // 1. Try to get from environment variable
258
+ if (field.env) {
259
+ const envValue = env.has(field.env) ? env.all()[field.env] : undefined
260
+ if (envValue !== undefined && envValue !== '') {
261
+ value = envValue
262
+ }
263
+ }
264
+
265
+ // 2. Use default value if not found in env
266
+ if (value === undefined) {
267
+ value = field.default
268
+ }
269
+
270
+ // 3. Apply custom transform if provided
271
+ if (value !== undefined && field.transform) {
272
+ try {
273
+ value = field.transform(value)
274
+ } catch (error) {
275
+ errors.push({
276
+ field: fieldName,
277
+ message: `Transform failed: ${error}`
278
+ })
279
+ continue
280
+ }
281
+ }
282
+
283
+ // 4. Cast to correct type
284
+ if (value !== undefined) {
285
+ value = castValue(value, field.type)
286
+ }
287
+
288
+ // 5. Validate
289
+ const validationError = validateField(fieldName, value, field)
290
+ if (validationError) {
291
+ errors.push(validationError)
292
+ continue
293
+ }
294
+
295
+ // 6. Set value
296
+ config[fieldName] = value
297
+ }
298
+
299
+ // Throw error if validation failed
300
+ if (errors.length > 0) {
301
+ const errorMessage = errors
302
+ .map(e => ` - ${e.message}${e.value !== undefined ? ` (got: ${JSON.stringify(e.value)})` : ''}`)
303
+ .join('\n')
304
+
305
+ throw new Error(
306
+ `❌ Configuration validation failed:\n${errorMessage}\n\n` +
307
+ `Please check your environment variables or configuration.`
308
+ )
309
+ }
310
+
311
+ return config as InferConfig<T>
312
+ }
313
+
314
+ /**
315
+ * Get current config values
316
+ */
317
+ get values(): InferConfig<T> {
318
+ return this.config
319
+ }
320
+
321
+ /**
322
+ * Reload config from environment (runtime reload)
323
+ */
324
+ reload(): InferConfig<T> {
325
+ // Clear env cache to get fresh values
326
+ env.clearCache()
327
+
328
+ // Reload config
329
+ const newConfig = this.loadConfig()
330
+ this.config = newConfig
331
+
332
+ // Notify watchers
333
+ this.watchers.forEach(watcher => watcher(newConfig))
334
+
335
+ return newConfig
336
+ }
337
+
338
+ /**
339
+ * Watch for config changes (called after reload)
340
+ */
341
+ watch(callback: (config: InferConfig<T>) => void): () => void {
342
+ this.watchers.push(callback)
343
+
344
+ // Return unwatch function
345
+ return () => {
346
+ const index = this.watchers.indexOf(callback)
347
+ if (index > -1) {
348
+ this.watchers.splice(index, 1)
349
+ }
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Get specific field value with runtime lookup
355
+ */
356
+ get<K extends keyof InferConfig<T>>(key: K): InferConfig<T>[K] {
357
+ return this.config[key]
358
+ }
359
+
360
+ /**
361
+ * Check if field exists
362
+ */
363
+ has<K extends keyof InferConfig<T>>(key: K): boolean {
364
+ return this.config[key] !== undefined
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Define and load configuration from schema
370
+ */
371
+ export function defineConfig<T extends ConfigSchema>(schema: T): InferConfig<T> {
372
+ const reactive = new ReactiveConfig(schema)
373
+ return reactive.values as InferConfig<T>
374
+ }
375
+
376
+ /**
377
+ * Define reactive configuration (can be reloaded in runtime)
378
+ */
379
+ export function defineReactiveConfig<T extends ConfigSchema>(schema: T): ReactiveConfig<T> {
380
+ return new ReactiveConfig(schema)
381
+ }
382
+
383
+ /**
384
+ * Validate configuration without throwing
385
+ */
386
+ export function validateConfig<T extends ConfigSchema>(
387
+ schema: T,
388
+ values: Partial<InferConfig<T>>
389
+ ): ValidationResult {
390
+ const errors: ValidationError[] = []
391
+
392
+ for (const [fieldName, field] of Object.entries(schema)) {
393
+ const value = (values as any)[fieldName]
394
+ const error = validateField(fieldName, value, field)
395
+ if (error) {
396
+ errors.push(error)
397
+ }
398
+ }
399
+
400
+ return {
401
+ valid: errors.length === 0,
402
+ errors
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Create nested config schema (for grouping)
408
+ */
409
+ export function defineNestedConfig<T extends Record<string, ConfigSchema>>(
410
+ schemas: T
411
+ ): { [K in keyof T]: InferConfig<T[K]> } {
412
+ const config: any = {}
413
+
414
+ for (const [groupName, schema] of Object.entries(schemas)) {
415
+ config[groupName] = defineConfig(schema)
416
+ }
417
+
418
+ return config
419
+ }
420
+
421
+ /**
422
+ * Helper to create env field quickly
423
+ */
424
+ export function envString(envVar: string, defaultValue?: string, required = false): ConfigField<string> {
425
+ return {
426
+ type: 'string' as const,
427
+ env: envVar,
428
+ default: defaultValue,
429
+ required
430
+ }
431
+ }
432
+
433
+ export function envNumber(envVar: string, defaultValue?: number, required = false): ConfigField<number> {
434
+ return {
435
+ type: 'number' as const,
436
+ env: envVar,
437
+ default: defaultValue,
438
+ required
439
+ }
440
+ }
441
+
442
+ export function envBoolean(envVar: string, defaultValue?: boolean, required = false): ConfigField<boolean> {
443
+ return {
444
+ type: 'boolean' as const,
445
+ env: envVar,
446
+ default: defaultValue,
447
+ required
448
+ }
449
+ }
450
+
451
+ export function envArray(envVar: string, defaultValue?: string[], required = false): ConfigField<string[]> {
452
+ return {
453
+ type: 'array' as const,
454
+ env: envVar,
455
+ default: defaultValue,
456
+ required
457
+ }
458
+ }
459
+
460
+ export function envEnum<T extends readonly string[]>(
461
+ envVar: string,
462
+ values: T,
463
+ defaultValue?: T[number],
464
+ required = false
465
+ ): ConfigField<T[number]> {
466
+ return {
467
+ type: 'enum' as const,
468
+ env: envVar,
469
+ values,
470
+ default: defaultValue,
471
+ required
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Export shorthand helpers
477
+ */
478
+ export const config = {
479
+ string: envString,
480
+ number: envNumber,
481
+ boolean: envBoolean,
482
+ array: envArray,
483
+ enum: envEnum
484
+ }