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}