use napi::{Result, bindgen_prelude::*, Task, Env, JsFunction};
use briefcase_core::storage::{SqliteBackend, LakeFSBackend, StorageBackend};
use crate::models::{JsDecisionSnapshot, JsSnapshot, JsSnapshotQuery};
#[napi]
pub struct JsSqliteBackend {
inner: SqliteBackend,
}
#[napi]
impl JsSqliteBackend {
#[napi(constructor)]
pub fn new(path: Option<String>) -> Result<Self> {
let backend = if let Some(path) = path {
SqliteBackend::new(path)
} else {
SqliteBackend::in_memory()
};
match backend {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(napi::Error::from_reason(format!("Failed to create SQLite backend: {}", e))),
}
}
#[napi(factory)]
pub fn in_memory() -> Result<Self> {
match SqliteBackend::in_memory() {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(napi::Error::from_reason(format!("Failed to create in-memory SQLite backend: {}", e))),
}
}
#[napi]
pub fn save(&self, env: Env, snapshot: &JsSnapshot) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let snapshot_inner = snapshot.inner.clone();
let task = SaveSnapshotTask {
backend,
snapshot: snapshot_inner,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn save_decision(&self, env: Env, decision: &JsDecisionSnapshot) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let decision_inner = decision.inner.clone();
let task = SaveDecisionTask {
backend,
decision: decision_inner,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn load(&self, env: Env, snapshot_id: String) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let task = LoadSnapshotTask {
backend,
snapshot_id,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn load_decision(&self, env: Env, decision_id: String) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let task = LoadDecisionTask {
backend,
decision_id,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn query(&self, env: Env, query: &JsSnapshotQuery) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let query_inner = query.inner.clone();
let task = QuerySnapshotsTask {
backend,
query: query_inner,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn delete(&self, env: Env, snapshot_id: String) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let task = DeleteSnapshotTask {
backend,
snapshot_id,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn health_check(&self, env: Env) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let task = HealthCheckTask {
backend,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
}
#[napi]
pub struct JsLakeFSBackend {
inner: LakeFSBackend,
}
#[napi]
impl JsLakeFSBackend {
#[napi(constructor)]
pub fn new(endpoint: String, repository: String, branch: String, access_key: String, secret_key: String) -> Result<Self> {
match LakeFSBackend::new(endpoint, repository, branch, access_key, secret_key) {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(napi::Error::from_reason(format!("Failed to create LakeFS backend: {}", e))),
}
}
#[napi]
pub fn save(&self, env: Env, snapshot: &JsSnapshot) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let snapshot_inner = snapshot.inner.clone();
let task = SaveSnapshotTask {
backend,
snapshot: snapshot_inner,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn save_decision(&self, env: Env, decision: &JsDecisionSnapshot) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let decision_inner = decision.inner.clone();
let task = SaveDecisionTask {
backend,
decision: decision_inner,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
#[napi]
pub fn flush(&self, env: Env) -> Result<napi::JsObject> {
let backend = self.inner.clone();
let task = FlushTask {
backend,
};
env.spawn(task).map(|async_work| async_work.promise_object())
}
}
struct SaveSnapshotTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
snapshot: briefcase_core::Snapshot,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for SaveSnapshotTask<T> {
type Output = String;
type JsValue = String;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.save(&self.snapshot)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
struct SaveDecisionTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
decision: briefcase_core::DecisionSnapshot,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for SaveDecisionTask<T> {
type Output = String;
type JsValue = String;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.save_decision(&self.decision)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
struct LoadSnapshotTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
snapshot_id: String,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for LoadSnapshotTask<T> {
type Output = briefcase_core::Snapshot;
type JsValue = JsSnapshot;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.load(&self.snapshot_id)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(JsSnapshot { inner: output })
}
}
struct LoadDecisionTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
decision_id: String,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for LoadDecisionTask<T> {
type Output = briefcase_core::DecisionSnapshot;
type JsValue = JsDecisionSnapshot;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.load_decision(&self.decision_id)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(JsDecisionSnapshot { inner: output })
}
}
struct QuerySnapshotsTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
query: briefcase_core::storage::SnapshotQuery,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for QuerySnapshotsTask<T> {
type Output = Vec<briefcase_core::Snapshot>;
type JsValue = Vec<JsSnapshot>;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.query(self.query.clone())
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output.into_iter().map(|snapshot| JsSnapshot { inner: snapshot }).collect())
}
}
struct DeleteSnapshotTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
snapshot_id: String,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for DeleteSnapshotTask<T> {
type Output = bool;
type JsValue = bool;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.delete(&self.snapshot_id)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
struct HealthCheckTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for HealthCheckTask<T> {
type Output = bool;
type JsValue = bool;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.health_check()
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
struct FlushTask<T: StorageBackend + Send + Sync + 'static> {
backend: T,
}
#[napi]
impl<T: StorageBackend + Send + Sync + 'static> Task for FlushTask<T> {
type Output = briefcase_core::storage::FlushResult;
type JsValue = serde_json::Value;
fn compute(&mut self) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
rt.block_on(async {
self.backend.flush()
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
})
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(serde_json::json!({
"snapshots_written": output.snapshots_written,
"bytes_written": output.bytes_written,
"checkpoint_id": output.checkpoint_id
}))
}
}