Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 5 additions & 54 deletions core/framework/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@ import { createHash } from "crypto"
import { createPluginUtils } from "@core/plugins/config"
import type { Plugin } from "@core/plugins"

/**
* Interface for accessing private PluginRegistry members.
* The framework needs direct access for synchronous plugin registration.
*/
interface PluginRegistryInternals {
plugins: Map<string, FluxStack.Plugin>
dependencies: Map<string, string[]>
loadOrder: string[]
updateLoadOrder(): void
}

export class FluxStackFramework {
private app: Elysia
private context: FluxStackContext
Expand All @@ -35,11 +24,6 @@ export class FluxStackFramework {
private requestTimings: Map<string, number> = new Map()
private _originalStderrWrite?: typeof process.stderr.write

/** Access registry internals for synchronous plugin management */
private get registryInternals(): PluginRegistryInternals {
return this.pluginRegistry as unknown as PluginRegistryInternals
}

/** Access typed config from context (config is stored as unknown to avoid circular deps) */
private get cfg(): import('@config').FluxStackConfig {
return this.context.config as import('@config').FluxStackConfig
Expand Down Expand Up @@ -188,23 +172,12 @@ export class FluxStackFramework {
const discoveredPlugins = this.pluginManager.getRegistry().getAll()
for (const plugin of discoveredPlugins) {
if (!this.pluginRegistry.has(plugin.name)) {
// Register in main registry (synchronously, will call setup in start())
this.registryInternals.plugins.set(plugin.name, plugin)
if (plugin.dependencies) {
this.registryInternals.dependencies.set(plugin.name, plugin.dependencies)
}
this.pluginRegistry.registerSync(plugin)
}
}

// Update load order
try {
this.registryInternals.updateLoadOrder()
} catch {
// Fallback: create basic load order
const plugins = this.registryInternals.plugins
const loadOrder = Array.from(plugins.keys())
this.registryInternals.loadOrder = loadOrder
}
// Refresh load order (falls back to insertion-order on failure)
this.pluginRegistry.refreshLoadOrder()

// Execute onConfigLoad hooks for all plugins
const configLoadContext = {
Expand Down Expand Up @@ -680,29 +653,7 @@ export class FluxStackFramework {

use(plugin: Plugin) {
try {
// Use the registry's public register method, but don't await it since we need sync operation
if (this.pluginRegistry.has(plugin.name)) {
throw new Error(`Plugin '${plugin.name}' is already registered`)
}

// Store plugin without calling setup - setup will be called in start()
// We need to manually set the plugin since register() is async but we need sync
this.registryInternals.plugins.set(plugin.name, plugin)

// Update dependencies tracking
if ((plugin as FluxStack.Plugin).dependencies) {
this.registryInternals.dependencies.set(plugin.name, (plugin as FluxStack.Plugin).dependencies!)
}

// Update load order by calling the private method
try {
this.registryInternals.updateLoadOrder()
} catch {
// Fallback: create basic load order
const plugins = this.registryInternals.plugins
const loadOrder = Array.from(plugins.keys())
this.registryInternals.loadOrder = loadOrder
}
this.pluginRegistry.registerSync(plugin as FluxStack.Plugin)

logger.debug(`Plugin '${plugin.name}' registered`, {
version: (plugin as FluxStack.Plugin).version,
Expand Down Expand Up @@ -733,7 +684,7 @@ export class FluxStackFramework {
await this.initializeAutomaticPlugins()

// Validate plugin dependencies before starting
const plugins = this.registryInternals.plugins
const plugins = this.pluginRegistry.getPluginsMap()
for (const [pluginName, plugin] of plugins) {
if (plugin.dependencies) {
for (const depName of plugin.dependencies) {
Expand Down
50 changes: 50 additions & 0 deletions core/plugins/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,56 @@ export class PluginRegistry {
return this.plugins.has(name)
}

/**
* Register a plugin synchronously (no async hooks).
*
* Used by the framework to add plugins via .use() and during automatic
* plugin discovery, where the full async register() flow (which fires
* onPluginRegister hooks) is not needed — setup hooks run later in start().
*/
registerSync(plugin: FluxStackPlugin): void {
if (this.plugins.has(plugin.name)) {
throw new FluxStackError(
`Plugin '${plugin.name}' is already registered`,
'PLUGIN_ALREADY_REGISTERED',
400
)
}

this.validatePlugin(plugin)
this.plugins.set(plugin.name, plugin)

if (plugin.dependencies) {
this.dependencies.set(plugin.name, plugin.dependencies)
}

this.updateLoadOrder()
}

/**
* Refresh the load order.
*
* Falls back to insertion-order if the topological sort fails
* (e.g. unresolvable external dependency listed but not yet registered).
*/
refreshLoadOrder(): void {
try {
this.updateLoadOrder()
} catch {
this.loadOrder = Array.from(this.plugins.keys())
}
}

/**
* Return a read-only snapshot of the internal plugin map.
*
* Allows the framework to iterate over registered plugins for dependency
* validation without reaching into private fields.
*/
getPluginsMap(): ReadonlyMap<string, FluxStackPlugin> {
return this.plugins
}

/**
* Check which dependencies are missing from main package.json
*/
Expand Down
Loading
Loading