dexiecable 2.0.0.alpha3 → 2.0.0.alpha4

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: 2c2de6d52cf60491a659aebc8452a88ca8b8878142d08e842fadc8fb4bb1050b
4
- data.tar.gz: 6be72c303e815a4809aff6b88a718b3e9ec1df8d5ebd341af03768225ee86bc3
3
+ metadata.gz: 502a99e32deef9551c55b8cb0bdae2e2d74af751e4f9889543f76ceea6d5a2ce
4
+ data.tar.gz: f42e9bca1c1d05ef287b3b72373e7c264af8ab78c350c092b8ccd9f887aa0270
5
5
  SHA512:
6
- metadata.gz: ac607ef7627a7a0562459042bcc8b23244362553624cb2f7543a7997430842c5185d5c889b8557c3ad28f10265d286f7a557a1ce76c397473adc97ead82b13d5
7
- data.tar.gz: 1386543cc03df9409d17f184a8e1b4b80e85f993663009a32324da9b8e79561000c907c42e25c84c683bef4d4f11e525a5c1cd6dda58a0817595d53417880786
6
+ metadata.gz: 37dc5d8afc9449875b3074c2684856ebb5d1f29e44a0c8d83558d061cf96baa270b8e5d1ad2a35a17bd01363b25956ec2946aead6929c3c465bee447a0c5d8d8
7
+ data.tar.gz: 7c370e6be94b70a65c63cf8bd47bb762b353df0ce0e964cd0f68b256703514c6a790124605a973008595358976319f9d6fdb3876fd5cfc97eeba65a8b5902642
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  >
6
6
  > Full synchronization utilizing event streams will arrive in DexieCable 3.0.
7
7
 
8
- DexieCable ships a single `DexieChannel` with a query DSL that mirrors the Dexie.js API, letting you push database mutations from the server to the client in real time. It also gives you a [`syncs_to_dexie`](#syncs_to_dexie--automatic-model-syncing) ActiveRecord macro for automatic change syncing.
8
+ DexieCable gives your ActionCable channel a query DSL that mirrors the Dexie.js API, letting you push database mutations from the server to the client in real time. It also gives you a [`syncs_to_dexie`](#syncs_to_dexie--automatic-model-syncing) ActiveRecord macro for automatic change syncing.
9
9
 
10
10
  Push Dexie table updates to a client from anywhere on the server:
11
11
 
@@ -73,7 +73,14 @@ setConsumer(createConsumer("wss://example.com/cable"));
73
73
 
74
74
  ### DexieChannel
75
75
 
76
- DexieCable ships one channel — `DexieChannel` so you don't need to define channels yourself. Every Dexie broadcast goes through it, and you can reopen it to add custom actions or push initial data.
76
+ Create a `DexieChannel` in your app and `include DexieCable` in it. Every Dexie broadcast goes through this channel:
77
+
78
+ ```ruby
79
+ # app/channels/dexie_channel.rb
80
+ class DexieChannel < ApplicationCable::Channel
81
+ include DexieCable
82
+ end
83
+ ```
77
84
 
78
85
  Stream tokens are signed with the application secret, so a client can only subscribe to streams the server has issued for it:
79
86
 
@@ -107,13 +114,15 @@ subscription.removeStream(userStream);
107
114
  subscription.removeAllStreams();
108
115
  ```
109
116
 
110
- #### Extending DexieChannel
117
+ #### Customizing DexieChannel
111
118
 
112
- To add custom actions or push initial data, reopen `DexieChannel` in your app:
119
+ Add custom actions or push initial data directly on your channel:
113
120
 
114
121
  ```ruby
115
- # config/initializers/dexiecable.rb
116
- class DexieCable::DexieChannel
122
+ # app/channels/dexie_channel.rb
123
+ class DexieChannel < ApplicationCable::Channel
124
+ include DexieCable
125
+
117
126
  # Push a snapshot when a private stream is added.
118
127
  def subscribed_to(record, params)
119
128
  case record
@@ -131,7 +140,7 @@ class DexieCable::DexieChannel
131
140
  end
132
141
  ```
133
142
 
134
- The client still subscribes to `DexieCable::DexieChannel`, so no extra client config is needed:
143
+ The client subscribes to `DexieChannel` by default:
135
144
 
136
145
  ```js
137
146
  const subscription = subscribe(db);
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DexieCable
4
+ # Include this in an ActionCable channel to add Dexie broadcasting and
5
+ # streaming:
6
+ #
7
+ # class DexieChannel < ApplicationCable::Channel
8
+ # include DexieCable
9
+ #
10
+ # # Push initial data when a private stream is added.
11
+ # def subscribed_to(record, params)
12
+ # case record
13
+ # when User
14
+ # table("notifications").bulkAdd(record.notifications.map(&:as_json_for_dexie))
15
+ # end
16
+ # end
17
+ # end
18
+ #
19
+ # The client subscribes to that channel and adds streams dynamically:
20
+ #
21
+ # subscribe(db) # subscribes to "DexieChannel"
22
+ # subscription.addStream(DexieChannel.stream_token_for(current_user))
23
+ # subscription.addPublicStream("feed")
24
+ #
25
+ extend ActiveSupport::Concern
26
+
27
+ PUBLIC_STREAM_PREFIX = "public:"
28
+
29
+ included do
30
+ public :transmit
31
+ end
32
+
33
+ class_methods do
34
+ # Open a scoped channel for broadcasting to a specific recipient.
35
+ #
36
+ # A String recipient is a public stream name (namespaced under
37
+ # +public:+); any other recipient targets that recipient's own stream.
38
+ def [](recipient)
39
+ target = recipient.is_a?(String) ? "#{PUBLIC_STREAM_PREFIX}#{recipient}" : recipient
40
+ ScopedChannel.new(self, target)
41
+ end
42
+
43
+ # Returns a signed token for +target+ (a record or other private
44
+ # target). Send it to the client, which passes it to
45
+ # +subscription.addStream+.
46
+ def stream_token_for(target)
47
+ verifier.generate(target.respond_to?(:to_gid_param) ? target.to_gid_param : target.to_s)
48
+ end
49
+
50
+ def verifier
51
+ @verifier ||= Rails.application.message_verifier("dexiecable:streams")
52
+ end
53
+ end
54
+
55
+ # Build a query against a Dexie table, transmitted to all subscribers
56
+ # of this channel.
57
+ def table(name)
58
+ Query.new(self, name)
59
+ end
60
+
61
+ def subscribed
62
+ # Streams are added dynamically via +add_stream+.
63
+ end
64
+
65
+ def add_stream(data)
66
+ target = verified_target(data["stream"])
67
+ return unless target
68
+
69
+ stream_for target
70
+ params = data.except("action", "stream").with_indifferent_access
71
+ subscribed_to(resolve_subscribe_target(target), params)
72
+ end
73
+
74
+ def remove_stream(data)
75
+ target = verified_target(data["stream"])
76
+ return unless target
77
+
78
+ stop_stream_from self.class.broadcasting_for(target)
79
+ end
80
+
81
+ def remove_all_streams(_data)
82
+ stop_all_streams
83
+ end
84
+
85
+ def add_public_stream(data)
86
+ name = data["stream"].to_s
87
+ stream_from public_stream_name(name) if name.present?
88
+ end
89
+
90
+ def remove_public_stream(data)
91
+ name = data["stream"].to_s
92
+ stop_stream_from public_stream_name(name) if name.present?
93
+ end
94
+
95
+ # Override to push initial data when a private stream is added. +record+
96
+ # is the resolved target and +params+ are any extra params sent from the
97
+ # client.
98
+ def subscribed_to(_record, _params)
99
+ end
100
+
101
+ private
102
+
103
+ def public_stream_name(name)
104
+ self.class.broadcasting_for("#{PUBLIC_STREAM_PREFIX}#{name}")
105
+ end
106
+
107
+ def verified_target(token)
108
+ return unless token.present?
109
+
110
+ self.class.verifier.verify(token)
111
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
112
+ nil
113
+ end
114
+
115
+ def resolve_subscribe_target(target)
116
+ target.start_with?("gid://") ? GlobalID::Locator.locate(target) : target
117
+ end
118
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DexieCable
4
- VERSION = "2.0.0.alpha3"
4
+ VERSION = "2.0.0.alpha4"
5
5
  end
data/lib/dexiecable.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "dexiecable/version"
4
+ require_relative "dexiecable/concern"
4
5
  require_relative "dexiecable/scoped_channel"
5
6
  require_relative "dexiecable/query"
6
7
  require_relative "dexiecable/active_record_ext"
7
- require_relative "dexiecable/dexie_channel" if defined?(ActionCable::Channel::Base)
8
8
  require_relative "dexiecable/railtie" if defined?(Rails::Railtie)
9
9
 
10
10
  module DexieCable
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dexiecable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha3
4
+ version: 2.0.0.alpha4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Buhrmester
@@ -50,7 +50,7 @@ files:
50
50
  - README.md
51
51
  - lib/dexiecable.rb
52
52
  - lib/dexiecable/active_record_ext.rb
53
- - lib/dexiecable/dexie_channel.rb
53
+ - lib/dexiecable/concern.rb
54
54
  - lib/dexiecable/query.rb
55
55
  - lib/dexiecable/railtie.rb
56
56
  - lib/dexiecable/scoped_channel.rb
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DexieCable
4
- # The single channel through which all Dexie transfer happens.
5
- #
6
- # Broadcasts are scoped to a recipient with +DexieChannel[recipient]+ or
7
- # +DexieChannel.broadcast_to+. String recipients are public stream names;
8
- # any other recipient (e.g. a record) targets a private, signed stream.
9
- #
10
- # # private (signed)
11
- # subscription.addStream(DexieChannel.stream_token_for(current_user))
12
- #
13
- # # public (no signature, namespaced under "public:")
14
- # subscription.addPublicStream("feed")
15
- #
16
- # Reopen this channel in your app to push initial data or add custom
17
- # actions:
18
- #
19
- # class DexieCable::DexieChannel
20
- # def subscribed_to(record, params)
21
- # case record
22
- # when User
23
- # table("notifications").bulkAdd(record.notifications.map(&:as_json_for_dexie))
24
- # end
25
- # end
26
- #
27
- # def my_custom_action(data)
28
- # # ...
29
- # end
30
- # end
31
- #
32
- class DexieChannel < ActionCable::Channel::Base
33
- PUBLIC_STREAM_PREFIX = "public:"
34
-
35
- public :transmit
36
-
37
- # Open a scoped channel for broadcasting to a specific recipient.
38
- #
39
- # A String recipient is a public stream name (namespaced under
40
- # +public:+); any other recipient targets that recipient's own stream.
41
- #
42
- # DexieChannel[current_user].table("notifications").add(notification)
43
- # DexieChannel["feed"].table("announcements").add(announcement)
44
- #
45
- def self.[](recipient)
46
- target = recipient.is_a?(String) ? "#{PUBLIC_STREAM_PREFIX}#{recipient}" : recipient
47
- ScopedChannel.new(self, target)
48
- end
49
-
50
- # Build a query against a Dexie table, transmitted to all subscribers
51
- # of this channel.
52
- #
53
- # table("messages").where(:room_id).equals(room.id).add(message)
54
- #
55
- def table(name)
56
- Query.new(self, name)
57
- end
58
-
59
- # Returns a signed token for +target+ (a record or other private
60
- # target). Send it to the client, which passes it to
61
- # +subscription.addStream+.
62
- def self.stream_token_for(target)
63
- verifier.generate(target.respond_to?(:to_gid_param) ? target.to_gid_param : target.to_s)
64
- end
65
-
66
- def self.verifier
67
- @verifier ||= Rails.application.message_verifier("dexiecable:streams")
68
- end
69
-
70
- def subscribed
71
- # Streams are added dynamically via +add_stream+.
72
- end
73
-
74
- def add_stream(data)
75
- target = verified_target(data["stream"])
76
- return unless target
77
-
78
- stream_for target
79
- params = data.except("action", "stream").with_indifferent_access
80
- subscribed_to(resolve_subscribe_target(target), params)
81
- end
82
-
83
- def remove_stream(data)
84
- target = verified_target(data["stream"])
85
- return unless target
86
-
87
- stop_stream_from self.class.broadcasting_for(target)
88
- end
89
-
90
- def remove_all_streams(_data)
91
- stop_all_streams
92
- end
93
-
94
- def add_public_stream(data)
95
- name = data["stream"].to_s
96
- stream_from public_stream_name(name) if name.present?
97
- end
98
-
99
- def remove_public_stream(data)
100
- name = data["stream"].to_s
101
- stop_stream_from public_stream_name(name) if name.present?
102
- end
103
-
104
- # Override by reopening the channel to push initial data when a private
105
- # stream is added. +record+ is the resolved target and +params+ are any
106
- # extra params sent from the client.
107
- def subscribed_to(_record, _params)
108
- end
109
-
110
- private
111
-
112
- def public_stream_name(name)
113
- self.class.broadcasting_for("#{PUBLIC_STREAM_PREFIX}#{name}")
114
- end
115
-
116
- def verified_target(token)
117
- return unless token.present?
118
-
119
- self.class.verifier.verify(token)
120
- rescue ActiveSupport::MessageVerifier::InvalidSignature
121
- nil
122
- end
123
-
124
- def resolve_subscribe_target(target)
125
- target.start_with?("gid://") ? GlobalID::Locator.locate(target) : target
126
- end
127
- end
128
- end