yrb-lite 0.1.0.beta5 → 0.1.0.beta7

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.
@@ -1,11 +1,16 @@
1
- use magnus::{function, method, prelude::*, Error, RString, Ruby, TryConvert, Value};
1
+ use magnus::{
2
+ function, method, prelude::*, Error, ExceptionClass, RString, Ruby, TryConvert, Value,
3
+ };
2
4
  use std::sync::Mutex;
3
- use yrs::encoding::read::{Cursor, Read};
4
- use yrs::sync::protocol::MessageReader;
5
5
  use yrs::sync::{Awareness, DefaultProtocol, Message, Protocol, SyncMessage};
6
- use yrs::updates::decoder::{Decode, DecoderV1};
6
+ use yrs::updates::decoder::Decode;
7
7
  use yrs::updates::encoder::{Encode, Encoder, EncoderV1};
8
- use yrs::{ClientID, Doc, ReadTxn, Transact};
8
+ use yrs::{Doc, ReadTxn, Transact};
9
+
10
+ mod protocol;
11
+ use protocol::{
12
+ classify_message, doc_has_pending, merged_doc_update, update_advances_doc, update_is_ready,
13
+ };
9
14
 
10
15
  /// Wrapper around yrs Doc.
11
16
  ///
@@ -123,99 +128,22 @@ fn copy_bytes(s: RString) -> Vec<u8> {
123
128
  unsafe { s.as_slice() }.to_vec()
124
129
  }
125
130
 
