Skip to main content

co_js/
map.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{
5	dynamic_value::DynamicValue,
6	js::{from_js_value, to_js_value},
7	JsBlockStorage,
8};
9use cid::Cid;
10use co_primitives::{CoMap, CoMapTransaction};
11use wasm_bindgen::prelude::*;
12use web_sys::js_sys::Uint8Array;
13
14#[wasm_bindgen(js_name = "CoMap")]
15pub struct JsCoMap {
16	root: Option<Cid>,
17}
18#[wasm_bindgen(js_class = "CoMap")]
19impl JsCoMap {
20	#[wasm_bindgen(constructor)]
21	pub fn new(cid: JsValue) -> Result<Self, JsValue> {
22		let root = if cid.is_null_or_undefined() { None } else { Some(from_js_value(cid)?) };
23		Ok(JsCoMap { root })
24	}
25
26	#[allow(clippy::should_implement_trait)]
27	pub fn default() -> Self {
28		JsCoMap { root: None }
29	}
30
31	pub async fn open(&self, storage: &JsBlockStorage) -> Result<JsCoMapTransaction, JsValue> {
32		let transaction = self
33			.map()
34			.open(storage)
35			.await
36			.map_err(|err| format!("Open failed: {:?}", err))?;
37		Ok(JsCoMapTransaction(transaction))
38	}
39
40	pub async fn commit(&mut self, transaction: JsCoMapTransaction) -> Result<(), JsValue> {
41		let mut map = self.map();
42		map.commit(transaction.0)
43			.await
44			.map_err(|err| format!("Commit transaction failed: {:?}", err))?;
45		self.root = Into::<Option<Cid>>::into(&map);
46		Ok(())
47	}
48
49	pub fn is_empty(&self) -> bool {
50		self.map().is_empty()
51	}
52
53	pub async fn get(&self, storage: &JsBlockStorage, key: JsValue) -> Result<Option<JsValue>, JsValue> {
54		self.open(storage).await?.get(key).await
55	}
56
57	pub async fn contains_key(&self, storage: &JsBlockStorage, key: JsValue) -> Result<bool, JsValue> {
58		self.open(storage).await?.contains_key(key).await
59	}
60
61	pub async fn insert(&mut self, storage: &JsBlockStorage, key: JsValue, value: JsValue) -> Result<(), JsValue> {
62		let mut transaction = self.open(storage).await?;
63		transaction.insert(key, value).await?;
64		self.commit(transaction).await
65	}
66
67	pub fn stream(&self, storage: &JsBlockStorage) -> web_sys::ReadableStream {
68		let map = self.map();
69		let storage = storage.clone();
70		let stream = async_stream::try_stream! {
71			let tree = map.open(&storage).await
72				.map_err(|err| format!("open failed: {:?}", err))?;
73			let stream = tree.stream();
74			for await item in stream {
75				let value = item
76					.map_err(|err| format!("read failed: {:?}", err))?;
77					let js_value = to_js_value(&value)?;
78				yield js_value;
79			}
80		};
81		wasm_streams::ReadableStream::from_stream(stream).into_raw()
82	}
83
84	pub fn cid(&self) -> Result<Option<Uint8Array>, JsValue> {
85		self.root.as_ref().map(|cid| to_js_value(cid).map(Uint8Array::from)).transpose()
86	}
87}
88impl JsCoMap {
89	fn map(&self) -> CoMap<DynamicValue, DynamicValue> {
90		CoMap::from(self.root)
91	}
92}
93impl From<Option<Cid>> for JsCoMap {
94	fn from(value: Option<Cid>) -> Self {
95		Self { root: value }
96	}
97}
98
99#[wasm_bindgen(js_name = "CoMapTransaction")]
100pub struct JsCoMapTransaction(CoMapTransaction<JsBlockStorage, DynamicValue, DynamicValue>);
101
102#[wasm_bindgen(js_class = "CoMapTransaction")]
103impl JsCoMapTransaction {
104	pub async fn store(&mut self) -> Result<JsCoMap, JsValue> {
105		let co_map = self.0.store().await.map_err(|err| format!("Store failed: {:?}", err))?;
106		Ok(Into::<Option<Cid>>::into(&co_map).into())
107	}
108	pub async fn get(&self, key: JsValue) -> Result<Option<JsValue>, JsValue> {
109		let key: DynamicValue = from_js_value(key)?;
110		let result = self.0.get(&key).await.map_err(|err| format!("Get failed: {:?}", err))?;
111		result.as_ref().map(to_js_value).transpose()
112	}
113	pub async fn contains_key(&self, key: JsValue) -> Result<bool, JsValue> {
114		let key: DynamicValue = from_js_value(key)?;
115		self.0
116			.contains_key(&key)
117			.await
118			.map_err(|err| format!("Contains key failed: {:?}", err).into())
119	}
120	pub async fn insert(&mut self, key: JsValue, value: JsValue) -> Result<(), JsValue> {
121		let key: DynamicValue = from_js_value(key)?;
122		let value: DynamicValue = from_js_value(value)?;
123		self.0
124			.insert(key, value)
125			.await
126			.map_err(|err| format!("insert failed: {:?}", err))?;
127		Ok(())
128	}
129	pub fn stream(&self) -> web_sys::ReadableStream {
130		let transaction = self.0.clone();
131		let stream = async_stream::try_stream! {
132			let stream = transaction.stream();
133			for await item in stream {
134				let value = item
135					.map_err(|err| format!("read failed: {:?}", err))?;
136					let js_value = to_js_value(&value)?;
137				yield js_value;
138			}
139		};
140		wasm_streams::ReadableStream::from_stream(stream).into_raw()
141	}
142}