eparch/event_manager
A type-safe, OTP-compatible event manager implementation that leverages Erlang’s gen_event behaviour through the Gleam FFI.
Overview
An event manager is a process that hosts any number of independent
handlers. Handlers are attached and detached at runtime. When you call
notify or sync_notify, every currently-registered handler receives the
event.
Example
import eparch/event_manager
import gleam/erlang/process
type MyEvent { LogLine(String) | Flush(process.Subject(Nil)) }
let assert Ok(mgr) = event_manager.start()
let handler =
event_manager.new_handler(0, fn(event, count) {
case event {
LogLine(_) -> event_manager.Continue(count + 1)
Flush(reply) -> {
process.send(reply, Nil)
event_manager.Continue(count)
}
}
})
let assert Ok(_ref) = event_manager.add_handler(mgr, handler)
event_manager.notify(mgr, LogLine("hello"))
event_manager.sync_notify(mgr, LogLine("world"))
Types
Errors that can occur when adding a handler.
pub type AddError {
HandlerAlreadyExists
InitFailed(String)
}
Constructors
-
HandlerAlreadyExists -
InitFailed(String)
The result of a handler processing an event.
Return Continue(new_state) to keep the handler alive with updated state,
or Remove to unregister the handler from the manager.
pub type EventStep(state) {
Continue(state: state)
Remove
}
Constructors
-
Continue(state: state)Keep the handler alive and update its state.
-
RemoveRemove this handler from the event manager.
A builder for configuring a handler before registering it with a manager.
Create one with new_handler/2 and optionally extend it with
on_terminate/2 and on_format_status/2.
pub opaque type Handler(state, event)
An opaque reference to a specific registered handler instance.
Values of this type are only ever produced by add_handler or
add_sup_handler. Pass them to remove_handler to unregister a specific
handler, or compare them with values returned by which_handlers.
pub type HandlerRef
An opaque reference to a running event manager process.
Values of this type are only ever produced by start. Pass them to
notify, sync_notify, add_handler, etc.
pub type Manager(event)
Errors that can occur when removing a handler.
pub type RemoveError {
HandlerNotFound
RemoveFailed(String)
}
Constructors
-
HandlerNotFound -
RemoveFailed(String)
Errors that can occur when starting an event manager.
pub type StartError {
AlreadyStarted
StartFailed(String)
}
Constructors
-
AlreadyStarted -
StartFailed(String)
Values
pub fn add_handler(
manager: Manager(event),
handler: Handler(state, event),
) -> Result(HandlerRef, AddError)
Register an unsupervised handler with the event manager.
Returns Ok(HandlerRef) on success. The returned ref uniquely identifies
this handler instance and can be used with remove_handler.
If the handler crashes, the manager removes it silently without notifying
the caller. For crash notifications use add_supervised_handler.
Example
let assert Ok(ref) = event_manager.add_handler(mgr, my_handler)
pub fn add_supervised_handler(
manager: Manager(event),
handler: Handler(state, event),
) -> Result(HandlerRef, AddError)
Register a supervised handler with the event manager.
Like add_handler, but links the handler to the calling process. If the
handler is removed for any reason other than a normal remove_handler call
(e.g. it crashes or returns Remove), the calling process receives a
message of the form:
{gen_event_EXIT, HandlerRef, Reason}
You can receive this message using process.selecting_anything with a
gleam/dynamic decoder.
pub fn manager_pid(manager: Manager(event)) -> process.Pid
Return the Pid of the event manager process.
Useful for monitoring the manager with process.monitor.
pub fn new_handler(
initial_state initial_state: state,
on_event handler: fn(event, state) -> EventStep(state),
) -> Handler(state, event)
Handler Builder
Create a handler with an initial state and an event callback.
The on_event function is called for every event delivered to this handler
via notify or sync_notify. It receives the event and the current state,
and must return either Continue(new_state) or Remove.
Example
let handler =
event_manager.new_handler(initial_state: 0, on_event: fn(event, count) {
case event {
Increment -> event_manager.Continue(count + 1)
Reset -> event_manager.Continue(0)
}
})
pub fn notify(manager: Manager(event), event: event) -> Nil
Asynchronously broadcast an event to all registered handlers.
Returns immediately without waiting for handlers to finish processing.
Use sync_notify if you need a synchronization point.
pub fn on_format_status(
handler: Handler(state, event),
formatter: fn(state) -> String,
) -> Handler(state, event)
Provide a function to format this handler’s state for OTP status reports.
When set, the returned string is used in place of the raw state in
sys:get_status/1 output and SASL crash reports. Useful for hiding
secrets, summarising large data structures, or presenting a domain-friendly
view. Since OTP 25.0.
Example
event_manager.new_handler(connection, on_event)
|> event_manager.on_format_status(fn(conn) {
"Conn(id=" <> conn.id <> ")"
})
pub fn on_terminate(
handler: Handler(state, event),
cleanup: fn(state) -> Nil,
) -> Handler(state, event)
Attach a cleanup function called when the handler is removed or the manager stops.
Example
event_manager.new_handler(conn, on_event)
|> event_manager.on_terminate(fn(conn) { db.close(conn) })
pub fn remove_handler(
manager: Manager(event),
ref: HandlerRef,
) -> Result(Nil, RemoveError)
Remove a specific handler from the event manager.
The handler’s on_terminate callback is called before removal.
pub fn start() -> Result(Manager(event), StartError)
Start an event manager process linked to the caller.
Example
let assert Ok(mgr) = event_manager.start()
pub fn stop(manager: Manager(event)) -> Nil
Stop the event manager, terminating it with reason normal.
All registered handlers have their on_terminate callback invoked before
the manager shuts down.
pub fn sync_notify(manager: Manager(event), event: event) -> Nil
Synchronously broadcast an event to all registered handlers.
Blocks until every currently registered handler has processed the event. Use this when you need to know that all handlers have seen the event before continuing.
pub fn which_handlers(
manager: Manager(event),
) -> List(HandlerRef)
Return the list of HandlerRefs for all currently registered handlers.