action_cable_cr

Version, currently 0.1.15 versions

github.com/OrelSokolov/action_cable.cr

No description declared in shard.yml.

0 stars
2 dependents
License: not declared

Nothing has been indexed for 0.1.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  action_cable_cr:
    github: OrelSokolov/action_cable.cr
    version: ~> 0.1.1

Then run:

shards install

shard.yml

No shard.yml has been indexed for 0.1.1. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

action_cable.cr

A 1:1 Crystal port of actioncable 8.1.4 (Rails' Action Cable) under the same ActionCable namespace. WebSocket layer is fiber-per-connection on top of the Crystal stdlib HTTP::WebSocket (no nio4r + thread pool), speaking the exact Rails JSON protocol (welcome / subscription_confirmation / ping / disconnect, commands subscribe/message/unsubscribe) — the official @rails/actioncable JS client works unchanged. Pubsub adapters: Async (in-process, default), PostgreSQL LISTEN/NOTIFY, Redis pubsub. The hot path carries a Bun/uWS-style fast broadcast (encode-once envelopes, per-connection cork buffers, 1 write per batch, 1 MiB backpressure cap): 2.72M msg/s fan-out vs 2.52M for bun-publish. Suite: 51/51, including 33 oracle cases recorded with the real actioncable 8.1.4.

⚠️ EXPERIMENTAL FRAMEWORK, DO NOT USE IN PRODUCTION YET.

Installation

dependencies:
  action_cable_cr:
    github: OrelSokolov/action_cable.cr
shards install

Depends on active_support.cr, active_record.cr, action_pack.cr and the pg / redis shards.

Usage

require "action_cable_cr"

class ActionCable::Connection
  identified_by :current_user

  def connect
    reject_unauthorized_connection! unless session["warden.user.user.key"]?
    self.current_user = session["user_gid"]
  end
end

class ChatChannel < ActionCable::Channel::Base
  def subscribed
    stream_from "chat_#{params["room"]}"
  end

  def speak(data)                       # public method = client RPC action
    ActionCable.server.broadcast("chat_#{params["room"]}", data)
  end

  periodically :transmit_presence, every: 30
end

server = ActionCable::Server.new                # Async adapter by default
HTTP::Server.new([server.ws_handler]).listen(3000)  # ws://localhost:3000/cable

Cross-process setups swap the adapter (SubscriptionAdapter::Redis.new(url) or SubscriptionAdapter::PostgreSQL.new(url)); remote disconnect:

ActionCable.server.remote_connections.where(current_user: "1").disconnect(reconnect: true)

Feature comparison vs actioncable 8.1.4

Connection & protocol

FeatureOriginal (actioncable)Port (action_cable.cr)Status
Rails JSON wire protocol, @rails/actioncable JS client compat✅Same protocol end-to-end (verified by WS integration spec)✅
identified_by / connection_identifier (sorted gid join)✅Macro registry; String values only⚠️ partial
connect / disconnect hooks, reject_unauthorized_connection (unauthorized disconnect frame)✅Same✅
Cookie auth (cookies) + verified signed session in connect✅request_cookies + Phase-C signed session (via action_pack.cr)✅
before_command / after_command / around_command✅Not implemented (P2)❌
Remote disconnect (RemoteConnections.where(...).disconnect)✅Same internal-channel design, cross-process on Redis/PG✅

Channel DSL

FeatureOriginalPort (action_cable.cr)Status
subscribed / unsubscribed, params, reject, transmit✅Same✅
perform_action dispatch to public methods✅Compile-time macro dispatch table✅
stream_from (with/without callback block), stop_stream_from, stop_all_streams✅Same; coder: option missing⚠️ partial
stream_for / stop_stream_for / stream_or_reject_for✅Same, GlobalID-style gid naming✅
broadcast_to / broadcasting_for✅Class & instance level✅
periodically (PeriodicTimers)✅Method name or block, every: span/seconds✅
before_subscribe/after_subscribe/before_unsubscribe/after_unsubscribe hooks✅Used internally (periodic timers) only, not user-facing❌

Server, pubsub & performance

FeatureOriginalPort (action_cable.cr)Status
Mount at /cable, ActionCable.server.broadcast✅Server#ws_handler HTTP handler✅
Pubsub adaptersasync, PostgreSQL, Redis, (Solid Cable in Rails)Async, PostgreSQL (LISTEN/NOTIFY), Redis✅
config/cable.yml, channel_prefix, allowed_request_origins, worker_pool_size, log_tags✅Not implemented❌
Fast broadcast (encode-once, cork buffers, backpressure)n/a2.72M msg/s ≈ bun-publish✅
Server stats, health-check endpoint✅Not implemented (P2)❌
Buffered WS reads, JSON fast-path on hot pathn/aBacklog (P1.6)❌

Deviations & Crystal-specific notes

  • transmit is public (private in Ruby; Crystal has no send); streams became public stream_names for testing.
  • unsubscribe(channel, callback) → unsubscribe(channel): Crystal Procs have no identity, so it drops in-process callbacks but not the underlying Redis subscription.
  • Identifier values are Strings only (gid strings); params is Hash(String, String) instead of HashWithIndifferentAccess; INTERNAL is named constants instead of a heterogeneous hash.
  • periodically is a macro DSL (a Proc is unspellable in a class body); every: accepts Time::Span or seconds, ArgumentError on ≤ 0.
  • The gid app name comes from ActionCable.gid_app (ENV CRAILS_APP_NAME, default "app"), not GlobalID.app.
  • broadcast returns nil, not a PUBLISH recipient count; PG NOTIFY payloads are capped at ~8000 bytes.

Full list with rationale: PLAN.md (Russian).

Testing

crystal spec          # stream_for/pubsub suites need a local PostgreSQL
                      # (db crails_ac_test) and a local Redis, else they fail

Oracle: 33 cases (20 channel/protocol + 5 pubsub + 8 stream_for) recorded with the real actioncable 8.1.4 (oracle/cases.rb, oracle/pubsub_cases.rb, oracle/stream_for_cases.rb → expected*.yml); the Crystal specs replay them 1:1 without edits. The pubsub cases mirror the Redis adapter's behavior against both the Redis and PostgreSQL adapters. Full suite: 51/51.