GtBridge.Analysis.LoadedModules (gt_bridge v0.18.0)

Copy Markdown View Source

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 string
  • modules_for_app/1 — the module atoms belonging to app
  • sync/0 — enter modules loaded outside the bridge's pipeline
  • full_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

all_names()

@spec all_names() :: [String.t()]

I return every loaded module's dotted-name string.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

full_sync()

@spec full_sync() :: %{added: [String.t()], removed: [String.t()]}

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.

full_sync_async()

@spec full_sync_async() :: :ok

loaded?(name)

@spec loaded?(String.t()) :: boolean()

I am true when name (a string of the dotted Elixir module name, e.g. "GtBridge.Eval") is currently loaded in the BEAM.

modules_for_app(app)

@spec modules_for_app(atom()) :: [module()]

I return the module atoms belonging to app.

start_link(_)

@spec start_link(term()) :: GenServer.on_start()

sync()

@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.

sync_async()

@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.