126
- fn runtime_error(msg: String) -> Error {
127
- Error::new(Ruby::get().unwrap().exception_runtime_error(), msg)
128
- }
129
-
130
- // ============================================================================
131
- // Pure protocol helpers (no Ruby, no GVL); unit-tested in the `tests` module.
132
- // ============================================================================
133
-
134
- /// Classify a frame: a non-zero code only for exactly one well-formed message
135
- /// that consumes the whole buffer (see `RbAwareness::message_kind` for codes).
136
- fn classify_message(bytes: &[u8]) -> u8 {
137
- let mut decoder = DecoderV1::new(Cursor::new(bytes));
138
- let msg = match Message::decode(&mut decoder) {
139
- Ok(msg) => msg,
140
- Err(_) => return 0, // empty or malformed
141
- };
142
- // Any remaining byte means a second message or trailing garbage.
143
- if decoder.read_u8().is_ok() {
144
- return 0;
145
- }
146
- match msg {
147
- Message::Sync(SyncMessage::SyncStep1(_)) => 1,
148
- Message::Sync(SyncMessage::SyncStep2(_)) | Message::Sync(SyncMessage::Update(_)) => 2,
149
- Message::Awareness(_) => 3,
150
- Message::AwarenessQuery => 4,
151
- _ => 0, // Auth / Custom: not part of our model
152
- }
153
- }
154
-
155
- /// Merge the document-update deltas (Update / SyncStep2 payloads) carried by a
156
- /// frame into one update, or `None` if the frame carries no document change
157
- /// (a request, an awareness update, or a no-op handshake SyncStep2).
158
- fn merged_doc_update(bytes: &[u8]) -> Result<Option<Vec<u8>>, String> {
159
- let mut decoder = DecoderV1::new(Cursor::new(bytes));
160
- let mut updates: Vec<Vec<u8>> = Vec::new();
161
- for msg in MessageReader::new(&mut decoder) {
162
- match msg.map_err(|e| e.to_string())? {
163
- Message::Sync(SyncMessage::Update(u)) | Message::Sync(SyncMessage::SyncStep2(u)) => {
164
- updates.push(u)
165
- }
166
- _ => {}
167
- }
168
- }
169
- let merged = match updates.len() {
170
- 0 => return Ok(None),
171
- 1 => updates.pop().unwrap(),
172
- _ => yrs::merge_updates_v1(&updates).map_err(|e| e.to_string())?,
173
- };
174
- let update = yrs::Update::decode_v1(&merged).map_err(|e| e.to_string())?;
175
- // A genuine no-op (e.g. the empty SyncStep2 in an opening handshake) carries
176
- // no structs, no deletes, and no dependencies. We must NOT treat a causally-
177
- // pending update as a no-op: since yrs 0.26 such an update reports an empty
178
- // state_vector (its structs can't integrate yet), but it still carries
179
- // content and a non-empty lower bound (the deps it's waiting on). Dropping it
180
- // here would silently swallow a gappy update instead of rejecting + resyncing.
181
- if update.state_vector().is_empty()
182
- && update.delete_set().is_empty()
183
- && update.state_vector_lower().is_empty()
184
- {
185
- return Ok(None);
186
- }
187
- Ok(Some(merged))
188
- }
189
-
190
- /// Collect the awareness client IDs referenced by a frame's awareness messages.
191
- fn awareness_client_ids_in(bytes: &[u8]) -> Result<Vec<u64>, String> {
192
- let mut decoder = DecoderV1::new(Cursor::new(bytes));
193
- let mut ids = Vec::new();
194
- for msg in MessageReader::new(&mut decoder) {
195
- if let Message::Awareness(update) = msg.map_err(|e| e.to_string())? {
196
- ids.extend(update.clients.keys().map(|c| c.get()));
197
- }
198
- }
199
- Ok(ids)
200
- }
201
-
202
- /// True if applying `update_bytes` to `doc` would integrate cleanly: every
203
- /// dependency the update references is already present (the doc's state vector
204
- /// covers the update's lower bound). A pure read; does not mutate the doc.
205
- /// When false, applying it would park a pending struct -- the signal that an
206
- /// earlier, causally-prior update is missing.
207
- fn update_is_ready(doc: &Doc, update_bytes: &[u8]) -> Result<bool, String> {
208
- let update = yrs::Update::decode_v1(update_bytes).map_err(|e| e.to_string())?;
209
- Ok(doc.transact().state_vector() >= update.state_vector_lower())
131
+ /// Build a `YrbLite::Error` (the gem's own error class, defined in `init`) so
132
+ /// native decode/apply failures surface as a project-specific error rather than
133
+ /// a generic RuntimeError. Falls back to RuntimeError only if the class somehow
134
+ /// can't be resolved.
135
+ fn yrb_error(msg: String) -> Error {
136
+ let ruby = Ruby::get().unwrap();
137
+ let class = ruby
138
+ .eval::<ExceptionClass>("YrbLite::Error")
139
+ .unwrap_or_else(|_| ruby.exception_runtime_error());
140
+ Error::new(class, msg)
210
141
  }
211
142
 
212
- /// True if the doc holds pending structs or a pending delete set -- blocks that
213
- /// couldn't integrate because a dependency is missing. Used as a backstop after
214
- /// loading from storage: leftover pending means the stored log has a causal gap.
215
- fn doc_has_pending(doc: &Doc) -> bool {
216
- let txn = doc.transact();
217
- txn.store().pending_update().is_some() || txn.store().pending_ds().is_some()
218
- }
143
+ // CLIENT IDs ARE NOT VALIDATED -- whoever supplies the id (the app via
144
+ // `Doc.new(id)` / `Awareness.new(id)`, or a remote peer over the wire) is
145
+ // responsible for keeping it JS-safe (<= 2^53 - 1). See the protocol.rs header
146
+ // for why (and `ClientID::try_new`, proposed upstream, for strict rejection).
219
147
 
220
148
  // ============================================================================
221
149
  // Doc Implementation
@@ -233,17 +161,14 @@ impl RbDoc {
233
161
  Ok(RbDoc(doc))
234
162
  }
235
163
 
236
- /// Get the client ID
237
164
  fn client_id(&self) -> u64 {
238
165
  self.0.client_id().get()
239
166
  }
240
167
 
241
- /// Get the document GUID
242
168
  fn guid(&self) -> String {
243
169
  self.0.guid().to_string()
244
170
  }
245
171
 
246
- /// Get the current state vector encoded as bytes
247
172
  fn encode_state_vector(&self) -> RString {
248
173
  let doc = &self.0;
249
174
  let sv = nogvl(move || {
@@ -270,11 +195,10 @@ impl RbDoc {
270
195
  let txn = doc.transact();
271
196
  Ok(txn.encode_state_as_update_v1(&sv))
272
197
  })
273
- .map_err(runtime_error)?;
198
+ .map_err(yrb_error)?;
274
199
  Ok(binary_string(&update))
275
200
  }
276
201
 
277
- /// Apply a V1 update to the document
278
202
  fn apply_update(&self, update: RString) -> Result<(), Error> {
279
203
  let update_bytes = copy_bytes(update);
280
204
  let doc = &self.0;
@@ -283,7 +207,7 @@ impl RbDoc {
283
207
  let mut txn = doc.transact_mut();
284
208
  txn.apply_update(update).map_err(|e| e.to_string())
285
209
  })
286
- .map_err(runtime_error)
210
+ .map_err(yrb_error)
287
211
  }
288
212
 
289
213
  /// True if applying `update` would integrate cleanly (its dependencies are
@@ -292,7 +216,16 @@ impl RbDoc {
292
216
  fn update_ready(&self, update: RString) -> Result<bool, Error> {
293
217
  let update_bytes = copy_bytes(update);
294
218
  let doc = &self.0;
295
- nogvl(move || update_is_ready(doc, &update_bytes)).map_err(runtime_error)
219
+ nogvl(move || update_is_ready(doc, &update_bytes)).map_err(yrb_error)
220
+ }
221
+
222
+ /// True if applying `update` would change the document (it carries new
223
+ /// content), false if the doc already contains it (an already-applied
224
+ /// retry). See `update_advances_doc`. Pure read; does not mutate.
225
+ fn update_advances(&self, update: RString) -> Result<bool, Error> {
226
+ let update_bytes = copy_bytes(update);
227
+ let doc = &self.0;
228
+ nogvl(move || update_advances_doc(doc, &update_bytes)).map_err(yrb_error)
296
229
  }
297
230
 
298
231
  /// True if the document holds pending (un-integrable) structs waiting on a
@@ -323,7 +256,7 @@ impl RbDoc {
323
256
  let update = txn.encode_state_as_update_v1(&sv);
324
257
  Ok(Message::Sync(SyncMessage::SyncStep2(update)).encode_v1())
325
258
  })
326
- .map_err(runtime_error)?;
259
+ .map_err(yrb_error)?;
327
260
  Ok(binary_string(&encoded))
328
261
  }
