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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc720a5faecec2dfb0ec89963797944f1dd57bcb97144fca78bddf6e5d80297d
4
- data.tar.gz: 83823b92fc78b4b5d47256ff51b8cb61bebf16c101fb968893580792b3fa96f9
3
+ metadata.gz: 4f59eb8dd6932e3c2332a23da5e4c23bf772ffd24071f44e10cd0da721aa8365
4
+ data.tar.gz: e29d6aab6138f4d15cdffd4b1294aff9dd783b36ee99ac1af0d4a16c268fb0ce
5
5
  SHA512:
6
- metadata.gz: 10dc5adae589b59709b1bb9c112971aaa98dc2c7cf9a0710fd050a6a812d35e2539a55b7793684f8d634ad9be9fbd012afc09461279c90d0c3d1ab8a134761e8
7
- data.tar.gz: '08573a8e489820c01c692553f475672fa92f8b55eb14b1c9d29e1fe7707a41e4c81d4ccaa5d22001f71cd05d8af98851a7e4dd3ecc835bf80870841ec6389eba'
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 = sync_for(params[:id])
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
- sync_for params[:id]
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 = sync_for(params[:id])
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`/`sync_unsubscribed` so the document key
233
- survives AnyCable's per-command instances.
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 = sync_for(params[:id])
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Entry point matching the gem name, so `Bundler.require` works out of the box.
3
+ # Entry point matching the gem name, so `Bundler.require` loads it automatically.
4
4
  require "yrb_lite"
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YrbLite
4
- VERSION = "0.1.0.beta9"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.0.beta9
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-24 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest