···11+import gleam/erlang/process.{type Subject}
22+import gleam/list
33+import group_registry
44+55+/// Event types for stats updates
66+pub type StatsEvent {
77+ RecordCreated
88+ RecordDeleted
99+ ActorCreated
1010+}
1111+1212+/// The group name for all stats event subscriptions
1313+const group_name = "stats_events"
1414+1515+// Global registry name - must be created once and reused
1616+@external(erlang, "stats_pubsub_ffi", "get_registry_name")
1717+fn registry_name() -> process.Name(group_registry.Message(StatsEvent))
1818+1919+/// Initialize the Stats PubSub registry
2020+/// Must be called once when the server starts
2121+pub fn start() -> Nil {
2222+ // Start the registry (idempotent - safe to call multiple times)
2323+ let _ = group_registry.start(registry_name())
2424+ Nil
2525+}
2626+2727+fn get_registry() -> group_registry.GroupRegistry(StatsEvent) {
2828+ // Get existing registry
2929+ group_registry.get_registry(registry_name())
3030+}
3131+3232+/// Subscribe to stats events
3333+/// Returns a Subject that will receive StatsEvent messages
3434+pub fn subscribe() -> Subject(StatsEvent) {
3535+ let registry = get_registry()
3636+ let my_pid = process.self()
3737+ group_registry.join(registry, group_name, my_pid)
3838+}
3939+4040+/// Unsubscribe from stats events
4141+pub fn unsubscribe(subscriber: Subject(StatsEvent)) -> Nil {
4242+ let registry = get_registry()
4343+ case process.subject_owner(subscriber) {
4444+ Ok(my_pid) -> group_registry.leave(registry, group_name, [my_pid])
4545+ Error(_) -> Nil
4646+ }
4747+}
4848+4949+/// Publish a stats event to all subscribers
5050+pub fn publish(event: StatsEvent) -> Nil {
5151+ let registry = get_registry()
5252+ let subscribers = group_registry.members(registry, group_name)
5353+ list.each(subscribers, fn(sub) { process.send(sub, event) })
5454+}
+13
server/src/stats_pubsub_ffi.erl
···11+-module(stats_pubsub_ffi).
22+-export([get_registry_name/0]).
33+44+%% Get the registry name, creating it once and caching it
55+get_registry_name() ->
66+ case persistent_term:get({stats_pubsub, registry_name}, undefined) of
77+ undefined ->
88+ Name = gleam@erlang@process:new_name(<<"quickslice_stats_pubsub_registry">>),
99+ persistent_term:put({stats_pubsub, registry_name}, Name),
1010+ Name;
1111+ Name ->
1212+ Name
1313+ end.