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
package/core/framework/server.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { Elysia } from "elysia"
|
|
2
2
|
import type { FluxStackConfig, FluxStackContext } from "@core/types"
|
|
3
|
-
import type { FluxStack, PluginContext, PluginUtils } from "@core/plugins/types"
|
|
3
|
+
import type { FluxStack, PluginContext, PluginUtils, PluginConfigSchema, PluginHook } from "@core/plugins/types"
|
|
4
4
|
import { PluginRegistry } from "@core/plugins/registry"
|
|
5
5
|
import { PluginManager } from "@core/plugins/manager"
|
|
6
6
|
import { fluxStackConfig } from "@config"
|
|
7
7
|
import { getEnvironmentInfo } from "@core/config"
|
|
8
|
-
import { logger } from "@core/utils/logger"
|
|
8
|
+
import { logger, type Logger } from "@core/utils/logger"
|
|
9
9
|
import { displayStartupBanner, type StartupInfo } from "@core/utils/logger/startup-banner"
|
|
10
10
|
import { componentRegistry } from "@core/server/live"
|
|
11
11
|
import { FluxStackError } from "@core/utils/errors"
|
|
12
12
|
import { createTimer, formatBytes, isProduction, isDevelopment } from "@core/utils/helpers"
|
|
13
|
+
import { createHash } from "crypto"
|
|
14
|
+
import { createPluginUtils } from "@core/plugins/config"
|
|
13
15
|
import type { Plugin } from "@core/plugins"
|
|
14
16
|
|
|
15
17
|
export class FluxStackFramework {
|
|
@@ -20,6 +22,12 @@ export class FluxStackFramework {
|
|
|
20
22
|
private pluginContext: PluginContext
|
|
21
23
|
private isStarted: boolean = false
|
|
22
24
|
private requestTimings: Map<string, number> = new Map()
|
|
25
|
+
private _originalStderrWrite?: typeof process.stderr.write
|
|
26
|
+
|
|
27
|
+
/** Access typed config from context (config is stored as unknown to avoid circular deps) */
|
|
28
|
+
private get cfg(): import('@config').FluxStackConfig {
|
|
29
|
+
return this.context.config as import('@config').FluxStackConfig
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
/**
|
|
25
33
|
* Helper to safely parse request.url which might be relative or absolute
|
|
@@ -59,7 +67,8 @@ export class FluxStackFramework {
|
|
|
59
67
|
|
|
60
68
|
// Fallback: try to get from Bun's server socket (if available)
|
|
61
69
|
// This is set by Bun when running in server mode
|
|
62
|
-
const
|
|
70
|
+
const requestWithSocket = request as Request & { ip?: string; remoteAddress?: string }
|
|
71
|
+
const socketIP = requestWithSocket.ip || requestWithSocket.remoteAddress
|
|
63
72
|
if (socketIP) {
|
|
64
73
|
return socketIP
|
|
65
74
|
}
|
|
@@ -96,58 +105,42 @@ export class FluxStackFramework {
|
|
|
96
105
|
isDevelopment,
|
|
97
106
|
getEnvironment: () => envInfo.name,
|
|
98
107
|
createHash: (data: string) => {
|
|
99
|
-
|
|
100
|
-
return crypto.createHash('sha256').update(data).digest('hex')
|
|
108
|
+
return createHash('sha256').update(data).digest('hex')
|
|
101
109
|
},
|
|
102
|
-
deepMerge: (target:
|
|
110
|
+
deepMerge: (target: Record<string, unknown>, source: Record<string, unknown>) => {
|
|
103
111
|
const result = { ...target }
|
|
104
112
|
for (const key in source) {
|
|
105
113
|
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
106
|
-
result[key] = pluginUtils.deepMerge(result[key] || {}, source[key])
|
|
114
|
+
result[key] = pluginUtils.deepMerge(result[key] as Record<string, unknown> || {}, source[key] as Record<string, unknown>)
|
|
107
115
|
} else {
|
|
108
116
|
result[key] = source[key]
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
return result
|
|
112
120
|
},
|
|
113
|
-
validateSchema: (
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Basic validation logic
|
|
117
|
-
return { valid: true, errors: [] }
|
|
118
|
-
} catch (error) {
|
|
119
|
-
return { valid: false, errors: [error instanceof Error ? error.message : 'Validation failed'] }
|
|
120
|
-
}
|
|
121
|
+
validateSchema: (data: Record<string, unknown>, schema: PluginConfigSchema) => {
|
|
122
|
+
return createPluginUtils(logger).validateSchema(data, schema)
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
// Create plugin-compatible logger
|
|
125
|
-
|
|
126
|
-
debug: (message:
|
|
127
|
-
info: (message:
|
|
128
|
-
warn: (message:
|
|
129
|
-
error: (message:
|
|
130
|
-
child: (
|
|
131
|
-
time: (label: string) => void
|
|
132
|
-
timeEnd: (label: string) => void
|
|
133
|
-
request: (method: string, path: string, status?: number, duration?: number, ip?: string) => void
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const pluginLogger: PluginLogger = {
|
|
137
|
-
debug: (message: string, meta?: unknown) => logger.debug(message, meta),
|
|
138
|
-
info: (message: string, meta?: unknown) => logger.info(message, meta),
|
|
139
|
-
warn: (message: string, meta?: unknown) => logger.warn(message, meta),
|
|
140
|
-
error: (message: string, meta?: unknown) => logger.error(message, meta),
|
|
141
|
-
child: (context: Record<string, unknown>) => pluginLogger,
|
|
126
|
+
// Create plugin-compatible logger
|
|
127
|
+
const pluginLogger: Logger = {
|
|
128
|
+
debug: (message: unknown, ...args: unknown[]) => logger.debug(message, ...args),
|
|
129
|
+
info: (message: unknown, ...args: unknown[]) => logger.info(message, ...args),
|
|
130
|
+
warn: (message: unknown, ...args: unknown[]) => logger.warn(message, ...args),
|
|
131
|
+
error: (message: unknown, ...args: unknown[]) => logger.error(message, ...args),
|
|
132
|
+
child: (_context: Record<string, unknown>) => pluginLogger,
|
|
142
133
|
time: (label: string) => logger.time(label),
|
|
143
134
|
timeEnd: (label: string) => logger.timeEnd(label),
|
|
144
135
|
request: (method: string, path: string, status?: number, duration?: number, ip?: string) =>
|
|
145
|
-
logger.request(method, path, status, duration, ip)
|
|
136
|
+
logger.request(method, path, status, duration, ip),
|
|
137
|
+
plugin: (pluginName: string, message: string, meta?: unknown) => logger.plugin(pluginName, message, meta),
|
|
138
|
+
framework: (message: string, meta?: unknown) => logger.framework(message, meta)
|
|
146
139
|
}
|
|
147
140
|
|
|
148
141
|
this.pluginContext = {
|
|
149
142
|
config: fullConfig,
|
|
150
|
-
logger: pluginLogger
|
|
143
|
+
logger: pluginLogger,
|
|
151
144
|
app: this.app,
|
|
152
145
|
utils: pluginUtils
|
|
153
146
|
}
|
|
@@ -155,7 +148,7 @@ export class FluxStackFramework {
|
|
|
155
148
|
// Initialize plugin manager
|
|
156
149
|
this.pluginManager = new PluginManager({
|
|
157
150
|
config: fullConfig,
|
|
158
|
-
logger: pluginLogger
|
|
151
|
+
logger: pluginLogger,
|
|
159
152
|
app: this.app
|
|
160
153
|
})
|
|
161
154
|
|
|
@@ -169,11 +162,6 @@ export class FluxStackFramework {
|
|
|
169
162
|
environment: envInfo.name,
|
|
170
163
|
port: fullConfig.server.port
|
|
171
164
|
})
|
|
172
|
-
|
|
173
|
-
// Initialize automatic plugin discovery in background
|
|
174
|
-
this.initializeAutomaticPlugins().catch(error => {
|
|
175
|
-
logger.error('Failed to initialize automatic plugins', { error })
|
|
176
|
-
})
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
private async initializeAutomaticPlugins() {
|
|
@@ -184,27 +172,16 @@ export class FluxStackFramework {
|
|
|
184
172
|
const discoveredPlugins = this.pluginManager.getRegistry().getAll()
|
|
185
173
|
for (const plugin of discoveredPlugins) {
|
|
186
174
|
if (!this.pluginRegistry.has(plugin.name)) {
|
|
187
|
-
|
|
188
|
-
(this.pluginRegistry as any).plugins.set(plugin.name, plugin)
|
|
189
|
-
if (plugin.dependencies) {
|
|
190
|
-
(this.pluginRegistry as any).dependencies.set(plugin.name, plugin.dependencies)
|
|
191
|
-
}
|
|
175
|
+
this.pluginRegistry.registerSync(plugin)
|
|
192
176
|
}
|
|
193
177
|
}
|
|
194
178
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
(this.pluginRegistry as any).updateLoadOrder()
|
|
198
|
-
} catch (error) {
|
|
199
|
-
// Fallback: create basic load order
|
|
200
|
-
const plugins = (this.pluginRegistry as any).plugins as Map<string, FluxStack.Plugin>
|
|
201
|
-
const loadOrder = Array.from(plugins.keys())
|
|
202
|
-
;(this.pluginRegistry as any).loadOrder = loadOrder
|
|
203
|
-
}
|
|
179
|
+
// Refresh load order (falls back to insertion-order on failure)
|
|
180
|
+
this.pluginRegistry.refreshLoadOrder()
|
|
204
181
|
|
|
205
182
|
// Execute onConfigLoad hooks for all plugins
|
|
206
183
|
const configLoadContext = {
|
|
207
|
-
config: this.context.config,
|
|
184
|
+
config: this.context.config as import('@config').FluxStackConfig,
|
|
208
185
|
envVars: process.env as Record<string, string | undefined>,
|
|
209
186
|
configPath: undefined
|
|
210
187
|
}
|
|
@@ -235,13 +212,13 @@ export class FluxStackFramework {
|
|
|
235
212
|
}
|
|
236
213
|
|
|
237
214
|
private setupCors() {
|
|
238
|
-
const cors = this.
|
|
215
|
+
const cors = this.cfg.cors
|
|
239
216
|
|
|
240
217
|
this.app
|
|
241
218
|
.onRequest(({ set }) => {
|
|
242
|
-
set.headers["Access-Control-Allow-Origin"] = cors.origins.join(", ") || "*"
|
|
243
|
-
set.headers["Access-Control-Allow-Methods"] = cors.methods.join(", ") || "*"
|
|
244
|
-
set.headers["Access-Control-Allow-Headers"] = cors.headers.join(", ") || "*"
|
|
219
|
+
set.headers["Access-Control-Allow-Origin"] = (cors.origins ?? []).join(", ") || "*"
|
|
220
|
+
set.headers["Access-Control-Allow-Methods"] = (cors.methods ?? []).join(", ") || "*"
|
|
221
|
+
set.headers["Access-Control-Allow-Headers"] = (cors.headers ?? []).join(", ") || "*"
|
|
245
222
|
if (cors.credentials) {
|
|
246
223
|
set.headers["Access-Control-Allow-Credentials"] = "true"
|
|
247
224
|
}
|
|
@@ -258,7 +235,7 @@ export class FluxStackFramework {
|
|
|
258
235
|
const url = this.parseRequestURL(request)
|
|
259
236
|
|
|
260
237
|
// Handle API routes
|
|
261
|
-
if (url.pathname.startsWith(this.
|
|
238
|
+
if (url.pathname.startsWith(this.cfg.server.apiPrefix)) {
|
|
262
239
|
set.status = 200
|
|
263
240
|
set.headers['Content-Type'] = 'application/json'
|
|
264
241
|
set.headers['Content-Length'] = '0'
|
|
@@ -270,7 +247,6 @@ export class FluxStackFramework {
|
|
|
270
247
|
if (isStatic) {
|
|
271
248
|
set.status = 200
|
|
272
249
|
set.headers['Content-Type'] = 'text/html'
|
|
273
|
-
set.headers['Content-Length'] = '478' // approximate size of index.html
|
|
274
250
|
set.headers['Cache-Control'] = 'no-cache'
|
|
275
251
|
return ""
|
|
276
252
|
}
|
|
@@ -322,7 +298,7 @@ export class FluxStackFramework {
|
|
|
322
298
|
}
|
|
323
299
|
|
|
324
300
|
// Store reference to restore original behavior if needed
|
|
325
|
-
|
|
301
|
+
this._originalStderrWrite = originalStderrWrite
|
|
326
302
|
}
|
|
327
303
|
|
|
328
304
|
private setupHooks() {
|
|
@@ -431,7 +407,7 @@ export class FluxStackFramework {
|
|
|
431
407
|
query: Object.fromEntries(url.searchParams.entries()),
|
|
432
408
|
params: {},
|
|
433
409
|
response: currentResponse,
|
|
434
|
-
statusCode: Number((currentResponse
|
|
410
|
+
statusCode: Number((currentResponse instanceof Response ? currentResponse.status : undefined) || set.status || 200),
|
|
435
411
|
duration,
|
|
436
412
|
startTime
|
|
437
413
|
}
|
|
@@ -464,7 +440,7 @@ export class FluxStackFramework {
|
|
|
464
440
|
}
|
|
465
441
|
|
|
466
442
|
// Log the request automatically (if not disabled in config)
|
|
467
|
-
if (this.
|
|
443
|
+
if (this.cfg.server.enableRequestLogging !== false) {
|
|
468
444
|
// Ensure status is always a number (HTTP status code)
|
|
469
445
|
const status = typeof responseContext.statusCode === 'number'
|
|
470
446
|
? responseContext.statusCode
|
|
@@ -533,17 +509,17 @@ export class FluxStackFramework {
|
|
|
533
509
|
})
|
|
534
510
|
}
|
|
535
511
|
|
|
536
|
-
private async executePluginHooks(hookName:
|
|
512
|
+
private async executePluginHooks(hookName: PluginHook, context: unknown): Promise<void> {
|
|
537
513
|
const loadOrder = this.pluginRegistry.getLoadOrder()
|
|
538
514
|
|
|
539
515
|
for (const pluginName of loadOrder) {
|
|
540
516
|
const plugin = this.pluginRegistry.get(pluginName)
|
|
541
517
|
if (!plugin) continue
|
|
542
518
|
|
|
543
|
-
const hookFn =
|
|
519
|
+
const hookFn = plugin[hookName]
|
|
544
520
|
if (typeof hookFn === 'function') {
|
|
545
521
|
try {
|
|
546
|
-
await hookFn(context)
|
|
522
|
+
await (hookFn as Function)(context)
|
|
547
523
|
} catch (error) {
|
|
548
524
|
const err = error instanceof Error ? error : new Error(String(error))
|
|
549
525
|
logger.error(`Plugin '${pluginName}' ${hookName} hook failed`, {
|
|
@@ -566,8 +542,8 @@ export class FluxStackFramework {
|
|
|
566
542
|
const otherPlugin = this.pluginRegistry.get(otherPluginName)
|
|
567
543
|
if (!otherPlugin) continue
|
|
568
544
|
|
|
569
|
-
const hookFn =
|
|
570
|
-
if (typeof hookFn === 'function') {
|
|
545
|
+
const hookFn = otherPlugin.onPluginError
|
|
546
|
+
if (hookFn && typeof hookFn === 'function') {
|
|
571
547
|
try {
|
|
572
548
|
await hookFn({
|
|
573
549
|
pluginName,
|
|
@@ -584,17 +560,17 @@ export class FluxStackFramework {
|
|
|
584
560
|
}
|
|
585
561
|
}
|
|
586
562
|
|
|
587
|
-
private async executePluginBeforeRouteHooks(requestContext:
|
|
563
|
+
private async executePluginBeforeRouteHooks(requestContext: { handled?: boolean; response?: Response; [key: string]: unknown }): Promise<Response | null> {
|
|
588
564
|
const loadOrder = this.pluginRegistry.getLoadOrder()
|
|
589
565
|
|
|
590
566
|
for (const pluginName of loadOrder) {
|
|
591
567
|
const plugin = this.pluginRegistry.get(pluginName)
|
|
592
568
|
if (!plugin) continue
|
|
593
569
|
|
|
594
|
-
const onBeforeRouteFn =
|
|
595
|
-
if (typeof onBeforeRouteFn === 'function') {
|
|
570
|
+
const onBeforeRouteFn = plugin.onBeforeRoute
|
|
571
|
+
if (onBeforeRouteFn && typeof onBeforeRouteFn === 'function') {
|
|
596
572
|
try {
|
|
597
|
-
await onBeforeRouteFn(requestContext)
|
|
573
|
+
await onBeforeRouteFn(requestContext as unknown as import('@core/plugins/types').RequestContext)
|
|
598
574
|
|
|
599
575
|
// If this plugin handled the request, return the response
|
|
600
576
|
if (requestContext.handled && requestContext.response) {
|
|
@@ -611,17 +587,17 @@ export class FluxStackFramework {
|
|
|
611
587
|
return null
|
|
612
588
|
}
|
|
613
589
|
|
|
614
|
-
private async executePluginErrorHooks(errorContext:
|
|
590
|
+
private async executePluginErrorHooks(errorContext: { handled?: boolean; error: Error; request: Request; [key: string]: unknown }): Promise<Response | null> {
|
|
615
591
|
const loadOrder = this.pluginRegistry.getLoadOrder()
|
|
616
592
|
|
|
617
593
|
for (const pluginName of loadOrder) {
|
|
618
594
|
const plugin = this.pluginRegistry.get(pluginName)
|
|
619
595
|
if (!plugin) continue
|
|
620
596
|
|
|
621
|
-
const onErrorFn =
|
|
622
|
-
if (typeof onErrorFn === 'function') {
|
|
597
|
+
const onErrorFn = plugin.onError
|
|
598
|
+
if (onErrorFn && typeof onErrorFn === 'function') {
|
|
623
599
|
try {
|
|
624
|
-
await onErrorFn(errorContext)
|
|
600
|
+
await onErrorFn(errorContext as unknown as import('@core/plugins/types').ErrorContext)
|
|
625
601
|
|
|
626
602
|
// If this plugin handled the error, check if it provides a response
|
|
627
603
|
if (errorContext.handled) {
|
|
@@ -644,8 +620,8 @@ export class FluxStackFramework {
|
|
|
644
620
|
return null
|
|
645
621
|
}
|
|
646
622
|
|
|
647
|
-
private async handleViteProxy(errorContext:
|
|
648
|
-
const vitePort = this.
|
|
623
|
+
private async handleViteProxy(errorContext: { request: Request; method?: string; headers?: Record<string, string> }): Promise<Response> {
|
|
624
|
+
const vitePort = this.cfg.client?.port || 5173
|
|
649
625
|
const url = this.parseRequestURL(errorContext.request)
|
|
650
626
|
|
|
651
627
|
try {
|
|
@@ -677,29 +653,7 @@ export class FluxStackFramework {
|
|
|
677
653
|
|
|
678
654
|
use(plugin: Plugin) {
|
|
679
655
|
try {
|
|
680
|
-
|
|
681
|
-
if (this.pluginRegistry.has(plugin.name)) {
|
|
682
|
-
throw new Error(`Plugin '${plugin.name}' is already registered`)
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Store plugin without calling setup - setup will be called in start()
|
|
686
|
-
// We need to manually set the plugin since register() is async but we need sync
|
|
687
|
-
(this.pluginRegistry as any).plugins.set(plugin.name, plugin)
|
|
688
|
-
|
|
689
|
-
// Update dependencies tracking
|
|
690
|
-
if ((plugin as FluxStack.Plugin).dependencies) {
|
|
691
|
-
(this.pluginRegistry as any).dependencies.set(plugin.name, (plugin as FluxStack.Plugin).dependencies)
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Update load order by calling the private method
|
|
695
|
-
try {
|
|
696
|
-
(this.pluginRegistry as any).updateLoadOrder()
|
|
697
|
-
} catch (error) {
|
|
698
|
-
// Fallback: create basic load order
|
|
699
|
-
const plugins = (this.pluginRegistry as any).plugins as Map<string, Plugin>
|
|
700
|
-
const loadOrder = Array.from(plugins.keys())
|
|
701
|
-
; (this.pluginRegistry as any).loadOrder = loadOrder
|
|
702
|
-
}
|
|
656
|
+
this.pluginRegistry.registerSync(plugin as FluxStack.Plugin)
|
|
703
657
|
|
|
704
658
|
logger.debug(`Plugin '${plugin.name}' registered`, {
|
|
705
659
|
version: (plugin as FluxStack.Plugin).version,
|
|
@@ -712,7 +666,7 @@ export class FluxStackFramework {
|
|
|
712
666
|
}
|
|
713
667
|
}
|
|
714
668
|
|
|
715
|
-
routes(routeModule:
|
|
669
|
+
routes(routeModule: Elysia) {
|
|
716
670
|
this.app.use(routeModule)
|
|
717
671
|
return this
|
|
718
672
|
}
|
|
@@ -724,8 +678,13 @@ export class FluxStackFramework {
|
|
|
724
678
|
}
|
|
725
679
|
|
|
726
680
|
try {
|
|
681
|
+
// Initialize automatic plugins before anything else
|
|
682
|
+
// This was previously fire-and-forget in the constructor, causing a race condition
|
|
683
|
+
// where listen() could be called before plugin discovery finished (issue #75)
|
|
684
|
+
await this.initializeAutomaticPlugins()
|
|
685
|
+
|
|
727
686
|
// Validate plugin dependencies before starting
|
|
728
|
-
const plugins =
|
|
687
|
+
const plugins = this.pluginRegistry.getPluginsMap()
|
|
729
688
|
for (const [pluginName, plugin] of plugins) {
|
|
730
689
|
if (plugin.dependencies) {
|
|
731
690
|
for (const depName of plugin.dependencies) {
|
|
@@ -762,8 +721,9 @@ export class FluxStackFramework {
|
|
|
762
721
|
for (const pluginName of loadOrder) {
|
|
763
722
|
const plugin = this.pluginRegistry.get(pluginName)!
|
|
764
723
|
|
|
765
|
-
|
|
766
|
-
|
|
724
|
+
const pluginWithRoutes = plugin as FluxStack.Plugin & { plugin?: Elysia }
|
|
725
|
+
if (pluginWithRoutes.plugin) {
|
|
726
|
+
this.app.use(pluginWithRoutes.plugin)
|
|
767
727
|
logger.debug(`Plugin '${pluginName}' routes mounted`)
|
|
768
728
|
}
|
|
769
729
|
}
|
|
@@ -849,21 +809,21 @@ export class FluxStackFramework {
|
|
|
849
809
|
// Start the framework (load plugins)
|
|
850
810
|
await this.start()
|
|
851
811
|
|
|
852
|
-
const port = this.
|
|
853
|
-
const apiPrefix = this.
|
|
812
|
+
const port = this.cfg.server.port
|
|
813
|
+
const apiPrefix = this.cfg.server.apiPrefix
|
|
854
814
|
|
|
855
815
|
this.app.listen(port, () => {
|
|
856
|
-
const showBanner = this.
|
|
816
|
+
const showBanner = this.cfg.server.showBanner !== false // default: true
|
|
857
817
|
const vitePluginActive = this.pluginRegistry.has('vite')
|
|
858
818
|
|
|
859
819
|
// Prepare startup info for banner or callback
|
|
860
820
|
const startupInfo: StartupInfo = {
|
|
861
821
|
port,
|
|
862
|
-
host: this.
|
|
822
|
+
host: this.cfg.server.host || 'localhost',
|
|
863
823
|
apiPrefix,
|
|
864
824
|
environment: this.context.environment,
|
|
865
825
|
pluginCount: this.pluginRegistry.getAll().length,
|
|
866
|
-
vitePort: this.
|
|
826
|
+
vitePort: this.cfg.client?.port,
|
|
867
827
|
viteEmbedded: vitePluginActive, // Vite is embedded when plugin is active
|
|
868
828
|
swaggerPath: '/swagger', // TODO: Get from swagger plugin config
|
|
869
829
|
liveComponents: componentRegistry.getRegisteredComponentNames()
|
package/core/framework/types.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface RouteDefinition {
|
|
|
42
42
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
|
|
43
43
|
path: string
|
|
44
44
|
handler: Function
|
|
45
|
-
schema?:
|
|
45
|
+
schema?: unknown
|
|
46
46
|
middleware?: Function[]
|
|
47
47
|
description?: string
|
|
48
48
|
tags?: string[]
|
|
@@ -57,7 +57,7 @@ export interface MiddlewareDefinition {
|
|
|
57
57
|
|
|
58
58
|
export interface ServiceDefinition {
|
|
59
59
|
name: string
|
|
60
|
-
instance:
|
|
60
|
+
instance: unknown
|
|
61
61
|
dependencies?: string[]
|
|
62
62
|
singleton?: boolean
|
|
63
63
|
}
|
|
@@ -218,7 +218,7 @@ export function ${name}Demo() {
|
|
|
218
218
|
<input {...form.$field('email', { syncOn: 'change', debounce: 500 })} type="email" placeholder="Email" className="w-full px-3 py-2 border rounded-lg" />
|
|
219
219
|
<textarea {...form.$field('message', { syncOn: 'blur' })} placeholder="Mensagem" rows={3} className="w-full px-3 py-2 border rounded-lg" />
|
|
220
220
|
<div className="flex gap-2">
|
|
221
|
-
<button onClick={async () => { try { await form.$sync(); await form.submit() } catch (e
|
|
221
|
+
<button onClick={async () => { try { await form.$sync(); await form.submit() } catch (e) { alert((e as Error).message) }}} className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg">Enviar</button>
|
|
222
222
|
<button onClick={() => form.reset()} className="px-4 py-2 bg-gray-500 text-white rounded-lg">Limpar</button>
|
|
223
223
|
</div>
|
|
224
224
|
</div>
|
|
@@ -327,8 +327,11 @@ export const createLiveComponentCommand: CliCommand = {
|
|
|
327
327
|
{ name: "force", short: "f", description: "Overwrite", type: "boolean" }
|
|
328
328
|
],
|
|
329
329
|
handler: async (args, options, context) => {
|
|
330
|
-
const [
|
|
331
|
-
const
|
|
330
|
+
const [rawName] = args;
|
|
331
|
+
const name = rawName as string;
|
|
332
|
+
const { type: rawType = 'basic', 'no-client': noClient, room: rawRoom, force } = options;
|
|
333
|
+
const type = rawType as string;
|
|
334
|
+
const room = rawRoom as string | undefined;
|
|
332
335
|
|
|
333
336
|
if (!name || !/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
334
337
|
context.logger.error("❌ Nome inválido. Use PascalCase (ex: MeuComponente)");
|