1#[forbid(missing_docs)]
19mod builder;
20
21pub use builder::*;
22
23use js_sys::futures::JsFuture;
24use js_sys::{global, Array, Function, Object, Promise, Reflect, Uint8Array};
25use serde::{Deserialize, Serialize};
26use serde_json::Value;
27use wasm_bindgen::JsValue;
28use web_sys::ReadableStream;
29
30#[derive(Clone, Debug)]
32pub struct KvStore {
33 pub(crate) this: Object,
34 pub(crate) get_function: Function,
35 pub(crate) get_with_meta_function: Function,
36 pub(crate) put_function: Function,
37 pub(crate) list_function: Function,
38 pub(crate) delete_function: Function,
39}
40
41unsafe impl Send for KvStore {}
43unsafe impl Sync for KvStore {}
44
45impl KvStore {
46 pub fn create(binding: &str) -> Result<Self, KvError> {
48 let this = get(&global(), binding)?;
49
50 if this.is_undefined() {
52 Err(KvError::InvalidKvStore(binding.into()))
53 } else {
54 Ok(Self {
55 get_function: get(&this, "get")?.into(),
56 get_with_meta_function: get(&this, "getWithMetadata")?.into(),
57 put_function: get(&this, "put")?.into(),
58 list_function: get(&this, "list")?.into(),
59 delete_function: get(&this, "delete")?.into(),
60 this: this.into(),
61 })
62 }
63 }
64
65 pub fn from_this(this: &JsValue, binding: &str) -> Result<Self, KvError> {
68 let this = get(this, binding)?;
69
70 if this.is_undefined() {
72 Err(KvError::InvalidKvStore(binding.into()))
73 } else {
74 Ok(Self {
75 get_function: get(&this, "get")?.into(),
76 get_with_meta_function: get(&this, "getWithMetadata")?.into(),
77 put_function: get(&this, "put")?.into(),
78 list_function: get(&this, "list")?.into(),
79 delete_function: get(&this, "delete")?.into(),
80 this: this.into(),
81 })
82 }
83 }
84
85 pub fn get(&self, name: &str) -> GetOptionsBuilder {
87 GetOptionsBuilder {
88 this: self.this.clone(),
89 get_function: self.get_function.clone(),
90 get_with_meta_function: self.get_with_meta_function.clone(),
91 name: JsValue::from(name),
92 cache_ttl: None,
93 value_type: None,
94 }
95 }
96
97 pub fn get_bulk(&self, keys: &[impl AsRef<str>]) -> GetBulkOptionsBuilder {
99 let array = Array::new();
100 for key in keys {
101 array.push(&JsValue::from(key.as_ref()));
102 }
103 GetBulkOptionsBuilder {
104 this: self.this.clone(),
105 get_function: self.get_function.clone(),
106 get_with_meta_function: self.get_with_meta_function.clone(),
107 keys: array.into(),
108 cache_ttl: None,
109 value_type: None,
110 }
111 }
112
113 pub fn put<T: ToRawKvValue>(&self, name: &str, value: T) -> Result<PutOptionsBuilder, KvError> {
115 Ok(PutOptionsBuilder {
116 this: self.this.clone(),
117 put_function: self.put_function.clone(),
118 name: JsValue::from(name),
119 value: value.raw_kv_value()?,
120 expiration: None,
121 expiration_ttl: None,
122 metadata: None,
123 })
124 }
125
126 pub fn put_bytes(&self, name: &str, value: &[u8]) -> Result<PutOptionsBuilder, KvError> {
128 let typed_array = Uint8Array::new_with_length(value.len() as u32);
129 typed_array.copy_from(value);
130 let value: JsValue = typed_array.buffer().into();
131 Ok(PutOptionsBuilder {
132 this: self.this.clone(),
133 put_function: self.put_function.clone(),
134 name: JsValue::from(name),
135 value,
136 expiration: None,
137 expiration_ttl: None,
138 metadata: None,
139 })
140 }
141
142 pub fn put_stream(
144 &self,
145 name: &str,
146 value: ReadableStream,
147 ) -> Result<PutOptionsBuilder, KvError> {
148 Ok(PutOptionsBuilder {
149 this: self.this.clone(),
150 put_function: self.put_function.clone(),
151 name: JsValue::from(name),
152 value: value.into(),
153 expiration: None,
154 expiration_ttl: None,
155 metadata: None,
156 })
157 }
158
159 pub fn list(&self) -> ListOptionsBuilder {
161 ListOptionsBuilder {
162 this: self.this.clone(),
163 list_function: self.list_function.clone(),
164 limit: None,
165 cursor: None,
166 prefix: None,
167 }
168 }
169
170 pub async fn delete(&self, name: &str) -> Result<(), KvError> {
172 let name = JsValue::from(name);
173 let promise: Promise = self.delete_function.call1(&self.this, &name)?.into();
174 JsFuture::from(promise).await?;
175 Ok(())
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ListResponse {
182 pub keys: Vec<Key>,
184 pub list_complete: bool,
186 pub cursor: Option<String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Key {
193 pub name: String,
195 pub expiration: Option<u64>,
198 pub metadata: Option<Value>,
200}
201
202#[derive(Debug)]
204pub enum KvError {
205 JavaScript(JsValue),
206 Serialization(serde_json::Error),
207 InvalidKvStore(String),
208}
209
210unsafe impl Send for KvError {}
211unsafe impl Sync for KvError {}
212
213impl std::fmt::Display for KvError {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 KvError::JavaScript(value) => write!(f, "js error: {value:?}"),
217 KvError::Serialization(e) => write!(f, "unable to serialize/deserialize: {e}"),
218 KvError::InvalidKvStore(binding) => write!(f, "invalid kv store: {binding}"),
219 }
220 }
221}
222
223impl std::error::Error for KvError {}
224
225impl From<KvError> for JsValue {
226 fn from(val: KvError) -> Self {
227 match val {
228 KvError::JavaScript(value) => value,
229 KvError::Serialization(e) => format!("KvError::Serialization: {e}").into(),
230 KvError::InvalidKvStore(binding) => {
231 format!("KvError::InvalidKvStore: {binding}").into()
232 }
233 }
234 }
235}
236
237impl From<JsValue> for KvError {
238 fn from(value: JsValue) -> Self {
239 Self::JavaScript(value)
240 }
241}
242
243impl From<serde_json::Error> for KvError {
244 fn from(value: serde_json::Error) -> Self {
245 Self::Serialization(value)
246 }
247}
248
249pub trait ToRawKvValue {
251 fn raw_kv_value(&self) -> Result<JsValue, KvError>;
252}
253
254impl ToRawKvValue for str {
255 fn raw_kv_value(&self) -> Result<JsValue, KvError> {
256 Ok(JsValue::from(self))
257 }
258}
259
260impl<T: Serialize> ToRawKvValue for T {
261 fn raw_kv_value(&self) -> Result<JsValue, KvError> {
262 let value = serde_wasm_bindgen::to_value(self).map_err(JsValue::from)?;
263
264 if value.as_string().is_some() {
265 Ok(value)
266 } else if let Some(number) = value.as_f64() {
267 Ok(JsValue::from(number.to_string()))
268 } else if let Some(boolean) = value.as_bool() {
269 Ok(JsValue::from(boolean.to_string()))
270 } else {
271 js_sys::JSON::stringify(&value)
272 .map(JsValue::from)
273 .map_err(Into::into)
274 }
275 }
276}
277
278fn get(target: &JsValue, name: &str) -> Result<JsValue, JsValue> {
279 Reflect::get(target, &JsValue::from(name))
280}