Skip to content

Commit

Permalink
docs: add warn(missing_docs) to loro and loro-wasm (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
zxch3n authored Apr 29, 2024
1 parent f869b95 commit 996ce70
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 4 deletions.
2 changes: 1 addition & 1 deletion crates/loro-internal/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct ContainerDiff {
pub diff: Diff,
}

///
/// The kind of the event trigger.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventTriggerKind {
/// The event is triggered by a local transaction.
Expand Down
2 changes: 1 addition & 1 deletion crates/loro-internal/src/loro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl LoroDoc {
self.oplog.lock().unwrap().is_empty() && self.state.lock().unwrap().is_empty()
}

/// Whether [OpLog] ans [DocState] are detached.
/// Whether [OpLog] and [DocState] are detached.
#[inline(always)]
pub fn is_detached(&self) -> bool {
self.detached.load(Acquire)
Expand Down
26 changes: 26 additions & 0 deletions crates/loro-wasm/src/awareness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ use wasm_bindgen::prelude::*;

use crate::{js_peer_to_peer, JsIntoPeerID, JsResult, JsStrPeerID};

/// `Awareness` is a structure that tracks the ephemeral state of peers.
///
/// It can be used to synchronize cursor positions, selections, and the names of the peers.
///
/// The state of a specific peer is expected to be removed after a specified timeout. Use
/// `remove_outdated` to eliminate outdated states.
#[wasm_bindgen]
pub struct AwarenessWasm {
inner: InternalAwareness,
Expand All @@ -19,6 +25,11 @@ extern "C" {

#[wasm_bindgen]
impl AwarenessWasm {
/// Creates a new `Awareness` instance.
///
/// The `timeout` parameter specifies the duration in milliseconds.
/// A state of a peer is considered outdated, if the last update of the state of the peer
/// is older than the `timeout`.
#[wasm_bindgen(constructor)]
pub fn new(peer: JsIntoPeerID, timeout: f64) -> AwarenessWasm {
let id = js_peer_to_peer(peer.into()).unwrap_throw();
Expand All @@ -27,6 +38,7 @@ impl AwarenessWasm {
}
}

/// Encodes the state of the given peers.
pub fn encode(&self, peers: Array) -> Vec<u8> {
let mut peer_vec = Vec::with_capacity(peers.length() as usize);
for peer in peers.iter() {
Expand All @@ -36,10 +48,15 @@ impl AwarenessWasm {
self.inner.encode(&peer_vec)
}

/// Encodes the state of all peers.
pub fn encodeAll(&self) -> Vec<u8> {
self.inner.encode_all()
}

/// Applies the encoded state of peers.
///
/// Each peer's deletion countdown will be reset upon update, requiring them to pass through the `timeout`
/// interval again before being eligible for deletion.
pub fn apply(&mut self, encoded_peers_info: Vec<u8>) -> JsResult<JsAwarenessApplyResult> {
let (updated, added) = self.inner.apply(&encoded_peers_info);
let ans = Object::new();
Expand All @@ -51,16 +68,19 @@ impl AwarenessWasm {
Ok(v.into())
}

/// Sets the state of the local peer.
#[wasm_bindgen(skip_typescript)]
pub fn setLocalState(&mut self, value: JsValue) {
self.inner.set_local_state(value);
}

/// Get the PeerID of the local peer.
pub fn peer(&self) -> JsStrPeerID {
let v: JsValue = format!("{}", self.inner.peer()).into();
v.into()
}

/// Get the state of all peers.
#[wasm_bindgen(skip_typescript)]
pub fn getAllStates(&self) -> JsAwarenessStates {
let states = self.inner.get_all_states();
Expand All @@ -72,6 +92,7 @@ impl AwarenessWasm {
v.into()
}

/// Get the state of a given peer.
#[wasm_bindgen(skip_typescript)]
pub fn getState(&self, peer: JsIntoPeerID) -> JsValue {
let id = js_peer_to_peer(peer.into()).unwrap_throw();
Expand All @@ -82,6 +103,7 @@ impl AwarenessWasm {
state.state.clone().into()
}

/// Get the timestamp of the state of a given peer.
pub fn getTimestamp(&self, peer: JsIntoPeerID) -> Option<f64> {
let id = js_peer_to_peer(peer.into()).unwrap_throw();
self.inner
Expand All @@ -90,6 +112,7 @@ impl AwarenessWasm {
.map(|r| r.timestamp as f64)
}

/// Remove the states of outdated peers.
pub fn removeOutdated(&mut self) -> Vec<JsStrPeerID> {
let outdated = self.inner.remove_outdated();
outdated
Expand All @@ -101,14 +124,17 @@ impl AwarenessWasm {
.collect()
}

/// Get the number of peers.
pub fn length(&self) -> i32 {
self.inner.get_all_states().len() as i32
}

/// If the state is empty.
pub fn isEmpty(&self) -> bool {
self.inner.get_all_states().is_empty()
}

/// Get all the peers
pub fn peers(&self) -> Vec<JsStrPeerID> {
self.inner
.get_all_states()
Expand Down
67 changes: 66 additions & 1 deletion crates/loro-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Loro WASM bindings.
#![allow(non_snake_case)]
#![warn(missing_docs)]
use convert::resolved_diff_to_js;
use js_sys::{Array, Object, Promise, Reflect, Uint8Array};
use loro_internal::{
Expand Down Expand Up @@ -35,6 +37,7 @@ fn run() {
console_error_panic_hook::set_once();
}

/// Enable debug info of Loro
#[wasm_bindgen(js_name = setDebug)]
pub fn set_debug() {
tracing_wasm::set_as_global_default();
Expand Down Expand Up @@ -1541,6 +1544,7 @@ impl LoroText {
}
}

/// get the cursor at the given position.
#[wasm_bindgen(skip_typescript)]
pub fn getCursor(&self, pos: usize, side: JsSide) -> Option<Cursor> {
let mut side_value = Side::Middle;
Expand Down Expand Up @@ -2181,6 +2185,7 @@ impl LoroList {
}
}

/// Get the cursor at the position.
#[wasm_bindgen(skip_typescript)]
pub fn getCursor(&self, pos: usize, side: JsSide) -> Option<Cursor> {
let mut side_value = Side::Middle;
Expand All @@ -2193,12 +2198,14 @@ impl LoroList {
.map(|pos| Cursor { pos })
}

/// Push a value to the end of the list.
pub fn push(&self, value: JsLoroValue) -> JsResult<()> {
let v: JsValue = value.into();
self.handler.push(v.into())?;
Ok(())
}

/// Pop a value from the end of the list.
pub fn pop(&self) -> JsResult<Option<JsLoroValue>> {
let v = self.handler.pop()?;
if let Some(v) = v {
Expand Down Expand Up @@ -2502,6 +2509,7 @@ impl LoroMovableList {
}
}

/// Get the cursor of the container.
#[wasm_bindgen(skip_typescript)]
pub fn getCursor(&self, pos: usize, side: JsSide) -> Option<Cursor> {
let mut side_value = Side::Middle;
Expand Down Expand Up @@ -2546,19 +2554,22 @@ impl LoroMovableList {
Ok(())
}

/// Set the container at the given position.
#[wasm_bindgen(skip_typescript)]
pub fn setContainer(&self, pos: usize, child: JsContainer) -> JsResult<JsContainer> {
let child = js_to_container(child)?;
let c = self.handler.set_container(pos, child.to_handler())?;
Ok(handler_to_js_value(c, self.doc.clone()).into())
}

/// Push a value to the end of the list.
pub fn push(&self, value: JsLoroValue) -> JsResult<()> {
let v: JsValue = value.into();
self.handler.push(v.into())?;
Ok(())
}

/// Pop a value from the end of the list.
pub fn pop(&self) -> JsResult<Option<JsLoroValue>> {
let v = self.handler.pop()?;
Ok(v.map(|v| {
Expand All @@ -2576,6 +2587,7 @@ pub struct LoroTree {
doc: Option<Arc<LoroDoc>>,
}

/// The handler of a tree node.
#[wasm_bindgen]
pub struct LoroTreeNode {
id: TreeID,
Expand Down Expand Up @@ -2993,6 +3005,37 @@ impl Default for LoroTree {
}
}

/// Cursor is a stable position representation in the doc.
/// When expressing the position of a cursor, using "index" can be unstable
/// because the cursor's position may change due to other deletions and insertions,
/// requiring updates with each edit. To stably represent a position or range within
/// a list structure, we can utilize the ID of each item/character on List CRDT or
/// Text CRDT for expression.
///
/// Loro optimizes State metadata by not storing the IDs of deleted elements. This
/// approach complicates tracking cursors since they rely on these IDs. The solution
/// recalculates position by replaying relevant history to update cursors
/// accurately. To minimize the performance impact of history replay, the system
/// updates cursor info to reference only the IDs of currently present elements,
/// thereby reducing the need for replay.
///
/// @example
/// ```ts
///
/// const doc = new Loro();
/// const text = doc.getText("text");
/// text.insert(0, "123");
/// const pos0 = text.getCursor(0, 0);
/// {
/// const ans = doc.getCursorPos(pos0!);
/// expect(ans.offset).toBe(0);
/// }
/// text.insert(0, "1");
/// {
/// const ans = doc.getCursorPos(pos0!);
/// expect(ans.offset).toBe(1);
/// }
/// ```
#[derive(Clone)]
#[wasm_bindgen]
pub struct Cursor {
Expand All @@ -3001,11 +3044,15 @@ pub struct Cursor {

#[wasm_bindgen]
impl Cursor {
/// Get the id of the given container.
pub fn containerId(&self) -> JsContainerID {
let js_value: JsValue = self.pos.container.to_string().into();
JsContainerID::from(js_value)
}

/// Get the ID that represents the position.
///
/// It can be undefined if it's not binded into a specific ID.
pub fn pos(&self) -> Option<JsID> {
match self.pos.id {
Some(id) => {
Expand All @@ -3016,6 +3063,7 @@ impl Cursor {
}
}

/// Get which side of the character/list item the cursor is on.
pub fn side(&self) -> JsSide {
JsValue::from(match self.pos.side {
cursor::Side::Left => -1,
Expand All @@ -3025,10 +3073,12 @@ impl Cursor {
.into()
}

/// Encode the cursor into a Uint8Array.
pub fn encode(&self) -> Vec<u8> {
self.pos.encode()
}

/// Decode the cursor from a Uint8Array.
pub fn decode(data: &[u8]) -> JsResult<Cursor> {
let pos = cursor::Cursor::decode(data).map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(Cursor { pos })
Expand All @@ -3051,12 +3101,18 @@ fn loro_value_to_js_value_or_container(
}
}

/// [VersionVector](https://en.wikipedia.org/wiki/Version_vector)
/// is a map from [PeerID] to [Counter]. Its a right-open interval.
///
/// i.e. a [VersionVector] of `{A: 1, B: 2}` means that A has 1 atomic op and B has 2 atomic ops,
/// thus ID of `{client: A, counter: 1}` is out of the range.
#[wasm_bindgen]
#[derive(Debug, Default)]
pub struct VersionVector(pub(crate) InternalVersionVector);

#[wasm_bindgen]
impl VersionVector {
/// Create a new version vector.
#[wasm_bindgen(constructor)]
pub fn new(value: JsIntoVersionVector) -> JsResult<VersionVector> {
let value: JsValue = value.into();
Expand All @@ -3074,6 +3130,7 @@ impl VersionVector {
VersionVector::from_json(JsVersionVectorMap::from(value))
}

/// Create a new version vector from a Map.
#[wasm_bindgen(js_name = "parseJSON", method)]
pub fn from_json(version: JsVersionVectorMap) -> JsResult<VersionVector> {
let map: JsValue = version.into();
Expand All @@ -3096,6 +3153,7 @@ impl VersionVector {
Ok(Self(vv))
}

/// Convert the version vector to a Map
#[wasm_bindgen(js_name = "toJSON", method)]
pub fn to_json(&self) -> JsVersionVectorMap {
let vv = &self.0;
Expand All @@ -3110,20 +3168,26 @@ impl VersionVector {
JsVersionVectorMap::from(value)
}

/// Encode the version vector into a Uint8Array.
pub fn encode(&self) -> Vec<u8> {
self.0.encode()
}

/// Decode the version vector from a Uint8Array.
pub fn decode(bytes: &[u8]) -> JsResult<VersionVector> {
let vv = InternalVersionVector::decode(bytes)?;
Ok(Self(vv))
}

/// Get the counter of a peer.
pub fn get(&self, peer_id: JsIntoPeerID) -> JsResult<Option<Counter>> {
let id = js_peer_to_peer(peer_id.into())?;
Ok(self.0.get(&id).copied())
}

/// Compare the version vector with another version vector.
///
/// If they are concurrent, return undefined.
pub fn compare(&self, other: &VersionVector) -> Option<i32> {
self.0.partial_cmp(&other.0).map(|o| match o {
std::cmp::Ordering::Less => -1,
Expand All @@ -3132,6 +3196,7 @@ impl VersionVector {
})
}
}

const ID_CONVERT_ERROR: &str = "Invalid peer id. It must be a number, a BigInt, or a decimal string that can be parsed to a unsigned 64-bit integer";
fn js_peer_to_peer(value: JsValue) -> JsResult<u64> {
if value.is_bigint() {
Expand All @@ -3154,7 +3219,7 @@ fn js_peer_to_peer(value: JsValue) -> JsResult<u64> {
}
}

pub enum Container {
enum Container {
Text(LoroText),
Map(LoroMap),
List(LoroList),
Expand Down
2 changes: 2 additions & 0 deletions crates/loro-wasm/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ extern "C" {
pub fn error(s: &str);
}

/// Console log macro.
#[macro_export]
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ($crate::log::log(&format_args!($($t)*).to_string()))
}

/// Console log macro.
#[macro_export]
macro_rules! console_error {
// Note that this is using the `log` function imported above during
Expand Down
Loading

0 comments on commit 996ce70

Please sign in to comment.