Module: Phlex::Reactive
- Defined in:
- lib/phlex/reactive.rb,
lib/phlex/reactive/js.rb,
lib/phlex/reactive/mcp.rb,
lib/phlex/reactive/defer.rb,
lib/phlex/reactive/reply.rb,
lib/phlex/reactive/doctor.rb,
lib/phlex/reactive/engine.rb,
lib/phlex/reactive/stream.rb,
lib/phlex/reactive/version.rb,
lib/phlex/reactive/response.rb,
lib/phlex/reactive/component.rb,
lib/phlex/reactive/inspector.rb,
lib/phlex/reactive/mcp/runner.rb,
lib/phlex/reactive/mcp/server.rb,
lib/phlex/reactive/streamable.rb,
lib/phlex/reactive/param_schema.rb,
lib/phlex/reactive/test_helpers.rb,
lib/phlex/reactive/authorization.rb,
lib/phlex/reactive/component/dsl.rb,
lib/phlex/reactive/mcp/base_tool.rb,
lib/phlex/reactive/component/lazy.rb,
lib/phlex/reactive/log_subscriber.rb,
lib/phlex/reactive/client_bindings.rb,
lib/phlex/reactive/show_conditions.rb,
lib/phlex/reactive/inspector/report.rb,
lib/phlex/reactive/component/helpers.rb,
lib/phlex/reactive/component/identity.rb,
lib/phlex/reactive/component/registry.rb,
lib/phlex/reactive/deferred_render_job.rb,
lib/phlex/reactive/mcp/tools/find_tool.rb,
lib/phlex/reactive/mcp/tools/config_tool.rb,
lib/phlex/reactive/mcp/tools/doctor_tool.rb,
lib/phlex/reactive/test_helpers/matchers.rb,
lib/phlex/reactive/mcp/tools/actions_tool.rb,
lib/phlex/reactive/mcp/tools/components_tool.rb,
app/controllers/phlex/reactive/actions_controller.rb,
lib/generators/phlex/reactive/claude/claude_generator.rb,
lib/generators/phlex/reactive/install/install_generator.rb,
lib/generators/phlex/reactive/component/component_generator.rb
Overview
phlex-reactive: reactive Phlex components for Rails.
Two cooperating mixins, one client runtime, one endpoint:
* Phlex::Reactive::Streamable — gives a component a stable `id` and class
methods to render itself as a Turbo Stream (`.replace`, `.append`, ...)
and to broadcast itself (`.broadcast_replace_to`, ...). The server->client
half (controller responses + background broadcasts).
* Phlex::Reactive::Component — declares client-invokable `action`s and
emits a signed identity token + the wiring the generic `reactive`
Stimulus controller needs. The client->server half (clicks, form input).
Both halves converge on ONE re-render unit: the component, targeted by its
id. See the README for the mental model and examples.
Defined Under Namespace
Modules: Authorization, ClientBindings, Component, Defer, Generators, Inspector, MCP, ShowConditions, Streamable, TestHelpers Classes: ActionContext, ActionsController, AuthorizationNotVerified, DeferredRenderJob, Doctor, Engine, Error, InvalidToken, JS, LogSubscriber, ParamSchema, Reply, Response, Stream, UnknownParamType
Constant Summary collapse
- IDENTITY_PURPOSE =
Purpose string bound into every identity token's signature so a token minted for phlex-reactive can't be replayed against another verifier use.
"phlex-reactive/identity"- DEFER_PURPOSE =
Purpose string for DEFER tokens (issue #165) — the short-TTL identity a reply.defer directive carries so the client can fetch the expensive render off the actor's critical path. A distinct purpose makes the two token families non-interchangeable BY SIGNATURE: an action token is rejected at the defer endpoint (it must not become a render oracle) and a defer token can never invoke an action (it carries no action grant).
"phlex-reactive/defer"- DEFER_TRANSPORTS =
The defer_transport values (issue #165): :auto picks push (pgbus durable one-shot stream + ActiveJob) when capable, else pull (parallel fetch); :fetch forces pull; :stream requests push but still degrades to pull with a warning when the capability is absent (degrade, never break).
%i[auto fetch stream].freeze
- TOKEN_VERSION =
The current identity-token payload version (issue #111), stamped into every signed token under the "v" key. It exists so the NEXT breaking shape change (a rename, per-token expiry, a nonce) can upgrade tokens already in flight instead of breaking every open page at deploy. A token minted before this existed carries no "v" — treated as version 0 (today's shape). Bump this and register a register_token_upgrader(old_version) when you change the shape.
1- INSTRUMENTATION_NAMESPACE =
The ActiveSupport::Notifications namespace for the gem's hot-path events (issue #107): action.phlex_reactive, render.phlex_reactive, broadcast.phlex_reactive. APM tools (AppSignal, Datadog, Skylight) auto-subscribe to
*.phlex_reactiveand get component-level visibility. Payloads carry NAMES/outcome/sizes ONLY — never the token, params, or state. "phlex_reactive"- ACTIONS_CONTROLLER =
The controller a correctly-mounted action path resolves to. Used by the route guard below.
"phlex/reactive/actions"- VERSION =
"0.11.0"
Class Attribute Summary collapse
- .action_path ⇒ Object
-
.authorization_errors ⇒ Object
Exception classes the action endpoint renders as 403.
- .authorization_methods ⇒ Object
- .base_controller_name ⇒ Object
- .debug ⇒ Object
- .defer_job_queue ⇒ Object
- .defer_path ⇒ Object
- .defer_token_ttl ⇒ Object
-
.error_flash ⇒ Object
A user-visible flash rendered on every endpoint rescue path (issue #100).
-
.flash_component ⇒ Object
A CALLABLE that builds a flash component from STRING flash content (issue #182).
-
.flash_target ⇒ Object
DOM id of the host-app container a Response#flash appends into.
- .log_events ⇒ Object
- .renderer ⇒ Object
- .verbose_errors ⇒ Object
- .verifier ⇒ Object
- .verify_authorized ⇒ Object
Class Method Summary collapse
-
.action_route_ok?(path = action_path) ⇒ Boolean
True when a POST to
pathresolves to the gem's ActionsController. -
.around_action(&block) ⇒ Object
Register a COMPONENT-AWARE around_action wrapper (issue #112).
-
.around_actions ⇒ Object
The registered around_action wrappers, oldest first.
- .base_controller ⇒ Object
-
.broadcast_to(*streamables, morph: false, target: nil, exclude: nil, visible_to: nil, each: nil, **verb) ⇒ Object
Module-level broadcast (issue #185) — the same broadcast_to as the class form, for a BUILT component payload, so a NON-Streamable target (a count badge, any plain Phlex component) broadcasts WITHOUT hand-rolling the raw channel + render, and IS instrumented:.
-
.current_connection_id ⇒ Object
The acting client's SSE connection id during an action, or nil.
-
.current_defer_binding ⇒ Object
The acting client's defer binding during a request, or nil.
-
.defer_binding_for(request) ⇒ Object
Resolve a request to its defer binding: the id of an ALREADY-PERSISTED session, else nil.
-
.defer_purpose ⇒ Object
The purpose string for the CURRENT actor's defer tokens: the base DEFER_PURPOSE, plus the binding when one is present.
-
.defer_push_capable? ⇒ Boolean
Everything the defer PUSH lane needs at runtime: streams-capable pgbus, server-side signed-src minting (SignedName.sign — the element's src is built off-request), and ActiveJob to run the render off the request thread.
-
.defer_transport ⇒ Object
How deferred segments reach the actor: :auto (push iff capable, else pull), :fetch (always pull), :stream (push; degrades to pull with a warning when the capability is absent).
- .defer_transport=(value) ⇒ Object
-
.flash_builder ⇒ Object
Issue #182: the pre-#113 aliases are removed (they contradicted the clean-break rule — two names for one thing).
- .freeze_param_schemas! ⇒ Object
-
.freeze_param_types! ⇒ Object
Freeze the registry so no further param_type registration is accepted — the initializer-only contract.
-
.instrument(event, payload = {}) ⇒ Object
Emit an
<event>.phlex_reactiveActiveSupport::Notifications event around a block, yielding the mutable payload so a rescue can finalize the outcome (issue #107). -
.off_request_view_context ⇒ Object
The off-request view context for the current thread, built once and reused for both the stream builder and standalone component renders.
- .off_request_view_context_generation ⇒ Object
-
.param_schema(name, schema = nil) ⇒ Object
Register (with a schema Hash) OR read (bare name) a NAMED param schema (issue #184) — a reusable, boot-declared schema so sibling components stop duplicating verbatim constants that drift.
- .param_schema?(name) ⇒ Boolean
- .param_schemas ⇒ Object
- .param_schemas_frozen? ⇒ Boolean
-
.param_type(name, &block) ⇒ Object
Register an app-defined param type (issue #109).
-
.param_type?(name) ⇒ Boolean
A declared type symbol is known iff it's a registry key.
-
.param_types ⇒ Object
The param-type registry: Symbol => callable(value) -> coerced | DROP.
- .param_types_frozen? ⇒ Boolean
-
.pgbus? ⇒ Boolean
Necessary but NOT sufficient — the Streams entrypoint exists.
-
.pgbus_streams? ⇒ Boolean
The reactive-Streams capability: Stream#broadcast accepts :exclude (the pgbus >= 0.9.2 shape).
-
.register_token_upgrader(from_version, &block) ⇒ Object
Register an upgrader that rewrites a payload signed at
from_versioninto the shape offrom_version + 1(issue #111). -
.render(component) ⇒ Object
Render a Phlex component to HTML with a full (off-request) view context.
-
.request_bound_view_context(controller_class) ⇒ Object
Build an off-request view context whose controller has a REAL
request. -
.reset_around_actions! ⇒ Object
Drop all registered around_action wrappers.
- .reset_flash_builder! ⇒ Object
-
.reset_param_schemas! ⇒ Object
Drop the named-schema registry and unfreeze it (tests only).
-
.reset_param_types! ⇒ Object
Drop the registry back to the built-ins and unfreeze it.
-
.reset_stream_builder! ⇒ Object
Invalidate the per-thread context + builder for ALL threads by bumping the generation; each thread rebuilds lazily on next use.
-
.reset_token_upgraders! ⇒ Object
Drop all registered upgraders.
-
.sign(payload) ⇒ Object
Signs a payload hash into an identity token, stamping the current TOKEN_VERSION (issue #111).
-
.sign_defer(payload, unbound: false) ⇒ Object
Signs a defer payload (issue #165): same shape + version stamp as the identity token, but purpose-scoped to DEFER_PURPOSE and expiring after defer_token_ttl.
-
.stream_builder ⇒ Object
A Turbo::Streams::TagBuilder bound to an off-request view context, used to build standalone streams not tied to a specific component's id — a Response flash append, a reactive_collection row removal, a count companion update, an also() companion.
-
.token_upgraders ⇒ Object
from_version => callable(payload) -> migrated payload.
-
.upgrade_token(payload) ⇒ Object
Migrate a verified payload from whatever version it was signed at up to TOKEN_VERSION (issue #111).
-
.verify(token) ⇒ Object
Returns the verified, version-upgraded payload hash, or nil if the token is invalid (bad signature/purpose) OR carries a version this code doesn't understand.
-
.verify_defer(token) ⇒ Object
Returns the verified, version-upgraded defer payload, or nil when the token is tampered, expired, carries the wrong purpose (an action token OR a BOUND token minted under a DIFFERENT actor binding), or a version this code doesn't understand — all fail closed through the endpoint's InvalidToken → 400 path.
-
.warn_unless_action_route_mounted!(path: action_path, logger: default_logger) ⇒ Object
Log a clear warning (once, at boot) when the action path doesn't resolve to the gem controller — pointing at the catch-all shadow rather than leaving an adopter to guess.
- .with_connection_id(connection_id) ⇒ Object
- .with_defer_binding(binding) ⇒ Object
Class Attribute Details
.action_path ⇒ Object
142 143 144 |
# File 'lib/phlex/reactive.rb', line 142 def action_path @action_path ||= "/reactive/actions" end |
.authorization_errors ⇒ Object
Exception classes the action endpoint renders as 403. Append your authorization library's error (Pundit::NotAuthorizedError, ActionPolicy::Unauthorized, ...).
134 135 136 |
# File 'lib/phlex/reactive.rb', line 134 def @authorization_errors end |
.authorization_methods ⇒ Object
219 220 221 222 223 |
# File 'lib/phlex/reactive.rb', line 219 def return @authorization_methods if defined?(@authorization_methods) %i[authorize! authorize allowed_to?] end |
.base_controller_name ⇒ Object
775 776 777 |
# File 'lib/phlex/reactive.rb', line 775 def base_controller_name @base_controller_name ||= "ActionController::Base" end |
.debug ⇒ Object
188 189 190 191 192 |
# File 'lib/phlex/reactive.rb', line 188 def debug return @debug if defined?(@debug) false end |
.defer_job_queue ⇒ Object
487 488 489 |
# File 'lib/phlex/reactive.rb', line 487 def defer_job_queue @defer_job_queue ||= "default" end |
.defer_path ⇒ Object
457 458 459 |
# File 'lib/phlex/reactive.rb', line 457 def defer_path @defer_path ||= "/reactive/defer" end |
.defer_token_ttl ⇒ Object
449 450 451 |
# File 'lib/phlex/reactive.rb', line 449 def defer_token_ttl @defer_token_ttl ||= 120 end |
.error_flash ⇒ Object
A user-visible flash rendered on every endpoint rescue path (issue #100). Default nil = today's behavior (a bare head, or the verbose_errors plain-text diagnostic). Set a lambda ->(kind) { "message" } (kind is :tampered/:unknown_class/:not_reactive_class/:forbidden/:not_found) and the ActionsController ALSO renders a turbo-stream flash into flash_target — at the SAME status it returns today (statuses never change). Composes with verbose_errors: the turbo-stream flash wins the response body, the diagnostic still goes to the log.
637 638 639 |
# File 'lib/phlex/reactive.rb', line 637 def error_flash @error_flash end |
.flash_component ⇒ Object
A CALLABLE that builds a flash component from STRING flash content (issue #182). Given (level, content), it returns a Phlex component the gem renders in place of the built-in