329
262
 
@@ -369,15 +302,15 @@ impl RbDoc {
369
302
  Message::Custom(tag, _) => Ok((tag, 0, Vec::new())),
370
303
  }
371
304
  })
372
- .map_err(runtime_error)?;
305
+ .map_err(yrb_error)?;
373
306
 
374
307
  Ok(Some((msg_type, sync_type, binary_string(&response))))
375
308
  }
376
309
 
377
310
  /// Encode raw update bytes as a sync Update message
378
311
  fn encode_update_message(&self, update: RString) -> RString {
379
- let update_bytes = unsafe { update.as_slice() };
380
- let msg = Message::Sync(SyncMessage::Update(update_bytes.to_vec()));
312
+ let update_bytes = copy_bytes(update);
313
+ let msg = Message::Sync(SyncMessage::Update(update_bytes));
381
314
  binary_string(&msg.encode_v1())
382
315
  }
383
316
  }
@@ -398,13 +331,11 @@ impl RbAwareness {
398
331
  Ok(RbAwareness(Mutex::new(awareness)))
399
332
  }
400
333
 
401
- /// Get the client ID of the underlying document
402
334
  fn client_id(&self) -> u64 {
403
335
  let awareness = &self.0;
404
336
  nogvl(move || awareness.lock().unwrap().doc().client_id().get())
405
337
  }
406
338
 
407
- /// Get the document GUID
408
339
  fn guid(&self) -> String {
409
340
  let awareness = &self.0;
410
341
  nogvl(move || awareness.lock().unwrap().doc().guid().to_string())
@@ -437,7 +368,7 @@ impl RbAwareness {
437
368
  .map_err(|e| e.to_string())?;
438
369
  Ok(encoder.to_vec())
439
370
  })
440
- .map_err(runtime_error)?;
371
+ .map_err(yrb_error)?;
441
372
  Ok(binary_string(&encoded))
442
373
  }
443
374
 
@@ -464,18 +395,17 @@ impl RbAwareness {
464
395
  }
465
396
  Ok(encoder.to_vec())
466
397
  })
467
- .map_err(runtime_error)?;
398
+ .map_err(yrb_error)?;
468
399
  Ok(binary_string(&encoded))
469
400
  }
470
401
 
471
402
  /// Encode an update message for broadcasting changes to peers.
472
403
  fn encode_update(&self, update: RString) -> RString {
473
- let update_bytes = unsafe { update.as_slice() };
474
- let msg = Message::Sync(SyncMessage::Update(update_bytes.to_vec()));
404
+ let update_bytes = copy_bytes(update);
405
+ let msg = Message::Sync(SyncMessage::Update(update_bytes));
475
406
  binary_string(&msg.encode_v1())
476
407
  }
477
408
 
478
- /// Get the current state vector encoded as bytes
479
409
  fn encode_state_vector(&self) -> RString {
480
410
  let awareness = &self.0;
481
411
  let sv = nogvl(move || {
@@ -504,14 +434,14 @@ impl RbAwareness {
504
434
  let txn = doc.transact();
505
435
  Ok(txn.encode_state_as_update_v1(&sv))
506
436
  })
507
- .map_err(runtime_error)?;
437
+ .map_err(yrb_error)?;
508
438
  Ok(binary_string(&update))
509
439
  }
510
440
 
511
441
  /// Set local awareness state (JSON string)
512
442
  fn set_local_state(&self, json: String) -> Result<(), Error> {
513
443
  let value: serde_json::Value =
514
- serde_json::from_str(&json).map_err(|e| runtime_error(e.to_string()))?;
444
+ serde_json::from_str(&json).map_err(|e| yrb_error(e.to_string()))?;
515
445
  let awareness = &self.0;
516
446
  nogvl(move || -> Result<(), String> {
517
447
  awareness
@@ -520,7 +450,7 @@ impl RbAwareness {
520
450
  .set_local_state(value)
521
451
  .map_err(|e| e.to_string())
522
452
  })
523
- .map_err(runtime_error)
453
+ .map_err(yrb_error)
524
454
  }
525
455
 
526
456
  /// Get local awareness state as JSON string (or nil if not set)
@@ -549,11 +479,10 @@ impl RbAwareness {
549
479
  let update = awareness.update().map_err(|e| e.to_string())?;
550
480
  Ok(Message::Awareness(update).encode_v1())
551
481
  })
552
- .map_err(runtime_error)?;
482
+ .map_err(yrb_error)?;
553
483
  Ok(binary_string(&encoded))
554
484
  }
555
485
 
556
- /// Apply a raw update to the underlying document
557
486
  fn apply_update(&self, update: RString) -> Result<(), Error> {
558
487
  let update_bytes = copy_bytes(update);
559
488
  let awareness = &self.0;
@@ -563,7 +492,7 @@ impl RbAwareness {
563
492
  let mut txn = doc.transact_mut();
564
493
  txn.apply_update(update).map_err(|e| e.to_string())
565
494
  })
566
- .map_err(runtime_error)
495
+ .map_err(yrb_error)
567
496
  }
568
497
 
569
498
  /// True if applying `update` would integrate cleanly (its dependencies are
@@ -576,7 +505,19 @@ impl RbAwareness {
576
505
  let doc = awareness.lock().unwrap().doc().clone();
577
506
  update_is_ready(&doc, &update_bytes)
578
507
  })
579
- .map_err(runtime_error)
508
+ .map_err(yrb_error)
509
+ }
510
+
511
+ /// True if applying `update` would change the document, false if it's an
512
+ /// already-applied retry. See `update_advances_doc`. Pure read.
513
+ fn update_advances(&self, update: RString) -> Result<bool, Error> {
514
+ let update_bytes = copy_bytes(update);
515
+ let awareness = &self.0;
516
+ nogvl(move || {
517
+ let doc = awareness.lock().unwrap().doc().clone();
518
+ update_advances_doc(&doc, &update_bytes)
519
+ })
520
+ .map_err(yrb_error)
580
521
  }
