yrby 0.2.1-x86_64-linux → 0.3.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: ff22dc8ae310daac142d0fe476d8659cfedff1b1f5cb88ccc9c46688e03fbe27
4
- data.tar.gz: e3ff8171d93f3785b1d349173b7b7ebf12e1f762ea172bf48c4f9c7c40a0255f
3
+ metadata.gz: 1c679e136a706fb345dedc22c7b3e9da97919a16c827dc67fe0a5e06ab0ed414
4
+ data.tar.gz: df08be53339b8a833474f3ecd8234838e1aad5dd178fd1ae419d44adaf29637c
5
5
  SHA512:
6
- metadata.gz: 4f3c2c3291521f47218ed70e4f2d9205efe7143312588d755079e87bbee32ac60e4c0fc45f7156453cc54c695d702ae53e91be8904cd6d1436551b8d10cddd31
7
- data.tar.gz: 6796d3d50aceac6f5924be12d9cb9b1beb9ef8b3c80a75745d88f0b04dd930e1af9abb3119bd495b358af08a5703e58a45a368af9cea3c460b36bd45c9cc0054
6
+ metadata.gz: '0903a87fcf089ec17d8aafbe0feb39fdd05b1068ae85540167434c818b2542a8b4f252115fd79f3f3d01a1ac63ee1a0a54eabd28172600538154f195e330cb04'
7
+ data.tar.gz: 4f349abd4f2f62f662d8c988489a9a5b9b79892e5f78ce5790a4e0638217e4262b3c69d39d390c2f4d34bcf1b5a9ee981520ad178cd7b81c820ff08e7771fdc6
data/CHANGELOG.md CHANGED
@@ -6,6 +6,72 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-07-01
10
+
11
+ ### Fixed
12
+
13
+ - **Sync no longer serves un-integrable pending structs.** When a doc holds a
14
+ *pending* struct (a gappy update whose causally-prior update is missing — e.g.
15
+ legacy data recorded before the `update_ready?` gate existed), its integrated
16
+ state vector is empty but `encode_state_as_update` merges the pending bytes back
17
+ in. Answering a peer's `SyncStep1` with that state handed the peer content it
18
+ couldn't integrate, so it parked the same pending forever and the empty-SV /
19
+ non-empty-content mismatch drove endless resync traffic (observed as a browser
20
+ re-sending frames several times a second). `handle_sync_message` now answers
21
+ `SyncStep1` with **integrated-only** state, so a server never serves a struct it
22
+ can't integrate itself. Neutralizes existing poisoned server state on deploy —
23
+ no migration needed. The server's own pending is untouched and still heals if
24
+ the missing dependency later arrives (only then does the content become
25
+ visible in sync). Live delta relay (`Update` frames) is unchanged.
26
+
27
+ ### Added
28
+
29
+ - `Doc#pending?` — true if the doc holds un-integrable pending structs or a
30
+ pending delete set (content waiting on a missing causally-prior update).
31
+ - `Doc#compacted_state_update` — like `encode_state_as_update` (full state) but
32
+ **gap-free**: excludes pending structs/delete set. Use it when persisting or
33
+ serving state other peers will apply. Non-destructive — the doc keeps its
34
+ pending (so it can still heal), and `encode_state_as_update` stays lossless for
35
+ raw-update recovery.
36
+
37
+ ## [0.2.3] - 2026-07-01
38
+
39
+ ### Fixed
40
+
41
+ - `Doc#update_advances?` is now exact for **delete-bearing** updates, so an
42
+ already-applied pure-delete retry no longer reports as advancing. Previously any
43
+ update carrying a delete set returned `true` (record it) because deletes don't
44
+ move the state vector, so the cheap state-vector probe couldn't prove a
45
+ duplicate. A lost-ack retry of a deletion the server had already integrated was
46
+ therefore re-recorded and re-broadcast every time. For delete-bearing updates we
47
+ now compare the full encoded document state (which includes the delete set)
48
+ before vs. after a trial apply on an isolated probe: a genuinely new deletion
49
+ changes it (`true`); an already-applied retry re-encodes identically (`false`).
50
+ Insert/format-only updates keep the cheaper state-vector path, so only
51
+ delete-bearing frames — a minority — pay for the exact comparison. The exactly-
52
+ once guarantee is unchanged in the safe direction: a real deletion is never
53
+ dropped.
54
+
55
+ This lets `yrby-actioncable` (and any caller gating `on_change` on
56
+ `update_advances?`) settle a duplicate pure-delete frame as `:applied` — acked,
57
+ but not stored or relayed — so apps no longer need an app-level
58
+ encode-and-compare guard around their durable writes.
59
+
60
+ ## [0.2.2] - 2026-06-30
61
+
62
+ ### Fixed
63
+
64
+ - `Doc#read_xml` now recovers text from **nested** Lexical/Lexxy blocks. Lexical
65
+ embeds child blocks (list items, table cells, nested lists) as `Y.XmlText`
66
+ embeds that `get_string` silently drops, so lists and tables previously came
67
+ back empty. `read_xml` now walks the embeds: text runs build a line, inline
68
+ children (links) join it, and nested block children flush and recurse — so a
69
+ document with headings, formatted text, links, bullet/numbered/check/nested
70
+ lists, blockquotes, code blocks and tables extracts every piece of text.
71
+ Lexical decorator elements (horizontal rule, image) are skipped instead of
72
+ emitting their `<UNDEFINED …>` serialization. ProseMirror handling is
73
+ unchanged.
74
+
9
75
  ## [0.2.1] - 2026-06-29
