mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-05 20:17:13 +00:00
refactor: replace "local" and "fromCheckout" in event with "triggeredBy" (#312)
* refactor: replace "local" and "fromCheckout" in event with "triggeredBy" * refactor: simplify the name to `by`
This commit is contained in:
parent
fc20f1f7d0
commit
7561448323
20 changed files with 275 additions and 178 deletions
|
@ -6,9 +6,10 @@ const validTargets = Array.from(
|
|||
Deno.readDirSync(resolve(__dirname, "../fuzz/fuzz_targets")),
|
||||
).map((x) => x.name.replace(/.rs$/, ""));
|
||||
|
||||
const targets = Deno.args.length === 0
|
||||
? validTargets
|
||||
: Deno.args.filter((x) => validTargets.includes(x));
|
||||
const targets =
|
||||
Deno.args.length === 0
|
||||
? validTargets
|
||||
: Deno.args.filter((x) => validTargets.includes(x));
|
||||
|
||||
const promises = [];
|
||||
for (const target of targets) {
|
||||
|
|
|
@ -30,6 +30,44 @@ pub struct ContainerDiff {
|
|||
pub diff: Diff,
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum EventTriggerKind {
|
||||
/// The event is triggered by a local transaction.
|
||||
Local,
|
||||
/// The event is triggered by importing
|
||||
Import,
|
||||
/// The event is triggered by checkout
|
||||
Checkout,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EventTriggerKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EventTriggerKind::Local => write!(f, "local"),
|
||||
EventTriggerKind::Import => write!(f, "import"),
|
||||
EventTriggerKind::Checkout => write!(f, "checkout"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventTriggerKind {
|
||||
#[inline]
|
||||
pub fn is_local(&self) -> bool {
|
||||
matches!(self, EventTriggerKind::Local)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_import(&self) -> bool {
|
||||
matches!(self, EventTriggerKind::Import)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_checkout(&self) -> bool {
|
||||
matches!(self, EventTriggerKind::Checkout)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiffEvent<'a> {
|
||||
/// The receiver of the event.
|
||||
|
@ -49,9 +87,7 @@ pub struct DocDiff {
|
|||
pub from: Frontiers,
|
||||
pub to: Frontiers,
|
||||
pub origin: InternalString,
|
||||
pub local: bool,
|
||||
/// Whether the diff is created from the checkout operation.
|
||||
pub from_checkout: bool,
|
||||
pub by: EventTriggerKind,
|
||||
pub diff: Vec<ContainerDiff>,
|
||||
}
|
||||
|
||||
|
@ -89,8 +125,7 @@ pub(crate) enum DiffVariant {
|
|||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InternalDocDiff<'a> {
|
||||
pub(crate) origin: InternalString,
|
||||
pub(crate) local: bool,
|
||||
pub(crate) from_checkout: bool,
|
||||
pub(crate) by: EventTriggerKind,
|
||||
pub(crate) diff: Cow<'a, [InternalContainerDiff]>,
|
||||
pub(crate) new_version: Cow<'a, Frontiers>,
|
||||
}
|
||||
|
@ -99,15 +134,14 @@ impl<'a> InternalDocDiff<'a> {
|
|||
pub fn into_owned(self) -> InternalDocDiff<'static> {
|
||||
InternalDocDiff {
|
||||
origin: self.origin,
|
||||
local: self.local,
|
||||
from_checkout: self.from_checkout,
|
||||
by: self.by,
|
||||
diff: Cow::Owned((*self.diff).to_owned()),
|
||||
new_version: Cow::Owned((*self.new_version).to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_merge(&self, other: &Self) -> bool {
|
||||
self.origin == other.origin && self.local == other.local
|
||||
self.by == other.by
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2477,7 +2477,7 @@ mod test {
|
|||
|
||||
let loro2 = LoroDoc::new();
|
||||
loro2.subscribe_root(Arc::new(|e| {
|
||||
println!("{} {:?} ", e.event_meta.local, e.event_meta.diff)
|
||||
println!("{} {:?} ", e.event_meta.by, e.event_meta.diff)
|
||||
}));
|
||||
loro2.import(&loro.export_from(&loro2.oplog_vv())).unwrap();
|
||||
assert_eq!(loro.get_deep_value(), loro2.get_deep_value());
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
|||
encoding::{
|
||||
decode_snapshot, export_snapshot, parse_header_and_body, EncodeMode, ParsedHeaderAndBody,
|
||||
},
|
||||
event::{str_to_path, Index},
|
||||
event::{str_to_path, EventTriggerKind, Index},
|
||||
handler::{Handler, TextHandler, TreeHandler, ValueOrHandler},
|
||||
id::PeerID,
|
||||
oplog::dag::FrontiersNotIncluded,
|
||||
|
@ -471,9 +471,8 @@ impl LoroDoc {
|
|||
let mut state = self.state.lock().unwrap();
|
||||
state.apply_diff(InternalDocDiff {
|
||||
origin,
|
||||
local: false,
|
||||
diff: (diff).into(),
|
||||
from_checkout: false,
|
||||
by: EventTriggerKind::Import,
|
||||
new_version: Cow::Owned(oplog.frontiers().clone()),
|
||||
});
|
||||
} else {
|
||||
|
@ -507,9 +506,8 @@ impl LoroDoc {
|
|||
let mut state = self.state.lock().unwrap();
|
||||
state.apply_diff(InternalDocDiff {
|
||||
origin: "".into(),
|
||||
local: false,
|
||||
diff: (diff).into(),
|
||||
from_checkout: false,
|
||||
by: EventTriggerKind::Import,
|
||||
new_version: Cow::Owned(oplog.frontiers().clone()),
|
||||
});
|
||||
}
|
||||
|
@ -784,9 +782,8 @@ impl LoroDoc {
|
|||
);
|
||||
state.apply_diff(InternalDocDiff {
|
||||
origin: "checkout".into(),
|
||||
local: true,
|
||||
by: EventTriggerKind::Checkout,
|
||||
diff: Cow::Owned(diff),
|
||||
from_checkout: true,
|
||||
new_version: Cow::Owned(frontiers.clone()),
|
||||
});
|
||||
drop(state);
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
},
|
||||
delta::DeltaItem,
|
||||
encoding::{StateSnapshotDecodeContext, StateSnapshotEncoder},
|
||||
event::{Diff, Index, InternalContainerDiff, InternalDiff},
|
||||
event::{Diff, EventTriggerKind, Index, InternalContainerDiff, InternalDiff},
|
||||
fx_map,
|
||||
handler::ValueOrHandler,
|
||||
id::PeerID,
|
||||
|
@ -310,7 +310,7 @@ impl DocState {
|
|||
}
|
||||
|
||||
/// This should be called when DocState is going to apply a transaction / a diff.
|
||||
fn pre_txn(&mut self, next_origin: InternalString, next_local: bool) {
|
||||
fn pre_txn(&mut self, next_origin: InternalString, next_trigger: EventTriggerKind) {
|
||||
if !self.is_recording() {
|
||||
return;
|
||||
}
|
||||
|
@ -319,7 +319,7 @@ impl DocState {
|
|||
return;
|
||||
};
|
||||
|
||||
if last_diff.origin == next_origin && last_diff.local == next_local {
|
||||
if last_diff.origin == next_origin && last_diff.by == next_trigger {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -359,7 +359,7 @@ impl DocState {
|
|||
}
|
||||
// tracing::info!("Diff = {:#?}", &diff);
|
||||
let is_recording = self.is_recording();
|
||||
self.pre_txn(diff.origin.clone(), diff.local);
|
||||
self.pre_txn(diff.origin.clone(), diff.by);
|
||||
let Cow::Owned(inner) = std::mem::take(&mut diff.diff) else {
|
||||
unreachable!()
|
||||
};
|
||||
|
@ -480,8 +480,8 @@ impl DocState {
|
|||
state.apply_local_op(raw_op, op)
|
||||
}
|
||||
|
||||
pub(crate) fn start_txn(&mut self, origin: InternalString, local: bool) {
|
||||
self.pre_txn(origin, local);
|
||||
pub(crate) fn start_txn(&mut self, origin: InternalString, trigger: EventTriggerKind) {
|
||||
self.pre_txn(origin, trigger);
|
||||
self.in_txn = true;
|
||||
}
|
||||
|
||||
|
@ -626,7 +626,7 @@ impl DocState {
|
|||
frontiers: Frontiers,
|
||||
) {
|
||||
assert!(self.states.is_empty(), "overriding states");
|
||||
self.pre_txn(Default::default(), false);
|
||||
self.pre_txn(Default::default(), EventTriggerKind::Import);
|
||||
self.states = states;
|
||||
for (idx, state) in self.states.iter() {
|
||||
for child_id in state.get_child_containers() {
|
||||
|
@ -652,8 +652,7 @@ impl DocState {
|
|||
.collect();
|
||||
self.record_diff(InternalDocDiff {
|
||||
origin: Default::default(),
|
||||
local: false,
|
||||
from_checkout: false,
|
||||
by: EventTriggerKind::Import,
|
||||
diff,
|
||||
new_version: Cow::Borrowed(&frontiers),
|
||||
});
|
||||
|
@ -896,11 +895,11 @@ impl DocState {
|
|||
panic!("diffs is empty");
|
||||
}
|
||||
|
||||
let triggered_by = diffs[0].by;
|
||||
assert!(diffs.iter().all(|x| x.by == triggered_by));
|
||||
let mut containers = FxHashMap::default();
|
||||
let to = (*diffs.last().unwrap().new_version).to_owned();
|
||||
let origin = diffs[0].origin.clone();
|
||||
let local = diffs[0].local;
|
||||
let from_checkout = diffs[0].from_checkout;
|
||||
for diff in diffs {
|
||||
#[allow(clippy::unnecessary_to_owned)]
|
||||
for container_diff in diff.diff.into_owned() {
|
||||
|
@ -950,8 +949,7 @@ impl DocState {
|
|||
from,
|
||||
to,
|
||||
origin,
|
||||
from_checkout,
|
||||
local,
|
||||
by: triggered_by,
|
||||
diff,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@ impl Transaction {
|
|||
}
|
||||
|
||||
let oplog_lock = oplog.lock().unwrap();
|
||||
state_lock.start_txn(origin, true);
|
||||
state_lock.start_txn(origin, crate::event::EventTriggerKind::Local);
|
||||
let arena = state_lock.arena.clone();
|
||||
let frontiers = state_lock.frontiers.clone();
|
||||
let peer = state_lock.peer;
|
||||
|
@ -300,7 +300,7 @@ impl Transaction {
|
|||
state.commit_txn(
|
||||
Frontiers::from_id(last_id),
|
||||
diff.map(|arr| InternalDocDiff {
|
||||
local: true,
|
||||
by: crate::event::EventTriggerKind::Local,
|
||||
origin: self.origin.clone(),
|
||||
diff: Cow::Owned(
|
||||
arr.into_iter()
|
||||
|
@ -312,7 +312,6 @@ impl Transaction {
|
|||
})
|
||||
.collect(),
|
||||
),
|
||||
from_checkout: false,
|
||||
new_version: Cow::Borrowed(oplog.frontiers()),
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ use fxhash::FxHashMap;
|
|||
use loro_common::{ContainerID, ContainerType, LoroResult, LoroValue, ID};
|
||||
use loro_internal::{
|
||||
delta::ResolvedMapValue,
|
||||
event::Diff,
|
||||
event::{Diff, EventTriggerKind},
|
||||
handler::{Handler, TextDelta, ValueOrHandler},
|
||||
version::Frontiers,
|
||||
ApplyDiff, HandlerTrait, ListHandler, LoroDoc, MapHandler, TextHandler, ToJson,
|
||||
|
@ -94,7 +94,10 @@ fn mark_with_the_same_key_value_should_be_skipped() {
|
|||
fn event_from_checkout() {
|
||||
let a = LoroDoc::new_auto_commit();
|
||||
let sub_id = a.subscribe_root(Arc::new(|event| {
|
||||
assert!(!event.event_meta.from_checkout);
|
||||
assert!(matches!(
|
||||
event.event_meta.by,
|
||||
EventTriggerKind::Checkout | EventTriggerKind::Local
|
||||
));
|
||||
}));
|
||||
a.get_text("text").insert(0, "hello").unwrap();
|
||||
a.commit_then_renew();
|
||||
|
@ -105,7 +108,7 @@ fn event_from_checkout() {
|
|||
let ran = Arc::new(AtomicBool::new(false));
|
||||
let ran_cloned = ran.clone();
|
||||
a.subscribe_root(Arc::new(move |event| {
|
||||
assert!(event.event_meta.from_checkout);
|
||||
assert!(event.event_meta.by.is_checkout());
|
||||
ran.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}));
|
||||
a.checkout(&version).unwrap();
|
||||
|
|
|
@ -37,7 +37,7 @@ async function build() {
|
|||
if (profile !== "dev") {
|
||||
await Promise.all(
|
||||
TARGETS.map(async (target) => {
|
||||
// --snip-rust-panicking-code --snip-rust-fmt-code
|
||||
// --snip-rust-panicking-code --snip-rust-fmt-code
|
||||
const snip = `wasm-snip ./${target}/loro_wasm_bg.wasm -o ./${target}/loro_wasm_bg.wasm`;
|
||||
console.log(">", snip);
|
||||
await Deno.run({ cmd: snip.split(" "), cwd: LoroWasmDir }).status();
|
||||
|
|
|
@ -61,6 +61,8 @@ pub struct Loro(Arc<LoroDoc>);
|
|||
extern "C" {
|
||||
#[wasm_bindgen(typescript_type = "number | bigint | `${number}`")]
|
||||
pub type JsIntoPeerID;
|
||||
#[wasm_bindgen(typescript_type = "`${number}`")]
|
||||
pub type JsStrPeerID;
|
||||
#[wasm_bindgen(typescript_type = "ContainerID")]
|
||||
pub type JsContainerID;
|
||||
#[wasm_bindgen(typescript_type = "ContainerID | string")]
|
||||
|
@ -468,8 +470,9 @@ impl Loro {
|
|||
|
||||
/// Get peer id in decimal string.
|
||||
#[wasm_bindgen(js_name = "peerIdStr", method, getter)]
|
||||
pub fn peer_id_str(&self) -> String {
|
||||
format!("{}", self.0.peer_id())
|
||||
pub fn peer_id_str(&self) -> JsStrPeerID {
|
||||
let v: JsValue = format!("{}", self.0.peer_id()).into();
|
||||
v.into()
|
||||
}
|
||||
|
||||
/// Set the peer ID of the current writer.
|
||||
|
@ -1106,9 +1109,7 @@ fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc<LoroDoc>) {
|
|||
// [1]: https://caniuse.com/?search=FinalizationRegistry
|
||||
// [2]: https://rustwasm.github.io/wasm-bindgen/reference/weak-references.html
|
||||
let event = diff_event_to_js_value(e, doc);
|
||||
if let Err(e) = ob.call1(&event) {
|
||||
console_error!("Error when calling observer: {:#?}", e);
|
||||
}
|
||||
ob.call1(&event).unwrap_throw();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -1138,13 +1139,7 @@ impl Default for Loro {
|
|||
|
||||
fn diff_event_to_js_value(event: DiffEvent, doc: &Arc<LoroDoc>) -> JsValue {
|
||||
let obj = js_sys::Object::new();
|
||||
Reflect::set(&obj, &"local".into(), &event.event_meta.local.into()).unwrap();
|
||||
Reflect::set(
|
||||
&obj,
|
||||
&"fromCheckout".into(),
|
||||
&event.event_meta.from_checkout.into(),
|
||||
)
|
||||
.unwrap();
|
||||
Reflect::set(&obj, &"by".into(), &event.event_meta.by.to_string().into()).unwrap();
|
||||
let origin: &str = &event.event_meta.origin;
|
||||
Reflect::set(&obj, &"origin".into(), &JsValue::from_str(origin)).unwrap();
|
||||
if let Some(t) = event.current_target.as_ref() {
|
||||
|
@ -2765,7 +2760,7 @@ export interface ImportBlobMetadata {
|
|||
partialStartVersionVector: VersionVector;
|
||||
/**
|
||||
* The version vector of the end of the import.
|
||||
*
|
||||
*
|
||||
* Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
|
||||
* However, it does not constitute a complete version vector, as it only contains counters
|
||||
* from peers included within the import blob.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use enum_as_inner::EnumAsInner;
|
||||
use loro_internal::container::ContainerID;
|
||||
use loro_internal::delta::{DeltaItem, TreeDiff};
|
||||
use loro_internal::event::EventTriggerKind;
|
||||
use loro_internal::handler::{TextDelta, ValueOrHandler};
|
||||
use loro_internal::FxHashMap;
|
||||
use loro_internal::{
|
||||
|
@ -15,8 +16,7 @@ pub type Subscriber = Arc<dyn (for<'a> Fn(DiffEvent<'a>)) + Send + Sync>;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct DiffEvent<'a> {
|
||||
pub local: bool,
|
||||
pub from_checkout: bool,
|
||||
pub triggered_by: EventTriggerKind,
|
||||
pub origin: &'a str,
|
||||
pub current_target: Option<ContainerID>,
|
||||
pub events: Vec<ContainerDiff<'a>>,
|
||||
|
@ -52,8 +52,7 @@ pub struct MapDelta<'a> {
|
|||
impl<'a> From<DiffEventInner<'a>> for DiffEvent<'a> {
|
||||
fn from(value: DiffEventInner<'a>) -> Self {
|
||||
DiffEvent {
|
||||
local: value.event_meta.local,
|
||||
from_checkout: value.event_meta.from_checkout,
|
||||
triggered_by: value.event_meta.by,
|
||||
origin: &value.event_meta.origin,
|
||||
current_target: value.current_target,
|
||||
events: value.events.iter().map(|&diff| diff.into()).collect(),
|
||||
|
|
|
@ -315,7 +315,7 @@ impl LoroDoc {
|
|||
/// doc.subscribe(
|
||||
/// &text.id(),
|
||||
/// Arc::new(move |event| {
|
||||
/// assert!(event.local);
|
||||
/// assert!(event.triggered_by.is_local());
|
||||
/// for event in event.events {
|
||||
/// let delta = event.diff.as_text().unwrap();
|
||||
/// let d = TextDelta::Insert {
|
||||
|
|
|
@ -376,7 +376,10 @@ fn subscribe() {
|
|||
doc.subscribe(
|
||||
&text.id(),
|
||||
Arc::new(move |event| {
|
||||
assert!(event.local);
|
||||
assert!(matches!(
|
||||
event.triggered_by,
|
||||
loro_internal::event::EventTriggerKind::Local
|
||||
));
|
||||
for event in event.events {
|
||||
let delta = event.diff.as_text().unwrap();
|
||||
let d = TextDelta::Insert {
|
||||
|
|
|
@ -11,13 +11,14 @@ import isEqual from "is-equal";
|
|||
const Delta = Quill.import("delta");
|
||||
// setDebug("*");
|
||||
|
||||
const EXPAND_CONFIG: { [key in string]: 'before' | 'after' | 'both' | 'none' } = {
|
||||
bold: 'after',
|
||||
italic: 'after',
|
||||
underline: 'after',
|
||||
link: 'none',
|
||||
header: 'none',
|
||||
}
|
||||
const EXPAND_CONFIG: { [key in string]: "before" | "after" | "both" | "none" } =
|
||||
{
|
||||
bold: "after",
|
||||
italic: "after",
|
||||
underline: "after",
|
||||
link: "none",
|
||||
header: "none",
|
||||
};
|
||||
|
||||
export class QuillBinding {
|
||||
private richtext: LoroText;
|
||||
|
@ -31,7 +32,7 @@ export class QuillBinding {
|
|||
underline: { expand: "after" },
|
||||
link: { expand: "none" },
|
||||
header: { expand: "none" },
|
||||
})
|
||||
});
|
||||
this.quill = quill;
|
||||
this.richtext = doc.getText("text");
|
||||
this.richtext.subscribe(doc, (event) => {
|
||||
|
@ -122,9 +123,9 @@ export class QuillBinding {
|
|||
for (const key of Object.keys(op.attributes)) {
|
||||
let value = op.attributes[key];
|
||||
if (value == null) {
|
||||
this.richtext.unmark({ start: index, end, }, key)
|
||||
this.richtext.unmark({ start: index, end }, key);
|
||||
} else {
|
||||
this.richtext.mark({ start: index, end }, key, value,)
|
||||
this.richtext.mark({ start: index, end }, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,20 +138,20 @@ export class QuillBinding {
|
|||
for (const key of Object.keys(op.attributes)) {
|
||||
let value = op.attributes[key];
|
||||
if (value == null) {
|
||||
this.richtext.unmark({ start: index, end, }, key)
|
||||
this.richtext.unmark({ start: index, end }, key);
|
||||
} else {
|
||||
this.richtext.mark({ start: index, end }, key, value)
|
||||
this.richtext.mark({ start: index, end }, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
index = end;
|
||||
} else {
|
||||
throw new Error("Not implemented")
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
} else if (op.delete != null) {
|
||||
this.richtext.delete(index, op.delete);
|
||||
} else {
|
||||
throw new Error("Unreachable")
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
this.doc.commit();
|
||||
|
@ -172,7 +173,7 @@ function assertEqual(a: Delta<string>[], b: Delta<string>[]): boolean {
|
|||
|
||||
/**
|
||||
* Removes the ending '\n's if it has no attributes.
|
||||
*
|
||||
*
|
||||
* Extract line-break to a single op
|
||||
*
|
||||
* Normalize attributes field
|
||||
|
@ -212,7 +213,7 @@ export const normQuillDelta = (delta: Delta<string>[]) => {
|
|||
}
|
||||
}
|
||||
|
||||
const ans: Delta<string>[] = []
|
||||
const ans: Delta<string>[] = [];
|
||||
for (const span of delta) {
|
||||
if (span.insert != null && span.insert.includes("\n")) {
|
||||
const lines = span.insert.split("\n");
|
||||
|
@ -224,7 +225,7 @@ export const normQuillDelta = (delta: Delta<string>[]) => {
|
|||
if (i < lines.length - 1) {
|
||||
const attr = { ...span.attributes };
|
||||
const v: Delta<string> = { insert: "\n" };
|
||||
for (const style of ['bold', 'link', 'italic', 'underline']) {
|
||||
for (const style of ["bold", "link", "italic", "underline"]) {
|
||||
if (attr && attr[style]) {
|
||||
delete attr[style];
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
LoroMap,
|
||||
LoroText,
|
||||
LoroTree,
|
||||
LoroTreeNode,
|
||||
OpId,
|
||||
TreeID,
|
||||
Value,
|
||||
|
@ -56,18 +55,21 @@ export type Path = (number | string | TreeID)[];
|
|||
/**
|
||||
* A batch of events that created by a single `import`/`transaction`/`checkout`.
|
||||
*
|
||||
* @prop local - Indicates whether the event is local.
|
||||
* @prop by - How the event is triggered.
|
||||
* @prop origin - (Optional) Provides information about the origin of the event.
|
||||
* @prop diff - Contains the differential information related to the event.
|
||||
* @prop target - Identifies the container ID of the event's target.
|
||||
* @prop path - Specifies the absolute path of the event's emitter, which can be an index of a list container or a key of a map container.
|
||||
*/
|
||||
export interface LoroEventBatch {
|
||||
local: boolean;
|
||||
/**
|
||||
* If true, this event was triggered by a checkout.
|
||||
* How the event is triggered.
|
||||
*
|
||||
* - `local`: The event is triggered by a local transaction.
|
||||
* - `import`: The event is triggered by an import operation.
|
||||
* - `checkout`: The event is triggered by a checkout operation.
|
||||
*/
|
||||
fromCheckout: boolean;
|
||||
by: "local" | "import" | "checkout";
|
||||
origin?: string;
|
||||
/**
|
||||
* The container ID of the current event receiver.
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
Loro,
|
||||
} from "../src";
|
||||
import { Loro } from "../src";
|
||||
|
||||
describe("Checkout", () => {
|
||||
it("simple checkout", () => {
|
||||
it("simple checkout", async () => {
|
||||
const doc = new Loro();
|
||||
doc.setPeerId(0n);
|
||||
const text = doc.getText("text");
|
||||
text.insert(0, "H");
|
||||
doc.commit();
|
||||
let triggered = false;
|
||||
doc.subscribe((e) => {
|
||||
expect(e.by).not.toBe("import");
|
||||
expect(e.by === "checkout" || e.by === "local").toBeTruthy();
|
||||
triggered = true;
|
||||
});
|
||||
const v = doc.frontiers();
|
||||
text.insert(1, "i");
|
||||
expect(doc.toJson()).toStrictEqual({
|
||||
text: "Hi"
|
||||
text: "Hi",
|
||||
});
|
||||
|
||||
expect(doc.isDetached()).toBeFalsy();
|
||||
doc.checkout([{ peer: "0", counter: 0 }]);
|
||||
expect(doc.isDetached()).toBeTruthy();
|
||||
expect(doc.toJson()).toStrictEqual({
|
||||
text: "H"
|
||||
text: "H",
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 1));
|
||||
expect(triggered).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Chinese char", () => {
|
||||
|
@ -34,19 +40,19 @@ describe("Checkout", () => {
|
|||
v[0].counter -= 1;
|
||||
doc.checkout(v);
|
||||
expect(doc.toJson()).toStrictEqual({
|
||||
text: "你好世"
|
||||
text: "你好世",
|
||||
});
|
||||
v[0].counter -= 1;
|
||||
doc.checkout(v);
|
||||
expect(doc.toJson()).toStrictEqual({
|
||||
text: "你好"
|
||||
text: "你好",
|
||||
});
|
||||
v[0].counter -= 1;
|
||||
doc.checkout(v);
|
||||
expect(doc.toJson()).toStrictEqual({
|
||||
text: "你"
|
||||
text: "你",
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it("two clients", () => {
|
||||
const doc = new Loro();
|
||||
|
|
|
@ -17,6 +17,7 @@ describe("event", () => {
|
|||
const loro = new Loro();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
expect(event.by).toBe("local");
|
||||
lastEvent = event;
|
||||
});
|
||||
const text = loro.getText("text");
|
||||
|
@ -131,7 +132,6 @@ describe("event", () => {
|
|||
const loro = new Loro();
|
||||
let lastEvent: undefined | LoroEventBatch;
|
||||
loro.subscribe((event) => {
|
||||
console.log(event);
|
||||
lastEvent = event;
|
||||
});
|
||||
const tree = loro.getTree("tree");
|
||||
|
@ -328,6 +328,50 @@ describe("event", () => {
|
|||
await new Promise((resolve) => setTimeout(resolve, 1));
|
||||
expect(ran).toBeTruthy();
|
||||
});
|
||||
|
||||
it("remote event", async () => {
|
||||
const doc = new Loro();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, 123);
|
||||
{
|
||||
const doc2 = new Loro();
|
||||
let triggered = false;
|
||||
doc2.subscribe((event) => {
|
||||
expect(event.by).toBe("import");
|
||||
triggered = true;
|
||||
});
|
||||
doc2.import(doc.exportFrom());
|
||||
await oneMs();
|
||||
expect(triggered).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const doc2 = new Loro();
|
||||
let triggered = false;
|
||||
doc2.subscribe((event) => {
|
||||
expect(event.by).toBe("import");
|
||||
triggered = true;
|
||||
});
|
||||
doc2.import(doc.exportSnapshot());
|
||||
await oneMs();
|
||||
expect(triggered).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("checkout event", async () => {
|
||||
const doc = new Loro();
|
||||
const list = doc.getList("list");
|
||||
list.insert(0, 123);
|
||||
doc.commit();
|
||||
let triggered = false;
|
||||
doc.subscribe((e) => {
|
||||
expect(e.by).toBe("checkout");
|
||||
triggered = true;
|
||||
});
|
||||
|
||||
doc.checkout([]);
|
||||
await oneMs();
|
||||
expect(triggered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
function oneMs(): Promise<void> {
|
||||
|
|
|
@ -1,73 +1,69 @@
|
|||
|
||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import {
|
||||
Loro,
|
||||
} from "../src";
|
||||
import { Loro } from "../src";
|
||||
import { Container, LoroText, OpId } from "../dist/loro";
|
||||
import { setDebug } from "loro-wasm";
|
||||
|
||||
it("#211", () => {
|
||||
const loro1 = new Loro()
|
||||
loro1.setPeerId(0n)
|
||||
const text1 = loro1.getText("text")
|
||||
const loro1 = new Loro();
|
||||
loro1.setPeerId(0n);
|
||||
const text1 = loro1.getText("text");
|
||||
|
||||
const loro2 = new Loro()
|
||||
loro2.setPeerId(1n)
|
||||
const text2 = loro2.getText("text")
|
||||
const loro2 = new Loro();
|
||||
loro2.setPeerId(1n);
|
||||
const text2 = loro2.getText("text");
|
||||
|
||||
console.log("[1] Insert T to #0")
|
||||
text1.insert(0, 'T')
|
||||
loro1.commit()
|
||||
show(text1, loro1, text2, loro2)
|
||||
console.log("[1] Insert T to #0");
|
||||
text1.insert(0, "T");
|
||||
loro1.commit();
|
||||
show(text1, loro1, text2, loro2);
|
||||
|
||||
console.log("[2] Synchronize")
|
||||
loro1.import(loro2.exportFrom(loro1.version()))
|
||||
loro2.import(loro1.exportFrom(loro2.version()))
|
||||
show(text1, loro1, text2, loro2)
|
||||
const frontiers1After2 = loro1.frontiers()
|
||||
const frontiers2After2 = loro2.frontiers()
|
||||
console.log("[2] Synchronize");
|
||||
loro1.import(loro2.exportFrom(loro1.version()));
|
||||
loro2.import(loro1.exportFrom(loro2.version()));
|
||||
show(text1, loro1, text2, loro2);
|
||||
const frontiers1After2 = loro1.frontiers();
|
||||
const frontiers2After2 = loro2.frontiers();
|
||||
|
||||
console.log("[3] Append A to #0")
|
||||
text1.insert(1, 'A')
|
||||
loro1.commit()
|
||||
show(text1, loro1, text2, loro2)
|
||||
console.log("[3] Append A to #0");
|
||||
text1.insert(1, "A");
|
||||
loro1.commit();
|
||||
show(text1, loro1, text2, loro2);
|
||||
|
||||
console.log("[4] Append B to #1")
|
||||
text2.insert(1, 'B')
|
||||
loro2.commit()
|
||||
show(text1, loro1, text2, loro2)
|
||||
console.log("[4] Append B to #1");
|
||||
text2.insert(1, "B");
|
||||
loro2.commit();
|
||||
show(text1, loro1, text2, loro2);
|
||||
|
||||
console.log("[5] Play back to the frontiers after 2")
|
||||
loro1.checkout(frontiers1After2)
|
||||
loro2.checkout(frontiers2After2)
|
||||
show(text1, loro1, text2, loro2)
|
||||
console.log("[5] Play back to the frontiers after 2");
|
||||
loro1.checkout(frontiers1After2);
|
||||
loro2.checkout(frontiers2After2);
|
||||
show(text1, loro1, text2, loro2);
|
||||
|
||||
console.log("[6] Check both to the latest")
|
||||
loro1.checkoutToLatest()
|
||||
loro2.checkoutToLatest()
|
||||
show(text1, loro1, text2, loro2)
|
||||
const frontiers1Before7 = loro1.frontiers()
|
||||
const frontiers2Before7 = loro2.frontiers()
|
||||
console.log("[6] Check both to the latest");
|
||||
loro1.checkoutToLatest();
|
||||
loro2.checkoutToLatest();
|
||||
show(text1, loro1, text2, loro2);
|
||||
const frontiers1Before7 = loro1.frontiers();
|
||||
const frontiers2Before7 = loro2.frontiers();
|
||||
|
||||
console.log("[7] Append B to #1")
|
||||
text2.insert(2, 'B')
|
||||
loro2.commit()
|
||||
show(text1, loro1, text2, loro2)
|
||||
console.log("[7] Append B to #1");
|
||||
text2.insert(2, "B");
|
||||
loro2.commit();
|
||||
show(text1, loro1, text2, loro2);
|
||||
|
||||
console.log("[8] Play back to the frontiers before 7")
|
||||
console.log("[8] Play back to the frontiers before 7");
|
||||
console.log("----------------------------------------------------------");
|
||||
loro1.checkout(frontiers1Before7)
|
||||
loro1.checkout(frontiers1Before7);
|
||||
console.log("----------------------------------------------------------");
|
||||
loro2.checkout(frontiers2Before7)
|
||||
show(text1, loro1, text2, loro2)
|
||||
})
|
||||
|
||||
loro2.checkout(frontiers2Before7);
|
||||
show(text1, loro1, text2, loro2);
|
||||
});
|
||||
|
||||
function show(text1: LoroText, loro1: Loro, text2: LoroText, loro2: Loro) {
|
||||
console.log(` #0 has content: ${JSON.stringify(text1.toString())}`)
|
||||
console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`)
|
||||
console.log(` #1 has content: ${JSON.stringify(text2.toString())}`)
|
||||
console.log(` #1 has frontiers: ${showFrontiers(loro2.frontiers())}`)
|
||||
console.log(` #0 has content: ${JSON.stringify(text1.toString())}`);
|
||||
console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`);
|
||||
console.log(` #1 has content: ${JSON.stringify(text2.toString())}`);
|
||||
console.log(` #1 has frontiers: ${showFrontiers(loro2.frontiers())}`);
|
||||
}
|
||||
|
||||
function showFrontiers(frontiers: OpId[]) {
|
||||
|
|
|
@ -129,15 +129,15 @@ describe("sync", () => {
|
|||
const b = new Loro();
|
||||
let a_version: undefined | VersionVector = undefined;
|
||||
let b_version: undefined | VersionVector = undefined;
|
||||
a.subscribe((e: { local: boolean }) => {
|
||||
if (e.local) {
|
||||
a.subscribe((e) => {
|
||||
if (e.by == "local") {
|
||||
const exported = a.exportFrom(a_version);
|
||||
b.import(exported);
|
||||
a_version = a.version();
|
||||
}
|
||||
});
|
||||
b.subscribe((e: { local: boolean }) => {
|
||||
if (e.local) {
|
||||
b.subscribe((e) => {
|
||||
if (e.by == "local") {
|
||||
const exported = b.exportFrom(b_version);
|
||||
a.import(exported);
|
||||
b_version = b.version();
|
||||
|
|
|
@ -33,26 +33,33 @@ describe("richtext", () => {
|
|||
expect(text.toString()).toBe("👨👩👦a");
|
||||
});
|
||||
|
||||
it("emit event correctly", () => {
|
||||
it("emit event correctly", async () => {
|
||||
const doc = new Loro();
|
||||
const text = doc.getText("text");
|
||||
text.subscribe(doc, (event) => {
|
||||
if (event.diff.type == "text") {
|
||||
expect(event.diff.diff).toStrictEqual([
|
||||
{
|
||||
insert: "Hello",
|
||||
attributes: {
|
||||
bold: true,
|
||||
let triggered = false;
|
||||
text.subscribe(doc, (e) => {
|
||||
for (const event of e.events) {
|
||||
if (event.diff.type == "text") {
|
||||
expect(event.diff.diff).toStrictEqual([
|
||||
{
|
||||
insert: "Hello",
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: " World!",
|
||||
},
|
||||
] as Delta<string>[]);
|
||||
{
|
||||
insert: " World!",
|
||||
},
|
||||
] as Delta<string>[]);
|
||||
triggered = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
text.insert(0, "Hello World!");
|
||||
text.mark({ start: 0, end: 5 }, "bold", true);
|
||||
doc.commit();
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
expect(triggered).toBeTruthy();
|
||||
});
|
||||
|
||||
it("emit event from merging doc correctly", async () => {
|
||||
|
|
|
@ -47,24 +47,39 @@ describe("Frontiers", () => {
|
|||
doc1.commit();
|
||||
|
||||
expect(() => {
|
||||
doc1.cmpFrontiers([{ peer: "1", counter: 1 }], [{
|
||||
peer: "2",
|
||||
counter: 10,
|
||||
}]);
|
||||
doc1.cmpFrontiers(
|
||||
[{ peer: "1", counter: 1 }],
|
||||
[
|
||||
{
|
||||
peer: "2",
|
||||
counter: 10,
|
||||
},
|
||||
],
|
||||
);
|
||||
}).toThrow();
|
||||
expect(doc1.cmpFrontiers([], [{ peer: "1", counter: 1 }])).toBe(-1);
|
||||
expect(doc1.cmpFrontiers([], [])).toBe(0);
|
||||
expect(
|
||||
doc1.cmpFrontiers([{ peer: "1", counter: 4 }], [{
|
||||
peer: "2",
|
||||
counter: 3,
|
||||
}]),
|
||||
doc1.cmpFrontiers(
|
||||
[{ peer: "1", counter: 4 }],
|
||||
[
|
||||
{
|
||||
peer: "2",
|
||||
counter: 3,
|
||||
},
|
||||
],
|
||||
),
|
||||
).toBe(-1);
|
||||
expect(
|
||||
doc1.cmpFrontiers([{ peer: "1", counter: 5 }], [{
|
||||
peer: "2",
|
||||
counter: 3,
|
||||
}]),
|
||||
doc1.cmpFrontiers(
|
||||
[{ peer: "1", counter: 5 }],
|
||||
[
|
||||
{
|
||||
peer: "2",
|
||||
counter: 3,
|
||||
},
|
||||
],
|
||||
),
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -77,9 +92,7 @@ it("peer id repr should be consistent", () => {
|
|||
const f = doc.frontiers();
|
||||
expect(f[0].peer).toBe(id);
|
||||
const child = new LoroMap();
|
||||
console.dir(child);
|
||||
const map = doc.getList("list").insertContainer(0, child);
|
||||
console.dir(child);
|
||||
const mapId = map.id;
|
||||
const peerIdInContainerId = mapId.split(":")[1].split("@")[1];
|
||||
expect(peerIdInContainerId).toBe(id);
|
||||
|
@ -115,9 +128,9 @@ describe("Version", () => {
|
|||
const v = a.version();
|
||||
const temp = a.vvToFrontiers(v);
|
||||
expect(temp).toStrictEqual(a.frontiers());
|
||||
expect(a.frontiers()).toStrictEqual(
|
||||
[{ peer: "0", counter: 2 }] as OpId[],
|
||||
);
|
||||
expect(a.frontiers()).toStrictEqual([
|
||||
{ peer: "0", counter: 2 },
|
||||
] as OpId[]);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -156,7 +169,6 @@ it("get import blob metadata", () => {
|
|||
expect(meta.startTimestamp).toBe(0);
|
||||
expect(meta.endTimestamp).toBe(0);
|
||||
expect(meta.isSnapshot).toBeFalsy();
|
||||
console.log(meta.startFrontiers);
|
||||
expect(meta.startFrontiers.length).toBe(0);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue