yrb-lite 0.1.0.beta7-aarch64-linux → 0.2.0-aarch64-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/CHANGELOG.md +0 -132
- data/README.md +92 -82
- 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: 20d4b2778b6436a336918c2699c6e5b568dd9cb1eff9fc566087ff5d698bba7d
|
|
4
|
+
data.tar.gz: 5bb8144def22e55471c679729accd6fc5da455bcbc67294eacb631c80adce937
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06a6155479bbaf77ce62ea573ae00c9e6754d63fc43e6aa952d379959df903ce67bca95ddd8625e0542d9bda4e8193f54abaf31149a227ded1ad3ed4b45e7a55
|
|
7
|
+
data.tar.gz: a245cfc5703b100092dcd817dd172339da1f498de813f4612347cd378bf37f113075766a21a2f73bfd8f950257c53ff8619837d4e06be974063b1e50369d3ecd
|
data/CHANGELOG.md
CHANGED
|
@@ -5,135 +5,3 @@ All notable changes to this project are documented here. The format is based on
|
|
|
5
5
|
to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
|
-
|
|
9
|
-
## [0.1.0.beta6] - 2026-06-22
|
|
10
|
-
|
|
11
|
-
(yrb-lite core gem. The `yrb-lite-client` npm package ships these client changes as 0.1.2.)
|
|
12
|
-
|
|
13
|
-
### Added
|
|
14
|
-
|
|
15
|
-
- `yrb-lite-client`, the TypeScript client package for the yrb-lite
|
|
16
|
-
ActionCable/AnyCable protocol. It provides `ActionCableProvider`,
|
|
17
|
-
`YProtocolSession`, and the standalone `ReliableSync` delivery core.
|
|
18
|
-
|
|
19
|
-
### Changed
|
|
20
|
-
|
|
21
|
-
- Document delivery is ack-tracked by default in `yrb-lite-client`: document
|
|
22
|
-
frames use `{ update, id }`, acknowledgements use `{ ack }`, and pending
|
|
23
|
-
document updates stay queued until acked.
|
|
24
|
-
- The ActionCable protocol surface uses a single canonical document envelope:
|
|
25
|
-
`{ "update" => "<base64 frame>" }`.
|
|
26
|
-
- AnyCable awareness/presence uses an awareness-only whisper envelope,
|
|
27
|
-
`{ awareness: "<base64 awareness frame>" }`, while document frames stay on
|
|
28
|
-
the server persistence/ack path.
|
|
29
|
-
|
|
30
|
-
### Fixed
|
|
31
|
-
|
|
32
|
-
- Incoming protocol frames are validated before mutating documents or awareness
|
|
33
|
-
state, including trailing-byte rejection on the TypeScript client.
|
|
34
|
-
- Native/Rust protocol entry points reject wire client IDs that are unsafe for
|
|
35
|
-
JavaScript clients.
|
|
36
|
-
- `lib0` is declared as a direct runtime dependency of `yrb-lite-client`.
|
|
37
|
-
|
|
38
|
-
## [0.1.0.beta5] - 2026-06-18
|
|
39
|
-
|
|
40
|
-
### Changed
|
|
41
|
-
|
|
42
|
-
- **Breaking:** the ActionCable integration has been extracted into a separate
|
|
43
|
-
gem, [`yrb-lite-actioncable`](https://rubygems.org/gems/yrb-lite-actioncable).
|
|
44
|
-
`yrb-lite` is now a standalone y-crdt wrapper: CRDT documents, awareness, and
|
|
45
|
-
the y-websocket sync protocol primitives, with no Rails/ActionCable coupling
|
|
46
|
-
(mirrors the `y-rb` / `yrb-actioncable` split). The `base64` runtime
|
|
47
|
-
dependency moved with it.
|
|
48
|
-
|
|
49
|
-
### Migration
|
|
50
|
-
|
|
51
|
-
- Using `YrbLite::Sync`? Add `gem "yrb-lite-actioncable"` and change
|
|
52
|
-
`include YrbLite::Sync` to `include YrbLite::ActionCable::Sync`. The concern's
|
|
53
|
-
API is otherwise unchanged. If you only use `YrbLite::Doc`/`YrbLite::Awareness`,
|
|
54
|
-
nothing changes.
|
|
55
|
-
|
|
56
|
-
## [0.1.0.beta4] - 2026-06-18
|
|
57
|
-
|
|
58
|
-
### Changed
|
|
59
|
-
|
|
60
|
-
- `on_change` block recorders now run in the **channel instance's context**
|
|
61
|
-
(via `instance_exec`), so a recorder can call the channel's own methods --
|
|
62
|
-
`current_user`, `params`, request/connection-scoped accessors -- directly,
|
|
63
|
-
instead of plumbing them in through a thread-local. A non-Proc callable (an
|
|
64
|
-
object responding to `#call`) is still invoked with `#call` and its own
|
|
65
|
-
context. Existing block recorders that use
|
|
66
|
-
only the `(key, update)` arguments and lexically-scoped constants are
|
|
67
|
-
unaffected; the only behavioral change is `self` inside the block.
|
|
68
|
-
|
|
69
|
-
## [0.1.0.beta3] - 2026-06-18
|
|
70
|
-
|
|
71
|
-
### Changed
|
|
72
|
-
|
|
73
|
-
- Upgraded the bundled `yrs` (y-crdt) from 0.21 to 0.27.2. No change to the
|
|
74
|
-
`YrbLite::Doc`, `YrbLite::Awareness`, or `YrbLite::Sync` public API; existing
|
|
75
|
-
code and the wire protocol are unaffected.
|
|
76
|
-
- Thread-safety is preserved across the upgrade. yrs 0.27 dropped `Awareness`'s
|
|
77
|
-
internal locking (its mutating methods now take `&mut self`, and `Awareness`
|
|
78
|
-
is no longer `Sync`), so `YrbLite::Awareness` now serializes access through an
|
|
79
|
-
internal `Mutex`. The lock is taken only while the GVL is released and is
|
|
80
|
-
never held across the GVL boundary, so concurrent access from multiple Ruby
|
|
81
|
-
threads stays safe and deadlock-free, and document reads still run in parallel
|
|
82
|
-
(they operate on a cheaply-cloned, `Arc`-backed `Doc` handle, not under the
|
|
83
|
-
presence lock).
|
|
84
|
-
|
|
85
|
-
### Build
|
|
86
|
-
|
|
87
|
-
- Building the gem from source now requires **Rust 1.94 or newer** (yrs 0.27.2
|
|
88
|
-
uses `let`-chains). The precompiled platform gems are unaffected -- they need
|
|
89
|
-
no Rust toolchain to install.
|
|
90
|
-
|
|
91
|
-
## [0.1.0.beta2] - 2026-06-16
|
|
92
|
-
|
|
93
|
-
### Added
|
|
94
|
-
|
|
95
|
-
- Reliable delivery (opt-in, client-driven). A client may tag a document update
|
|
96
|
-
with an `"id"`; the server replies `{ "ack": <id> }` once the update has been
|
|
97
|
-
durably recorded. This lets an
|
|
98
|
-
ack-aware client retain and retransmit an update until delivery is confirmed,
|
|
99
|
-
so an edit can't be silently lost on a flaky connection. Clients that omit
|
|
100
|
-
`"id"` are still accepted, but their delivery is not ack-tracked.
|
|
101
|
-
- Demo coverage for reliable delivery with "sync-since-last-ack" framing (the
|
|
102
|
-
unacknowledged tail is sent as one merged, causally-complete delta), plus a
|
|
103
|
-
minimal reference client and an intensive message-loss stress test.
|
|
104
|
-
|
|
105
|
-
### Fixed
|
|
106
|
-
|
|
107
|
-
- Causal-gap protection. The authoritative, fast, and store paths now reject a
|
|
108
|
-
document update that isn't causally ready -- one whose dependencies are
|
|
109
|
-
missing because an earlier update was lost in transit or its durable record
|
|
110
|
-
failed -- and ask the client to resync, instead of recording or relaying an
|
|
111
|
-
un-integrable update that would leave the log permanently pending. Adds native
|
|
112
|
-
`Doc#update_ready?`/`#pending?` (cheap, read-only checks) used to gate the
|
|
113
|
-
record-before-distribute path.
|
|
114
|
-
|
|
115
|
-
## [0.1.0.beta1]
|
|
116
|
-
|
|
117
|
-
### Added
|
|
118
|
-
|
|
119
|
-
- Thread-safe `YrbLite::Doc` and `YrbLite::Awareness` over `yrs` (magnus/rb-sys
|
|
120
|
-
native extension). The GVL is released during CRDT work so docs can run in
|
|
121
|
-
parallel on MRI.
|
|
122
|
-
- `YrbLite::Sync` ActionCable channel concern implementing the y-websocket
|
|
123
|
-
protocol (document sync plus awareness/presence).
|
|
124
|
-
- A "record-before-distribute" mode via an `on_change` hook, so every change is
|
|
125
|
-
recorded durably before it's applied or relayed.
|
|
126
|
-
- Presence cleanup on disconnect, and idle-document eviction.
|
|
127
|
-
- Store-backed ActionCable delivery for AnyCable and multi-process use.
|
|
128
|
-
- Hardening against bad input: malformed or multi-message frames are dropped
|
|
129
|
-
before processing or relay, and native panics are contained at the FFI
|
|
130
|
-
boundary.
|
|
131
|
-
- Precompiled native gems for common platforms (no Rust toolchain needed to
|
|
132
|
-
install) via the cross-gem workflow.
|
|
133
|
-
|
|
134
|
-
[Unreleased]: https://github.com/jpcamara/yrb-lite/compare/v0.1.0.beta5...main
|
|
135
|
-
[0.1.0.beta5]: https://github.com/jpcamara/yrb-lite/compare/v0.1.0.beta4...v0.1.0.beta5
|
|
136
|
-
[0.1.0.beta4]: https://github.com/jpcamara/yrb-lite/compare/v0.1.0.beta3...v0.1.0.beta4
|
|
137
|
-
[0.1.0.beta3]: https://github.com/jpcamara/yrb-lite/compare/v0.1.0.beta2...v0.1.0.beta3
|
|
138
|
-
[0.1.0.beta2]: https://github.com/jpcamara/yrb-lite/compare/v0.1.0.beta1...v0.1.0.beta2
|
|
139
|
-
[0.1.0.beta1]: https://github.com/jpcamara/yrb-lite/releases/tag/v0.1.0.beta1
|
data/README.md
CHANGED
|
@@ -11,20 +11,19 @@ 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
|
|
|
20
19
|
On the browser, use the `yrb-lite-client` `ActionCableProvider`. Tiptap,
|
|
21
20
|
ProseMirror, and BlockNote all sync through the `Y.Doc` you pass in and the
|
|
22
|
-
provider's Awareness instance
|
|
21
|
+
provider's Awareness instance.
|
|
23
22
|
|
|
24
23
|
## What you get
|
|
25
24
|
|
|
26
|
-
-
|
|
27
|
-
|
|
25
|
+
- A thread-safe Ruby `Doc` you can share across Puma threads; native CRDT work
|
|
26
|
+
runs with the GVL released.
|
|
28
27
|
- The y-websocket protocol (document sync plus awareness/presence) as a
|
|
29
28
|
one-include ActionCable concern.
|
|
30
29
|
- Store-backed ActionCable/AnyCable delivery for multi-process deployments.
|
|
@@ -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
|
|
@@ -86,11 +101,7 @@ require "yrb_lite"
|
|
|
86
101
|
|
|
87
102
|
# Create docs
|
|
88
103
|
doc = YrbLite::Doc.new # random client ID
|
|
89
|
-
doc = YrbLite::Doc.new(12345) # specific client ID
|
|
90
|
-
|
|
91
|
-
# Get document info
|
|
92
|
-
doc.client_id # => unique client identifier
|
|
93
|
-
doc.guid # => document GUID
|
|
104
|
+
doc = YrbLite::Doc.new(12345) # specific client ID (used for CRDT identity)
|
|
94
105
|
|
|
95
106
|
# Encoding
|
|
96
107
|
doc.encode_state_vector # => current state vector
|
|
@@ -100,36 +111,23 @@ doc.encode_state_as_update(sv) # => update diff against state vector
|
|
|
100
111
|
# Applying updates
|
|
101
112
|
doc.apply_update(update_bytes) # apply raw V1 update
|
|
102
113
|
|
|
103
|
-
# Sync protocol
|
|
104
|
-
doc.sync_step1 # => SyncStep1 message (
|
|
105
|
-
doc.
|
|
106
|
-
|
|
107
|
-
doc.encode_update_message(update) # => wrap update as sync Update message
|
|
114
|
+
# Sync protocol
|
|
115
|
+
doc.sync_step1 # => SyncStep1 message (this doc's state vector)
|
|
116
|
+
doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers a
|
|
117
|
+
# peer's SyncStep1 with a SyncStep2
|
|
108
118
|
```
|
|
109
119
|
|
|
110
|
-
###
|
|
120
|
+
### Protocol codec (module functions)
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# Get document info
|
|
118
|
-
awareness.client_id # => unique client identifier
|
|
119
|
-
awareness.guid # => document GUID
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Handling Sync Messages
|
|
122
|
+
Classifying and unwrapping wire frames is stateless, so it's exposed as
|
|
123
|
+
`YrbLite` module functions rather than a class. The server never holds presence
|
|
124
|
+
or document state to route a frame — presence lives in the browser clients, and
|
|
125
|
+
the server only relays awareness frames opaquely.
|
|
123
126
|
|
|
124
127
|
```ruby
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
# When receiving messages from peer
|
|
130
|
-
response = awareness.handle(incoming_data)
|
|
131
|
-
# Send response back to peer if not empty
|
|
132
|
-
send_to_peer(response) unless response.empty?
|
|
128
|
+
YrbLite.message_kind(frame) # => 0 drop / 1 step1 / 2 update / 3 awareness / 4 query
|
|
129
|
+
YrbLite.update_from_message(frame) # => the document delta carried by a frame, or nil
|
|
130
|
+
YrbLite.wrap_update(update_bytes) # => wrap a raw doc update as a sync Update frame
|
|
133
131
|
```
|
|
134
132
|
|
|
135
133
|
### ActionCable Integration
|
|
@@ -147,16 +145,12 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
147
145
|
on_change { |key, update| MyStore.append(key, update) } # durable record
|
|
148
146
|
|
|
149
147
|
def subscribed
|
|
150
|
-
|
|
148
|
+
sync_subscribed params[:id]
|
|
151
149
|
end
|
|
152
150
|
|
|
153
151
|
def receive(data)
|
|
154
152
|
sync_receive(data, params[:id])
|
|
155
153
|
end
|
|
156
|
-
|
|
157
|
-
def unsubscribed
|
|
158
|
-
sync_unsubscribed(params[:id])
|
|
159
|
-
end
|
|
160
154
|
end
|
|
161
155
|
```
|
|
162
156
|
|
|
@@ -178,6 +172,54 @@ oversized, or unknown frames are dropped. A bad frame can't crash the process: a
|
|
|
178
172
|
Rust panic is caught at the FFI boundary and re-raised as a Ruby exception. And
|
|
179
173
|
no single client can relay garbage that breaks the others in a room.
|
|
180
174
|
|
|
175
|
+
#### Delivery guarantees
|
|
176
|
+
|
|
177
|
+
The contract is the same at every scale — one process, or hundreds across many
|
|
178
|
+
servers:
|
|
179
|
+
|
|
180
|
+
- **The document always converges.** CRDT updates are commutative and
|
|
181
|
+
idempotent, so out-of-order, duplicate, or concurrent delivery all converge to
|
|
182
|
+
the same correct document. This needs no coordination and holds everywhere.
|
|
183
|
+
- **The durable log never goes gappy.** An update is recorded only once its
|
|
184
|
+
causal dependencies are already in the store (checked against `on_load`); a
|
|
185
|
+
causally-incomplete update triggers a resync instead, so the log always
|
|
186
|
+
rebuilds cleanly.
|
|
187
|
+
- **`on_change` is at-least-once, and the durable guarantee is that replaying the
|
|
188
|
+
log reconstructs the document.** Every change is recorded before it's acked or
|
|
189
|
+
broadcast (record-before-distribute). Entry count is not 1:1 with edits: a
|
|
190
|
+
best-effort check skips most lost-ack retries but isn't cross-process exact (a
|
|
191
|
+
retry on another process can record the same update twice), and a resync can
|
|
192
|
+
coalesce a client's un-acked tail into a single record. So **make `on_change`
|
|
193
|
+
idempotent** if duplicate side effects would matter (a webhook, a counter) — a
|
|
194
|
+
raw append-only delta log is naturally fine, since it replays to the same
|
|
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.
|
|
216
|
+
|
|
217
|
+
There is deliberately no in-gem cross-process lock. One that only spanned a
|
|
218
|
+
single process would give exactly-once at small scale and silently degrade as
|
|
219
|
+
you scale out, so the guarantee is uniform instead. If you need exactly-once
|
|
220
|
+
*side effects*, enforce it in your store (a unique key on the update) or with
|
|
221
|
+
your own distributed lock — the gem stays storage-agnostic and assumes neither.
|
|
222
|
+
|
|
181
223
|
#### Multi-process deployments
|
|
182
224
|
|
|
183
225
|
Most Rails apps run several processes (Puma workers, multiple dynos), and any of
|
|
@@ -204,9 +246,8 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
204
246
|
on_load { |key| MyStore.load(key) } # required: source of truth
|
|
205
247
|
on_change { |key, update| MyStore.append(key, update) } # required: record
|
|
206
248
|
|
|
207
|
-
def subscribed =
|
|
249
|
+
def subscribed = sync_subscribed(params[:id])
|
|
208
250
|
def receive(data) = sync_receive(data, params[:id]) # pass the key each call
|
|
209
|
-
def unsubscribed = sync_unsubscribed(params[:id])
|
|
210
251
|
end
|
|
211
252
|
```
|
|
212
253
|
|
|
@@ -218,8 +259,8 @@ end
|
|
|
218
259
|
separate awareness stream with AnyCable `whisper: true`, so cursor traffic can
|
|
219
260
|
take the low-latency client-to-client path without bypassing document
|
|
220
261
|
durability.
|
|
221
|
-
- Pass `params[:id]` into `sync_receive
|
|
222
|
-
|
|
262
|
+
- Pass `params[:id]` into `sync_receive` so the document key survives AnyCable's
|
|
263
|
+
per-command instances.
|
|
223
264
|
- The sender gets its own updates echoed back (no Ruby callback to filter them).
|
|
224
265
|
That's a no-op, since applying an update twice does nothing.
|
|
225
266
|
|
|
@@ -256,9 +297,8 @@ class DocumentChannel < ApplicationCable::Channel
|
|
|
256
297
|
AuditLog.append!(key, update) # raise to REJECT the change
|
|
257
298
|
end
|
|
258
299
|
|
|
259
|
-
def subscribed =
|
|
300
|
+
def subscribed = sync_subscribed(params[:id])
|
|
260
301
|
def receive(data) = sync_receive(data, params[:id])
|
|
261
|
-
def unsubscribed = sync_unsubscribed(params[:id])
|
|
262
302
|
end
|
|
263
303
|
```
|
|
264
304
|
|
|
@@ -295,43 +335,15 @@ one `{ ack: id }` cumulatively confirms everything up to it. Because CRDT apply
|
|
|
295
335
|
is idempotent, a resend that already landed is a harmless no-op that just
|
|
296
336
|
re-acks. Awareness stays ephemeral and is not acked.
|
|
297
337
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Set local user state (cursor position, name, etc.)
|
|
302
|
-
awareness.set_local_state('{"user": {"name": "Alice", "color": "#ff0000"}}')
|
|
303
|
-
|
|
304
|
-
# Get local state
|
|
305
|
-
awareness.local_state # => '{"user": {"name": "Alice", "color": "#ff0000"}}'
|
|
306
|
-
|
|
307
|
-
# Clear local state (e.g., when disconnecting)
|
|
308
|
-
awareness.clear_local_state
|
|
309
|
-
|
|
310
|
-
# Encode awareness update for broadcasting
|
|
311
|
-
update = awareness.encode_awareness_update
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Low-Level Access
|
|
315
|
-
|
|
316
|
-
```ruby
|
|
317
|
-
# Get state vector for manual sync
|
|
318
|
-
sv = awareness.encode_state_vector
|
|
319
|
-
|
|
320
|
-
# Get update diffed against a state vector
|
|
321
|
-
update = awareness.encode_state_as_update(remote_state_vector)
|
|
322
|
-
|
|
323
|
-
# Apply raw update to the document
|
|
324
|
-
awareness.apply_update(update_bytes)
|
|
325
|
-
|
|
326
|
-
# Wrap raw update data in a sync message
|
|
327
|
-
message = awareness.encode_update(update_bytes)
|
|
328
|
-
```
|
|
338
|
+
Presence (cursors, selections) is owned by the browser clients — the server
|
|
339
|
+
never sets or holds presence state, it only relays awareness frames opaquely.
|
|
340
|
+
See `yrb-lite-client` for the client-side awareness API.
|
|
329
341
|
|
|
330
342
|
## Thread Safety
|
|
331
343
|
|
|
332
|
-
`Doc`
|
|
333
|
-
|
|
334
|
-
|
|
344
|
+
A `Doc` is safe to share across Ruby threads — used concurrently from Puma
|
|
345
|
+
workers, ActionCable connection threads, or background jobs without external
|
|
346
|
+
locking.
|
|
335
347
|
|
|
336
348
|
That comes from how the underlying types work, not from locking on top:
|
|
337
349
|
|
|
@@ -379,8 +391,6 @@ exceptions.
|
|
|
379
391
|
```ruby
|
|
380
392
|
YrbLite::MSG_SYNC # 0 - Document sync messages
|
|
381
393
|
YrbLite::MSG_AWARENESS # 1 - User presence data
|
|
382
|
-
YrbLite::MSG_AUTH # 2 - Authentication
|
|
383
|
-
YrbLite::MSG_QUERY_AWARENESS # 3 - Request awareness state
|
|
384
394
|
|
|
385
395
|
YrbLite::MSG_SYNC_STEP1 # 0 - State vector request
|
|
386
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: aarch64-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
|