I maintain the set of every loaded module, keyed by its name as
inspect/1 renders it ("GtBridge.Eval", ":erlang") and carrying its
module atom and owning application. I am populated initially from
:application.get_key/2 for every loaded application plus whatever
the VM has already loaded, 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' module listings, 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 known module across all applications.
I return every loaded module's dotted-name string.
I resolve the application a module belongs to, falling back where the
raw Application.get_application/1 returns nil.
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 return every loaded module's dotted name starting with prefix, in
sorted order, by walking my key range rather than filtering every name.
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. 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_modules() :: [module()]
I return every known module across all applications.
@spec all_names() :: [String.t()]
I return every loaded module's dotted-name string.
I resolve the application a module belongs to, falling back where the
raw Application.get_application/1 returns nil.
get_application/1 reads the app spec's module list, frozen at load,
so a live-recompiled new module comes back nil. app_from_beam/1
matches :code.which against ebin dirs, but a Code.compile_file'd
module reports its source path (or ""), never an ebin beam.
app_from_source/1 places a new file in a path dep by its source
location. Anything still unplaced (Erlang internals, eval-only
modules) falls back to the current application rather than nil, so it
surfaces under the project being worked on instead of vanishing.
This is what the GT-side Application button resolves through, so a brand-new module lands on its app instead of nowhere.
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.
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 return every loaded module's dotted name starting with prefix, in
sorted order, by walking my key range rather than filtering every name.
Erlang modules are stored as inspect/1 renders them, so ask for them
with a ":"-prefixed hint.
@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. 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.