Every plugin becomes a supervised tower. Every event becomes a priority-routed pipe. Every halt is a junction valve. Mishka Installer is the plugin and event manager that runs the underground of your Elixir application, wired with Erlang OTP and lit by sodium-lamp telemetry.
v0.0.0 Hex ↗Mishka Installer is an open-source Elixir library for adding behavior to your application as plugins, without touching its core code. Every plugin you register becomes its own supervised GenServer process. It tracks its own status, can be switched off and on at runtime, and can report its health.
Plugins attach to named events ("after_register", "after_login", anything you invent) and the dispatcher runs them in priority order, a plugin's depends first. Each plugin's reply is the next plugin's input, and any plugin can
halt
the pipeline; the rest are skipped. An optional sealed
:private
payload is merged into a map or keyword result and returned to the caller.
It is also a runtime library installer:
Installer.install/1
loads a pre-built library (a compiled ebin from a local path, a URL or a GitHub release) into the running node, with an allowlist for remote sources and an optional sha256 checksum.
Pinned to the steel wall, blueprint pen on rivets. Five wires, one purpose.
defmodule RegisterEmailSender do use MishkaInstaller.Event.Hook, event: "after_success_login", initial: %{depends: [OtherPlugin], priority: 20} @impl true def call(entries) do {:reply, entries} endend
▸ Select a part to light up its line of code.
One registration · three towers · one valve closure. Scrub the sequence below.
%{email: "ada@..."}
Watch a packet enter the trunk pipe, ride through three plugin junctions, and get cut short by an inline valve.
Every plugin has one of these statuses. Read it with Event.get(:name, MyPlugin).
A maintenance reference: what each reply means, whether it merges with :private, and whether it halts the line. A reply that is not {:reply, state} or {:reply, :halt, state} is logged and the event returns its input unchanged.
{:reply, {:ok, data}}
✕ no
○ continues
not a map or list
{:reply, {:ok, [name: "..."]}}
✓ yes
○ continues
keyword list
{:reply, {:ok, %{name: "..."}}}
✓ yes
○ continues
map
{:reply, {:error, errors}}
✕ no
○ continues
error path
{:reply, [name: "..."]}
✓ yes
○ continues
keyword list
{:reply, %{name: "..."}}
✓ yes
○ continues
map
{:reply, :halt, {:ok, %{...}}}
✓ yes
◼ closes line
—
{:reply, :halt, {:error, errors}}
✕ no
◼ closes line
halts with errors
Hook.call/3 takes private: and return: options. A third, meta:, hands read-only context to plugins that define call/2.
Open valve: params flow through every plugin and come back modified.
Hook.call("after_login", params)
A sealed bypass line skips every plugin and is merged into a map or keyword result.
Hook.call("after_login", params, private: %{request_id: rid})
A return-pipe runs the plugins, then hands the caller its original input.
Hook.call("after_login", params, return: true)
Pick a tower, hit a switch. A simulation of the plugin registry that follows the library's statuses and rules.
One transmission a month: new patterns from the trunk line, dispatcher tricks, plugin recipes. No siren spam. Cut the line in one keystroke.