#[forbid(missing_docs)]
mod builder;
pub use builder::*;
use js_sys::{global, Function, Object, Promise, Reflect, Uint8Array};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
#[derive(Clone)]
pub struct KvStore {
pub(crate) this: Object,
pub(crate) get_function: Function,
pub(crate) get_with_meta_function: Function,
pub(crate) put_function: Function,
pub(crate) list_function: Function,
pub(crate) delete_function: Function,
}
unsafe impl Send for KvStore {}
unsafe impl Sync for KvStore {}
impl KvStore {
pub fn create(binding: &str) -> Result<Self, KvError> {
let this = get(&global(), binding)?;
if this.is_undefined() {
Err(KvError::InvalidKvStore(binding.into()))
} else {
Ok(Self {
get_function: get(&this, "get")?.into(),
get_with_meta_function: get(&this, "getWithMetadata")?.into(),
put_function: get(&this, "put")?.into(),
list_function: get(&this, "list")?.into(),
delete_function: get(&this, "delete")?.into(),
this: this.into(),
})
}
}
pub fn from_this(this: &JsValue, binding: &str) -> Result<Self, KvError> {
let this = get(this, binding)?;
if this.is_undefined() {
Err(KvError::InvalidKvStore(binding.into()))
} else {
Ok(Self {
get_function: get(&this, "get")?.into(),
get_with_meta_function: get(&this, "getWithMetadata")?.into(),
put_function: get(&this, "put")?.into(),
list_function: get(&this, "list")?.into(),
delete_function: get(&this, "delete")?.into(),
this: this.into(),
})
}
}
pub fn get(&self, name: &str) -> GetOptionsBuilder {
GetOptionsBuilder {
this: self.this.clone(),
get_function: self.get_function.clone(),
get_with_meta_function: self.get_with_meta_function.clone(),
name: JsValue::from(name),
cache_ttl: None,
value_type: None,
}
}
pub fn put<T: ToRawKvValue>(&self, name: &str, value: T) -> Result<PutOptionsBuilder, KvError> {
Ok(PutOptionsBuilder {
this: self.this.clone(),
put_function: self.put_function.clone(),
name: JsValue::from(name),
value: value.raw_kv_value()?,
expiration: None,
expiration_ttl: None,
metadata: None,
})
}
pub fn put_bytes(&self, name: &str, value: &[u8]) -> Result<PutOptionsBuilder, KvError> {
let typed_array = Uint8Array::new_with_length(value.len() as u32);
typed_array.copy_from(value);
let value: JsValue = typed_array.buffer().into();
Ok(PutOptionsBuilder {
this: self.this.clone(),
put_function: self.put_function.clone(),
name: JsValue::from(name),
value,
expiration: None,
expiration_ttl: None,
metadata: None,
})
}
pub fn list(&self) -> ListOptionsBuilder {
ListOptionsBuilder {
this: self.this.clone(),
list_function: self.list_function.clone(),
limit: None,
cursor: None,
prefix: None,
}
}
pub async fn delete(&self, name: &str) -> Result<(), KvError> {
let name = JsValue::from(name);
let promise: Promise = self.delete_function.call1(&self.this, &name)?.into();
JsFuture::from(promise).await?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResponse {
pub keys: Vec<Key>,
pub list_complete: bool,
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Key {
pub name: String,
pub expiration: Option<u64>,
pub metadata: Option<Value>,
}
#[derive(Debug, thiserror::Error)]
pub enum KvError {
#[error("js error: {0:?}")]
JavaScript(JsValue),
#[error("unable to serialize/deserialize: {0}")]
Serialization(serde_json::Error),
#[error("invalid kv store: {0}")]
InvalidKvStore(String),
}
impl From<KvError> for JsValue {
fn from(val: KvError) -> Self {
match val {
KvError::JavaScript(value) => value,
KvError::Serialization(e) => format!("KvError::Serialization: {e}").into(),
KvError::InvalidKvStore(binding) => {
format!("KvError::InvalidKvStore: {binding}").into()
}
}
}
}
impl From<JsValue> for KvError {
fn from(value: JsValue) -> Self {
Self::JavaScript(value)
}
}
impl From<serde_json::Error> for KvError {
fn from(value: serde_json::Error) -> Self {
Self::Serialization(value)
}
}
pub trait ToRawKvValue {
fn raw_kv_value(&self) -> Result<JsValue, KvError>;
}
impl ToRawKvValue for str {
fn raw_kv_value(&self) -> Result<JsValue, KvError> {
Ok(JsValue::from(self))
}
}
impl<T: Serialize> ToRawKvValue for T {
fn raw_kv_value(&self) -> Result<JsValue, KvError> {
let value = serde_wasm_bindgen::to_value(self).map_err(JsValue::from)?;
if value.as_string().is_some() {
Ok(value)
} else if let Some(number) = value.as_f64() {
Ok(JsValue::from(number.to_string()))
} else if let Some(boolean) = value.as_bool() {
Ok(JsValue::from(boolean.to_string()))
} else {
js_sys::JSON::stringify(&value)
.map(JsValue::from)
.map_err(Into::into)
}
}
}
fn get(target: &JsValue, name: &str) -> Result<JsValue, JsValue> {
Reflect::get(target, &JsValue::from(name))
}