import { UpdateSDK, type PluginRegistration } from './UpdateSDK' export interface XuqmPluginContext { navigate(route: string, params?: unknown): void emit(event: string, payload?: unknown): void getHostState(): T } export interface XuqmPluginDefinition { moduleId: string activate(context: XuqmPluginContext): void | Promise deactivate?(): void | Promise } export interface XuqmRuntimeOptions { plugins: PluginRegistration[] context: XuqmPluginContext loadBundle(moduleId: string, bundlePath: string): Promise checkUpdatesOnStart?: boolean } export interface XuqmPluginActivationOptions { /** Re-evaluate the active bundle after a buz-only release was installed. */ reloadBundle?: boolean } const definitions = new Map() /** Called by a plugin bundle when it is evaluated. */ export function definePlugin(definition: XuqmPluginDefinition): void { if (!definition.moduleId.trim()) { throw new Error('[XuqmRuntime] moduleId is required') } const existing = definitions.get(definition.moduleId) if (existing && existing !== definition) { throw new Error(`[XuqmRuntime] Plugin "${definition.moduleId}" is already defined`) } definitions.set(definition.moduleId, definition) } export const XuqmRuntime = (() => { let options: XuqmRuntimeOptions | null = null const active = new Set() return { configure(next: XuqmRuntimeOptions): void { if (!next.plugins.length) { throw new Error('[XuqmRuntime] At least one plugin registration is required') } options = next UpdateSDK.registerPlugins(next.plugins) }, async start() { if (!options) throw new Error('[XuqmRuntime] configure() must be called before start()') await UpdateSDK.preparePendingRelease() if (options.checkUpdatesOnStart !== false) { return UpdateSDK.checkStartupUpdate() } return { kind: 'none' as const } }, async activate( moduleId: string, activationOptions: XuqmPluginActivationOptions = {}, ): Promise { if (!options) throw new Error('[XuqmRuntime] configure() must be called before activate()') if (active.has(moduleId) && !activationOptions.reloadBundle) return if (activationOptions.reloadBundle) { const previous = definitions.get(moduleId) if (previous?.deactivate) await previous.deactivate() active.delete(moduleId) definitions.delete(moduleId) } let definition = definitions.get(moduleId) if (!definition) { const bundlePath = await UpdateSDK.getLaunchBundlePath(moduleId) await options.loadBundle(moduleId, bundlePath) definition = definitions.get(moduleId) } if (!definition) { await UpdateSDK.reportPluginLaunchFailure(moduleId, 'Plugin did not call definePlugin()') throw new Error(`[XuqmRuntime] Plugin "${moduleId}" did not register after bundle load`) } try { await definition.activate(options.context) active.add(moduleId) await UpdateSDK.confirmPluginLaunch(moduleId) } catch (error) { await UpdateSDK.reportPluginLaunchFailure( moduleId, error instanceof Error ? error.message : String(error), ) throw error } }, async deactivate(moduleId: string): Promise { const definition = definitions.get(moduleId) if (definition?.deactivate) await definition.deactivate() active.delete(moduleId) }, getDefinedPlugins(): string[] { return Array.from(definitions.keys()) }, } })()