turbo_cr
Version, currently master branch7 versions
github.com/OrelSokolov/turbo.cr
No description declared in shard.yml.
Installation
# Add this to your shard.yml
dependencies:
turbo_cr:
github: OrelSokolov/turbo.cr
branch: mastermaster is a branch, not a release, so this tracks it rather than pinning a version.
Then run:
shards installshard.yml
- Crystal
>= 1.21.0
Dependencies
Runtime Dependencies
- active_job_cr*path: ../active_job.cr
- action_pack_cr*path: ../action_pack.cr
- action_view_cr*path: ../action_view.cr
- action_cable_cr*path: ../action_cable.cr
- active_record_cr*path: ../active_record.cr
- active_support_cr*path: ../active_support.cr
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
turbo.cr
A 1:1 Crystal port of turbo-rails 2.0.20 (Hotwire Turbo Streams) with
the namespace Turbo, built on top of action_view.cr, action_pack.cr,
action_cable.cr, active_record.cr and active_job.cr. Covers the stream
tag builder (turbo_stream.append/prepend/...), cable broadcasting
(broadcast_*_to, declarative broadcasts/broadcasts_to on models),
_later jobs over ActiveJob, view helpers (turbo_stream_from,
turbo_frame_tag) and the text/vnd.turbo-stream.html HTTP format.
Client side is the official vendored @hotwired/turbo 8.0.13 UMD plus a
~60-line cable connector instead of the npm bundle. Suite: 60/60 oracle
cases recorded from the real gem + an E2E smoke of the demo app.
⚠️ EXPERIMENTAL FRAMEWORK, DO NOT USE IN PRODUCTION YET.
Installation
dependencies:
turbo_cr:
github: OrelSokolov/turbo.cr
shards install
Depends on active_support.cr, action_view.cr, action_pack.cr,
action_cable.cr, active_record.cr and active_job.cr.
Usage
# In a view or turbo_stream template:
turbo_stream.append "entries", "<div>hi</div>"
turbo_stream.remove "clearance_5"
turbo_stream.update "entries", partial: "entries/entry", locals: {"entry" => entry}
turbo_stream.refresh
turbo_frame_tag "cart", src: "/cart", loading: "lazy" { }
turbo_stream_from "room", "entries"
# From a model or controller:
Turbo::StreamsChannel.broadcast_prepend_to ["entries"],
target: "entries", content: "<div>new</div>"
class Message < ActiveRecord::Base
include Turbo::Broadcastable
broadcasts_to :board, inserts_by: :prepend, target: "board_messages"
end
Feature comparison vs turbo-rails 2.0.20
Streams tag builder & view helpers
| Feature | Original (turbo-rails) | Port (turbo.cr) | Status |
|---|---|---|---|
turbo_stream actions append/prepend/replace/update/remove/before/after (+ _all variants) | ✅ | Same 7 actions, block/no-block overloads | ✅ |
turbo_stream.refresh, custom action/action_all | ✅ | Same | ✅ |
Rendering semantics: positional content unescaped, block/html escaped, method: before action/target | ✅ | Same (locked by oracle) | ✅ |
turbo_frame_tag (+ block form, content_tag escaping) | ✅ | Same | ✅ |
turbo_stream_from (signed stream name, custom channel) | ✅ | Same (raise "streamables can't be blank" locked by oracle) | ✅ |
renderable: objects | ✅ | P2, not implemented | ❌ |
Broadcasting
| Feature | Original | Port | Status |
|---|---|---|---|
broadcast_*_to for all actions + broadcast_action_to, broadcast_stream_to, broadcast_render_to | ✅ | Same | ✅ |
broadcast_refresh_to (no attributes in 2.0.20) | ✅ | Same (oracle pins the no-attributes behavior) | ✅ |
Signed stream names (signed_stream_name/verified_stream_name, HEX digest) | ✅ | Same via ActiveSupport::MessageVerifier | ✅ |
Turbo::Broadcastable: broadcasts/broadcasts_to macros (after_commit create/update/destroy), instance broadcast_*, suppressing_turbo_broadcasts, GlobalID-lite | ✅ | Same | ✅ |
Proc streams in broadcasts_to | ✅ | P2, Symbol (model method) or String literal only | ⚠️ partial |
Debounce in broadcast_refreshes_with | ✅ | P1, not implemented | ❌ |
Jobs, channel & HTTP format
| Feature | Original | Port | Status |
|---|---|---|---|
Turbo::StreamsChannel subscription by signed stream name | ✅ | Same (rejects invalid signatures) | ✅ |
_later jobs: ActionBroadcastJob / BroadcastJob / BroadcastStreamJob | ✅ | Same via ActiveJob (payload-hash dispatch, ctors up to 4 args) | ✅ |
respond_to { format.turbo_stream }, default #{action}.turbo_stream.erb render without layout, CT text/vnd.turbo-stream.html; charset=utf-8 | ✅ | Same (in action_pack.cr), Accept negotiation + ?format=turbo_stream | ✅ |
redirect_to from a turbo_stream request → http fallback with CT text/html | ✅ | Same | ✅ |
| Client JS (npm bundle) | ✅ | Vendored @hotwired/turbo 8.0.13 UMD + ~60-line turbo_cable.js WS connector | ⚠️ partial |
Turbo::Frames frame_loading / enforce_frame_routes | ✅ | No routing-port analog | ❌ |
Deviations & Crystal-specific notes
- kwargs → named args:
turbo_stream.append target, "text"instead of**options. - Renderer seam instead of
ApplicationController.render: compiled languages can't render from a string at runtime — the app registersTurbo.partial_renderer/Turbo.template_rendererprocs over compile-time-compiled partials/templates. Seespec/oracle_spec.cr(TurboView) and demo (src/turbo_renderers.cr) for boilerplate. broadcastsmacro: pluralize is a runtime inflector, unavailable in macro-land — the stream name is computed at runtime.- Block overloads are typed (
&block : Proc(String)) because untyped blocks passed through as Proc return nil; locals are normalized viaTurbo.cast_locals(generics invariance); ActiveJob argument hashes are strictlyHash(String | Symbol, ArgValue). - Declarative macros live in the
Broadcastablemodule body (plain methods in ClassMethods viamacro included) becauseextendinsidemacro includeddoesn't hoist macros.
Testing
make spec # = CRAILS_VIEWS=spec/fixtures/views crystal spec
Requires a local PostgreSQL (default
postgres://root@/crails_turbo_spec?host=/var/run/postgresql,
override with TURBO_SPEC_URL); Redis is not needed (in-process Test
subscription adapter). Oracle: 60 cases recorded from the real
turbo-rails 2.0.20 + rails 8.1.4 (DB crails_turbo_oracle, TZ=UTC) in
oracle/cases.rb → oracle/expected.yml; the Crystal mirror
spec/oracle_spec.cr replays them 1:1 without edits (expected raise
cases included). E2E smoke spec/e2e_smoke.cr additionally needs the
demo app running: demo/bin/app serve -p 3001.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This branch
- Branch
master- Seen
- Oct 12, 2026
- Crystal
>= 1.21.0- Indexed
- yes
Dependents
Repository
github.com/OrelSokolov/turbo.cr
Metadata
- Created
- Oct 14, 2026
- Updated
- Oct 24, 2026
- Synced
- Oct 24, 2026
- Versions
- 7