Skip to content

feat(streams): per-Turbo-helper durable opt-in#154

Merged
mhenrixon merged 3 commits into
mainfrom
feature/153-per-turbo-helper-durable-opt-in
May 10, 2026
Merged

feat(streams): per-Turbo-helper durable opt-in#154
mhenrixon merged 3 commits into
mainfrom
feature/153-per-turbo-helper-durable-opt-in

Conversation

@mhenrixon
Copy link
Copy Markdown
Owner

@mhenrixon mhenrixon commented May 8, 2026

Summary

  • Adds durable: kwarg to all Turbo broadcast helpers (broadcast_replace_to, broadcast_append_to, etc.) and their _later_to variants
  • Adds durable: option to class-level declarations (broadcasts_to :account, durable: true, broadcasts_refreshes_to :board, durable: true)
  • Uses thread-local (Thread.current[:pgbus_broadcast_durable]) to pass the override through the existing broadcast_stream_to funnel — minimal patching surface, no turbo-rails internal signatures modified
  • Falls back to existing resolver (streams_durable_patternsstreams_default_broadcast_mode) when durable: is omitted — fully backwards compatible

New files

  • lib/pgbus/streams/broadcastable_override.rb — prepended onto Turbo::Broadcastable
  • spec/pgbus/streams/broadcastable_override_spec.rb — 16 examples covering instance-level durable: forwarding
  • spec/pgbus/streams/broadcastable_class_methods_spec.rb — 6 examples covering broadcasts_to/broadcasts_refreshes_to class methods

Modified files

  • lib/pgbus/streams/turbo_broadcastable.rb — reads thread-local before falling back to config
  • lib/pgbus/engine.rb — installs BroadcastableOverride patch at boot
  • spec/pgbus/streams/turbo_broadcastable_spec.rb — 3 new examples for thread-local integration

Closes #153

Test plan

  • broadcast_replace_to(record, durable: true) forwards durable: true to Pgbus.stream
  • broadcast_replace_to(record, durable: false) forwards durable: false
  • Omitting durable: falls back to streams_default_broadcast_mode config
  • All 11 broadcast helpers forward durable: correctly
  • Both broadcast_action_to and broadcast_action_later_to forward durable:
  • broadcasts_to :account, durable: true sets durable on create/update/destroy callbacks
  • broadcasts_refreshes_to :board, durable: true sets durable on commit callback
  • Thread-local is cleaned up after broadcast, even on error
  • Thread-local false overrides durable config mode
  • Patch installation is idempotent
  • All 1986 non-system specs pass (0 failures)
  • Rubocop clean on all changed files

Summary by CodeRabbit

  • New Features

    • Broadcast helpers and stream-declaration APIs accept a durable: option for per-stream delivery mode.
    • Runtime now conditionally installs the durability behavior only when the streaming system is present.
    • A thread-local override lets code temporarily force durable/non-durable delivery for a single broadcast.
  • Tests

    • Added specs covering durability propagation, default behavior, thread-local cleanup, error handling, and idempotent installation.

Add `durable:` kwarg support to all Turbo broadcast helpers so apps can
control durable mode at the call site instead of relying solely on
global config (`streams_durable_patterns` / `streams_default_broadcast_mode`).

Approach: a thin `BroadcastableOverride` module prepended onto
`Turbo::Broadcastable` extracts `durable:` from kwargs and sets a
thread-local that `TurboBroadcastable#broadcast_stream_to` reads.
The thread-local is always cleaned up after the broadcast, even on error.

Supported APIs:
- `broadcasts_to :account, durable: true` (class-level declarative)
- `broadcasts_refreshes_to :board, durable: true`
- `broadcast_replace_to(record, durable: true)` (instance-level direct)
- All other broadcast helpers (append, prepend, update, remove, action,
  refresh, and their _later_to variants)

When `durable:` is omitted, resolution falls back to the existing
chain: streams_durable_patterns → streams_default_broadcast_mode.

Closes #153
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7c8987ce-da69-4dab-9446-117431b821b7

📥 Commits

Reviewing files that changed from the base of the PR and between e1f0899 and cad11e9.

📒 Files selected for processing (2)
  • lib/pgbus/streams/broadcastable_override.rb
  • spec/pgbus/streams/broadcastable_override_spec.rb

📝 Walkthrough

Walkthrough

