yrb-lite 0.1.0.beta7-aarch64-mingw-ucrt → 0.1.0.beta9-aarch64-mingw-ucrt
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 +50 -67
- 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: 5c65659e7f4abd5578f7775314d9410ec41656c2226b6291d65302d56c0cccf6
|
|
4
|
+
data.tar.gz: f848f63fcf096be692b100fce8080609a1500c97a47f7d73e171cbf94c89a41f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7577c5170055d9a41d535ee694adfcab716432541d836fa3a7066eda6798191db9a5e41835c54a772545e394977de2cec4bbfc7831be1400e2cb62a4a58f53cd
|
|
7
|
+
data.tar.gz: a8b1b143dc96f97771bb817b1ce8687b338d7533fdf353b2cb373b54de07d5f3a4c9572e2a32e06b5a7a1f6d01d4e0e5e132521e2b68c7793963fde4c543141b
|
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
|
@@ -19,12 +19,12 @@ end
|
|
|
19
19
|
|
|
20
20
|
On the browser, use the `yrb-lite-client` `ActionCableProvider`. Tiptap,
|
|
21
21
|
ProseMirror, and BlockNote all sync through the `Y.Doc` you pass in and the
|
|
22
|
-
provider's Awareness instance
|
|
22
|
+
provider's Awareness instance.
|
|
23
23
|
|
|
24
24
|
## What you get
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
|
|
26
|
+
- A thread-safe Ruby `Doc` you can share across Puma threads; native CRDT work
|
|
27
|
+
runs with the GVL released.
|
|
28
28
|
- The y-websocket protocol (document sync plus awareness/presence) as a
|
|
29
29
|
one-include ActionCable concern.
|
|
30
30
|
- Store-backed ActionCable/AnyCable delivery for multi-process deployments.
|
|
@@ -86,11 +86,7 @@ require "yrb_lite"
|
|
|
86
86
|
|
|
87
87
|
# Create docs
|
|
88
88
|
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
|
|
89
|
+
doc = YrbLite::Doc.new(12345) # specific client ID (used for CRDT identity)
|
|
94
90
|
|
|
95
91
|
# Encoding
|
|
96
92
|
doc.encode_state_vector # => current state vector
|
|
@@ -100,36 +96,23 @@ doc.encode_state_as_update(sv) # => update diff against state vector
|
|
|
100
96
|
# Applying updates
|
|
101
97
|
doc.apply_update(update_bytes) # apply raw V1 update
|
|
102
98
|
|
|
103
|
-
# Sync protocol
|
|
104
|
-
doc.sync_step1 # => SyncStep1 message (
|
|
105
|
-
doc.
|
|
106
|
-
|
|
107
|
-
doc.encode_update_message(update) # => wrap update as sync Update message
|
|
99
|
+
# Sync protocol
|
|
100
|
+
doc.sync_step1 # => SyncStep1 message (this doc's state vector)
|
|
101
|
+
doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers a
|
|
102
|
+
# peer's SyncStep1 with a SyncStep2
|
|
108
103
|
```
|
|
109
104
|
|
|
110
|
-
###
|
|
111
|
-
|
|
112
|
-
```ruby
|
|
113
|
-
# Create awareness instances (each contains a Doc)
|
|
114
|
-
awareness = YrbLite::Awareness.new # random client ID
|
|
115
|
-
awareness = YrbLite::Awareness.new(12345) # specific client ID
|
|
116
|
-
|
|
117
|
-
# Get document info
|
|
118
|
-
awareness.client_id # => unique client identifier
|
|
119
|
-
awareness.guid # => document GUID
|
|
120
|
-
```
|
|
105
|
+
### Protocol codec (module functions)
|
|
121
106
|
|
|
122
|
-
|
|
107
|
+
Classifying and unwrapping wire frames is stateless, so it's exposed as
|
|
108
|
+
`YrbLite` module functions rather than a class. The server never holds presence
|
|
109
|
+
or document state to route a frame — presence lives in the browser clients, and
|
|
110
|
+
the server only relays awareness frames opaquely.
|
|
123
111
|
|
|
124
112
|
```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?
|
|
113
|
+
YrbLite.message_kind(frame) # => 0 drop / 1 step1 / 2 update / 3 awareness / 4 query
|
|
114
|
+
YrbLite.update_from_message(frame) # => the document delta carried by a frame, or nil
|
|
115
|
+
YrbLite.wrap_update(update_bytes) # => wrap a raw doc update as a sync Update frame
|
|
133
116
|
```
|
|
134
117
|
|
|
135
118
|
### ActionCable Integration
|
|
@@ -178,6 +161,34 @@ oversized, or unknown frames are dropped. A bad frame can't crash the process: a
|
|
|
178
161
|
Rust panic is caught at the FFI boundary and re-raised as a Ruby exception. And
|
|
179
162
|
no single client can relay garbage that breaks the others in a room.
|
|
180
163
|
|
|
164
|
+
#### Delivery guarantees
|
|
165
|
+
|
|
166
|
+
The contract is the same at every scale — one process, or hundreds across many
|
|
167
|
+
servers:
|
|
168
|
+
|
|
169
|
+
- **The document always converges.** CRDT updates are commutative and
|
|
170
|
+
idempotent, so out-of-order, duplicate, or concurrent delivery all converge to
|
|
171
|
+
the same correct document. This needs no coordination and holds everywhere.
|
|
172
|
+
- **The durable log never goes gappy.** An update is recorded only once its
|
|
173
|
+
causal dependencies are already in the store (checked against `on_load`); a
|
|
174
|
+
causally-incomplete update triggers a resync instead, so the log always
|
|
175
|
+
rebuilds cleanly.
|
|
176
|
+
- **`on_change` is at-least-once, and the durable guarantee is that replaying the
|
|
177
|
+
log reconstructs the document.** Every change is recorded before it's acked or
|
|
178
|
+
broadcast (record-before-distribute). Entry count is not 1:1 with edits: a
|
|
179
|
+
best-effort check skips most lost-ack retries but isn't cross-process exact (a
|
|
180
|
+
retry on another process can record the same update twice), and a resync can
|
|
181
|
+
coalesce a client's un-acked tail into a single record. So **make `on_change`
|
|
182
|
+
idempotent** if duplicate side effects would matter (a webhook, a counter) — a
|
|
183
|
+
raw append-only delta log is naturally fine, since it replays to the same
|
|
184
|
+
document either way.
|
|
185
|
+
|
|
186
|
+
There is deliberately no in-gem cross-process lock. One that only spanned a
|
|
187
|
+
single process would give exactly-once at small scale and silently degrade as
|
|
188
|
+
you scale out, so the guarantee is uniform instead. If you need exactly-once
|
|
189
|
+
*side effects*, enforce it in your store (a unique key on the update) or with
|
|
190
|
+
your own distributed lock — the gem stays storage-agnostic and assumes neither.
|
|
191
|
+
|
|
181
192
|
#### Multi-process deployments
|
|
182
193
|
|
|
183
194
|
Most Rails apps run several processes (Puma workers, multiple dynos), and any of
|
|
@@ -295,43 +306,15 @@ one `{ ack: id }` cumulatively confirms everything up to it. Because CRDT apply
|
|
|
295
306
|
is idempotent, a resend that already landed is a harmless no-op that just
|
|
296
307
|
re-acks. Awareness stays ephemeral and is not acked.
|
|
297
308
|
|
|
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
|
-
```
|
|
309
|
+
Presence (cursors, selections) is owned by the browser clients — the server
|
|
310
|
+
never sets or holds presence state, it only relays awareness frames opaquely.
|
|
311
|
+
See `yrb-lite-client` for the client-side awareness API.
|
|
329
312
|
|
|
330
313
|
## Thread Safety
|
|
331
314
|
|
|
332
|
-
`Doc`
|
|
333
|
-
|
|
334
|
-
|
|
315
|
+
A `Doc` is safe to share across Ruby threads — used concurrently from Puma
|
|
316
|
+
workers, ActionCable connection threads, or background jobs without external
|
|
317
|
+
locking.
|
|
335
318
|
|
|
336
319
|
That comes from how the underlying types work, not from locking on top:
|
|
337
320
|
|
|
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.1.0.
|
|
4
|
+
version: 0.1.0.beta9
|
|
5
5
|
platform: aarch64-mingw-ucrt
|
|
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-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|