1use crate::collection::SharedCollection;
2use crate::js::{Callback, Js};
3use crate::transaction::YTransaction;
4use crate::weak::YWeakLink;
5use crate::{js, ImplicitTransaction};
6use gloo_utils::format::JsValueSerdeExt;
7use std::collections::HashMap;
8use wasm_bindgen::prelude::wasm_bindgen;
9use wasm_bindgen::JsValue;
10use yrs::types::map::MapEvent;
11use yrs::types::{ToJson, TYPE_REFS_MAP};
12use yrs::{DeepObservable, Map, MapRef, Observable, TransactionMut};
13
14#[wasm_bindgen]
23pub struct YMap(pub(crate) SharedCollection<HashMap<String, JsValue>, MapRef>);
24
25#[wasm_bindgen]
26impl YMap {
27 #[wasm_bindgen(constructor)]
34 pub fn new(init: Option<js_sys::Object>) -> Self {
35 let map = if let Some(object) = init {
36 let mut map = HashMap::new();
37 let entries = js_sys::Object::entries(&object);
38 for tuple in entries.iter() {
39 let tuple = js_sys::Array::from(&tuple);
40 let key = tuple.get(0).as_string().unwrap();
41 let value = tuple.get(1);
42 map.insert(key, value);
43 }
44 map
45 } else {
46 HashMap::new()
47 };
48 YMap(SharedCollection::prelim(map))
49 }
50
51 #[wasm_bindgen(getter, js_name = type)]
52 #[inline]
53 pub fn get_type(&self) -> u8 {
54 TYPE_REFS_MAP
55 }
56
57 #[wasm_bindgen(getter, js_name = id)]
60 #[inline]
61 pub fn id(&self) -> crate::Result<JsValue> {
62 self.0.id()
63 }
64
65 #[wasm_bindgen(getter)]
71 pub fn prelim(&self) -> bool {
72 self.0.is_prelim()
73 }
74
75 #[wasm_bindgen(js_name = alive)]
79 pub fn alive(&self, txn: &YTransaction) -> bool {
80 self.0.is_alive(txn)
81 }
82
83 #[wasm_bindgen(js_name = length)]
85 pub fn length(&self, txn: &ImplicitTransaction) -> crate::Result<u32> {
86 match &self.0 {
87 SharedCollection::Prelim(c) => Ok(c.len() as u32),
88 SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| Ok(c.len(txn))),
89 }
90 }
91
92 #[wasm_bindgen(js_name = toJson)]
94 pub fn to_json(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
95 match &self.0 {
96 SharedCollection::Prelim(c) => {
97 let map = js_sys::Object::new();
98 for (k, v) in c.iter() {
99 js_sys::Reflect::set(&map, &k.into(), v).unwrap();
100 }
101 Ok(map.into())
102 }
103 SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
104 let any = c.to_json(txn);
105 JsValue::from_serde(&any).map_err(|e| JsValue::from_str(&e.to_string()))
106 }),
107 }
108 }
109
110 #[wasm_bindgen(js_name = set)]
113 pub fn set(
114 &mut self,
115 key: &str,
116 value: JsValue,
117 txn: ImplicitTransaction,
118 ) -> crate::Result<()> {
119 match &mut self.0 {
120 SharedCollection::Prelim(c) => {
121 c.insert(key.to_string(), value);
122 Ok(())
123 }
124 SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
125 c.insert(txn, key.to_string(), Js::new(value));
126 Ok(())
127 }),
128 }
129 }
130
131 #[wasm_bindgen(method, js_name = delete)]
133 pub fn delete(&mut self, key: &str, txn: ImplicitTransaction) -> crate::Result<()> {
134 match &mut self.0 {
135 SharedCollection::Prelim(c) => {
136 c.remove(key);
137 Ok(())
138 }
139 SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
140 c.remove(txn, key);
141 Ok(())
142 }),
143 }
144 }
145
146 #[wasm_bindgen(js_name = get)]
149 pub fn get(&self, key: &str, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
150 match &self.0 {
151 SharedCollection::Prelim(c) => {
152 let value = c.get(key);
153 Ok(value.cloned().unwrap_or(JsValue::UNDEFINED))
154 }
155 SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
156 let value = c.get(txn, key);
157 match value {
158 None => Ok(JsValue::UNDEFINED),
159 Some(value) => Ok(Js::from_value(&value, txn.doc()).into()),
160 }
161 }),
162 }
163 }
164
165 #[wasm_bindgen(js_name = link)]
166 pub fn link(&self, key: &str, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
167 match &self.0 {
168 SharedCollection::Prelim(_) => Err(JsValue::from_str(js::errors::INVALID_PRELIM_OP)),
169 SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
170 let link = c.link(txn, key);
171 match link {
172 Some(link) => Ok(YWeakLink::from_prelim(link, txn.doc().clone()).into()),
173 None => Err(JsValue::from_str(js::errors::KEY_NOT_FOUND)),
174 }
175 }),
176 }
177 }
178
179 #[wasm_bindgen(js_name = entries)]
203 pub fn entries(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
204 match &self.0 {
205 SharedCollection::Prelim(c) => {
206 let map = js_sys::Object::new();
207 for (k, v) in c.iter() {
208 js_sys::Reflect::set(&map, &k.into(), v)?;
209 }
210 Ok(map.into())
211 }
212 SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
213 let map = js_sys::Object::new();
214 let doc = txn.doc();
215 for (k, v) in c.iter(txn) {
216 let value = Js::from_value(&v, doc);
217 js_sys::Reflect::set(&map, &k.into(), &value.into())?;
218 }
219 Ok(map.into())
220 }),
221 }
222 }
223
224 #[wasm_bindgen(js_name = observe)]
227 pub fn observe(&mut self, callback: js_sys::Function) -> crate::Result<()> {
228 match &self.0 {
229 SharedCollection::Prelim(_) => {
230 Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
231 }
232 SharedCollection::Integrated(c) => {
233 let txn = c.transact()?;
234 let array = c.resolve(&txn)?;
235 let abi = callback.subscription_key();
236 array.observe_with(abi, move |txn, e| {
237 let e = YMapEvent::new(e, txn);
238 let txn = YTransaction::from_ref(txn);
239 callback
240 .call2(&JsValue::UNDEFINED, &e.into(), &txn.into())
241 .unwrap();
242 });
243 Ok(())
244 }
245 }
246 }
247
248 #[wasm_bindgen(js_name = unobserve)]
250 pub fn unobserve(&mut self, callback: js_sys::Function) -> crate::Result<bool> {
251 match &self.0 {
252 SharedCollection::Prelim(_) => {
253 Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
254 }
255 SharedCollection::Integrated(c) => {
256 let txn = c.transact()?;
257 let shared_ref = c.resolve(&txn)?;
258 let abi = callback.subscription_key();
259 Ok(shared_ref.unobserve(abi))
260 }
261 }
262 }
263
264 #[wasm_bindgen(js_name = observeDeep)]
268 pub fn observe_deep(&mut self, callback: js_sys::Function) -> crate::Result<()> {
269 match &self.0 {
270 SharedCollection::Prelim(_) => {
271 Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
272 }
273 SharedCollection::Integrated(c) => {
274 let txn = c.transact()?;
275 let shared_ref = c.resolve(&txn)?;
276 let abi = callback.subscription_key();
277 shared_ref.observe_deep_with(abi, move |txn, e| {
278 let e = crate::js::convert::events_into_js(txn, e);
279 let txn = YTransaction::from_ref(txn);
280 callback
281 .call2(&JsValue::UNDEFINED, &e, &txn.into())
282 .unwrap();
283 });
284 Ok(())
285 }
286 }
287 }
288
289 #[wasm_bindgen(js_name = unobserveDeep)]
291 pub fn unobserve_deep(&mut self, callback: js_sys::Function) -> crate::Result<bool> {
292 match &self.0 {
293 SharedCollection::Prelim(_) => {
294 Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
295 }
296 SharedCollection::Integrated(c) => {
297 let txn = c.transact()?;
298 let shared_ref = c.resolve(&txn)?;
299 let abi = callback.subscription_key();
300 Ok(shared_ref.unobserve_deep(abi))
301 }
302 }
303 }
304}
305
306#[wasm_bindgen]
308pub struct YMapEvent {
309 inner: &'static MapEvent,
310 txn: &'static TransactionMut<'static>,
311 target: Option<JsValue>,
312 keys: Option<JsValue>,
313}
314
315#[wasm_bindgen]
316impl YMapEvent {
317 pub(crate) fn new<'doc>(event: &MapEvent, txn: &TransactionMut<'doc>) -> Self {
318 let inner: &'static MapEvent = unsafe { std::mem::transmute(event) };
319 let txn: &'static TransactionMut<'static> = unsafe { std::mem::transmute(txn) };
320 YMapEvent {
321 inner,
322 txn,
323 target: None,
324 keys: None,
325 }
326 }
327
328 #[wasm_bindgen(getter)]
329 pub fn origin(&mut self) -> JsValue {
330 let origin = self.txn.origin();
331 if let Some(origin) = origin {
332 Js::from(origin).into()
333 } else {
334 JsValue::UNDEFINED
335 }
336 }
337
338 #[wasm_bindgen]
341 pub fn path(&self) -> JsValue {
342 crate::js::convert::path_into_js(self.inner.path())
343 }
344
345 #[wasm_bindgen(getter)]
347 pub fn target(&mut self) -> JsValue {
348 let target = self.inner.target();
349 let doc = self.txn.doc();
350 let js = self.target.get_or_insert_with(|| {
351 YMap(SharedCollection::integrated(target.clone(), doc.clone())).into()
352 });
353 js.clone()
354 }
355
356 #[wasm_bindgen(getter)]
361 pub fn keys(&mut self) -> crate::Result<JsValue> {
362 if let Some(keys) = &self.keys {
363 Ok(keys.clone())
364 } else {
365 let txn = self.txn;
366 let keys = self.inner.keys(txn);
367 let result = js_sys::Object::new();
368 for (key, value) in keys.iter() {
369 let key = JsValue::from(key.as_ref());
370 let value = crate::js::convert::entry_change_into_js(value, txn.doc())?;
371 js_sys::Reflect::set(&result, &key, &value).unwrap();
372 }
373 let keys: JsValue = result.into();
374 self.keys = Some(keys.clone());
375 Ok(keys)
376 }
377 }
378}