Registers a module descriptor with the registry.
Idempotent: a module already registered under the same id wins, and a second call is a
no-op.
Registering after init() — a plugin whose bundle loads on demand — stores the module.
A lifecycle module is not initialised for the application already running, which it
joined by itself, but destroy() reaches it: it is torn down with the others, and
initialised like them from the next init() on. A UI slot is not drawn before the next
mount, the toolbar being built once per boot, and a warning says so.
Throws a GeoLeafError when the module is neither a lifecycle module (init and
destroy, both functions) nor a UI-only slot ({ id, ui }).
The module to register.
Resolves the dependency graph and initialises all registered modules in topological order.
Rejects with a GeoLeafError if a circular dependency is detected — the message
includes the full cycle path (e.g. "A → B → A") — or if a module declares a
dependency that is not registered.
Returns a Promise that resolves when all modules have completed init().
If any module's init() rejects, the returned Promise rejects with
that error (subsequent modules are not initialised) — unless
options.onModuleError answers "continue": the failure is then isolated to that
module and to the modules that depend on it (see ModuleInitOptions).
Engine-agnostic map adapter.
Read-only configuration access.
onModuleError, to isolate a module failure instead of rejecting.
Retrieves a registered module by its id, cast to type T.
Throws a GeoLeafError when no module with the given id is registered.
Use has() first when presence is uncertain.
The module's id string.
Returns true if a module with the given id is registered.
The module's id string.
Returns true if init() has already been called on this registry.
Use this guard before calling register() in contexts where the registry
may have already been initialised (e.g. double-boot scenarios).
Returns all registered modules in insertion order.
The returned array is read-only — use register() to add modules.
Collects and returns the ui slot of every module that declares one.
Used by the UI orchestrator to build the mobile toolbar and the filter panel tabs without coupling the UI layer to specific module classes.
Returns an empty array when no module declares a UI slot.
Returns the schema of a registered module, or null if no module
with the given id is registered.
The returned object is a lightweight metadata view — it does not expose
internal module state. IModuleSchema is enriched as capability
schemas (config, capabilities, metadata) get defined.
Module id (e.g. 'poi', 'route', 'search').
The modules that are actually RUNNING — not merely the ones registered.
They were identical for as long as a switched-off capability was never registered:
registerPresetModules filtered at Pass 2, so the map only ever held live modules and
returning all of them was accurate by accident. 9.2 removed that filter — every
capability module is now registered, and each decides in its own init() whether to
run, so that a profile loaded after Pass 2 gets a say. Returning the whole map from a
method called getActiveModules would, from that commit on, have answered
« registered » to a question that asks « active ». The introspection surface would
report capabilities that are switched off — silently, and to integrators.
isEnabled() is not in ICoreModule, deliberately: it is put there by the wrapper in
presets/apply-preset.ts, and the registry has no business learning what a capability
gate is. Everything else — kernel modules, plugin UI slots, anything registered by
hand — carries no gate, so it has nothing to be switched off by. Absent ⟹ active is
therefore the correct default, not a lenient one.
⚠️ A wrapper answers false until its init() has run. Called mid-boot, this method
lists the kernel only — which is the honest answer: nothing else has started yet.
Under init(…, { onModuleError: () => "continue" }), a module whose init() threw, and
every module skipped because it depends on one, are left out: a gate says whether a
capability is switched on, not whether it started.
Destroys the modules that started, in reverse initialisation order, and re-arms the
registry: a later init() runs every module again.
Each module's destroy() is called exactly once. Errors in individual
destroy() calls are logged but do not interrupt the teardown sequence —
all modules are destroyed regardless of individual failures. A module that never ran is
not destroyed: one skipped because a module it depends on failed (see
ModuleInitOptions), or one an initialisation stopped by shouldContinue never
reached.
Refused — logged, nothing torn down — while init() is still running.
Concrete implementation of
IModuleRegistry.Example