Module: Phlex::Reactive::Inspector

Defined in:
lib/phlex/reactive/inspector.rb,
lib/phlex/reactive/inspector/report.rb

Overview

The ONE read-only introspection layer (issue #168), consumed by the rake tasks (phlex_reactive:actions / find), the MCP tools, and Doctor. It discovers every constant-backed reactive component from the loaded Streamable registry and, per declared action, its param schema, source location, full method-definition source (extracted with Prism), and a heuristic authorization status.

It reports NAMES, PATHS, and declared SCHEMAS only — never tokens, secrets, or runtime state (the instrumentation privacy contract extended to tooling). Like Doctor, it is READ-ONLY: it never mounts a component, mutates state, or touches the default-deny boundary.

Phlex::Reactive::Inspector.components   # => [ComponentInfo]
Phlex::Reactive::Inspector.find(query)  # => ranked [ComponentInfo]

Defined Under Namespace

Modules: Report Classes: ActionInfo, ComponentInfo

Constant Summary collapse

DEFAULT_AUTHORIZATION_METHODS =

The authorization method names the heuristic looks for. Reads Phlex::Reactive.authorization_methods when configured (issue #168 phase 2); otherwise the common set (Pundit/CanCanCan/ActionPolicy-style). The manual escape hatch mark_authorized! always counts.

%i[authorize! authorize allowed_to?].freeze
MANUAL_AUTHORIZATION_MARK =
:mark_authorized!

Class Method Summary collapse

Class Method Details

.authorization_call?(definition) ⇒ Boolean

Does the method-definition source definition call an authorization method or mark_authorized! anywhere? A Prism scan for a call node whose method name is in the configured set. Heuristic; degrades to false on an unparseable snippet.

Returns:

  • (Boolean)


104
105
106
107
108
109
110
111
112
# File 'lib/phlex/reactive/inspector.rb', line 104

def authorization_call?(definition)
  result = Prism.parse(definition)
  return false unless result.success?

  names = authorization_method_names
  call_names(result.value).any? { names.include?(it) }
rescue StandardError
  false
end

.authorization_method_namesObject

The configured authorization method names plus the manual mark, as a Set of symbols. Reads Phlex::Reactive.authorization_methods when it exists (phase 2), else the default set. Guarded: a misconfigured authorization_methods (nil, or a single symbol) is coerced through Array() rather than raising .map on nil — which the caller's rescue would swallow, silently disabling the heuristic instead of surfacing it.



120
121
122
123
124
125
126
127
128
# File 'lib/phlex/reactive/inspector.rb', line 120

def authorization_method_names
  configured =
    if Phlex::Reactive.respond_to?(:authorization_methods)
      Phlex::Reactive.authorization_methods || DEFAULT_AUTHORIZATION_METHODS
    else
      DEFAULT_AUTHORIZATION_METHODS
    end
  (Array(configured).map(&:to_sym) + [MANUAL_AUTHORIZATION_MARK]).to_set
end

.componentsObject

Every constant-backed reactive component, as [ComponentInfo]. Call Rails.application.eager_load! first (the caller / rake task does) so every app component is in the Streamable registry.



64
65
66
# File 'lib/phlex/reactive/inspector.rb', line 64

def components
  reactive_component_classes.map { component_info(it) }
end

.constant_backed_component?(klass) ⇒ Boolean

True when a component class is a real, CONSTANT-RESOLVABLE reactive component — its own name round-trips through safe_constantize (exactly how ActionsController#resolve_component rebuilds it from the token). This also excludes anonymous classes (name nil) and fixtures that fake def self.name without a matching constant. Moved here from Doctor (issue #168); Doctor delegates.

Returns:

  • (Boolean)


88
89
90
91
92
# File 'lib/phlex/reactive/inspector.rb', line 88

def constant_backed_component?(klass)
  reactive_component?(klass) && klass.name && klass.name.safe_constantize.equal?(klass)
rescue StandardError
  false
end

.find(query) ⇒ Object

Ranked fuzzy matches for query (issue #168). Scored on BOTH the demodulized and the full namespaced name, best of the two: exact > prefix > substring > subsequence, case-insensitive. Returns [] on no match. No dependency — a ~1-screen scorer.



72
73
74
75
76
77
78
79
80
# File 'lib/phlex/reactive/inspector.rb', line 72

def find(query)
  q = query.to_s.downcase
  return [] if q.empty?

  reactive_component_classes
    .filter_map { score_component(it, q) }
    .sort_by { |score, name, _klass| [-score, name] }
    .map { |_score, _name, klass| component_info(klass) }
end

.reactive_component?(klass) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
97
98
# File 'lib/phlex/reactive/inspector.rb', line 94

def reactive_component?(klass)
  klass.respond_to?(:reactive_actions) && klass.include?(Phlex::Reactive::Component)
rescue StandardError
  false
end