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