Adds a BroadcastableOverride that accepts durable: on Turbo broadcast helpers, propagates durable via a thread-local into Pgbus.stream, provides class-level durable: declarations, updates turbo broadcast resolution to respect the thread-local, conditionally installs the override in the engine, and adds specs.

Changes

Durable broadcast propagation

Layer / File(s) Summary
Thread-local context foundation
lib/pgbus/streams/broadcastable_override.rb
Implements with_pgbus_durable that sets and restores Thread.current[:pgbus_broadcast_durable] around a block.
Instance-level broadcast overrides
lib/pgbus/streams/broadcastable_override.rb
Prepends instance methods for synchronous Turbo broadcast helpers that extract durable: from kwargs and call super inside with_pgbus_durable.
Class-level durable configuration
lib/pgbus/streams/broadcastable_override.rb
Adds broadcasts_to and broadcasts_refreshes_to accepting durable:; stores per-stream durable values and registers callbacks that call synchronous helpers with durable passed through; exposes pgbus_durable_streams.
Installation and integration
lib/pgbus/streams/broadcastable_override.rb, lib/pgbus/streams/turbo_broadcastable.rb, lib/pgbus/engine.rb
Adds install! to prepend overrides into Turbo modules, updates broadcast_stream_to to prefer the thread-local durable override over config when present, and conditionally installs the override in the engine when ::Turbo::Broadcastable is defined.
Turbo broadcast_stream_to tests
spec/pgbus/streams/turbo_broadcastable_spec.rb
Verifies broadcast_stream_to uses thread-local override when set, falls back to config when nil, and always clears the thread-local in ensure.
Class configuration tests
spec/pgbus/streams/broadcastable_class_methods_spec.rb
Tests broadcasts_to and broadcasts_refreshes_to register callbacks that call Pgbus.stream with the declared durable: flag or with the default mode when omitted.
Instance broadcast tests
spec/pgbus/streams/broadcastable_override_spec.rb
Validates instance-level broadcast helpers forward durable: to Pgbus.stream across APIs, ensures thread-local cleanup on success/error, and checks .install! idempotency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • mhenrixon/pgbus#152: Per-broadcast and per-stream durable support that this PR integrates into Turbo helpers.
  • mhenrixon/pgbus#151: Foundation for durable/ephemeral broadcast mode and earlier TurboBroadcastable changes.
  • mhenrixon/pgbus#76: Prior Turbo broadcast integration pattern and engine install behavior.

Suggested labels

streaming

Poem

