I maintain the set of every loaded Elixir module, keyed by its dotted
name string (e.g. "GtBridge.Eval") and carrying its module atom and
owning application. I am populated initially from
:application.get_key/2 for every loaded application and maintained
additively by EventBroker %ModuleEvent{} events.
This is the FRP shape the bridge is moving toward: derived state (the
"modules currently loaded" projection) maintained by the
infrastructure (events) instead of recomputed by every consumer.
:application.get_key/2 is a static snapshot frozen at app load, so a
module born from a live recompile (a new file, or a nested module from
typedstruct module:) never appears in it — but it does arrive here as
a :recompiled fact. That is why module enumeration
(Analysis.all_module_names/0, the private modules/1, and through
them the spotter and the module browser) reads me rather than
get_key directly.
Public API
loaded?/1— true when the named module is in my set (O(1))all_names/0— every loaded module's dotted-name stringmodules_for_app/1— the module atoms belonging toappsync/0— enter modules loaded outside the bridge's pipelinefull_sync/0— sync both directions, retracting vanished modules
Summary
Functions
I return every loaded module's dotted-name string.
Returns a specification to start this module under a supervisor.
I reconcile in both directions: enter appearances like sync/0, and
retract modules that no longer exist (not loaded, not in any loaded
app's spec, not loadable), broadcasting :source_removed for each.
Absence from :code.all_loaded/0 alone is ambiguous (the BEAM loads
lazily), so retraction cross-checks the app specs and the code
server too - heavier than sync/0, so I run at bridge connection,
not per eval.
I am true when name (a string of the dotted Elixir module name,
e.g. "GtBridge.Eval") is currently loaded in the BEAM.
I return the module atoms belonging to app.
I diff the BEAM's loaded modules against my set and broadcast a
:recompiled fact for each one that arrived outside the bridge's
pipeline (iex defmodule, dynamic codegen, eval snippets). Other
projections hear the facts through their normal subscriptions; to
every consumer a discovered module is indistinguishable from a
recompiled one. Returns the newly entered module names.
I enter facts without blocking the caller: sync_async/0 after
every eval and full_sync_async/0 at bridge connection. Entering
facts is fire-and-forget; a blocking call here serializes every
eval through me and lets a busy queue kill the SSE stream's init
on its call timeout.
Functions
@spec all_names() :: [String.t()]
I return every loaded module's dotted-name string.
Returns a specification to start this module under a supervisor.
See Supervisor.
I reconcile in both directions: enter appearances like sync/0, and
retract modules that no longer exist (not loaded, not in any loaded
app's spec, not loadable), broadcasting :source_removed for each.
Absence from :code.all_loaded/0 alone is ambiguous (the BEAM loads
lazily), so retraction cross-checks the app specs and the code
server too - heavier than sync/0, so I run at bridge connection,
not per eval.
@spec full_sync_async() :: :ok
I am true when name (a string of the dotted Elixir module name,
e.g. "GtBridge.Eval") is currently loaded in the BEAM.
I return the module atoms belonging to app.
@spec start_link(term()) :: GenServer.on_start()
@spec sync() :: [String.t()]
I diff the BEAM's loaded modules against my set and broadcast a
:recompiled fact for each one that arrived outside the bridge's
pipeline (iex defmodule, dynamic codegen, eval snippets). Other
projections hear the facts through their normal subscriptions; to
every consumer a discovered module is indistinguishable from a
recompiled one. Returns the newly entered module names.
@spec sync_async() :: :ok
I enter facts without blocking the caller: sync_async/0 after
every eval and full_sync_async/0 at bridge connection. Entering
facts is fire-and-forget; a blocking call here serializes every
eval through me and lets a busy queue kill the SSE stream's init
on its call timeout.