Skip to main content

spin_sdk/
key_value.rs

1//! Spin key-value persistent storage.
2//!
3//! This module provides a generic interface for key-value storage, which may be implemented by the host various
4//! ways (e.g. via an in-memory table, a local file, or a remote database). Details such as consistency model and
5//! durability will depend on the implementation and may vary from one to store to the next.
6//!
7//! # Examples
8//!
9//! Open the default store and set the 'message' key:
10//!
11//! ```no_run
12//! # async fn run() -> anyhow::Result<()> {
13//! let store = spin_sdk::key_value::Store::open_default().await?;
14//! store.set("message", "Hello world".as_bytes()).await?;
15//! # Ok(())
16//! # }
17//! ```
18
19use crate::wit_bindgen;
20
21#[doc(hidden)]
22/// Module containing wit bindgen generated code.
23///
24/// This is only meant for internal consumption.
25pub mod wit {
26    #![allow(missing_docs)]
27
28    use crate::wit_bindgen;
29
30    wit_bindgen::generate!({
31        runtime_path: "crate::wit_bindgen::rt",
32        world: "spin-sdk-kv",
33        path: "wit",
34        generate_all,
35    });
36
37    pub use spin::key_value::key_value;
38}
39
40#[cfg(feature = "json")]
41use serde::{de::DeserializeOwned, Serialize};
42
43#[doc(inline)]
44pub use wit::key_value::Error;
45
46/// An open key-value store.
47///
48/// # Examples
49///
50/// Open the default store and set the 'message' key:
51///
52/// ```no_run
53/// # async fn run() -> anyhow::Result<()> {
54/// let store = spin_sdk::key_value::Store::open_default().await?;
55/// store.set("message", "Hello world".as_bytes()).await?;
56/// # Ok(())
57/// # }
58/// ```
59///
60/// Open the default store and get the 'message' key:
61///
62/// ```no_run
63/// # async fn run() -> anyhow::Result<()> {
64/// let store = spin_sdk::key_value::Store::open_default().await?;
65/// let message = store.get("message").await?;
66/// let response = message.unwrap_or_else(|| "not found".into());
67/// # Ok(())
68/// # }
69/// ```
70///
71/// Open a named store and list all the keys defined in it:
72///
73/// ```no_run
74/// # async fn run() -> anyhow::Result<()> {
75/// let store = spin_sdk::key_value::Store::open("finance").await?;
76/// let keys = store.get_keys().await;
77/// println!("{:?}", keys.collect().await?);
78/// # Ok(())
79/// # }
80/// ```
81///
82/// Open the default store and delete the 'message' key:
83///
84/// ```no_run
85/// # async fn run() -> anyhow::Result<()> {
86/// let store = spin_sdk::key_value::Store::open_default().await?;
87/// store.delete("message").await?;
88/// # Ok(())
89/// # }
90/// ```
91pub struct Store(wit::key_value::Store);
92
93impl Store {
94    /// Open the default store.
95    ///
96    /// This is equivalent to `Store::open("default").await`.
97    pub async fn open_default() -> Result<Self, Error> {
98        wit::key_value::Store::open("default".into())
99            .await
100            .map(Store)
101    }
102}
103
104impl Store {
105    /// Open the store with the specified label.
106    ///
107    /// `label` must refer to a store allowed in the spin.toml manifest.
108    ///
109    /// `error::no-such-store` will be raised if the `label` is not recognized.
110    pub async fn open(label: impl AsRef<str>) -> Result<Self, Error> {
111        wit::key_value::Store::open(label.as_ref().to_string())
112            .await
113            .map(Store)
114    }
115
116    /// Get the value associated with the specified `key`
117    ///
118    /// Returns `ok(none)` if the key does not exist.
119    pub async fn get(&self, key: impl AsRef<str>) -> Result<Option<Vec<u8>>, Error> {
120        self.0.get(key.as_ref().to_string()).await
121    }
122
123    /// Set the `value` associated with the specified `key` overwriting any existing value.
124    pub async fn set(&self, key: impl AsRef<str>, value: impl AsRef<[u8]>) -> Result<(), Error> {
125        self.0
126            .set(key.as_ref().to_string(), value.as_ref().to_vec())
127            .await
128    }
129
130    /// Delete the tuple with the specified `key`
131    ///
132    /// No error is raised if a tuple did not previously exist for `key`.
133    pub async fn delete(&self, key: impl AsRef<str>) -> Result<(), Error> {
134        self.0.delete(key.as_ref().to_string()).await
135    }
136
137    /// Return whether a tuple exists for the specified `key`
138    pub async fn exists(&self, key: impl AsRef<str>) -> Result<bool, Error> {
139        self.0.exists(key.as_ref().to_string()).await
140    }
141
142    /// Return a list of all the keys
143    pub async fn get_keys(&self) -> Keys {
144        let (keys, result) = self.0.get_keys().await;
145        Keys { keys, result }
146    }
147
148    #[cfg(feature = "json")]
149    /// Serialize the given data to JSON, then set it as the value for the specified `key`.
150    ///
151    /// # Examples
152    ///
153    /// Open the default store and save a customer information document against the customer ID:
154    ///
155    /// ```no_run
156    /// # use serde::{Deserialize, Serialize};
157    /// #[derive(Deserialize, Serialize)]
158    /// struct Customer {
159    ///     name: String,
160    ///     address: Vec<String>,
161    /// }
162    ///
163    /// # async fn run() -> anyhow::Result<()> {
164    /// let customer_id = "CR1234567";
165    /// let customer = Customer {
166    ///     name: "Alice".to_owned(),
167    ///     address: vec!["Wonderland Way".to_owned()],
168    /// };
169    ///
170    /// let store = spin_sdk::key_value::Store::open_default().await?;
171    /// store.set_json(customer_id, &customer).await?;
172    /// # Ok(())
173    /// # }
174    /// ```
175    pub async fn set_json<T: Serialize>(
176        &self,
177        key: impl AsRef<str>,
178        value: &T,
179    ) -> Result<(), anyhow::Error> {
180        Ok(self
181            .0
182            .set(key.as_ref().to_string(), serde_json::to_vec(value)?)
183            .await?)
184    }
185
186    #[cfg(feature = "json")]
187    /// Deserialize an instance of type `T` from the value of `key`.
188    ///
189    /// # Examples
190    ///
191    /// Open the default store and retrieve a customer information document by customer ID:
192    ///
193    /// ```no_run
194    /// # use serde::{Deserialize, Serialize};
195    /// #[derive(Deserialize, Serialize)]
196    /// struct Customer {
197    ///     name: String,
198    ///     address: Vec<String>,
199    /// }
200    ///
201    /// # async fn run() -> anyhow::Result<()> {
202    /// let customer_id = "CR1234567";
203    ///
204    /// let store = spin_sdk::key_value::Store::open_default().await?;
205    /// let customer = store.get_json::<Customer>(customer_id).await?;
206    /// # Ok(())
207    /// # }
208    /// ```
209    pub async fn get_json<T: DeserializeOwned>(
210        &self,
211        key: impl AsRef<str>,
212    ) -> Result<Option<T>, anyhow::Error> {
213        let Some(value) = self.0.get(key.as_ref().to_string()).await? else {
214            return Ok(None);
215        };
216        Ok(serde_json::from_slice(&value)?)
217    }
218}
219
220/// A streaming list of keys from a key-value store.
221///
222/// Keys are returned as a stream, allowing you to process them incrementally
223/// without loading the entire key set into memory. Use [`Keys::next()`] to
224/// retrieve keys one at a time, or [`Keys::collect()`] to gather all keys
225/// into a `Vec`.
226///
227/// After consuming the stream, you _must_ check [`Keys::result()`] to
228/// determine whether the operation completed successfully.
229pub struct Keys {
230    keys: wit_bindgen::StreamReader<String>,
231    result: wit_bindgen::FutureReader<Result<(), Error>>,
232}
233
234impl Keys {
235    /// Gets the next key from the stream.
236    ///
237    /// Returns `None` when there are no more keys available. You _must_
238    /// await [`Keys::result()`] after the stream is exhausted to determine
239    /// if all keys were read successfully.
240    pub async fn next(&mut self) -> Option<String> {
241        self.keys.next().await
242    }
243
244    /// Whether the key listing completed successfully or with an error.
245    ///
246    /// This must be called after the stream has been fully consumed to check
247    /// for errors that may have occurred during streaming.
248    pub async fn result(self) -> Result<(), Error> {
249        self.result.await
250    }
251
252    /// Collects all keys into a `Vec`.
253    ///
254    /// This is a convenience method for when the key set is small enough to
255    /// fit in memory and you do not require streaming behaviour.
256    pub async fn collect(self) -> Result<Vec<String>, Error> {
257        let keys = self.keys.collect().await;
258        self.result.await?;
259        Ok(keys)
260    }
261
262    /// Extracts the underlying Wasm Component Model stream and future.
263    #[allow(
264        clippy::type_complexity,
265        reason = "sorry clippy that's just what the inner bits are"
266    )]
267    pub fn into_inner(
268        self,
269    ) -> (
270        wit_bindgen::StreamReader<String>,
271        wit_bindgen::FutureReader<Result<(), Error>>,
272    ) {
273        (self.keys, self.result)
274    }
275}