actioncable-next 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b86ee72b0095c7a8ce99b0d354231b2ac29145e07a1482387fa9664f1c9a08b8
4
- data.tar.gz: e72beed883e8789189fa6811d6fb70cdc4e9efbec2ad9b259648c8575196aca3
3
+ metadata.gz: a3500aff266cab716b9cc6ef36748f32ecd4e6b5e1d24a1c8ce462c0e3e2dd9d
4
+ data.tar.gz: 74123e275958e25046b8a3a10ac7a3c2b5fa8066c63209baf591f3623100e867
5
5
  SHA512:
6
- metadata.gz: 955b568fb0d4e01d81ad6871292515c1a3d742eef03067c6322d68b7c6908eeac12947aaef91cb9d4fcbccf2e3790ff84c797ad30e462db15179b04ee6a2d8a3
7
- data.tar.gz: 3cc6151d155e3719b008bad22ecf4fbf0a401a5858a7ef5bd75a941debca5b26ac9d06cb30d781929d6da10399c9f534d06aed6ac7afd2ea7a96490066dc6891
6
+ metadata.gz: e6662409e60c2c0a2ac869771d05df9c620529a0cc92642e6bc3b5c953e1f78e3d64460953ecee186d713c8079e0b296fc25a8faadca646128a4c6b28f01c175
7
+ data.tar.gz: 588b659b2eddf199182e25bd7bec23f386621f97abdb169d33c81ac8022556cfa2ad8275cbaad3cf420c8f89306974843f47f851d7db3db0d37363f352d6e120
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## main
4
4
 
5
+ ## 0.3.0
6
+
7
+ - Sync with upstream (Rails 8.1.1).
8
+
9
+ - Do not call `#subscribed` when subscription is rejected in a `before_subscribe` callback.
10
+
11
+ ## 0.2.0
12
+
13
+ - Performance: store connections in a Hash, not an Array.
14
+
15
+ - Add `Connection#broadcast` as an interface for broadcasting from channels.
16
+
17
+ - Add `config.fastlane_broadcasts_enabled` to opt-in for optimized broadcasts (no double JSON encoding).
18
+
5
19
  ## 0.1.2
6
20
 
7
21
  - Added a hack to prevent third-party extensions from changing the methods visibility.
data/README.md CHANGED
@@ -4,6 +4,8 @@ This gem provides the functionality of the _server adapterization_ PR: [rails/ra
4
4
 
5
5
  See the PR description for more information on the purpose of this refactoring.
6
6
 
7
+ There are also some additional improvements added on top of the PR (see below).
8
+
7
9
  ## Usage
8
10
 
9
11
  Add this line to your application's Gemfile **before Rails or Action Cable**:
@@ -26,3 +28,23 @@ by adding the following line to your `spec/rails_helper.rb`:
26
28
  require "rspec/rails"
27
29
  require "action_cable/next/rspec"
28
30
  ```
31
+
32
+ ### New features
33
+
34
+ #### `ActionCable.server.config.fastlane_broadcasts_enabled = true`
35
+
36
+ This option allows you to enable the _fast lane_ for broadcasting messages. When enabled, messages sent via `ActionCable.server.broadcast` are only decoded-encoded from/to JSON once per channel identifier and not for every connected client. This significantly reduces the broadcasting latency (up to 2x faster).
37
+
38
+ #### `Connection#broadcast`
39
+
40
+ You can now broadcast message from connection or channel instances using the connection `#broadcast` method. For example:
41
+
42
+ ```ruby
43
+ class TestChannel < ActionCable::Channel::Base
44
+ def notify_all(data)
45
+ connection.broadcast("testing-#{test_id}", data)
46
+ end
47
+ end
48
+ ```
49
+
50
+ The purpose of this interface is to avoid direct usage of `server` or `ActionCable.server` in channels code.
@@ -2,7 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "set"
6
5
  require "active_support/rescuable"
7
6
  require "active_support/parameter_filter"
8
7
 
@@ -133,7 +132,10 @@ module ActionCable
133
132
  # Except for public instance methods of Base and its ancestors
134
133
  ActionCable::Channel::Base.public_instance_methods(true) +
135
134
  # Be sure to include shadowed public instance methods of this class
136
- public_instance_methods(false)).uniq.map(&:to_s)
135
+ public_instance_methods(false) -
136
+ # Except the internal methods
137
+ internal_methods).uniq
138
+ methods.map!(&:name)
137
139
  methods.to_set
