use std::cell::RefCell;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use yrs::{Doc, ReadTxn, Transact, Update, updates::decoder::Decode};
use crate::{
Result, Store, Transaction,
crdt::{CRDT, Data},
store::errors::StoreError,
};
#[derive(Debug, Error)]
pub enum YDocError {
#[error("Y-CRDT operation failed: {operation} - {reason}")]
Operation { operation: String, reason: String },
#[error("Invalid Y-CRDT binary data: {reason}")]
InvalidData { reason: String },
#[error("Y-CRDT merge failed: {reason}")]
Merge { reason: String },
}
impl From<YDocError> for StoreError {
fn from(err: YDocError) -> Self {
StoreError::ImplementationError {
store: "YDoc".to_string(),
reason: err.to_string(),
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct YrsBinary {
data: Vec<u8>,
}
impl Data for YrsBinary {}
impl CRDT for YrsBinary {
fn merge(&self, other: &Self) -> Result<Self> {
let doc = Doc::new();
if !self.data.is_empty() {
let update = Update::decode_v1(&self.data).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode Y-CRDT update (self): {e}"),
})
})?;
let mut txn = doc.transact_mut();
txn.apply_update(update).map_err(|e| {
StoreError::from(YDocError::Merge {
reason: format!("Failed to apply Y-CRDT update (self): {e}"),
})
})?;
}
if !other.data.is_empty() {
let other_update = Update::decode_v1(&other.data).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode Y-CRDT update (other): {e}"),
})
})?;
let mut txn = doc.transact_mut();
txn.apply_update(other_update).map_err(|e| {
StoreError::from(YDocError::Merge {
reason: format!("Failed to apply Y-CRDT update (other): {e}"),
})
})?;
}
let txn = doc.transact();
let merged_update = txn.encode_state_as_update_v1(&yrs::StateVector::default());
Ok(YrsBinary {
data: merged_update,
})
}
}
impl YrsBinary {
pub fn new(data: Vec<u8>) -> Self {
Self { data }
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
pub struct YDoc {
name: String,
atomic_op: Transaction,
cached_backend_data: RefCell<Option<YrsBinary>>,
}
impl Store for YDoc {
fn new(op: &Transaction, subtree_name: impl Into<String>) -> Result<Self> {
Ok(Self {
name: subtree_name.into(),
atomic_op: op.clone(),
cached_backend_data: RefCell::new(None),
})
}
fn name(&self) -> &str {
&self.name
}
}
impl YDoc {
pub fn doc(&self) -> Result<Doc> {
let doc = self.get_initial_doc()?;
let local_data = self
.atomic_op
.get_local_data::<YrsBinary>(&self.name)
.unwrap_or_default();
if !local_data.is_empty() {
let local_update = Update::decode_v1(local_data.as_bytes()).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode local Y-CRDT update: {e}"),
})
})?;
let mut txn = doc.transact_mut();
txn.apply_update(local_update).map_err(|e| {
StoreError::from(YDocError::Operation {
operation: "apply_local_update".to_string(),
reason: format!("Failed to apply local Y-CRDT update: {e}"),
})
})?;
}
Ok(doc)
}
pub fn with_doc<F, R>(&self, f: F) -> Result<R>
where
F: FnOnce(&Doc) -> Result<R>,
{
let doc = self.doc()?;
f(&doc)
}
pub fn with_doc_mut<F, R>(&self, f: F) -> Result<R>
where
F: FnOnce(&Doc) -> Result<R>,
{
let doc = self.doc()?;
let result = f(&doc)?;
self.save_doc(&doc)?;
Ok(result)
}
pub fn apply_update(&self, update_data: &[u8]) -> Result<()> {
let doc = self.doc()?;
let update = Update::decode_v1(update_data).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode Y-CRDT update: {e}"),
})
})?;
{
let mut txn = doc.transact_mut();
txn.apply_update(update).map_err(|e| {
StoreError::from(YDocError::Operation {
operation: "apply_update".to_string(),
reason: format!("Failed to apply Y-CRDT update: {e}"),
})
})?;
}
self.save_doc(&doc)
}
pub fn get_update(&self) -> Result<Vec<u8>> {
let doc = self.doc()?;
let txn = doc.transact();
let update = txn.encode_state_as_update_v1(&yrs::StateVector::default());
Ok(update)
}
pub fn save_doc_full(&self, doc: &Doc) -> Result<()> {
let txn = doc.transact();
let update = txn.encode_state_as_update_v1(&yrs::StateVector::default());
let yrs_binary = YrsBinary::new(update);
let serialized = serde_json::to_string(&yrs_binary)?;
self.atomic_op.update_subtree(&self.name, &serialized)
}
pub fn save_doc(&self, doc: &Doc) -> Result<()> {
let txn = doc.transact();
let backend_state_vector = self.get_initial_state_vector()?;
let diff_update = txn.encode_state_as_update_v1(&backend_state_vector);
if !diff_update.is_empty() {
let yrs_binary = YrsBinary::new(diff_update);
let serialized = serde_json::to_string(&yrs_binary)?;
self.atomic_op.update_subtree(&self.name, &serialized)?;
}
Ok(())
}
fn get_initial_state_vector(&self) -> Result<yrs::StateVector> {
let backend_data = self.get_cached_backend_data()?;
if backend_data.is_empty() {
return Ok(yrs::StateVector::default());
}
let temp_doc = Doc::new();
let backend_update = Update::decode_v1(backend_data.as_bytes()).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode backend Y-CRDT update: {e}"),
})
})?;
let mut temp_txn = temp_doc.transact_mut();
temp_txn.apply_update(backend_update).map_err(|e| {
StoreError::from(YDocError::Operation {
operation: "get_initial_state_vector".to_string(),
reason: format!("Failed to apply backend Y-CRDT update: {e}"),
})
})?;
drop(temp_txn);
let temp_txn = temp_doc.transact();
Ok(temp_txn.state_vector())
}
fn get_initial_doc(&self) -> Result<Doc> {
let backend_data = self.get_cached_backend_data()?;
let doc = Doc::new();
if !backend_data.is_empty() {
let update = Update::decode_v1(backend_data.as_bytes()).map_err(|e| {
StoreError::from(YDocError::InvalidData {
reason: format!("Failed to decode Y-CRDT update: {e}"),
})
})?;
let mut txn = doc.transact_mut();
txn.apply_update(update).map_err(|e| {
StoreError::from(YDocError::Operation {
operation: "get_initial_doc".to_string(),
reason: format!("Failed to apply Y-CRDT update from backend: {e}"),
})
})?;
}
Ok(doc)
}
fn get_cached_backend_data(&self) -> Result<YrsBinary> {
if let Some(backend_data) = self.cached_backend_data.borrow().as_ref() {
return Ok(backend_data.clone());
}
let backend_data = self.atomic_op.get_full_state::<YrsBinary>(&self.name)?;
*self.cached_backend_data.borrow_mut() = Some(backend_data.clone());
Ok(backend_data)
}
}