581
522
 
582
523
  /// True if the document holds pending (un-integrable) structs waiting on a
@@ -589,16 +530,6 @@ impl RbAwareness {
589
530
  })
590
531
  }
591
532
 
592
- /// Decode the awareness client IDs referenced by a protocol message
593
- /// (which may pack several sub-messages together). Sync sub-messages are
594
- /// ignored. The ActionCable layer uses this to learn which presence
595
- /// states arrived on a connection, so it can clear them when that
596
- /// connection closes.
597
- fn awareness_client_ids(&self, data: RString) -> Result<Vec<u64>, Error> {
598
- let data_bytes = copy_bytes(data);
599
- nogvl(move || awareness_client_ids_in(&data_bytes)).map_err(runtime_error)
600
- }
601
-
602
533
  /// Classify a frame for safe routing and relay. Returns a code only when
603
534
  /// the frame is exactly one well-formed message that consumes the whole
604
535
  /// buffer, so a malformed, truncated, multi-message, or trailing-garbage
@@ -617,42 +548,13 @@ impl RbAwareness {
617
548
  /// Extract the document-update delta carried by a protocol message: the
618
549
  /// payloads of any Update or SyncStep2 sub-messages, merged into a single
619
550
  /// update. Returns nil if the message carries no document change (for
620
- /// instance a SyncStep1 request or an awareness update). The strict audit
621
- /// path records this exact delta before applying it.
551
+ /// instance a SyncStep1 request or an awareness update). The store-backed
552
+ /// path records this exact delta before relaying it.
622
553
  fn update_from_message(&self, data: RString) -> Result<Option<RString>, Error> {
623
554
  let data_bytes = copy_bytes(data);
624
- let merged = nogvl(move || merged_doc_update(&data_bytes)).map_err(runtime_error)?;
555
+ let merged = nogvl(move || merged_doc_update(&data_bytes)).map_err(yrb_error)?;
625
556
  Ok(merged.map(|b| binary_string(&b)))
626
557
  }
627
-
628
- /// Mark the given clients as disconnected and return an awareness protocol
629
- /// message (null-state, bumped clock) announcing their removal to peers.
630
- /// Only clients currently known to this Awareness are removed; unknown
631
- /// IDs are skipped (so we never broadcast phantom removals). Returns an
632
- /// empty string when nothing was removed.
633
- fn remove_clients(&self, client_ids: Vec<u64>) -> Result<RString, Error> {
634
- let awareness = &self.0;
635
- let encoded = nogvl(move || -> Result<Vec<u8>, String> {
636
- let mut awareness = awareness.lock().unwrap();
637
- let mut removed = Vec::new();
638
- for id in client_ids {
639
- let cid = ClientID::new(id);
640
- if awareness.meta(cid).is_some() {
641
- awareness.remove_state(cid);
642
- removed.push(cid);
643
- }
644
- }
645
- if removed.is_empty() {
646
- return Ok(Vec::new());
647
- }
648
- let update = awareness
649
- .update_with_clients(removed)
650
- .map_err(|e| e.to_string())?;
651
- Ok(Message::Awareness(update).encode_v1())
652
- })
653
- .map_err(runtime_error)?;
654
- Ok(binary_string(&encoded))
655
- }
656
558
  }
657
559
 
658
560
  // ============================================================================
@@ -682,6 +584,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
682
584
  )?;
683
585
  doc_class.define_method("apply_update", method!(RbDoc::apply_update, 1))?;
684
586
  doc_class.define_method("update_ready?", method!(RbDoc::update_ready, 1))?;
587
+ doc_class.define_method("update_advances?", method!(RbDoc::update_advances, 1))?;
685
588
  doc_class.define_method("pending?", method!(RbDoc::pending, 0))?;
686
589
  doc_class.define_method("sync_step1", method!(RbDoc::sync_step1, 0))?;
687
590
  doc_class.define_method("sync_step2", method!(RbDoc::sync_step2, 1))?;