138
140
  end
139
141
  end
@@ -146,6 +148,10 @@ module ActionCable
146
148
  @action_methods = nil
147
149
  end
148
150
 
151
+ def internal_methods
152
+ super
153
+ end
154
+
149
155
  # Refresh the cached action_methods when a new action_method is added.
150
156
  def method_added(name) # :doc:
151
157
  super
@@ -166,6 +172,7 @@ module ActionCable
166
172
 
167
173
  @reject_subscription = nil
168
174
  @subscription_confirmation_sent = nil
175
+ @unsubscribed = false
169
176
 
170
177
  delegate_connection_identifiers
171
178
  end
@@ -190,7 +197,7 @@ module ActionCable
190
197
  # confirms or rejects the subscription.
191
198
  def subscribe_to_channel
192
199
  run_callbacks :subscribe do
193
- subscribed
200
+ subscribed unless subscription_rejected?
194
201
  end
195
202
 
196
203
  reject_subscription if subscription_rejected?
@@ -201,11 +208,16 @@ module ActionCable
201
208
  # cleanup with callbacks. This method is not intended to be called directly by
202
209
  # the user. Instead, override the #unsubscribed callback.
203
210
  def unsubscribe_from_channel # :nodoc:
211
+ @unsubscribed = true
204
212
  run_callbacks :unsubscribe do
205
213
  unsubscribed
206
214
  end
207
215
  end
208
216
 
217
+ def unsubscribed? # :nodoc:
218
+ @unsubscribed
219
+ end
220
+
209
221
  private
210
222
  # Called once a consumer has become a subscriber of the channel. Usually the
211
223
  # place to set up any streams you want this channel to be sending to the
@@ -10,19 +10,19 @@ module ActionCable
10
10
  extend ActiveSupport::Concern
11
11
 
12
12
  module ClassMethods
13
- # Broadcast a hash to a unique broadcasting for this `model` in this channel.
14
- def broadcast_to(model, message)
15
- ActionCable.server.broadcast(broadcasting_for(model), message)
13
+ # Broadcast a hash to a unique broadcasting for this array of `broadcastables` in this channel.
14
+ def broadcast_to(broadcastables, message)
15
+ ActionCable.server.broadcast(broadcasting_for(broadcastables), message)
16
16
  end
17
17
 
18
18
  # Returns a unique broadcasting identifier for this `model` in this channel:
19
19
  #
20
20
  # CommentsChannel.broadcasting_for("all") # => "comments:all"
21
21
  #
22
- # You can pass any object as a target (e.g. Active Record model), and it would
22
+ # You can pass an array of objects as a target (e.g. Active Record model), and it would
23
23
  # be serialized into a string under the hood.
24
- def broadcasting_for(model)
25
- serialize_broadcasting([ channel_name, model ])
24
+ def broadcasting_for(broadcastables)
25
+ serialize_broadcasting([ channel_name ] + Array(broadcastables))
26
26
  end
27
27
 
28
28
  private
@@ -39,6 +39,8 @@ module ActionCable
39
39
  extend ActiveSupport::Concern
40
40
  include ActiveSupport::Callbacks
41
41
 
42
+ INTERNAL_METHODS = [:_run_subscribe_callbacks, :_run_unsubscribe_callbacks] # :nodoc:
43
+
42
44
  included do
43
45
  define_callbacks :subscribe
44
46
  define_callbacks :unsubscribe
@@ -70,6 +72,11 @@ module ActionCable
70
72
  set_callback(:unsubscribe, :after, *methods, &block)
71
73
  end
