dexiecable 0.1.9 → 1.0.0

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: 390eddcb71f97e5e8755696c49a8ce2417e7f8636e0e43be8742acd4f31f4b59
4
- data.tar.gz: 2058ae8d76759483bc047b4e74622154335eb55fa2124052f518f51d14539a0d
3
+ metadata.gz: b462191000153b424593099689d503a34152404f08cd33911f8db01a61d154cf
4
+ data.tar.gz: 3fd86cd2c4b9cb4a73b43fc0fee7e686a91a9c4d1a1e390cc544d87f0dddbdc0
5
5
  SHA512:
6
- metadata.gz: 9057f8f5bdbb694a2673c47773db75d2d99a0f3383043908b51152f3f6279a4aabae9f4436dffeebd31e6882d684ca9f505f7a30e5bf1942de89787199a07589
7
- data.tar.gz: 39b6503f4ac13bf8abbaae9352ea6b2355bf34f3210dfd2cd99d87413a39a606f389f39236cafca0143aa94621f8bb0be5835fb34c08027cca0d0c2a7ac6feac
6
+ metadata.gz: 933c9999fa8d60d04fb3571a094568f1fe78ace8f3a13de6e48dd18d71471e647a1b33c6eb49e2ec6123321933967a8479a1fb6c10906412b5a518302b141f66
7
+ data.tar.gz: 13751b561bb8f7d57d05ba7639b474c15d3077262a3ec488a8f217f1d5c20be4235757172eac5fa1b5e558af50b5c97d6d253d5a04544c60c80a768f44ab3049
data/README.md CHANGED
@@ -1,9 +1,11 @@
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
+ > The current version of DexieCable is NOT a local-first solution, because it lacks the capability to automatically sync updates back to the server. For now, think of it more as an alternative to turbo streams that works well with component frameworks.
5
+ >
6
+ > Full synchronization utilizing event streams will arrive in DexieCable 2.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 [`syncs_to_dexie`](#syncs_to_dexie--automatic-model-syncing) ActiveRecord macro for automatic change syncing.
8
+ 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_via`](#streams_via--automatic-model-syncing) ActiveRecord macro for automatic change syncing.
7
9
 
8
10
  You can run any Dexie table update directly inside a channel:
9
11
 
@@ -30,11 +32,11 @@ class NotificationsController < ApplicationController
30
32
  end
31
33
  ```
32
34
 
33
- An even more convenient way is to use the `syncs_to_dexie` macro (more info [below](#syncs_to_dexie--automatic-model-syncing))
35
+ An even more convenient way is to use the `streams_via` macro (more info [below](#streams_via--automatic-model-syncing))
34
36
 
35
37
  ```ruby
36
38
  class Notification < ApplicationRecord
37
- syncs_to_dexie via: -> { UserChannel[user] }
39
+ streams_via UserChannel, to: :user
38
40
  end
39
41
  ```
40
42
 
@@ -48,7 +50,7 @@ Add to your `Gemfile`:
48
50
  gem "dexiecable"
49
51
  ```
50
52
 
51
- Then `bundle install`. The Railtie automatically extends `ActiveRecord::Base` with `syncs_to_dexie`.
53
+ Then `bundle install`. The Railtie automatically extends `ActiveRecord::Base` with `streams_via`.
52
54
 
53
55
  ### npm package
54
56
 
@@ -131,33 +133,25 @@ UserChannel[current_user]
131
133
 
132
134
  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
135
 
134
- ### `syncs_to_dexie` — automatic model syncing
136
+ ### `streams_via` — automatic model streaming
135
137
 
136
- Add to any ActiveRecord model. The simplest approach: pass a Proc to `via` that returns a scoped channel.
137
-
138
- ```ruby
139
- class Message < ApplicationRecord
140
- syncs_to_dexie via: -> { UserChannel[current_user] }
141
- end
142
- ```
143
-
144
- You can also pass a channel class with `to:` to have DexieCable handle the scoping:
138
+ Add to any ActiveRecord model. Just provide the channel class and, optionally, the broadcast target.
145
139
 
146
140
  ```ruby
147
141
  class Message < ApplicationRecord
148
142
  # Calls send(:receiver), then broadcasts: UserChannel.broadcast_to(receiver, ...)
149
- syncs_to_dexie via: UserChannel, to: :receiver
143
+ streams_via UserChannel, to: :receiver
150
144
 
151
- # String used directly: UserChannel.broadcast_to("global_feed", ...)
152
- syncs_to_dexie via: UserChannel, to: "global_feed"
145
+ # String used directly: RoomChannel.broadcast_to("public", ...)
146
+ streams_via RoomChannel, to: "public"
153
147
 
154
148
  # Procs are also supported. If an array is returned, multiple broadcasts are made
155
149
  # conversation.users.each { |u| UserChannel.broadcast_to(u, ...) }
156
- syncs_to_dexie via: UserChannel, to: -> { conversation.users }
150
+ streams_via UserChannel, to: -> { conversation.users }
157
151
  end
158
152
  ```
159
153
 
160
- Internally, `syncs_to_dexie` sets up the following ActiveRecord callbacks:
154
+ Internally, `streams_via` sets up the following ActiveRecord callbacks:
161
155
 
162
156
  | Event | Action |
163
157
  |---|---|
@@ -169,21 +163,23 @@ Internally, `syncs_to_dexie` sets up the following ActiveRecord callbacks:
169
163
 
170
164
  | Option | Default | Description |
171
165
  |---|---|---|
172
- | `via:` | *(required)* | A DexieCable channel class, or a Proc that returns a channel or scoped channel (e.g. `-> { UserChannel[current_user] }`) |
173
- | `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. Only used when `via` is a channel class. |
166
+ | *(first argument)* | *(required)* | A DexieCable channel class |
167
+ | `to:` | the record itself | 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. |
174
168
  | `table:` | model's `table_name` | Override the Dexie table name. A Proc is evaluated in the record's context. |
175
169
  | `only:` | `[:create, :update, :destroy]` | Limit which events trigger a sync |
176
170
  | `with:` | `:as_json_for_dexie` | Method name (Symbol) or Proc for serializing records |
177
171
  | `if:` | *(none)* | Symbol (method name) or Proc — only sync when it returns truthy |
178
172
  | `unless:` | *(none)* | Symbol (method name) or Proc — skip sync when it returns truthy |
179
173
 
180
- You can combine multiple `syncs_to_dexie` declarations, each with different conditions:
174
+ You can combine multiple `streams_via` declarations, each with different conditions:
181
175
 
182
176
  ```ruby
183
177
  class Message < ApplicationRecord
184
- syncs_to_dexie via: -> { UserChannel[current_user] }
185
- syncs_to_dexie via: -> { BoardChannel[board] },
186
- if: :published?
178
+ streams_via UserChannel, to: -> { sender },
179
+ if: :published?
180
+
181
+ streams_via AdminChannel,
182
+ unless: -> { draft? }
187
183
  end
188
184
  ```
189
185
 
@@ -194,23 +190,23 @@ Override `as_json_for_dexie` in your model, or use the `with` option to specify
194
190
  ```ruby
195
191
  class Message < ApplicationRecord
196
192
  # Using the default as_json_for_dexie override:
197
- syncs_to_dexie via: -> { UserChannel[current_user] }
193
+ streams_via UserChannel, to: :sender
198
194
 
199
195
  def as_json_for_dexie
200
196
  super.merge(room_name: room.name)
201
197
  end
202
198
 
203
199
  # Or use a custom serializer method:
204
- syncs_to_dexie via: AdminChannel,
205
- with: :admin_payload
200
+ streams_via AdminChannel, to: :admin,
201
+ with: :admin_payload
206
202
 
207
203
  def admin_payload
208
204
  attributes.slice("id", "body", "flagged")
209
205
  end
210
206
 
211
207
  # Or a Proc:
212
- syncs_to_dexie via: PublicChannel,
213
- with: -> { { id: id, summary: body.truncate(100) } }
208
+ streams_via PublicChannel,
209
+ with: -> { { id: id, summary: body.truncate(100) } }
214
210
  end
215
211
  ```
216
212
 
@@ -255,7 +251,7 @@ dexie.messages.where("room_id").equals(5).add({ id: 1, text: "hello" })
255
251
 
256
252
  ### Use sequence IDs to avoid data loss
257
253
 
258
- A common pattern to avoid data loss during transient disconnections is using sequence IDs to bridge the offline gap. 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.
254
+ 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.
259
255
 
260
256
  To enable this, DexieCable ships its own version of the ActionCable client with one key
261
257
  extension: **channel params can be functions**. When a param value is a
@@ -286,7 +282,7 @@ class RoomChannel < ApplicationChannel:Base
286
282
  end
287
283
  ```
288
284
 
289
- You might want to have look at the [Sequenced](https://github.com/derrickreimer/sequenced) gem to automatically add sequence IDs to your records.
285
+ 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.
290
286
 
291
287
  ### Multi-user environments
292
288
 
@@ -9,24 +9,21 @@ module DexieCable
9
9
  # DexieCable channel.
10
10
  #
11
11
  # class Message < ApplicationRecord
12
- # syncs_to_dexie via: -> { UserChannel[current_user] }
13
- # syncs_to_dexie via: PublicChannel
14
- # syncs_to_dexie via: UserChannel, to: :user
15
- # syncs_to_dexie via: UserChannel, to: -> { conversation.users }
16
- # syncs_to_dexie via: -> { tenant_channel }
12
+ # streams_via UserChannel, to: :sender
13
+ # streams_via UserChannel, to: "global_feed"
14
+ # streams_via UserChannel, to: -> { conversation.users }
15
+ # streams_via PublicChannel
17
16
  # end
18
17
  #
19
- # @param via [Class, Proc] A DexieCable channel class, or a Proc
20
- # evaluated in record context that returns a channel
21
- # class or a scoped channel (e.g.
22
- # +-> { UserChannel[current_user] }+). When +to+
23
- # is given, each recipient is mapped through
24
- # +via[to]+ to produce scoped channels.
18
+ # @param via [Class] A DexieCable channel class, passed as the
19
+ # first positional argument. Each recipient is
20
+ # mapped through +via[to]+ to produce scoped
21
+ # channels.
25
22
  # @param to [Proc, Symbol, String] A Proc evaluated in record
26
23
  # context, a Symbol to call via +send+, or a String
27
24
  # used directly as the stream name for +broadcast_to+.
28
25
  # Must return a single recipient or collection of
29
- # recipients.
26
+ # recipients. Defaults to the record itself.
30
27
  # @param table [String, Symbol, Proc] Override the Dexie table name
31
28
  # (defaults to the model's table_name). A Proc is
32
29
  # evaluated in the record's context.
@@ -38,7 +35,7 @@ module DexieCable
38
35
  # returns truthy (evaluated in the record's context).
39
36
  # @param unless [Symbol, Proc] Skip sync if the given method or proc
40
37
  # returns truthy (evaluated in the record's context).
41
- def syncs_to_dexie(via:, to: nil, table: nil, only: nil, with: nil, **options)
38
+ def streams_via(via, to: nil, table: nil, only: nil, with: nil, **options)
42
39
  events = Array(only || %i[create update destroy])
43
40
  conditions = options.slice(:if, :unless)
44
41
  serializer = with || :as_json_for_dexie
@@ -50,7 +47,7 @@ module DexieCable
50
47
  before_destroy :dexie_sync_before_destroy
51
48
 
52
49
  after_commit on: :destroy, **conditions do
53
- Array(resolve_channel(via, to)).each do |channel|
50
+ resolve_channels(via, to).each do |channel|
54
51
  next unless channel
55
52
  channel.table(resolve_table(table)).delete(dexie_destroy_id)
56
53
  end
@@ -59,19 +56,19 @@ module DexieCable
59
56
 
60
57
  if events.include?(:create)
61
58
  after_commit on: :create, **conditions do
62
- Array(resolve_channel(via, to)).each do |channel|
59
+ resolve_channels(via, to).each do |channel|
63
60
  next unless channel
64
- channel.table(resolve_table(table)).add(serialize_record(serializer))
61
+ channel.table(resolve_table(table)).add(resolve(serializer))
65
62
  end
66
63
  end
67
64
  end
68
65
 
69
66
  if events.include?(:update)
70
67
  after_commit on: :update, **conditions do
71
- Array(resolve_channel(via, to)).each do |channel|
68
+ resolve_channels(via, to).each do |channel|
72
69
  next unless channel
73
70
 
74
- changes = serialize_record(serializer).slice(*saved_changes.keys)
71
+ changes = resolve(serializer).slice(*saved_changes.keys)
75
72
  channel.table(resolve_table(table)).update(id, changes)
76
73
  end
77
74
  end
@@ -81,21 +78,16 @@ module DexieCable
81
78
 
82
79
  private
83
80
 
84
- def resolve_channel(via, to = nil)
85
- channel = via.is_a?(Proc) ? instance_exec(&via) : via
86
- if to
87
- recipients = resolve_recipient(to)
88
- Array(recipients).map { |r| channel[r] }
89
- else
90
- channel
91
- end
81
+ def resolve_channels(via, to = nil)
82
+ recipients = to ? resolve(to) : self
83
+ Array(recipients).map { |r| via[r] }
92
84
  end
93
85
 
94
- def resolve_recipient(to)
95
- case to
96
- when Proc then instance_exec(&to)
97
- when Symbol then send(to)
98
- else to
86
+ def resolve(val)
87
+ case val
88
+ when Proc then instance_exec(&val)
89
+ when Symbol then send(val)
90
+ else val
99
91
  end
100
92
  end
101
93
 
@@ -107,14 +99,6 @@ module DexieCable
107
99
  end
108
100
  end
109
101
 
110
- def serialize_record(serializer)
111
- case serializer
112
- when Proc then instance_exec(&serializer)
113
- when Symbol then send(serializer)
114
- else serializer
115
- end
116
- end
117
-
118
102
  # Override in your model to customise the payload synced to Dexie.
119
103
  def as_json_for_dexie
120
104
  as_json
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DexieCable
4
- VERSION = "0.1.9"
4
+ VERSION = "1.0.0"
5
5
  end
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.1.9
4
+ version: 1.0.0
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 (syncs_to_dexie) for automatic change
42
+ in real time. Includes an ActiveRecord macro (streams_via) for automatic change
43
43
  syncing.
44
44
  email:
45
45
  - stefan@buhrmi.de
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  - !ruby/object:Gem::Version
76
76
  version: '0'
77
77
  requirements: []
78
- rubygems_version: 4.0.8
78
+ rubygems_version: 4.0.16
79
79
  specification_version: 4
80
80
  summary: Run Dexie.js IndexedDB operations from your Rails ActionCable channels.
81
81
  test_files: []