PluginRegistry is the single source of truth for registered plugins, their persisted enable/install status, extension/widget/hook resolution, and lifecycle orchestration. It is registered in the IoC container by PluginsServiceProvider and reached at runtime through the bound plugins context (or the module-level facade helpers).
PluginRegistry#
@Injectable() class constructed with @Inject(PLUGIN_REPOSITORY), @Inject(MIGRATION_RUNNER), @Inject(LOGGER) and @Inject(EVENT_BUS).
| Method | Behavior |
|---|---|
register(plugin) | Registers a plugin (and its nested plugins) by manifest.id; re-registers in development for HMR. |
init() | Idempotent bootstrap: publishes system:plugins:init:start, seeds statuses, loads the DB status, topologically sorts plugins, runs migrations and onMigrate/onEnable/init for enabled-but-not-installed plugins, then publishes system:plugins:init:end. |
getPlugins() | All registered plugins. |
getEnabledPlugins() | Plugins whose runtime status is enabled. |
getPlugin(id) | A plugin by id. |
getPluginsStatus() | Manifests merged with persisted/in-memory status (enabled, installed, lastStep). |
getPluginStatus(id) | Status for one plugin, or null. |
getHooks(point) | Enabled plugins' hooks for a point, sorted by priority (default 100). |
getExtensions(target, point, includeDisabled?, context?) | Extensions for a slot, filtered by context.roles/context.permissions and sorted by priority. |
getWidgets(area, context?) | Widgets for an area, filtered by context.roles/context.permissions and sorted by priority. |
togglePlugin(id, enabled, context?) | Enables/disables a plugin, cascading dependency activation and dependent deactivation, running migrations and onEnable/onDisable, and publishing system:plugin:toggle. |
hasHooks(point) / hasExtension(target, point?) | Boolean checks over enabled plugins. |
getConfig<T>(id) | Parses the stored JSON config, or null. |
updateConfig(id, config) | Stores the JSON config. |
Notes from the implementation:
- Plugin ids are normalized: an npm dependency like
@veap/media-pluginresolves to the registered idmedia-plugin(resolveDependencyId). getHooks/getExtensions/getWidgetsare async even though the work is in-memory.- Role filtering is OR across
roles; permission filtering requires all listed permissions. togglePluginrolls back in-memory status when a state-transition write fails, and processes dependency activation/deactivation recursively (guarding against cycles).
Contracts#
The /registry entry point (and the root barrel) export the persistence and migration ports:
PluginStatusRecord(aliasPluginRecord):id,enabled,installed,system,config?,lastStep?.PluginRuntimeStatus:enabled,installed,lastStep?.UpsertPluginStatusRecord:enabled?,installed?,lastStep?.IPluginRepository:findAll(),findById(id),seed(values),upsertStatus(id, update, defaults),updateConfig(id, configJson).IMigrationRunner:run(scope, migrations),rollback(scope, migrations).- Tokens:
PLUGIN_REPOSITORY,MIGRATION_RUNNER. ActiveRecordPluginRepositoryandKnexMigrationRunnerare the default implementations used by the provider.
Module-level facade#
registry/facade.ts exposes composition-bound helpers that read the bound plugins context instead of resolving the container themselves:
| Helper | Behavior |
|---|---|
registerPlugins(plugins) | Registers each plugin on the context registry. |
ensurePluginsInitialized() | Calls registry.init(). |
applyPluginFilters(point, data, context?) | Runs the hooks for point in priority order, threading data through each handler. |
togglePluginState(id, enabled, context?) | Delegates to registry.togglePlugin. |
hasPluginHooks(point) | Boolean check. |
hasPluginExtension(target, point?) | Boolean check. |
getPluginConfig(id) / updatePluginConfig(id, config) | Config read/write. |
getPluginsStatus() / getPluginStatus(id) | Status reads. |
facade.ts also re-exports several aliases used by the older module API (for example applyFilters, getModule, toggleModuleState) via server.ts; prefer the *Plugin* names.
Context binding#
The registry is reached through pluginsContext(), a globalThis-bound object ({ registry, navigation, router? }) that PluginsServiceProvider.boot() binds via bindPluginsContext. Calling pluginsContext() before boot throws AppError.Internal. isPluginsContextBound() reports whether it is available (used by PluginExtensionPoint to fall back gracefully).
Events#
system:plugins:init:start/system:plugins:init:end— published around registry initialization (init:endcarries{ timestamp, pluginCount }).system:plugin:toggle— published after a toggle with{ pluginId, isEnabled }; the provider subscribes to clear the router cache.