72
74
  alias_method :on_unsubscribe, :after_unsubscribe
75
+
76
+ private
77
+ def internal_methods
78
+ INTERNAL_METHODS
79
+ end
73
80
  end
74
81
  end
75
82
  end
@@ -88,6 +88,8 @@ module ActionCable
88
88
  # callback. Defaults to `coder: nil` which does no decoding, passes raw
89
89
  # messages.
90
90
  def stream_from(broadcasting, callback = nil, coder: nil, &block)
91
+ return if unsubscribed?
92
+
91
93
  broadcasting = String(broadcasting)
92
94
 
93
95
  # Don't send the confirmation until pubsub#subscribe is successful
@@ -104,15 +106,15 @@ module ActionCable
104
106
  end)
105
107
  end
106
108
 
107
- # Start streaming the pubsub queue for the `model` in this channel. Optionally,
109
+ # Start streaming the pubsub queue for the `broadcastables` in this channel. Optionally,
108
110
  # you can pass a `callback` that'll be used instead of the default of just
109
111
  # transmitting the updates straight to the subscriber.
110
112
  #
111
113
  # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to
112
114
  # the callback. Defaults to `coder: nil` which does no decoding, passes raw
113
115
  # messages.
114
- def stream_for(model, ...)
115
- stream_from(broadcasting_for(model), ...)
116
+ def stream_for(broadcastables, ...)
117
+ stream_from(broadcasting_for(broadcastables), ...)
116
118
  end
117
119
 
118
120
  # Unsubscribes streams from the named `broadcasting`.
@@ -157,10 +159,21 @@ module ActionCable
157
159
  # Always wrap the outermost handler to invoke the user handler on the worker
158
160
  # pool rather than blocking the event loop.
159
161
  def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
162
+ if !user_handler && !coder && connection.config.fastlane_broadcasts_enabled
163
+ return opt_worker_pool_stream_handler(broadcasting)
164
+ end
165
+
160
166
  handler = stream_handler(broadcasting, user_handler, coder: coder)
161
167
 
162
168
  -> message do
163
- connection.perform_work handler, :call, message
169
+ connection.perform_work handler, :call, message.data
170
+ end
171
+ end
172
+
173
+ # Optimized stream handler that avoids double JSON encoding/decoding on broadcast
174
+ def opt_worker_pool_stream_handler(broadcasting)
175
+ -> (message) do
176
+ connection.raw_transmit message.encoded_for(@identifier)
164
177
  end
165
178
  end
166
179
 
@@ -62,7 +62,7 @@ module ActionCable
62
62
  attr_reader :subscriptions, :logger
63
63
  private attr_reader :server, :socket
64
64
 
65
- delegate :pubsub, :executor, :config, to: :server
65
+ delegate :pubsub, :executor, :config, :broadcast, to: :server
66
66
  delegate :env, :request, :protocol, :perform_work, to: :socket, allow_nil: true
67
67
 
68
68
  def initialize(server, socket)
@@ -117,6 +117,10 @@ module ActionCable
117
117
  socket.transmit(data)
118
118
  end
119
119
 
120
+ def raw_transmit(data) # :nodoc:
121
+ socket.raw_transmit(data)
122
+ end
123
+
120
124
  # Close the connection.
121
125
  def close(reason: nil, reconnect: true)