@@ -713,6 +616,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
713
616
  )?;
714
617
  awareness_class.define_method("apply_update", method!(RbAwareness::apply_update, 1))?;
715
618
  awareness_class.define_method("update_ready?", method!(RbAwareness::update_ready, 1))?;
619
+ awareness_class.define_method("update_advances?", method!(RbAwareness::update_advances, 1))?;
716
620
  awareness_class.define_method("pending?", method!(RbAwareness::pending, 0))?;
717
621
  awareness_class.define_method("set_local_state", method!(RbAwareness::set_local_state, 1))?;
718
622
  awareness_class.define_method("local_state", method!(RbAwareness::local_state, 0))?;
@@ -724,11 +628,6 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
724
628
  "encode_awareness_update",
725
629
  method!(RbAwareness::encode_awareness_update, 0),
726
630
  )?;
727
- awareness_class.define_method(
728
- "awareness_client_ids",
729
- method!(RbAwareness::awareness_client_ids, 1),
730
- )?;
731
- awareness_class.define_method("remove_clients", method!(RbAwareness::remove_clients, 1))?;
732
631
  awareness_class.define_method(
733
632
  "update_from_message",
734
633
  method!(RbAwareness::update_from_message, 1),
@@ -746,161 +645,3 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
746
645
 
747
646
  Ok(())
748
647
  }
