dexiecable 0.2.0 → 2.0.0.alpha1

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: d0959acfdf7865fc834899dd5ec5f14c5fbc85bdbc16a874f269bca2688d2579
4
- data.tar.gz: 9e1b0ea9fb9a89c8289aa0faa77b47c6cdd09bb6fd336eb14c9292fc7c67d0a1
3
+ metadata.gz: 5262c05d144bc7c002152fd8da1918e87421f4ec81a060100b71c57237c152be
4
+ data.tar.gz: 339cf7b68cd9923b0b20b4a74c0caf563699be7ff8fbaf32ace71728a87fc787
5
5
  SHA512:
6
- metadata.gz: a7106c7ed40f0fcf680109f1f476f4195e911836633766c0706d84852621f8a36b76a7eeaa78b2b9fb09ffa5f750a0147fae11a73fc4d8028e75b00840a77799
7
- data.tar.gz: 8f78e58b67faef035cf225ce08d79f4ab0b954123fe76385e7f458e995f955f4880827b3c62353b3763ab888b57f47af4d8954978758fb4edbf5e50601d68ec5
6
+ metadata.gz: e315b48c4d222dd5c4fb04056dded3f61d9d76584dfc7ee02d1645b6d28521120130b945cda897ca6682b7d6483232a3f6b92a3220bc417dc9fb6d8718169416
7
+ data.tar.gz: a5c155688bd21d6970842b86f28d2f9e4d901561fb5e786a59ed3447ae7b96db083d5b63e1d724b7fc8c5b7c2428661d713b00a5dc41a5c5b0f2a0f62ca568cc
data/README.md CHANGED
@@ -1,40 +1,28 @@
1
1
  # DexieCable
2
2
 
3
3
  > [!NOTE]
4
- > DexieCable is NOT a local-first solution, because it lacks the capability to automatically sync updates back to the server (this might be added later). For now, think of it as a real-time local-cache solution. Also, check out this [blog post introducing DexieCable](https://dev.to/buhrmi/real-time-rails-without-turbo-modern-reactive-uis-with-inertia-and-dexiecable-4lge).
4
+ > DexieCable is NOT meant to be a local-first solution. It has no automatic capability to sync updates back to the server. For now, think of it as an alternative to Turbo Streams built with component frameworks (Vue, React, Svelte, etc) in mind.
5
+ >
6
+ > Full synchronization utilizing event streams will arrive in DexieCable 3.0.
5
7
 
6
- DexieCable augments ActionCable channels 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 [`streams_to_dexie`](#streams_to_dexie--automatic-model-syncing) ActiveRecord macro for automatic change syncing.
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.
7
9
 
8
- You can run any Dexie table update directly inside a channel:
9
-
10
- ```ruby
11
- class UserChannel < ApplicationCable::Channel
12
- include DexieCable
13
-
14
- def subscribed
15
- stream_for current_user
16
- recent_notifications = current_user.notifications.last(10)
17
- table("notifications").bulkAdd(recent_notifications)
18
- end
19
- end
20
- ```
21
-
22
- Or from inside a controller:
10
+ Push Dexie table updates to a client from anywhere on the server:
23
11
 
24
12
  ```ruby
25
13
  class NotificationsController < ApplicationController
26
14
  def create
27
15
  notification = current_user.notifications.create!(notification_params)
28
- UserChannel[current_user].table("notifications").add(notification)
16
+ DexieChannel[current_user].table("notifications").add(notification)
29
17
  end
30
18
  end
31
19
  ```
32
20
 
33
- An even more convenient way is to use the `streams_to_dexie` macro (more info [below](#streams_to_dexie--automatic-model-syncing))
21
+ Or sync model changes automatically with the `syncs_to_dexie` macro (more info [below](#syncs_to_dexie--automatic-model-syncing))
34
22
 
35
23
  ```ruby
36
24
  class Notification < ApplicationRecord
37
- streams_to_dexie via: UserChannel, to: :user
25
+ syncs_to_dexie via: :user
38
26
  end
39
27
  ```
40
28
 
@@ -48,7 +36,7 @@ Add to your `Gemfile`:
48
36
  gem "dexiecable"
49
37
  ```
50
38
 
51
- Then `bundle install`. The Railtie automatically extends `ActiveRecord::Base` with `streams_to_dexie`.
39
+ Then `bundle install`. The Railtie automatically extends `ActiveRecord::Base` with `syncs_to_dexie`.
52
40
 
53
41
  ### npm package
54
42
 
@@ -64,7 +52,9 @@ Pass your Dexie database as the first argument to `subscribe()`:
64
52
  import { subscribe } from "dexiecable";
65
53
  import { db } from "./db";
66
54
 
67
- subscribe(db, "UserChannel");
55
+ const subscription = subscribe(db);
56
+ // Stream tokens come from the server: DexieChannel.stream_token_for(target)
57
+ subscription.addStream(streamToken);
68
58
  ```
69
59
 
70
60
  A consumer is lazily created on the first `subscribe()` call. If you need to access or set the consumer explicitly, use `getConsumer()` and `setConsumer()`:
@@ -81,24 +71,69 @@ setConsumer(createConsumer("wss://example.com/cable"));
81
71
 
82
72
  ## Usage
83
73
 
84
- ### `include DexieCable` in a channel
74
+ ### DexieChannel
75
+
76
+ DexieCable ships one channel — `DexieChannel` — so you never define your own channels or include anything. Every Dexie broadcast goes through it.
77
+
78
+ Stream tokens are signed with the application secret, so a client can only subscribe to streams the server has issued for it:
85
79
 
86
80
  ```ruby
87
- class UserChannel < ApplicationCable::Channel
88
- include DexieCable
81
+ DexieChannel.stream_token_for(target)
82
+ ```
89
83
 
90
- def subscribed
91
- stream_for current_user
92
- end
84
+ For an ActiveRecord model it signs the record's GlobalID:
85
+
86
+ ```ruby
87
+ DexieChannel.stream_token_for(current_user)
88
+ # => "eyJkYXRhIjoiZGV4aWVfY2FibGU6ZGV4aWVfY2hhbm5lbDpnaWQ6Ly9hcHAvVXNlci8xIn0=--signature"
89
+ ```
90
+
91
+ Send that token to the client (render it in a view, return it from an endpoint, etc.) and add it to the subscription:
92
+
93
+ ```js
94
+ const subscription = subscribe(db);
95
+ subscription.addStream(userStream);
96
+ ```
97
+
98
+ The client now receives every mutation broadcast to `current_user`. To stop listening:
99
+
100
+ ```js
101
+ subscription.removeStream(userStream);
102
+ ```
103
+
104
+ `addStream`/`removeStream` perform `add_stream`/`remove_stream` on `DexieChannel`, which verifies the token and then `stream_from`/`stop_stream_from` the decoded identifier. `removeAllStreams()` performs `remove_all_streams`, stopping every current stream — handy on logout:
105
+
106
+ ```js
107
+ subscription.removeAllStreams();
108
+ ```
109
+
110
+ #### Public streams
111
+
112
+ For data that's public (a global feed, announcements, etc.), skip the signature. Use a string target — it's namespaced under `public:` automatically:
113
+
114
+ ```ruby
115
+ DexieChannel["feed"].table("announcements").add(announcement)
116
+
117
+ # or, on a model:
118
+ class Announcement < ApplicationRecord
119
+ syncs_to_dexie via: "feed"
93
120
  end
94
121
  ```
95
122
 
96
- This gives you:
123
+ Then subscribe by name — no token required:
97
124
 
98
- | Method | Description |
99
- |---|---|
100
- | `self.[](to)` | Returns a `ScopedChannel` bound to a recipient. `UserChannel[current_user]` |
101
- | `table(name)` | Starts a query chain. `table("messages")` |
125
+ ```js
126
+ subscription.addPublicStream("feed");
127
+ subscription.removePublicStream("feed");
128
+ ```
129
+
130
+ Public streams are namespaced under `public:`, so this path can never reach a signed (private) stream.
131
+
132
+ `DexieChannel[target]` returns a scoped channel for broadcasting to one recipient:
133
+
134
+ ```ruby
135
+ DexieChannel[current_user].table("notifications").add(notification)
136
+ ```
102
137
 
103
138
  ### Chaining Dexie operations
104
139
 
@@ -106,24 +141,24 @@ Any Dexie.js write operation triggers an immediate broadcast:
106
141
 
107
142
  ```ruby
108
143
  # Single insert
109
- UserChannel[current_user].table("messages").add(id: 1, text: "hello")
144
+ DexieChannel[current_user].table("messages").add(id: 1, text: "hello")
110
145
 
111
146
  # Bulk insert
112
- UserChannel[current_user].table("messages").bulkAdd(messages)
147
+ DexieChannel[current_user].table("messages").bulkAdd(messages)
113
148
 
114
149
  # Update (using modify)
115
- UserChannel[current_user]
150
+ DexieChannel[current_user]
116
151
  .table("messages")
117
152
  .where(:id).equals(msg.id)
118
153
  .modify(read: true)
119
154
 
120
155
  # Update (using update)
121
- UserChannel[current_user]
156
+ DexieChannel[current_user]
122
157
  .table("messages")
123
158
  .update(msg.id, text: "updated text")
124
159
 
125
160
  # Delete
126
- UserChannel[current_user]
161
+ DexieChannel[current_user]
127
162
  .table("messages")
128
163
  .where(:room_id).equals(room.id)
129
164
  .delete()
@@ -131,25 +166,32 @@ UserChannel[current_user]
131
166
 
132
167
  The full query chain is serialized as JSON and sent over ActionCable. The JS client replays every method call against the local Dexie database in order.
133
168
 
134
- ### `streams_to_dexie` — automatic model syncing
169
+ ### `syncs_to_dexie` — automatic model streaming
135
170
 
136
- Add to any ActiveRecord model. Just provide the channel class and the broadcast target.
171
+ Add to any ActiveRecord model. Optionally provide the broadcast target.
137
172
 
138
173
  ```ruby
139
174
  class Message < ApplicationRecord
140
- # Calls send(:receiver), then broadcasts: UserChannel.broadcast_to(receiver, ...)
141
- streams_to_dexie via: UserChannel, to: :receiver
175
+ # Calls send(:receiver), then broadcasts: DexieChannel.broadcast_to(receiver, ...)
176
+ syncs_to_dexie via: :receiver
142
177
 
143
- # String used directly: UserChannel.broadcast_to("global_feed", ...)
144
- streams_to_dexie via: UserChannel, to: "global_feed"
178
+ # String = public stream (subscribe via addPublicStream("public"))
179
+ syncs_to_dexie via: "public"
145
180
 
146
181
  # Procs are also supported. If an array is returned, multiple broadcasts are made
147
- # conversation.users.each { |u| UserChannel.broadcast_to(u, ...) }
148
- streams_to_dexie via: UserChannel, to: -> { conversation.users }
182
+ # conversation.users.each { |u| DexieChannel.broadcast_to(u, ...) }
183
+ syncs_to_dexie via: -> { conversation.users }
149
184
  end
150
185
  ```
151
186
 
152
- Internally, `streams_to_dexie` sets up the following ActiveRecord callbacks:
187
+ Broadcasts go out over `DexieChannel`, the channel DexieCable provides. On the client, subscribe to it and add the stream token returned by `DexieChannel.stream_token_for(target)`:
188
+
189
+ ```js
190
+ const subscription = subscribe(db);
191
+ subscription.addStream(streamIdentifier);
192
+ ```
193
+
194
+ Internally, `syncs_to_dexie` sets up the following ActiveRecord callbacks:
153
195
 
154
196
  | Event | Action |
155
197
  |---|---|
@@ -161,23 +203,21 @@ Internally, `streams_to_dexie` sets up the following ActiveRecord callbacks:
161
203
 
162
204
  | Option | Default | Description |
163
205
  |---|---|---|
164
- | `via:` | *(required)* | A DexieCable channel class |
165
- | `to:` | *(none)* | The stream target passed to `broadcast_to`. Symbol → calls `send`. String → used as-is. Proc → evaluated in record context. Returns a single recipient or collection. |
206
+ | `via:` | the record itself | The stream target. Symbol → calls `send` (a record, signed). String public stream name. Proc → evaluated in record context. Returns a single recipient or collection. |
166
207
  | `table:` | model's `table_name` | Override the Dexie table name. A Proc is evaluated in the record's context. |
167
208
  | `only:` | `[:create, :update, :destroy]` | Limit which events trigger a sync |
168
209
  | `with:` | `:as_json_for_dexie` | Method name (Symbol) or Proc for serializing records |
169
210
  | `if:` | *(none)* | Symbol (method name) or Proc — only sync when it returns truthy |
170
211
  | `unless:` | *(none)* | Symbol (method name) or Proc — skip sync when it returns truthy |
171
212
 
172
- You can combine multiple `streams_to_dexie` declarations, each with different conditions:
213
+ You can combine multiple `syncs_to_dexie` declarations, each with different conditions:
173
214
 
174
215
  ```ruby
175
216
  class Message < ApplicationRecord
176
- streams_to_dexie via: UserChannel, to: -> { sender },
217
+ syncs_to_dexie via: -> { sender },
177
218
  if: :published?
178
219
 
179
- streams_to_dexie via: AdminChannel,
180
- unless: -> { draft? }
220
+ syncs_to_dexie unless: -> { draft? }
181
221
  end
182
222
  ```
183
223
 
@@ -188,14 +228,14 @@ Override `as_json_for_dexie` in your model, or use the `with` option to specify
188
228
  ```ruby
189
229
  class Message < ApplicationRecord
190
230
  # Using the default as_json_for_dexie override:
191
- streams_to_dexie via: UserChannel, to: :sender
231
+ syncs_to_dexie via: :sender
192
232
 
193
233
  def as_json_for_dexie
194
234
  super.merge(room_name: room.name)
195
235
  end
196
236
 
197
237
  # Or use a custom serializer method:
198
- streams_to_dexie via: AdminChannel, to: :admin,
238
+ syncs_to_dexie via: :admin,
199
239
  with: :admin_payload
200
240
 
201
241
  def admin_payload
@@ -203,8 +243,7 @@ class Message < ApplicationRecord
203
243
  end
204
244
 
205
245
  # Or a Proc:
206
- streams_to_dexie via: PublicChannel,
207
- with: -> { { id: id, summary: body.truncate(100) } }
246
+ syncs_to_dexie with: -> { { id: id, summary: body.truncate(100) } }
208
247
  end
209
248
  ```
210
249
 
@@ -245,60 +284,6 @@ The JS side replays it as:
245
284
  dexie.messages.where("room_id").equals(5).add({ id: 1, text: "hello" })
246
285
  ```
247
286
 
248
- ## Recipies
249
-
250
- ### Use sequence IDs to avoid data loss
251
-
252
- A common pattern to avoid data loss during transient disconnections is using sequence IDs to bridge the offline gap and detect gaps in transmitted records. When a connection drops, updates continue on the server. Sending the client’s latest known sequence ID upon reconnect allows the backend to query and stream only the records missed while offline.
253
-
254
- To enable this, DexieCable ships its own version of the ActionCable client with one key
255
- extension: **channel params can be functions**. When a param value is a
256
- function, it is called and awaited at subscribe time — use this to submit the latest known sequence ID on connection:
257
-
258
- ```js
259
- import { subscribe } from "dexiecable";
260
- import { db, getLastSeqId } from './database';
261
-
262
- const roomId = 123;
263
-
264
- subscribe(db, {
265
- channel: "RoomChannel",
266
- room_id: roomId,
267
- seq_id: () => getLastSeqId(roomId) // evaluated fresh on each reconnect
268
- });
269
- ```
270
-
271
- Send missed messages on reconnection:
272
-
273
- ```ruby
274
- class RoomChannel < ApplicationChannel:Base
275
- def subscribed
276
- stream_from "room:#{params[:room_id]}"
277
- missed_messages = room.messages.where("seq_id > ?", params[:seq_id])
278
- table("messages").bulkAdd(missed_messages)
279
- end
280
- end
281
- ```
282
-
283
- To add Sequence IDs, you might want to have look at the [Sequenced](https://github.com/derrickreimer/sequenced). Another option is to use [AnyCable](https://docs.anycable.io/rails/getting_started) since it guarantees deliveries of ActionCable messages.
284
-
285
- ### Multi-user environments
286
-
287
- In multi-user or multi-tenant applications, you can isolate records by binding different subscription channels to separate Dexie database instances. This prevents local data leaks between user accounts and keeps private user data separate from public or shared feeds.
288
-
289
- ```js
290
-
291
- import Dexie from 'dexie'
292
- import { subscribe } from 'dexiecable'
293
-
294
- const userDB = new Dexie("user_"+userId)
295
- const sharedDB = new Dexie("shared")
296
-
297
- subscribe(userDB, 'UserChannel')
298
- subscribe(sharedDB, 'PublicChannel')
299
-
300
- ```
301
-
302
287
  ## License
303
288
 
304
289
  MIT
@@ -5,26 +5,21 @@ module DexieCable
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  class_methods do
8
- # Declares that this model syncs changes to Dexie (IndexedDB) via a
9
- # DexieCable channel.
8
+ # Declares that this model syncs changes to Dexie (IndexedDB) via
9
+ # DexieChannel.
10
10
  #
11
11
  # class Message < ApplicationRecord
12
- # streams_to_dexie via: UserChannel, to: :sender
13
- # streams_to_dexie via: UserChannel, to: "global_feed"
14
- # streams_to_dexie via: UserChannel, to: -> { conversation.users }
15
- # streams_to_dexie via: PublicChannel
12
+ # syncs_to_dexie via: :sender
13
+ # syncs_to_dexie via: "global_feed"
14
+ # syncs_to_dexie via: -> { conversation.users }
15
+ # syncs_to_dexie
16
16
  # end
17
17
  #
18
- # @param via [Class] A DexieCable channel class. When +to+
19
- # is given, each recipient is mapped through
20
- # +via[to]+ to produce scoped channels.
21
- # Without +to+, +via+ is used directly as an
22
- # unscoped channel.
23
- # @param to [Proc, Symbol, String] A Proc evaluated in record
18
+ # @param via [Proc, Symbol, String] A Proc evaluated in record
24
19
  # context, a Symbol to call via +send+, or a String
25
- # used directly as the stream name for +broadcast_to+.
26
- # Must return a single recipient or collection of
27
- # recipients.
20
+ # naming a public stream. Must return a single
21
+ # recipient or collection of recipients. Defaults to
22
+ # the record itself.
28
23
  # @param table [String, Symbol, Proc] Override the Dexie table name
29
24
  # (defaults to the model's table_name). A Proc is
30
25
  # evaluated in the record's context.
@@ -36,19 +31,19 @@ module DexieCable
36
31
  # returns truthy (evaluated in the record's context).
37
32
  # @param unless [Symbol, Proc] Skip sync if the given method or proc
38
33
  # returns truthy (evaluated in the record's context).
39
- def streams_to_dexie(via:, to: nil, table: nil, only: nil, with: nil, **options)
34
+ def syncs_to_dexie(via: nil, table: nil, only: nil, with: nil, **options)
40
35
  events = Array(only || %i[create update destroy])
41
36
  conditions = options.slice(:if, :unless)
42
37
  serializer = with || :as_json_for_dexie
43
38
 
44
39
  @dexie_sync_configs ||= []
45
- @dexie_sync_configs << { via: via, to: to, table: table, only: events, with: serializer, **conditions }
40
+ @dexie_sync_configs << { via: via, table: table, only: events, with: serializer, **conditions }
46
41
 
47
42
  if events.include?(:destroy)
48
43
  before_destroy :dexie_sync_before_destroy
49
44
 
50
45
  after_commit on: :destroy, **conditions do
51
- Array(resolve_channel(via, to)).each do |channel|
46
+ resolve_channels(via).each do |channel|
52
47
  next unless channel
53
48
  channel.table(resolve_table(table)).delete(dexie_destroy_id)
54
49
  end
@@ -57,19 +52,19 @@ module DexieCable
57
52
 
58
53
  if events.include?(:create)
59
54
  after_commit on: :create, **conditions do
60
- Array(resolve_channel(via, to)).each do |channel|
55
+ resolve_channels(via).each do |channel|
61
56
  next unless channel
62
- channel.table(resolve_table(table)).add(serialize_record(serializer))
57
+ channel.table(resolve_table(table)).add(resolve(serializer))
63
58
  end
64
59
  end
65
60
  end
66
61
 
67
62
  if events.include?(:update)
68
63
  after_commit on: :update, **conditions do
69
- Array(resolve_channel(via, to)).each do |channel|
64
+ resolve_channels(via).each do |channel|
70
65
  next unless channel
71
66
 
72
- changes = serialize_record(serializer).slice(*saved_changes.keys)
67
+ changes = resolve(serializer).slice(*saved_changes.keys)
73
68
  channel.table(resolve_table(table)).update(id, changes)
74
69
  end
75
70
  end
@@ -79,20 +74,16 @@ module DexieCable
79
74
 
80
75
  private
81
76
 
82
- def resolve_channel(via, to = nil)
83
- if to
84
- recipients = resolve_recipient(to)
85
- Array(recipients).map { |r| via[r] }
86
- else
87
- via
88
- end
77
+ def resolve_channels(via = nil)
78
+ recipients = via ? resolve(via) : self
79
+ Array(recipients).map { |r| DexieChannel[r] }
89
80
  end
90
81
 
91
- def resolve_recipient(to)
92
- case to
93
- when Proc then instance_exec(&to)
94
- when Symbol then send(to)
95
- else to
82
+ def resolve(val)
83
+ case val
84
+ when Proc then instance_exec(&val)
85
+ when Symbol then send(val)
86
+ else val
96
87
  end
97
88
  end
98
89
 
@@ -104,14 +95,6 @@ module DexieCable
104
95
  end
105
96
  end
106
97
 
107
- def serialize_record(serializer)
108
- case serializer
109
- when Proc then instance_exec(&serializer)
110
- when Symbol then send(serializer)
111
- else serializer
112
- end
113
- end
114
-
115
98
  # Override in your model to customise the payload synced to Dexie.
116
99
  def as_json_for_dexie
117
100
  as_json
@@ -0,0 +1,96 @@
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
+ class DexieChannel < ActionCable::Channel::Base
17
+ PUBLIC_STREAM_PREFIX = "public:"
18
+
19
+ public :transmit
20
+
21
+ # Open a scoped channel for broadcasting to a specific recipient.
22
+ #
23
+ # A String recipient is a public stream name (namespaced under
24
+ # +public:+); any other recipient targets that recipient's own stream.
25
+ #
26
+ # DexieChannel[current_user].table("notifications").add(notification)
27
+ # DexieChannel["feed"].table("announcements").add(announcement)
28
+ #
29
+ def self.[](recipient)
30
+ target = recipient.is_a?(String) ? "#{PUBLIC_STREAM_PREFIX}#{recipient}" : recipient
31
+ ScopedChannel.new(self, target)
32
+ end
33
+
34
+ # Build a query against a Dexie table, transmitted to all subscribers
35
+ # of this channel.
36
+ #
37
+ # table("messages").where(:room_id).equals(room.id).add(message)
38
+ #
39
+ def table(name)
40
+ Query.new(self, name)
41
+ end
42
+
43
+ # Returns a signed token for the stream identifier of +target+ (private
44
+ # record-based streams). Send it to the client, which passes it to
45
+ # +subscription.addStream+.
46
+ def self.stream_token_for(target)
47
+ verifier.generate(broadcasting_for(target))
48
+ end
49
+
50
+ def self.verifier
51
+ @verifier ||= Rails.application.message_verifier("dexiecable:streams")
52
+ end
53
+
54
+ def subscribed
55
+ # Streams are added dynamically via +add_stream+.
56
+ end
57
+
58
+ def add_stream(data)
59
+ stream = verified_stream(data["stream"])
60
+ stream_from stream if stream
61
+ end
62
+
63
+ def remove_stream(data)
64
+ stream = verified_stream(data["stream"])
65
+ stop_stream_from stream if stream
66
+ end
67
+
68
+ def remove_all_streams(_data)
69
+ stop_all_streams
70
+ end
71
+
72
+ def add_public_stream(data)
73
+ name = data["stream"].to_s
74
+ stream_from public_stream_name(name) if name.present?
75
+ end
76
+
77
+ def remove_public_stream(data)
78
+ name = data["stream"].to_s
79
+ stop_stream_from public_stream_name(name) if name.present?
80
+ end
81
+
82
+ private
83
+
84
+ def public_stream_name(name)
85
+ self.class.broadcasting_for("#{PUBLIC_STREAM_PREFIX}#{name}")
86
+ end
87
+
88
+ def verified_stream(token)
89
+ return unless token.present?
90
+
91
+ self.class.verifier.verify(token)
92
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
93
+ nil
94
+ end
95
+ end
96
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DexieCable
4
- VERSION = "0.2.0"
4
+ VERSION = "2.0.0.alpha1"
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"
5
4
  require_relative "dexiecable/scoped_channel"
6
5
  require_relative "dexiecable/query"
7
6
  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: 0.2.0
4
+ version: 2.0.0.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Buhrmester
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: '7.0'
40
40
  description: DexieCable augments ActionCable channels with a query DSL that mirrors
41
41
  the Dexie.js API, letting you push database mutations from the server to the client
42
- in real time. Includes an ActiveRecord macro (streams_to_dexie) for automatic change
42
+ in real time. Includes an ActiveRecord macro (syncs_to_dexie) for automatic change
43
43
  syncing.
44
44
  email:
45
45
  - stefan@buhrmi.de
@@ -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/concern.rb
53
+ - lib/dexiecable/dexie_channel.rb
54
54
  - lib/dexiecable/query.rb
55
55
  - lib/dexiecable/railtie.rb
56
56
  - lib/dexiecable/scoped_channel.rb
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DexieCable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- public :transmit
8
-
9
- # Open a scoped channel for broadcasting to a specific recipient.
10
- #
11
- # UserChannel[current_user].table("notifications").add(notification)
12
- #
13
- def self.[](recipient)
14
- ScopedChannel.new(self, recipient)
15
- end
16
-
17
- # Build a query against a Dexie table, transmitted to all subscribers
18
- # of this channel.
19
- #
20
- # table("messages").where(:room_id).equals(room.id).add(message)
21
- #
22
- def table(name)
23
- Query.new(self, name)
24
- end
25
- end
26
- end