actioncable-next 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +17 -0
  5. data/lib/action_cable/channel/base.rb +335 -0
  6. data/lib/action_cable/channel/broadcasting.rb +50 -0
  7. data/lib/action_cable/channel/callbacks.rb +76 -0
  8. data/lib/action_cable/channel/naming.rb +28 -0
  9. data/lib/action_cable/channel/periodic_timers.rb +81 -0
  10. data/lib/action_cable/channel/streams.rb +213 -0
  11. data/lib/action_cable/channel/test_case.rb +329 -0
  12. data/lib/action_cable/connection/authorization.rb +18 -0
  13. data/lib/action_cable/connection/base.rb +165 -0
  14. data/lib/action_cable/connection/callbacks.rb +57 -0
  15. data/lib/action_cable/connection/identification.rb +51 -0
  16. data/lib/action_cable/connection/internal_channel.rb +50 -0
  17. data/lib/action_cable/connection/subscriptions.rb +124 -0
  18. data/lib/action_cable/connection/test_case.rb +294 -0
  19. data/lib/action_cable/deprecator.rb +9 -0
  20. data/lib/action_cable/engine.rb +98 -0
  21. data/lib/action_cable/gem_version.rb +19 -0
  22. data/lib/action_cable/helpers/action_cable_helper.rb +45 -0
  23. data/lib/action_cable/remote_connections.rb +82 -0
  24. data/lib/action_cable/server/base.rb +163 -0
  25. data/lib/action_cable/server/broadcasting.rb +62 -0
  26. data/lib/action_cable/server/configuration.rb +75 -0
  27. data/lib/action_cable/server/connections.rb +44 -0
  28. data/lib/action_cable/server/socket/client_socket.rb +159 -0
  29. data/lib/action_cable/server/socket/message_buffer.rb +56 -0
  30. data/lib/action_cable/server/socket/stream.rb +117 -0
  31. data/lib/action_cable/server/socket/web_socket.rb +47 -0
  32. data/lib/action_cable/server/socket.rb +180 -0
  33. data/lib/action_cable/server/stream_event_loop.rb +119 -0
  34. data/lib/action_cable/server/tagged_logger_proxy.rb +46 -0
  35. data/lib/action_cable/server/worker/active_record_connection_management.rb +23 -0
  36. data/lib/action_cable/server/worker.rb +75 -0
  37. data/lib/action_cable/subscription_adapter/async.rb +14 -0
  38. data/lib/action_cable/subscription_adapter/base.rb +39 -0
  39. data/lib/action_cable/subscription_adapter/channel_prefix.rb +30 -0
  40. data/lib/action_cable/subscription_adapter/inline.rb +40 -0
  41. data/lib/action_cable/subscription_adapter/postgresql.rb +130 -0
  42. data/lib/action_cable/subscription_adapter/redis.rb +257 -0
  43. data/lib/action_cable/subscription_adapter/subscriber_map.rb +80 -0
  44. data/lib/action_cable/subscription_adapter/test.rb +41 -0
  45. data/lib/action_cable/test_case.rb +13 -0
  46. data/lib/action_cable/test_helper.rb +163 -0
  47. data/lib/action_cable/version.rb +12 -0
  48. data/lib/action_cable.rb +81 -0
  49. data/lib/actioncable-next.rb +5 -0
  50. data/lib/rails/generators/channel/USAGE +19 -0
  51. data/lib/rails/generators/channel/channel_generator.rb +127 -0
  52. data/lib/rails/generators/channel/templates/application_cable/channel.rb.tt +4 -0
  53. data/lib/rails/generators/channel/templates/application_cable/connection.rb.tt +4 -0
  54. data/lib/rails/generators/channel/templates/channel.rb.tt +16 -0
  55. data/lib/rails/generators/channel/templates/javascript/channel.js.tt +20 -0
  56. data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
  57. data/lib/rails/generators/channel/templates/javascript/index.js.tt +1 -0
  58. data/lib/rails/generators/test_unit/channel_generator.rb +22 -0
  59. data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
  60. metadata +191 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Copyright (c) 37signals LLC
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ require "active_support"
27
+ require "active_support/rails"
28
+ require "zeitwerk"
29
+
30
+ # We compute lib this way instead of using __dir__ because __dir__ gives a real
31
+ # path, while __FILE__ honors symlinks. If the gem is stored under a symlinked
32
+ # directory, this matters.
33
+ lib = File.dirname(__FILE__)
34
+
35
+ Zeitwerk::Loader.for_gem.tap do |loader|
36
+ loader.ignore(
37
+ "#{lib}/rails", # Contains generators, templates, docs, etc.
38
+ "#{lib}/action_cable/gem_version.rb",
39
+ "#{lib}/action_cable/version.rb",
40
+ "#{lib}/action_cable/deprecator.rb",
41
+ "#{lib}/actioncable-next.rb",
42
+ )
43
+
44
+ loader.do_not_eager_load(
45
+ "#{lib}/action_cable/subscription_adapter", # Adapters are required and loaded on demand.
46
+ "#{lib}/action_cable/test_helper.rb",
47
+ Dir["#{lib}/action_cable/**/test_case.rb"]
48
+ )
49
+
50
+ loader.inflector.inflect("postgresql" => "PostgreSQL")
51
+ end.setup
52
+
53
+ # :markup: markdown
54
+ # :include: ../README.md
55
+ module ActionCable
56
+ require_relative "action_cable/version"
57
+ require_relative "action_cable/deprecator"
58
+
59
+ INTERNAL = {
60
+ message_types: {
61
+ welcome: "welcome",
62
+ disconnect: "disconnect",
63
+ ping: "ping",
64
+ confirmation: "confirm_subscription",
65
+ rejection: "reject_subscription"
66
+ },
67
+ disconnect_reasons: {
68
+ unauthorized: "unauthorized",
69
+ invalid_request: "invalid_request",
70
+ server_restart: "server_restart",
71
+ remote: "remote"
72
+ },
73
+ default_mount_path: "/cable",
74
+ protocols: ["actioncable-v1-json", "actioncable-unsupported"].freeze
75
+ }
76
+
77
+ # Singleton instance of the server
78
+ module_function def server
79
+ @server ||= ActionCable::Server::Base.new
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionCableNext
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Generates a new cable channel for the server (in Ruby) and client (in JavaScript).
3
+ Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments.
4
+
5
+ Examples:
6
+ `bin/rails generate channel notification`
7
+
8
+ creates a notification channel class, test and JavaScript asset:
9
+ Channel: app/channels/notification_channel.rb
10
+ Test: test/channels/notification_channel_test.rb
11
+ Assets: $JAVASCRIPT_PATH/channels/notification_channel.js
12
+
13
+ `bin/rails generate channel chat speak`
14
+
15
+ creates a chat channel with a speak action.
16
+
17
+ `bin/rails generate channel comments --no-assets`
18
+
19
+ creates a comments channel without JavaScript assets.
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module Rails
6
+ module Generators
7
+ class ChannelGenerator < NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :actions, type: :array, default: [], banner: "method method"
11
+
12
+ class_option :assets, type: :boolean
13
+
14
+ check_class_collision suffix: "Channel"
15
+
16
+ hook_for :test_framework
17
+
18
+ def create_channel_files
19
+ create_shared_channel_files
20
+ create_channel_file
21
+
22
+ if using_javascript?
23
+ if first_setup_required?
24
+ create_shared_channel_javascript_files
25
+ import_channels_in_javascript_entrypoint
26
+
27
+ if using_importmap?
28
+ pin_javascript_dependencies
29
+ elsif using_js_runtime?
30
+ install_javascript_dependencies
31
+ end
32
+ end
33
+
34
+ create_channel_javascript_file
35
+ import_channel_in_javascript_entrypoint
36
+ end
37
+ end
38
+
39
+ private
40
+ def create_shared_channel_files
41
+ return if behavior != :invoke
42
+
43
+ copy_file "#{__dir__}/templates/application_cable/channel.rb",
44
+ "app/channels/application_cable/channel.rb"
45
+ copy_file "#{__dir__}/templates/application_cable/connection.rb",
46
+ "app/channels/application_cable/connection.rb"
47
+ end
48
+
49
+ def create_channel_file
50
+ template "channel.rb",
51
+ File.join("app/channels", class_path, "#{file_name}_channel.rb")
52
+ end
53
+
54
+ def create_shared_channel_javascript_files
55
+ template "javascript/index.js", "app/javascript/channels/index.js"
56
+ template "javascript/consumer.js", "app/javascript/channels/consumer.js"
57
+ end
58
+
59
+ def create_channel_javascript_file
60
+ channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
61
+ js_template "javascript/channel", channel_js_path
62
+ gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_js_runtime?
63
+ end
64
+
65
+ def import_channels_in_javascript_entrypoint
66
+ append_to_file "app/javascript/application.js",
67
+ using_js_runtime? ? %(import "./channels"\n) : %(import "channels"\n)
68
+ end
69
+
70
+ def import_channel_in_javascript_entrypoint
71
+ append_to_file "app/javascript/channels/index.js",
72
+ using_js_runtime? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
73
+ end
74
+
75
+ def install_javascript_dependencies
76
+ say "Installing JavaScript dependencies", :green
77
+ if using_bun?
78
+ run "bun add @rails/actioncable"
79
+ elsif using_node?
80
+ run "yarn add @rails/actioncable"
81
+ end
82
+ end
83
+
84
+ def pin_javascript_dependencies
85
+ append_to_file "config/importmap.rb", <<-RUBY
86
+ pin "@rails/actioncable", to: "actioncable.esm.js"
87
+ pin_all_from "app/javascript/channels", under: "channels"
88
+ RUBY
89
+ end
90
+
91
+ def file_name
92
+ @_file_name ||= super.sub(/_channel\z/i, "")
93
+ end
94
+
95
+ def first_setup_required?
96
+ !root.join("app/javascript/channels/index.js").exist?
97
+ end
98
+
99
+ def using_javascript?
100
+ @using_javascript ||= options[:assets] && root.join("app/javascript").exist?
101
+ end
102
+
103
+ def using_js_runtime?
104
+ @using_js_runtime ||= root.join("package.json").exist?
105
+ end
106
+
107
+ def using_bun?
108
+ # Cannot assume bun.lockb has been generated yet so we look for a file known to
109
+ # be generated by the jsbundling-rails gem
110
+ @using_bun ||= using_js_runtime? && root.join("bun.config.js").exist?
111
+ end
112
+
113
+ def using_node?
114
+ # Bun is the only runtime that _isn't_ node.
115
+ @using_node ||= using_js_runtime? && !root.join("bun.config.js").exist?
116
+ end
117
+
118
+ def using_importmap?
119
+ @using_importmap ||= root.join("config/importmap.rb").exist?
120
+ end
121
+
122
+ def root
123
+ @root ||= Pathname(destination_root)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Channel < ApplicationCable::Channel
3
+ def subscribed
4
+ # stream_from "some_channel"
5
+ end
6
+
7
+ def unsubscribed
8
+ # Any cleanup needed when channel is unsubscribed
9
+ end
10
+ <% actions.each do |action| -%>
11
+
12
+ def <%= action %>
13
+ end
14
+ <% end -%>
15
+ end
16
+ <% end -%>
@@ -0,0 +1,20 @@
1
+ import consumer from "./consumer"
2
+
3
+ consumer.subscriptions.create("<%= class_name %>Channel", {
4
+ connected() {
5
+ // Called when the subscription is ready for use on the server
6
+ },
7
+
8
+ disconnected() {
9
+ // Called when the subscription has been terminated by the server
10
+ },
11
+
12
+ received(data) {
13
+ // Called when there's incoming data on the websocket for this channel
14
+ }<%= actions.any? ? ",\n" : '' %>
15
+ <% actions.each do |action| -%>
16
+ <%=action %>: function() {
17
+ return this.perform('<%= action %>');
18
+ }<%= action == actions[-1] ? '' : ",\n" %>
19
+ <% end -%>
20
+ });
@@ -0,0 +1,6 @@
1
+ // Action Cable provides the framework to deal with WebSockets in Rails.
2
+ // You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
3
+
4
+ import { createConsumer } from "@rails/actioncable"
5
+
6
+ export default createConsumer()
@@ -0,0 +1 @@
1
+ // Import all the channels to be used by Action Cable
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module TestUnit
6
+ module Generators
7
+ class ChannelGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ check_class_collision suffix: "ChannelTest"
11
+
12
+ def create_test_files
13
+ template "channel_test.rb", File.join("test/channels", class_path, "#{file_name}_channel_test.rb")
14
+ end
15
+
16
+ private
17
+ def file_name # :doc:
18
+ @_file_name ||= super.sub(/_channel\z/i, "")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ require "test_helper"
2
+
3
+ class <%= class_name %>ChannelTest < ActionCable::Channel::TestCase
4
+ # test "subscribes" do
5
+ # subscribe
6
+ # assert subscription.confirmed?
7
+ # end
8
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: actioncable-next
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pratik Naik
8
+ - David Heinemeier Hansson
9
+ - Vladimir Dementyev
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2024-09-30 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '7.0'
22
+ - - "<="
23
+ - !ruby/object:Gem::Version
24
+ version: '8.1'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: '7.0'
32
+ - - "<="
33
+ - !ruby/object:Gem::Version
34
+ version: '8.1'
35
+ - !ruby/object:Gem::Dependency
36
+ name: actionpack
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '7.0'
42
+ - - "<="
43
+ - !ruby/object:Gem::Version
44
+ version: '8.1'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '7.0'
52
+ - - "<="
53
+ - !ruby/object:Gem::Version
54
+ version: '8.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nio4r
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: websocket-driver
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: zeitwerk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.6'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.6'
97
+ description: Next-gen version of Action Cable
98
+ email:
99
+ - pratiknaik@gmail.com
100
+ - david@loudthinking.com
101
+ - palkan@evilmartians.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - CHANGELOG.md
107
+ - MIT-LICENSE
108
+ - README.md
109
+ - lib/action_cable.rb
110
+ - lib/action_cable/channel/base.rb
111
+ - lib/action_cable/channel/broadcasting.rb
112
+ - lib/action_cable/channel/callbacks.rb
113
+ - lib/action_cable/channel/naming.rb
114
+ - lib/action_cable/channel/periodic_timers.rb
115
+ - lib/action_cable/channel/streams.rb
116
+ - lib/action_cable/channel/test_case.rb
117
+ - lib/action_cable/connection/authorization.rb
118
+ - lib/action_cable/connection/base.rb
119
+ - lib/action_cable/connection/callbacks.rb
120
+ - lib/action_cable/connection/identification.rb
121
+ - lib/action_cable/connection/internal_channel.rb
122
+ - lib/action_cable/connection/subscriptions.rb
123
+ - lib/action_cable/connection/test_case.rb
124
+ - lib/action_cable/deprecator.rb
125
+ - lib/action_cable/engine.rb
126
+ - lib/action_cable/gem_version.rb
127
+ - lib/action_cable/helpers/action_cable_helper.rb
128
+ - lib/action_cable/remote_connections.rb
129
+ - lib/action_cable/server/base.rb
130
+ - lib/action_cable/server/broadcasting.rb
131
+ - lib/action_cable/server/configuration.rb
132
+ - lib/action_cable/server/connections.rb
133
+ - lib/action_cable/server/socket.rb
134
+ - lib/action_cable/server/socket/client_socket.rb
135
+ - lib/action_cable/server/socket/message_buffer.rb
136
+ - lib/action_cable/server/socket/stream.rb
137
+ - lib/action_cable/server/socket/web_socket.rb
138
+ - lib/action_cable/server/stream_event_loop.rb
139
+ - lib/action_cable/server/tagged_logger_proxy.rb
140
+ - lib/action_cable/server/worker.rb
141
+ - lib/action_cable/server/worker/active_record_connection_management.rb
142
+ - lib/action_cable/subscription_adapter/async.rb
143
+ - lib/action_cable/subscription_adapter/base.rb
144
+ - lib/action_cable/subscription_adapter/channel_prefix.rb
145
+ - lib/action_cable/subscription_adapter/inline.rb
146
+ - lib/action_cable/subscription_adapter/postgresql.rb
147
+ - lib/action_cable/subscription_adapter/redis.rb
148
+ - lib/action_cable/subscription_adapter/subscriber_map.rb
149
+ - lib/action_cable/subscription_adapter/test.rb
150
+ - lib/action_cable/test_case.rb
151
+ - lib/action_cable/test_helper.rb
152
+ - lib/action_cable/version.rb
153
+ - lib/actioncable-next.rb
154
+ - lib/rails/generators/channel/USAGE
155
+ - lib/rails/generators/channel/channel_generator.rb
156
+ - lib/rails/generators/channel/templates/application_cable/channel.rb.tt
157
+ - lib/rails/generators/channel/templates/application_cable/connection.rb.tt
158
+ - lib/rails/generators/channel/templates/channel.rb.tt
159
+ - lib/rails/generators/channel/templates/javascript/channel.js.tt
160
+ - lib/rails/generators/channel/templates/javascript/consumer.js.tt
161
+ - lib/rails/generators/channel/templates/javascript/index.js.tt
162
+ - lib/rails/generators/test_unit/channel_generator.rb
163
+ - lib/rails/generators/test_unit/templates/channel_test.rb.tt
164
+ homepage: https://github.com/anycable/action_cable-next
165
+ licenses:
166
+ - MIT
167
+ metadata:
168
+ bug_tracker_uri: https://github.com/anycable/actioncable-next
169
+ changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.1.0/CHANGELOG.md
170
+ source_code_uri: https://github.com/anycable/actioncable-next
171
+ rubygems_mfa_required: 'true'
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 3.1.0
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubygems_version: 3.5.18
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Next-gen version of Action Cable
191
+ test_files: []