superglue 1.1.0 → 2.0.0.alpha.1

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: e4b39909974d5f15cf6353b2a5748e4e31878b3dee8622d8874cc18395bf684c
4
- data.tar.gz: c375f0687470c2afb7de11fb1b566bd87e6a5e51d200faea291f42a7bb7b7b95
3
+ metadata.gz: ece4f5c86bd486f1c253c62fdbfff95056fb60bbcabc9e951d52945c17b9b1bc
4
+ data.tar.gz: 665759fb6ce210973e4c49838936161fa685c9e1361432a63745c30e4170c9ce
5
5
  SHA512:
6
- metadata.gz: 4fa8626cc3b719c3df4f0c521e22a7bf7857bce6871c2b43bab714ab1ae019677bb8a854b257aa9a7181b928a399d518a6350f0b3aafcbe3237e3cd58ca4d8ea
7
- data.tar.gz: 93798fbd725b53a4353415bc648fc7e8ffb0dffc046b10daa2a436398ea9c6d2e678a4ca0662d9e20ed001b1b5591830c81b982cfc7f6d9e6d69851e589c8ebb
6
+ metadata.gz: 9add30dbf5df13ce875abe6510e7ad4f3d9820333341821bbeb8620bfe96c3559168eeff4ea34512ac3210f29e46cf784529f2665720fa25bd24919299e8571c
7
+ data.tar.gz: 68755c19906211366a20bfc28d56bb68dc47404474071cbf071681cd30093c8315d8895f0be70136beef3c4017658139faf6d601818a0d3f259b1b3c9efdf6b9
@@ -0,0 +1,129 @@
1
+ module Superglue::Streams::Broadcasts
2
+ def broadcast_save_to(*streamables, **opts)
3
+ broadcast_action_to(*streamables, action: :save, **opts)
4
+ end
5
+
6
+ def broadcast_append_to(*streamables, **opts)
7
+ broadcast_action_to(*streamables, action: :append, **opts)
8
+ end
9
+
10
+ def broadcast_prepend_to(*streamables, **opts)
11
+ broadcast_action_to(*streamables, action: :prepend, **opts)
12
+ end
13
+
14
+ def broadcast_refresh_to(*streamables, **opts)
15
+ request_id = Superglue.current_request_id
16
+ content = JSON.generate({
17
+ type: "message",
18
+ action: "refresh",
19
+ requestId: request_id,
20
+ options: opts
21
+ })
22
+ broadcast_stream_to(*streamables, content: content)
23
+ end
24
+
25
+ def broadcast_action_to(*streamables, action:, fragment: nil, fragments: nil, save_as: nil, options: {}, **rendering)
26
+ locals = rendering[:locals] || {}
27
+ fragments = (fragment ? [fragment] : fragments)
28
+
29
+ fragments = fragments.map do |item|
30
+ convert_to_superglue_fragment_id(item)
31
+ end
32
+
33
+ if save_as
34
+ options[:saveAs] = convert_to_superglue_fragment_id(save_as)
35
+ end
36
+
37
+ locals[:broadcast_fragment_keys] = fragments
38
+ locals[:broadcast_action] = action
39
+ locals[:broadcast_options] = options
40
+ rendering[:locals] = locals
41
+
42
+ broadcast_stream_to(*streamables, content: render_broadcast_action(rendering))
43
+ end
44
+
45
+ def broadcast_save_later_to(*streamables, **opts)
46
+ broadcast_action_later_to(*streamables, action: :save, **opts)
47
+ end
48
+
49
+ def broadcast_append_later_to(*streamables, **opts)
50
+ broadcast_action_later_to(*streamables, action: :append, **opts)
51
+ end
52
+
53
+ def broadcast_prepend_later_to(*streamables, **opts)
54
+ broadcast_action_later_to(*streamables, action: :prepend, **opts)
55
+ end
56
+
57
+ def broadcast_refresh_later_to(*streamables, request_id: Superglue.current_request_id, **opts)
58
+ stream_name = stream_name_from(streamables)
59
+
60
+ refresh_debouncer_for(*streamables, request_id: request_id).debounce do
61
+ content = JSON.generate({
62
+ type: "message",
63
+ action: "refresh",
64
+ requestId: request_id,
65
+ options: opts
66
+ })
67
+
68
+ Superglue::Streams::BroadcastStreamJob.perform_later stream_name, content: content
69
+ end
70
+ end
71
+
72
+ def broadcast_action_later_to(*streamables, action:, fragment: nil, fragments: nil, save_as: nil, options: {}, **rendering)
73
+ streamables.flatten!
74
+ streamables.compact_blank!
75
+
76
+ return unless streamables.present?
77
+
78
+ fragments = (fragment ? [fragment] : fragments).map do |item|
79
+ convert_to_superglue_fragment_id(item)
80
+ end
81
+
82
+ if save_as
83
+ options[:saveAs] = convert_to_superglue_fragment_id(save_as)
84
+ end
85
+
86
+ Superglue::Streams::ActionBroadcastJob.perform_later \
87
+ stream_name_from(streamables), action: action, fragments: fragments, options: options, **rendering
88
+ end
89
+
90
+ def broadcast_stream_to(*streamables, content:)
91
+ streamables.flatten!
92
+ streamables.compact_blank!
93
+
94
+ return unless streamables.present?
95
+
96
+ ActionCable.server.broadcast stream_name_from(streamables), content
97
+ end
98
+
99
+ def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
100
+ Superglue::ThreadDebouncer.for("superglue-refresh-debouncer-#{stream_name_from(streamables.including(request_id))}")
101
+ end
102
+
103
+ private
104
+
105
+ def convert_to_superglue_fragment_id(fragment)
106
+ fragment_array = Array.wrap(fragment)
107
+ if fragment_array.any? { |value| value.respond_to?(:to_key) }
108
+ ActionView::RecordIdentifier.dom_id(*fragment_array)
109
+ else
110
+ fragment
111
+ end
112
+ end
113
+
114
+ def render_format(format, **rendering)
115
+ rendering[:layout] = "superglue/layouts/stream_message"
116
+ ApplicationController.render(formats: [format], **rendering)
117
+ end
118
+
119
+ def render_broadcast_action(rendering)
120
+ json = rendering.delete(:json)
121
+
122
+ if json
123
+ rendering[:locals] ||= {}
124
+ rendering[:locals][:broadcast_json] = json
125
+ end
126
+
127
+ render_format(:json, **rendering)
128
+ end
129
+ end
@@ -0,0 +1,25 @@
1
+ module Superglue::Streams::StreamName
2
+ def verified_stream_name(signed_stream_name)
3
+ Superglue.signed_stream_verifier.verified signed_stream_name
4
+ end
5
+
6
+ def signed_stream_name(streamables)
7
+ Superglue.signed_stream_verifier.generate stream_name_from(streamables)
8
+ end
9
+
10
+ module ClassMethods
11
+ def verified_stream_name_from_params
12
+ self.class.verified_stream_name(params[:signed_stream_name])
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def stream_name_from(streamables)
19
+ if streamables.is_a?(Array)
20
+ streamables.map { |streamable| stream_name_from(streamable) }.join(":")
21
+ else
22
+ streamables.then { |streamable| streamable.try(:to_gid_param) || streamable.to_param }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ class Superglue::StreamsChannel < ActionCable::Channel::Base
2
+ extend Superglue::Streams::StreamName
3
+ extend Superglue::Streams::Broadcasts
4
+ include Superglue::Streams::StreamName::ClassMethods
5
+
6
+ def subscribed
7
+ if stream_name = verified_stream_name_from_params
8
+ stream_from stream_name
9
+ else
10
+ reject
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Superglue::RequestIdTracking
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ around_action :superglue_tracking_request_id
6
+ end
7
+
8
+ private
9
+
10
+ def superglue_tracking_request_id(&block)
11
+ Superglue.with_request_id(request.headers["X-Superglue-Request-Id"], &block)
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ module Superglue::StreamsHelper
2
+ def stream_from_props(*streamables, **attributes)
3
+ raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
4
+ attributes[:channel] = attributes[:channel]&.to_s || "Superglue::StreamsChannel"
5
+ attributes[:signed_stream_name] = Superglue::StreamsChannel.signed_stream_name(streamables)
6
+
7
+ attributes
8
+ end
9
+
10
+ def fragment_id(value)
11
+ if value.respond_to?(:to_key)
12
+ ActionView::RecordIdentifier.dom_id(value)
13
+ elsif value.respond_to?(:broadcast_fragment_default)
14
+ value.broadcast_fragment_default
15
+ else
16
+ value.to_s
17
+ end
18
+ end
19
+
20
+ def broadcast_prepend_props(model: nil, fragment: nil, save_as: nil, options: {}, **rendering)
21
+ if save_as
22
+ options[:saveAs] ||= fragment_id(save_as)
23
+ end
24
+
25
+ broadcast_action_props(action: "prepend", model:, fragment:, options:, **rendering)
26
+ end
27
+
28
+ def broadcast_append_props(model: nil, fragment: nil, save_as: nil, options: {}, **rendering)
29
+ if save_as
30
+ options[:saveAs] ||= fragment_id(save_as)
31
+ end
32
+
33
+ broadcast_action_props(action: "append", model:, fragment:, options:, **rendering)
34
+ end
35
+
36
+ def broadcast_save_props(model: nil, partial: nil, fragment: nil, options: {}, **rendering)
37
+ if model && !fragment
38
+ fragment = fragment_id(model)
39
+ end
40
+
41
+ broadcast_action_props(action: "save", model:, fragment:, options:, **rendering)
42
+ end
43
+
44
+ def broadcast_action_props(action:, partial: nil, model: nil, fragment: nil, options: {}, **rendering)
45
+ if model
46
+ fragment = model.broadcast_fragment_default if !fragment
47
+
48
+ if model.respond_to?(:to_partial_path)
49
+ rendering[:locals] = (rendering[:locals] || {}).reverse_merge(model.model_name.element.to_sym => model).compact
50
+ partial ||= model.to_partial_path
51
+ end
52
+ end
53
+
54
+ fragment = fragment_id(fragment)
55
+
56
+ if !partial
57
+ raise StandardError, "A partial is needed to render a stream"
58
+ end
59
+
60
+ json = instance_variable_get(:@__json)
61
+
62
+ json.child! do
63
+ json.fragmentIds [fragment]
64
+ json.handler action
65
+ json.options(options)
66
+ json.data(partial: [partial, rendering]) do
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ class Superglue::Streams::ActionBroadcastJob < ActiveJob::Base
2
+ discard_on ActiveJob::DeserializationError
3
+
4
+ def perform(stream, action:, fragments:, options: {}, **rendering)
5
+ Superglue::StreamsChannel.broadcast_action_to stream, action: action, fragments: fragments, options: options, **rendering
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Superglue::Streams::BroadcastStreamJob < ActiveJob::Base
2
+ discard_on ActiveJob::DeserializationError
3
+
4
+ def perform(stream, content:)
5
+ Superglue::StreamsChannel.broadcast_stream_to(stream, content: content)
6
+ end
7
+ end
@@ -0,0 +1,151 @@
1
+ module Superglue::Broadcastable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ thread_mattr_accessor :suppressed_superglue_broadcasts, instance_accessor: false
6
+ delegate :suppressed_superglue_broadcasts?, to: "self.class"
7
+ end
8
+
9
+ module ClassMethods
10
+ def broadcasts_to(stream, inserts_by: :append, fragment: broadcast_fragment_default, save_as: nil, **rendering)
11
+ after_create_commit -> { broadcast_action_later_to(stream.try(:call, self) || send(stream), action: inserts_by, fragment: fragment.try(:call, self) || fragment, save_as: save_as&.try(:call, self), **rendering) }
12
+ after_update_commit -> { broadcast_save_later_to(stream.try(:call, self) || send(stream), **rendering) }
13
+ end
14
+
15
+ def broadcasts(stream = model_name.plural, inserts_by: :append, fragment: broadcast_fragment_default, save_as: nil, **rendering)
16
+ after_create_commit -> { broadcast_action_later_to(stream, action: inserts_by, fragment: fragment.try(:call, self) || fragment, save_as: save_as&.try(:call, self), **rendering) }
17
+ after_update_commit -> { broadcast_save_later(**rendering) }
18
+ end
19
+
20
+ def broadcasts_refreshes_to(stream)
21
+ after_commit -> { broadcast_refresh_later_to(stream.try(:call, self) || send(stream)) }
22
+ end
23
+
24
+ def broadcasts_refreshes(stream = model_name.plural)
25
+ after_create_commit -> { broadcast_refresh_later_to(stream) }
26
+ after_update_commit -> { broadcast_refresh_later }
27
+ after_destroy_commit -> { broadcast_refresh }
28
+ end
29
+
30
+ def broadcast_fragment_default
31
+ model_name.plural
32
+ end
33
+
34
+ def suppressing_superglue_broadcasts(&block)
35
+ original, self.suppressed_superglue_broadcasts = suppressed_superglue_broadcasts, true
36
+ yield
37
+ ensure
38
+ self.suppressed_superglue_broadcasts = original
39
+ end
40
+
41
+ def suppressed_superglue_broadcasts?
42
+ suppressed_superglue_broadcasts
43
+ end
44
+ end
45
+
46
+ # add fragment?
47
+ def broadcast_save_to(*streamables, **rendering)
48
+ Superglue::StreamsChannel.broadcast_save_to(*streamables, **extract_options_and_add_fragment(rendering, fragment: self)) unless suppressed_superglue_broadcasts?
49
+ end
50
+
51
+ def broadcast_save(**rendering)
52
+ broadcast_save_to self, **rendering
53
+ end
54
+
55
+ # todo save_as: true
56
+ def broadcast_append_to(*streamables, fragment: broadcast_fragment_default, save_as: nil, **rendering)
57
+ Superglue::StreamsChannel.broadcast_append_to(*streamables, save_as: save_as, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
58
+ end
59
+
60
+ def broadcast_append(fragment: broadcast_fragment_default, save_as: nil, **rendering)
61
+ broadcast_append_to self, fragment: fragment, save_as: save_as, **rendering
62
+ end
63
+
64
+ def broadcast_prepend_to(*streamables, fragment: broadcast_fragment_default, save_as: nil, **rendering)
65
+ Superglue::StreamsChannel.broadcast_prepend_to(*streamables, save_as: save_as, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
66
+ end
67
+
68
+ def broadcast_prepend(fragment: broadcast_fragment_default, save_as: nil, **rendering)
69
+ broadcast_prepend_to self, fragment: fragment, save_as: save_as, **rendering
70
+ end
71
+
72
+ def broadcast_refresh_to(*streamables)
73
+ Superglue::StreamsChannel.broadcast_refresh_to(*streamables) unless suppressed_superglue_broadcasts?
74
+ end
75
+
76
+ def broadcast_refresh
77
+ broadcast_refresh_to self
78
+ end
79
+
80
+ # todo rename options to js_options
81
+ def broadcast_action_to(*streamables, action:, fragment: broadcast_fragment_default, options: {}, **rendering)
82
+ Superglue::StreamsChannel.broadcast_action_to(*streamables, action: action, options: options, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
83
+ end
84
+
85
+ def broadcast_action(action, fragment: broadcast_fragment_default, options: {}, **rest)
86
+ broadcast_action_to self, action: action, fragment: fragment, options: options, **rest
87
+ end
88
+
89
+ def broadcast_save_later_to(*streamables, **rendering)
90
+ Superglue::StreamsChannel.broadcast_save_later_to(*streamables, **extract_options_and_add_fragment(rendering, fragment: self)) unless suppressed_superglue_broadcasts?
91
+ end
92
+
93
+ def broadcast_save_later(**rendering)
94
+ broadcast_save_later_to self, **rendering
95
+ end
96
+
97
+ def broadcast_append_later_to(*streamables, fragment: broadcast_fragment_default, save_as: nil, **rendering)
98
+ Superglue::StreamsChannel.broadcast_append_later_to(*streamables, save_as: save_as, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
99
+ end
100
+
101
+ def broadcast_append_later(fragment: broadcast_fragment_default, save_as: nil, **rendering)
102
+ broadcast_append_later_to self, fragment: fragment, save_as: save_as, **rendering
103
+ end
104
+
105
+ def broadcast_prepend_later_to(*streamables, fragment: broadcast_fragment_default, save_as: nil, **rendering)
106
+ Superglue::StreamsChannel.broadcast_prepend_later_to(*streamables, save_as: save_as, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
107
+ end
108
+
109
+ def broadcast_prepend_later(fragment: broadcast_fragment_default, save_as: nil, **rendering)
110
+ broadcast_prepend_later_to self, fragment: fragment, save_as: save_as, **rendering
111
+ end
112
+
113
+ def broadcast_refresh_later_to(*streamables)
114
+ Superglue::StreamsChannel.broadcast_refresh_later_to(*streamables, request_id: Superglue.current_request_id) unless suppressed_superglue_broadcasts?
115
+ end
116
+
117
+ def broadcast_refresh_later
118
+ broadcast_refresh_later_to self
119
+ end
120
+
121
+ def broadcast_action_later_to(*streamables, action:, fragment: broadcast_fragment_default, options: {}, **rendering)
122
+ Superglue::StreamsChannel.broadcast_action_later_to(*streamables, action: action, options: options, **extract_options_and_add_fragment(rendering, fragment: fragment)) unless suppressed_superglue_broadcasts?
123
+ end
124
+
125
+ def broadcast_action_later(action:, fragment: broadcast_fragment_default, options: {}, **rendering)
126
+ broadcast_action_later_to self, action: action, fragment: fragment, options: options, **rendering
127
+ end
128
+
129
+ def broadcast_fragment_default
130
+ self.class.broadcast_fragment_default
131
+ end
132
+
133
+ private
134
+
135
+ def extract_options_and_add_fragment(rendering = {}, fragment: broadcast_fragment_default)
136
+ broadcast_rendering_with_defaults(rendering).tap do |options|
137
+ options[:fragment] = fragment if !options.key?(:fragment) && !options.key?(:fragments)
138
+ end
139
+ end
140
+
141
+ def broadcast_rendering_with_defaults(options)
142
+ options.tap do |o|
143
+ # Add the current instance into the locals with the element name (which is the un-namespaced name)
144
+ # as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
145
+ o[:locals] = (o[:locals] || {}).reverse_merge(model_name.element.to_sym => self).compact
146
+
147
+ # if none of these options are passed in, it will set a partial from #to_partial_path
148
+ o[:partial] ||= to_partial_path
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,25 @@
1
+ class Superglue::Debouncer
2
+ attr_reader :delay, :scheduled_task
3
+
4
+ DEFAULT_DELAY = 0.5
5
+
6
+ def initialize(delay: DEFAULT_DELAY)
7
+ @delay = delay
8
+ @scheduled_task = nil
9
+ end
10
+
11
+ def debounce(&block)
12
+ scheduled_task&.cancel unless scheduled_task&.complete?
13
+ @scheduled_task = Concurrent::ScheduledTask.execute(delay, &block)
14
+ end
15
+
16
+ def wait
17
+ scheduled_task&.wait(wait_timeout)
18
+ end
19
+
20
+ private
21
+
22
+ def wait_timeout
23
+ delay + 1
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ class Superglue::ThreadDebouncer
2
+ delegate :wait, to: :debouncer
3
+
4
+ def self.for(key, delay: Superglue::Debouncer::DEFAULT_DELAY)
5
+ Thread.current[key] ||= new(key, Thread.current, delay: delay)
6
+ end
7
+
8
+ private_class_method :new
9
+
10
+ def initialize(key, thread, delay:)
11
+ @key = key
12
+ @debouncer = Superglue::Debouncer.new(delay: delay)
13
+ @thread = thread
14
+ end
15
+
16
+ def debounce
17
+ debouncer.debounce do
18
+ yield.tap do
19
+ thread[key] = nil
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :key, :debouncer, :thread
27
+ end
@@ -0,0 +1,15 @@
1
+ json.disable_deferments!
2
+
3
+ if local_assigns[:broadcast_json]
4
+ json.data broadcast_json
5
+ else
6
+ json.data do
7
+ yield
8
+ end
9
+ end
10
+
11
+ json.fragments json.fragments!
12
+ json.action "handleStreamMessage"
13
+ json.handler broadcast_action
14
+ json.fragmentIds broadcast_fragment_keys
15
+ json.options broadcast_options
@@ -0,0 +1,10 @@
1
+ json.disable_deferments!
2
+
3
+ json.data do
4
+ yield
5
+ end
6
+
7
+ json.fragments json.fragments!
8
+ json.action "handleStreamResponse"
9
+
10
+ json.flash flash.to_h
@@ -30,6 +30,9 @@ module Superglue
30
30
  say "Copying application.json.props"
31
31
  copy_file "#{__dir__}/templates/application.json.props", "app/views/layouts/application.json.props"
32
32
 
33
+ say "Copying stream.json.props"
34
+ copy_file "#{__dir__}/templates/stream.json.props", "app/views/layouts/stream.json.props"
35
+
33
36
  say "Adding required member methods to ApplicationRecord"
34
37
  add_member_methods
35
38
 
@@ -37,7 +40,7 @@ module Superglue
37
40
  insert_jsx_rendering_defaults
38
41
 
39
42
  say "Installing Superglue and friends"
40
- run "yarn add react react-dom @reduxjs/toolkit react-redux @thoughtbot/superglue"
43
+ run "yarn add react react-dom @reduxjs/toolkit react-redux @thoughtbot/superglue@2.0.0-alpha.1"
41
44
 
42
45
  if use_typescript
43
46
  run "yarn add -D @types/react-dom @types/react @types/node @thoughtbot/candy_wrapper"
@@ -0,0 +1,15 @@
1
+ json.disable_deferments!
2
+
3
+ json.data do
4
+ json.array! do
5
+ yield json
6
+ end
7
+ end
8
+ json.fragments json.fragments!
9
+ json.assets [ asset_path('application.js') ]
10
+
11
+ if protect_against_forgery?
12
+ json.csrfToken form_authenticity_token
13
+ end
14
+
15
+ json.action "handleStreamResponse"
@@ -1,4 +1,4 @@
1
- // import your page component #todo fix output of new lines
1
+ // import your page component
2
2
  // e.g import PostsEdit from '../views/posts/edit'
3
3
 
4
4
  // Mapping between your props template to Component, you must add to this
@@ -28,7 +28,6 @@
28
28
  // }
29
29
  // ```
30
30
  //
31
- const pageIdentifierToPageComponent = {
32
- };
31
+ const pageIdentifierToPageComponent = {};
33
32
 
34
- export { pageIdentifierToPageComponent }
33
+ export { pageIdentifierToPageComponent };
@@ -0,0 +1,55 @@
1
+ module Superglue
2
+ module Controller
3
+ include Helpers
4
+
5
+ def self.included(base)
6
+ base.include ::Superglue::Rendering
7
+ return unless base.respond_to?(:helper_method)
8
+
9
+ base.helper_method :param_to_dig_path
10
+ base.helper_method :render_props
11
+ end
12
+ end
13
+
14
+ class Engine < ::Rails::Engine
15
+ isolate_namespace Superglue
16
+ config.eager_load_namespaces << Superglue
17
+ config.superglue = ActiveSupport::OrderedOptions.new
18
+ config.superglue.auto_include = true
19
+ config.autoload_once_paths = %W[
20
+ #{root}/app/channels
21
+ #{root}/app/controllers
22
+ #{root}/app/controllers/concerns
23
+ #{root}/app/helpers
24
+ #{root}/app/models
25
+ #{root}/app/models/concerns
26
+ #{root}/app/jobs
27
+ ]
28
+
29
+ initializer :superglue do |app|
30
+ ActiveSupport.on_load(:action_controller) do
31
+ next if self != ActionController::Base
32
+
33
+ include Controller
34
+ include Superglue::RequestIdTracking
35
+
36
+ prepend_view_path(
37
+ Superglue::Resolver.new(Rails.root.join("app/views"))
38
+ )
39
+ end
40
+ end
41
+
42
+ initializer "superglue.helpers" do
43
+ ActiveSupport.on_load(:action_controller) do
44
+ helper Superglue::StreamsHelper
45
+ end
46
+ end
47
+
48
+ initializer "superglue.signed_stream_verifier_key" do
49
+ config.after_initialize do
50
+ Superglue.signed_stream_verifier_key = config.superglue.signed_stream_verifier_key ||
51
+ Rails.application.key_generator.generate_key("superglue/signed_stream_verifier_key")
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/superglue.rb CHANGED
@@ -1,46 +1,38 @@
1
- require "superglue/helpers"
2
- require "superglue/redirection"
3
- require "superglue/rendering"
4
- require "superglue/resolver"
5
- require "props_template"
6
- require "form_props"
1
+ require 'superglue/helpers'
2
+ require 'superglue/rendering'
3
+ require 'superglue/resolver'
4
+ require 'superglue/engine'
5
+ require 'props_template'
6
+ require 'form_props'
7
7
 
8
8
  module Superglue
9
- module Controller
10
- include Redirection
11
- include Helpers
9
+ extend ActiveSupport::Autoload
12
10
 
13
- def self.included(base)
14
- base.include ::Superglue::Rendering
15
- if base.respond_to?(:helper_method)
16
- base.helper_method :param_to_dig_path
17
- base.helper_method :render_props
18
- end
19
- end
20
- end
11
+ mattr_accessor :draw_routes, default: true
21
12
 
22
- class Engine < ::Rails::Engine
23
- config.superglue = ActiveSupport::OrderedOptions.new
24
- config.superglue.auto_include = true
13
+ thread_mattr_accessor :current_request_id
25
14
 
26
- generators do |app|
27
- Rails::Generators.configure! app.config.generators
28
- Rails::Generators.hidden_namespaces.uniq!
29
- require "generators/rails/scaffold_controller_generator"
30
- end
15
+ class << self
16
+ attr_writer :signed_stream_verifier_key
31
17
 
32
- initializer :superglue do |app|
33
- ActiveSupport.on_load(:action_controller) do
34
- next if self != ActionController::Base
18
+ def signed_stream_verifier
19
+ @signed_stream_verifier ||= ActiveSupport::MessageVerifier.new(
20
+ signed_stream_verifier_key,
21
+ digest: 'SHA256',
22
+ serializer: JSON
23
+ )
24
+ end
35
25
 
36
- if app.config.superglue.auto_include
37
- include Controller
26
+ def signed_stream_verifier_key
27
+ @signed_stream_verifier_key or raise ArgumentError, 'Superglue requires a signed_stream_verifier_key'
28
+ end
38
29
 
39
- prepend_view_path(
40
- Superglue::Resolver.new(Rails.root.join("app/views"))
41
- )
42
- end
43
- end
30
+ def with_request_id(request_id)
31
+ old_request_id = current_request_id
32
+ self.current_request_id = request_id
33
+ yield
34
+ ensure
35
+ self.current_request_id = old_request_id
44
36
  end
45
37
  end
46
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: superglue
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0.alpha.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-18 00:00:00.000000000 Z
11
+ date: 2025-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.37'
39
+ version: 1.0.0.alpha
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.37'
46
+ version: 1.0.0.alpha
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: form_props
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +65,18 @@ extensions: []
65
65
  extra_rdoc_files: []
66
66
  files:
67
67
  - README.md
68
+ - app/channels/superglue/streams/broadcasts.rb
69
+ - app/channels/superglue/streams/stream_name.rb
70
+ - app/channels/superglue/streams_channel.rb
71
+ - app/controllers/concerns/superglue/request_id_tracking.rb
72
+ - app/helpers/superglue/streams_helper.rb
73
+ - app/jobs/superglue/streams/action_broadcast_job.rb
74
+ - app/jobs/superglue/streams/broadcast_stream_job.rb
75
+ - app/models/concerns/superglue/broadcastable.rb
76
+ - app/models/superglue/debouncer.rb
77
+ - app/models/superglue/thread_debouncer.rb
78
+ - app/views/superglue/layouts/_stream_message.json.props
79
+ - app/views/superglue/layouts/stream.json.props
68
80
  - lib/generators/superglue/install/install_generator.rb
69
81
  - lib/generators/superglue/install/templates/application.json.props
70
82
  - lib/generators/superglue/install/templates/erb/superglue.html.erb
@@ -78,6 +90,7 @@ files:
78
90
  - lib/generators/superglue/install/templates/js/layout.jsx
79
91
  - lib/generators/superglue/install/templates/js/page_to_page_mapping.js
80
92
  - lib/generators/superglue/install/templates/js/store.js
93
+ - lib/generators/superglue/install/templates/stream.json.props
81
94
  - lib/generators/superglue/install/templates/ts/application.tsx
82
95
  - lib/generators/superglue/install/templates/ts/application_visit.ts
83
96
  - lib/generators/superglue/install/templates/ts/components.ts
@@ -104,8 +117,8 @@ files:
104
117
  - lib/generators/superglue/view_collection/templates/ts/show.tsx
105
118
  - lib/generators/superglue/view_collection/view_collection_generator.rb
106
119
  - lib/superglue.rb
120
+ - lib/superglue/engine.rb
107
121
  - lib/superglue/helpers.rb
108
- - lib/superglue/redirection.rb
109
122
  - lib/superglue/rendering.rb
110
123
  - lib/superglue/resolver.rb
111
124
  homepage: https://github.com/thoughtbot/superglue_rails/
@@ -1,29 +0,0 @@
1
- module Superglue
2
- module Redirection
3
- def _compute_redirect_to_location(request, options)
4
- computed_location = URI.parse(super)
5
- next_param = Rack::Utils
6
- .parse_nested_query(computed_location.query)
7
-
8
- if request.params[:__] == "0"
9
- computed_location.query = next_param.merge({__: "0"}).to_query
10
- end
11
-
12
- computed_location.to_s
13
- end
14
-
15
- def redirect_back_with_props_at(opts)
16
- if request.referrer && params[:props_at]
17
- referrer_url = URI.parse(request.referrer)
18
- referrer_url.query = Rack::Utils
19
- .parse_nested_query(referrer_url.query)
20
- .merge({props_at: params[:props_at]})
21
- .to_query
22
-
23
- redirect_to referrer_url.to_s, opts
24
- else
25
- redirect_back(opts)
26
- end
27
- end
28
- end
29
- end