GtBridge.Analysis.LoadedModules (gt_bridge v0.19.2)

Copy Markdown View Source

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

all_modules()

@spec all_modules() :: [module()]

I return every known module across all applications.

all_names()

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

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

app_of(mod)

@spec app_of(module()) :: atom() | nil

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.

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.

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.

names_with_prefix(prefix)

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

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.

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