///|
fn run_async(f : async () -> Unit noraise) -> Unit = "%async.run"
///|
/// Async Callback-based Storage implementation for JS environments.
///
/// Wraps JS Promise-returning callbacks into the target-independent
/// AsyncStorage trait (continuation-passing style).
///
/// JS FFI registers Promise-returning functions. This adapter uses
/// Promise.then() to bridge to the continuation callbacks, avoiding
/// the need for async fn in trait impl bodies.
///
/// Supports multiple named instances for dual-backend patterns:
/// - "wal-{instance_id}" → WAL storage (e.g., Durable Object storage)
/// - "snapshot-{instance_id}" → Snapshot storage (e.g., R2)
/// Name derivation is centralized in persistent_exports.mbt
/// (wal_callback_name / snapshot_callback_name).
///
/// JS target only (uses @js_async.Promise for JS interop).
///|
/// JS-specific async storage callbacks — each returns a Promise.
pub struct JsAsyncStorageCallbacks {
read : (String, StorageKind) -> @js_async.Promise[Bytes]
write : (String, Bytes, StorageKind) -> @js_async.Promise[Unit]
atomic_write : (String, Bytes, StorageKind) -> @js_async.Promise[Unit]
del : (String, StorageKind) -> @js_async.Promise[Unit]
exists : (String, StorageKind) -> @js_async.Promise[Bool]
list : (StorageKind) -> @js_async.Promise[Array[String]]
}
///|
pub fn JsAsyncStorageCallbacks::new(
read : (String, StorageKind) -> @js_async.Promise[Bytes],
write : (String, Bytes, StorageKind) -> @js_async.Promise[Unit],
atomic_write : (String, Bytes, StorageKind) -> @js_async.Promise[Unit],
del : (String, StorageKind) -> @js_async.Promise[Unit],
exists : (String, StorageKind) -> @js_async.Promise[Bool],
list : (StorageKind) -> @js_async.Promise[Array[String]],
) -> JsAsyncStorageCallbacks {
{ read, write, atomic_write, del, exists, list }
}
///|
/// Named callback registry for multiple JS async storage backends.
let js_async_callbacks : @hashmap.HashMap[String, JsAsyncStorageCallbacks] = @hashmap.new()
///|
pub fn register_js_async_callbacks(
name : String,
cb : JsAsyncStorageCallbacks,
) -> Unit {
js_async_callbacks[name] = cb
}
///|
pub fn clear_js_async_callbacks(name : String) -> Unit {
js_async_callbacks.remove(name)
}
///|
pub fn has_js_async_callbacks(name : String) -> Bool {
js_async_callbacks.contains(name)
}
///|
/// JS async callback storage, bound to a named backend.
/// Implements the target-independent AsyncStorage trait by bridging
/// JS Promises through the continuation-passing interface.
pub struct JsAsyncCallbackStorage {
name : String
}
///|
pub fn JsAsyncCallbackStorage::new(name : String) -> JsAsyncCallbackStorage {
{ name, }
}
///|
fn JsAsyncCallbackStorage::get_callbacks(
self : JsAsyncCallbackStorage,
) -> Result[JsAsyncStorageCallbacks, String] {
match js_async_callbacks.get(self.name) {
Some(cb) => Ok(cb)
None => Err("JS async storage callbacks not registered: " + self.name)
}
}
// ── Helper: bridge JS Promise to continuation-passing ──
// We use run_async + Promise::wait() inside a spawned coroutine,
// then call the continuation from within that coroutine.
///|
fn[T] await_promise(
promise : @js_async.Promise[T],
resolve : (T) -> Unit,
reject : (String) -> Unit,
) -> Unit {
fn local_run() {
run_async(async fn() noraise {
try {
let result = promise.wait()
resolve(result)
} catch {
err => reject(err.to_string())
}
})
}
local_run()
}
///|
fn await_promise_unit(
promise : @js_async.Promise[Unit],
resolve : () -> Unit,
reject : (String) -> Unit,
) -> Unit {
fn local_run() {
run_async(async fn() noraise {
try {
promise.wait()
resolve()
} catch {
err => reject(err.to_string())
}
})
}
local_run()
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_read(
self,
path,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise((cb.read)(path, kind), resolve, reject)
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_write(
self,
path,
data,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise_unit((cb.write)(path, data, kind), resolve, reject)
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_atomic_write(
self,
path,
data,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise_unit((cb.atomic_write)(path, data, kind), resolve, reject)
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_del(
self,
path,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise_unit((cb.del)(path, kind), resolve, reject)
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_exists(
self,
path,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise((cb.exists)(path, kind), resolve, reject)
}
///|
pub impl AsyncStorage for JsAsyncCallbackStorage with fn async_list(
self,
kind,
resolve,
reject,
) {
let cb = match self.get_callbacks() {
Ok(cb) => cb
Err(msg) => return reject(msg)
}
await_promise((cb.list)(kind), resolve, reject)
}