Event reference

v0.1.14

Reference for MishkaInstaller.Event.Event, the plugin registry, and for the modules that build event chains: MishkaInstaller.Event.EventHandler and MishkaInstaller.Event.ModuleStateCompiler. It lists the fields of a plugin record, every registry function, the errors they return, and the functions of a compiled event module. The plugin-side functions in Hook reference call these.

The plugin record

%MishkaInstaller.Event.Event{} is stored in a Mnesia table and built with Event.builder/1.

FieldTypeDefaultRule
idstringgeneratedA version 4 UUID.
namemodulenoneRequired. The plugin module.
eventstringnoneRequired, not empty.
priorityinteger1000 to 100. A smaller number runs first.
statusatom:registeredOne of :registered, :started, :stopped, :restarted, :held.
extensionatomnoneRequired. The OTP application of the plugin. register/3 sets it from name.config(:app).
dependslist[]Plugin modules this plugin needs.
extralist of maps[]Data stored with the plugin. The library does not read it.
inserted_atintegernowUnix time in seconds.
updated_atintegernowUnix time in seconds, set on every change.

The table is indexed by name, event and extension. It uses disc copies outside the test environment and RAM copies in it.

Lifecycle functions

queue is a boolean. With true the rebuild of the event is queued and the function returns before it ends. With false it returns after the rebuild.

FunctionReturnsWhat it does
register(name, event, initial){:ok, record} or an errorStores a plugin. If the plugin already has a record, it updates priority, extra and depends, keeps id, event and inserted_at, and keeps status unless a changed dependency moves the plugin into or out of :held. It rebuilds the event only for such a changed record, never for a new plugin.
start(:name, name, queue){:ok, record} or an errorWrites :started, rebuilds the event, and releases dependents.
start(:event, event, queue){:ok, records} or an errorStarts every plugin of an event except stopped ones. A plugin with inactive dependencies becomes :held. Records are sorted by priority and name.
start(queue \\ true){:ok, results}, or an error when the registry cannot be readThe same for every event in the registry.
restart(:name, name, queue){:ok, record} or an errorWrites :restarted. Refuses a stopped plugin and a plugin with inactive dependencies.
restart(:event, event, queue){:ok, records} or an errorRestarts every plugin of an event that can restart. Others are skipped.
restart(queue \\ true){:ok, results}, or an error when the registry cannot be readThe same for every event.
stop(:name, name, queue){:ok, record} or an errorWrites :stopped from any status except :stopped. Holds dependents.
stop(:event, event, queue){:ok, records} or an errorStops every plugin of an event that is not stopped.
stop(queue \\ true){:ok, results}, or an error when the registry cannot be readThe same for every event.
enable(name, queue \\ true){:ok, record} or an errorWrites :restarted, or :held when a dependency is inactive. The only way out of :stopped.
unregister(:name, name, queue){:ok, record} or an errorDeletes the record, rebuilds the event, holds dependents, and stops the plugin's process if it runs.
unregister(:event, event, queue){:ok, records} or an errorUnregisters every plugin of an event.
unregister(queue \\ true){:ok, results}, or an error when the registry cannot be readThe same for every event.
unregister_gone(queue \\ true)list of recordsUnregisters each plugin whose application is loaded, does not list the plugin's module, and whose module is not loaded either. Returns the records it removed. The event handler runs it each time it starts every event: on boot, after the libraries installed at runtime are loaded, and again whenever the Mnesia store announces that it is ready again. At no other time does anything run it. Calling it by hand after a library was upgraded or uninstalled at runtime finds nothing: an upgrade leaves the old module loaded, and an uninstall unloads the application, which the check reads as a library that is still on its way in. See the documentation on plugins in installed libraries for how to remove those plugins.

Queries

FunctionReturns
get()Every record.
get(id)One record by id, or nil.
get(:name, module)One record, or nil.
get(:event, event)The records of an event, or [].
get(:extension, app)The records of an application, or [].
dirty_get(:name, module)One record by a read with no transaction. It may be slightly stale.
ids()Every id.
group_events(key \\ [:event]){:ok, events}, the distinct event names.
unique(field, value):ok when no record matches, else an error. Use it with :name. With :event or :extension it always answers the error, because get/2 returns a list for those fields and a list is never nil.
unique?(field, value)A boolean. The same rule applies: with :event or :extension it is always false.

Writes

FunctionReturnsWhat it does
write(map){:ok, record} or an errorValidates and stores a whole record.
write(field, value, changes){:ok, record} or an errorMerges changes into the record found by :id or :name. It reads the row under a write lock, so two changes to different fields do not overwrite each other. It does not rebuild any event or touch dependents.
delete(field, value){:ok, record} or an errorDeletes by :id or :name.
drop(){:ok, :atomic} or {:error, source, description}Empties the table. It is the one function here whose error is not a list of maps.

