#![doc(
html_logo_url = "https://raw.githubusercontent.com/automerge/automerge-rs/main/img/brandmark.svg",
html_favicon_url = "https:///raw.githubusercontent.com/automerge/automerge-rs/main/img/favicon.ico"
)]
#![warn(
missing_debug_implementations,
// missing_docs, // TODO: add documentation!
rust_2021_compatibility,
rust_2018_idioms,
unreachable_pub,
bad_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)]
#![allow(clippy::unused_unit)]
use am::transaction::CommitOptions;
use am::transaction::Transactable;
use automerge as am;
use automerge::{Change, ObjId, Prop, Value, ROOT};
use js_sys::{Array, Object, Uint8Array};
use std::convert::TryInto;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
mod interop;
mod observer;
mod sync;
mod value;
use observer::Observer;
use interop::{
apply_patch, get_heads, js_get, js_set, list_to_js, list_to_js_at, map_to_js, map_to_js_at,
to_js_err, to_objtype, to_prop, AR, JS,
};
use sync::SyncState;
use value::{datatype, ScalarValue};
#[allow(unused_macros)]
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
};
}
type AutoCommit = am::AutoCommitWithObs;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
#[derive(Debug)]
pub struct Automerge {
doc: AutoCommit,
}
#[wasm_bindgen]
impl Automerge {
pub fn new(actor: Option) -> Result {
let mut doc = AutoCommit::default();
if let Some(a) = actor {
let a = automerge::ActorId::from(hex::decode(a).map_err(to_js_err)?.to_vec());
doc.set_actor(a);
}
Ok(Automerge { doc })
}
#[allow(clippy::should_implement_trait)]
pub fn clone(&mut self, actor: Option) -> Result {
let mut automerge = Automerge {
doc: self.doc.clone(),
};
if let Some(s) = actor {
let actor = automerge::ActorId::from(hex::decode(s).map_err(to_js_err)?.to_vec());
automerge.doc.set_actor(actor);
}
Ok(automerge)
}
pub fn fork(&mut self, actor: Option) -> Result {
let mut automerge = Automerge {
doc: self.doc.fork(),
};
if let Some(s) = actor {
let actor = automerge::ActorId::from(hex::decode(s).map_err(to_js_err)?.to_vec());
automerge.doc.set_actor(actor);
}
Ok(automerge)
}
#[wasm_bindgen(js_name = forkAt)]
pub fn fork_at(&mut self, heads: JsValue, actor: Option) -> Result {
let deps: Vec<_> = JS(heads).try_into()?;
let mut automerge = Automerge {
doc: self.doc.fork_at(&deps)?,
};
if let Some(s) = actor {
let actor = automerge::ActorId::from(hex::decode(s).map_err(to_js_err)?.to_vec());
automerge.doc.set_actor(actor);
}
Ok(automerge)
}
pub fn free(self) {}
#[wasm_bindgen(js_name = pendingOps)]
pub fn pending_ops(&self) -> JsValue {
(self.doc.pending_ops() as u32).into()
}
pub fn commit(&mut self, message: Option, time: Option) -> JsValue {
let mut commit_opts = CommitOptions::default();
if let Some(message) = message {
commit_opts.set_message(message);
}
if let Some(time) = time {
commit_opts.set_time(time as i64);
}
let hash = self.doc.commit_with(commit_opts);
JsValue::from_str(&hex::encode(&hash.0))
}
pub fn merge(&mut self, other: &mut Automerge) -> Result {
let heads = self.doc.merge(&mut other.doc)?;
let heads: Array = heads
.iter()
.map(|h| JsValue::from_str(&hex::encode(&h.0)))
.collect();
Ok(heads)
}
pub fn rollback(&mut self) -> f64 {
self.doc.rollback() as f64
}
pub fn keys(&self, obj: JsValue, heads: Option) -> Result {
let obj = self.import(obj)?;
let result = if let Some(heads) = get_heads(heads) {
self.doc
.keys_at(&obj, &heads)
.map(|s| JsValue::from_str(&s))
.collect()
} else {
self.doc.keys(&obj).map(|s| JsValue::from_str(&s)).collect()
};
Ok(result)
}
pub fn text(&self, obj: JsValue, heads: Option) -> Result {
let obj = self.import(obj)?;
if let Some(heads) = get_heads(heads) {
Ok(self.doc.text_at(&obj, &heads)?)
} else {
Ok(self.doc.text(&obj)?)
}
}
pub fn splice(
&mut self,
obj: JsValue,
start: f64,
delete_count: f64,
text: JsValue,
) -> Result<(), JsValue> {
let obj = self.import(obj)?;
let start = start as usize;
let delete_count = delete_count as usize;
let mut vals = vec![];
if let Some(t) = text.as_string() {
self.doc.splice_text(&obj, start, delete_count, &t)?;
} else {
if let Ok(array) = text.dyn_into::() {
for i in array.iter() {
let value = self
.import_scalar(&i, &None)
.ok_or_else(|| to_js_err("expected scalar"))?;
vals.push(value);
}
}
self.doc
.splice(&obj, start, delete_count, vals.into_iter())?;
}
Ok(())
}
pub fn push(&mut self, obj: JsValue, value: JsValue, datatype: JsValue) -> Result<(), JsValue> {
let obj = self.import(obj)?;
let value = self
.import_scalar(&value, &datatype.as_string())
.ok_or_else(|| to_js_err("invalid scalar value"))?;
let index = self.doc.length(&obj);
self.doc.insert(&obj, index, value)?;
Ok(())
}
#[wasm_bindgen(js_name = pushObject)]
pub fn push_object(&mut self, obj: JsValue, value: JsValue) -> Result