Module: Phlex::Reactive::Defer

Defined in:
lib/phlex/reactive/defer.rb

Overview

Deferred reply segments (issue #165): the machinery behind reply.defer — "placeholder now, the real render to the ACTOR when ready".

A Response only RECORDS segments (see Response#defer); this module turns them into wire streams at the ENDPOINT, after the action's transaction committed — so a rolled-back action can never leak a directive — and picks the delivery lane:

* pull (:fetch)  — the directive carries a purpose-scoped, short-TTL
defer token; the client POSTs it to the defer endpoint OFF the action
queue and applies the rendered stream when it lands. Works on every
transport (it's just HTTP) — this is the universal lane.
* push (:stream) — pgbus durable one-shot stream + ActiveJob: the reply
carries a signed SSE src; a job renders and broadcasts durably, and
the since-id=0 replay closes the broadcast-before-subscribe race.
Capability-gated (Phlex::Reactive.defer_push_capable?); anything
missing degrades to pull.

Defined Under Namespace

Classes: Segment

Constant Summary collapse

DIRECTIVE_ACTION =

The custom turbo-stream action the client's defer handler registers.

"reactive:defer"
DEFER_KEY_MARKER =

The stable marker every one-shot push-lane stream key starts with — how pgbus's orphan sweep and an operator recognize a phlex-reactive defer queue (issue #165).

"prdefer_"
DEFER_KEY_MIN_SUFFIX =

Minimum random hex chars on the key (64 bits) — collision-safe.

16
PGBUS_MAX_QUEUE_NAME =

pgbus's MAX_QUEUE_NAME_LENGTH (its QueueNameValidator); the live queue_prefix is subtracted from it to get the usable budget.

47
DEFAULT_PGBUS_QUEUE_PREFIX =

pgbus's default queue_prefix, used when the live one can't be read.

"pgbus"
REAL_RENDER_KEY =

Thread/fiber-local flag marking a REAL reactive-machinery render — inside it a reactive_lazy component renders its actual template instead of the placeholder shell. Set by Streamable.render_component (replies, broadcasts, the defer endpoint/job, the class stream builders) and Phlex::Reactive.render; a page-embedded initial mount never passes through either, so it gets the shell.

:phlex_reactive_defer_real_render

Class Method Summary collapse

Class Method Details

.real_render?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'lib/phlex/reactive/defer.rb', line 54

def real_render?
  Thread.current[REAL_RENDER_KEY] ? true : false
end

.resolve_viaObject

Which lane deferred segments take, resolved PER REPLY (capability is probed live — cheap: without pgbus it's one defined? short-circuit). :fetch forces pull; :stream requests push and DEGRADES to pull with a one-time warning when the capability is absent (never break); :auto picks push iff fully capable.



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

def resolve_via
  case Phlex::Reactive.defer_transport
  when :fetch then :fetch
  when :stream
    return :stream if Phlex::Reactive.defer_push_capable?

    warn_stream_degraded
    :fetch
  else
    Phlex::Reactive.defer_push_capable? ? :stream : :fetch
  end
end

.streams_for(segment, via: resolve_via) ⇒ Object

The wire streams for ONE segment on the given lane, in apply order: the optional placeholder shell FIRST (so the pending state paints before the directive kicks off delivery), then the directive.



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

def streams_for(segment, via: resolve_via)
  streams = []
  shell = placeholder_stream(segment)
  streams << shell if shell
  streams << directive_stream(segment, via)
  streams
end

.validate_segment!(component, placeholder) ⇒ Object

Validate reply.defer arguments LOUDLY at the call site — a dead segment (a component the endpoint could never rebuild, a bogus placeholder) must fail in the action that wrote it, not silently at reply render or, worse, at fetch time.

Raises:

  • (::ArgumentError)


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/phlex/reactive/defer.rb', line 99

def validate_segment!(component, placeholder)
  unless component.class.include?(Phlex::Reactive::Component)
    raise ::ArgumentError,
      "reply.defer expects a Phlex::Reactive::Component instance (its identity is what the " \
      "deferred render is rebuilt from) — got #{component.class}"
  end

  return if placeholder.nil? || placeholder == true ||
            placeholder.is_a?(::String) || placeholder.is_a?(::Phlex::SGML)

  raise ::ArgumentError,
    "reply.defer placeholder: must be nil (keep current content), true (the component's " \
    "deferred_placeholder or the built-in shell), a String, or a Phlex component — " \
    "got #{placeholder.class}"
end

.with_real_renderObject



58
59
60
61
62
63
64
# File 'lib/phlex/reactive/defer.rb', line 58

def with_real_render
  previous = Thread.current[REAL_RENDER_KEY]
  Thread.current[REAL_RENDER_KEY] = true
  yield
ensure
  Thread.current[REAL_RENDER_KEY] = previous
end