dexiecable 2.0.0.alpha2 → 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 +4 -4
- data/README.md +41 -12
- data/lib/dexiecable/concern.rb +118 -0
- data/lib/dexiecable/version.rb +1 -1
- data/lib/dexiecable.rb +1 -1
- metadata +2 -2
- data/lib/dexiecable/dexie_channel.rb +0 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 502a99e32deef9551c55b8cb0bdae2e2d74af751e4f9889543f76ceea6d5a2ce
|
|
4
|
+
data.tar.gz: f42e9bca1c1d05ef287b3b72373e7c264af8ab78c350c092b8ccd9f887aa0270
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
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,23 +114,45 @@ subscription.removeStream(userStream);
|
|
|
107
114
|
subscription.removeAllStreams();
|
|
108
115
|
```
|
|
109
116
|
|
|
110
|
-
####
|
|
117
|
+
#### Customizing DexieChannel
|
|
111
118
|
|
|
112
|
-
|
|
119
|
+
Add custom actions or push initial data directly on your channel:
|
|
113
120
|
|
|
114
121
|
```ruby
|
|
115
|
-
#
|
|
116
|
-
DexieChannel
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
# app/channels/dexie_channel.rb
|
|
123
|
+
class DexieChannel < ApplicationCable::Channel
|
|
124
|
+
include DexieCable
|
|
125
|
+
|
|
126
|
+
# Push a snapshot when a private stream is added.
|
|
127
|
+
def subscribed_to(record, params)
|
|
128
|
+
case record
|
|
129
|
+
when User
|
|
130
|
+
table("notifications").bulkAdd(record.notifications.map(&:as_json_for_dexie))
|
|
131
|
+
when Conversation
|
|
132
|
+
table("messages").bulkAdd(record.messages.where("seq_id > ?", params[:last_seq_id]).map(&:as_json_for_dexie))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Any public method is a custom action the client can perform.
|
|
137
|
+
def mark_as_read(data)
|
|
138
|
+
Message.find(data["id"]).update!(read: true)
|
|
122
139
|
end
|
|
123
140
|
end
|
|
124
141
|
```
|
|
125
142
|
|
|
126
|
-
The
|
|
143
|
+
The client subscribes to `DexieChannel` by default:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
const subscription = subscribe(db);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Pass params from the client when adding a stream:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
subscription.addStream(userStream, { last_seq_id: 100 });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`subscribed_to` runs after the stream is opened, and `table(...)` transmits to just this subscriber — so the snapshot arrives before any live mutation. Custom actions are triggered like any ActionCable action: `subscription.perform("mark_as_read", { id: 42 })`.
|
|
127
156
|
|
|
128
157
|
#### Public streams
|
|
129
158
|
|
|
@@ -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
|
data/lib/dexiecable/version.rb
CHANGED
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.
|
|
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/
|
|
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,119 +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
|
-
# When a private stream is added, +on_subscribe+ is invoked with the
|
|
17
|
-
# channel and the subscribed record, so initial data can be pushed:
|
|
18
|
-
#
|
|
19
|
-
# DexieChannel.on_subscribe = ->(channel, record) do
|
|
20
|
-
# case record
|
|
21
|
-
# when User
|
|
22
|
-
# channel.table("notifications").bulkAdd(record.notifications.map(&:as_json_for_dexie))
|
|
23
|
-
# when Conversation
|
|
24
|
-
# channel.table("messages").bulkAdd(record.messages.map(&:as_json_for_dexie))
|
|
25
|
-
# end
|
|
26
|
-
# end
|
|
27
|
-
#
|
|
28
|
-
class DexieChannel < ActionCable::Channel::Base
|
|
29
|
-
PUBLIC_STREAM_PREFIX = "public:"
|
|
30
|
-
|
|
31
|
-
class_attribute :on_subscribe, instance_accessor: false, default: nil
|
|
32
|
-
|
|
33
|
-
public :transmit
|
|
34
|
-
|
|
35
|
-
# Open a scoped channel for broadcasting to a specific recipient.
|
|
36
|
-
#
|
|
37
|
-
# A String recipient is a public stream name (namespaced under
|
|
38
|
-
# +public:+); any other recipient targets that recipient's own stream.
|
|
39
|
-
#
|
|
40
|
-
# DexieChannel[current_user].table("notifications").add(notification)
|
|
41
|
-
# DexieChannel["feed"].table("announcements").add(announcement)
|
|
42
|
-
#
|
|
43
|
-
def self.[](recipient)
|
|
44
|
-
target = recipient.is_a?(String) ? "#{PUBLIC_STREAM_PREFIX}#{recipient}" : recipient
|
|
45
|
-
ScopedChannel.new(self, target)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Build a query against a Dexie table, transmitted to all subscribers
|
|
49
|
-
# of this channel.
|
|
50
|
-
#
|
|
51
|
-
# table("messages").where(:room_id).equals(room.id).add(message)
|
|
52
|
-
#
|
|
53
|
-
def table(name)
|
|
54
|
-
Query.new(self, name)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Returns a signed token for +target+ (a record or other private
|
|
58
|
-
# target). Send it to the client, which passes it to
|
|
59
|
-
# +subscription.addStream+.
|
|
60
|
-
def self.stream_token_for(target)
|
|
61
|
-
verifier.generate(target.respond_to?(:to_gid_param) ? target.to_gid_param : target.to_s)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def self.verifier
|
|
65
|
-
@verifier ||= Rails.application.message_verifier("dexiecable:streams")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def subscribed
|
|
69
|
-
# Streams are added dynamically via +add_stream+.
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def add_stream(data)
|
|
73
|
-
target = verified_target(data["stream"])
|
|
74
|
-
return unless target
|
|
75
|
-
|
|
76
|
-
stream_for target
|
|
77
|
-
self.class.on_subscribe&.call(self, resolve_subscribe_target(target))
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def remove_stream(data)
|
|
81
|
-
target = verified_target(data["stream"])
|
|
82
|
-
return unless target
|
|
83
|
-
|
|
84
|
-
stop_stream_from self.class.broadcasting_for(target)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def remove_all_streams(_data)
|
|
88
|
-
stop_all_streams
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def add_public_stream(data)
|
|
92
|
-
name = data["stream"].to_s
|
|
93
|
-
stream_from public_stream_name(name) if name.present?
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def remove_public_stream(data)
|
|
97
|
-
name = data["stream"].to_s
|
|
98
|
-
stop_stream_from public_stream_name(name) if name.present?
|
|
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
|
|
119
|
-
end
|