mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-08 21:47:41 +00:00
feat: add subscribe to containers in wasm
This commit is contained in:
parent
39ece045a8
commit
6df69bd2be
3 changed files with 237 additions and 23 deletions
|
@ -225,27 +225,7 @@ impl Loro {
|
||||||
pub fn subscribe(&self, f: js_sys::Function) -> u32 {
|
pub fn subscribe(&self, f: js_sys::Function) -> u32 {
|
||||||
let observer = observer::Observer::new(f);
|
let observer = observer::Observer::new(f);
|
||||||
self.0.borrow_mut().subscribe_deep(Box::new(move |e| {
|
self.0.borrow_mut().subscribe_deep(Box::new(move |e| {
|
||||||
let promise = Promise::resolve(&JsValue::NULL);
|
call_after_micro_task(observer.clone(), e);
|
||||||
let ob = observer.clone();
|
|
||||||
type C = Closure<dyn FnMut(JsValue)>;
|
|
||||||
let drop_handler: Rc<RefCell<Option<C>>> = Rc::new(RefCell::new(None));
|
|
||||||
let copy = drop_handler.clone();
|
|
||||||
let closure = Closure::once(move |_: JsValue| {
|
|
||||||
ob.call1(
|
|
||||||
&Event {
|
|
||||||
local: e.local,
|
|
||||||
origin: e.origin.clone(),
|
|
||||||
target: e.target.clone(),
|
|
||||||
diff: Either::A(e.diff.to_vec()),
|
|
||||||
path: Either::A(e.absolute_path.clone()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
drop(copy);
|
|
||||||
});
|
|
||||||
let _ = promise.then(&closure);
|
|
||||||
drop_handler.borrow_mut().replace(closure);
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +244,29 @@ impl Loro {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_after_micro_task(ob: observer::Observer, e: Arc<loro_internal::event::Event>) {
|
||||||
|
let promise = Promise::resolve(&JsValue::NULL);
|
||||||
|
type C = Closure<dyn FnMut(JsValue)>;
|
||||||
|
let drop_handler: Rc<RefCell<Option<C>>> = Rc::new(RefCell::new(None));
|
||||||
|
let copy = drop_handler.clone();
|
||||||
|
let closure = Closure::once(move |_: JsValue| {
|
||||||
|
ob.call1(
|
||||||
|
&Event {
|
||||||
|
local: e.local,
|
||||||
|
origin: e.origin.clone(),
|
||||||
|
target: e.target.clone(),
|
||||||
|
diff: Either::A(e.diff.to_vec()),
|
||||||
|
path: Either::A(e.absolute_path.clone()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(copy);
|
||||||
|
});
|
||||||
|
let _ = promise.then(&closure);
|
||||||
|
drop_handler.borrow_mut().replace(closure);
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Loro {
|
impl Default for Loro {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
@ -399,6 +402,37 @@ impl LoroText {
|
||||||
pub fn length(&self) -> usize {
|
pub fn length(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||||
|
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe_once(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
self.0.unsubscribe(&txn, subscription)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -477,6 +511,50 @@ impl LoroMap {
|
||||||
};
|
};
|
||||||
Ok(container)
|
Ok(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||||
|
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe_once(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "subscribeDeep")]
|
||||||
|
pub fn subscribe_deep(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe_deep(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
self.0.unsubscribe(&txn, subscription)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -554,6 +632,50 @@ impl LoroList {
|
||||||
};
|
};
|
||||||
Ok(container)
|
Ok(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||||
|
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe_once(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "subscribeDeep")]
|
||||||
|
pub fn subscribe_deep(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||||
|
let observer = observer::Observer::new(f);
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
let ans = self.0.subscribe_deep(
|
||||||
|
&txn,
|
||||||
|
Box::new(move |e| {
|
||||||
|
call_after_micro_task(observer.clone(), e);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
Ok(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||||
|
let txn = get_transaction_mut(txn);
|
||||||
|
self.0.unsubscribe(&txn, subscription)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(typescript_custom_section)]
|
#[wasm_bindgen(typescript_custom_section)]
|
||||||
|
|
|
@ -136,6 +136,9 @@ declare module "loro-wasm" {
|
||||||
): never;
|
): never;
|
||||||
|
|
||||||
get(index: number): Value;
|
get(index: number): Value;
|
||||||
|
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoroMap {
|
interface LoroMap {
|
||||||
|
@ -161,5 +164,14 @@ declare module "loro-wasm" {
|
||||||
): never;
|
): never;
|
||||||
|
|
||||||
get(key: string): Value;
|
get(key: string): Value;
|
||||||
|
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoroText {
|
||||||
|
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||||
|
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
Diff,
|
Delta,
|
||||||
ListDiff,
|
ListDiff,
|
||||||
Loro,
|
Loro,
|
||||||
LoroEvent,
|
LoroEvent,
|
||||||
LoroMap,
|
|
||||||
MapDIff as MapDiff,
|
MapDIff as MapDiff,
|
||||||
TextDiff,
|
TextDiff,
|
||||||
} from "../src";
|
} from "../src";
|
||||||
|
@ -131,6 +130,87 @@ describe("event", () => {
|
||||||
} as MapDiff],
|
} as MapDiff],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("subscribe container events", () => {
|
||||||
|
it("text", async () => {
|
||||||
|
const loro = new Loro();
|
||||||
|
const text = loro.getText("text");
|
||||||
|
let ran = 0;
|
||||||
|
let oneTimeRan = 0;
|
||||||
|
text.subscribeOnce(loro, (_) => {
|
||||||
|
oneTimeRan += 1;
|
||||||
|
});
|
||||||
|
const sub = text.subscribe(loro, (event) => {
|
||||||
|
if (!ran) {
|
||||||
|
expect(event.diff[0].diff).toStrictEqual(
|
||||||
|
[{ type: "insert", "value": "123" }] as Delta<string>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ran += 1;
|
||||||
|
expect(event.target).toBe(text.id);
|
||||||
|
});
|
||||||
|
text.insert(loro, 0, "123");
|
||||||
|
text.insert(loro, 1, "456");
|
||||||
|
await zeroMs();
|
||||||
|
expect(ran).toBeTruthy();
|
||||||
|
// subscribeOnce test
|
||||||
|
expect(oneTimeRan).toBe(1);
|
||||||
|
expect(text.toString()).toEqual("145623");
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
const oldRan = ran;
|
||||||
|
text.unsubscribe(loro, sub);
|
||||||
|
text.insert(loro, 0, "789");
|
||||||
|
expect(ran).toBe(oldRan);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("map subscribe deep", async () => {
|
||||||
|
const loro = new Loro();
|
||||||
|
const map = loro.getMap("map");
|
||||||
|
let times = 0;
|
||||||
|
const sub = map.subscribeDeep(loro, (event) => {
|
||||||
|
times += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const subMap = map.insertContainer(loro, "sub", "Map");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(1);
|
||||||
|
const text = subMap.insertContainer(loro, "k", "Text");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(2);
|
||||||
|
text.insert(loro, 0, "123");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(3);
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
map.unsubscribe(loro, sub);
|
||||||
|
text.insert(loro, 0, "123");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("list subscribe deep", async () => {
|
||||||
|
const loro = new Loro();
|
||||||
|
const list = loro.getList("list");
|
||||||
|
let times = 0;
|
||||||
|
const sub = list.subscribeDeep(loro, (_) => {
|
||||||
|
times += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = list.insertContainer(loro, 0, "Text");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(1);
|
||||||
|
text.insert(loro, 0, "123");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(2);
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
list.unsubscribe(loro, sub);
|
||||||
|
text.insert(loro, 0, "123");
|
||||||
|
await zeroMs();
|
||||||
|
expect(times).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function zeroMs(): Promise<void> {
|
function zeroMs(): Promise<void> {
|
||||||
|
|
Loading…
Reference in a new issue