use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use ::worker::worker_sys::DurableObjectState;
use ::worker::{SqlStorage, State};
use drizzle_core::error::DrizzleError;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = js_sys::Object)]
#[derive(Clone)]
type SyncTransactionStorage;
#[wasm_bindgen(method, catch, js_name = transactionSync)]
fn transaction_sync(
this: &SyncTransactionStorage,
callback: &mut dyn FnMut() -> Result<JsValue, JsValue>,
) -> Result<JsValue, JsValue>;
}
#[derive(Clone)]
pub struct DurableStorage {
sql: SqlStorage,
storage: SyncTransactionStorage,
}
impl std::fmt::Debug for DurableStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DurableStorage").finish_non_exhaustive()
}
}
impl DurableStorage {
pub fn new(state: &mut State) -> Self {
let sql = state.storage().sql();
let placeholder = State::from(JsValue::UNDEFINED.unchecked_into::<DurableObjectState>());
let raw = std::mem::replace(state, placeholder)._inner();
let storage = raw.storage();
*state = State::from(raw);
let storage = storage
.expect("Durable Object state has no storage")
.unchecked_into::<SyncTransactionStorage>();
Self { sql, storage }
}
#[inline]
pub fn sql(&self) -> &SqlStorage {
&self.sql
}
pub(crate) fn transaction<R>(
&self,
body: impl FnOnce() -> drizzle_core::error::Result<R>,
) -> drizzle_core::error::Result<R> {
let mut body = Some(body);
let mut outcome = None;
let mut panic = None;
let mut callback = || {
let Some(body) = body.take() else {
return Err(JsValue::from_str("transaction callback called twice"));
};
match catch_unwind(AssertUnwindSafe(body)) {
Ok(result) => {
let signal = match &result {
Ok(_) => Ok(JsValue::UNDEFINED),
Err(error) => Err(js_sys::Error::new(&error.to_string()).into()),
};
outcome = Some(result);
signal
}
Err(payload) => {
panic = Some(payload);
Err(js_sys::Error::new("transaction callback panicked").into())
}
}
};
let completed = self.storage.transaction_sync(&mut callback);
if let Some(payload) = panic {
resume_unwind(payload);
}
match (outcome, completed) {
(Some(Ok(value)), Ok(_)) => Ok(value),
(Some(Err(error)), _) => Err(error),
(Some(Ok(_)), Err(error)) => Err(DrizzleError::TransactionError(
format!("commit failed: {}", js_message(&error)).into(),
)),
(None, Err(error)) => Err(DrizzleError::TransactionError(js_message(&error).into())),
(None, Ok(_)) => Err(DrizzleError::TransactionError(
"transactionSync returned without running the callback".into(),
)),
}
}
}
fn js_message(value: &JsValue) -> String {
match value.dyn_ref::<js_sys::Error>() {
Some(error) => error.message().into(),
None => value.as_string().unwrap_or_else(|| format!("{value:?}")),
}
}