749
-
750
- // ============================================================================
751
- // Tests for the pure protocol helpers (run with `cargo test`, no Ruby VM)
752
- // ============================================================================
753
-
754
- #[cfg(test)]
755
- mod tests {
756
- use super::*;
757
- use yrs::sync::Awareness;
758
- use yrs::Text;
759
-
760
- fn text_update(content: &str) -> Vec<u8> {
761
- let doc = Doc::new();
762
- let text = doc.get_or_insert_text("content");
763
- text.insert(&mut doc.transact_mut(), 0, content);
764
- let update = doc
765
- .transact()
766
- .encode_state_as_update_v1(&yrs::StateVector::default());
767
- update
768
- }
769
-
770
- fn update_frame(content: &str) -> Vec<u8> {
771
- Message::Sync(SyncMessage::Update(text_update(content))).encode_v1()
772
- }
773
-
774
- fn step1_frame() -> Vec<u8> {
775
- Message::Sync(SyncMessage::SyncStep1(yrs::StateVector::default())).encode_v1()
776
- }
777
-
778
- fn awareness_frame(client_id: u64) -> Vec<u8> {
779
- let mut awareness = Awareness::new(Doc::with_client_id(client_id));
780
- awareness
781
- .set_local_state(serde_json::json!({ "user": "alice" }))
782
- .unwrap();
783
- Message::Awareness(awareness.update().unwrap()).encode_v1()
784
- }
785
-
786
- #[test]
787
- fn classify_accepts_clean_single_messages() {
788
- assert_eq!(classify_message(&step1_frame()), 1);
789
- assert_eq!(classify_message(&update_frame("hi")), 2);
790
- assert_eq!(classify_message(&awareness_frame(7)), 3);
791
- assert_eq!(classify_message(&Message::AwarenessQuery.encode_v1()), 4);
792
- }
793
-
794
- #[test]
795
- fn classify_rejects_unsafe_frames() {
796
- assert_eq!(classify_message(b""), 0, "empty");
797
- assert_eq!(classify_message(&[0xff, 0xff, 0xff]), 0, "garbage");
798
- assert_eq!(classify_message(&[0x63, 0x63, 0x63]), 0, "unknown type");
799
-
800
- let mut two = update_frame("a");
801
- two.extend(awareness_frame(1)); // two messages packed together
802
- assert_eq!(classify_message(&two), 0, "multi-message");
803
-
804
- let mut trailing = update_frame("a");
805
- trailing.extend_from_slice(&[0xde, 0xad]);
806
- assert_eq!(classify_message(&trailing), 0, "trailing garbage");
807
-
808
- let frame = update_frame("hello");
809
- assert_eq!(classify_message(&frame[..frame.len() / 2]), 0, "truncated");
810
- }
811
-
812
- #[test]
813
- fn merged_doc_update_extracts_and_skips_no_ops() {
814
- // A document update yields a delta that reconstructs the content.
815
- let delta = merged_doc_update(&update_frame("hello"))
816
- .unwrap()
817
- .expect("a document update");
818
- let doc = Doc::new();
819
- doc.transact_mut()
820
- .apply_update(yrs::Update::decode_v1(&delta).unwrap())
821
- .unwrap();
822
- // The delta carried real content, so applying it advances the doc.
823
- assert!(!doc.transact().state_vector().is_empty());
824
-
825
- // A SyncStep1 request carries no document change.
826
- assert!(merged_doc_update(&step1_frame()).unwrap().is_none());
827
-
828
- // An empty SyncStep2 (no new structs) is a no-op.
829
- let empty = Message::Sync(SyncMessage::SyncStep2(
830
- Doc::new()
831
- .transact()
832
- .encode_state_as_update_v1(&yrs::StateVector::default()),
833
- ))
834
- .encode_v1();
835
- assert!(merged_doc_update(&empty).unwrap().is_none());
836
- }
837
-
838
- #[test]
839
- fn merged_doc_update_merges_multiple_updates() {
840
- // Two updates from different clients packed in one frame merge into one.
841
- let mut frame = update_frame("a");
842
- frame.extend(update_frame("b"));
843
- let merged = merged_doc_update(&frame).unwrap().expect("merged update");
844
-
845
- // The merged update must decode cleanly as a single update.
846
- assert!(yrs::Update::decode_v1(&merged).is_ok());
847
- }
848
-
849
- #[test]
850
- fn awareness_client_ids_are_collected() {
851
- assert_eq!(
852
- awareness_client_ids_in(&awareness_frame(111)).unwrap(),
853
- vec![111]
854
- );
855
- // A document frame has no awareness client ids.
856
- assert!(awareness_client_ids_in(&update_frame("x"))
857
- .unwrap()
858
- .is_empty());
859
- }
860
-
861
- #[test]
862
- fn update_readiness_and_pending_detect_a_causal_gap() {
863
- // Three sequential single-char inserts from one client: A, then B, then
864
- // C. Each delta depends on the previous, so C can't integrate without B.
865
- let src = Doc::new();
866
- let txt = src.get_or_insert_text("t");
867
- let mut deltas: Vec<Vec<u8>> = Vec::new();
868
- let mut prev = yrs::StateVector::default();
869
- for (i, ch) in ["A", "B", "C"].into_iter().enumerate() {
870
- txt.insert(&mut src.transact_mut(), i as u32, ch);
871
- deltas.push(src.transact().encode_state_as_update_v1(&prev));
872
- prev = src.transact().state_vector();
873
- }
874
- let (u1, u2, u3) = (&deltas[0], &deltas[1], &deltas[2]);
875
-
876
- // A doc holding only u1 (u2 was lost in transit / its record failed):
877
- let doc = Doc::new();
878
- doc.transact_mut()
879
- .apply_update(yrs::Update::decode_v1(u1).unwrap())
880
- .unwrap();
881
- assert!(update_is_ready(&doc, u1).unwrap(), "u1 has no missing deps");
882
- assert!(
883
- !update_is_ready(&doc, u3).unwrap(),
884
- "u3 depends on the missing u2"
885
- );
886
- assert!(
887
- !doc_has_pending(&doc),
888
- "nothing pending until u3 is applied"
889
- );
890
-
891
- // Applying u3 anyway parks it as a pending struct.
892
- doc.transact_mut()
893
- .apply_update(yrs::Update::decode_v1(u3).unwrap())
894
- .unwrap();
895
- assert!(
896
- doc_has_pending(&doc),
897
- "u3 is pending: its parent u2 is missing"
898
- );
899
-
900
- // Once u2 arrives (via resync), u3 integrates and pending clears.
901
- doc.transact_mut()
902
- .apply_update(yrs::Update::decode_v1(u2).unwrap())
903
- .unwrap();
904
- assert!(!doc_has_pending(&doc), "u2 arrived; u3 integrated");
905
- }
906
- }