y-rb_actioncable 0.1.1 → 0.1.3
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 +112 -0
- data/lib/y/actioncable/sync.rb +160 -13
- data/lib/y/actioncable/version.rb +1 -1
- data/lib/y/lib0/decoding.rb +3 -7
- data/lib/y/lib0/encoding.rb +1 -1
- data/lib/y/lib0/integer.rb +2 -2
- data/lib/y/lib0/sync.rb +1 -1
- data/lib/y/lib0/typed_array.rb +2 -2
- data/lib/y/sync.rb +6 -6
- data/lib/{yrb-actioncable.rb → y-rb_actioncable.rb} +0 -0
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c1bd91dd64aefce65bac62b72f51bf0441e222fb5c865a45a3257addbc5f4dd
|
4
|
+
data.tar.gz: 48074bedce935ac2adabf6d4f34942bb1c2ba1f08e1d278c3004b508f1b0eedf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ecaf95ff7925f08a035e54f41290fb939f6ae40666af50d736771df103aa14b988b84cd08bbfbfa006320eac72d63f573d3afa1f172f51abcbf28c6b2a06a6c
|
7
|
+
data.tar.gz: 3a734d0f445edfc492901efe1219e72d39b9321e5e00fbe8f56a248882ff6822ab276d294dd219570d51dde8a14ee3f4baf6cb401db7adcd1790eaa4764ca0e6
|
data/README.md
CHANGED
@@ -22,6 +22,118 @@ Or install it yourself as:
|
|
22
22
|
$ gem install y-rb_actioncable
|
23
23
|
```
|
24
24
|
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
`yrb-actioncable` provides a module that can be used to extend the capabilities
|
28
|
+
of a regular channel with a real-time sync mechanism. `yrb-actioncable`
|
29
|
+
implements the same protocol as
|
30
|
+
[`y-websocket`](https://github.com/yjs/y-websocket/blob/master/bin/utils.js).
|
31
|
+
|
32
|
+
It will make sure that a newly connected client will be provided with the
|
33
|
+
current state and also syncs changes from the client to the server.
|
34
|
+
|
35
|
+
```mermaid
|
36
|
+
sequenceDiagram
|
37
|
+
Client->>Server: Subscribe to channel
|
38
|
+
Server->>Client: Successfully subscribed to channel
|
39
|
+
|
40
|
+
Server->>Client: Send server-side document state-vector
|
41
|
+
Client->>Server: Respond with update based on server-side state-vector
|
42
|
+
|
43
|
+
Client->>Server: Send client-side document state-vector
|
44
|
+
Server->>Client: Respond with update based on client-side state-vector
|
45
|
+
|
46
|
+
Client->>Server: Send incremental updates from client
|
47
|
+
Server->>Client: Send incremental updates from server (broadcast)
|
48
|
+
```
|
49
|
+
|
50
|
+
In order to use the above described protocol, someone can simply include the
|
51
|
+
`Sync` module. There are three methods we need to use:
|
52
|
+
|
53
|
+
1. Initiate the connection with initial sync steps: `initiate`
|
54
|
+
1. Integrate any incoming changes: `integrate`
|
55
|
+
1. Broadcast incoming updates to all clients: `sync_to`
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# app/channels/sync_channel.rb
|
59
|
+
class SyncChannel < ApplicationCable::Channel
|
60
|
+
include Y::Actioncable::Sync
|
61
|
+
|
62
|
+
def subscribed
|
63
|
+
# initiate sync & subscribe to updates, with optional persistence mechanism
|
64
|
+
sync_from("document-1")
|
65
|
+
end
|
66
|
+
|
67
|
+
def receive(message)
|
68
|
+
# broadcast update to all connected clients on all servers
|
69
|
+
sync_to("document-1", message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
**⚠️ Attention:** This integration and API eventually change before the final
|
75
|
+
release. The goal for `yrb-actioncable` is simplicity and ease-of-use, and the
|
76
|
+
current implementation requires at least some knowledge about internals.
|
77
|
+
|
78
|
+
### Persistence
|
79
|
+
|
80
|
+
We can also implement a persistence mechanism with `yrb-actioncable` via hooks.
|
81
|
+
This is a quite common use case, and therefore it is relatively simple to add.
|
82
|
+
|
83
|
+
#### Load document
|
84
|
+
|
85
|
+
In order to instantiate the document with some state, `yrb-actioncable` expects
|
86
|
+
the `load` method to be called with a block that returns a full state update for
|
87
|
+
the document. Internally it just calls `Y::Doc#sync(update)`.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class SyncChannel < ApplicationCable::Channel
|
91
|
+
include Y::Actioncable::Sync
|
92
|
+
|
93
|
+
def initialize(connection, identifier, params = nil)
|
94
|
+
super
|
95
|
+
load { |id| load_doc(id) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
#### Persist document
|
101
|
+
|
102
|
+
It is usually desirable to persist updates as soon as they arrive. The method
|
103
|
+
`persist` expects a block that can process a document full state update for the
|
104
|
+
given ID.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
|
108
|
+
class SyncChannel < ApplicationCable::Channel
|
109
|
+
include Y::Actioncable::Sync
|
110
|
+
|
111
|
+
def subscribed
|
112
|
+
stream_for(session, coder: ActiveSupport::JSON) do |_message|
|
113
|
+
persist { |id, update| save_doc(id, update) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
#### Example implementation for `load_doc` and `save_doc`
|
120
|
+
|
121
|
+
We eventually provide storage providers that are easy to use, e.g.
|
122
|
+
[`yrb-redis`](https://github.com/y-crdt/yrb-redis), but you can also implement
|
123
|
+
your own _store_ methods.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
def load_doc(id)
|
127
|
+
data = REDIS.get(id)
|
128
|
+
data = data.unpack("C*") unless data.nil?
|
129
|
+
data
|
130
|
+
end
|
131
|
+
|
132
|
+
def save_doc(id, update)
|
133
|
+
REDIS.set(id, update.pack("C*"))
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
25
137
|
## License
|
26
138
|
|
27
139
|
The gem is available as *open source* under the terms of the
|
data/lib/y/actioncable/sync.rb
CHANGED
@@ -2,12 +2,40 @@
|
|
2
2
|
|
3
3
|
module Y
|
4
4
|
module Actioncable
|
5
|
-
|
5
|
+
# A Sync module for Rails ActionCable channels.
|
6
|
+
#
|
7
|
+
# This module contains a set of utility methods that allows a relatively
|
8
|
+
# convenient implementation of a real-time sync channel. The module
|
9
|
+
# implements the synchronization steps described in
|
10
|
+
# [`y-protocols/sync`](https://github.com/yjs/y-protocols/blob/master/sync.js).
|
11
|
+
#
|
12
|
+
# @example Create a SyncChannel including this module
|
13
|
+
# class SyncChannel
|
14
|
+
# def subscribed
|
15
|
+
# # initiate sync & subscribe to updates, with optional persistence mechanism
|
16
|
+
# sync_for(session) { |id, update| save_doc(id, update) }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def receive(message)
|
20
|
+
# # broadcast update to all connected clients on all servers
|
21
|
+
# sync_to(session, message)
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module Sync
|
6
25
|
extend ActiveSupport::Concern
|
7
26
|
|
27
|
+
CHANNEL_PREFIX = "sync"
|
28
|
+
FIELD_ORIGIN = "origin"
|
29
|
+
FIELD_UPDATE = "update"
|
8
30
|
MESSAGE_SYNC = 0
|
9
31
|
MESSAGE_AWARENESS = 1
|
10
|
-
private_constant
|
32
|
+
private_constant(
|
33
|
+
:CHANNEL_PREFIX,
|
34
|
+
:FIELD_ORIGIN,
|
35
|
+
:FIELD_UPDATE,
|
36
|
+
:MESSAGE_SYNC,
|
37
|
+
:MESSAGE_AWARENESS
|
38
|
+
)
|
11
39
|
|
12
40
|
# Initiate synchronization. Encodes the current state_vector and transmits
|
13
41
|
# to the connecting client.
|
@@ -25,15 +53,14 @@ module Y
|
|
25
53
|
# This methods should be passed as a block to stream subscription, and not
|
26
54
|
# be put into a generic #receive method.
|
27
55
|
#
|
28
|
-
# @param [Y::Doc] doc
|
29
56
|
# @param [Hash] message The encoded message must include a field named
|
30
57
|
# exactly like the field argument. The field value must be a Base64
|
31
58
|
# binary.
|
32
59
|
# @param [String] field The field that the encoded update should be
|
33
60
|
# extracted from.
|
34
|
-
def integrate(message, field:
|
35
|
-
origin = message[
|
36
|
-
update = Y::Lib0::Decoding.decode_base64_to_uint8_array(message[
|
61
|
+
def integrate(message, field: FIELD_UPDATE) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
62
|
+
origin = message[FIELD_ORIGIN]
|
63
|
+
update = Y::Lib0::Decoding.decode_base64_to_uint8_array(message[field])
|
37
64
|
|
38
65
|
encoder = Y::Lib0::Encoding.create_encoder
|
39
66
|
decoder = Y::Lib0::Decoding.create_decoder(update)
|
@@ -54,6 +81,8 @@ module Y
|
|
54
81
|
end
|
55
82
|
when MESSAGE_AWARENESS
|
56
83
|
# TODO: implement awareness https://github.com/yjs/y-websocket/blob/master/bin/utils.js#L179-L181
|
84
|
+
else
|
85
|
+
raise "unexpected message_type=`#{message_type}`"
|
57
86
|
end
|
58
87
|
|
59
88
|
# do not transmit message back to current connection if the connection
|
@@ -61,8 +90,80 @@ module Y
|
|
61
90
|
transmit(message) if origin != connection.connection_identifier
|
62
91
|
end
|
63
92
|
|
64
|
-
|
65
|
-
|
93
|
+
# Sync for given model. This is a utility method that simplifies the setup
|
94
|
+
# of a sync channel.
|
95
|
+
#
|
96
|
+
# @param [Object] model
|
97
|
+
#
|
98
|
+
# for block { |id, update| … }
|
99
|
+
# @yield [id, update] Optional block that allows to persist the document
|
100
|
+
#
|
101
|
+
# @yieldparam [String] id The document ID
|
102
|
+
# @yieldparam [Array<Integer>] update The full document state as binary
|
103
|
+
# encoded update
|
104
|
+
def sync_for(model, &block)
|
105
|
+
stream_for(model, coder: ActiveSupport::JSON) do |message|
|
106
|
+
# integrate updates in the y-rb document
|
107
|
+
integrate(message)
|
108
|
+
|
109
|
+
# persist document
|
110
|
+
persist(&block) if block
|
111
|
+
end
|
112
|
+
|
113
|
+
# negotiate initial state with client
|
114
|
+
initiate
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sync for given stream. This is a utility method that simplifies the
|
118
|
+
# setup of a sync channel.
|
119
|
+
#
|
120
|
+
# @param [String] broadcasting
|
121
|
+
#
|
122
|
+
# for block { |id, update| … }
|
123
|
+
# @yield [id, update] Optional block that allows to persist the document
|
124
|
+
#
|
125
|
+
# @yieldparam [String] id The document ID
|
126
|
+
# @yieldparam [Array<Integer>] update The full document state as binary
|
127
|
+
# encoded update
|
128
|
+
def sync_from(broadcasting, &block)
|
129
|
+
stream_from(broadcasting, coder: ActiveSupport::JSON) do |message|
|
130
|
+
# integrate updates in the y-rb document
|
131
|
+
integrate(message)
|
132
|
+
|
133
|
+
# persist document
|
134
|
+
persist(&block) if block
|
135
|
+
end
|
136
|
+
|
137
|
+
# negotiate initial state with client
|
138
|
+
initiate
|
139
|
+
end
|
140
|
+
|
141
|
+
# Synchronize update with all other connected clients (and server
|
142
|
+
# processes).
|
143
|
+
#
|
144
|
+
# @param [String] broadcasting
|
145
|
+
# @param [Hash] message
|
146
|
+
# @param [optional, String] field
|
147
|
+
def sync(broadcasting, message, field: FIELD_UPDATE)
|
148
|
+
update = message[field]
|
149
|
+
|
150
|
+
# we broadcast to all connected clients, but provide the
|
151
|
+
# connection_identifier as origin so that the [#integrate] method is
|
152
|
+
# able to filter sending back the update to its origin.
|
153
|
+
self.class.broadcast(
|
154
|
+
broadcasting,
|
155
|
+
{ update: update, origin: connection.connection_identifier }
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Synchronize update with all other connected clients (and server
|
160
|
+
# processes).
|
161
|
+
#
|
162
|
+
# @param [Object] to
|
163
|
+
# @param [Hash] message
|
164
|
+
# @param [optional, String] field
|
165
|
+
def sync_to(to, message, field: FIELD_UPDATE)
|
166
|
+
update = message[field]
|
66
167
|
|
67
168
|
# we broadcast to all connected clients, but provide the
|
68
169
|
# connection_identifier as origin so that the [#integrate] method is
|
@@ -96,27 +197,73 @@ module Y
|
|
96
197
|
# "issue_channel:id:1"
|
97
198
|
def canonical_channel_key
|
98
199
|
@canonical_channel_key ||= begin
|
99
|
-
|
100
|
-
params_part = pairs.map do |k, v|
|
200
|
+
params_part = channel_identifier.map do |k, v|
|
101
201
|
"#{k.to_s.parameterize}-#{v.to_s.parameterize}"
|
102
202
|
end
|
103
203
|
|
104
|
-
"
|
204
|
+
"#{CHANNEL_PREFIX}:#{params_part.join(":")}"
|
105
205
|
end
|
106
206
|
end
|
107
207
|
|
208
|
+
# Load the current state of a document from an external source and returns
|
209
|
+
# a reference to the document.
|
210
|
+
#
|
211
|
+
# for block { |id| … }
|
212
|
+
# @yield [id] Read document from e.g. an external store
|
213
|
+
#
|
214
|
+
# @yieldparam [String] id The document ID
|
215
|
+
# @yieldreturn [Array<Integer>] The binary encoded state of the document
|
216
|
+
# @return [Y::Doc] A reference to the loaded document
|
108
217
|
def load(&block)
|
109
|
-
full_diff =
|
218
|
+
full_diff = nil
|
219
|
+
full_diff = yield(canonical_channel_key) if block
|
110
220
|
doc.sync(full_diff) unless full_diff.nil?
|
221
|
+
doc
|
111
222
|
end
|
112
223
|
|
224
|
+
# Persist the current document state to an external store.
|
225
|
+
#
|
226
|
+
# for block { |id, update| … }
|
227
|
+
# @yield [id, update] Store document state to e.g. an external store
|
228
|
+
#
|
229
|
+
# @yieldparam [String] id The document ID
|
230
|
+
# @yieldparam [Array<Integer>] update The full document state as binary
|
231
|
+
# encoded state
|
113
232
|
def persist(&block)
|
114
|
-
yield(canonical_channel_key, doc.diff)
|
233
|
+
yield(canonical_channel_key, doc.diff) if block
|
115
234
|
end
|
116
235
|
|
236
|
+
# Creates the document once.
|
237
|
+
#
|
238
|
+
# This method can be overriden in case the document should be initialized
|
239
|
+
# with any state other than an empty one. In conjunction with
|
240
|
+
# {Y::Actioncable::Sync#load load}, this allows to provide a document to
|
241
|
+
# clients that is restored from a persistent store like Redis or also an
|
242
|
+
# ActiveRecord model.
|
243
|
+
#
|
244
|
+
# @example Initialize a {Y::Doc} from state stored in Redis
|
245
|
+
# def doc
|
246
|
+
# @doc ||= load { |id| load_doc(id) }
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# def load_doc(id)
|
250
|
+
# data = REDIS.get(id)
|
251
|
+
# data = data.unpack("C*") unless data.nil?
|
252
|
+
# data
|
253
|
+
# end
|
254
|
+
#
|
255
|
+
# @return [Y::Doc] The initialized document
|
117
256
|
def doc
|
118
257
|
@doc ||= Y::Doc.new
|
119
258
|
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def channel_identifier
|
263
|
+
return ["test", identifier] if Rails.env.test?
|
264
|
+
|
265
|
+
JSON.parse(identifier)
|
266
|
+
end
|
120
267
|
end
|
121
268
|
end
|
122
269
|
end
|
data/lib/y/lib0/decoding.rb
CHANGED
@@ -41,14 +41,10 @@ module Y
|
|
41
41
|
while decoder.pos < size
|
42
42
|
r = decoder.arr[decoder.pos]
|
43
43
|
decoder.pos += 1
|
44
|
-
num
|
44
|
+
num += ((r & Binary::BITS7) * mult)
|
45
45
|
mult *= 128 # next iteration, shift 7 "more" to the left
|
46
|
-
if r < Binary::BIT8
|
47
|
-
|
48
|
-
end
|
49
|
-
if num > Integer::MAX
|
50
|
-
raise "integer out of range"
|
51
|
-
end
|
46
|
+
return num if r < Binary::BIT8
|
47
|
+
raise "integer out of range" if num > Integer::MAX
|
52
48
|
end
|
53
49
|
raise "unexpected end of array"
|
54
50
|
end
|
data/lib/y/lib0/encoding.rb
CHANGED
data/lib/y/lib0/integer.rb
CHANGED
data/lib/y/lib0/sync.rb
CHANGED
@@ -7,7 +7,7 @@ module Y
|
|
7
7
|
write_sync_step2(encoder, doc, Decoding.read_var_uint8_array(decoder))
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.read_sync_step2(decoder, doc,
|
10
|
+
def self.read_sync_step2(decoder, doc, _transaction_origin)
|
11
11
|
update = Decoding.read_var_uint8_array(decoder)
|
12
12
|
doc.sync(update)
|
13
13
|
end
|
data/lib/y/lib0/typed_array.rb
CHANGED
@@ -18,7 +18,7 @@ module Y
|
|
18
18
|
# Create a new TypedArray from a buffer and offset. The projected
|
19
19
|
# @overload initialize(buffer, offset, size)
|
20
20
|
def initialize(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
21
|
-
if args.
|
21
|
+
if args.empty?
|
22
22
|
super()
|
23
23
|
elsif args.size == 1 && args.first.is_a?(Numeric)
|
24
24
|
super(args.first, 0)
|
@@ -27,7 +27,7 @@ module Y
|
|
27
27
|
elsif args.size == 1 && args.first.is_a?(Enumerable)
|
28
28
|
super(args.first.to_a)
|
29
29
|
elsif args.size == 2 && args.first.is_a?(Enumerable) && args.last.is_a?(Numeric)
|
30
|
-
super(args.first.to_a[(args.last)
|
30
|
+
super(args.first.to_a[(args.last)..])
|
31
31
|
elsif args.size == 3 && args.first.is_a?(Enumerable) && args[1].is_a?(Numeric) && args.last.is_a?(Numeric)
|
32
32
|
super(args.first.to_a[args[1], args.last])
|
33
33
|
else
|
data/lib/y/sync.rb
CHANGED
@@ -31,7 +31,7 @@ module Y
|
|
31
31
|
|
32
32
|
# @param [Y::Lib0::Decoding::Decoder] decoder
|
33
33
|
# @param [Y::Doc] doc
|
34
|
-
# @param [Object] transaction_origin
|
34
|
+
# @param [Object, nil] transaction_origin
|
35
35
|
# TODO: y-rb sync does not support transaction origins
|
36
36
|
def self.read_sync_step2(decoder, doc, _transaction_origin)
|
37
37
|
update = Y::Lib0::Decoding.read_var_uint8_array(decoder)
|
@@ -47,17 +47,17 @@ module Y
|
|
47
47
|
|
48
48
|
# @param [Y::Lib0::Decoding::Decoder] decoder
|
49
49
|
# @param [Y::Doc] doc
|
50
|
-
# @param [Object] transaction_origin
|
51
|
-
def self.read_update(decoder, doc,
|
52
|
-
read_sync_step2(decoder, doc,
|
50
|
+
# @param [Object, nil] transaction_origin
|
51
|
+
def self.read_update(decoder, doc, transaction_origin)
|
52
|
+
read_sync_step2(decoder, doc, transaction_origin)
|
53
53
|
end
|
54
54
|
|
55
55
|
# @param [Y::Lib0::Decoding::Decoder] decoder
|
56
56
|
# @param [Y::Lib0::Encoding::Encoder] encoder
|
57
57
|
# @param [Y::Doc] doc
|
58
|
-
# @param [Object] transaction_origin
|
58
|
+
# @param [Object, nil] transaction_origin
|
59
59
|
# TODO: y-rb sync does not support transaction origins
|
60
|
-
def self.read_sync_message(decoder, encoder, doc, transaction_origin)
|
60
|
+
def self.read_sync_message(decoder, encoder, doc, transaction_origin) # rubocop:disable Metrics/MethodLength
|
61
61
|
message_type = Y::Lib0::Decoding.read_var_uint(decoder)
|
62
62
|
|
63
63
|
case message_type
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: y-rb_actioncable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hannes Moser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -25,20 +25,20 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 7.0.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: y-rb
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
33
|
+
version: 0.4.3
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
41
|
-
description:
|
40
|
+
version: 0.4.3
|
41
|
+
description: An ActionCable companion for Y.js clients.
|
42
42
|
email:
|
43
43
|
- box@hannesmoser.at
|
44
44
|
executables: []
|
@@ -53,6 +53,7 @@ files:
|
|
53
53
|
- app/jobs/yrb/actioncable/application_job.rb
|
54
54
|
- app/models/yrb/actioncable/application_record.rb
|
55
55
|
- lib/tasks/yrb/actioncable_tasks.rake
|
56
|
+
- lib/y-rb_actioncable.rb
|
56
57
|
- lib/y/actioncable.rb
|
57
58
|
- lib/y/actioncable/config.rb
|
58
59
|
- lib/y/actioncable/config/abstract_builder.rb
|
@@ -71,7 +72,6 @@ files:
|
|
71
72
|
- lib/y/lib0/sync.rb
|
72
73
|
- lib/y/lib0/typed_array.rb
|
73
74
|
- lib/y/sync.rb
|
74
|
-
- lib/yrb-actioncable.rb
|
75
75
|
homepage: https://github.com/y-crdt/yrb-actioncable
|
76
76
|
licenses:
|
77
77
|
- MIT
|
@@ -96,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
requirements: []
|
99
|
-
rubygems_version: 3.4.
|
99
|
+
rubygems_version: 3.4.5
|
100
100
|
signing_key:
|
101
101
|
specification_version: 4
|
102
102
|
summary: An ActionCable companion for Y.js clients.
|