10
76
 
11
77
  ### Changed
@@ -15,15 +81,9 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
15
81
 
16
82
  ## [0.2.0] - 2026-06-28
17
83
 
18
- First release under the **`yrby`** name (the project was previously developed
19
- as `yrb-lite`). The public Ruby interface is the top-level module **`Y`** —
20
- mirroring the `y-rb` gem's `Y::Doc` interface.
21
-
22
- ### Changed
23
- - **Renamed `yrb-lite` → `yrby`.** Module `YrbLite` → top-level `Y`
24
- (`Y::Doc`, `Y::Error`, `Y::VERSION`). Require path `require "yrb_lite"` →
25
- `require "y"`. (The native extension crate shipped as `y_ruby` in 0.2.0; see
26
- 0.2.1 for its rename to `yrby`.)
84
+ First release. The public Ruby interface is the top-level module **`Y`**
85
+ (`Y::Doc`, `Y::Error`, `Y::VERSION`), loaded with `require "y"` mirroring the
86
+ `y-rb` gem's `Y::Doc` interface.
27
87
 
28
88
  ### Added
29
89
  - Native `Doc#read_text` and `Doc#read_map` readers — reconstruct plain text and
data/README.md CHANGED
@@ -41,23 +41,25 @@ npm install yrby-client
41
41
  one-include ActionCable concern.
42
42
  - Authoritative record-before-distribute semantics: each document change can be
43
43
  recorded durably before it goes out to anyone.
44
- -
44
+ - Optional server-side reads: `Doc#read_text` and `Doc#read_map` reconstruct a
45
+ document's contents in Ruby - no Node process - for search, exports, validation,
46
+ or server-side rendering.
45
47
 
46
- ## Why "lite"
48
+ ## Scope
47
49
 
48
- The "lite" is the size of the surface. `yrby` binds just the part of `y-crdt` you
49
- need to *sync and persist* collaborative documents - a `Doc`, awareness, and the
50
- y-websocket protocol primitives. The Ruby side treats a document as opaque CRDT
51
- state: it applies updates, answers sync handshakes, and records deltas, but never
52
- reaches in to read or edit the contents. The browser editor owns the document's
53
- shape.
50
+ `yrby` binds just the part of `y-crdt` you need to *sync and persist* collaborative
51
+ documents - a `Doc`, awareness, and the y-websocket protocol primitives. By default
52
+ the Ruby side treats a document as opaque CRDT state: it applies updates, answers
53
+ sync handshakes, and records deltas without reaching into the contents - the browser
54
+ editor owns the document's shape. When you do need to look inside, `Doc#read_text`
55
+ and `Doc#read_map` reconstruct it server-side, in Ruby.
54
56
 
55
- ## What isn't "lite"
57
+ ## Durability and delivery
56
58
 
57
- The surface area may be "lite", but a core focus is on durability, resiliency, delivery
59
+ The surface is intentionally small, but the focus is durability, resiliency, delivery
58
60
  guarantees, correctness, and thread safety.
59
61
 
60
- Towards that goal, `yrby` adds capabilities that may even stand out in the Yjs ecosystem:
62
+ Towards that goal, `yrby` adds capabilities that stand out even in the Yjs ecosystem:
61
63
 
62
64
  - Built-in update acknowledgement: the `ActionCableProvider` in `yrby-client` will continue to
63
65
  send updates until an ack is received from the server. [`yrby-actioncable`](https://rubygems.org/gems/yrby-actioncable)
@@ -140,18 +142,39 @@ doc = Y::Doc.new(12345) # specific client ID (used for CRDT identity)
140
142
 
141
143
  # Encoding
142
144
  doc.encode_state_vector # => current state vector
143
- doc.encode_state_as_update # => full update
145
+ doc.encode_state_as_update # => full update (lossless: keeps pending)
144
146
  doc.encode_state_as_update(sv) # => update diff against state vector
147
+ doc.compacted_state_update # => full update, gap-free (excludes pending)
145
148
 
146
149
  # Applying updates
147
150
  doc.apply_update(update_bytes) # apply raw V1 update
151
+ doc.pending? # => true if holding un-integrable pending structs
148
152
 
149
153
  # Sync protocol
150
154
  doc.sync_step1 # => SyncStep1 message (this doc's state vector)
151
155
  doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers a
152
- # peer's SyncStep1 with a SyncStep2
156
+ # peer's SyncStep1 with an integrated-only
157
+ # SyncStep2 (never serves pending structs)
153
158
  ```
154
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
+
155
178
  ### Protocol codec (module functions)
156
179
 
157
180
  Classifying and unwrapping wire frames is stateless, so it's exposed as
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Y
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.1
4
+ version: 0.3.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-29 00:00:00.000000000 Z
11
+ date: 2026-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest