yrby-rails 0.6.1 → 0.8.0

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: ce4164de6e9497a1f6236581facfc1ca47bf33951fc32a977078016dd52a42d2
4
- data.tar.gz: 1353f914370ba5b0142ece86d1ab38960d0a92c07c63b1e5320b8fe155350ce6
3
+ metadata.gz: 83a5440fa6532dbedcf992660959297611b9b7cf958f7c7c6c1522dd0ac162b0
4
+ data.tar.gz: 23509b37d1bb0ce706c648a779993236addb244cb603031415de4084a58e4636
5
5
  SHA512:
6
- metadata.gz: f5e63dfbc35fd3969cd9effcb26d92f05cfa7f29efb302c7c71ecba11f51bc25b686e99ded9e979cd2c4d382069e4a8c10625a622c389f3e4b0ae3131f959ab7
7
- data.tar.gz: 120215f5109ec37e77371a59318bb9fded69fbe79338f0a50cf1b6810a784b191bfc8696400369e0ec97e01752f7fc77b0fd74fb85ae220fc2f294d67c26a6c4
6
+ metadata.gz: c51b2e0b9acefc89d6c69e3b935206d195ccaba7b302ec13316c39f7fab0115d09fd7a5d108f537294b8c069a54ef2e1a53fc19b39ec21873e7b61be866e7a5d
7
+ data.tar.gz: 5ffd94a472cd16ada77f62e57c7bd571f7df461a9552756212ef6fd12c9c1a2cb92bdb3ac72fe0320a1992d27f6b22ce8964240db81057ffaaa4cc2be1872124
data/CHANGELOG-rails.md CHANGED
@@ -5,6 +5,159 @@ documented here. The
5
5
  format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
  this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.8.0] - 2026-11-03
11
+
12
+ Requires yrby 0.9.0 or later.
13
+
14
+ ### Experimental
15
+
16
+ These are new, and they may change in a minor release. See
17
+ [docs/experimental.md](docs/experimental.md).
18
+
19
+ - `collaborative_document(:body).edit { |doc| ... }` edits a record's
20
+ document from Ruby. It records the change in the document's rows, encrypted
21
+ ones included, and broadcasts it to open editors.
22
+ `Y::ActionCable.broadcast(key, update)` sends an already recorded update.
23
+
24
+ ### Added
25
+
26
+ - `collaborative_document(name)` answers `grant(expires_in:)` and `channel`:
27
+ the grant a page or client subscribes with, and the channel that accepts
28
+ it. A gem with its own grants and channel, such as lexxy-realtime, returns
29
+ its own document with both.
30
+
31
+ ### Changed
32
+
33
+ - `collaborative_document_tag` renders the document's grant and channel, so
34
+ it works for a gem that returns its own document. `channel:` still
35
+ overrides the channel.
36
+
37
+ ### Deprecated
38
+
39
+ - `collaborative_sgid(name, expires_in:)`. Use
40
+ `collaborative_document(name).grant(expires_in:)`. It logs through
41
+ `Y.deprecator`, which the engine adds to `Rails.application.deprecators`,
42
+ and goes away in yrby-rails 0.9.
43
+
44
+ ### Fixed
45
+
46
+ - The channel relays a client's SyncStep2 to peers as a plain update.
47
+ Relayed as is, a peer that was still joining could take it for the answer
48
+ to its own handshake and report itself loaded before it had the server's
49
+ state.
50
+
51
+ ## [0.7.0] - 2026-10-01
52
+
53
+ ### Added
54
+
55
+ - `Y::DocumentChannel.authorize_document { |record, name| ... }` runs the
56
+ application's permission check when a client subscribes, on top of the
57
+ signed grant. The block runs in channel context. If it denies access, the
58
+ channel rejects that subscription without opening a stream or serving
59
+ state, and other subscriptions on the connection keep working. Without a
60
+ block, a valid signed grant is enough.
61
+
62
+ The check runs once per subscription, so a permission you revoke
63
+ mid-session takes effect the next time that client subscribes.
64
+ - `record.collaborative_document(name)` returns a bound
65
+ `Y::Collaborative::Attribute`, which both application code and the shipped
66
+ channel use to read and write the document. `y_doc` builds a native
67
+ `Y::Doc`, and `load_state`, `append`, and `key` all use the same storage
68
+ class. `key` and `load_state` don't create a row, but the first `append`
69
+ does. Use `.document_row` for the built-in row operations.
70
+ - `Y::Document.key_for(record, name)` returns the conventional key without
71
+ creating a document row.
72
+ - `collaborative_sgid(name, expires_in:)` and
73
+ `collaborative_document_tag(record, name, expires_in:, refresh:)` let you
74
+ set the grant lifetime, which used to be GlobalID's default with no way to
75
+ shorten it. `refresh:` is a URL the element fetches when a subscription is
76
+ rejected. That action re-runs the app's authorization and renders
77
+ `{ grant: ... }`, and the client resubscribes the same session with the new
78
+ grant. The client fetches the URL once per rejection and does not poll.
79
+
80
+ - `Y::DocumentChannel` ships in the gem, so apps don't need to write a
81
+ channel. Clients subscribe with the signed grant the page rendered
82
+ (`{ grant:, name: }`). The channel finds the record from the grant, loads
83
+ the document, and stores changes in `Y::Document`. It rejects grants that
84
+ are missing, tampered with, for the wrong attribute, or for a destroyed
85
+ record.
86
+
87
+ - `collaborative_document_tag(record, name, **options)` is available in
88
+ Action View through the engine. It renders the mount element with the
89
+ signed grant, the attribute name, and the channel name as data attributes.
90
+ Anyone holding the grant can open the document, the same as with a
91
+ `turbo_stream_from` stream name, so only render the tag on pages where the
92
+ user is allowed to edit the record.
93
+
94
+ - Channels get `Y::Document` storage by default. Declare `on_load` and
95
+ `on_change` if you want a different store. Outside a yrby-rails app there
96
+ is still no default, and subscribing raises until both hooks are declared.
97
+
98
+ - `has_collaborative_document :name, encrypted: true` declares which storage
99
+ class backs an attribute, and `Y::DocumentChannel` uses that class. Every
100
+ load and append for an encrypted attribute goes through
101
+ `Y::EncryptedDocument`, so the bytes are ciphertext at rest and the plain
102
+ classes can't read them. The model decides whether an attribute is
103
+ encrypted, and a page or client can't change that. Undeclared attributes use
104
+ plain `Y::Document`.
105
+
106
+ - `Y::Collaborative` adds signed tokens for record-backed documents, and the
107
+ engine includes it into ActiveRecord::Base. A page creates a token with
108
+ `record.collaborative_sgid(:body)`, and the channel finds the record with
109
+ `Y::Collaborative.locate(params[:grant], :body)`. The token is a signed
110
+ GlobalID scoped to one attribute, so a token for one field can't open
111
+ another. This is the standard way to implement `authorized?` for
112
+ record-backed documents. lexxy-realtime already uses this flow, and now it
113
+ ships in yrby-rails.
114
+
115
+ ### Changed
116
+
117
+ - **Breaking:** channels reject every subscription until they define
118
+ `authorized?(key)`. `sync_subscribed` now calls it before opening a stream
119
+ or serving state, and the default returns `false`. Add the method to every
120
+ channel that includes `Y::ActionCable`:
121
+
122
+ ```ruby
123
+ private
124
+
125
+ def authorized?(key)
126
+ current_user&.can_edit?(key)
127
+ end
128
+ ```
129
+
130
+ Return `true` for public documents. If the default is what rejected a
131
+ subscription, the log message says so.
132
+
133
+ - `yrby:install` now creates only the storage migration, since the gem ships
134
+ `Y::DocumentChannel`. Pass `--channel` to also generate an application
135
+ channel for custom authorization or room-keyed documents. That channel
136
+ rejects every subscription until you implement `authorized?`.
137
+
138
+ ### Fixed
139
+
140
+ - Document elements now attach editors to sessions that exist independently
141
+ of any editor, so pending edits survive navigation under their original
142
+ grant and rejected deliveries can still be recovered. Turbo previews are
143
+ inert, and cached HTML contains no CRDT bytes.
144
+ - When an attribute morphs, the element releases the old editor binding and
145
+ acquires the new document. It doesn't move pending edits or authorization
146
+ over to the new document.
147
+
148
+ - Default storage now supplies the loader and recorder together, and
149
+ declaring only one custom hook raises before subscribing or acknowledging
150
+ an update. Previously, reads and writes silently went to different stores.
151
+
152
+ - Signed grants work even when the host app doesn't load Active Job.
153
+
154
+ - The `yrby:tables` migration template caps `y_documents.state` at
155
+ `1.gigabyte - 1` instead of `4.gigabytes - 1`. Postgres raises
156
+ `ArgumentError` for binary limits above 1 GB - 1, so `db:migrate` on a
157
+ fresh Postgres app failed. MySQL maps both values to `longblob`, and
158
+ SQLite ignores limits, so nothing changes there. Apps that already
159
+ migrated are unaffected.
160
+
8
161
  ## [0.6.1] - 2026-08-11
9
162
 
10
163
  ### Fixed