lmjtfy.git / apps / lmjtfy / src / past.rs
past.rsannotatedpast.rssource90 lines · 3.9 KB · raw

What an object's storage can do that workers-rs has no binding for: go back to an earlier moment, and do several statements as one.

Cloudflare keeps thirty days of an SQLite object's history. A moment in it is named by a bookmark; told to restore to one, the object has that moment's storage the next time it starts. That rewinds everything the object keeps: it is for a migration that went wrong minutes ago, not for taking back one row.

10use js_sys::{Function, Promise, Reflect};
11use wasm_bindgen::closure::Closure;
12use wasm_bindgen::{JsCast, JsValue};
13use wasm_bindgen_futures::JsFuture;
14use worker::State;
16fn message(error: JsValue) -> String {
17    error
18        .dyn_ref::<js_sys::Error>()
19        .map(|error| String::from(error.message()))
20        .or_else(|| error.as_string())
21        .unwrap_or_else(|| format!("{error:?}"))
22}

The object's state as the runtime gave it, for the calls below.

25#[derive(Clone)]
26pub struct Past(JsValue);
28impl Past {

Takes a second hold on state, and gives it back.

30    pub fn of(state: State) -> (Past, State) {
31        let inner = state._inner();
32        let held: &JsValue = inner.as_ref();
33        (Past(held.clone()), State::from(inner))
34    }

Calls storage.<name>(argument).

37    fn storage(&self, name: &str, argument: &JsValue) -> Result<JsValue, String> {
38        let storage = Reflect::get(&self.0, &"storage".into()).map_err(message)?;
39        let method: Function = Reflect::get(&storage, &name.into()).map_err(message)?.dyn_into().map_err(|_| format!("the storage has no {name}"))?;
40        method.call1(&storage, argument).map_err(message)
41    }
43    async fn text(&self, name: &str, argument: &JsValue) -> Result<String, String> {
44        let promise: Promise = self.storage(name, argument)?.dyn_into().map_err(|_| format!("{name} did not return a promise"))?;
45        JsFuture::from(promise).await.map_err(message)?.as_string().ok_or_else(|| format!("{name} did not answer with a bookmark"))
46    }

The bookmark for at_ms, which must be in the last thirty days, or for now. Asking changes nothing.

50    pub async fn bookmark(&self, at_ms: Option<f64>) -> Result<String, String> {
51        match at_ms {
52            Some(at_ms) => self.text("getBookmarkForTime", &at_ms.into()).await,
53            None => self.text("getCurrentBookmark", &JsValue::UNDEFINED).await,
54        }
55    }

Has the object's storage be as it was at bookmark when it next starts, and answers with the bookmark of the moment before that, to undo it by. restart is what makes it happen.

60    pub async fn restore(&self, bookmark: &str) -> Result<String, String> {
61        self.text("onNextSessionRestoreBookmark", &bookmark.into()).await
62    }

Ends the object where it stands; the next request starts it again. Whatever is waiting on it fails.

66    pub fn restart(&self) {
67        if let Ok(abort) = Reflect::get(&self.0, &"abort".into()).and_then(|abort| abort.dyn_into::<Function>()) {
68            let _ = abort.call1(&self.0, &"restoring an earlier moment".into());
69        }
70    }

Runs all as one: if it fails, nothing it wrote is kept.

73    pub fn atomically(&self, all: impl FnOnce() -> worker::Result<()> + 'static) -> Result<(), String> {
74        let failed = std::rc::Rc::new(std::cell::RefCell::new(None));
75        let said = failed.clone();
76        // A callback that throws is what rolls the transaction back.
77        let callback = Closure::once_into_js(move || -> Result<JsValue, JsValue> {
78            all().map(|()| JsValue::UNDEFINED).map_err(|error| {
79                let error = error.to_string();
80                *said.borrow_mut() = Some(error.clone());
81                JsValue::from(js_sys::Error::new(&error))
82            })
83        });
84        let done = self.storage("transactionSync", &callback).map(|_| ());
85        match failed.borrow_mut().take() {
86            Some(error) => Err(error),
87            None => done,
88        }
89    }
90}