122
126
  transmit(
@@ -2,8 +2,6 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
- require "set"
6
-
7
5
  module ActionCable
8
6
  module Connection
9
7
  module Identification
@@ -18,7 +18,7 @@ module ActionCable
18
18
 
19
19
  def subscribe_to_internal_channel
20
20
  if connection_identifier.present?
21
- callback = -> (message) { process_internal_message ActiveSupport::JSON.decode(message) }
21
+ callback = -> (message) { process_internal_message ActiveSupport::JSON.decode(message.data) }
22
22
  @_internal_subscriptions ||= []
23
23
  @_internal_subscriptions << [ internal_channel, callback ]
24
24
 
@@ -40,7 +40,7 @@ module ActionCable
40
40
 
41
41
  class UnknownSubscription < Error
42
42
  def initialize(identifier)
43
- "Unable to find subscription with identifier: #{identifier}"
43
+ super "Unable to find subscription with identifier: #{identifier}"
44
44
  end
45
45
  end
46
46
 
@@ -142,11 +142,8 @@ module ActionCable
142
142
  #
143
143
  # ## Basic example
144
144
  #
145
- # Unit tests are written as follows:
146
- #
147
- # 1. Simulate a connection attempt by calling `connect`.
148
- # 2. Assert state, e.g. identifiers, has been assigned.
149
- #
145
+ # Unit tests are written by first simulating a connection attempt by calling
146
+ # `connect` and then asserting state, e.g. identifiers, have been assigned.
150
147
  #
151
148
  # class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
152
149
  # def test_connects_with_proper_cookie
@@ -10,9 +10,9 @@ module ActionCable
10
10
 
11
11
  module VERSION
12
12
  MAJOR = 8
13
- MINOR = 0
14
- TINY = 0
15
- PRE = "alpha"
13
+ MINOR = 1
14
+ TINY = 1
15
+ PRE = nil
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
18
18
  end
@@ -10,7 +10,7 @@ module ActionCable
10
10
  class ThreadedExecutor # :nodoc:
11
11
  def initialize(max_size: 10)
12
12
  @executor = Concurrent::ThreadPoolExecutor.new(
13
- name: "ActionCable server",
13
+ name: "ActionCable-streamer",
14
14
  min_threads: 1,
15
15
  max_threads: max_size,
16
16
  max_queue: 0,
@@ -21,10 +21,12 @@ module ActionCable
21
21
  # ActionCable.server.broadcast \
22
22
  # "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
23
23
  #
24
- # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
25
- # App.cable.subscriptions.create "WebNotificationsChannel",
26
- # received: (data) ->
27
- # new Notification data['title'], body: data['body']
24
+ # // Client-side JavaScript, which assumes you've already requested the right to send web notifications:
25
+ # App.cable.subscriptions.create("WebNotificationsChannel", {
26
+ # received: function(data) {
27
+ # new Notification(data['title'], { body: data['body'] });
28
+ # }
29
+ # });
28
30
  module Broadcasting
29
31
  # Broadcast a hash directly to a named `broadcasting`. This will later be JSON
30
32
  # encoded.
@@ -19,6 +19,7 @@ module ActionCable
19
19
  attr_accessor :precompile_assets
20
20
  attr_accessor :health_check_path, :health_check_application
21
21
  attr_writer :pubsub_adapter
22
+ attr_accessor :fastlane_broadcasts_enabled
22
23
 
23
24
  def initialize
24
25
  @log_tags = []
@@ -31,6 +32,8 @@ module ActionCable
31
32
  @allow_same_origin_as_host = true
32
33
  @filter_parameters = []
33
34
 
35
+ @fastlane_broadcasts_enabled = false
36
+
34
37
  @health_check_application = ->(env) {
35
38
  [200, { Rack::CONTENT_TYPE => "text/html", "date" => Time.now.httpdate }, []]
36
39
  }
@@ -13,16 +13,18 @@ module ActionCable
13
13
  module Connections # :nodoc:
14
14
  BEAT_INTERVAL = 3
15
15
 
16
- def connections
17
- @connections ||= []
16
+ def connections_map
17
+ @connections_map ||= {}
18
18
  end
19
19
 
20
+ def connections = connections_map.values
21
+
20
22
  def add_connection(connection)
21
- connections << connection
23
+ connections_map[connection.object_id] = connection
22
24
  end
23
25
 
24
26
  def remove_connection(connection)
25
- connections.delete connection
27
+ connections_map.delete connection.object_id
26
28
  end
27
29
 
28
30
  # WebSocket connection implementations differ on when they'll mark a connection
@@ -32,12 +34,12 @@ module ActionCable
32
34
  # disconnect.
33
35
  def setup_heartbeat_timer
34
36
  @heartbeat_timer ||= executor.timer(BEAT_INTERVAL) do
35
- executor.post { connections.each(&:beat) }
37
+ executor.post { connections_map.each_value(&:beat) }
36
38
  end
37
39
  end
38
40
 
39
41
  def open_connections_statistics
40
- connections.map(&:statistics)
42
+ connections_map.each_value.map(&:statistics)
41
43
  end
42
44
  end
43
45
  end
@@ -47,6 +47,12 @@ module ActionCable
47
47
  websocket.transmit encode(cable_message)
48
48
  end
49
49
 
50
+ def raw_transmit(message)
51
+ return unless websocket.alive?
52
+
53
+ websocket.transmit message
54
+ end
55
+
50
56
  # Close the WebSocket connection.
51
57
  def close(...)
52
58
  websocket.close(...) if websocket.alive?
@@ -20,7 +20,7 @@ module ActionCable
20
20
 
21
21
  def initialize(max_size: 5)
22
22
  @executor = Concurrent::ThreadPoolExecutor.new(
23
- name: "ActionCable worker",
23
+ name: "ActionCable-server",
24
24
  min_threads: 1,
25
25
  max_threads: max_size,
26
26
  max_queue: 0,
@@ -32,7 +32,8 @@ module ActionCable
32
32
  end
33
33
 
34
34
  def identifier
35
- config.cable[:id] ||= "ActionCable-PID-#{$$}"
35
+ config.cable[:id] = "ActionCable-PID-#{$$}" unless config.cable.key?(:id)
36
+ config.cable[:id]
36
37
  end
37
38
  end
38
39
  end
@@ -36,18 +36,17 @@ module ActionCable
36
36
  end
37
37
 
38
38
  def with_subscriptions_connection(&block) # :nodoc:
39
- ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
40
- # Action Cable is taking ownership over this database connection, and will
41
- # perform the necessary cleanup tasks
42
- ActiveRecord::Base.connection_pool.remove(conn)
43
- end
39
+ # Action Cable is taking ownership over this database connection, and will
40
+ # perform the necessary cleanup tasks.
41
+ # We purposedly avoid #checkout to not end up with a pinned connection
42
+ ar_conn = ActiveRecord::Base.connection_pool.new_connection
44
43
  pg_conn = ar_conn.raw_connection
45
44
 
46
45
  verify!(pg_conn)
47
46
  pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(identifier)}")
48
47
  yield pg_conn
49
48
  ensure
50
- ar_conn.disconnect!
49
+ ar_conn&.disconnect!
51
50
  end
52
51
 
53
52
  def with_broadcast_connection(&block) # :nodoc:
@@ -162,7 +162,7 @@ module ActionCable
162
162
  begin
163
163
  conn = @adapter.redis_connection_for_subscriptions
164
164
  listen conn
165
- rescue ConnectionError => e
165
+ rescue *CONNECTION_ERRORS => e
166
166
  reset
167
167
  if retry_connecting?
168
168
  logger&.warn "Redis connection failed: #{e.message}. Trying to reconnect..."
@@ -211,7 +211,7 @@ module ActionCable
211
211
  end
212
212
 
213
213
  if ::Redis::VERSION < "5"
214
- ConnectionError = ::Redis::BaseConnectionError
214
+ CONNECTION_ERRORS = [::Redis::BaseConnectionError].freeze
215
215
 
216
216
  class SubscribedClient
217
217
  def initialize(raw_client)
@@ -245,7 +245,12 @@ module ActionCable
245
245
  SubscribedClient.new(conn._client)
246
246
  end
247
247
  else
248
- ConnectionError = RedisClient::ConnectionError
248
+ CONNECTION_ERRORS = [
249
+ ::Redis::BaseConnectionError,
250
+
251
+ # Some older versions of redis-rb sometime leak underlying exceptions
252
+ RedisClient::ConnectionError,
253
+ ].freeze
249
254
 
250
255
  def extract_subscribed_client(conn)
251
256
  conn
@@ -5,6 +5,19 @@
5
5
  module ActionCable
6
6
  module SubscriptionAdapter
7
7
  class SubscriberMap
8
+ class Message < Struct.new(:data)
9
+ def initialize(...)
10
+ super
11
+ @cache = Concurrent::Map.new
12
+ end
13
+
14
+ def encoded_for(identifier)
15
+ @cache.compute_if_absent(identifier) do
16
+ ActiveSupport::JSON.encode({ identifier: identifier, message: ActiveSupport::JSON.decode(data) })
17
+ end
18
+ end
19
+ end
20
+
8
21
  def initialize
9
22
  @subscribers = Hash.new { |h, k| h[k] = [] }
10
23
  @sync = Mutex.new
@@ -41,8 +54,10 @@ module ActionCable
41
54
  @subscribers[channel].dup
42
55
  end
43
56
 
57
+ msg = Message.new(message)
58
+
44
59
  list.each do |subscriber|
45
- invoke_callback(subscriber, message)
60
+ invoke_callback(subscriber, msg)
46
61
  end
47
62
  end
48
63
 
data/lib/action_cable.rb CHANGED
@@ -23,6 +23,7 @@
23
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
  #++
25
25
 
26
+ require "logger"
26
27
  require "active_support"
27
28
  require "active_support/rails"
28
29
  require "zeitwerk"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionCableNext
4
- VERSION = "0.1.2"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -59,17 +59,17 @@ module Rails
59
59
  def create_channel_javascript_file
60
60
  channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
61
61
  js_template "javascript/channel", channel_js_path
62
- gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_js_runtime?
62
+ gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" if using_importmap?
63
63
  end
64
64
 
65
65
  def import_channels_in_javascript_entrypoint
66
66
  append_to_file "app/javascript/application.js",
67
- using_js_runtime? ? %(import "./channels"\n) : %(import "channels"\n)
67
+ using_importmap? ? %(import "channels"\n) : %(import "./channels"\n)
68
68
  end
69
69
 
70
70
  def import_channel_in_javascript_entrypoint
71
71
  append_to_file "app/javascript/channels/index.js",
72
- using_js_runtime? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
72
+ using_importmap? ? %(import "channels/#{file_name}_channel"\n) : %(import "./#{file_name}_channel"\n)
73
73
  end
74
74
 
75
75
  def install_javascript_dependencies
@@ -105,7 +105,7 @@ pin_all_from "app/javascript/channels", under: "channels"
105
105
  end
106
106
 
107
107
  def using_bun?
108
- # Cannot assume bun.lockb has been generated yet so we look for a file known to
108
+ # Cannot assume Bun lockfile has been generated yet so we look for a file known to
109
109
  # be generated by the jsbundling-rails gem
110
110
  @using_bun ||= using_js_runtime? && root.join("bun.config.js").exist?
111
111
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actioncable-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pratik Naik
8
8
  - David Heinemeier Hansson
9
9
  - Vladimir Dementyev
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-01-10 00:00:00.000000000 Z
13
+ date: 2025-10-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -167,10 +167,10 @@ licenses:
167
167
  - MIT
168
168
  metadata:
169
169
  bug_tracker_uri: https://github.com/anycable/actioncable-next
170
- changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.1.2/CHANGELOG.md
170
+ changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.3.0/CHANGELOG.md
171
171
  source_code_uri: https://github.com/anycable/actioncable-next
172
172
  rubygems_mfa_required: 'true'
173
- post_install_message:
173
+ post_install_message:
174
174
  rdoc_options: []
175
175
  require_paths:
176
176
  - lib
@@ -178,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
- version: 3.1.0
181
+ version: 3.2.0
182
182
  required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - ">="
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
186
  version: '0'
187
187
  requirements: []
188
188
  rubygems_version: 3.5.22
189
- signing_key:
189
+ signing_key:
190
190
  specification_version: 4
191
191
  summary: Next-gen version of Action Cable
192
192
  test_files: []