Skip to main content

syncweb_core/node/
docs_engine.rs

1use bytes::Bytes;
2use iroh_blobs::Hash;
3use iroh_docs::{
4    AuthorId, DocTicket, Entry, NamespaceId,
5    api::{
6        Doc,
7        protocol::{AddrInfoOptions, ShareMode},
8    },
9    engine::LiveEvent,
10    protocol::Docs,
11    store::Query,
12};
13use n0_future::StreamExt;
14
15use crate::error::{Result, SyncwebError};
16
17#[derive(Clone)]
18pub struct DocsEngine {
19    docs: Docs,
20}
21
22impl DocsEngine {
23    #[must_use]
24    pub fn new(docs: &Docs) -> Self {
25        Self { docs: docs.clone() }
26    }
27
28    #[must_use]
29    pub const fn inner(&self) -> &Docs {
30        &self.docs
31    }
32
33    /// # Errors
34    ///
35    /// Returns an error if the namespace cannot be created.
36    pub async fn create_namespace(&self) -> Result<Doc> {
37        self.docs
38            .create()
39            .await
40            .map_err(|error| SyncwebError::operation("failed to create namespace", error))
41    }
42
43    /// # Errors
44    ///
45    /// Returns an error if the ticket cannot be imported.
46    pub async fn import_ticket(&self, ticket: DocTicket) -> Result<Doc> {
47        self.docs
48            .import(ticket)
49            .await
50            .map_err(|error| SyncwebError::operation("failed to import document ticket", error))
51    }
52
53    /// # Errors
54    ///
55    /// Returns an error if the document cannot be opened.
56    pub async fn open(&self, namespace_id: NamespaceId) -> Result<Option<Doc>> {
57        self.docs
58            .open(namespace_id)
59            .await
60            .map_err(|error| SyncwebError::operation("failed to open namespace", error))
61    }
62
63    /// # Errors
64    ///
65    /// Returns an error if the namespace cannot be dropped.
66    pub async fn drop_namespace(&self, namespace_id: NamespaceId) -> Result<()> {
67        self.docs
68            .drop_doc(namespace_id)
69            .await
70            .map_err(|error| SyncwebError::operation("failed to drop namespace", error))?;
71        Ok(())
72    }
73
74    /// Start live synchronization for a document.
75    ///
76    /// An empty peer list uses peers already learned by iroh-docs for the
77    /// namespace.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if the document cannot be opened for synchronization.
82    pub async fn start_sync(&self, doc: &Doc, peers: Vec<iroh::EndpointAddr>) -> Result<()> {
83        doc.start_sync(peers)
84            .await
85            .map_err(|error| SyncwebError::operation("failed to start document synchronization", error))
86    }
87
88    /// # Errors
89    ///
90    /// Returns an error if the document cannot be shared.
91    pub async fn share(&self, doc: &Doc, mode: ShareMode, _endpoint: iroh::EndpointAddr) -> Result<DocTicket> {
92        doc.share(mode, AddrInfoOptions::RelayAndAddresses)
93            .await
94            .map_err(|error| SyncwebError::operation("failed to share document", error))
95    }
96
97    /// # Errors
98    ///
99    /// Returns an error if the default author cannot be retrieved.
100    pub async fn author(&self) -> Result<AuthorId> {
101        self.docs
102            .author_default()
103            .await
104            .map_err(|error| SyncwebError::operation("failed to retrieve document author", error))
105    }
106
107    /// # Errors
108    ///
109    /// Returns an error if the author cannot be exported.
110    pub async fn export_author(&self, author_id: AuthorId) -> Result<Option<iroh_docs::Author>> {
111        self.docs
112            .author_export(author_id)
113            .await
114            .map_err(|error| SyncwebError::operation("failed to export document author", error))
115    }
116
117    /// # Errors
118    ///
119    /// Returns an error if the author cannot be imported.
120    pub async fn import_author(&self, author: iroh_docs::Author) -> Result<AuthorId> {
121        self.docs
122            .author_import(author.clone())
123            .await
124            .map_err(|error| SyncwebError::operation("failed to import document author", error))?;
125        Ok(author.id())
126    }
127
128    /// # Errors
129    ///
130    /// Returns an error if setting the document entry fails.
131    pub async fn set(
132        &self,
133        doc: &Doc,
134        author: AuthorId,
135        key: impl AsRef<[u8]>,
136        value: impl AsRef<[u8]>,
137    ) -> Result<Hash> {
138        doc.set_bytes(
139            author,
140            Bytes::copy_from_slice(key.as_ref()),
141            Bytes::copy_from_slice(value.as_ref()),
142        )
143        .await
144        .map_err(|error| SyncwebError::operation("failed to set document entry", error))
145    }
146
147    /// # Errors
148    ///
149    /// Returns an error if setting the document blob fails.
150    pub async fn set_blob(
151        &self,
152        doc: &Doc,
153        author: AuthorId,
154        key: impl AsRef<[u8]>,
155        hash: Hash,
156        size: u64,
157    ) -> Result<()> {
158        doc.set_hash(author, Bytes::copy_from_slice(key.as_ref()), hash, size)
159            .await
160            .map_err(|error| SyncwebError::operation("failed to set document blob", error))?;
161        Ok(())
162    }
163
164    /// # Errors
165    ///
166    /// Returns an error if getting the document entry fails.
167    pub async fn get(&self, doc: &Doc, author: AuthorId, key: impl AsRef<[u8]>) -> Result<Option<Entry>> {
168        doc.get_exact(author, key, false)
169            .await
170            .map_err(|error| SyncwebError::operation("failed to get document entry", error))
171    }
172
173    /// Read the latest entry for a key regardless of which author wrote it.
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if the document query fails.
178    pub async fn get_any(&self, doc: &Doc, key: impl AsRef<[u8]>) -> Result<Option<Entry>> {
179        let entries = doc
180            .get_many(Query::single_latest_per_key().key_exact(key))
181            .await
182            .map_err(|error| SyncwebError::operation("failed to query document entries", error))?;
183        tokio::pin!(entries);
184        n0_future::StreamExt::next(&mut entries)
185            .await
186            .transpose()
187            .map_err(|error| SyncwebError::operation("failed to read document query result", error))
188    }
189
190    /// Read the latest non-empty entry for every document key.
191    ///
192    /// # Errors
193    ///
194    /// Returns an error if the document query fails.
195    pub async fn list_latest(&self, doc: &Doc) -> Result<Vec<Entry>> {
196        let entries = doc
197            .get_many(Query::single_latest_per_key().build())
198            .await
199            .map_err(|error| SyncwebError::operation("failed to query document entries", error))?;
200        tokio::pin!(entries);
201        let mut output = Vec::new();
202        while let Some(entry_result) = n0_future::StreamExt::next(&mut entries).await {
203            let entry =
204                entry_result.map_err(|error| SyncwebError::operation("failed to read document entry", error))?;
205            output.push(entry);
206        }
207        Ok(output)
208    }
209
210    /// Delete the current value for a document key.
211    ///
212    /// # Errors
213    ///
214    /// Returns an error if the document entry cannot be deleted.
215    pub async fn delete(&self, doc: &Doc, author: AuthorId, key: impl AsRef<[u8]>) -> Result<()> {
216        doc.del(author, key.as_ref().to_vec())
217            .await
218            .map_err(|error| SyncwebError::operation("failed to delete document entry", error))?;
219        Ok(())
220    }
221
222    /// # Errors
223    ///
224    /// Returns an error if watching the document fails.
225    pub async fn watch(
226        &self,
227        doc: &Doc,
228    ) -> Result<impl n0_future::Stream<Item = Result<LiveEvent>> + Send + Unpin + 'static> {
229        let stream = doc
230            .subscribe()
231            .await
232            .map_err(|error| SyncwebError::operation("failed to subscribe to document", error))?;
233        Ok(stream.map(|event| event.map_err(|error| SyncwebError::operation("document event failed", error))))
234    }
235
236    #[must_use]
237    pub fn namespace_id(&self, doc: &Doc) -> NamespaceId {
238        doc.id()
239    }
240}