yrb-lite 0.1.0.beta9-x86_64-linux → 0.2.0-x86_64-linux
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 +42 -15
- data/lib/yrb-lite.rb +1 -1
- data/lib/yrb_lite/3.4/yrb_lite.so +0 -0
- data/lib/yrb_lite/4.0/yrb_lite.so +0 -0
- data/lib/yrb_lite/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f59eb8dd6932e3c2332a23da5e4c23bf772ffd24071f44e10cd0da721aa8365
|
|
4
|
+
data.tar.gz: e29d6aab6138f4d15cdffd4b1294aff9dd783b36ee99ac1af0d4a16c268fb0ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c86354666e93c157927ca87ee246642ca558b4bfa6da340efa077864fa62099dd0e5f4e719f681c6853e2eb75c6e6c7bc447494e2d102430f9f02c8960c6ce8
|
|
7
|
+
data.tar.gz: b93343e1b592db74e92f67f88011ba549d46789cdb002c6a45ed264ff0cc066f0a7f993a35bfa207bdff976a08afaaa539581932f5c0550e825b5bf6331b6f9a
|
data/README.md
CHANGED
|
@@ -11,9 +11,8 @@ documents.
|
|
|
11
11
|
class DocumentChannel < ApplicationCable::Channel
|
|
12
12
|
include YrbLite::ActionCable::Sync
|
|
13
13
|
|
|
14
|
-
def subscribed =
|
|
14
|
+
def subscribed = sync_subscribed(params[:id])
|
|
15
15
|
def receive(data) = sync_receive(data)
|
|
16
|
-
def unsubscribed = sync_unsubscribed(params[:id])
|
|
17
16
|
end
|
|
18
17
|
```
|
|
19
18
|
|
|
@@ -35,6 +34,22 @@ What it doesn't do: auth, read-only connections, rate limiting, webhooks,
|
|
|
35
34
|
metrics. Hocuspocus ships extensions for those; here you'd build them with
|
|
36
35
|
Rails.
|
|
37
36
|
|
|
37
|
+
## Why "lite"
|
|
38
|
+
|
|
39
|
+
The "lite" is the size of the surface. yrb-lite binds just the part of y-crdt you
|
|
40
|
+
need to *sync and persist* collaborative documents — a `Doc`, awareness, and the
|
|
41
|
+
y-websocket protocol primitives. The Ruby side treats a document as opaque CRDT
|
|
42
|
+
state: it applies updates, answers sync handshakes, and records deltas, but never
|
|
43
|
+
reaches in to read or edit the contents. The browser editor owns the document's
|
|
44
|
+
shape; Rails owns durability and delivery.
|
|
45
|
+
|
|
46
|
+
A full y-crdt Ruby binding like `y-rb` gives you the whole type system — shared
|
|
47
|
+
text, arrays, maps, XML — to build and query documents in Ruby. yrb-lite leaves
|
|
48
|
+
that out on purpose. What's left is a sync engine plus a one-include ActionCable
|
|
49
|
+
concern, with the server concerns it skips (auth, rate limiting, metrics — see
|
|
50
|
+
above) built from the Rails you already run, and no Node process hosting the
|
|
51
|
+
documents.
|
|
52
|
+
|
|
38
53
|
## Testing
|
|
39
54
|
|
|
40
55
|
Ruby and Rust unit tests cover the core. CI also runs the npm client tests and a
|
|
@@ -130,16 +145,12 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
130
145
|
on_change { |key, update| MyStore.append(key, update) } # durable record
|
|
131
146
|
|
|
132
147
|
def subscribed
|
|
133
|
-
|
|
148
|
+
sync_subscribed params[:id]
|
|
134
149
|
end
|
|
135
150
|
|
|
136
151
|
def receive(data)
|
|
137
152
|
sync_receive(data, params[:id])
|
|
138
153
|
end
|
|
139
|
-
|
|
140
|
-
def unsubscribed
|
|
141
|
-
sync_unsubscribed(params[:id])
|
|
142
|
-
end
|
|
143
154
|
end
|
|
144
155
|
```
|
|
145
156
|
|
|
@@ -182,6 +193,26 @@ servers:
|
|
|
182
193
|
idempotent** if duplicate side effects would matter (a webhook, a counter) — a
|
|
183
194
|
raw append-only delta log is naturally fine, since it replays to the same
|
|
184
195
|
document either way.
|
|
196
|
+
- **A raising `on_change` rejects the update implicitly.** If the block raises,
|
|
197
|
+
the update is neither acked nor broadcast (record-before-distribute stops both).
|
|
198
|
+
There is no negative-ack: the client simply never receives the ack, keeps the
|
|
199
|
+
update pending, and retransmits on its timer/reconnect. This is built for
|
|
200
|
+
*transient* failures (the store is briefly down → a retry lands). A block that
|
|
201
|
+
raises *deterministically* — a validation that always fails for this edit —
|
|
202
|
+
will be retried forever, since nothing tells the client to stop. Enforce hard
|
|
203
|
+
rejections before the edit reaches `on_change` (channel authorization in
|
|
204
|
+
`subscribed`), not by raising inside it.
|
|
205
|
+
- **An over-cap frame is dropped the same silent way.** A frame larger than
|
|
206
|
+
`max_frame_bytes` (default 8 MiB) is dropped before decoding — no ack, no
|
|
207
|
+
broadcast — to bound the work a client can force. For a genuine document
|
|
208
|
+
update that means the same implicit rejection as above: unacked, retransmitted
|
|
209
|
+
forever. Normal typing never approaches the cap, but a large paste, an embedded
|
|
210
|
+
image, or a big initial `SyncStep2` can. The drop is logged (`warn` for
|
|
211
|
+
over-cap, `debug` for undecodable) with the document key and update id so it's
|
|
212
|
+
findable; override `sync_log_context` on the channel to add a user/connection
|
|
213
|
+
id. Size the cap for your largest expected payload, and reject
|
|
214
|
+
genuinely-too-big content upstream rather than relying on the cap to reject it
|
|
215
|
+
gracefully.
|
|
185
216
|
|
|
186
217
|
There is deliberately no in-gem cross-process lock. One that only spanned a
|
|
187
218
|
single process would give exactly-once at small scale and silently degrade as
|
|
@@ -215,9 +246,8 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
215
246
|
on_load { |key| MyStore.load(key) } # required: source of truth
|
|
216
247
|
on_change { |key, update| MyStore.append(key, update) } # required: record
|
|
217
248
|
|
|
218
|
-
def subscribed =
|
|
249
|
+
def subscribed = sync_subscribed(params[:id])
|
|
219
250
|
def receive(data) = sync_receive(data, params[:id]) # pass the key each call
|
|
220
|
-
def unsubscribed = sync_unsubscribed(params[:id])
|
|
221
251
|
end
|
|
222
252
|
```
|
|
223
253
|
|
|
@@ -229,8 +259,8 @@ end
|
|
|
229
259
|
separate awareness stream with AnyCable `whisper: true`, so cursor traffic can
|
|
230
260
|
take the low-latency client-to-client path without bypassing document
|
|
231
261
|
durability.
|
|
232
|
-
- Pass `params[:id]` into `sync_receive
|
|
233
|
-
|
|
262
|
+
- Pass `params[:id]` into `sync_receive` so the document key survives AnyCable's
|
|
263
|
+
per-command instances.
|
|
234
264
|
- The sender gets its own updates echoed back (no Ruby callback to filter them).
|
|
235
265
|
That's a no-op, since applying an update twice does nothing.
|
|
236
266
|
|
|
@@ -267,9 +297,8 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
267
297
|
AuditLog.append!(key, update) # raise to REJECT the change
|
|
268
298
|
end
|
|
269
299
|
|
|
270
|
-
def subscribed =
|
|
300
|
+
def subscribed = sync_subscribed(params[:id])
|
|
271
301
|
def receive(data) = sync_receive(data, params[:id])
|
|
272
|
-
def unsubscribed = sync_unsubscribed(params[:id])
|
|
273
302
|
end
|
|
274
303
|
```
|
|
275
304
|
|
|
@@ -362,8 +391,6 @@ exceptions.
|
|
|
362
391
|
```ruby
|
|
363
392
|
YrbLite::MSG_SYNC # 0 - Document sync messages
|
|
364
393
|
YrbLite::MSG_AWARENESS # 1 - User presence data
|
|
365
|
-
YrbLite::MSG_AUTH # 2 - Authentication
|
|
366
|
-
YrbLite::MSG_QUERY_AWARENESS # 3 - Request awareness state
|
|
367
394
|
|
|
368
395
|
YrbLite::MSG_SYNC_STEP1 # 0 - State vector request
|
|
369
396
|
YrbLite::MSG_SYNC_STEP2 # 1 - Update response
|
data/lib/yrb-lite.rb
CHANGED
|
Binary file
|
|
Binary file
|
data/lib/yrb_lite/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yrb-lite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- JP Camara
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|