Status writes commit synchronously and flush the log, so they survive an unclean shutdown.

Dependency functions

FunctionReturnsWhat it does
dependents(name)list of recordsEvery plugin that lists name in depends, on any event.
sync_dependents(names, queue \\ true)list of eventsRe-evaluates what depends on names, holds or releases it, rebuilds each event once, and returns the events rebuilt. Lifecycle functions call it. Call it yourself only after you change a status with write/3.
no_dependency_cycle(name, depends):ok or an errorChecks that depends adds no cycle back to name.
connections()a libgraph GraphOne edge from each plugin to each dependency.
dependency_order(plugins)listSorts plugins so each follows its dependencies, ties by priority and name.

Errors

Every error is {:error, [%{action: action, field: field, message: message}]}, except the one of drop/0.

actionfieldMessage
:ensure_loaded:globalThis module is not loaded in the system.
:register:dependsDependency cycle detected: <module> ultimately depends on itself.
:exist_record?:globalThe ID of the record you want is incorrect or has already been deleted.
:plugin_status:globalThis plugin is stopped, use `enable/2` to switch it back on. Or: This plugin is already stopped.
:hold_statuses:globalThis plugin has dependencies that are not yet activated in the system
:start_event, :restart_event, :stop_event, :unregister_event:globalThere are no plugins in the database that can be started, stopped or unregistered for this event.
:write:globalThe ID of the record you want to update is incorrect or has already been deleted.
:delete:globalThe ID of the record you want to delete is incorrect or has already been deleted.
:unique:globalThis event already exists in the database.
:database:globalUnfortunately, there is a problem in reading, storing or deleting data in the database. The description of the Mnesia error follows, such as "Node not running".
:compile:eventThe description of the compiler's error, or Unexpected error. Only ModuleStateCompiler.create/3 and purge_create/3 return it.
:start, :restart, :stop, :enable, :unregister:globalAn error occurred in the compile of an event.

A field that fails validation, such as a priority above 100, returns an error whose field names it.

The :database error has a fourth key, source, which holds the raw Mnesia result, such as {:aborted, {:node_not_running, node}}. write/1, write/3, delete/2 and group_events/1 return it when their transaction aborts, and so do the lifecycle functions that call them. get/0, get/1 and get/2 log the same failure and answer [] or nil instead.

With queue set to false, start/3, restart/3, stop/3 and unregister/3 by :name, and enable/2, return an error when the rebuild of the event fails. Its action is the name of the function. The new status is already stored when the error returns, and the plugins that depend on this one are not re-evaluated.

A module that cannot be built fails the rebuild, because the event handler expects :ok from purge_create/3. The handler logs compile failed for event and emits the :compile telemetry event with result: :error. The Telemetry and log reference lists both.

Rebuild an event

FunctionReturnsWhat it does
EventHandler.do_compile(event, status, queue \\ true):ok, or an error when queue is false and the rebuild failsRebuilds the compiled module of an event from the registry. status is an atom naming the reason, such as :register. With false it waits, and it must not be called from the event handler's own process.

The compiled event module

Each event with plugins has a module named MishkaInstaller.Event.ModuleStateCompiler.State.<Name>. Its functions:

FunctionReturns
call(state, args \\ [])The chain's result. In :error mode, the :cannot_run argument or state.
mode():ok, or :error when a plugin of the chain is started but not loaded.
initialize?()true.
initialize()%{module: module, plugins: [record]}, the chain in order.
is_changed?(plugins)Whether plugins differs from the compiled chain.
is_initialized?(plugin)Whether the record is in the chain.

Functions of ModuleStateCompiler:

FunctionWhat it does
create(plugins, event, inaccessible \\ [])Builds the module and returns :ok. With a non-empty inaccessible list it builds the stub for an event that cannot run. When the module cannot be built it returns {:error, [%{action: :compile, field: :event, message: message}]}. message is the description of the compiler's error, or Unexpected error for any other failure.
purge_create(plugins, event, inaccessible \\ [])Drops the oldest loaded copy, then builds the module. It returns what create/3 returns.
purge(event_or_events)Removes the module of one event or a list of events.
module_event_name(event)The module name for an event. For a name it has not seen before, it creates an atom and a :persistent_term key. The library never removes either.
initialize?(event)Whether the module is initialized. Raises when it does not exist.
rescue_initialize?(event)The same, and false instead of raising.
compile_initialize?(event)Whether the module is loaded.
safe_initialize?(event)Whether the module exports initialize?/0.