crux_kv/
lib.rs

1#![deny(clippy::pedantic)]
2//! A basic Key-Value store for use with Crux
3//!
4//! `crux_kv` allows Crux apps to store and retrieve arbitrary data by asking the Shell to
5//! persist the data using platform native capabilities (e.g. disk or web localStorage)
6
7pub mod command;
8pub mod error;
9pub mod value;
10
11use serde::{Deserialize, Serialize};
12
13use crux_core::capability::{CapabilityContext, Operation};
14
15use error::KeyValueError;
16use value::Value;
17
18/// Supported operations
19#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub enum KeyValueOperation {
21    /// Read bytes stored under a key
22    Get { key: String },
23    /// Write bytes under a key
24    Set {
25        key: String,
26        #[serde(with = "serde_bytes")]
27        value: Vec<u8>,
28    },
29    /// Remove a key and its value
30    Delete { key: String },
31    /// Test if a key exists
32    Exists { key: String },
33    // List keys that start with a prefix, starting at the cursor
34    ListKeys {
35        /// The prefix to list keys for, or an empty string to list all keys
36        prefix: String,
37        /// The cursor to start listing from, or 0 to start from the beginning.
38        /// If there are more keys to list, the response will include a new cursor.
39        /// If there are no more keys, the response will include a cursor of 0.
40        /// The cursor is opaque to the caller, and should be passed back to the
41        /// `ListKeys` operation to continue listing keys.
42        /// If the cursor is not found for the specified prefix, the response will include
43        /// a `KeyValueError::CursorNotFound` error.
44        cursor: u64,
45    },
46}
47
48impl std::fmt::Debug for KeyValueOperation {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            KeyValueOperation::Get { key } => f.debug_struct("Get").field("key", key).finish(),
52            KeyValueOperation::Set { key, value } => {
53                let body_repr = if let Ok(s) = std::str::from_utf8(value) {
54                    if s.len() < 50 {
55                        format!("\"{s}\"")
56                    } else {
57                        format!("\"{}\"...", s.chars().take(50).collect::<String>())
58                    }
59                } else {
60                    format!("<binary data - {} bytes>", value.len())
61                };
62                f.debug_struct("Set")
63                    .field("key", key)
64                    .field("value", &format_args!("{body_repr}"))
65                    .finish()
66            }
67            KeyValueOperation::Delete { key } => {
68                f.debug_struct("Delete").field("key", key).finish()
69            }
70            KeyValueOperation::Exists { key } => {
71                f.debug_struct("Exists").field("key", key).finish()
72            }
73            KeyValueOperation::ListKeys { prefix, cursor } => f
74                .debug_struct("ListKeys")
75                .field("prefix", prefix)
76                .field("cursor", cursor)
77                .finish(),
78        }
79    }
80}
81
82/// The result of an operation on the store.
83///
84/// Note: we can't use `Result` and `Option` here because generics are not currently
85/// supported across the FFI boundary, when using the builtin typegen.
86#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
87pub enum KeyValueResult {
88    Ok { response: KeyValueResponse },
89    Err { error: KeyValueError },
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub enum KeyValueResponse {
94    /// Response to a `KeyValueOperation::Get`,
95    /// returning the value stored under the key, which may be empty
96    Get { value: Value },
97    /// Response to a `KeyValueOperation::Set`,
98    /// returning the value that was previously stored under the key, may be empty
99    Set { previous: Value },
100    /// Response to a `KeyValueOperation::Delete`,
101    /// returning the value that was previously stored under the key, may be empty
102    Delete { previous: Value },
103    /// Response to a `KeyValueOperation::Exists`,
104    /// returning whether the key is present in the store
105    Exists { is_present: bool },
106    /// Response to a `KeyValueOperation::ListKeys`,
107    /// returning a list of keys that start with the prefix, and a cursor to continue listing
108    /// if there are more keys
109    ///
110    /// Note: the cursor is 0 if there are no more keys
111    ListKeys {
112        keys: Vec<String>,
113        /// The cursor to continue listing keys, or 0 if there are no more keys.
114        /// If the cursor is not found for the specified prefix, the response should instead
115        /// include a `KeyValueError::CursorNotFound` error.
116        next_cursor: u64,
117    },
118}
119
120impl Operation for KeyValueOperation {
121    type Output = KeyValueResult;
122
123    #[cfg(feature = "typegen")]
124    fn register_types(generator: &mut crux_core::typegen::TypeGen) -> crux_core::typegen::Result {
125        generator.register_type::<KeyValueResponse>()?;
126        generator.register_type::<KeyValueError>()?;
127        generator.register_type::<Value>()?;
128        generator.register_type::<Self>()?;
129        generator.register_type::<Self::Output>()?;
130        Ok(())
131    }
132}
133
134pub struct KeyValue<Ev> {
135    context: CapabilityContext<KeyValueOperation, Ev>,
136}
137
138impl<Ev> crux_core::Capability<Ev> for KeyValue<Ev> {
139    type Operation = KeyValueOperation;
140
141    type MappedSelf<MappedEv> = KeyValue<MappedEv>;
142
143    fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
144    where
145        F: Fn(NewEv) -> Ev + Send + Sync + 'static,
146        Ev: 'static,
147        NewEv: 'static + Send,
148    {
149        KeyValue::new(self.context.map_event(f))
150    }
151}
152
153impl<Ev> Clone for KeyValue<Ev> {
154    fn clone(&self) -> Self {
155        Self {
156            context: self.context.clone(),
157        }
158    }
159}
160
161impl<Ev> KeyValue<Ev>
162where
163    Ev: 'static,
164{
165    #[must_use]
166    pub fn new(context: CapabilityContext<KeyValueOperation, Ev>) -> Self {
167        Self { context }
168    }
169
170    /// Read a value under `key`, will dispatch the event with a
171    /// `KeyValueResult::Get { value: Vec<u8> }` as payload
172    pub fn get<F>(&self, key: String, make_event: F)
173    where
174        F: FnOnce(Result<Option<Vec<u8>>, KeyValueError>) -> Ev + Send + Sync + 'static,
175    {
176        self.context.spawn({
177            let context = self.context.clone();
178            async move {
179                let response = get(&context, key).await;
180                context.update_app(make_event(response));
181            }
182        });
183    }
184
185    /// Read a value under `key`, while in an async context. This is used together with
186    /// [`crux_core::compose::Compose`].
187    ///
188    /// Returns the value stored under the key, or `None` if the key is not present.
189    /// # Errors
190    /// Returns a `KeyValueError` if there is a problem getting the value.
191    pub async fn get_async(&self, key: String) -> Result<Option<Vec<u8>>, KeyValueError> {
192        get(&self.context, key).await
193    }
194
195    /// Set `key` to be the provided `value`. Typically the bytes would be
196    /// a value serialized/deserialized by the app.
197    ///
198    /// Will dispatch the event with a `KeyValueResult::Set { previous: Vec<u8> }` as payload
199    pub fn set<F>(&self, key: String, value: Vec<u8>, make_event: F)
200    where
201        F: FnOnce(Result<Option<Vec<u8>>, KeyValueError>) -> Ev + Send + Sync + 'static,
202    {
203        self.context.spawn({
204            let context = self.context.clone();
205            async move {
206                let response = set(&context, key, value).await;
207                context.update_app(make_event(response));
208            }
209        });
210    }
211
212    /// Set `key` to be the provided `value`, while in an async context. This is used together with
213    /// [`crux_core::compose::Compose`].
214    ///
215    /// Returns the previous value stored under the key, if any.
216    /// # Errors
217    /// Returns a `KeyValueError` if there is a problem setting the value.
218    pub async fn set_async(
219        &self,
220        key: String,
221        value: Vec<u8>,
222    ) -> Result<Option<Vec<u8>>, KeyValueError> {
223        set(&self.context, key, value).await
224    }
225
226    /// Remove a `key` and its value, will dispatch the event with a
227    /// `KeyValueResult::Delete { previous: Vec<u8> }` as payload
228    pub fn delete<F>(&self, key: String, make_event: F)
229    where
230        F: FnOnce(Result<Option<Vec<u8>>, KeyValueError>) -> Ev + Send + Sync + 'static,
231    {
232        self.context.spawn({
233            let context = self.context.clone();
234            async move {
235                let response = delete(&context, key).await;
236                context.update_app(make_event(response));
237            }
238        });
239    }
240
241    /// Remove a `key` and its value, while in an async context. This is used together with
242    /// [`crux_core::compose::Compose`].
243    ///
244    /// Returns the previous value stored under the key, if any.
245    /// # Errors
246    /// Returns a `KeyValueError` if there is a problem deleting the value.
247    pub async fn delete_async(&self, key: String) -> Result<Option<Vec<u8>>, KeyValueError> {
248        delete(&self.context, key).await
249    }
250
251    /// Check to see if a `key` exists, will dispatch the event with a
252    /// `KeyValueResult::Exists { is_present: bool }` as payload
253    pub fn exists<F>(&self, key: String, make_event: F)
254    where
255        F: FnOnce(Result<bool, KeyValueError>) -> Ev + Send + Sync + 'static,
256    {
257        self.context.spawn({
258            let context = self.context.clone();
259            async move {
260                let response = exists(&context, key).await;
261                context.update_app(make_event(response));
262            }
263        });
264    }
265
266    /// Check to see if a `key` exists, while in an async context. This is used together with
267    /// [`crux_core::compose::Compose`].
268    ///
269    /// Returns `true` if the key exists, `false` otherwise.
270    ///
271    /// # Errors
272    /// Returns a `KeyValueError` if there is a problem checking the existence of the key.
273    pub async fn exists_async(&self, key: String) -> Result<bool, KeyValueError> {
274        exists(&self.context, key).await
275    }
276
277    /// List keys that start with the provided `prefix`, starting from the provided `cursor`.
278    /// Will dispatch the event with a `KeyValueResult::ListKeys { keys: Vec<String>, cursor: u64 }`
279    /// as payload.
280    ///
281    /// A cursor is an opaque value that points to the first key in the next page of keys.
282    ///
283    /// If the cursor is not found for the specified prefix, the response will include
284    /// a `KeyValueError::CursorNotFound` error.
285    ///
286    /// If the cursor is found the result will be a tuple of the keys and the next cursor
287    /// (if there are more keys to list, the cursor will be non-zero, otherwise it will be zero)
288    pub fn list_keys<F>(&self, prefix: String, cursor: u64, make_event: F)
289    where
290        F: FnOnce(Result<(Vec<String>, u64), KeyValueError>) -> Ev + Send + Sync + 'static,
291    {
292        self.context.spawn({
293            let context = self.context.clone();
294            async move {
295                let response = list_keys(&context, prefix, cursor).await;
296                context.update_app(make_event(response));
297            }
298        });
299    }
300
301    /// List keys that start with the provided `prefix`, starting from the provided `cursor`,
302    /// while in an async context. This is used together with [`crux_core::compose::Compose`].
303    ///
304    /// A cursor is an opaque value that points to the first key in the next page of keys.
305    ///
306    /// If the cursor is not found for the specified prefix, the response will include
307    /// a `KeyValueError::CursorNotFound` error.
308    ///
309    /// If the cursor is found the result will be a tuple of the keys and the next cursor
310    /// (if there are more keys to list, the cursor will be non-zero, otherwise it will be zero)
311    ///
312    /// # Errors
313    /// Returns a `KeyValueError` if there is a problem listing the keys.
314    pub async fn list_keys_async(
315        &self,
316        prefix: String,
317        cursor: u64,
318    ) -> Result<(Vec<String>, u64), KeyValueError> {
319        list_keys(&self.context, prefix, cursor).await
320    }
321}
322
323async fn get<Ev: 'static>(
324    context: &CapabilityContext<KeyValueOperation, Ev>,
325    key: String,
326) -> Result<Option<Vec<u8>>, KeyValueError> {
327    context
328        .request_from_shell(KeyValueOperation::Get { key })
329        .await
330        .unwrap_get()
331}
332
333async fn set<Ev: 'static>(
334    context: &CapabilityContext<KeyValueOperation, Ev>,
335    key: String,
336    value: Vec<u8>,
337) -> Result<Option<Vec<u8>>, KeyValueError> {
338    context
339        .request_from_shell(KeyValueOperation::Set { key, value })
340        .await
341        .unwrap_set()
342}
343
344async fn delete<Ev: 'static>(
345    context: &CapabilityContext<KeyValueOperation, Ev>,
346    key: String,
347) -> Result<Option<Vec<u8>>, KeyValueError> {
348    context
349        .request_from_shell(KeyValueOperation::Delete { key })
350        .await
351        .unwrap_delete()
352}
353
354async fn exists<Ev: 'static>(
355    context: &CapabilityContext<KeyValueOperation, Ev>,
356    key: String,
357) -> Result<bool, KeyValueError> {
358    context
359        .request_from_shell(KeyValueOperation::Exists { key })
360        .await
361        .unwrap_exists()
362}
363
364async fn list_keys<Ev: 'static>(
365    context: &CapabilityContext<KeyValueOperation, Ev>,
366    prefix: String,
367    cursor: u64,
368) -> Result<(Vec<String>, u64), KeyValueError> {
369    context
370        .request_from_shell(KeyValueOperation::ListKeys { prefix, cursor })
371        .await
372        .unwrap_list_keys()
373}
374
375impl KeyValueResult {
376    fn unwrap_get(self) -> Result<Option<Vec<u8>>, KeyValueError> {
377        match self {
378            KeyValueResult::Ok { response } => match response {
379                KeyValueResponse::Get { value } => Ok(value.into()),
380                _ => {
381                    panic!("attempt to convert KeyValueResponse other than Get to Option<Vec<u8>>")
382                }
383            },
384            KeyValueResult::Err { error } => Err(error.clone()),
385        }
386    }
387
388    fn unwrap_set(self) -> Result<Option<Vec<u8>>, KeyValueError> {
389        match self {
390            KeyValueResult::Ok { response } => match response {
391                KeyValueResponse::Set { previous } => Ok(previous.into()),
392                _ => {
393                    panic!("attempt to convert KeyValueResponse other than Set to Option<Vec<u8>>")
394                }
395            },
396            KeyValueResult::Err { error } => Err(error.clone()),
397        }
398    }
399
400    fn unwrap_delete(self) -> Result<Option<Vec<u8>>, KeyValueError> {
401        match self {
402            KeyValueResult::Ok { response } => match response {
403                KeyValueResponse::Delete { previous } => Ok(previous.into()),
404                _ => panic!(
405                    "attempt to convert KeyValueResponse other than Delete to Option<Vec<u8>>"
406                ),
407            },
408            KeyValueResult::Err { error } => Err(error.clone()),
409        }
410    }
411
412    fn unwrap_exists(self) -> Result<bool, KeyValueError> {
413        match self {
414            KeyValueResult::Ok { response } => match response {
415                KeyValueResponse::Exists { is_present } => Ok(is_present),
416                _ => panic!("attempt to convert KeyValueResponse other than Exists to bool"),
417            },
418            KeyValueResult::Err { error } => Err(error.clone()),
419        }
420    }
421
422    fn unwrap_list_keys(self) -> Result<(Vec<String>, u64), KeyValueError> {
423        match self {
424            KeyValueResult::Ok { response } => match response {
425                KeyValueResponse::ListKeys {
426                    keys,
427                    next_cursor: cursor,
428                } => Ok((keys, cursor)),
429                _ => panic!(
430                    "attempt to convert KeyValueResponse other than ListKeys to (Vec<String>, u64)"
431                ),
432            },
433            KeyValueResult::Err { error } => Err(error.clone()),
434        }
435    }
436}
437
438#[cfg(test)]
439mod tests;