mirror of
https://github.com/trailbaseio/trailbase
synced 2026-04-21 13:37:44 +00:00
Add Wasmtime's component-model-async back.
This commit is contained in:
parent
369785a7f1
commit
2c8cfdf9bd
9 changed files with 78 additions and 96 deletions
|
|
@ -108,7 +108,7 @@ trailbase-wasm-common = { path = "crates/wasm-runtime-common", version = "0.2.0"
|
|||
trailbase-wasm-runtime-host = { path = "crates/wasm-runtime-host", version = "0.1.0" }
|
||||
ts-rs = { version = "11", features = ["uuid-impl", "serde-json-impl", "indexmap-impl"] }
|
||||
uuid = { version = "1", default-features = false, features = ["std", "v4", "v7", "serde"] }
|
||||
wasmtime = { version = "41.0.0", features = ["winch"] }
|
||||
wasmtime = { version = "41.0.0", features = ["winch", "component-model-async"] }
|
||||
wasmtime-wasi = { version = "41.0.0", default-features = false, features = [] }
|
||||
wasmtime-wasi-http = { version = "41.0.0", features = [] }
|
||||
wasmtime-wasi-http = { version = "41.0.0", features = ["component-model-async"] }
|
||||
wasmtime-wasi-io = { version = "41.0.0", features = [] }
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,6 +3,7 @@ name = "trailbase-wasm"
|
|||
version = "0.5.0"
|
||||
edition = "2024"
|
||||
license = "OSL-3.0"
|
||||
rust-version = "1.93"
|
||||
description = "WASM runtime for the TrailBase framework"
|
||||
homepage = "https://trailbase.io"
|
||||
repository = "https://github.com/trailbaseio/trailbase"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use trailbase_sqlvalue::{Blob, DecodeError, SqlValue};
|
||||
use wstd::http::body::IntoBody;
|
||||
use wstd::http::{Client, Request};
|
||||
|
||||
use crate::wit::trailbase::database::sqlite::{
|
||||
tx_begin, tx_commit, tx_execute, tx_query, tx_rollback,
|
||||
execute as call_execute, query as call_query, tx_begin, tx_commit, tx_execute, tx_query,
|
||||
tx_rollback,
|
||||
};
|
||||
|
||||
pub use crate::wit::trailbase::database::sqlite::{TxError, Value};
|
||||
|
|
@ -62,84 +61,18 @@ pub async fn query(
|
|||
query: impl std::string::ToString,
|
||||
params: impl Into<Vec<Value>>,
|
||||
) -> Result<Vec<Vec<Value>>, Error> {
|
||||
let r = SqliteRequest {
|
||||
query: query.to_string(),
|
||||
params: params.into().into_iter().map(to_sql_value).collect(),
|
||||
};
|
||||
let request = Request::builder()
|
||||
.uri("http://__sqlite/query")
|
||||
.method("POST")
|
||||
.body(
|
||||
serde_json::to_vec(&r)
|
||||
.map_err(|_| Error::UnexpectedType)?
|
||||
.into_body(),
|
||||
)
|
||||
.map_err(|err| Error::Other(err.to_string()))?;
|
||||
|
||||
let client = Client::new();
|
||||
let (_parts, mut body) = client
|
||||
.send(request)
|
||||
return call_query(query.to_string(), params.into())
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))?
|
||||
.into_parts();
|
||||
|
||||
let bytes = body
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))?;
|
||||
|
||||
return match serde_json::from_slice(&bytes) {
|
||||
Ok(SqliteResponse::Query { rows }) => Ok(
|
||||
rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
row
|
||||
.into_iter()
|
||||
.map(from_sql_value)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
Ok(_) => Err(Error::UnexpectedType),
|
||||
Err(err) => Err(Error::Other(err.to_string())),
|
||||
};
|
||||
.map_err(|err| Error::Other(err.to_string()));
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
query: impl std::string::ToString,
|
||||
params: impl Into<Vec<Value>>,
|
||||
) -> Result<usize, Error> {
|
||||
let r = SqliteRequest {
|
||||
query: query.to_string(),
|
||||
params: params.into().into_iter().map(to_sql_value).collect(),
|
||||
};
|
||||
let request = Request::builder()
|
||||
.uri("http://__sqlite/execute")
|
||||
.method("POST")
|
||||
.body(
|
||||
serde_json::to_vec(&r)
|
||||
.map_err(|_| Error::UnexpectedType)?
|
||||
.into_body(),
|
||||
)
|
||||
.map_err(|err| Error::Other(err.to_string()))?;
|
||||
|
||||
let client = Client::new();
|
||||
let (_parts, mut body) = client
|
||||
.send(request)
|
||||
) -> Result<u64, Error> {
|
||||
return call_execute(query.to_string(), params.into())
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))?
|
||||
.into_parts();
|
||||
|
||||
let bytes = body
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))?;
|
||||
|
||||
return match serde_json::from_slice(&bytes) {
|
||||
Ok(SqliteResponse::Execute { rows_affected }) => Ok(rows_affected),
|
||||
Ok(_) => Err(Error::UnexpectedType),
|
||||
Err(err) => Err(Error::Other(err.to_string())),
|
||||
};
|
||||
.map_err(|err| Error::Other(err.to_string()));
|
||||
}
|
||||
|
||||
// fn from_json_value(value: serde_json::Value) -> Result<Value, Error> {
|
||||
|
|
@ -165,15 +98,15 @@ pub async fn execute(
|
|||
// };
|
||||
// }
|
||||
|
||||
fn from_sql_value(value: SqlValue) -> Result<Value, DecodeError> {
|
||||
return match value {
|
||||
SqlValue::Null => Ok(Value::Null),
|
||||
SqlValue::Integer(v) => Ok(Value::Integer(v)),
|
||||
SqlValue::Real(v) => Ok(Value::Real(v)),
|
||||
SqlValue::Text(v) => Ok(Value::Text(v)),
|
||||
SqlValue::Blob(v) => Ok(Value::Blob(v.into_bytes()?)),
|
||||
};
|
||||
}
|
||||
// fn from_sql_value(value: SqlValue) -> Result<Value, DecodeError> {
|
||||
// return match value {
|
||||
// SqlValue::Null => Ok(Value::Null),
|
||||
// SqlValue::Integer(v) => Ok(Value::Integer(v)),
|
||||
// SqlValue::Real(v) => Ok(Value::Real(v)),
|
||||
// SqlValue::Text(v) => Ok(Value::Text(v)),
|
||||
// SqlValue::Blob(v) => Ok(Value::Blob(v.into_bytes()?)),
|
||||
// };
|
||||
// }
|
||||
|
||||
// #[derive(Serialize)]
|
||||
// struct Blob {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ world interfaces {
|
|||
|
||||
// TrailBase's interfaces:
|
||||
@since(version = 0.1.0)
|
||||
include trailbase:database/interfaces@0.1.0;
|
||||
include trailbase:database/interfaces@0.1.1;
|
||||
|
||||
@since(version = 0.1.0)
|
||||
export init-endpoint;
|
||||
|
|
@ -38,7 +38,7 @@ world interfaces {
|
|||
world init {
|
||||
// TrailBase's interfaces:
|
||||
@since(version = 0.1.0)
|
||||
include trailbase:database/interfaces@0.1.0;
|
||||
include trailbase:database/interfaces@0.1.1;
|
||||
|
||||
@since(version = 0.1.0)
|
||||
export init-endpoint;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package trailbase:database@0.1.0;
|
||||
package trailbase:database@0.1.1;
|
||||
|
||||
interface sqlite {
|
||||
// WARNING: Evolving a variant currently breaks the ABI:
|
||||
|
|
@ -15,12 +15,10 @@ interface sqlite {
|
|||
real(f64),
|
||||
}
|
||||
|
||||
// NOTE: Ideally, we'd use these but they can currently block guests, w/o a
|
||||
// better non-blocking event loop.
|
||||
// @since(version = 0.1.0)
|
||||
// execute: func(query: string, params: list<value>) -> result<u64, tx-error>;
|
||||
// @since(version = 0.1.0)
|
||||
// query: func(query: string, params: list<value>) -> result<list<list<value>>, tx-error>;
|
||||
@since(version = 0.1.1)
|
||||
execute: async func(query: string, params: list<value>) -> result<u64, tx-error>;
|
||||
@since(version = 0.1.1)
|
||||
query: async func(query: string, params: list<value>) -> result<list<list<value>>, tx-error>;
|
||||
|
||||
// However, transactions have to be sync.
|
||||
@since(version = 0.1.0)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ name = "trailbase-wasm-runtime-host"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "OSL-3.0"
|
||||
rust-version = "1.93"
|
||||
description = "WASM runtime for the TrailBase framework"
|
||||
homepage = "https://trailbase.io"
|
||||
exclude = [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
|||
use trailbase_sqlite::{Params, Rows};
|
||||
use trailbase_wasi_keyvalue::WasiKeyValueCtx;
|
||||
use wasmtime::Result;
|
||||
use wasmtime::component::{HasData, ResourceTable};
|
||||
use wasmtime::component::{Accessor, HasData, ResourceTable};
|
||||
use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
|
||||
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
|
||||
use wasmtime_wasi_io::IoView;
|
||||
|
|
@ -40,6 +40,7 @@ wasmtime::component::bindgen!({
|
|||
// to return traps from generated functions.
|
||||
imports: {
|
||||
"trailbase:database/sqlite.tx-begin": async,
|
||||
"trailbase:database/sqlite.execute": async,
|
||||
},
|
||||
exports: {
|
||||
default: async | store | task_exit,
|
||||
|
|
@ -145,6 +146,52 @@ impl HasData for State {
|
|||
type Data<'a> = &'a mut State;
|
||||
}
|
||||
|
||||
impl self::trailbase::database::sqlite::HostWithStore for State {
|
||||
async fn execute<T>(
|
||||
accessor: &Accessor<T, Self>,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> Result<u64, TxError> {
|
||||
let Some(conn) = accessor.with(|mut a| a.get().shared.conn.clone()) else {
|
||||
return Err(TxError::Other("missing conn".to_string()));
|
||||
};
|
||||
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
return conn
|
||||
.execute(query, params)
|
||||
.await
|
||||
.map_err(|err| TxError::Other(err.to_string()))
|
||||
.map(|v| v as u64);
|
||||
}
|
||||
|
||||
async fn query<T>(
|
||||
accessor: &Accessor<T, Self>,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> Result<Vec<Vec<Value>>, TxError> {
|
||||
let Some(conn) = accessor.with(|mut a| a.get().shared.conn.clone()) else {
|
||||
return Err(TxError::Other("missing conn".to_string()));
|
||||
};
|
||||
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
let rows = conn
|
||||
.write_query_rows(query, params)
|
||||
.await
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
let values: Vec<_> = rows
|
||||
.into_iter()
|
||||
.map(|trailbase_sqlite::Row(row, _col)| {
|
||||
return row.into_iter().map(from_sqlite_value).collect::<Vec<_>>();
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Ok(values);
|
||||
}
|
||||
}
|
||||
|
||||
impl self::trailbase::database::sqlite::Host for State {
|
||||
fn tx_begin(&mut self) -> impl Future<Output = Result<(), TxError>> + Send {
|
||||
async fn begin(
|
||||
|
|
|
|||
|
|
@ -426,12 +426,14 @@ fn build_config(cache: Option<wasmtime::Cache>, use_winch: bool) -> Config {
|
|||
// calling synchronous bindings will panic.
|
||||
config.async_support(true);
|
||||
config.wasm_component_model(true);
|
||||
config.wasm_component_model_async(true);
|
||||
// config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
|
||||
|
||||
// Compilation settings.
|
||||
config.cache(cache);
|
||||
|
||||
if use_winch {
|
||||
// FIXME: ASYNC component model doesn't currently support winch;
|
||||
if false && use_winch {
|
||||
config.strategy(wasmtime::Strategy::Winch);
|
||||
} else {
|
||||
config.strategy(wasmtime::Strategy::Cranelift);
|
||||
|
|
|
|||
Loading…
Reference in a new issue