🐰
I tuck a durable flag into thread-local soil,
So broadcasts hop true without extra toil.
Classes remember which streams should stay,
Instance calls whisper the mode on their way.
Hooray — messages land steady, neat, and royal!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(streams): per-Turbo-helper durable opt-in' directly and concisely describes the main change: adding durable opt-in capability to Turbo broadcast helpers.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from #153: class-level durable opt-in (broadcasts_to with durable:), per-call durable forwarding (broadcast_replace_to with durable:), fallback behavior preservation, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue objectives: BroadcastableOverride implements the durable feature, TurboBroadcastable reads thread-local overrides, Engine installs the patch, and specs validate the functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/153-per-turbo-helper-durable-opt-in

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/pgbus/streams/broadcastable_override.rb`:
- Around line 18-35: BROADCAST_METHODS is missing Turbo instance methods so
calls like broadcast_after_to, broadcast_before_to, broadcast_render_to, and
broadcast_render_later_to bypass the override and can't accept the durable:
kwarg; update the BROADCAST_METHODS constant to include these four symbols
(broadcast_after_to, broadcast_before_to, broadcast_render_to,
broadcast_render_later_to) alongside the existing entries so the override logic
in this file applies to them as well.
- Around line 104-109: The install! method in Turbo::Broadcastable uses
mod.singleton_class.prepend(ClassMethods) for Module cases, which doesn't
replace the ClassMethods that get applied when other modules/classes include
this module; change that call to mod.extend(ClassMethods) so the patched
ClassMethods are applied to any module or class that includes or extends
Turbo::Broadcastable (update the install! method that references mod,
ClassMethods, and singleton_class.prepend to use mod.extend(ClassMethods)
instead).

In `@lib/pgbus/streams/turbo_broadcastable.rb`:
- Around line 39-45: The thread-local override
Thread.current[:pgbus_broadcast_durable] is lost when turbo enqueues a
background job, so persist the caller's durable intent into the job payload and
read it when the job executes: change places that currently read
Thread.current[:pgbus_broadcast_durable] (e.g., in broadcast_action_to /
broadcast_stream_to and the Pgbus wrapper around Pgbus.stream(name, durable:
durable).broadcast) to capture the resolved durable boolean and pass it as an
explicit argument into the enqueued Turbo::Streams::ActionBroadcastJob (or
include it in the serialized broadcast payload), and update the job runner to
extract that durable flag and call Pgbus.stream(..., durable: durable).broadcast
using that explicit flag instead of re-reading Thread.current.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25196b85-aee9-4972-b4b4-b930c2c5ae86

📥 Commits

Reviewing files that changed from the base of the PR and between c0fedfa and eebd99d.

📒 Files selected for processing (6)
  • lib/pgbus/engine.rb
  • lib/pgbus/streams/broadcastable_override.rb
  • lib/pgbus/streams/turbo_broadcastable.rb
  • spec/pgbus/streams/broadcastable_class_methods_spec.rb
  • spec/pgbus/streams/broadcastable_override_spec.rb
  • spec/pgbus/streams/turbo_broadcastable_spec.rb

Comment thread lib/pgbus/streams/broadcastable_override.rb
Comment thread lib/pgbus/streams/broadcastable_override.rb Outdated
Comment thread lib/pgbus/streams/turbo_broadcastable.rb
@mhenrixon mhenrixon self-assigned this May 8, 2026
- Add missing broadcast methods (broadcast_after_to, broadcast_before_to,
  broadcast_render_to, broadcast_render_later_to) to BROADCAST_METHODS
- Remove _later_to async variants from override list since thread-local
  cannot survive into background job execution context
- Use synchronous broadcast calls in broadcasts_to durable: callbacks
  (pgbus handles transactional deferral natively via after_commit)
- Fix ClassMethods installation: prepend onto Turbo::Broadcastable::ClassMethods
  instead of singleton_class.prepend, so ActiveSupport::Concern includers
  pick up the overridden broadcasts_to/broadcasts_refreshes_to
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/pgbus/streams/broadcastable_override.rb`:
- Around line 23-34: BROADCAST_METHODS currently includes
broadcast_render_later_to which contradicts the file docs and causes
with_pgbus_durable wrapping to be applied only at enqueue time; remove
broadcast_render_later_to from the BROADCAST_METHODS array so it is not wrapped
by with_pgbus_durable (leave all other methods intact), and ensure any
references to with_pgbus_durable or tests that stub broadcast_render_later_to
are updated to treat the _later_to variant as a background-job enqueue (i.e., do
not expect durable wrapping to be effective at perform time); this targets the
BROADCAST_METHODS constant and the with_pgbus_durable wrapper logic around those
methods and prevents incorrect thread-local assumptions when
Turbo::Streams::BroadcastJob#perform runs.

In `@spec/pgbus/streams/broadcastable_class_methods_spec.rb`:
- Around line 168-175: The specs are not exercising the real
Turbo::Broadcastable::ClassMethods install path because they extend the test
class with described_class directly; instead, stub a nested
Turbo::Broadcastable::ClassMethods module on Turbo::Broadcastable, call
Pgbus::Streams::BroadcastableOverride.install!(Turbo::Broadcastable) to let it
prepend into Turbo::Broadcastable::ClassMethods, and then include
Turbo::Broadcastable into the test class (rather than
klass.extend(described_class)); apply the same change to the other occurrence
around lines 272-278 so the spec fails if install! stops prepending onto
Turbo::Broadcastable::ClassMethods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: fa19d65d-e674-4341-9c05-daeb789cdba5

📥 Commits

Reviewing files that changed from the base of the PR and between eebd99d and e1f0899.

📒 Files selected for processing (3)
  • lib/pgbus/streams/broadcastable_override.rb
  • spec/pgbus/streams/broadcastable_class_methods_spec.rb
  • spec/pgbus/streams/broadcastable_override_spec.rb

Comment thread lib/pgbus/streams/broadcastable_override.rb
Comment thread spec/pgbus/streams/broadcastable_class_methods_spec.rb
It's a _later_to variant that enqueues BroadcastJob — the thread-local
cannot survive into the job execution context.
@mhenrixon mhenrixon merged commit d6c7e42 into main May 10, 2026
9 checks passed
@mhenrixon mhenrixon deleted the feature/153-per-turbo-helper-durable-opt-in branch May 10, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-Turbo-helper durable opt-in (broadcasts_to :foo, durable: true)

1 participant