yrby 0.2.3-aarch64-linux → 0.3.1-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 +67 -0
- data/README.md +38 -2
- data/lib/y/3.4/yrby.so +0 -0
- data/lib/y/4.0/yrby.so +0 -0
- data/lib/y/version.rb +1 -1
- metadata +2 -5
- data/lib/y/decoder/version.rb +0 -7
- data/lib/y/decoder.rb +0 -66
- data/lib/yrby-decoder.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d23cfc4529b65f1bc9b1ad727c4f21894ce336dcc4c8aac3a676ddade4557ff1
|
|
4
|
+
data.tar.gz: 0b40878c0e0fe2cbc8295a074bf6609b45beb80375713e1390a116b301b35787
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42fd99039628908e230393760a55a999652d3993441796f4bd45d7371674e80fe9a82ba103183b613608fc737a83e26bbaaeddd83bc6cc87936eea2113225ca3
|
|
7
|
+
data.tar.gz: ca67b181166c76f830a997ea0140684395ae68ed452bd1c9afd94e825ba4da64364189c47f20603837df506cd3d84b9e1edc577bc0b2585616c497e678afba78
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,73 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.1] - 2026-07-01
|
|
10
|
+
|
|
11
|
+
Fixes from a full source review.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`Doc#update_ready?` is now exact.** It previously checked only the
|
|
16
|
+
per-client clock lower bound, but yrs's real integration gate also requires
|
|
17
|
+
every block referenced by an item's origin / right-origin / parent — which
|
|
18
|
+
routinely belong to *other* clients — and post-Skip blocks in a merged update
|
|
19
|
+
sit above the lower bound. An update could pass the clock check yet park as
|
|
20
|
+
pending; downstream, `update_advances?` then misread the parked update as an
|
|
21
|
+
already-applied retry (pending doesn't move a state vector) and the sync
|
|
22
|
+
channel **acked and dropped real content**. `update_ready?` now
|
|
23
|
+
trial-integrates on a throwaway probe seeded with the doc's integrated state
|
|
24
|
+
(the clock check remains as a cheap pre-filter), so a cross-client-origin gap
|
|
25
|
+
is correctly rejected for a resync. `update_advances?` also gained defense in
|
|
26
|
+
depth: an update that would park reports as advancing, never as a duplicate.
|
|
27
|
+
- **`Doc#read_text` could deadlock the process.** It opened a second read
|
|
28
|
+
transaction while still holding the first (a chained temporary); yrs's lock is
|
|
29
|
+
write-preferring, so a concurrent writer between the two acquisitions
|
|
30
|
+
deadlocked reader-vs-writer inside the GVL-released (uninterruptible) region.
|
|
31
|
+
Now uses a single transaction.
|
|
32
|
+
- **TOCTOU in gap-free encoding.** The pending check and the encode ran in
|
|
33
|
+
separate transactions, so a concurrent gappy `apply_update` between them could
|
|
34
|
+
make `handle_sync_message`/`compacted_state_update` serve pending structs
|
|
35
|
+
anyway. Both now happen under one transaction.
|
|
36
|
+
- `read_xml`: Lexical soft line breaks and tabs now come through as `\n`/`\t`
|
|
37
|
+
instead of vanishing (`"foo⏎bar"` no longer extracts as `"foobar"`).
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- `update_advances?` skips its full-document probe when the update carries
|
|
42
|
+
blocks beyond the doc's state vector (a novel update trivially advances) —
|
|
43
|
+
the common case no longer pays O(doc) per frame.
|
|
44
|
+
- The gem no longer packages the `yrby-decoder` gem's files (they ship in that
|
|
45
|
+
gem; the duplicate copy could shadow a newer standalone release), and now
|
|
46
|
+
ships `Cargo.lock` so source builds compile the exact crate graph CI tested.
|
|
47
|
+
|
|
48
|
+
## [0.3.0] - 2026-07-01
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **Sync no longer serves un-integrable pending structs.** When a doc holds a
|
|
53
|
+
*pending* struct (a gappy update whose causally-prior update is missing — e.g.
|
|
54
|
+
legacy data recorded before the `update_ready?` gate existed), its integrated
|
|
55
|
+
state vector is empty but `encode_state_as_update` merges the pending bytes back
|
|
56
|
+
in. Answering a peer's `SyncStep1` with that state handed the peer content it
|
|
57
|
+
couldn't integrate, so it parked the same pending forever and the empty-SV /
|
|
58
|
+
non-empty-content mismatch drove endless resync traffic (observed as a browser
|
|
59
|
+
re-sending frames several times a second). `handle_sync_message` now answers
|
|
60
|
+
`SyncStep1` with **integrated-only** state, so a server never serves a struct it
|
|
61
|
+
can't integrate itself. Neutralizes existing poisoned server state on deploy —
|
|
62
|
+
no migration needed. The server's own pending is untouched and still heals if
|
|
63
|
+
the missing dependency later arrives (only then does the content become
|
|
64
|
+
visible in sync). Live delta relay (`Update` frames) is unchanged.
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- `Doc#pending?` — true if the doc holds un-integrable pending structs or a
|
|
69
|
+
pending delete set (content waiting on a missing causally-prior update).
|
|
70
|
+
- `Doc#compacted_state_update` — like `encode_state_as_update` (full state) but
|
|
71
|
+
**gap-free**: excludes pending structs/delete set. Use it when persisting or
|
|
72
|
+
serving state other peers will apply. Non-destructive — the doc keeps its
|
|
73
|
+
pending (so it can still heal), and `encode_state_as_update` stays lossless for
|
|
74
|
+
raw-update recovery.
|
|
75
|
+
|
|
9
76
|
## [0.2.3] - 2026-07-01
|
|
10
77
|
|
|
11
78
|
### Fixed
|
data/README.md
CHANGED
|
@@ -142,18 +142,39 @@ doc = Y::Doc.new(12345) # specific client ID (used for CRDT identity)
|
|
|
142
142
|
|
|
143
143
|
# Encoding
|
|
144
144
|
doc.encode_state_vector # => current state vector
|
|
145
|
-
doc.encode_state_as_update # => full update
|
|
145
|
+
doc.encode_state_as_update # => full update (lossless: keeps pending)
|
|
146
146
|
doc.encode_state_as_update(sv) # => update diff against state vector
|
|
147
|
+
doc.compacted_state_update # => full update, gap-free (excludes pending)
|
|
147
148
|
|
|
148
149
|
# Applying updates
|
|
149
150
|
doc.apply_update(update_bytes) # apply raw V1 update
|
|
151
|
+
doc.pending? # => true if holding un-integrable pending structs
|
|
150
152
|
|
|
151
153
|
# Sync protocol
|
|
152
154
|
doc.sync_step1 # => SyncStep1 message (this doc's state vector)
|
|
153
155
|
doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers a
|
|
154
|
-
# peer's SyncStep1 with
|
|
156
|
+
# peer's SyncStep1 with an integrated-only
|
|
157
|
+
# SyncStep2 (never serves pending structs)
|
|
155
158
|
```
|
|
156
159
|
|
|
160
|
+
### Pending structs and gap-free state
|
|
161
|
+
|
|
162
|
+
If a doc applies an update whose causally-prior update is missing (a "gappy"
|
|
163
|
+
update), yrs parks it as a **pending** struct: the integrated state vector stays
|
|
164
|
+
empty, but the pending block is held as a recovery buffer and heals if the
|
|
165
|
+
missing dependency later arrives. `Doc#pending?` reports this.
|
|
166
|
+
|
|
167
|
+
Pending structs are *not* document state, so they must not cross the sync
|
|
168
|
+
boundary — a peer that receives one can't integrate it and gets stuck. Two
|
|
169
|
+
guarantees keep serving safe:
|
|
170
|
+
|
|
171
|
+
- `handle_sync_message` answers `SyncStep1` with **integrated-only** state, so a
|
|
172
|
+
server never serves a struct it can't integrate itself (this is automatic).
|
|
173
|
+
- `Doc#compacted_state_update` gives you the same gap-free full-state update for
|
|
174
|
+
when you persist or hand off state yourself. It's non-destructive (the doc
|
|
175
|
+
keeps its pending), while `encode_state_as_update` stays lossless so you can
|
|
176
|
+
still preserve the raw pending bytes for recovery.
|
|
177
|
+
|
|
157
178
|
### Protocol codec (module functions)
|
|
158
179
|
|
|
159
180
|
Classifying and unwrapping wire frames is stateless, so it's exposed as
|
|
@@ -221,6 +242,21 @@ servers:
|
|
|
221
242
|
causal dependencies are already in the store (checked against `on_load`); a
|
|
222
243
|
causally-incomplete update triggers a resync instead, so the log always
|
|
223
244
|
rebuilds cleanly.
|
|
245
|
+
- **An unhealable gap is dropped, not resynced forever.** A resync heals a gap
|
|
246
|
+
whose missing dependency is still in flight. But a *permanently*-orphaned update
|
|
247
|
+
(its dependency is gone for good) stays gappy through every resync, and a client
|
|
248
|
+
retransmitting it would loop endlessly (server resyncs → client resends →
|
|
249
|
+
repeat). After `gap_strike_limit` rejections of the same update on one
|
|
250
|
+
connection (default 3, minimum 2), the channel settles it with
|
|
251
|
+
`{ "ack" => id, "dropped" => true }` and drops it instead of resyncing again —
|
|
252
|
+
breaking the loop while never dropping a *healable* gap (those heal within a
|
|
253
|
+
resync or two, and healing frees the strike). The `dropped` flag lets the
|
|
254
|
+
client surface the loss (`yrby-client` reports it via `onError`) instead of
|
|
255
|
+
silently showing synced. Set `gap_strike_limit nil` to disable. Works on both
|
|
256
|
+
transports: plain ActionCable keeps strikes on the channel instance; under
|
|
257
|
+
AnyCable (fresh instance per RPC command) they persist through
|
|
258
|
+
anycable-rails' `state_attr_accessor` (istate), declared automatically when
|
|
259
|
+
anycable-rails is loaded.
|
|
224
260
|
- **`on_change` is at-least-once, and the durable guarantee is that replaying the
|
|
225
261
|
log reconstructs the document.** Every update triggers `on_change` before it's acked or
|
|
226
262
|
broadcast (record-before-distribute). If exactly-once updates matter for you, **you
|
data/lib/y/3.4/yrby.so
CHANGED
|
Binary file
|
data/lib/y/4.0/yrby.so
CHANGED
|
Binary file
|
data/lib/y/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yrby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
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-07-
|
|
11
|
+
date: 2026-07-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -68,10 +68,7 @@ files:
|
|
|
68
68
|
- lib/y.rb
|
|
69
69
|
- lib/y/3.4/yrby.so
|
|
70
70
|
- lib/y/4.0/yrby.so
|
|
71
|
-
- lib/y/decoder.rb
|
|
72
|
-
- lib/y/decoder/version.rb
|
|
73
71
|
- lib/y/version.rb
|
|
74
|
-
- lib/yrby-decoder.rb
|
|
75
72
|
- lib/yrby.rb
|
|
76
73
|
homepage: https://github.com/jpcamara/yrby
|
|
77
74
|
licenses:
|
data/lib/y/decoder/version.rb
DELETED
data/lib/y/decoder.rb
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "y"
|
|
4
|
-
require "y/decoder/version"
|
|
5
|
-
|
|
6
|
-
module Y
|
|
7
|
-
# Plain-text reconstruction of a stored Yjs document, in pure Ruby — for search
|
|
8
|
-
# indexing and previews. The core `yrby` gem moves and stores opaque CRDT
|
|
9
|
-
# updates without reading them; this reads the text out of the shared type the
|
|
10
|
-
# editor uses (Lexical's `Y.XmlText`, plain `Y.Text`, or ProseMirror's
|
|
11
|
-
# `Y.XmlFragment`), in-process, on the native extension core already ships — no
|
|
12
|
-
# Node, no subprocess, no binary.
|
|
13
|
-
#
|
|
14
|
-
# state = doc.encode_state_as_update # opaque CRDT bytes from the store
|
|
15
|
-
# Y::Decoder.text(state) # => "hello world"
|
|
16
|
-
# Y::Decoder.preview(state, 280) # => "hello world…"
|
|
17
|
-
#
|
|
18
|
-
# Full-fidelity reconstruction (the exact Lexical EditorState / HTML, which
|
|
19
|
-
# needs @lexical/yjs) is a separate, opt-in concern — see the `yrby-decode`
|
|
20
|
-
# package's Bun binary. This gem stays pure Ruby on purpose.
|
|
21
|
-
module Decoder
|
|
22
|
-
class Error < Y::Error; end
|
|
23
|
-
|
|
24
|
-
module_function
|
|
25
|
-
|
|
26
|
-
# Plain text of the document. `field` pins the root key (Lexical: the editor
|
|
27
|
-
# id; ProseMirror: "default"); omit it to use the document's sole root.
|
|
28
|
-
def text(state, field: nil)
|
|
29
|
-
field ||= Y::Doc.new.tap { |d| d.apply_update(state) }.root_names.first
|
|
30
|
-
return "" unless field
|
|
31
|
-
|
|
32
|
-
# A plain `Y.Text` root (a simple shared-text editor) reads straight out.
|
|
33
|
-
# (A yrs root's type is fixed by its first typed access, so each reader
|
|
34
|
-
# gets a fresh doc to try a different shared type against the same state.)
|
|
35
|
-
direct = load(state).read_text(field)
|
|
36
|
-
return normalize(direct) if direct && !direct.strip.empty?
|
|
37
|
-
|
|
38
|
-
# Lexical (each block a sibling `Y.XmlText`) and ProseMirror (blocks are
|
|
39
|
-
# `Y.XmlElement`s) both come back from read_xml as block-per-line markup;
|
|
40
|
-
# strip any element tags to plain text.
|
|
41
|
-
markup = load(state).read_xml(field)
|
|
42
|
-
markup ? normalize(strip_tags(markup)) : ""
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# A compact, single-line preview for list UIs.
|
|
46
|
-
def preview(state, limit: 280, field: nil)
|
|
47
|
-
body = text(state, field: field).gsub(/\s+/, " ").strip
|
|
48
|
-
body.length > limit ? "#{body[0, limit].rstrip}…" : body
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def load(state)
|
|
52
|
-
Y::Doc.new.tap { |doc| doc.apply_update(state) }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def strip_tags(markup)
|
|
56
|
-
markup.gsub(/<[^>]*>/, " ")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def normalize(text)
|
|
60
|
-
text.gsub(/[ \t]+/, " ") # collapse runs of spaces/tabs
|
|
61
|
-
.gsub(/ *\n */, "\n") # trim spaces left around block separators
|
|
62
|
-
.gsub(/\n{3,}/, "\n\n") # cap blank-line runs
|
|
63
|
-
.strip
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
data/lib/yrby-decoder.rb
DELETED