1#[forbid(missing_docs)]
19mod builder;
20
21pub use builder::*;
22
23use js_sys::{global, Function, Object, Promise, Reflect, Uint8Array};
24use serde::{Deserialize, Serialize};
25use serde_json::Value;
26use wasm_bindgen::JsValue;
27use wasm_bindgen_futures::JsFuture;
28
29#[derive(Clone, Debug)]
31pub struct KvStore {
32 pub(crate) this: Object,
33 pub(crate) get_function: Function,
34 pub(crate) get_with_meta_function: Function,
35 pub(crate) put_function: Function,
36 pub(crate) list_function: Function,
37 pub(crate) delete_function: Function,
38}
39
40unsafe impl Send for KvStore {}
42unsafe impl Sync for KvStore {}
43
44impl KvStore {
45 pub fn create(binding: &str) -> Result<Self, KvError> {
47 let this = get(&global(), binding)?;
48
49 if this.is_undefined() {
51 Err(KvError::InvalidKvStore(binding.into()))
52 } else {
53 Ok(Self {
54 get_function: get(&this, "get")?.into(),
55 get_with_meta_function: get(&this, "getWithMetadata")?.into(),
56 put_function: get(&this, "put")?.into(),
57 list_function: get(&this, "list")?.into(),
58 delete_function: get(&this, "delete")?.into(),
59 this: this.into(),
60 })
61 }
62 }
63
64 pub fn from_this(this: &JsValue, binding: &str) -> Result<Self, KvError> {
67 let this = get(this, binding)?;
68
69 if this.is_undefined() {
71 Err(KvError::InvalidKvStore(binding.into()))
72 } else {
73 Ok(Self {
74 get_function: get(&this, "get")?.into(),
75 get_with_meta_function: get(&this, "getWithMetadata")?.into(),
76 put_function: get(&this, "put")?.into(),
77 list_function: get(&this, "list")?.into(),
78 delete_function: get(&this, "delete")?.into(),
79 this: this.into(),
80 })
81 }
82 }
83
84 pub fn get(&self, name: &str) -> GetOptionsBuilder {
86 GetOptionsBuilder {
87 this: self.this.clone(),
88 get_function: self.get_function.clone(),
89 get_with_meta_function: self.get_with_meta_function.clone(),
90 name: JsValue::from(name),
91 cache_ttl: None,
92 value_type: None,
93 }
94 }
95
96 pub fn put<T: ToRawKvValue>(&self, name: &str, value: T) -> Result<PutOptionsBuilder, KvError> {
98 Ok(PutOptionsBuilder {
99 this: self.this.clone(),
100 put_function: self.put_function.clone(),
101 name: JsValue::from(name),
102 value: value.raw_kv_value()?,
103 expiration: None,
104 expiration_ttl: None,
105 metadata: None,
106 })
107 }
108
109 pub fn put_bytes(&self, name: &str, value: &[u8]) -> Result<PutOptionsBuilder, KvError> {
111 let typed_array = Uint8Array::new_with_length(value.len() as u32);
112 typed_array.copy_from(value);
113 let value: JsValue = typed_array.buffer().into();
114 Ok(PutOptionsBuilder {
115 this: self.this.clone(),
116 put_function: self.put_function.clone(),
117 name: JsValue::from(name),
118 value,
119 expiration: None,
120 expiration_ttl: None,
121 metadata: None,
122 })
123 }
124
125 pub fn list(&self) -> ListOptionsBuilder {
127 ListOptionsBuilder {
128 this: self.this.clone(),
129 list_function: self.list_function.clone(),
130 limit: None,
131 cursor: None,
132 prefix: None,
133 }
134 }
135
136 pub async fn delete(&self, name: &str) -> Result<(), KvError> {
138 let name = JsValue::from(name);
139 let promise: Promise = self.delete_function.call1(&self.this, &name)?.into();
140 JsFuture::from(promise).await?;
141 Ok(())
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ListResponse {
148 pub keys: Vec<Key>,
150 pub list_complete: bool,
152 pub cursor: Option<String>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct Key {
159 pub name: String,
161 pub expiration: Option<u64>,
164 pub metadata: Option<Value>,
166}
167
168#[derive(Debug)]
170pub enum KvError {
171 JavaScript(JsValue),
172 Serialization(serde_json::Error),
173 InvalidKvStore(String),
174}
175
176unsafe impl Send for KvError {}
177unsafe impl Sync for KvError {}
178
179impl std::fmt::Display for KvError {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 KvError::JavaScript(value) => write!(f, "js error: {value:?}"),
183 KvError::Serialization(e) => write!(f, "unable to serialize/deserialize: {e}"),
184 KvError::InvalidKvStore(binding) => write!(f, "invalid kv store: {binding}"),
185 }
186 }
187}
188
189impl std::error::Error for KvError {}
190
191impl From<KvError> for JsValue {
192 fn from(val: KvError) -> Self {
193 match val {
194 KvError::JavaScript(value) => value,
195 KvError::Serialization(e) => format!("KvError::Serialization: {e}").into(),
196 KvError::InvalidKvStore(binding) => {
197 format!("KvError::InvalidKvStore: {binding}").into()
198 }
199 }
200 }
201}
202
203impl From<JsValue> for KvError {
204 fn from(value: JsValue) -> Self {
205 Self::JavaScript(value)
206 }
207}
208
209impl From<serde_json::Error> for KvError {
210 fn from(value: serde_json::Error) -> Self {
211 Self::Serialization(value)
212 }
213}
214
215pub trait ToRawKvValue {
217 fn raw_kv_value(&self) -> Result<JsValue, KvError>;
218}
219
220impl ToRawKvValue for str {
221 fn raw_kv_value(&self) -> Result<JsValue, KvError> {
222 Ok(JsValue::from(self))
223 }
224}
225
226impl<T: Serialize> ToRawKvValue for T {
227 fn raw_kv_value(&self) -> Result<JsValue, KvError> {
228 let value = serde_wasm_bindgen::to_value(self).map_err(JsValue::from)?;
229
230 if value.as_string().is_some() {
231 Ok(value)
232 } else if let Some(number) = value.as_f64() {
233 Ok(JsValue::from(number.to_string()))
234 } else if let Some(boolean) = value.as_bool() {
235 Ok(JsValue::from(boolean.to_string()))
236 } else {
237 js_sys::JSON::stringify(&value)
238 .map(JsValue::from)
239 .map_err(Into::into)
240 }
241 }
242}
243
244fn get(target: &JsValue, name: &str) -> Result<JsValue, JsValue> {
245 Reflect::get(target, &JsValue::from(name))
246}